diff --git a/hlsdk/cl_dll/GameStudioModelRenderer.cpp b/hlsdk/cl_dll/GameStudioModelRenderer.cpp new file mode 100644 index 00000000..56e6823d --- /dev/null +++ b/hlsdk/cl_dll/GameStudioModelRenderer.cpp @@ -0,0 +1,119 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "com_model.h" +#include "studio.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "dlight.h" +#include "triangleapi.h" + +#include +#include +#include +#include + +#include "studio_util.h" +#include "r_studioint.h" + +#include "StudioModelRenderer.h" +#include "GameStudioModelRenderer.h" + +// +// Override the StudioModelRender virtual member functions here to implement custom bone +// setup, blending, etc. +// + +// Global engine <-> studio model rendering code interface +extern engine_studio_api_t IEngineStudio; + +// The renderer object, created on the stack. +CGameStudioModelRenderer g_StudioRenderer; +/* +==================== +CGameStudioModelRenderer + +==================== +*/ +CGameStudioModelRenderer::CGameStudioModelRenderer( void ) +{ +} + +//////////////////////////////////// +// Hooks to class implementation +//////////////////////////////////// + +/* +==================== +R_StudioDrawPlayer + +==================== +*/ +int R_StudioDrawPlayer( int flags, entity_state_t *pplayer ) +{ + return g_StudioRenderer.StudioDrawPlayer( flags, pplayer ); +} + +/* +==================== +R_StudioDrawModel + +==================== +*/ +int R_StudioDrawModel( int flags ) +{ + return g_StudioRenderer.StudioDrawModel( flags ); +} + +/* +==================== +R_StudioInit + +==================== +*/ +void R_StudioInit( void ) +{ + g_StudioRenderer.Init(); +} + +// The simple drawing interface we'll pass back to the engine +r_studio_interface_t studio = +{ + STUDIO_INTERFACE_VERSION, + R_StudioDrawModel, + R_StudioDrawPlayer, +}; + +/* +==================== +HUD_GetStudioModelInterface + +Export this function for the engine to use the studio renderer class to render objects. +==================== +*/ +#define DLLEXPORT __declspec( dllexport ) +extern "C" int DLLEXPORT HUD_GetStudioModelInterface( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio ) +{ + if ( version != STUDIO_INTERFACE_VERSION ) + return 0; + + // Point the engine to our callbacks + *ppinterface = &studio; + + // Copy in engine helper functions + memcpy( &IEngineStudio, pstudio, sizeof( IEngineStudio ) ); + + // Initialize local variables, etc. + R_StudioInit(); + + // Success + return 1; +} diff --git a/hlsdk/cl_dll/GameStudioModelRenderer.h b/hlsdk/cl_dll/GameStudioModelRenderer.h new file mode 100644 index 00000000..a9d2efff --- /dev/null +++ b/hlsdk/cl_dll/GameStudioModelRenderer.h @@ -0,0 +1,26 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( GAMESTUDIOMODELRENDERER_H ) +#define GAMESTUDIOMODELRENDERER_H +#if defined( _WIN32 ) +#pragma once +#endif + +/* +==================== +CGameStudioModelRenderer + +==================== +*/ +class CGameStudioModelRenderer : public CStudioModelRenderer +{ +public: + CGameStudioModelRenderer( void ); +}; + +#endif // GAMESTUDIOMODELRENDERER_H \ No newline at end of file diff --git a/hlsdk/cl_dll/GameStudioModelRenderer_Sample.cpp b/hlsdk/cl_dll/GameStudioModelRenderer_Sample.cpp new file mode 100644 index 00000000..5ea50b51 --- /dev/null +++ b/hlsdk/cl_dll/GameStudioModelRenderer_Sample.cpp @@ -0,0 +1,992 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "com_model.h" +#include "studio.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "dlight.h" +#include "triangleapi.h" + +#include +#include +#include +#include + +#include "studio_util.h" +#include "r_studioint.h" + +#include "StudioModelRenderer.h" +#include "GameStudioModelRenderer.h" + +// Predicted values saved off in hl_weapons.cpp +void Game_GetSequence( int *seq, int *gaitseq ); +void Game_GetOrientation( float *o, float *a ); + +float g_flStartScaleTime; +int iPrevRenderState; +int iRenderStateChanged; + +// Global engine <-> studio model rendering code interface +extern engine_studio_api_t IEngineStudio; + +typedef struct +{ + vec3_t origin; + vec3_t angles; + + vec3_t realangles; + + float animtime; + float frame; + int sequence; + int gaitsequence; + float framerate; + + int m_fSequenceLoops; + int m_fSequenceFinished; + + byte controller[ 4 ]; + byte blending[ 2 ]; + + latchedvars_t lv; +} client_anim_state_t; + +static client_anim_state_t g_state; +static client_anim_state_t g_clientstate; + +// The renderer object, created on the stack. +CGameStudioModelRenderer g_StudioRenderer; +/* +==================== +CGameStudioModelRenderer + +==================== +*/ +CGameStudioModelRenderer::CGameStudioModelRenderer( void ) +{ + // If you want to predict animations locally, set this to TRUE + // NOTE: The animation code is somewhat broken, but gives you a sense for how + // to do client side animation of the predicted player in a third person game. + m_bLocal = false; +} + +/* +==================== +StudioSetupBones + +==================== +*/ +void CGameStudioModelRenderer::StudioSetupBones ( void ) +{ + int i; + double f; + + mstudiobone_t *pbones; + mstudioseqdesc_t *pseqdesc; + mstudioanim_t *panim; + + static float pos[MAXSTUDIOBONES][3]; + static vec4_t q[MAXSTUDIOBONES]; + float bonematrix[3][4]; + + static float pos2[MAXSTUDIOBONES][3]; + static vec4_t q2[MAXSTUDIOBONES]; + static float pos3[MAXSTUDIOBONES][3]; + static vec4_t q3[MAXSTUDIOBONES]; + static float pos4[MAXSTUDIOBONES][3]; + static vec4_t q4[MAXSTUDIOBONES]; + + // Use default bone setup for nonplayers + if ( !m_pCurrentEntity->player ) + { + CStudioModelRenderer::StudioSetupBones(); + return; + } + + // Bound sequence number. + if ( m_pCurrentEntity->curstate.sequence >= m_pStudioHeader->numseq ) + { + m_pCurrentEntity->curstate.sequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + if ( m_pPlayerInfo && m_pPlayerInfo->gaitsequence != 0 ) + { + f = m_pPlayerInfo->gaitframe; + } + else + { + f = StudioEstimateFrame( pseqdesc ); + } + + // This game knows how to do three way blending + if ( pseqdesc->numblends == 3 ) + { + float s; + + // Get left anim + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + + // Blending is 0-127 == Left to Middle, 128 to 255 == Middle to right + if ( m_pCurrentEntity->curstate.blending[0] <= 127 ) + { + StudioCalcRotations( pos, q, pseqdesc, panim, f ); + + // Scale 0-127 blending up to 0-255 + s = m_pCurrentEntity->curstate.blending[0]; + s = ( s * 2.0 ); + } + else + { + + // Skip ahead to middle + panim += m_pStudioHeader->numbones; + + StudioCalcRotations( pos, q, pseqdesc, panim, f ); + + // Scale 127-255 blending up to 0-255 + s = m_pCurrentEntity->curstate.blending[0]; + s = 2.0 * ( s - 127.0 ); + } + + // Normalize interpolant + s /= 255.0; + + // Go to middle or right + panim += m_pStudioHeader->numbones; + + StudioCalcRotations( pos2, q2, pseqdesc, panim, f ); + + // Spherically interpolate the bones + StudioSlerpBones( q, pos, q2, pos2, s ); + } + else + { + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + StudioCalcRotations( pos, q, pseqdesc, panim, f ); + } + + // Are we in the process of transitioning from one sequence to another. + if ( m_fDoInterp && + m_pCurrentEntity->latched.sequencetime && + ( m_pCurrentEntity->latched.sequencetime + 0.2 > m_clTime ) && + ( m_pCurrentEntity->latched.prevsequence < m_pStudioHeader->numseq )) + { + // blend from last sequence + static float pos1b[MAXSTUDIOBONES][3]; + static vec4_t q1b[MAXSTUDIOBONES]; + float s; + + // Blending value into last sequence + unsigned char prevseqblending = m_pCurrentEntity->latched.prevseqblending[ 0 ]; + + // Point at previous sequenece + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->latched.prevsequence; + + // Know how to do three way blends + if ( pseqdesc->numblends == 3 ) + { + float s; + + // Get left animation + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + + if ( prevseqblending <= 127 ) + { + // Set up bones based on final frame of previous sequence + StudioCalcRotations( pos1b, q1b, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + s = prevseqblending; + s = ( s * 2.0 ); + } + else + { + // Skip to middle blend + panim += m_pStudioHeader->numbones; + + StudioCalcRotations( pos1b, q1b, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + s = prevseqblending; + s = 2.0 * ( s - 127.0 ); + } + + // Normalize + s /= 255.0; + + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos2, q2, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + // Interpolate bones + StudioSlerpBones( q1b, pos1b, q2, pos2, s ); + } + else + { + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + // clip prevframe + StudioCalcRotations( pos1b, q1b, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + } + + // Now blend last frame of previous sequence with current sequence. + s = 1.0 - (m_clTime - m_pCurrentEntity->latched.sequencetime) / 0.2; + StudioSlerpBones( q, pos, q1b, pos1b, s ); + } + else + { + m_pCurrentEntity->latched.prevframe = f; + } + + // Now convert quaternions and bone positions into matrices + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + QuaternionMatrix( q[i], bonematrix ); + + bonematrix[0][3] = pos[i][0]; + bonematrix[1][3] = pos[i][1]; + bonematrix[2][3] = pos[i][2]; + + if (pbones[i].parent == -1) + { + if ( IEngineStudio.IsHardware() ) + { + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + } + else + { + ConcatTransforms ((*m_paliastransform), bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + } + + // Apply client-side effects to the transformation matrix + StudioFxTransform( m_pCurrentEntity, (*m_pbonetransform)[i] ); + } + else + { + ConcatTransforms ((*m_pbonetransform)[pbones[i].parent], bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_plighttransform)[pbones[i].parent], bonematrix, (*m_plighttransform)[i]); + } + } +} + +/* +==================== +StudioEstimateGait + +==================== +*/ +void CGameStudioModelRenderer::StudioEstimateGait( entity_state_t *pplayer ) +{ + float dt; + vec3_t est_velocity; + + dt = (m_clTime - m_clOldTime); + dt = max( 0.0, dt ); + dt = min( 1.0, dt ); + + if (dt == 0 || m_pPlayerInfo->renderframe == m_nFrameCount) + { + m_flGaitMovement = 0; + return; + } + + // VectorAdd( pplayer->velocity, pplayer->prediction_error, est_velocity ); + if ( m_fGaitEstimation ) + { + VectorSubtract( m_pCurrentEntity->origin, m_pPlayerInfo->prevgaitorigin, est_velocity ); + VectorCopy( m_pCurrentEntity->origin, m_pPlayerInfo->prevgaitorigin ); + m_flGaitMovement = Length( est_velocity ); + if (dt <= 0 || m_flGaitMovement / dt < 5) + { + m_flGaitMovement = 0; + est_velocity[0] = 0; + est_velocity[1] = 0; + } + } + else + { + VectorCopy( pplayer->velocity, est_velocity ); + m_flGaitMovement = Length( est_velocity ) * dt; + } + + if (est_velocity[1] == 0 && est_velocity[0] == 0) + { + float flYawDiff = m_pCurrentEntity->angles[YAW] - m_pPlayerInfo->gaityaw; + flYawDiff = flYawDiff - (int)(flYawDiff / 360) * 360; + if (flYawDiff > 180) + flYawDiff -= 360; + if (flYawDiff < -180) + flYawDiff += 360; + + if (dt < 0.25) + flYawDiff *= dt * 4; + else + flYawDiff *= dt; + + m_pPlayerInfo->gaityaw += flYawDiff; + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - (int)(m_pPlayerInfo->gaityaw / 360) * 360; + + m_flGaitMovement = 0; + } + else + { + m_pPlayerInfo->gaityaw = (atan2(est_velocity[1], est_velocity[0]) * 180 / M_PI); + if (m_pPlayerInfo->gaityaw > 180) + m_pPlayerInfo->gaityaw = 180; + if (m_pPlayerInfo->gaityaw < -180) + m_pPlayerInfo->gaityaw = -180; + } + +} + +/* +==================== +StudioProcessGait + +==================== +*/ +void CGameStudioModelRenderer::StudioProcessGait( entity_state_t *pplayer ) +{ + mstudioseqdesc_t *pseqdesc; + float dt; + float flYaw; // view direction relative to movement + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + m_pCurrentEntity->angles[PITCH] = 0; + m_pCurrentEntity->latched.prevangles[PITCH] = m_pCurrentEntity->angles[PITCH]; + + dt = (m_clTime - m_clOldTime); + dt = max( 0.0, dt ); + dt = min( 1.0, dt ); + + StudioEstimateGait( pplayer ); + + // calc side to side turning + flYaw = m_pCurrentEntity->angles[YAW] - m_pPlayerInfo->gaityaw; + + flYaw = fmod( flYaw, 360.0 ); + + if (flYaw < -180) + { + flYaw = flYaw + 360; + } + else if (flYaw > 180) + { + flYaw = flYaw - 360; + } + + float maxyaw = 120.0; + + if (flYaw > maxyaw) + { + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - 180; + m_flGaitMovement = -m_flGaitMovement; + flYaw = flYaw - 180; + } + else if (flYaw < -maxyaw) + { + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw + 180; + m_flGaitMovement = -m_flGaitMovement; + flYaw = flYaw + 180; + } + + float blend_yaw = ( flYaw / 90.0 ) * 128.0 + 127.0; + blend_yaw = min( 255.0, blend_yaw ); + blend_yaw = max( 0.0, blend_yaw ); + + blend_yaw = 255.0 - blend_yaw; + + m_pCurrentEntity->curstate.blending[0] = (int)(blend_yaw); + m_pCurrentEntity->latched.prevblending[0] = m_pCurrentEntity->curstate.blending[0]; + m_pCurrentEntity->latched.prevseqblending[0] = m_pCurrentEntity->curstate.blending[0]; + + m_pCurrentEntity->angles[YAW] = m_pPlayerInfo->gaityaw; + if (m_pCurrentEntity->angles[YAW] < -0) + { + m_pCurrentEntity->angles[YAW] += 360; + } + m_pCurrentEntity->latched.prevangles[YAW] = m_pCurrentEntity->angles[YAW]; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + pplayer->gaitsequence; + + // Calc gait frame + if (pseqdesc->linearmovement[0] > 0) + { + m_pPlayerInfo->gaitframe += (m_flGaitMovement / pseqdesc->linearmovement[0]) * pseqdesc->numframes; + } + else + { + m_pPlayerInfo->gaitframe += pseqdesc->fps * dt * m_pCurrentEntity->curstate.framerate; + } + + // Do modulo + m_pPlayerInfo->gaitframe = m_pPlayerInfo->gaitframe - (int)(m_pPlayerInfo->gaitframe / pseqdesc->numframes) * pseqdesc->numframes; + if (m_pPlayerInfo->gaitframe < 0) + { + m_pPlayerInfo->gaitframe += pseqdesc->numframes; + } +} + +/* +============================== +SavePlayerState + +For local player, in third person, we need to store real render data and then + setup for with fake/client side animation data +============================== +*/ +void CGameStudioModelRenderer::SavePlayerState( entity_state_t *pplayer ) +{ + client_anim_state_t *st; + cl_entity_t *ent = IEngineStudio.GetCurrentEntity(); + assert( ent ); + if ( !ent ) + return; + + st = &g_state; + + st->angles = ent->curstate.angles; + st->origin = ent->curstate.origin; + + st->realangles = ent->angles; + + st->sequence = ent->curstate.sequence; + st->gaitsequence = pplayer->gaitsequence; + st->animtime = ent->curstate.animtime; + st->frame = ent->curstate.frame; + st->framerate = ent->curstate.framerate; + memcpy( st->blending, ent->curstate.blending, 2 ); + memcpy( st->controller, ent->curstate.controller, 4 ); + + st->lv = ent->latched; +} + +void GetSequenceInfo( void *pmodel, client_anim_state_t *pev, float *pflFrameRate, float *pflGroundSpeed ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return; + + mstudioseqdesc_t *pseqdesc; + + if (pev->sequence >= pstudiohdr->numseq) + { + *pflFrameRate = 0.0; + *pflGroundSpeed = 0.0; + return; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + if (pseqdesc->numframes > 1) + { + *pflFrameRate = 256 * pseqdesc->fps / (pseqdesc->numframes - 1); + *pflGroundSpeed = sqrt( pseqdesc->linearmovement[0]*pseqdesc->linearmovement[0]+ pseqdesc->linearmovement[1]*pseqdesc->linearmovement[1]+ pseqdesc->linearmovement[2]*pseqdesc->linearmovement[2] ); + *pflGroundSpeed = *pflGroundSpeed * pseqdesc->fps / (pseqdesc->numframes - 1); + } + else + { + *pflFrameRate = 256.0; + *pflGroundSpeed = 0.0; + } +} + +int GetSequenceFlags( void *pmodel, client_anim_state_t *pev ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || pev->sequence >= pstudiohdr->numseq ) + return 0; + + mstudioseqdesc_t *pseqdesc; + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + return pseqdesc->flags; +} + +float StudioFrameAdvance ( client_anim_state_t *st, float framerate, float flInterval ) +{ + if (flInterval == 0.0) + { + flInterval = (gEngfuncs.GetClientTime() - st->animtime); + if (flInterval <= 0.001) + { + st->animtime = gEngfuncs.GetClientTime(); + return 0.0; + } + } + if (!st->animtime) + flInterval = 0.0; + + st->frame += flInterval * framerate * st->framerate; + st->animtime = gEngfuncs.GetClientTime(); + + if (st->frame < 0.0 || st->frame >= 256.0) + { + if ( st->m_fSequenceLoops ) + st->frame -= (int)(st->frame / 256.0) * 256.0; + else + st->frame = (st->frame < 0.0) ? 0 : 255; + st->m_fSequenceFinished = TRUE; // just in case it wasn't caught in GetEvents + } + + return flInterval; +} + +/* +============================== +SetupClientAnimation + +Called to set up local player's animation values +============================== +*/ +void CGameStudioModelRenderer::SetupClientAnimation( entity_state_t *pplayer ) +{ + static double oldtime; + double curtime, dt; + + client_anim_state_t *st; + float fr, gs; + + cl_entity_t *ent = IEngineStudio.GetCurrentEntity(); + assert( ent ); + if ( !ent ) + return; + + curtime = gEngfuncs.GetClientTime(); + dt = curtime - oldtime; + dt = min( 1.0, max( 0.0, dt ) ); + + oldtime = curtime; + st = &g_clientstate; + + st->framerate = 1.0; + + int oldseq = st->sequence; + Game_GetSequence( &st->sequence, &st->gaitsequence ); //CVAR_GET_FLOAT( "sequence" ); + Game_GetOrientation( (float *)&st->origin, (float *)&st->angles ); + st->realangles = st->angles; + + if ( st->sequence != oldseq ) + { + st->frame = 0.0; + st->lv.prevsequence = oldseq; + st->lv.sequencetime = st->animtime; + + memcpy( st->lv.prevseqblending, st->blending, 2 ); + memcpy( st->lv.prevcontroller, st->controller, 4 ); + } + + void *pmodel = (studiohdr_t *)IEngineStudio.Mod_Extradata( ent->model ); + + GetSequenceInfo( pmodel, st, &fr, &gs ); + st->m_fSequenceLoops = ((GetSequenceFlags( pmodel, st ) & STUDIO_LOOPING) != 0); + StudioFrameAdvance( st, fr, dt ); + +// gEngfuncs.Con_Printf( "gs %i frame %f\n", st->gaitsequence, st->frame ); + + ent->angles = st->realangles; + ent->curstate.angles = st->angles; + ent->curstate.origin = st->origin; + + ent->curstate.sequence = st->sequence; + pplayer->gaitsequence = st->gaitsequence; + ent->curstate.animtime = st->animtime; + ent->curstate.frame = st->frame; + ent->curstate.framerate = st->framerate; + memcpy( ent->curstate.blending, st->blending, 2 ); + memcpy( ent->curstate.controller, st->controller, 4 ); + + ent->latched = st->lv; +} + +/* +============================== +RestorePlayerState + +Called to restore original player state information +============================== +*/ +void CGameStudioModelRenderer::RestorePlayerState( entity_state_t *pplayer ) +{ + client_anim_state_t *st; + cl_entity_t *ent = IEngineStudio.GetCurrentEntity(); + assert( ent ); + if ( !ent ) + return; + + st = &g_clientstate; + + st->angles = ent->curstate.angles; + st->origin = ent->curstate.origin; + st->realangles = ent->angles; + + st->sequence = ent->curstate.sequence; + st->gaitsequence = pplayer->gaitsequence; + st->animtime = ent->curstate.animtime; + st->frame = ent->curstate.frame; + st->framerate = ent->curstate.framerate; + memcpy( st->blending, ent->curstate.blending, 2 ); + memcpy( st->controller, ent->curstate.controller, 4 ); + + st->lv = ent->latched; + + st = &g_state; + + ent->curstate.angles = st->angles; + ent->curstate.origin = st->origin; + ent->angles = st->realangles; + + ent->curstate.sequence = st->sequence; + pplayer->gaitsequence = st->gaitsequence; + ent->curstate.animtime = st->animtime; + ent->curstate.frame = st->frame; + ent->curstate.framerate = st->framerate; + memcpy( ent->curstate.blending, st->blending, 2 ); + memcpy( ent->curstate.controller, st->controller, 4 ); + + ent->latched = st->lv; +} + +/* +============================== +StudioDrawPlayer + +============================== +*/ +int CGameStudioModelRenderer::StudioDrawPlayer( int flags, entity_state_t *pplayer ) +{ + int iret = 0; + + bool isLocalPlayer = false; + + // Set up for client? + if ( m_bLocal && IEngineStudio.GetCurrentEntity() == gEngfuncs.GetLocalPlayer() ) + { + isLocalPlayer = true; + } + + if ( isLocalPlayer ) + { + // Store original data + SavePlayerState( pplayer ); + + // Copy in client side animation data + SetupClientAnimation( pplayer ); + } + + // Call real draw function + iret = _StudioDrawPlayer( flags, pplayer ); + + // Restore for client? + if ( isLocalPlayer ) + { + // Restore the original data for the player + RestorePlayerState( pplayer ); + } + + return iret; +} + +/* +==================== +_StudioDrawPlayer + +==================== +*/ +int CGameStudioModelRenderer::_StudioDrawPlayer( int flags, entity_state_t *pplayer ) +{ + alight_t lighting; + vec3_t dir; + + m_pCurrentEntity = IEngineStudio.GetCurrentEntity(); + IEngineStudio.GetTimes( &m_nFrameCount, &m_clTime, &m_clOldTime ); + IEngineStudio.GetViewInfo( m_vRenderOrigin, m_vUp, m_vRight, m_vNormal ); + IEngineStudio.GetAliasScale( &m_fSoftwareXScale, &m_fSoftwareYScale ); + + m_nPlayerIndex = pplayer->number - 1; + + if (m_nPlayerIndex < 0 || m_nPlayerIndex >= gEngfuncs.GetMaxClients()) + return 0; + + m_pRenderModel = IEngineStudio.SetupPlayerModel( m_nPlayerIndex ); + if (m_pRenderModel == NULL) + return 0; + + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (m_pRenderModel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + IEngineStudio.SetRenderModel( m_pRenderModel ); + + if (pplayer->gaitsequence) + { + vec3_t orig_angles; + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + + VectorCopy( m_pCurrentEntity->angles, orig_angles ); + + StudioProcessGait( pplayer ); + + m_pPlayerInfo->gaitsequence = pplayer->gaitsequence; + m_pPlayerInfo = NULL; + + StudioSetUpTransform( 0 ); + VectorCopy( orig_angles, m_pCurrentEntity->angles ); + } + else + { + m_pCurrentEntity->curstate.controller[0] = 127; + m_pCurrentEntity->curstate.controller[1] = 127; + m_pCurrentEntity->curstate.controller[2] = 127; + m_pCurrentEntity->curstate.controller[3] = 127; + m_pCurrentEntity->latched.prevcontroller[0] = m_pCurrentEntity->curstate.controller[0]; + m_pCurrentEntity->latched.prevcontroller[1] = m_pCurrentEntity->curstate.controller[1]; + m_pCurrentEntity->latched.prevcontroller[2] = m_pCurrentEntity->curstate.controller[2]; + m_pCurrentEntity->latched.prevcontroller[3] = m_pCurrentEntity->curstate.controller[3]; + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + m_pPlayerInfo->gaitsequence = 0; + + StudioSetUpTransform( 0 ); + } + + if (flags & STUDIO_RENDER) + { + // see if the bounding box lets us trivially reject, also sets + if (!IEngineStudio.StudioCheckBBox ()) + return 0; + + (*m_pModelsDrawn)++; + (*m_pStudioModelCount)++; // render data cache cookie + + if (m_pStudioHeader->numbodyparts == 0) + return 1; + } + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + StudioSetupBones( ); + StudioSaveBones( ); + m_pPlayerInfo->renderframe = m_nFrameCount; + + m_pPlayerInfo = NULL; + + if (flags & STUDIO_EVENTS) + { + StudioCalcAttachments( ); + IEngineStudio.StudioClientEvents( ); + // copy attachments into global entity array + if ( m_pCurrentEntity->index > 0 ) + { + cl_entity_t *ent = gEngfuncs.GetEntityByIndex( m_pCurrentEntity->index ); + + memcpy( ent->attachment, m_pCurrentEntity->attachment, sizeof( vec3_t ) * 4 ); + } + } + + if (flags & STUDIO_RENDER) + { + /* + if (m_pCvarHiModels->value && m_pRenderModel != m_pCurrentEntity->model ) + { + // show highest resolution multiplayer model + m_pCurrentEntity->curstate.body = 255; + } + + if (!(m_pCvarDeveloper->value == 0 && gEngfuncs.GetMaxClients() == 1 ) && ( m_pRenderModel == m_pCurrentEntity->model ) ) + { + m_pCurrentEntity->curstate.body = 1; // force helmet + } + */ + + lighting.plightvec = dir; + IEngineStudio.StudioDynamicLight(m_pCurrentEntity, &lighting ); + + IEngineStudio.StudioEntityLight( &lighting ); + + // model and frame independant + IEngineStudio.StudioSetupLighting (&lighting); + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + + // get remap colors + m_nTopColor = m_pPlayerInfo->topcolor; + if (m_nTopColor < 0) + m_nTopColor = 0; + if (m_nTopColor > 360) + m_nTopColor = 360; + m_nBottomColor = m_pPlayerInfo->bottomcolor; + if (m_nBottomColor < 0) + m_nBottomColor = 0; + if (m_nBottomColor > 360) + m_nBottomColor = 360; + + IEngineStudio.StudioSetRemapColors( m_nTopColor, m_nBottomColor ); + + StudioRenderModel( ); + m_pPlayerInfo = NULL; + + if (pplayer->weaponmodel) + { + cl_entity_t saveent = *m_pCurrentEntity; + + model_t *pweaponmodel = IEngineStudio.GetModelByIndex( pplayer->weaponmodel ); + + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (pweaponmodel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + + StudioMergeBones( pweaponmodel); + + IEngineStudio.StudioSetupLighting (&lighting); + + StudioRenderModel( ); + + StudioCalcAttachments( ); + + *m_pCurrentEntity = saveent; + } + } + + return 1; +} + +/* +==================== +Studio_FxTransform + +==================== +*/ +void CGameStudioModelRenderer::StudioFxTransform( cl_entity_t *ent, float transform[3][4] ) +{ + switch( ent->curstate.renderfx ) + { + case kRenderFxDistort: + case kRenderFxHologram: + if ( gEngfuncs.pfnRandomLong(0,49) == 0 ) + { + int axis = gEngfuncs.pfnRandomLong(0,1); + if ( axis == 1 ) // Choose between x & z + axis = 2; + VectorScale( transform[axis], gEngfuncs.pfnRandomFloat(1,1.484), transform[axis] ); + } + else if ( gEngfuncs.pfnRandomLong(0,49) == 0 ) + { + float offset; + int axis = gEngfuncs.pfnRandomLong(0,1); + if ( axis == 1 ) // Choose between x & z + axis = 2; + offset = gEngfuncs.pfnRandomFloat(-10,10); + transform[gEngfuncs.pfnRandomLong(0,2)][3] += offset; + } + break; + case kRenderFxExplode: + { + if ( iRenderStateChanged ) + { + g_flStartScaleTime = m_clTime; + iRenderStateChanged = FALSE; + } + + // Make the Model continue to shrink + float flTimeDelta = m_clTime - g_flStartScaleTime; + if ( flTimeDelta > 0 ) + { + float flScale = 0.001; + // Goes almost all away + if ( flTimeDelta <= 2.0 ) + flScale = 1.0 - (flTimeDelta / 2.0); + + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + transform[i][j] *= flScale; + } + } + } + break; + } +} + +//////////////////////////////////// +// Hooks to class implementation +//////////////////////////////////// + +/* +==================== +R_StudioDrawPlayer + +==================== +*/ +int R_StudioDrawPlayer( int flags, entity_state_t *pplayer ) +{ + return g_StudioRenderer.StudioDrawPlayer( flags, pplayer ); +} + +/* +==================== +R_StudioDrawModel + +==================== +*/ +int R_StudioDrawModel( int flags ) +{ + return g_StudioRenderer.StudioDrawModel( flags ); +} + +/* +==================== +R_StudioInit + +==================== +*/ +void R_StudioInit( void ) +{ + g_StudioRenderer.Init(); +} + +// The simple drawing interface we'll pass back to the engine +r_studio_interface_t studio = +{ + STUDIO_INTERFACE_VERSION, + R_StudioDrawModel, + R_StudioDrawPlayer, +}; + +/* +==================== +HUD_GetStudioModelInterface + +Export this function for the engine to use the studio renderer class to render objects. +==================== +*/ +#define DLLEXPORT __declspec( dllexport ) +extern "C" int DLLEXPORT HUD_GetStudioModelInterface( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio ) +{ + if ( version != STUDIO_INTERFACE_VERSION ) + return 0; + + // Point the engine to our callbacks + *ppinterface = &studio; + + // Copy in engine helper functions + memcpy( &IEngineStudio, pstudio, sizeof( IEngineStudio ) ); + + // Initialize local variables, etc. + R_StudioInit(); + + // Success + return 1; +} diff --git a/hlsdk/cl_dll/GameStudioModelRenderer_Sample.h b/hlsdk/cl_dll/GameStudioModelRenderer_Sample.h new file mode 100644 index 00000000..bf5cc73f --- /dev/null +++ b/hlsdk/cl_dll/GameStudioModelRenderer_Sample.h @@ -0,0 +1,55 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( GAMESTUDIOMODELRENDERER_H ) +#define GAMESTUDIOMODELRENDERER_H +#if defined( _WIN32 ) +#pragma once +#endif + +/* +==================== +CGameStudioModelRenderer + +==================== +*/ +class CGameStudioModelRenderer : public CStudioModelRenderer +{ +public: + CGameStudioModelRenderer( void ); + + // Set up model bone positions + virtual void StudioSetupBones ( void ); + + // Estimate gait frame for player + virtual void StudioEstimateGait ( entity_state_t *pplayer ); + + // Process movement of player + virtual void StudioProcessGait ( entity_state_t *pplayer ); + + // Player drawing code + virtual int StudioDrawPlayer( int flags, entity_state_t *pplayer ); + virtual int _StudioDrawPlayer( int flags, entity_state_t *pplayer ); + + // Apply special effects to transform matrix + virtual void StudioFxTransform( cl_entity_t *ent, float transform[3][4] ); + +private: + // For local player, in third person, we need to store real render data and then + // setup for with fake/client side animation data + void SavePlayerState( entity_state_t *pplayer ); + // Called to set up local player's animation values + void SetupClientAnimation( entity_state_t *pplayer ); + // Called to restore original player state information + void RestorePlayerState( entity_state_t *pplayer ); + +private: + // Private data + bool m_bLocal; +}; + +#endif // GAMESTUDIOMODELRENDERER_H \ No newline at end of file diff --git a/hlsdk/cl_dll/StudioModelRenderer.cpp b/hlsdk/cl_dll/StudioModelRenderer.cpp new file mode 100644 index 00000000..e051fa95 --- /dev/null +++ b/hlsdk/cl_dll/StudioModelRenderer.cpp @@ -0,0 +1,1677 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// studio_model.cpp +// routines for setting up to draw 3DStudio models + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "com_model.h" +#include "studio.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "dlight.h" +#include "triangleapi.h" + +#include +#include +#include +#include + +#include "studio_util.h" +#include "r_studioint.h" + +#include "StudioModelRenderer.h" +#include "GameStudioModelRenderer.h" + +// Global engine <-> studio model rendering code interface +engine_studio_api_t IEngineStudio; + +///////////////////// +// Implementation of CStudioModelRenderer.h + +/* +==================== +Init + +==================== +*/ +void CStudioModelRenderer::Init( void ) +{ + // Set up some variables shared with engine + m_pCvarHiModels = IEngineStudio.GetCvar( "cl_himodels" ); + m_pCvarDeveloper = IEngineStudio.GetCvar( "developer" ); + m_pCvarDrawEntities = IEngineStudio.GetCvar( "r_drawentities" ); + + m_pChromeSprite = IEngineStudio.GetChromeSprite(); + + IEngineStudio.GetModelCounters( &m_pStudioModelCount, &m_pModelsDrawn ); + + // Get pointers to engine data structures + m_pbonetransform = (float (*)[MAXSTUDIOBONES][3][4])IEngineStudio.StudioGetBoneTransform(); + m_plighttransform = (float (*)[MAXSTUDIOBONES][3][4])IEngineStudio.StudioGetLightTransform(); + m_paliastransform = (float (*)[3][4])IEngineStudio.StudioGetAliasTransform(); + m_protationmatrix = (float (*)[3][4])IEngineStudio.StudioGetRotationMatrix(); +} + +/* +==================== +CStudioModelRenderer + +==================== +*/ +CStudioModelRenderer::CStudioModelRenderer( void ) +{ + m_fDoInterp = 1; + m_fGaitEstimation = 1; + m_pCurrentEntity = NULL; + m_pCvarHiModels = NULL; + m_pCvarDeveloper = NULL; + m_pCvarDrawEntities = NULL; + m_pChromeSprite = NULL; + m_pStudioModelCount = NULL; + m_pModelsDrawn = NULL; + m_protationmatrix = NULL; + m_paliastransform = NULL; + m_pbonetransform = NULL; + m_plighttransform = NULL; + m_pStudioHeader = NULL; + m_pBodyPart = NULL; + m_pSubModel = NULL; + m_pPlayerInfo = NULL; + m_pRenderModel = NULL; +} + +/* +==================== +~CStudioModelRenderer + +==================== +*/ +CStudioModelRenderer::~CStudioModelRenderer( void ) +{ +} + +/* +==================== +StudioCalcBoneAdj + +==================== +*/ +void CStudioModelRenderer::StudioCalcBoneAdj( float dadt, float *adj, const byte *pcontroller1, const byte *pcontroller2, byte mouthopen ) +{ + int i, j; + float value; + mstudiobonecontroller_t *pbonecontroller; + + pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bonecontrollerindex); + + for (j = 0; j < m_pStudioHeader->numbonecontrollers; j++) + { + i = pbonecontroller[j].index; + if (i <= 3) + { + // check for 360% wrapping + if (pbonecontroller[j].type & STUDIO_RLOOP) + { + if (abs(pcontroller1[i] - pcontroller2[i]) > 128) + { + int a, b; + a = (pcontroller1[j] + 128) % 256; + b = (pcontroller2[j] + 128) % 256; + value = ((a * dadt) + (b * (1 - dadt)) - 128) * (360.0/256.0) + pbonecontroller[j].start; + } + else + { + value = ((pcontroller1[i] * dadt + (pcontroller2[i]) * (1.0 - dadt))) * (360.0/256.0) + pbonecontroller[j].start; + } + } + else + { + value = (pcontroller1[i] * dadt + pcontroller2[i] * (1.0 - dadt)) / 255.0; + if (value < 0) value = 0; + if (value > 1.0) value = 1.0; + value = (1.0 - value) * pbonecontroller[j].start + value * pbonecontroller[j].end; + } + // Con_DPrintf( "%d %d %f : %f\n", m_pCurrentEntity->curstate.controller[j], m_pCurrentEntity->latched.prevcontroller[j], value, dadt ); + } + else + { + value = mouthopen / 64.0; + if (value > 1.0) value = 1.0; + value = (1.0 - value) * pbonecontroller[j].start + value * pbonecontroller[j].end; + // Con_DPrintf("%d %f\n", mouthopen, value ); + } + switch(pbonecontroller[j].type & STUDIO_TYPES) + { + case STUDIO_XR: + case STUDIO_YR: + case STUDIO_ZR: + adj[j] = value * (M_PI / 180.0); + break; + case STUDIO_X: + case STUDIO_Y: + case STUDIO_Z: + adj[j] = value; + break; + } + } +} + + +/* +==================== +StudioCalcBoneQuaterion + +==================== +*/ +void CStudioModelRenderer::StudioCalcBoneQuaterion( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *q ) +{ + int j, k; + vec4_t q1, q2; + vec3_t angle1, angle2; + mstudioanimvalue_t *panimvalue; + + for (j = 0; j < 3; j++) + { + if (panim->offset[j+3] == 0) + { + angle2[j] = angle1[j] = pbone->value[j+3]; // default; + } + else + { + panimvalue = (mstudioanimvalue_t *)((byte *)panim + panim->offset[j+3]); + k = frame; + // DEBUG + if (panimvalue->num.total < panimvalue->num.valid) + k = 0; + while (panimvalue->num.total <= k) + { + k -= panimvalue->num.total; + panimvalue += panimvalue->num.valid + 1; + // DEBUG + if (panimvalue->num.total < panimvalue->num.valid) + k = 0; + } + // Bah, missing blend! + if (panimvalue->num.valid > k) + { + angle1[j] = panimvalue[k+1].value; + + if (panimvalue->num.valid > k + 1) + { + angle2[j] = panimvalue[k+2].value; + } + else + { + if (panimvalue->num.total > k + 1) + angle2[j] = angle1[j]; + else + angle2[j] = panimvalue[panimvalue->num.valid+2].value; + } + } + else + { + angle1[j] = panimvalue[panimvalue->num.valid].value; + if (panimvalue->num.total > k + 1) + { + angle2[j] = angle1[j]; + } + else + { + angle2[j] = panimvalue[panimvalue->num.valid + 2].value; + } + } + angle1[j] = pbone->value[j+3] + angle1[j] * pbone->scale[j+3]; + angle2[j] = pbone->value[j+3] + angle2[j] * pbone->scale[j+3]; + } + + if (pbone->bonecontroller[j+3] != -1) + { + angle1[j] += adj[pbone->bonecontroller[j+3]]; + angle2[j] += adj[pbone->bonecontroller[j+3]]; + } + } + + if (!VectorCompare( angle1, angle2 )) + { + AngleQuaternion( angle1, q1 ); + AngleQuaternion( angle2, q2 ); + QuaternionSlerp( q1, q2, s, q ); + } + else + { + AngleQuaternion( angle1, q ); + } +} + +/* +==================== +StudioCalcBonePosition + +==================== +*/ +void CStudioModelRenderer::StudioCalcBonePosition( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *pos ) +{ + int j, k; + mstudioanimvalue_t *panimvalue; + + for (j = 0; j < 3; j++) + { + pos[j] = pbone->value[j]; // default; + if (panim->offset[j] != 0) + { + panimvalue = (mstudioanimvalue_t *)((byte *)panim + panim->offset[j]); + /* + if (i == 0 && j == 0) + Con_DPrintf("%d %d:%d %f\n", frame, panimvalue->num.valid, panimvalue->num.total, s ); + */ + + k = frame; + // DEBUG + if (panimvalue->num.total < panimvalue->num.valid) + k = 0; + // find span of values that includes the frame we want + while (panimvalue->num.total <= k) + { + k -= panimvalue->num.total; + panimvalue += panimvalue->num.valid + 1; + // DEBUG + if (panimvalue->num.total < panimvalue->num.valid) + k = 0; + } + // if we're inside the span + if (panimvalue->num.valid > k) + { + // and there's more data in the span + if (panimvalue->num.valid > k + 1) + { + pos[j] += (panimvalue[k+1].value * (1.0 - s) + s * panimvalue[k+2].value) * pbone->scale[j]; + } + else + { + pos[j] += panimvalue[k+1].value * pbone->scale[j]; + } + } + else + { + // are we at the end of the repeating values section and there's another section with data? + if (panimvalue->num.total <= k + 1) + { + pos[j] += (panimvalue[panimvalue->num.valid].value * (1.0 - s) + s * panimvalue[panimvalue->num.valid + 2].value) * pbone->scale[j]; + } + else + { + pos[j] += panimvalue[panimvalue->num.valid].value * pbone->scale[j]; + } + } + } + if ( pbone->bonecontroller[j] != -1 && adj ) + { + pos[j] += adj[pbone->bonecontroller[j]]; + } + } +} + +/* +==================== +StudioSlerpBones + +==================== +*/ +void CStudioModelRenderer::StudioSlerpBones( vec4_t q1[], float pos1[][3], vec4_t q2[], float pos2[][3], float s ) +{ + int i; + vec4_t q3; + float s1; + + if (s < 0) s = 0; + else if (s > 1.0) s = 1.0; + + s1 = 1.0 - s; + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + QuaternionSlerp( q1[i], q2[i], s, q3 ); + q1[i][0] = q3[0]; + q1[i][1] = q3[1]; + q1[i][2] = q3[2]; + q1[i][3] = q3[3]; + pos1[i][0] = pos1[i][0] * s1 + pos2[i][0] * s; + pos1[i][1] = pos1[i][1] * s1 + pos2[i][1] * s; + pos1[i][2] = pos1[i][2] * s1 + pos2[i][2] * s; + } +} + +/* +==================== +StudioGetAnim + +==================== +*/ +mstudioanim_t *CStudioModelRenderer::StudioGetAnim( model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc ) +{ + mstudioseqgroup_t *pseqgroup; + cache_user_t *paSequences; + + pseqgroup = (mstudioseqgroup_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqgroupindex) + pseqdesc->seqgroup; + + if (pseqdesc->seqgroup == 0) + { + return (mstudioanim_t *)((byte *)m_pStudioHeader + pseqgroup->data + pseqdesc->animindex); + } + + paSequences = (cache_user_t *)m_pSubModel->submodels; + + if (paSequences == NULL) + { + paSequences = (cache_user_t *)IEngineStudio.Mem_Calloc( 16, sizeof( cache_user_t ) ); // UNDONE: leak! + m_pSubModel->submodels = (dmodel_t *)paSequences; + } + + if (!IEngineStudio.Cache_Check( (struct cache_user_s *)&(paSequences[pseqdesc->seqgroup]))) + { + gEngfuncs.Con_DPrintf("loading %s\n", pseqgroup->name ); + IEngineStudio.LoadCacheFile( pseqgroup->name, (struct cache_user_s *)&paSequences[pseqdesc->seqgroup] ); + } + return (mstudioanim_t *)((byte *)paSequences[pseqdesc->seqgroup].data + pseqdesc->animindex); +} + +/* +==================== +StudioPlayerBlend + +==================== +*/ +void CStudioModelRenderer::StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch ) +{ + // calc up/down pointing + *pBlend = (*pPitch * 3); + if (*pBlend < pseqdesc->blendstart[0]) + { + *pPitch -= pseqdesc->blendstart[0] / 3.0; + *pBlend = 0; + } + else if (*pBlend > pseqdesc->blendend[0]) + { + *pPitch -= pseqdesc->blendend[0] / 3.0; + *pBlend = 255; + } + else + { + if (pseqdesc->blendend[0] - pseqdesc->blendstart[0] < 0.1) // catch qc error + *pBlend = 127; + else + *pBlend = 255 * (*pBlend - pseqdesc->blendstart[0]) / (pseqdesc->blendend[0] - pseqdesc->blendstart[0]); + *pPitch = 0; + } +} + +/* +==================== +StudioSetUpTransform + +==================== +*/ +void CStudioModelRenderer::StudioSetUpTransform (int trivial_accept) +{ + int i; + vec3_t angles; + vec3_t modelpos; + +// tweek model origin + //for (i = 0; i < 3; i++) + // modelpos[i] = m_pCurrentEntity->origin[i]; + + VectorCopy( m_pCurrentEntity->origin, modelpos ); + +// TODO: should really be stored with the entity instead of being reconstructed +// TODO: should use a look-up table +// TODO: could cache lazily, stored in the entity + angles[ROLL] = m_pCurrentEntity->curstate.angles[ROLL]; + angles[PITCH] = m_pCurrentEntity->curstate.angles[PITCH]; + angles[YAW] = m_pCurrentEntity->curstate.angles[YAW]; + + //Con_DPrintf("Angles %4.2f prev %4.2f for %i\n", angles[PITCH], m_pCurrentEntity->index); + //Con_DPrintf("movetype %d %d\n", m_pCurrentEntity->movetype, m_pCurrentEntity->aiment ); + if (m_pCurrentEntity->curstate.movetype == MOVETYPE_STEP) + { + float f = 0; + float d; + + // don't do it if the goalstarttime hasn't updated in a while. + + // NOTE: Because we need to interpolate multiplayer characters, the interpolation time limit + // was increased to 1.0 s., which is 2x the max lag we are accounting for. + + if ( ( m_clTime < m_pCurrentEntity->curstate.animtime + 1.0f ) && + ( m_pCurrentEntity->curstate.animtime != m_pCurrentEntity->latched.prevanimtime ) ) + { + f = (m_clTime - m_pCurrentEntity->curstate.animtime) / (m_pCurrentEntity->curstate.animtime - m_pCurrentEntity->latched.prevanimtime); + //Con_DPrintf("%4.2f %.2f %.2f\n", f, m_pCurrentEntity->curstate.animtime, m_clTime); + } + + if (m_fDoInterp) + { + // ugly hack to interpolate angle, position. current is reached 0.1 seconds after being set + f = f - 1.0; + } + else + { + f = 0; + } + + for (i = 0; i < 3; i++) + { + modelpos[i] += (m_pCurrentEntity->origin[i] - m_pCurrentEntity->latched.prevorigin[i]) * f; + } + + // NOTE: Because multiplayer lag can be relatively large, we don't want to cap + // f at 1.5 anymore. + //if (f > -1.0 && f < 1.5) {} + +// Con_DPrintf("%.0f %.0f\n",m_pCurrentEntity->msg_angles[0][YAW], m_pCurrentEntity->msg_angles[1][YAW] ); + for (i = 0; i < 3; i++) + { + float ang1, ang2; + + ang1 = m_pCurrentEntity->angles[i]; + ang2 = m_pCurrentEntity->latched.prevangles[i]; + + d = ang1 - ang2; + if (d > 180) + { + d -= 360; + } + else if (d < -180) + { + d += 360; + } + + angles[i] += d * f; + } + //Con_DPrintf("%.3f \n", f ); + } + else if ( m_pCurrentEntity->curstate.movetype != MOVETYPE_NONE ) + { + VectorCopy( m_pCurrentEntity->angles, angles ); + } + + //Con_DPrintf("%.0f %0.f %0.f\n", modelpos[0], modelpos[1], modelpos[2] ); + //Con_DPrintf("%.0f %0.f %0.f\n", angles[0], angles[1], angles[2] ); + + angles[PITCH] = -angles[PITCH]; + AngleMatrix (angles, (*m_protationmatrix)); + + if ( !IEngineStudio.IsHardware() ) + { + static float viewmatrix[3][4]; + + VectorCopy (m_vRight, viewmatrix[0]); + VectorCopy (m_vUp, viewmatrix[1]); + VectorInverse (viewmatrix[1]); + VectorCopy (m_vNormal, viewmatrix[2]); + + (*m_protationmatrix)[0][3] = modelpos[0] - m_vRenderOrigin[0]; + (*m_protationmatrix)[1][3] = modelpos[1] - m_vRenderOrigin[1]; + (*m_protationmatrix)[2][3] = modelpos[2] - m_vRenderOrigin[2]; + + ConcatTransforms (viewmatrix, (*m_protationmatrix), (*m_paliastransform)); + + // do the scaling up of x and y to screen coordinates as part of the transform + // for the unclipped case (it would mess up clipping in the clipped case). + // Also scale down z, so 1/z is scaled 31 bits for free, and scale down x and y + // correspondingly so the projected x and y come out right + // FIXME: make this work for clipped case too? + if (trivial_accept) + { + for (i=0 ; i<4 ; i++) + { + (*m_paliastransform)[0][i] *= m_fSoftwareXScale * + (1.0 / (ZISCALE * 0x10000)); + (*m_paliastransform)[1][i] *= m_fSoftwareYScale * + (1.0 / (ZISCALE * 0x10000)); + (*m_paliastransform)[2][i] *= 1.0 / (ZISCALE * 0x10000); + + } + } + } + + (*m_protationmatrix)[0][3] = modelpos[0]; + (*m_protationmatrix)[1][3] = modelpos[1]; + (*m_protationmatrix)[2][3] = modelpos[2]; +} + + +/* +==================== +StudioEstimateInterpolant + +==================== +*/ +float CStudioModelRenderer::StudioEstimateInterpolant( void ) +{ + float dadt = 1.0; + + if ( m_fDoInterp && ( m_pCurrentEntity->curstate.animtime >= m_pCurrentEntity->latched.prevanimtime + 0.01 ) ) + { + dadt = (m_clTime - m_pCurrentEntity->curstate.animtime) / 0.1; + if (dadt > 2.0) + { + dadt = 2.0; + } + } + return dadt; +} + +/* +==================== +StudioCalcRotations + +==================== +*/ +void CStudioModelRenderer::StudioCalcRotations ( float pos[][3], vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f ) +{ + int i; + int frame; + mstudiobone_t *pbone; + + float s; + float adj[MAXSTUDIOCONTROLLERS]; + float dadt; + + if (f > pseqdesc->numframes - 1) + { + f = 0; // bah, fix this bug with changing sequences too fast + } + // BUG ( somewhere else ) but this code should validate this data. + // This could cause a crash if the frame # is negative, so we'll go ahead + // and clamp it here + else if ( f < -0.01 ) + { + f = -0.01; + } + + frame = (int)f; + + // Con_DPrintf("%d %.4f %.4f %.4f %.4f %d\n", m_pCurrentEntity->curstate.sequence, m_clTime, m_pCurrentEntity->animtime, m_pCurrentEntity->frame, f, frame ); + + // Con_DPrintf( "%f %f %f\n", m_pCurrentEntity->angles[ROLL], m_pCurrentEntity->angles[PITCH], m_pCurrentEntity->angles[YAW] ); + + // Con_DPrintf("frame %d %d\n", frame1, frame2 ); + + + dadt = StudioEstimateInterpolant( ); + s = (f - frame); + + // add in programtic controllers + pbone = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + StudioCalcBoneAdj( dadt, adj, m_pCurrentEntity->curstate.controller, m_pCurrentEntity->latched.prevcontroller, m_pCurrentEntity->mouth.mouthopen ); + + for (i = 0; i < m_pStudioHeader->numbones; i++, pbone++, panim++) + { + StudioCalcBoneQuaterion( frame, s, pbone, panim, adj, q[i] ); + + StudioCalcBonePosition( frame, s, pbone, panim, adj, pos[i] ); + // if (0 && i == 0) + // Con_DPrintf("%d %d %d %d\n", m_pCurrentEntity->curstate.sequence, frame, j, k ); + } + + if (pseqdesc->motiontype & STUDIO_X) + { + pos[pseqdesc->motionbone][0] = 0.0; + } + if (pseqdesc->motiontype & STUDIO_Y) + { + pos[pseqdesc->motionbone][1] = 0.0; + } + if (pseqdesc->motiontype & STUDIO_Z) + { + pos[pseqdesc->motionbone][2] = 0.0; + } + + s = 0 * ((1.0 - (f - (int)(f))) / (pseqdesc->numframes)) * m_pCurrentEntity->curstate.framerate; + + if (pseqdesc->motiontype & STUDIO_LX) + { + pos[pseqdesc->motionbone][0] += s * pseqdesc->linearmovement[0]; + } + if (pseqdesc->motiontype & STUDIO_LY) + { + pos[pseqdesc->motionbone][1] += s * pseqdesc->linearmovement[1]; + } + if (pseqdesc->motiontype & STUDIO_LZ) + { + pos[pseqdesc->motionbone][2] += s * pseqdesc->linearmovement[2]; + } +} + +/* +==================== +Studio_FxTransform + +==================== +*/ +void CStudioModelRenderer::StudioFxTransform( cl_entity_t *ent, float transform[3][4] ) +{ + switch( ent->curstate.renderfx ) + { + case kRenderFxDistort: + case kRenderFxHologram: + if ( gEngfuncs.pfnRandomLong(0,49) == 0 ) + { + int axis = gEngfuncs.pfnRandomLong(0,1); + if ( axis == 1 ) // Choose between x & z + axis = 2; + VectorScale( transform[axis], gEngfuncs.pfnRandomFloat(1,1.484), transform[axis] ); + } + else if ( gEngfuncs.pfnRandomLong(0,49) == 0 ) + { + float offset; + int axis = gEngfuncs.pfnRandomLong(0,1); + if ( axis == 1 ) // Choose between x & z + axis = 2; + offset = gEngfuncs.pfnRandomFloat(-10,10); + transform[gEngfuncs.pfnRandomLong(0,2)][3] += offset; + } + break; + case kRenderFxExplode: + { + float scale; + + scale = 1.0 + ( m_clTime - ent->curstate.animtime) * 10.0; + if ( scale > 2 ) // Don't blow up more than 200% + scale = 2; + transform[0][1] *= scale; + transform[1][1] *= scale; + transform[2][1] *= scale; + } + break; + + } +} + +/* +==================== +StudioEstimateFrame + +==================== +*/ +float CStudioModelRenderer::StudioEstimateFrame( mstudioseqdesc_t *pseqdesc ) +{ + double dfdt, f; + + if ( m_fDoInterp ) + { + if ( m_clTime < m_pCurrentEntity->curstate.animtime ) + { + dfdt = 0; + } + else + { + dfdt = (m_clTime - m_pCurrentEntity->curstate.animtime) * m_pCurrentEntity->curstate.framerate * pseqdesc->fps; + + } + } + else + { + dfdt = 0; + } + + if (pseqdesc->numframes <= 1) + { + f = 0; + } + else + { + f = (m_pCurrentEntity->curstate.frame * (pseqdesc->numframes - 1)) / 256.0; + } + + f += dfdt; + + if (pseqdesc->flags & STUDIO_LOOPING) + { + if (pseqdesc->numframes > 1) + { + f -= (int)(f / (pseqdesc->numframes - 1)) * (pseqdesc->numframes - 1); + } + if (f < 0) + { + f += (pseqdesc->numframes - 1); + } + } + else + { + if (f >= pseqdesc->numframes - 1.001) + { + f = pseqdesc->numframes - 1.001; + } + if (f < 0.0) + { + f = 0.0; + } + } + return f; +} + +/* +==================== +StudioSetupBones + +==================== +*/ +void CStudioModelRenderer::StudioSetupBones ( void ) +{ + int i; + double f; + + mstudiobone_t *pbones; + mstudioseqdesc_t *pseqdesc; + mstudioanim_t *panim; + + static float pos[MAXSTUDIOBONES][3]; + static vec4_t q[MAXSTUDIOBONES]; + float bonematrix[3][4]; + + static float pos2[MAXSTUDIOBONES][3]; + static vec4_t q2[MAXSTUDIOBONES]; + static float pos3[MAXSTUDIOBONES][3]; + static vec4_t q3[MAXSTUDIOBONES]; + static float pos4[MAXSTUDIOBONES][3]; + static vec4_t q4[MAXSTUDIOBONES]; + + if (m_pCurrentEntity->curstate.sequence >= m_pStudioHeader->numseq) + { + m_pCurrentEntity->curstate.sequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + f = StudioEstimateFrame( pseqdesc ); + + if (m_pCurrentEntity->latched.prevframe > f) + { + //Con_DPrintf("%f %f\n", m_pCurrentEntity->prevframe, f ); + } + + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + StudioCalcRotations( pos, q, pseqdesc, panim, f ); + + if (pseqdesc->numblends > 1) + { + float s; + float dadt; + + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos2, q2, pseqdesc, panim, f ); + + dadt = StudioEstimateInterpolant(); + s = (m_pCurrentEntity->curstate.blending[0] * dadt + m_pCurrentEntity->latched.prevblending[0] * (1.0 - dadt)) / 255.0; + + StudioSlerpBones( q, pos, q2, pos2, s ); + + if (pseqdesc->numblends == 4) + { + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos3, q3, pseqdesc, panim, f ); + + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos4, q4, pseqdesc, panim, f ); + + s = (m_pCurrentEntity->curstate.blending[0] * dadt + m_pCurrentEntity->latched.prevblending[0] * (1.0 - dadt)) / 255.0; + StudioSlerpBones( q3, pos3, q4, pos4, s ); + + s = (m_pCurrentEntity->curstate.blending[1] * dadt + m_pCurrentEntity->latched.prevblending[1] * (1.0 - dadt)) / 255.0; + StudioSlerpBones( q, pos, q3, pos3, s ); + } + } + + if (m_fDoInterp && + m_pCurrentEntity->latched.sequencetime && + ( m_pCurrentEntity->latched.sequencetime + 0.2 > m_clTime ) && + ( m_pCurrentEntity->latched.prevsequence < m_pStudioHeader->numseq )) + { + // blend from last sequence + static float pos1b[MAXSTUDIOBONES][3]; + static vec4_t q1b[MAXSTUDIOBONES]; + float s; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->latched.prevsequence; + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + // clip prevframe + StudioCalcRotations( pos1b, q1b, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + if (pseqdesc->numblends > 1) + { + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos2, q2, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + s = (m_pCurrentEntity->latched.prevseqblending[0]) / 255.0; + StudioSlerpBones( q1b, pos1b, q2, pos2, s ); + + if (pseqdesc->numblends == 4) + { + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos3, q3, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos4, q4, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + s = (m_pCurrentEntity->latched.prevseqblending[0]) / 255.0; + StudioSlerpBones( q3, pos3, q4, pos4, s ); + + s = (m_pCurrentEntity->latched.prevseqblending[1]) / 255.0; + StudioSlerpBones( q1b, pos1b, q3, pos3, s ); + } + } + + s = 1.0 - (m_clTime - m_pCurrentEntity->latched.sequencetime) / 0.2; + StudioSlerpBones( q, pos, q1b, pos1b, s ); + } + else + { + //Con_DPrintf("prevframe = %4.2f\n", f); + m_pCurrentEntity->latched.prevframe = f; + } + + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + // calc gait animation + if (m_pPlayerInfo && m_pPlayerInfo->gaitsequence != 0) + { + if (m_pPlayerInfo->gaitsequence >= m_pStudioHeader->numseq) + { + m_pPlayerInfo->gaitsequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pPlayerInfo->gaitsequence; + + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + StudioCalcRotations( pos2, q2, pseqdesc, panim, m_pPlayerInfo->gaitframe ); + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + if (strcmp( pbones[i].name, "Bip01 Spine") == 0) + break; + memcpy( pos[i], pos2[i], sizeof( pos[i] )); + memcpy( q[i], q2[i], sizeof( q[i] )); + } + } + + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + QuaternionMatrix( q[i], bonematrix ); + + bonematrix[0][3] = pos[i][0]; + bonematrix[1][3] = pos[i][1]; + bonematrix[2][3] = pos[i][2]; + + if (pbones[i].parent == -1) + { + if ( IEngineStudio.IsHardware() ) + { + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_pbonetransform)[i]); + + // MatrixCopy should be faster... + //ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + MatrixCopy( (*m_pbonetransform)[i], (*m_plighttransform)[i] ); + } + else + { + ConcatTransforms ((*m_paliastransform), bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + } + + // Apply client-side effects to the transformation matrix + StudioFxTransform( m_pCurrentEntity, (*m_pbonetransform)[i] ); + } + else + { + ConcatTransforms ((*m_pbonetransform)[pbones[i].parent], bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_plighttransform)[pbones[i].parent], bonematrix, (*m_plighttransform)[i]); + } + } +} + + +/* +==================== +StudioSaveBones + +==================== +*/ +void CStudioModelRenderer::StudioSaveBones( void ) +{ + int i; + + mstudiobone_t *pbones; + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + m_nCachedBones = m_pStudioHeader->numbones; + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + strcpy( m_nCachedBoneNames[i], pbones[i].name ); + MatrixCopy( (*m_pbonetransform)[i], m_rgCachedBoneTransform[i] ); + MatrixCopy( (*m_plighttransform)[i], m_rgCachedLightTransform[i] ); + } +} + + +/* +==================== +StudioMergeBones + +==================== +*/ +void CStudioModelRenderer::StudioMergeBones ( model_t *m_pSubModel ) +{ + int i, j; + double f; + int do_hunt = true; + + mstudiobone_t *pbones; + mstudioseqdesc_t *pseqdesc; + mstudioanim_t *panim; + + static float pos[MAXSTUDIOBONES][3]; + float bonematrix[3][4]; + static vec4_t q[MAXSTUDIOBONES]; + + if (m_pCurrentEntity->curstate.sequence >= m_pStudioHeader->numseq) + { + m_pCurrentEntity->curstate.sequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + f = StudioEstimateFrame( pseqdesc ); + + if (m_pCurrentEntity->latched.prevframe > f) + { + //Con_DPrintf("%f %f\n", m_pCurrentEntity->prevframe, f ); + } + + panim = StudioGetAnim( m_pSubModel, pseqdesc ); + StudioCalcRotations( pos, q, pseqdesc, panim, f ); + + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + for (j = 0; j < m_nCachedBones; j++) + { + if (stricmp(pbones[i].name, m_nCachedBoneNames[j]) == 0) + { + MatrixCopy( m_rgCachedBoneTransform[j], (*m_pbonetransform)[i] ); + MatrixCopy( m_rgCachedLightTransform[j], (*m_plighttransform)[i] ); + break; + } + } + if (j >= m_nCachedBones) + { + QuaternionMatrix( q[i], bonematrix ); + + bonematrix[0][3] = pos[i][0]; + bonematrix[1][3] = pos[i][1]; + bonematrix[2][3] = pos[i][2]; + + if (pbones[i].parent == -1) + { + if ( IEngineStudio.IsHardware() ) + { + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_pbonetransform)[i]); + + // MatrixCopy should be faster... + //ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + MatrixCopy( (*m_pbonetransform)[i], (*m_plighttransform)[i] ); + } + else + { + ConcatTransforms ((*m_paliastransform), bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + } + + // Apply client-side effects to the transformation matrix + StudioFxTransform( m_pCurrentEntity, (*m_pbonetransform)[i] ); + } + else + { + ConcatTransforms ((*m_pbonetransform)[pbones[i].parent], bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_plighttransform)[pbones[i].parent], bonematrix, (*m_plighttransform)[i]); + } + } + } +} + +/* +==================== +StudioDrawModel + +==================== +*/ +int CStudioModelRenderer::StudioDrawModel( int flags ) +{ + alight_t lighting; + vec3_t dir; + + m_pCurrentEntity = IEngineStudio.GetCurrentEntity(); + IEngineStudio.GetTimes( &m_nFrameCount, &m_clTime, &m_clOldTime ); + IEngineStudio.GetViewInfo( m_vRenderOrigin, m_vUp, m_vRight, m_vNormal ); + IEngineStudio.GetAliasScale( &m_fSoftwareXScale, &m_fSoftwareYScale ); + + if (m_pCurrentEntity->curstate.renderfx == kRenderFxDeadPlayer) + { + entity_state_t deadplayer; + + int result; + int save_interp; + + if (m_pCurrentEntity->curstate.renderamt <= 0 || m_pCurrentEntity->curstate.renderamt > gEngfuncs.GetMaxClients() ) + return 0; + + // get copy of player + deadplayer = *(IEngineStudio.GetPlayerState( m_pCurrentEntity->curstate.renderamt - 1 )); //cl.frames[cl.parsecount & CL_UPDATE_MASK].playerstate[m_pCurrentEntity->curstate.renderamt-1]; + + // clear weapon, movement state + deadplayer.number = m_pCurrentEntity->curstate.renderamt; + deadplayer.weaponmodel = 0; + deadplayer.gaitsequence = 0; + + deadplayer.movetype = MOVETYPE_NONE; + VectorCopy( m_pCurrentEntity->curstate.angles, deadplayer.angles ); + VectorCopy( m_pCurrentEntity->curstate.origin, deadplayer.origin ); + + save_interp = m_fDoInterp; + m_fDoInterp = 0; + + // draw as though it were a player + result = StudioDrawPlayer( flags, &deadplayer ); + + m_fDoInterp = save_interp; + return result; + } + + m_pRenderModel = m_pCurrentEntity->model; + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (m_pRenderModel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + IEngineStudio.SetRenderModel( m_pRenderModel ); + + StudioSetUpTransform( 0 ); + + if (flags & STUDIO_RENDER) + { + // see if the bounding box lets us trivially reject, also sets + if (!IEngineStudio.StudioCheckBBox ()) + return 0; + + (*m_pModelsDrawn)++; + (*m_pStudioModelCount)++; // render data cache cookie + + if (m_pStudioHeader->numbodyparts == 0) + return 1; + } + + if (m_pCurrentEntity->curstate.movetype == MOVETYPE_FOLLOW) + { + StudioMergeBones( m_pRenderModel ); + } + else + { + StudioSetupBones( ); + } + StudioSaveBones( ); + + if (flags & STUDIO_EVENTS) + { + StudioCalcAttachments( ); + IEngineStudio.StudioClientEvents( ); + // copy attachments into global entity array + if ( m_pCurrentEntity->index > 0 ) + { + cl_entity_t *ent = gEngfuncs.GetEntityByIndex( m_pCurrentEntity->index ); + + memcpy( ent->attachment, m_pCurrentEntity->attachment, sizeof( vec3_t ) * 4 ); + } + } + + if (flags & STUDIO_RENDER) + { + lighting.plightvec = dir; + IEngineStudio.StudioDynamicLight(m_pCurrentEntity, &lighting ); + + IEngineStudio.StudioEntityLight( &lighting ); + + // model and frame independant + IEngineStudio.StudioSetupLighting (&lighting); + + // get remap colors + m_nTopColor = m_pCurrentEntity->curstate.colormap & 0xFF; + m_nBottomColor = (m_pCurrentEntity->curstate.colormap & 0xFF00) >> 8; + + IEngineStudio.StudioSetRemapColors( m_nTopColor, m_nBottomColor ); + + StudioRenderModel( ); + } + + return 1; +} + +/* +==================== +StudioEstimateGait + +==================== +*/ +void CStudioModelRenderer::StudioEstimateGait( entity_state_t *pplayer ) +{ + float dt; + vec3_t est_velocity; + + dt = (m_clTime - m_clOldTime); + if (dt < 0) + dt = 0; + else if (dt > 1.0) + dt = 1; + + if (dt == 0 || m_pPlayerInfo->renderframe == m_nFrameCount) + { + m_flGaitMovement = 0; + return; + } + + // VectorAdd( pplayer->velocity, pplayer->prediction_error, est_velocity ); + if ( m_fGaitEstimation ) + { + VectorSubtract( m_pCurrentEntity->origin, m_pPlayerInfo->prevgaitorigin, est_velocity ); + VectorCopy( m_pCurrentEntity->origin, m_pPlayerInfo->prevgaitorigin ); + m_flGaitMovement = Length( est_velocity ); + if (dt <= 0 || m_flGaitMovement / dt < 5) + { + m_flGaitMovement = 0; + est_velocity[0] = 0; + est_velocity[1] = 0; + } + } + else + { + VectorCopy( pplayer->velocity, est_velocity ); + m_flGaitMovement = Length( est_velocity ) * dt; + } + + if (est_velocity[1] == 0 && est_velocity[0] == 0) + { + float flYawDiff = m_pCurrentEntity->angles[YAW] - m_pPlayerInfo->gaityaw; + flYawDiff = flYawDiff - (int)(flYawDiff / 360) * 360; + if (flYawDiff > 180) + flYawDiff -= 360; + if (flYawDiff < -180) + flYawDiff += 360; + + if (dt < 0.25) + flYawDiff *= dt * 4; + else + flYawDiff *= dt; + + m_pPlayerInfo->gaityaw += flYawDiff; + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - (int)(m_pPlayerInfo->gaityaw / 360) * 360; + + m_flGaitMovement = 0; + } + else + { + m_pPlayerInfo->gaityaw = (atan2(est_velocity[1], est_velocity[0]) * 180 / M_PI); + if (m_pPlayerInfo->gaityaw > 180) + m_pPlayerInfo->gaityaw = 180; + if (m_pPlayerInfo->gaityaw < -180) + m_pPlayerInfo->gaityaw = -180; + } + +} + +/* +==================== +StudioProcessGait + +==================== +*/ +void CStudioModelRenderer::StudioProcessGait( entity_state_t *pplayer ) +{ + mstudioseqdesc_t *pseqdesc; + float dt; + int iBlend; + float flYaw; // view direction relative to movement + + if (m_pCurrentEntity->curstate.sequence >= m_pStudioHeader->numseq) + { + m_pCurrentEntity->curstate.sequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + StudioPlayerBlend( pseqdesc, &iBlend, &m_pCurrentEntity->angles[PITCH] ); + + m_pCurrentEntity->latched.prevangles[PITCH] = m_pCurrentEntity->angles[PITCH]; + m_pCurrentEntity->curstate.blending[0] = iBlend; + m_pCurrentEntity->latched.prevblending[0] = m_pCurrentEntity->curstate.blending[0]; + m_pCurrentEntity->latched.prevseqblending[0] = m_pCurrentEntity->curstate.blending[0]; + + // Con_DPrintf("%f %d\n", m_pCurrentEntity->angles[PITCH], m_pCurrentEntity->blending[0] ); + + dt = (m_clTime - m_clOldTime); + if (dt < 0) + dt = 0; + else if (dt > 1.0) + dt = 1; + + StudioEstimateGait( pplayer ); + + // Con_DPrintf("%f %f\n", m_pCurrentEntity->angles[YAW], m_pPlayerInfo->gaityaw ); + + // calc side to side turning + flYaw = m_pCurrentEntity->angles[YAW] - m_pPlayerInfo->gaityaw; + flYaw = flYaw - (int)(flYaw / 360) * 360; + if (flYaw < -180) + flYaw = flYaw + 360; + if (flYaw > 180) + flYaw = flYaw - 360; + + if (flYaw > 120) + { + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - 180; + m_flGaitMovement = -m_flGaitMovement; + flYaw = flYaw - 180; + } + else if (flYaw < -120) + { + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw + 180; + m_flGaitMovement = -m_flGaitMovement; + flYaw = flYaw + 180; + } + + // adjust torso + m_pCurrentEntity->curstate.controller[0] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); + m_pCurrentEntity->curstate.controller[1] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); + m_pCurrentEntity->curstate.controller[2] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); + m_pCurrentEntity->curstate.controller[3] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); + m_pCurrentEntity->latched.prevcontroller[0] = m_pCurrentEntity->curstate.controller[0]; + m_pCurrentEntity->latched.prevcontroller[1] = m_pCurrentEntity->curstate.controller[1]; + m_pCurrentEntity->latched.prevcontroller[2] = m_pCurrentEntity->curstate.controller[2]; + m_pCurrentEntity->latched.prevcontroller[3] = m_pCurrentEntity->curstate.controller[3]; + + m_pCurrentEntity->angles[YAW] = m_pPlayerInfo->gaityaw; + if (m_pCurrentEntity->angles[YAW] < -0) + m_pCurrentEntity->angles[YAW] += 360; + m_pCurrentEntity->latched.prevangles[YAW] = m_pCurrentEntity->angles[YAW]; + + if (pplayer->gaitsequence >= m_pStudioHeader->numseq) + { + pplayer->gaitsequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + pplayer->gaitsequence; + + // calc gait frame + if (pseqdesc->linearmovement[0] > 0) + { + m_pPlayerInfo->gaitframe += (m_flGaitMovement / pseqdesc->linearmovement[0]) * pseqdesc->numframes; + } + else + { + m_pPlayerInfo->gaitframe += pseqdesc->fps * dt; + } + + // do modulo + m_pPlayerInfo->gaitframe = m_pPlayerInfo->gaitframe - (int)(m_pPlayerInfo->gaitframe / pseqdesc->numframes) * pseqdesc->numframes; + if (m_pPlayerInfo->gaitframe < 0) + m_pPlayerInfo->gaitframe += pseqdesc->numframes; +} + +/* +==================== +StudioDrawPlayer + +==================== +*/ +int CStudioModelRenderer::StudioDrawPlayer( int flags, entity_state_t *pplayer ) +{ + alight_t lighting; + vec3_t dir; + + m_pCurrentEntity = IEngineStudio.GetCurrentEntity(); + IEngineStudio.GetTimes( &m_nFrameCount, &m_clTime, &m_clOldTime ); + IEngineStudio.GetViewInfo( m_vRenderOrigin, m_vUp, m_vRight, m_vNormal ); + IEngineStudio.GetAliasScale( &m_fSoftwareXScale, &m_fSoftwareYScale ); + + // Con_DPrintf("DrawPlayer %d\n", m_pCurrentEntity->blending[0] ); + + // Con_DPrintf("DrawPlayer %d %d (%d)\n", m_nFrameCount, pplayer->player_index, m_pCurrentEntity->curstate.sequence ); + + // Con_DPrintf("Player %.2f %.2f %.2f\n", pplayer->velocity[0], pplayer->velocity[1], pplayer->velocity[2] ); + + m_nPlayerIndex = pplayer->number - 1; + + if (m_nPlayerIndex < 0 || m_nPlayerIndex >= gEngfuncs.GetMaxClients()) + return 0; + + m_pRenderModel = IEngineStudio.SetupPlayerModel( m_nPlayerIndex ); + if (m_pRenderModel == NULL) + return 0; + + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (m_pRenderModel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + IEngineStudio.SetRenderModel( m_pRenderModel ); + + if (pplayer->gaitsequence) + { + vec3_t orig_angles; + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + + VectorCopy( m_pCurrentEntity->angles, orig_angles ); + + StudioProcessGait( pplayer ); + + m_pPlayerInfo->gaitsequence = pplayer->gaitsequence; + m_pPlayerInfo = NULL; + + StudioSetUpTransform( 0 ); + VectorCopy( orig_angles, m_pCurrentEntity->angles ); + } + else + { + m_pCurrentEntity->curstate.controller[0] = 127; + m_pCurrentEntity->curstate.controller[1] = 127; + m_pCurrentEntity->curstate.controller[2] = 127; + m_pCurrentEntity->curstate.controller[3] = 127; + m_pCurrentEntity->latched.prevcontroller[0] = m_pCurrentEntity->curstate.controller[0]; + m_pCurrentEntity->latched.prevcontroller[1] = m_pCurrentEntity->curstate.controller[1]; + m_pCurrentEntity->latched.prevcontroller[2] = m_pCurrentEntity->curstate.controller[2]; + m_pCurrentEntity->latched.prevcontroller[3] = m_pCurrentEntity->curstate.controller[3]; + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + m_pPlayerInfo->gaitsequence = 0; + + StudioSetUpTransform( 0 ); + } + + if (flags & STUDIO_RENDER) + { + // see if the bounding box lets us trivially reject, also sets + if (!IEngineStudio.StudioCheckBBox ()) + return 0; + + (*m_pModelsDrawn)++; + (*m_pStudioModelCount)++; // render data cache cookie + + if (m_pStudioHeader->numbodyparts == 0) + return 1; + } + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + StudioSetupBones( ); + StudioSaveBones( ); + m_pPlayerInfo->renderframe = m_nFrameCount; + + m_pPlayerInfo = NULL; + + if (flags & STUDIO_EVENTS) + { + StudioCalcAttachments( ); + IEngineStudio.StudioClientEvents( ); + // copy attachments into global entity array + if ( m_pCurrentEntity->index > 0 ) + { + cl_entity_t *ent = gEngfuncs.GetEntityByIndex( m_pCurrentEntity->index ); + + memcpy( ent->attachment, m_pCurrentEntity->attachment, sizeof( vec3_t ) * 4 ); + } + } + + if (flags & STUDIO_RENDER) + { + if (m_pCvarHiModels->value && m_pRenderModel != m_pCurrentEntity->model ) + { + // show highest resolution multiplayer model + m_pCurrentEntity->curstate.body = 255; + } + + if (!(m_pCvarDeveloper->value == 0 && gEngfuncs.GetMaxClients() == 1 ) && ( m_pRenderModel == m_pCurrentEntity->model ) ) + { + m_pCurrentEntity->curstate.body = 1; // force helmet + } + + lighting.plightvec = dir; + IEngineStudio.StudioDynamicLight(m_pCurrentEntity, &lighting ); + + IEngineStudio.StudioEntityLight( &lighting ); + + // model and frame independant + IEngineStudio.StudioSetupLighting (&lighting); + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + + // get remap colors + m_nTopColor = m_pPlayerInfo->topcolor; + m_nBottomColor = m_pPlayerInfo->bottomcolor; + if (m_nTopColor < 0) + m_nTopColor = 0; + if (m_nTopColor > 360) + m_nTopColor = 360; + if (m_nBottomColor < 0) + m_nBottomColor = 0; + if (m_nBottomColor > 360) + m_nBottomColor = 360; + + IEngineStudio.StudioSetRemapColors( m_nTopColor, m_nBottomColor ); + + StudioRenderModel( ); + m_pPlayerInfo = NULL; + + if (pplayer->weaponmodel) + { + cl_entity_t saveent = *m_pCurrentEntity; + + model_t *pweaponmodel = IEngineStudio.GetModelByIndex( pplayer->weaponmodel ); + + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (pweaponmodel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + + StudioMergeBones( pweaponmodel); + + IEngineStudio.StudioSetupLighting (&lighting); + + StudioRenderModel( ); + + StudioCalcAttachments( ); + + *m_pCurrentEntity = saveent; + } + } + + return 1; +} + +/* +==================== +StudioCalcAttachments + +==================== +*/ +void CStudioModelRenderer::StudioCalcAttachments( void ) +{ + int i; + mstudioattachment_t *pattachment; + + if ( m_pStudioHeader->numattachments > 4 ) + { + gEngfuncs.Con_DPrintf( "Too many attachments on %s\n", m_pCurrentEntity->model->name ); + exit( -1 ); + } + + // calculate attachment points + pattachment = (mstudioattachment_t *)((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex); + for (i = 0; i < m_pStudioHeader->numattachments; i++) + { + VectorTransform( pattachment[i].org, (*m_plighttransform)[pattachment[i].bone], m_pCurrentEntity->attachment[i] ); + } +} + +/* +==================== +StudioRenderModel + +==================== +*/ +void CStudioModelRenderer::StudioRenderModel( void ) +{ + IEngineStudio.SetChromeOrigin(); + IEngineStudio.SetForceFaceFlags( 0 ); + + if ( m_pCurrentEntity->curstate.renderfx == kRenderFxGlowShell ) + { + m_pCurrentEntity->curstate.renderfx = kRenderFxNone; + StudioRenderFinal( ); + + if ( !IEngineStudio.IsHardware() ) + { + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + } + + IEngineStudio.SetForceFaceFlags( STUDIO_NF_CHROME ); + + gEngfuncs.pTriAPI->SpriteTexture( m_pChromeSprite, 0 ); + m_pCurrentEntity->curstate.renderfx = kRenderFxGlowShell; + + StudioRenderFinal( ); + if ( !IEngineStudio.IsHardware() ) + { + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); + } + } + else + { + StudioRenderFinal( ); + } +} + +/* +==================== +StudioRenderFinal_Software + +==================== +*/ +void CStudioModelRenderer::StudioRenderFinal_Software( void ) +{ + int i; + + // Note, rendermode set here has effect in SW + IEngineStudio.SetupRenderer( 0 ); + + if (m_pCvarDrawEntities->value == 2) + { + IEngineStudio.StudioDrawBones( ); + } + else if (m_pCvarDrawEntities->value == 3) + { + IEngineStudio.StudioDrawHulls( ); + } + else + { + for (i=0 ; i < m_pStudioHeader->numbodyparts ; i++) + { + IEngineStudio.StudioSetupModel( i, (void **)&m_pBodyPart, (void **)&m_pSubModel ); + IEngineStudio.StudioDrawPoints( ); + } + } + + if (m_pCvarDrawEntities->value == 4) + { + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + IEngineStudio.StudioDrawHulls( ); + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); + } + + if (m_pCvarDrawEntities->value == 5) + { + IEngineStudio.StudioDrawAbsBBox( ); + } + + IEngineStudio.RestoreRenderer(); +} + +/* +==================== +StudioRenderFinal_Hardware + +==================== +*/ +void CStudioModelRenderer::StudioRenderFinal_Hardware( void ) +{ + int i; + int rendermode; + + rendermode = IEngineStudio.GetForceFaceFlags() ? kRenderTransAdd : m_pCurrentEntity->curstate.rendermode; + IEngineStudio.SetupRenderer( rendermode ); + + if (m_pCvarDrawEntities->value == 2) + { + IEngineStudio.StudioDrawBones(); + } + else if (m_pCvarDrawEntities->value == 3) + { + IEngineStudio.StudioDrawHulls(); + } + else + { + for (i=0 ; i < m_pStudioHeader->numbodyparts ; i++) + { + IEngineStudio.StudioSetupModel( i, (void **)&m_pBodyPart, (void **)&m_pSubModel ); + + if (m_fDoInterp) + { + // interpolation messes up bounding boxes. + m_pCurrentEntity->trivial_accept = 0; + } + + IEngineStudio.GL_SetRenderMode( rendermode ); + IEngineStudio.StudioDrawPoints(); + IEngineStudio.GL_StudioDrawShadow(); + } + } + + if ( m_pCvarDrawEntities->value == 4 ) + { + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + IEngineStudio.StudioDrawHulls( ); + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); + } + + IEngineStudio.RestoreRenderer(); +} + +/* +==================== +StudioRenderFinal + +==================== +*/ +void CStudioModelRenderer::StudioRenderFinal(void) +{ + if ( IEngineStudio.IsHardware() ) + { + StudioRenderFinal_Hardware(); + } + else + { + StudioRenderFinal_Software(); + } +} + diff --git a/hlsdk/cl_dll/StudioModelRenderer.h b/hlsdk/cl_dll/StudioModelRenderer.h new file mode 100644 index 00000000..51eaaa60 --- /dev/null +++ b/hlsdk/cl_dll/StudioModelRenderer.h @@ -0,0 +1,189 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined ( STUDIOMODELRENDERER_H ) +#define STUDIOMODELRENDERER_H +#if defined( _WIN32 ) +#pragma once +#endif + +/* +==================== +CStudioModelRenderer + +==================== +*/ +class CStudioModelRenderer +{ +public: + // Construction/Destruction + CStudioModelRenderer( void ); + virtual ~CStudioModelRenderer( void ); + + // Initialization + virtual void Init( void ); + +public: + // Public Interfaces + virtual int StudioDrawModel ( int flags ); + virtual int StudioDrawPlayer ( int flags, struct entity_state_s *pplayer ); + +public: + // Local interfaces + // + + // Look up animation data for sequence + virtual mstudioanim_t *StudioGetAnim ( model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc ); + + // Interpolate model position and angles and set up matrices + virtual void StudioSetUpTransform (int trivial_accept); + + // Set up model bone positions + virtual void StudioSetupBones ( void ); + + // Find final attachment points + virtual void StudioCalcAttachments ( void ); + + // Save bone matrices and names + virtual void StudioSaveBones( void ); + + // Merge cached bones with current bones for model + virtual void StudioMergeBones ( model_t *m_pSubModel ); + + // Determine interpolation fraction + virtual float StudioEstimateInterpolant( void ); + + // Determine current frame for rendering + virtual float StudioEstimateFrame ( mstudioseqdesc_t *pseqdesc ); + + // Apply special effects to transform matrix + virtual void StudioFxTransform( cl_entity_t *ent, float transform[3][4] ); + + // Spherical interpolation of bones + virtual void StudioSlerpBones ( vec4_t q1[], float pos1[][3], vec4_t q2[], float pos2[][3], float s ); + + // Compute bone adjustments ( bone controllers ) + virtual void StudioCalcBoneAdj ( float dadt, float *adj, const byte *pcontroller1, const byte *pcontroller2, byte mouthopen ); + + // Get bone quaternions + virtual void StudioCalcBoneQuaterion ( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *q ); + + // Get bone positions + virtual void StudioCalcBonePosition ( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *pos ); + + // Compute rotations + virtual void StudioCalcRotations ( float pos[][3], vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f ); + + // Send bones and verts to renderer + virtual void StudioRenderModel ( void ); + + // Finalize rendering + virtual void StudioRenderFinal (void); + + // GL&D3D vs. Software renderer finishing functions + virtual void StudioRenderFinal_Software ( void ); + virtual void StudioRenderFinal_Hardware ( void ); + + // Player specific data + // Determine pitch and blending amounts for players + virtual void StudioPlayerBlend ( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch ); + + // Estimate gait frame for player + virtual void StudioEstimateGait ( entity_state_t *pplayer ); + + // Process movement of player + virtual void StudioProcessGait ( entity_state_t *pplayer ); + +public: + + // Client clock + double m_clTime; + // Old Client clock + double m_clOldTime; + + // Do interpolation? + int m_fDoInterp; + // Do gait estimation? + int m_fGaitEstimation; + + // Current render frame # + int m_nFrameCount; + + // Cvars that studio model code needs to reference + // + // Use high quality models? + cvar_t *m_pCvarHiModels; + // Developer debug output desired? + cvar_t *m_pCvarDeveloper; + // Draw entities bone hit boxes, etc? + cvar_t *m_pCvarDrawEntities; + + // The entity which we are currently rendering. + cl_entity_t *m_pCurrentEntity; + + // The model for the entity being rendered + model_t *m_pRenderModel; + + // Player info for current player, if drawing a player + player_info_t *m_pPlayerInfo; + + // The index of the player being drawn + int m_nPlayerIndex; + + // The player's gait movement + float m_flGaitMovement; + + // Pointer to header block for studio model data + studiohdr_t *m_pStudioHeader; + + // Pointers to current body part and submodel + mstudiobodyparts_t *m_pBodyPart; + mstudiomodel_t *m_pSubModel; + + // Palette substition for top and bottom of model + int m_nTopColor; + int m_nBottomColor; + + // + // Sprite model used for drawing studio model chrome + model_t *m_pChromeSprite; + + // Caching + // Number of bones in bone cache + int m_nCachedBones; + // Names of cached bones + char m_nCachedBoneNames[ MAXSTUDIOBONES ][ 32 ]; + // Cached bone & light transformation matrices + float m_rgCachedBoneTransform [ MAXSTUDIOBONES ][ 3 ][ 4 ]; + float m_rgCachedLightTransform[ MAXSTUDIOBONES ][ 3 ][ 4 ]; + + // Software renderer scale factors + float m_fSoftwareXScale, m_fSoftwareYScale; + + // Current view vectors and render origin + float m_vUp[ 3 ]; + float m_vRight[ 3 ]; + float m_vNormal[ 3 ]; + + float m_vRenderOrigin[ 3 ]; + + // Model render counters ( from engine ) + int *m_pStudioModelCount; + int *m_pModelsDrawn; + + // Matrices + // Model to world transformation + float (*m_protationmatrix)[ 3 ][ 4 ]; + // Model to view transformation + float (*m_paliastransform)[ 3 ][ 4 ]; + + // Concatenated bone and light transforms + float (*m_pbonetransform) [ MAXSTUDIOBONES ][ 3 ][ 4 ]; + float (*m_plighttransform)[ MAXSTUDIOBONES ][ 3 ][ 4 ]; +}; + +#endif // STUDIOMODELRENDERER_H \ No newline at end of file diff --git a/hlsdk/cl_dll/ammo.cpp b/hlsdk/cl_dll/ammo.cpp new file mode 100644 index 00000000..5084e084 --- /dev/null +++ b/hlsdk/cl_dll/ammo.cpp @@ -0,0 +1,1204 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Ammo.cpp +// +// implementation of CHudAmmo class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" +#include "pm_shared.h" + +#include +#include + +#include "ammohistory.h" +#include "vgui_TeamFortressViewport.h" + +WEAPON *gpActiveSel; // NULL means off, 1 means just the menu bar, otherwise + // this points to the active weapon menu item +WEAPON *gpLastSel; // Last weapon menu selection + +client_sprite_t *GetSpriteList(client_sprite_t *pList, const char *psz, int iRes, int iCount); + +WeaponsResource gWR; + +int g_weaponselect = 0; + +void WeaponsResource :: LoadAllWeaponSprites( void ) +{ + for (int i = 0; i < MAX_WEAPONS; i++) + { + if ( rgWeapons[i].iId ) + LoadWeaponSprites( &rgWeapons[i] ); + } +} + +int WeaponsResource :: CountAmmo( int iId ) +{ + if ( iId < 0 ) + return 0; + + return riAmmo[iId]; +} + +int WeaponsResource :: HasAmmo( WEAPON *p ) +{ + if ( !p ) + return FALSE; + + // weapons with no max ammo can always be selected + if ( p->iMax1 == -1 ) + return TRUE; + + return (p->iAmmoType == -1) || p->iClip > 0 || CountAmmo(p->iAmmoType) + || CountAmmo(p->iAmmo2Type) || ( p->iFlags & WEAPON_FLAGS_SELECTONEMPTY ); +} + + +void WeaponsResource :: LoadWeaponSprites( WEAPON *pWeapon ) +{ + int i, iRes; + + if (ScreenWidth < 640) + iRes = 320; + else + iRes = 640; + + char sz[128]; + + if ( !pWeapon ) + return; + + memset( &pWeapon->rcActive, 0, sizeof(wrect_t) ); + memset( &pWeapon->rcInactive, 0, sizeof(wrect_t) ); + memset( &pWeapon->rcAmmo, 0, sizeof(wrect_t) ); + memset( &pWeapon->rcAmmo2, 0, sizeof(wrect_t) ); + pWeapon->hInactive = 0; + pWeapon->hActive = 0; + pWeapon->hAmmo = 0; + pWeapon->hAmmo2 = 0; + + sprintf(sz, "sprites/%s.txt", pWeapon->szName); + client_sprite_t *pList = SPR_GetList(sz, &i); + + if (!pList) + return; + + client_sprite_t *p; + + p = GetSpriteList( pList, "crosshair", iRes, i ); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hCrosshair = SPR_Load(sz); + pWeapon->rcCrosshair = p->rc; + } + else + pWeapon->hCrosshair = NULL; + + p = GetSpriteList(pList, "autoaim", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hAutoaim = SPR_Load(sz); + pWeapon->rcAutoaim = p->rc; + } + else + pWeapon->hAutoaim = 0; + + p = GetSpriteList( pList, "zoom", iRes, i ); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hZoomedCrosshair = SPR_Load(sz); + pWeapon->rcZoomedCrosshair = p->rc; + } + else + { + pWeapon->hZoomedCrosshair = pWeapon->hCrosshair; //default to non-zoomed crosshair + pWeapon->rcZoomedCrosshair = pWeapon->rcCrosshair; + } + + p = GetSpriteList(pList, "zoom_autoaim", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hZoomedAutoaim = SPR_Load(sz); + pWeapon->rcZoomedAutoaim = p->rc; + } + else + { + pWeapon->hZoomedAutoaim = pWeapon->hZoomedCrosshair; //default to zoomed crosshair + pWeapon->rcZoomedAutoaim = pWeapon->rcZoomedCrosshair; + } + + p = GetSpriteList(pList, "weapon", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hInactive = SPR_Load(sz); + pWeapon->rcInactive = p->rc; + + gHR.iHistoryGap = max( gHR.iHistoryGap, pWeapon->rcActive.bottom - pWeapon->rcActive.top ); + } + else + pWeapon->hInactive = 0; + + p = GetSpriteList(pList, "weapon_s", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hActive = SPR_Load(sz); + pWeapon->rcActive = p->rc; + } + else + pWeapon->hActive = 0; + + p = GetSpriteList(pList, "ammo", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hAmmo = SPR_Load(sz); + pWeapon->rcAmmo = p->rc; + + gHR.iHistoryGap = max( gHR.iHistoryGap, pWeapon->rcActive.bottom - pWeapon->rcActive.top ); + } + else + pWeapon->hAmmo = 0; + + p = GetSpriteList(pList, "ammo2", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hAmmo2 = SPR_Load(sz); + pWeapon->rcAmmo2 = p->rc; + + gHR.iHistoryGap = max( gHR.iHistoryGap, pWeapon->rcActive.bottom - pWeapon->rcActive.top ); + } + else + pWeapon->hAmmo2 = 0; + +} + +// Returns the first weapon for a given slot. +WEAPON *WeaponsResource :: GetFirstPos( int iSlot ) +{ + WEAPON *pret = NULL; + + for (int i = 0; i < MAX_WEAPON_POSITIONS; i++) + { + if ( rgSlots[iSlot][i] && HasAmmo( rgSlots[iSlot][i] ) ) + { + pret = rgSlots[iSlot][i]; + break; + } + } + + return pret; +} + + +WEAPON* WeaponsResource :: GetNextActivePos( int iSlot, int iSlotPos ) +{ + if ( iSlotPos >= MAX_WEAPON_POSITIONS || iSlot >= MAX_WEAPON_SLOTS ) + return NULL; + + WEAPON *p = gWR.rgSlots[ iSlot ][ iSlotPos+1 ]; + + if ( !p || !gWR.HasAmmo(p) ) + return GetNextActivePos( iSlot, iSlotPos + 1 ); + + return p; +} + + +int giBucketHeight, giBucketWidth, giABHeight, giABWidth; // Ammo Bar width and height + +HSPRITE ghsprBuckets; // Sprite for top row of weapons menu + +DECLARE_MESSAGE(m_Ammo, CurWeapon ); // Current weapon and clip +DECLARE_MESSAGE(m_Ammo, WeaponList); // new weapon type +DECLARE_MESSAGE(m_Ammo, AmmoX); // update known ammo type's count +DECLARE_MESSAGE(m_Ammo, AmmoPickup); // flashes an ammo pickup record +DECLARE_MESSAGE(m_Ammo, WeapPickup); // flashes a weapon pickup record +DECLARE_MESSAGE(m_Ammo, HideWeapon); // hides the weapon, ammo, and crosshair displays temporarily +DECLARE_MESSAGE(m_Ammo, ItemPickup); + +DECLARE_COMMAND(m_Ammo, Slot1); +DECLARE_COMMAND(m_Ammo, Slot2); +DECLARE_COMMAND(m_Ammo, Slot3); +DECLARE_COMMAND(m_Ammo, Slot4); +DECLARE_COMMAND(m_Ammo, Slot5); +DECLARE_COMMAND(m_Ammo, Slot6); +DECLARE_COMMAND(m_Ammo, Slot7); +DECLARE_COMMAND(m_Ammo, Slot8); +DECLARE_COMMAND(m_Ammo, Slot9); +DECLARE_COMMAND(m_Ammo, Slot10); +DECLARE_COMMAND(m_Ammo, Close); +DECLARE_COMMAND(m_Ammo, NextWeapon); +DECLARE_COMMAND(m_Ammo, PrevWeapon); + +// width of ammo fonts +#define AMMO_SMALL_WIDTH 10 +#define AMMO_LARGE_WIDTH 20 + +#define HISTORY_DRAW_TIME "5" + +int CHudAmmo::Init(void) +{ + gHUD.AddHudElem(this); + + HOOK_MESSAGE(CurWeapon); + HOOK_MESSAGE(WeaponList); + HOOK_MESSAGE(AmmoPickup); + HOOK_MESSAGE(WeapPickup); + HOOK_MESSAGE(ItemPickup); + HOOK_MESSAGE(HideWeapon); + HOOK_MESSAGE(AmmoX); + + HOOK_COMMAND("slot1", Slot1); + HOOK_COMMAND("slot2", Slot2); + HOOK_COMMAND("slot3", Slot3); + HOOK_COMMAND("slot4", Slot4); + HOOK_COMMAND("slot5", Slot5); + HOOK_COMMAND("slot6", Slot6); + HOOK_COMMAND("slot7", Slot7); + HOOK_COMMAND("slot8", Slot8); + HOOK_COMMAND("slot9", Slot9); + HOOK_COMMAND("slot10", Slot10); + HOOK_COMMAND("cancelselect", Close); + HOOK_COMMAND("invnext", NextWeapon); + HOOK_COMMAND("invprev", PrevWeapon); + + Reset(); + + CVAR_CREATE( "hud_drawhistory_time", HISTORY_DRAW_TIME, 0 ); + CVAR_CREATE( "hud_fastswitch", "0", FCVAR_ARCHIVE ); // controls whether or not weapons can be selected in one keypress + + m_iFlags |= HUD_ACTIVE; //!!! + + gWR.Init(); + gHR.Init(); + + return 1; +}; + +void CHudAmmo::Reset(void) +{ + m_fFade = 0; + m_iFlags |= HUD_ACTIVE; //!!! + + gpActiveSel = NULL; + gHUD.m_iHideHUDDisplay = 0; + + gWR.Reset(); + gHR.Reset(); + + // VidInit(); + +} + +int CHudAmmo::VidInit(void) +{ + // Load sprites for buckets (top row of weapon menu) + m_HUD_bucket0 = gHUD.GetSpriteIndex( "bucket1" ); + m_HUD_selection = gHUD.GetSpriteIndex( "selection" ); + + ghsprBuckets = gHUD.GetSprite(m_HUD_bucket0); + giBucketWidth = gHUD.GetSpriteRect(m_HUD_bucket0).right - gHUD.GetSpriteRect(m_HUD_bucket0).left; + giBucketHeight = gHUD.GetSpriteRect(m_HUD_bucket0).bottom - gHUD.GetSpriteRect(m_HUD_bucket0).top; + + gHR.iHistoryGap = max( gHR.iHistoryGap, gHUD.GetSpriteRect(m_HUD_bucket0).bottom - gHUD.GetSpriteRect(m_HUD_bucket0).top); + + // If we've already loaded weapons, let's get new sprites + gWR.LoadAllWeaponSprites(); + + if (ScreenWidth >= 640) + { + giABWidth = 20; + giABHeight = 4; + } + else + { + giABWidth = 10; + giABHeight = 2; + } + + return 1; +} + +// +// Think: +// Used for selection of weapon menu item. +// +void CHudAmmo::Think(void) +{ + if ( gHUD.m_fPlayerDead ) + return; + + if ( gHUD.m_iWeaponBits != gWR.iOldWeaponBits ) + { + gWR.iOldWeaponBits = gHUD.m_iWeaponBits; + + for (int i = MAX_WEAPONS-1; i > 0; i-- ) + { + WEAPON *p = gWR.GetWeapon(i); + + if ( p ) + { + if ( gHUD.m_iWeaponBits & ( 1 << p->iId ) ) + gWR.PickupWeapon( p ); + else + gWR.DropWeapon( p ); + } + } + } + + if (!gpActiveSel) + return; + + // has the player selected one? + if (gHUD.m_iKeyBits & IN_ATTACK) + { + 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); + } + +} + +// +// Helper function to return a Ammo pointer from id +// + +HSPRITE* WeaponsResource :: GetAmmoPicFromWeapon( int iAmmoId, wrect_t& rect ) +{ + for ( int i = 0; i < MAX_WEAPONS; i++ ) + { + if ( rgWeapons[i].iAmmoType == iAmmoId ) + { + rect = rgWeapons[i].rcAmmo; + return &rgWeapons[i].hAmmo; + } + else if ( rgWeapons[i].iAmmo2Type == iAmmoId ) + { + rect = rgWeapons[i].rcAmmo2; + return &rgWeapons[i].hAmmo2; + } + } + + return NULL; +} + + +// Menu Selection Code + +void WeaponsResource :: SelectSlot( int iSlot, int fAdvance, int iDirection ) +{ + if ( gHUD.m_Menu.m_fMenuDisplayed && (fAdvance == FALSE) && (iDirection == 1) ) + { // menu is overriding slot use commands + gHUD.m_Menu.SelectMenuItem( iSlot + 1 ); // slots are one off the key numbers + return; + } + + if ( iSlot > MAX_WEAPON_SLOTS ) + return; + + if ( gHUD.m_fPlayerDead || gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL ) ) + return; + + 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; + + if ( (gpActiveSel == NULL) || (gpActiveSel == (WEAPON *)1) || (iSlot != gpActiveSel->iSlot) ) + { + PlaySound( "common/wpn_hudon.wav", 1 ); + p = GetFirstPos( iSlot ); + + if ( p && fastSwitch ) // check for fast weapon switch mode + { + // if fast weapon switch is on, then weapons can be selected in a single keypress + // but only if there is only one item in the bucket + WEAPON *p2 = GetNextActivePos( p->iSlot, p->iSlotPos ); + if ( !p2 ) + { // only one active item in bucket, so change directly to weapon + ServerCmd( p->szName ); + g_weaponselect = p->iId; + return; + } + } + } + else + { + PlaySound("common/wpn_moveselect.wav", 1); + if ( gpActiveSel ) + p = GetNextActivePos( gpActiveSel->iSlot, gpActiveSel->iSlotPos ); + if ( !p ) + p = GetFirstPos( iSlot ); + } + + + if ( !p ) // no selection found + { + // just display the weapon list, unless fastswitch is on just ignore it + if ( !fastSwitch ) + gpActiveSel = (WEAPON *)1; + else + gpActiveSel = NULL; + } + else + gpActiveSel = p; +} + +//------------------------------------------------------------------------ +// Message Handlers +//------------------------------------------------------------------------ + +// +// AmmoX -- Update the count of a known type of ammo +// +int CHudAmmo::MsgFunc_AmmoX(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + int iIndex = READ_BYTE(); + int iCount = READ_BYTE(); + + gWR.SetAmmo( iIndex, abs(iCount) ); + + return 1; +} + +int CHudAmmo::MsgFunc_AmmoPickup( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + int iIndex = READ_BYTE(); + int iCount = READ_BYTE(); + + // Add ammo to the history + gHR.AddToHistory( HISTSLOT_AMMO, iIndex, abs(iCount) ); + + return 1; +} + +int CHudAmmo::MsgFunc_WeapPickup( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + int iIndex = READ_BYTE(); + + // Add the weapon to the history + gHR.AddToHistory( HISTSLOT_WEAP, iIndex ); + + return 1; +} + +int CHudAmmo::MsgFunc_ItemPickup( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + const char *szName = READ_STRING(); + + // Add the weapon to the history + gHR.AddToHistory( HISTSLOT_ITEM, szName ); + + return 1; +} + + +int CHudAmmo::MsgFunc_HideWeapon( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + gHUD.m_iHideHUDDisplay = READ_BYTE(); + + if (gEngfuncs.IsSpectateOnly()) + return 1; + + if ( gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL ) ) + { + static wrect_t nullrc; + gpActiveSel = NULL; + SetCrosshair( 0, nullrc, 0, 0, 0 ); + } + else + { + if ( m_pWeapon ) + SetCrosshair( m_pWeapon->hCrosshair, m_pWeapon->rcCrosshair, 255, 255, 255 ); + } + + return 1; +} + +// +// CurWeapon: Update hud state with the current weapon and clip count. Ammo +// counts are updated with AmmoX. Server assures that the Weapon ammo type +// numbers match a real ammo type. +// +int CHudAmmo::MsgFunc_CurWeapon(const char *pszName, int iSize, void *pbuf ) +{ + static wrect_t nullrc; + int fOnTarget = FALSE; + + BEGIN_READ( pbuf, iSize ); + + int iState = READ_BYTE(); + int iId = READ_CHAR(); + int iClip = READ_CHAR(); + + // detect if we're also on target + if ( iState > 1 ) + { + fOnTarget = TRUE; + } + + if ( iId < 1 ) + { + SetCrosshair(0, nullrc, 0, 0, 0); + return 0; + } + + if ( g_iUser1 != OBS_IN_EYE ) + { + // Is player dead??? + if ((iId == -1) && (iClip == -1)) + { + gHUD.m_fPlayerDead = TRUE; + gpActiveSel = NULL; + return 1; + } + gHUD.m_fPlayerDead = FALSE; + } + + WEAPON *pWeapon = gWR.GetWeapon( iId ); + + if ( !pWeapon ) + return 0; + + if ( iClip < -1 ) + pWeapon->iClip = abs(iClip); + else + pWeapon->iClip = iClip; + + + if ( iState == 0 ) // we're not the current weapon, so update no more + return 1; + + m_pWeapon = pWeapon; + + if ( gHUD.m_iFOV >= 90 ) + { // normal crosshairs + if (fOnTarget && m_pWeapon->hAutoaim) + SetCrosshair(m_pWeapon->hAutoaim, m_pWeapon->rcAutoaim, 255, 255, 255); + else + SetCrosshair(m_pWeapon->hCrosshair, m_pWeapon->rcCrosshair, 255, 255, 255); + } + else + { // zoomed crosshairs + if (fOnTarget && m_pWeapon->hZoomedAutoaim) + SetCrosshair(m_pWeapon->hZoomedAutoaim, m_pWeapon->rcZoomedAutoaim, 255, 255, 255); + else + SetCrosshair(m_pWeapon->hZoomedCrosshair, m_pWeapon->rcZoomedCrosshair, 255, 255, 255); + + } + + m_fFade = 200.0f; //!!! + m_iFlags |= HUD_ACTIVE; + + return 1; +} + +// +// WeaponList -- Tells the hud about a new weapon type. +// +int CHudAmmo::MsgFunc_WeaponList(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + WEAPON Weapon; + + strcpy( Weapon.szName, READ_STRING() ); + Weapon.iAmmoType = (int)READ_CHAR(); + + Weapon.iMax1 = READ_BYTE(); + if (Weapon.iMax1 == 255) + Weapon.iMax1 = -1; + + Weapon.iAmmo2Type = READ_CHAR(); + Weapon.iMax2 = READ_BYTE(); + if (Weapon.iMax2 == 255) + Weapon.iMax2 = -1; + + Weapon.iSlot = READ_CHAR(); + Weapon.iSlotPos = READ_CHAR(); + Weapon.iId = READ_CHAR(); + Weapon.iFlags = READ_BYTE(); + Weapon.iClip = 0; + + gWR.AddWeapon( &Weapon ); + + return 1; + +} + +//------------------------------------------------------------------------ +// Command Handlers +//------------------------------------------------------------------------ +// Slot button pressed +void CHudAmmo::SlotInput( int iSlot ) +{ + // Let the Viewport use it first, for menus + if ( gViewPort && gViewPort->SlotInput( iSlot ) ) + return; + + gWR.SelectSlot(iSlot, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot1(void) +{ + SlotInput( 0 ); +} + +void CHudAmmo::UserCmd_Slot2(void) +{ + SlotInput( 1 ); +} + +void CHudAmmo::UserCmd_Slot3(void) +{ + SlotInput( 2 ); +} + +void CHudAmmo::UserCmd_Slot4(void) +{ + SlotInput( 3 ); +} + +void CHudAmmo::UserCmd_Slot5(void) +{ + SlotInput( 4 ); +} + +void CHudAmmo::UserCmd_Slot6(void) +{ + SlotInput( 5 ); +} + +void CHudAmmo::UserCmd_Slot7(void) +{ + SlotInput( 6 ); +} + +void CHudAmmo::UserCmd_Slot8(void) +{ + SlotInput( 7 ); +} + +void CHudAmmo::UserCmd_Slot9(void) +{ + SlotInput( 8 ); +} + +void CHudAmmo::UserCmd_Slot10(void) +{ + SlotInput( 9 ); +} + +void CHudAmmo::UserCmd_Close(void) +{ + if (gpActiveSel) + { + gpLastSel = gpActiveSel; + gpActiveSel = NULL; + PlaySound("common/wpn_hudoff.wav", 1); + } + else + ClientCmd("escape"); +} + + +// Selects the next item in the weapon menu +void CHudAmmo::UserCmd_NextWeapon(void) +{ + if ( gHUD.m_fPlayerDead || (gHUD.m_iHideHUDDisplay & (HIDEHUD_WEAPONS | HIDEHUD_ALL)) ) + return; + + if ( !gpActiveSel || gpActiveSel == (WEAPON*)1 ) + gpActiveSel = m_pWeapon; + + int pos = 0; + int slot = 0; + if ( gpActiveSel ) + { + pos = gpActiveSel->iSlotPos + 1; + slot = gpActiveSel->iSlot; + } + + for ( int loop = 0; loop <= 1; loop++ ) + { + for ( ; slot < MAX_WEAPON_SLOTS; slot++ ) + { + for ( ; pos < MAX_WEAPON_POSITIONS; pos++ ) + { + WEAPON *wsp = gWR.GetWeaponSlot( slot, pos ); + + if ( wsp && gWR.HasAmmo(wsp) ) + { + gpActiveSel = wsp; + return; + } + } + + pos = 0; + } + + slot = 0; // start looking from the first slot again + } + + gpActiveSel = NULL; +} + +// Selects the previous item in the menu +void CHudAmmo::UserCmd_PrevWeapon(void) +{ + if ( gHUD.m_fPlayerDead || (gHUD.m_iHideHUDDisplay & (HIDEHUD_WEAPONS | HIDEHUD_ALL)) ) + return; + + if ( !gpActiveSel || gpActiveSel == (WEAPON*)1 ) + gpActiveSel = m_pWeapon; + + int pos = MAX_WEAPON_POSITIONS-1; + int slot = MAX_WEAPON_SLOTS-1; + if ( gpActiveSel ) + { + pos = gpActiveSel->iSlotPos - 1; + slot = gpActiveSel->iSlot; + } + + for ( int loop = 0; loop <= 1; loop++ ) + { + for ( ; slot >= 0; slot-- ) + { + for ( ; pos >= 0; pos-- ) + { + WEAPON *wsp = gWR.GetWeaponSlot( slot, pos ); + + if ( wsp && gWR.HasAmmo(wsp) ) + { + gpActiveSel = wsp; + return; + } + } + + pos = MAX_WEAPON_POSITIONS-1; + } + + slot = MAX_WEAPON_SLOTS-1; + } + + gpActiveSel = NULL; +} + + + +//------------------------------------------------------------------------- +// Drawing code +//------------------------------------------------------------------------- + +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; + + // Draw Weapon Menu + DrawWList(flTime); + + // Draw ammo pickup history + gHR.DrawAmmoHistory( flTime ); + + if (!(m_iFlags & HUD_ACTIVE)) + return 0; + + if (!m_pWeapon) + return 0; + + WEAPON *pw = m_pWeapon; // shorthand + + // SPR_Draw Ammo + 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; + + a = (int) max( MIN_ALPHA, m_fFade ); + + if (m_fFade > 0) + m_fFade -= (gHUD.m_flTimeDelta * 20); + + UnpackRGB(r,g,b, RGB_YELLOWISH); + + ScaleColors(r, g, b, a ); + + // Does this weapon have a clip? + y = ScreenHeight - gHUD.m_iFontHeight - gHUD.m_iFontHeight/2; + + // Does weapon have any ammo at all? + if (m_pWeapon->iAmmoType > 0) + { + int iIconWidth = m_pWeapon->rcAmmo.right - m_pWeapon->rcAmmo.left; + + if (pw->iClip >= 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->iClip, 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_YELLOWISH); + + // 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->iAmmoType), r, g, b); + + + } + else + { + // SPR_Draw a bullets only line + x = ScreenWidth - 4 * AmmoWidth - iIconWidth; + x = gHUD.DrawHudNumber(x, y, iFlags | DHN_3DIGITS, gWR.CountAmmo(pw->iAmmoType), r, g, b); + } + + // 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); + } + + // Does weapon have seconday ammo? + if (pw->iAmmo2Type > 0) + { + int iIconWidth = m_pWeapon->rcAmmo2.right - m_pWeapon->rcAmmo2.left; + + // Do we have secondary ammo? + if ((pw->iAmmo2Type != 0) && (gWR.CountAmmo(pw->iAmmo2Type) > 0)) + { + y -= gHUD.m_iFontHeight + gHUD.m_iFontHeight/4; + x = ScreenWidth - 4 * AmmoWidth - iIconWidth; + x = gHUD.DrawHudNumber(x, y, iFlags|DHN_3DIGITS, gWR.CountAmmo(pw->iAmmo2Type), r, g, b); + + // Draw the ammo Icon + SPR_Set(m_pWeapon->hAmmo2, r, g, b); + int iOffset = (m_pWeapon->rcAmmo2.bottom - m_pWeapon->rcAmmo2.top)/8; + SPR_DrawAdditive(0, x, y - iOffset, &m_pWeapon->rcAmmo2); + } + } + return 1; +} + + +// +// Draws the ammo bar on the hud +// +int DrawBar(int x, int y, int width, int height, float f) +{ + int r, g, b; + + if (f < 0) + f = 0; + if (f > 1) + f = 1; + + if (f) + { + int w = f * width; + + // Always show at least one pixel if we have ammo. + if (w <= 0) + w = 1; + UnpackRGB(r, g, b, RGB_GREENISH); + FillRGBA(x, y, w, height, r, g, b, 255); + x += w; + width -= w; + } + + UnpackRGB(r, g, b, RGB_YELLOWISH); + + FillRGBA(x, y, width, height, r, g, b, 128); + + return (x + width); +} + + + +void DrawAmmoBar(WEAPON *p, int x, int y, int width, int height) +{ + if ( !p ) + return; + + if (p->iAmmoType != -1) + { + if (!gWR.CountAmmo(p->iAmmoType)) + return; + + float f = (float)gWR.CountAmmo(p->iAmmoType)/(float)p->iMax1; + + x = DrawBar(x, y, width, height, f); + + + // Do we have secondary ammo too? + + if (p->iAmmo2Type != -1) + { + f = (float)gWR.CountAmmo(p->iAmmo2Type)/(float)p->iMax2; + + x += 5; //!!! + + DrawBar(x, y, width, height, f); + } + } +} + + + + +// +// Draw Weapon Menu +// +int CHudAmmo::DrawWList(float flTime) +{ + int r,g,b,x,y,a,i; + + if ( !gpActiveSel ) + return 0; + + int iActiveSlot; + + if ( gpActiveSel == (WEAPON *)1 ) + iActiveSlot = -1; // current slot has no weapons + else + iActiveSlot = gpActiveSel->iSlot; + + x = 10; //!!! + y = 10; //!!! + + + // Ensure that there are available choices in the active slot + if ( iActiveSlot > 0 ) + { + if ( !gWR.GetFirstPos( iActiveSlot ) ) + { + gpActiveSel = (WEAPON *)1; + iActiveSlot = -1; + } + } + + // Draw top line + for ( i = 0; i < MAX_WEAPON_SLOTS; i++ ) + { + int iWidth; + + UnpackRGB(r,g,b, RGB_YELLOWISH); + + if ( iActiveSlot == i ) + a = 255; + else + a = 192; + + ScaleColors(r, g, b, 255); + SPR_Set(gHUD.GetSprite(m_HUD_bucket0 + i), r, g, b ); + + // make active slot wide enough to accomodate gun pictures + if ( i == iActiveSlot ) + { + WEAPON *p = gWR.GetFirstPos(iActiveSlot); + if ( p ) + iWidth = p->rcActive.right - p->rcActive.left; + else + iWidth = giBucketWidth; + } + else + iWidth = giBucketWidth; + + SPR_DrawAdditive(0, x, y, &gHUD.GetSpriteRect(m_HUD_bucket0 + i)); + + x += iWidth + 5; + } + + + a = 128; //!!! + x = 10; + + // Draw all of the buckets + for (i = 0; i < MAX_WEAPON_SLOTS; i++) + { + y = giBucketHeight + 10; + + // If this is the active slot, draw the bigger pictures, + // otherwise just draw boxes + if ( i == iActiveSlot ) + { + WEAPON *p = gWR.GetFirstPos( i ); + int iWidth = giBucketWidth; + if ( p ) + iWidth = p->rcActive.right - p->rcActive.left; + + for ( int iPos = 0; iPos < MAX_WEAPON_POSITIONS; iPos++ ) + { + p = gWR.GetWeaponSlot( i, iPos ); + + if ( !p || !p->iId ) + continue; + + UnpackRGB( r,g,b, RGB_YELLOWISH ); + + // if active, then we must have ammo. + + if ( gpActiveSel == p ) + { + SPR_Set(p->hActive, r, g, b ); + SPR_DrawAdditive(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)); + } + else + { + // Draw Weapon if Red if no ammo + + if ( gWR.HasAmmo(p) ) + ScaleColors(r, g, b, 192); + else + { + UnpackRGB(r,g,b, RGB_REDISH); + ScaleColors(r, g, b, 128); + } + + SPR_Set( p->hInactive, r, g, b ); + SPR_DrawAdditive( 0, x, y, &p->rcInactive ); + } + + // Draw Ammo Bar + + DrawAmmoBar(p, x + giABWidth/2, y, giABWidth, giABHeight); + + y += p->rcActive.bottom - p->rcActive.top + 5; + } + + x += iWidth + 5; + + } + else + { + // Draw Row of weapons. + + UnpackRGB(r,g,b, RGB_YELLOWISH); + + for ( int iPos = 0; iPos < MAX_WEAPON_POSITIONS; iPos++ ) + { + WEAPON *p = gWR.GetWeaponSlot( i, iPos ); + + if ( !p || !p->iId ) + continue; + + if ( gWR.HasAmmo(p) ) + { + UnpackRGB(r,g,b, RGB_YELLOWISH); + a = 128; + } + else + { + UnpackRGB(r,g,b, RGB_REDISH); + a = 96; + } + + FillRGBA( x, y, giBucketWidth, giBucketHeight, r, g, b, a ); + + y += giBucketHeight + 5; + } + + x += giBucketWidth + 5; + } + } + + return 1; + +} + + +/* ================================= + GetSpriteList + +Finds and returns the matching +sprite name 'psz' and resolution 'iRes' +in the given sprite list 'pList' +iCount is the number of items in the pList +================================= */ +client_sprite_t *GetSpriteList(client_sprite_t *pList, const char *psz, int iRes, int iCount) +{ + if (!pList) + return NULL; + + int i = iCount; + client_sprite_t *p = pList; + + while(i--) + { + if ((!strcmp(psz, p->szName)) && (p->iRes == iRes)) + return p; + p++; + } + + return NULL; +} diff --git a/hlsdk/cl_dll/ammo.h b/hlsdk/cl_dll/ammo.h new file mode 100644 index 00000000..ad730cc8 --- /dev/null +++ b/hlsdk/cl_dll/ammo.h @@ -0,0 +1,62 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef __AMMO_H__ +#define __AMMO_H__ + +#define MAX_WEAPON_NAME 128 + + +#define WEAPON_FLAGS_SELECTONEMPTY 1 + +#define WEAPON_IS_ONTARGET 0x40 + +struct WEAPON +{ + char szName[MAX_WEAPON_NAME]; + int iAmmoType; + int iAmmo2Type; + int iMax1; + int iMax2; + int iSlot; + int iSlotPos; + int iFlags; + int iId; + int iClip; + + int iCount; // # of itesm in plist + + HSPRITE hActive; + wrect_t rcActive; + HSPRITE hInactive; + wrect_t rcInactive; + HSPRITE hAmmo; + wrect_t rcAmmo; + HSPRITE hAmmo2; + wrect_t rcAmmo2; + HSPRITE hCrosshair; + wrect_t rcCrosshair; + HSPRITE hAutoaim; + wrect_t rcAutoaim; + HSPRITE hZoomedCrosshair; + wrect_t rcZoomedCrosshair; + HSPRITE hZoomedAutoaim; + wrect_t rcZoomedAutoaim; +}; + +typedef int AMMO; + + +#endif \ No newline at end of file diff --git a/hlsdk/cl_dll/ammo_secondary.cpp b/hlsdk/cl_dll/ammo_secondary.cpp new file mode 100644 index 00000000..2ca07d2d --- /dev/null +++ b/hlsdk/cl_dll/ammo_secondary.cpp @@ -0,0 +1,159 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// ammo_secondary.cpp +// +// implementation of CHudAmmoSecondary class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE( m_AmmoSecondary, SecAmmoVal ); +DECLARE_MESSAGE( m_AmmoSecondary, SecAmmoIcon ); + +int CHudAmmoSecondary :: Init( void ) +{ + HOOK_MESSAGE( SecAmmoVal ); + HOOK_MESSAGE( SecAmmoIcon ); + + gHUD.AddHudElem(this); + m_HUD_ammoicon = 0; + + for ( int i = 0; i < MAX_SEC_AMMO_VALUES; i++ ) + m_iAmmoAmounts[i] = -1; // -1 means don't draw this value + + Reset(); + + return 1; +} + +void CHudAmmoSecondary :: Reset( void ) +{ + m_fFade = 0; +} + +int CHudAmmoSecondary :: VidInit( void ) +{ + return 1; +} + +int CHudAmmoSecondary :: Draw(float flTime) +{ + if ( (gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL )) ) + return 1; + + // draw secondary ammo icons above normal ammo readout + int a, x, y, r, g, b, AmmoWidth; + UnpackRGB( r, g, b, RGB_YELLOWISH ); + a = (int) max( MIN_ALPHA, m_fFade ); + if (m_fFade > 0) + m_fFade -= (gHUD.m_flTimeDelta * 20); // slowly lower alpha to fade out icons + ScaleColors( r, g, b, a ); + + AmmoWidth = gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left; + + y = ScreenHeight - (gHUD.m_iFontHeight*4); // this is one font height higher than the weapon ammo values + x = ScreenWidth - AmmoWidth; + + if ( m_HUD_ammoicon ) + { + // Draw the ammo icon + x -= (gHUD.GetSpriteRect(m_HUD_ammoicon).right - gHUD.GetSpriteRect(m_HUD_ammoicon).left); + y -= (gHUD.GetSpriteRect(m_HUD_ammoicon).top - gHUD.GetSpriteRect(m_HUD_ammoicon).bottom); + + SPR_Set( gHUD.GetSprite(m_HUD_ammoicon), r, g, b ); + SPR_DrawAdditive( 0, x, y, &gHUD.GetSpriteRect(m_HUD_ammoicon) ); + } + else + { // move the cursor by the '0' char instead, since we don't have an icon to work with + x -= AmmoWidth; + y -= (gHUD.GetSpriteRect(gHUD.m_HUD_number_0).top - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).bottom); + } + + // draw the ammo counts, in reverse order, from right to left + for ( int i = MAX_SEC_AMMO_VALUES-1; i >= 0; i-- ) + { + if ( m_iAmmoAmounts[i] < 0 ) + continue; // negative ammo amounts imply that they shouldn't be drawn + + // half a char gap between the ammo number and the previous pic + x -= (AmmoWidth / 2); + + // draw the number, right-aligned + x -= (gHUD.GetNumWidth( m_iAmmoAmounts[i], DHN_DRAWZERO ) * AmmoWidth); + gHUD.DrawHudNumber( x, y, DHN_DRAWZERO, m_iAmmoAmounts[i], r, g, b ); + + if ( i != 0 ) + { + // draw the divider bar + x -= (AmmoWidth / 2); + FillRGBA(x, y, (AmmoWidth/10), gHUD.m_iFontHeight, r, g, b, a); + } + } + + return 1; +} + +// Message handler for Secondary Ammo Value +// accepts one value: +// string: sprite name +int CHudAmmoSecondary :: MsgFunc_SecAmmoIcon( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + m_HUD_ammoicon = gHUD.GetSpriteIndex( READ_STRING() ); + + return 1; +} + +// Message handler for Secondary Ammo Icon +// Sets an ammo value +// takes two values: +// byte: ammo index +// byte: ammo value +int CHudAmmoSecondary :: MsgFunc_SecAmmoVal( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int index = READ_BYTE(); + if ( index < 0 || index >= MAX_SEC_AMMO_VALUES ) + return 1; + + m_iAmmoAmounts[index] = READ_BYTE(); + m_iFlags |= HUD_ACTIVE; + + // check to see if there is anything left to draw + int count = 0; + for ( int i = 0; i < MAX_SEC_AMMO_VALUES; i++ ) + { + count += max( 0, m_iAmmoAmounts[i] ); + } + + if ( count == 0 ) + { // the ammo fields are all empty, so turn off this hud area + m_iFlags &= ~HUD_ACTIVE; + return 1; + } + + // make the icons light up + m_fFade = 200.0f; + + return 1; +} + + diff --git a/hlsdk/cl_dll/ammohistory.cpp b/hlsdk/cl_dll/ammohistory.cpp new file mode 100644 index 00000000..bfe70058 --- /dev/null +++ b/hlsdk/cl_dll/ammohistory.cpp @@ -0,0 +1,190 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// ammohistory.cpp +// + + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "ammohistory.h" + +HistoryResource gHR; + +#define AMMO_PICKUP_GAP (gHR.iHistoryGap+5) +#define AMMO_PICKUP_PICK_HEIGHT (32 + (gHR.iHistoryGap * 2)) +#define AMMO_PICKUP_HEIGHT_MAX (ScreenHeight - 100) + +#define MAX_ITEM_NAME 32 +int HISTORY_DRAW_TIME = 5; + +// keep a list of items +struct ITEM_INFO +{ + char szName[MAX_ITEM_NAME]; + HSPRITE spr; + wrect_t rect; +}; + +void HistoryResource :: AddToHistory( int iType, int iId, int iCount ) +{ + if ( iType == HISTSLOT_AMMO && !iCount ) + return; // no amount, so don't add + + if ( (((AMMO_PICKUP_GAP * iCurrentHistorySlot) + AMMO_PICKUP_PICK_HEIGHT) > AMMO_PICKUP_HEIGHT_MAX) || (iCurrentHistorySlot >= MAX_HISTORY) ) + { // the pic would have to be drawn too high + // so start from the bottom + iCurrentHistorySlot = 0; + } + + HIST_ITEM *freeslot = &rgAmmoHistory[iCurrentHistorySlot++]; // default to just writing to the first slot + HISTORY_DRAW_TIME = CVAR_GET_FLOAT( "hud_drawhistory_time" ); + + freeslot->type = iType; + freeslot->iId = iId; + freeslot->iCount = iCount; + freeslot->DisplayTime = gHUD.m_flTime + HISTORY_DRAW_TIME; +} + +void HistoryResource :: AddToHistory( int iType, const char *szName, int iCount ) +{ + if ( iType != HISTSLOT_ITEM ) + return; + + if ( (((AMMO_PICKUP_GAP * iCurrentHistorySlot) + AMMO_PICKUP_PICK_HEIGHT) > AMMO_PICKUP_HEIGHT_MAX) || (iCurrentHistorySlot >= MAX_HISTORY) ) + { // the pic would have to be drawn too high + // so start from the bottom + iCurrentHistorySlot = 0; + } + + HIST_ITEM *freeslot = &rgAmmoHistory[iCurrentHistorySlot++]; // default to just writing to the first slot + + // I am really unhappy with all the code in this file + + int i = gHUD.GetSpriteIndex( szName ); + if ( i == -1 ) + return; // unknown sprite name, don't add it to history + + freeslot->iId = i; + freeslot->type = iType; + freeslot->iCount = iCount; + + HISTORY_DRAW_TIME = CVAR_GET_FLOAT( "hud_drawhistory_time" ); + freeslot->DisplayTime = gHUD.m_flTime + HISTORY_DRAW_TIME; +} + + +void HistoryResource :: CheckClearHistory( void ) +{ + for ( int i = 0; i < MAX_HISTORY; i++ ) + { + if ( rgAmmoHistory[i].type ) + return; + } + + iCurrentHistorySlot = 0; +} + +// +// Draw Ammo pickup history +// +int HistoryResource :: DrawAmmoHistory( float flTime ) +{ + for ( int i = 0; i < MAX_HISTORY; i++ ) + { + if ( rgAmmoHistory[i].type ) + { + rgAmmoHistory[i].DisplayTime = min( rgAmmoHistory[i].DisplayTime, gHUD.m_flTime + HISTORY_DRAW_TIME ); + + if ( rgAmmoHistory[i].DisplayTime <= flTime ) + { // pic drawing time has expired + memset( &rgAmmoHistory[i], 0, sizeof(HIST_ITEM) ); + CheckClearHistory(); + } + else if ( rgAmmoHistory[i].type == HISTSLOT_AMMO ) + { + wrect_t rcPic; + HSPRITE *spr = gWR.GetAmmoPicFromWeapon( rgAmmoHistory[i].iId, rcPic ); + + int r, g, b; + UnpackRGB(r,g,b, RGB_YELLOWISH); + float scale = (rgAmmoHistory[i].DisplayTime - flTime) * 80; + ScaleColors(r, g, b, min(scale, 255) ); + + // Draw the pic + int ypos = ScreenHeight - (AMMO_PICKUP_PICK_HEIGHT + (AMMO_PICKUP_GAP * i)); + int xpos = ScreenWidth - 24; + if ( spr && *spr ) // weapon isn't loaded yet so just don't draw the pic + { // the dll has to make sure it has sent info the weapons you need + SPR_Set( *spr, r, g, b ); + SPR_DrawAdditive( 0, xpos, ypos, &rcPic ); + } + + // Draw the number + gHUD.DrawHudNumberString( xpos - 10, ypos, xpos - 100, rgAmmoHistory[i].iCount, r, g, b ); + } + else if ( rgAmmoHistory[i].type == HISTSLOT_WEAP ) + { + WEAPON *weap = gWR.GetWeapon( rgAmmoHistory[i].iId ); + + if ( !weap ) + 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); + + if ( !gWR.HasAmmo( weap ) ) + UnpackRGB(r,g,b, RGB_REDISH); // if the weapon doesn't have ammo, display it as red + + float scale = (rgAmmoHistory[i].DisplayTime - flTime) * 80; + ScaleColors(r, g, b, min(scale, 255) ); + + int ypos = ScreenHeight - (AMMO_PICKUP_PICK_HEIGHT + (AMMO_PICKUP_GAP * i)); + int xpos = ScreenWidth - (weap->rcInactive.right - weap->rcInactive.left); + SPR_Set( weap->hInactive, r, g, b ); + SPR_DrawAdditive( 0, xpos, ypos, &weap->rcInactive ); + } + else if ( rgAmmoHistory[i].type == HISTSLOT_ITEM ) + { + int r, g, b; + + if ( !rgAmmoHistory[i].iId ) + continue; // sprite not loaded + + wrect_t rect = gHUD.GetSpriteRect( rgAmmoHistory[i].iId ); + + UnpackRGB(r,g,b, RGB_YELLOWISH); + float scale = (rgAmmoHistory[i].DisplayTime - flTime) * 80; + ScaleColors(r, g, b, min(scale, 255) ); + + int ypos = ScreenHeight - (AMMO_PICKUP_PICK_HEIGHT + (AMMO_PICKUP_GAP * i)); + int xpos = ScreenWidth - (rect.right - rect.left) - 10; + + SPR_Set( gHUD.GetSprite( rgAmmoHistory[i].iId ), r, g, b ); + SPR_DrawAdditive( 0, xpos, ypos, &rect ); + } + } + } + + + return 1; +} + + diff --git a/hlsdk/cl_dll/ammohistory.h b/hlsdk/cl_dll/ammohistory.h new file mode 100644 index 00000000..74bf43f5 --- /dev/null +++ b/hlsdk/cl_dll/ammohistory.h @@ -0,0 +1,143 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// ammohistory.h +// + +// this is the max number of items in each bucket +#define MAX_WEAPON_POSITIONS MAX_WEAPON_SLOTS + +class WeaponsResource +{ +private: + // Information about weapons & ammo + WEAPON rgWeapons[MAX_WEAPONS]; // Weapons Array + + // 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 ) + { + memset( rgWeapons, 0, sizeof rgWeapons ); + Reset(); + } + + void Reset( void ) + { + iOldWeaponBits = 0; + memset( rgSlots, 0, sizeof rgSlots ); + memset( riAmmo, 0, sizeof riAmmo ); + } + +///// WEAPON ///// + int iOldWeaponBits; + + WEAPON *GetWeapon( int iId ) { return &rgWeapons[iId]; } + void AddWeapon( WEAPON *wp ) + { + rgWeapons[ wp->iId ] = *wp; + LoadWeaponSprites( &rgWeapons[ wp->iId ] ); + } + + void PickupWeapon( WEAPON *wp ) + { + rgSlots[ wp->iSlot ][ wp->iSlotPos ] = wp; + } + + void DropWeapon( WEAPON *wp ) + { + rgSlots[ wp->iSlot ][ wp->iSlotPos ] = NULL; + } + + void DropAllWeapons( void ) + { + for ( int i = 0; i < MAX_WEAPONS; i++ ) + { + if ( rgWeapons[i].iId ) + DropWeapon( &rgWeapons[i] ); + } + } + + WEAPON* GetWeaponSlot( int slot, int pos ) { return rgSlots[slot][pos]; } + + void LoadWeaponSprites( WEAPON* wp ); + void LoadAllWeaponSprites( void ); + WEAPON* GetFirstPos( int iSlot ); + void SelectSlot( int iSlot, int fAdvance, int iDirection ); + WEAPON* GetNextActivePos( int iSlot, int iSlotPos ); + + int HasAmmo( WEAPON *p ); + +///// AMMO ///// + AMMO GetAmmo( int iId ) { return iId; } + + void SetAmmo( int iId, int iCount ) { riAmmo[ iId ] = iCount; } + + int CountAmmo( int iId ); + + HSPRITE* GetAmmoPicFromWeapon( int iAmmoId, wrect_t& rect ); +}; + +extern WeaponsResource gWR; + + +#define MAX_HISTORY 12 +enum { + HISTSLOT_EMPTY, + HISTSLOT_AMMO, + HISTSLOT_WEAP, + HISTSLOT_ITEM, +}; + +class HistoryResource +{ +private: + struct HIST_ITEM { + int type; + float DisplayTime; // the time at which this item should be removed from the history + int iCount; + int iId; + }; + + HIST_ITEM rgAmmoHistory[MAX_HISTORY]; + +public: + + void Init( void ) + { + Reset(); + } + + void Reset( void ) + { + memset( rgAmmoHistory, 0, sizeof rgAmmoHistory ); + } + + int iHistoryGap; + int iCurrentHistorySlot; + + void AddToHistory( int iType, int iId, int iCount = 0 ); + void AddToHistory( int iType, const char *szName, int iCount = 0 ); + + void CheckClearHistory( void ); + int DrawAmmoHistory( float flTime ); +}; + +extern HistoryResource gHR; + + + diff --git a/hlsdk/cl_dll/battery.cpp b/hlsdk/cl_dll/battery.cpp new file mode 100644 index 00000000..0ec45542 --- /dev/null +++ b/hlsdk/cl_dll/battery.cpp @@ -0,0 +1,138 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// battery.cpp +// +// implementation of CHudBattery class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +DECLARE_MESSAGE(m_Battery, Battery) + +int CHudBattery::Init(void) +{ + m_iBat = 0; + m_fFade = 0; + m_iFlags = 0; + + HOOK_MESSAGE(Battery); + + gHUD.AddHudElem(this); + + return 1; +}; + + +int CHudBattery::VidInit(void) +{ + int HUD_suit_empty = gHUD.GetSpriteIndex( "suit_empty" ); + int HUD_suit_full = gHUD.GetSpriteIndex( "suit_full" ); + + m_hSprite1 = m_hSprite2 = 0; // delaying get sprite handles until we know the sprites are loaded + m_prc1 = &gHUD.GetSpriteRect( HUD_suit_empty ); + m_prc2 = &gHUD.GetSpriteRect( HUD_suit_full ); + m_iHeight = m_prc2->bottom - m_prc1->top; + m_fFade = 0; + return 1; +}; + +int CHudBattery:: MsgFunc_Battery(const char *pszName, int iSize, void *pbuf ) +{ + m_iFlags |= HUD_ACTIVE; + + + BEGIN_READ( pbuf, iSize ); + int x = READ_SHORT(); + + if (x != m_iBat) + { + m_fFade = FADE_TIME; + m_iBat = x; + } + + return 1; +} + + +int CHudBattery::Draw(float flTime) +{ + if ( gHUD.m_iHideHUDDisplay & HIDEHUD_HEALTH ) + return 1; + + int r, g, b, x, y, a; + wrect_t rc; + + rc = *m_prc2; + rc.top += m_iHeight * ((float)(100-(min(100,m_iBat))) * 0.01); // battery can go from 0 to 100 so * 0.01 goes from 0 to 1 + + UnpackRGB(r,g,b, RGB_YELLOWISH); + + if (!(gHUD.m_iWeaponBits & (1<<(WEAPON_SUIT)) )) + return 1; + + // Has health changed? Flash the health # + if (m_fFade) + { + if (m_fFade > FADE_TIME) + m_fFade = FADE_TIME; + + m_fFade -= (gHUD.m_flTimeDelta * 20); + if (m_fFade <= 0) + { + a = 128; + m_fFade = 0; + } + + // Fade the health number back to dim + + a = MIN_ALPHA + (m_fFade/FADE_TIME) * 128; + + } + else + a = MIN_ALPHA; + + ScaleColors(r, g, b, a ); + + int iOffset = (m_prc1->bottom - m_prc1->top)/6; + + y = ScreenHeight - gHUD.m_iFontHeight - gHUD.m_iFontHeight / 2; + x = ScreenWidth/5; + + // make sure we have the right sprite handles + if ( !m_hSprite1 ) + m_hSprite1 = gHUD.GetSprite( gHUD.GetSpriteIndex( "suit_empty" ) ); + if ( !m_hSprite2 ) + m_hSprite2 = gHUD.GetSprite( gHUD.GetSpriteIndex( "suit_full" ) ); + + SPR_Set(m_hSprite1, r, g, b ); + SPR_DrawAdditive( 0, x, y - iOffset, m_prc1); + + if (rc.bottom > rc.top) + { + SPR_Set(m_hSprite2, r, g, b ); + SPR_DrawAdditive( 0, x, y - iOffset + (rc.top - m_prc2->top), &rc); + } + + x += (m_prc1->right - m_prc1->left); + x = gHUD.DrawHudNumber(x, y, DHN_3DIGITS | DHN_DRAWZERO, m_iBat, r, g, b); + + return 1; +} \ No newline at end of file diff --git a/hlsdk/cl_dll/camera.h b/hlsdk/cl_dll/camera.h new file mode 100644 index 00000000..af1591d7 --- /dev/null +++ b/hlsdk/cl_dll/camera.h @@ -0,0 +1,24 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// Camera.h -- defines and such for a 3rd person camera +// NOTE: must include quakedef.h first + +#ifndef _CAMERA_H_ +#define _CAMEA_H_ + +// pitch, yaw, dist +extern vec3_t cam_ofs; +// Using third person camera +extern int cam_thirdperson; + +void CAM_Init( void ); +void CAM_ClearStates( void ); +void CAM_StartMouseMove(void); +void CAM_EndMouseMove(void); + +#endif // _CAMERA_H_ diff --git a/hlsdk/cl_dll/cdll_int.cpp b/hlsdk/cl_dll/cdll_int.cpp new file mode 100644 index 00000000..ee2a20f3 --- /dev/null +++ b/hlsdk/cl_dll/cdll_int.cpp @@ -0,0 +1,286 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cdll_int.c +// +// this implementation handles the linking of the engine to the DLL +// + +#include "hud.h" +#include "cl_util.h" +#include "netadr.h" +#include "vgui_schememanager.h" + +extern "C" +{ +#include "pm_shared.h" +} + +#include +#include "hud_servers.h" +#include "vgui_int.h" +#include "interface.h" + +#define DLLEXPORT __declspec( dllexport ) + + +cl_enginefunc_t gEngfuncs; +CHud gHUD; +TeamFortressViewport *gViewPort = NULL; + +void InitInput (void); +void EV_HookEvents( void ); +void IN_Commands( void ); + +/* +========================== + Initialize + +Called when the DLL is first loaded. +========================== +*/ +extern "C" +{ +int DLLEXPORT Initialize( cl_enginefunc_t *pEnginefuncs, int iVersion ); +int DLLEXPORT HUD_VidInit( void ); +void DLLEXPORT HUD_Init( void ); +int DLLEXPORT HUD_Redraw( float flTime, int intermission ); +int DLLEXPORT HUD_UpdateClientData( client_data_t *cdata, float flTime ); +void DLLEXPORT HUD_Reset ( void ); +void DLLEXPORT HUD_PlayerMove( struct playermove_s *ppmove, int server ); +void DLLEXPORT HUD_PlayerMoveInit( struct playermove_s *ppmove ); +char DLLEXPORT HUD_PlayerMoveTexture( char *name ); +int DLLEXPORT HUD_ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); +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_DirectorMessage( int iSize, void *pbuf ); +} + +/* +================================ +HUD_GetHullBounds + + Engine calls this to enumerate player collision hulls, for prediction. Return 0 if the hullnumber doesn't exist. +================================ +*/ +int DLLEXPORT HUD_GetHullBounds( int hullnumber, float *mins, float *maxs ) +{ + int iret = 0; + + switch ( hullnumber ) + { + case 0: // Normal player + mins = Vector(-16, -16, -36); + maxs = Vector(16, 16, 36); + iret = 1; + break; + case 1: // Crouched player + mins = Vector(-16, -16, -18 ); + maxs = Vector(16, 16, 18 ); + iret = 1; + break; + case 2: // Point based hull + mins = Vector( 0, 0, 0 ); + maxs = Vector( 0, 0, 0 ); + iret = 1; + break; + } + + return iret; +} + +/* +================================ +HUD_ConnectionlessPacket + + Return 1 if the packet is valid. Set response_buffer_size if you want to send a response packet. Incoming, it holds the max + size of the response_buffer, so you must zero it out if you choose not to respond. +================================ +*/ +int DLLEXPORT HUD_ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ) +{ + // Parse stuff from args + int max_buffer_size = *response_buffer_size; + + // Zero it out since we aren't going to respond. + // If we wanted to response, we'd write data into response_buffer + *response_buffer_size = 0; + + // Since we don't listen for anything here, just respond that it's a bogus message + // If we didn't reject the message, we'd return 1 for success instead. + return 0; +} + +void DLLEXPORT HUD_PlayerMoveInit( struct playermove_s *ppmove ) +{ + PM_Init( ppmove ); +} + +char DLLEXPORT HUD_PlayerMoveTexture( char *name ) +{ + return PM_FindTextureType( name ); +} + +void DLLEXPORT HUD_PlayerMove( struct playermove_s *ppmove, int server ) +{ + PM_Move( ppmove, server ); +} + +int DLLEXPORT Initialize( cl_enginefunc_t *pEnginefuncs, int iVersion ) +{ + gEngfuncs = *pEnginefuncs; + + if (iVersion != CLDLL_INTERFACE_VERSION) + return 0; + + memcpy(&gEngfuncs, pEnginefuncs, sizeof(cl_enginefunc_t)); + + EV_HookEvents(); + + return 1; +} + + +/* +========================== + HUD_VidInit + +Called when the game initializes +and whenever the vid_mode is changed +so the HUD can reinitialize itself. +========================== +*/ + +int DLLEXPORT HUD_VidInit( void ) +{ + gHUD.VidInit(); + + VGui_Startup(); + + return 1; +} + +/* +========================== + HUD_Init + +Called whenever the client connects +to a server. Reinitializes all +the hud variables. +========================== +*/ + +void DLLEXPORT HUD_Init( void ) +{ + InitInput(); + gHUD.Init(); + Scheme_Init(); +} + + +/* +========================== + HUD_Redraw + +called every screen frame to +redraw the HUD. +=========================== +*/ + +int DLLEXPORT HUD_Redraw( float time, int intermission ) +{ + gHUD.Redraw( time, intermission ); + + return 1; +} + + +/* +========================== + HUD_UpdateClientData + +called every time shared client +dll/engine data gets changed, +and gives the cdll a chance +to modify the data. + +returns 1 if anything has been changed, 0 otherwise. +========================== +*/ + +int DLLEXPORT HUD_UpdateClientData(client_data_t *pcldata, float flTime ) +{ + IN_Commands(); + + return gHUD.UpdateClientData(pcldata, flTime ); +} + +/* +========================== + HUD_Reset + +Called at start and end of demos to restore to "non"HUD state. +========================== +*/ + +void DLLEXPORT HUD_Reset( void ) +{ + gHUD.VidInit(); +} + +/* +========================== +HUD_Frame + +Called by engine every frame that client .dll is loaded +========================== +*/ + +void DLLEXPORT HUD_Frame( double time ) +{ + ServersThink( time ); + + GetClientVoiceMgr()->Frame(time); +} + + +/* +========================== +HUD_VoiceStatus + +Called when a player starts or stops talking. +========================== +*/ + +void DLLEXPORT HUD_VoiceStatus(int entindex, qboolean bTalking) +{ + GetClientVoiceMgr()->UpdateSpeakerStatus(entindex, bTalking); +} + +/* +========================== +HUD_DirectorEvent + +Called when a director event message was received +========================== +*/ + +void DLLEXPORT HUD_DirectorMessage( int iSize, void *pbuf ) +{ + gHUD.m_Spectator.DirectorMessage( iSize, pbuf ); +} + + diff --git a/hlsdk/cl_dll/cl_dll.h b/hlsdk/cl_dll/cl_dll.h new file mode 100644 index 00000000..2bb3d1ac --- /dev/null +++ b/hlsdk/cl_dll/cl_dll.h @@ -0,0 +1,39 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cl_dll.h +// + +// 4-23-98 JOHN + +// +// This DLL is linked by the client when they first initialize. +// This DLL is responsible for the following tasks: +// - Loading the HUD graphics upon initialization +// - Drawing the HUD graphics every frame +// - Handling the custum HUD-update packets +// +typedef unsigned char byte; +typedef unsigned short word; +typedef float vec_t; +typedef int (*pfnUserMsgHook)(const char *pszName, int iSize, void *pbuf); + +#include "util_vector.h" +#define EXPORT _declspec( dllexport ) + +#include "../engine/cdll_int.h" +#include "../dlls/cdll_dll.h" + +extern cl_enginefunc_t gEngfuncs; diff --git a/hlsdk/cl_dll/cl_util.h b/hlsdk/cl_dll/cl_util.h new file mode 100644 index 00000000..f72d5892 --- /dev/null +++ b/hlsdk/cl_dll/cl_util.h @@ -0,0 +1,160 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cl_util.h +// + +#include "cvardef.h" + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +// Macros to hook function calls into the HUD object +#define HOOK_MESSAGE(x) gEngfuncs.pfnHookUserMsg(#x, __MsgFunc_##x ); + +#define DECLARE_MESSAGE(y, x) int __MsgFunc_##x(const char *pszName, int iSize, void *pbuf) \ + { \ + return gHUD.##y.MsgFunc_##x(pszName, iSize, pbuf ); \ + } + + +#define HOOK_COMMAND(x, y) gEngfuncs.pfnAddCommand( x, __CmdFunc_##y ); +#define DECLARE_COMMAND(y, x) void __CmdFunc_##x( void ) \ + { \ + gHUD.##y.UserCmd_##x( ); \ + } + +inline float CVAR_GET_FLOAT( const char *x ) { return gEngfuncs.pfnGetCvarFloat( (char*)x ); } +inline char* CVAR_GET_STRING( const char *x ) { return gEngfuncs.pfnGetCvarString( (char*)x ); } +inline struct cvar_s *CVAR_CREATE( const char *cv, const char *val, const int flags ) { return gEngfuncs.pfnRegisterVariable( (char*)cv, (char*)val, flags ); } + +#define SPR_Load (*gEngfuncs.pfnSPR_Load) +#define SPR_Set (*gEngfuncs.pfnSPR_Set) +#define SPR_Frames (*gEngfuncs.pfnSPR_Frames) +#define SPR_GetList (*gEngfuncs.pfnSPR_GetList) + +// SPR_Draw draws a the current sprite as solid +#define SPR_Draw (*gEngfuncs.pfnSPR_Draw) +// SPR_DrawHoles draws the current sprites, with color index255 not drawn (transparent) +#define SPR_DrawHoles (*gEngfuncs.pfnSPR_DrawHoles) +// SPR_DrawAdditive adds the sprites RGB values to the background (additive transulency) +#define SPR_DrawAdditive (*gEngfuncs.pfnSPR_DrawAdditive) + +// SPR_EnableScissor sets a clipping rect for HUD sprites. (0,0) is the top-left hand corner of the screen. +#define SPR_EnableScissor (*gEngfuncs.pfnSPR_EnableScissor) +// SPR_DisableScissor disables the clipping rect +#define SPR_DisableScissor (*gEngfuncs.pfnSPR_DisableScissor) +// +#define FillRGBA (*gEngfuncs.pfnFillRGBA) + + +// ScreenHeight returns the height of the screen, in pixels +#define ScreenHeight (gHUD.m_scrinfo.iHeight) +// ScreenWidth returns the width of the screen, in pixels +#define ScreenWidth (gHUD.m_scrinfo.iWidth) + +// Use this to set any co-ords in 640x480 space +#define XRES(x) ((int)(float(x) * ((float)ScreenWidth / 640.0f) + 0.5f)) +#define YRES(y) ((int)(float(y) * ((float)ScreenHeight / 480.0f) + 0.5f)) + +// use this to project world coordinates to screen coordinates +#define XPROJECT(x) ( (1.0f+(x))*ScreenWidth*0.5f ) +#define YPROJECT(y) ( (1.0f-(y))*ScreenHeight*0.5f ) + +#define GetScreenInfo (*gEngfuncs.pfnGetScreenInfo) +#define ServerCmd (*gEngfuncs.pfnServerCmd) +#define ClientCmd (*gEngfuncs.pfnClientCmd) +#define SetCrosshair (*gEngfuncs.pfnSetCrosshair) +#define AngleVectors (*gEngfuncs.pfnAngleVectors) + + +// Gets the height & width of a sprite, at the specified frame +inline int SPR_Height( HSPRITE x, int f ) { return gEngfuncs.pfnSPR_Height(x, f); } +inline int SPR_Width( HSPRITE x, int f ) { return gEngfuncs.pfnSPR_Width(x, f); } + +inline client_textmessage_t *TextMessageGet( const char *pName ) { return gEngfuncs.pfnTextMessageGet( pName ); } +inline int TextMessageDrawChar( int x, int y, int number, int r, int g, int b ) +{ + return gEngfuncs.pfnDrawCharacter( x, y, number, r, g, b ); +} + +inline int DrawConsoleString( int x, int y, const char *string ) +{ + return gEngfuncs.pfnDrawConsoleString( x, y, (char*) string ); +} + +inline void GetConsoleStringSize( const char *string, int *width, int *height ) +{ + gEngfuncs.pfnDrawConsoleStringLen( string, width, height ); +} + +inline int ConsoleStringLen( const char *string ) +{ + int _width, _height; + GetConsoleStringSize( string, &_width, &_height ); + return _width; +} + +inline void ConsolePrint( const char *string ) +{ + gEngfuncs.pfnConsolePrint( string ); +} + +inline void CenterPrint( const char *string ) +{ + gEngfuncs.pfnCenterPrint( string ); +} + +// returns the players name of entity no. +#define GetPlayerInfo (*gEngfuncs.pfnGetPlayerInfo) + +// sound functions +inline void PlaySound( char *szSound, float vol ) { gEngfuncs.pfnPlaySoundByName( szSound, vol ); } +inline void PlaySound( int iSound, float vol ) { gEngfuncs.pfnPlaySoundByIndex( iSound, vol ); } + +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#define fabs(x) ((x) > 0 ? (x) : 0 - (x)) + +void ScaleColors( int &r, int &g, int &b, int a ); + +#define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]) +#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];} +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); +void VectorScale (const float *in, float scale, float *out); +float VectorNormalize (float *v); +void VectorInverse ( float *v ); + +extern vec3_t vec3_origin; + +// disable 'possible loss of data converting float to int' warning message +#pragma warning( disable: 4244 ) +// disable 'truncation from 'const double' to 'float' warning message +#pragma warning( disable: 4305 ) + +inline void UnpackRGB(int &r, int &g, int &b, unsigned long ulRGB)\ +{\ + r = (ulRGB & 0xFF0000) >>16;\ + g = (ulRGB & 0xFF00) >> 8;\ + b = ulRGB & 0xFF;\ +} + +HSPRITE LoadSprite(const char *pszName); diff --git a/hlsdk/cl_dll/com_weapons.cpp b/hlsdk/cl_dll/com_weapons.cpp new file mode 100644 index 00000000..61463d40 --- /dev/null +++ b/hlsdk/cl_dll/com_weapons.cpp @@ -0,0 +1,277 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +// Com_Weapons.cpp +// Shared weapons common/shared functions +#include +#include "hud.h" +#include "cl_util.h" +#include "com_weapons.h" + +#include "const.h" +#include "entity_state.h" +#include "r_efx.h" + +// g_runfuncs is true if this is the first time we've "predicated" a particular movement/firing +// command. If it is 1, then we should play events/sounds etc., otherwise, we just will be +// updating state info, but not firing events +int g_runfuncs = 0; + +// During our weapon prediction processing, we'll need to reference some data that is part of +// the final state passed into the postthink functionality. We'll set this pointer and then +// reset it to NULL as appropriate +struct local_state_s *g_finalstate = NULL; + +/* +==================== +COM_Log + +Log debug messages to file ( appends ) +==================== +*/ +void COM_Log( char *pszFile, char *fmt, ...) +{ + va_list argptr; + char string[1024]; + FILE *fp; + char *pfilename; + + if ( !pszFile ) + { + pfilename = "c:\\hllog.txt"; + } + else + { + pfilename = pszFile; + } + + va_start (argptr,fmt); + vsprintf (string, fmt,argptr); + va_end (argptr); + + fp = fopen( pfilename, "a+t"); + if (fp) + { + fprintf(fp, "%s", string); + fclose(fp); + } +} + +// remember the current animation for the view model, in case we get out of sync with +// server. +static int g_currentanim; + +/* +===================== +HUD_SendWeaponAnim + +Change weapon model animation +===================== +*/ +void HUD_SendWeaponAnim( int iAnim, int body, int force ) +{ + // Don't actually change it. + if ( !g_runfuncs && !force ) + return; + + g_currentanim = iAnim; + + // Tell animation system new info + gEngfuncs.pfnWeaponAnim( iAnim, body ); +} + +/* +===================== +HUD_GetWeaponAnim + +Retrieve current predicted weapon animation +===================== +*/ +int HUD_GetWeaponAnim( void ) +{ + return g_currentanim; +} + +/* +===================== +HUD_PlaySound + +Play a sound, if we are seeing this command for the first time +===================== +*/ +void HUD_PlaySound( char *sound, float volume ) +{ + if ( !g_runfuncs || !g_finalstate ) + return; + + gEngfuncs.pfnPlaySoundByNameAtLocation( sound, volume, (float *)&g_finalstate->playerstate.origin ); +} + +/* +===================== +HUD_PlaybackEvent + +Directly queue up an event on the client +===================== +*/ +void HUD_PlaybackEvent( int flags, const edict_t *pInvoker, unsigned short eventindex, float delay, + float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ) +{ + vec3_t org; + vec3_t ang; + + if ( !g_runfuncs || !g_finalstate ) + return; + + // Weapon prediction events are assumed to occur at the player's origin + org = g_finalstate->playerstate.origin; + ang = v_angles; + gEngfuncs.pfnPlaybackEvent( flags, pInvoker, eventindex, delay, (float *)&org, (float *)&ang, fparam1, fparam2, iparam1, iparam2, bparam1, bparam2 ); +} + +/* +===================== +HUD_SetMaxSpeed + +===================== +*/ +void HUD_SetMaxSpeed( const edict_t *ed, float speed ) +{ +} + + +/* +===================== +UTIL_WeaponTimeBase + +Always 0.0 on client, even if not predicting weapons ( won't get called + in that case ) +===================== +*/ +float UTIL_WeaponTimeBase( void ) +{ + return 0.0; +} + +static unsigned int glSeed = 0; + +unsigned int seed_table[ 256 ] = +{ + 28985, 27138, 26457, 9451, 17764, 10909, 28790, 8716, 6361, 4853, 17798, 21977, 19643, 20662, 10834, 20103, + 27067, 28634, 18623, 25849, 8576, 26234, 23887, 18228, 32587, 4836, 3306, 1811, 3035, 24559, 18399, 315, + 26766, 907, 24102, 12370, 9674, 2972, 10472, 16492, 22683, 11529, 27968, 30406, 13213, 2319, 23620, 16823, + 10013, 23772, 21567, 1251, 19579, 20313, 18241, 30130, 8402, 20807, 27354, 7169, 21211, 17293, 5410, 19223, + 10255, 22480, 27388, 9946, 15628, 24389, 17308, 2370, 9530, 31683, 25927, 23567, 11694, 26397, 32602, 15031, + 18255, 17582, 1422, 28835, 23607, 12597, 20602, 10138, 5212, 1252, 10074, 23166, 19823, 31667, 5902, 24630, + 18948, 14330, 14950, 8939, 23540, 21311, 22428, 22391, 3583, 29004, 30498, 18714, 4278, 2437, 22430, 3439, + 28313, 23161, 25396, 13471, 19324, 15287, 2563, 18901, 13103, 16867, 9714, 14322, 15197, 26889, 19372, 26241, + 31925, 14640, 11497, 8941, 10056, 6451, 28656, 10737, 13874, 17356, 8281, 25937, 1661, 4850, 7448, 12744, + 21826, 5477, 10167, 16705, 26897, 8839, 30947, 27978, 27283, 24685, 32298, 3525, 12398, 28726, 9475, 10208, + 617, 13467, 22287, 2376, 6097, 26312, 2974, 9114, 21787, 28010, 4725, 15387, 3274, 10762, 31695, 17320, + 18324, 12441, 16801, 27376, 22464, 7500, 5666, 18144, 15314, 31914, 31627, 6495, 5226, 31203, 2331, 4668, + 12650, 18275, 351, 7268, 31319, 30119, 7600, 2905, 13826, 11343, 13053, 15583, 30055, 31093, 5067, 761, + 9685, 11070, 21369, 27155, 3663, 26542, 20169, 12161, 15411, 30401, 7580, 31784, 8985, 29367, 20989, 14203, + 29694, 21167, 10337, 1706, 28578, 887, 3373, 19477, 14382, 675, 7033, 15111, 26138, 12252, 30996, 21409, + 25678, 18555, 13256, 23316, 22407, 16727, 991, 9236, 5373, 29402, 6117, 15241, 27715, 19291, 19888, 19847 +}; + +unsigned int U_Random( void ) +{ + glSeed *= 69069; + glSeed += seed_table[ glSeed & 0xff ]; + + return ( ++glSeed & 0x0fffffff ); +} + +void U_Srand( unsigned int seed ) +{ + glSeed = seed_table[ seed & 0xff ]; +} + +/* +===================== +UTIL_SharedRandomLong +===================== +*/ +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ) +{ + unsigned int range; + + U_Srand( (int)seed + low + high ); + + range = high - low + 1; + if ( !(range - 1) ) + { + return low; + } + else + { + int offset; + int rnum; + + rnum = U_Random(); + + offset = rnum % range; + + return (low + offset); + } +} + +/* +===================== +UTIL_SharedRandomFloat +===================== +*/ +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ) +{ + // + unsigned int range; + + U_Srand( (int)seed + *(int *)&low + *(int *)&high ); + + U_Random(); + U_Random(); + + range = high - low; + if ( !range ) + { + return low; + } + else + { + int tensixrand; + float offset; + + tensixrand = U_Random() & 65535; + + offset = (float)tensixrand / 65536.0; + + return (low + offset * range ); + } +} + +/* +====================== +stub_* + +stub functions for such things as precaching. So we don't have to modify weapons code that + is compiled into both game and client .dlls. +====================== +*/ +int stub_PrecacheModel ( char* s ) { return 0; } +int stub_PrecacheSound ( char* s ) { return 0; } +unsigned short stub_PrecacheEvent ( int type, const char *s ) { return 0; } +const char *stub_NameForFunction ( uint32 function ) { return "func"; } +void stub_SetModel ( edict_t *e, const char *m ) {} diff --git a/hlsdk/cl_dll/com_weapons.h b/hlsdk/cl_dll/com_weapons.h new file mode 100644 index 00000000..e0c54556 --- /dev/null +++ b/hlsdk/cl_dll/com_weapons.h @@ -0,0 +1,48 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// com_weapons.h +// Shared weapons common function prototypes +#if !defined( COM_WEAPONSH ) +#define COM_WEAPONSH +#ifdef _WIN32 +#pragma once +#endif + +#include "hud_iface.h" + +extern "C" +{ + 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 ); +} + +void COM_Log( char *pszFile, char *fmt, ...); +int CL_IsDead( void ); + +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ); +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ); + +int HUD_GetWeaponAnim( void ); +void HUD_SendWeaponAnim( int iAnim, int body, int force ); +void HUD_PlaySound( char *sound, float volume ); +void HUD_PlaybackEvent( int flags, const struct edict_s *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); +void HUD_SetMaxSpeed( const struct edict_s *ed, float speed ); +int stub_PrecacheModel( char* s ); +int stub_PrecacheSound( char* s ); +unsigned short stub_PrecacheEvent( int type, const char *s ); +const char *stub_NameForFunction ( uint32 function ); +void stub_SetModel ( struct edict_s *e, const char *m ); + + +extern cvar_t *cl_lw; + +extern int g_runfuncs; +extern vec3_t v_angles; +extern float g_lastFOV; +extern struct local_state_s *g_finalstate; + +#endif \ No newline at end of file diff --git a/hlsdk/cl_dll/death.cpp b/hlsdk/cl_dll/death.cpp new file mode 100644 index 00000000..df14489d --- /dev/null +++ b/hlsdk/cl_dll/death.cpp @@ -0,0 +1,305 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// death notice +// +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "vgui_TeamFortressViewport.h" + +DECLARE_MESSAGE( m_DeathNotice, DeathMsg ); + +struct DeathNoticeItem { + char szKiller[MAX_PLAYER_NAME_LENGTH*2]; + char szVictim[MAX_PLAYER_NAME_LENGTH*2]; + int iId; // the index number of the associated sprite + int iSuicide; + int iTeamKill; + int iNonPlayerKill; + float flDisplayTime; + float *KillerColor; + float *VictimColor; +}; + +#define MAX_DEATHNOTICES 4 +static int DEATHNOTICE_DISPLAY_TIME = 6; + +#define DEATHNOTICE_TOP 32 + +DeathNoticeItem rgDeathNoticeList[ MAX_DEATHNOTICES + 1 ]; + +float g_ColorBlue[3] = { 0.6, 0.8, 1.0 }; +float g_ColorRed[3] = { 1.0, 0.25, 0.25 }; +float g_ColorGreen[3] = { 0.6, 1.0, 0.6 }; +float g_ColorYellow[3] = { 1.0, 0.7, 0.0 }; +float g_ColorGrey[3] = { 0.8, 0.8, 0.8 }; + +float *GetClientColor( int clientIndex ) +{ + switch ( g_PlayerExtraInfo[clientIndex].teamnumber ) + { + case 1: return g_ColorBlue; + case 2: return g_ColorRed; + case 3: return g_ColorYellow; + case 4: return g_ColorGreen; + case 0: return g_ColorYellow; + + default : return g_ColorGrey; + } + + return NULL; +} + +int CHudDeathNotice :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( DeathMsg ); + + CVAR_CREATE( "hud_deathnotice_time", "6", 0 ); + + return 1; +} + + +void CHudDeathNotice :: InitHUDData( void ) +{ + memset( rgDeathNoticeList, 0, sizeof(rgDeathNoticeList) ); +} + + +int CHudDeathNotice :: VidInit( void ) +{ + m_HUD_d_skull = gHUD.GetSpriteIndex( "d_skull" ); + + return 1; +} + +int CHudDeathNotice :: Draw( float flTime ) +{ + int x, y, r, g, b; + + for ( int i = 0; i < MAX_DEATHNOTICES; i++ ) + { + if ( rgDeathNoticeList[i].iId == 0 ) + break; // we've gone through them all + + if ( rgDeathNoticeList[i].flDisplayTime < flTime ) + { // display time has expired + // remove the current item from the list + memmove( &rgDeathNoticeList[i], &rgDeathNoticeList[i+1], sizeof(DeathNoticeItem) * (MAX_DEATHNOTICES - i) ); + i--; // continue on the next item; stop the counter getting incremented + continue; + } + + rgDeathNoticeList[i].flDisplayTime = min( rgDeathNoticeList[i].flDisplayTime, gHUD.m_flTime + DEATHNOTICE_DISPLAY_TIME ); + + // Only draw if the viewport will let me + if ( gViewPort && gViewPort->AllowedToPrintText() ) + { + // Draw the death notice + y = YRES(DEATHNOTICE_TOP) + 2 + (20 * i); //!!! + + 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); + + if ( !rgDeathNoticeList[i].iSuicide ) + { + x -= (5 + ConsoleStringLen( rgDeathNoticeList[i].szKiller ) ); + + // Draw killers name + if ( rgDeathNoticeList[i].KillerColor ) + gEngfuncs.pfnDrawSetTextColor( rgDeathNoticeList[i].KillerColor[0], rgDeathNoticeList[i].KillerColor[1], rgDeathNoticeList[i].KillerColor[2] ); + x = 5 + DrawConsoleString( x, y, rgDeathNoticeList[i].szKiller ); + } + + r = 255; g = 80; b = 0; + 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) ); + + x += (gHUD.GetSpriteRect(id).right - gHUD.GetSpriteRect(id).left); + + // Draw victims name (if it was a player that was killed) + if (rgDeathNoticeList[i].iNonPlayerKill == FALSE) + { + if ( rgDeathNoticeList[i].VictimColor ) + gEngfuncs.pfnDrawSetTextColor( rgDeathNoticeList[i].VictimColor[0], rgDeathNoticeList[i].VictimColor[1], rgDeathNoticeList[i].VictimColor[2] ); + x = DrawConsoleString( x, y, rgDeathNoticeList[i].szVictim ); + } + } + } + + return 1; +} + +// This message handler may be better off elsewhere +int CHudDeathNotice :: MsgFunc_DeathMsg( const char *pszName, int iSize, void *pbuf ) +{ + m_iFlags |= HUD_ACTIVE; + + BEGIN_READ( pbuf, iSize ); + + int killer = READ_BYTE(); + int victim = READ_BYTE(); + + char killedwith[32]; + strcpy( killedwith, "d_" ); + strncat( killedwith, READ_STRING(), 32 ); + + if (gViewPort) + gViewPort->DeathMsg( killer, victim ); + + gHUD.m_Spectator.DeathMessage(victim); + + int i; + for ( i = 0; i < MAX_DEATHNOTICES; i++ ) + { + if ( rgDeathNoticeList[i].iId == 0 ) + break; + } + if ( i == MAX_DEATHNOTICES ) + { // move the rest of the list forward to make room for this item + memmove( rgDeathNoticeList, rgDeathNoticeList+1, sizeof(DeathNoticeItem) * MAX_DEATHNOTICES ); + i = MAX_DEATHNOTICES - 1; + } + + if (gViewPort) + gViewPort->GetAllPlayersInfo(); + + // Get the Killer's name + char *killer_name = g_PlayerInfoList[ killer ].name; + if ( !killer_name ) + { + killer_name = ""; + rgDeathNoticeList[i].szKiller[0] = 0; + } + else + { + rgDeathNoticeList[i].KillerColor = GetClientColor( killer ); + strncpy( rgDeathNoticeList[i].szKiller, killer_name, MAX_PLAYER_NAME_LENGTH ); + rgDeathNoticeList[i].szKiller[MAX_PLAYER_NAME_LENGTH-1] = 0; + } + + // Get the Victim's name + char *victim_name = NULL; + // If victim is -1, the killer killed a specific, non-player object (like a sentrygun) + if ( ((char)victim) != -1 ) + victim_name = g_PlayerInfoList[ victim ].name; + if ( !victim_name ) + { + victim_name = ""; + rgDeathNoticeList[i].szVictim[0] = 0; + } + else + { + rgDeathNoticeList[i].VictimColor = GetClientColor( victim ); + strncpy( rgDeathNoticeList[i].szVictim, victim_name, MAX_PLAYER_NAME_LENGTH ); + rgDeathNoticeList[i].szVictim[MAX_PLAYER_NAME_LENGTH-1] = 0; + } + + // Is it a non-player object kill? + if ( ((char)victim) == -1 ) + { + rgDeathNoticeList[i].iNonPlayerKill = TRUE; + + // Store the object's name in the Victim slot (skip the d_ bit) + strcpy( rgDeathNoticeList[i].szVictim, killedwith+2 ); + } + else + { + if ( killer == victim || killer == 0 ) + rgDeathNoticeList[i].iSuicide = TRUE; + + if ( !strcmp( killedwith, "d_teammate" ) ) + rgDeathNoticeList[i].iTeamKill = TRUE; + } + + // Find the sprite in the list + int spr = gHUD.GetSpriteIndex( killedwith ); + + rgDeathNoticeList[i].iId = spr; + + DEATHNOTICE_DISPLAY_TIME = CVAR_GET_FLOAT( "hud_deathnotice_time" ); + rgDeathNoticeList[i].flDisplayTime = gHUD.m_flTime + DEATHNOTICE_DISPLAY_TIME; + + if (rgDeathNoticeList[i].iNonPlayerKill) + { + ConsolePrint( rgDeathNoticeList[i].szKiller ); + ConsolePrint( " killed a " ); + ConsolePrint( rgDeathNoticeList[i].szVictim ); + ConsolePrint( "\n" ); + } + 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" ); + } + + return 1; +} + + + + diff --git a/hlsdk/cl_dll/demo.cpp b/hlsdk/cl_dll/demo.cpp new file mode 100644 index 00000000..6968b387 --- /dev/null +++ b/hlsdk/cl_dll/demo.cpp @@ -0,0 +1,103 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "hud.h" +#include "cl_util.h" +#include "demo.h" +#include "demo_api.h" +#include + +#define DLLEXPORT __declspec( dllexport ) + +int g_demosniper = 0; +int g_demosniperdamage = 0; +float g_demosniperorg[3]; +float g_demosniperangles[3]; +float g_demozoom; + +// FIXME: There should be buffer helper functions to avoid all of the *(int *)& crap. + +extern "C" +{ + void DLLEXPORT Demo_ReadBuffer( int size, unsigned char *buffer ); +} + +/* +===================== +Demo_WriteBuffer + +Write some data to the demo stream +===================== +*/ +void Demo_WriteBuffer( int type, int size, unsigned char *buffer ) +{ + int pos = 0; + unsigned char buf[ 32 * 1024 ]; + *( int * )&buf[pos] = type; + pos+=sizeof( int ); + + memcpy( &buf[pos], buffer, size ); + + // Write full buffer out + gEngfuncs.pDemoAPI->WriteBuffer( size + sizeof( int ), buf ); +} + +/* +===================== +Demo_ReadBuffer + +Engine wants us to parse some data from the demo stream +===================== +*/ +void DLLEXPORT Demo_ReadBuffer( int size, unsigned char *buffer ) +{ + int type; + int i = 0; + + type = *( int * )buffer; + i += sizeof( int ); + switch ( type ) + { + case TYPE_SNIPERDOT: + g_demosniper = *(int * )&buffer[ i ]; + i += sizeof( int ); + + if ( g_demosniper ) + { + g_demosniperdamage = *( int * )&buffer[ i ]; + i += sizeof( int ); + + g_demosniperangles[ 0 ] = *(float *)&buffer[i]; + i += sizeof( float ); + g_demosniperangles[ 1 ] = *(float *)&buffer[i]; + i += sizeof( float ); + g_demosniperangles[ 2 ] = *(float *)&buffer[i]; + i += sizeof( float ); + g_demosniperorg[ 0 ] = *(float *)&buffer[i]; + i += sizeof( float ); + g_demosniperorg[ 1 ] = *(float *)&buffer[i]; + i += sizeof( float ); + g_demosniperorg[ 2 ] = *(float *)&buffer[i]; + i += sizeof( float ); + } + break; + case TYPE_ZOOM: + g_demozoom = *(float * )&buffer[ i ]; + i += sizeof( float ); + break; + default: + gEngfuncs.Con_DPrintf( "Unknown demo buffer type, skipping.\n" ); + break; + } +} \ No newline at end of file diff --git a/hlsdk/cl_dll/demo.h b/hlsdk/cl_dll/demo.h new file mode 100644 index 00000000..c5a5057b --- /dev/null +++ b/hlsdk/cl_dll/demo.h @@ -0,0 +1,27 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( DEMOH ) +#define DEMOH +#pragma once + +// Types of demo messages we can write/parse +enum +{ + TYPE_SNIPERDOT = 0, + TYPE_ZOOM +}; + +void Demo_WriteBuffer( int type, int size, unsigned char *buffer ); + +extern int g_demosniper; +extern int g_demosniperdamage; +extern float g_demosniperorg[3]; +extern float g_demosniperangles[3]; +extern float g_demozoom; + +#endif \ No newline at end of file diff --git a/hlsdk/cl_dll/entity.cpp b/hlsdk/cl_dll/entity.cpp new file mode 100644 index 00000000..6a209c86 --- /dev/null +++ b/hlsdk/cl_dll/entity.cpp @@ -0,0 +1,977 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// Client side entity management functions + +#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 "pm_shared.h" + +#define DLLEXPORT __declspec( dllexport ) + +void Game_AddObjects( void ); + +extern vec3_t v_origin; + +int g_iAlive = 1; + +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 ); + void DLLEXPORT HUD_TempEntUpdate( double frametime, double client_time, double cl_gravity, struct tempent_s **ppTempEntFree, struct tempent_s **ppTempEntActive, int ( *Callback_AddVisibleEntity )( struct cl_entity_s *pEntity ), void ( *Callback_TempEntPlaySound )( struct tempent_s *pTemp, float damp ) ); + struct cl_entity_s DLLEXPORT *HUD_GetUserEntity( int index ); +} + +/* +======================== +HUD_AddEntity + Return 0 to filter entity from visible list for rendering +======================== +*/ +int DLLEXPORT HUD_AddEntity( int type, struct cl_entity_s *ent, const char *modelname ) +{ + switch ( type ) + { + case ET_NORMAL: + case ET_PLAYER: + case ET_BEAM: + case ET_TEMPENTITY: + case ET_FRAGMENTED: + default: + break; + } + // each frame every entity passes this function, so the overview hooks it to filter the overview entities + // in spectator mode: + // each frame every entity passes this function, so the overview hooks + // it to filter the overview entities + + if ( g_iUser1 ) + { + gHUD.m_Spectator.AddOverviewEntity( type, ent, modelname ); + + if ( ( g_iUser1 == OBS_IN_EYE || gHUD.m_Spectator.m_pip->value == INSET_IN_EYE ) && + ent->index == g_iUser2 ) + return 0; // don't draw the player we are following in eye + + } + + return 1; +} + +/* +========================= +HUD_TxferLocalOverrides + +The server sends us our origin with extra precision as part of the clientdata structure, not during the normal +playerstate update in entity_state_t. In order for these overrides to eventually get to the appropriate playerstate +structure, we need to copy them into the state structure at this point. +========================= +*/ +void DLLEXPORT HUD_TxferLocalOverrides( struct entity_state_s *state, const struct clientdata_s *client ) +{ + VectorCopy( client->origin, state->origin ); + + // Spectator + state->iuser1 = client->iuser1; + state->iuser2 = client->iuser2; + + // Duck prevention + state->iuser3 = client->iuser3; + + // Fire prevention + state->iuser4 = client->iuser4; +} + +/* +========================= +HUD_ProcessPlayerState + +We have received entity_state_t for this player over the network. We need to copy appropriate fields to the +playerstate structure +========================= +*/ +void DLLEXPORT HUD_ProcessPlayerState( struct entity_state_s *dst, const struct entity_state_s *src ) +{ + // Copy in network data + VectorCopy( src->origin, dst->origin ); + VectorCopy( src->angles, dst->angles ); + + VectorCopy( src->velocity, dst->velocity ); + + dst->frame = src->frame; + dst->modelindex = src->modelindex; + dst->skin = src->skin; + dst->effects = src->effects; + dst->weaponmodel = src->weaponmodel; + dst->movetype = src->movetype; + dst->sequence = src->sequence; + dst->animtime = src->animtime; + + dst->solid = src->solid; + + dst->rendermode = src->rendermode; + dst->renderamt = src->renderamt; + dst->rendercolor.r = src->rendercolor.r; + dst->rendercolor.g = src->rendercolor.g; + dst->rendercolor.b = src->rendercolor.b; + dst->renderfx = src->renderfx; + + dst->framerate = src->framerate; + dst->body = src->body; + + memcpy( &dst->controller[0], &src->controller[0], 4 * sizeof( byte ) ); + memcpy( &dst->blending[0], &src->blending[0], 2 * sizeof( byte ) ); + + VectorCopy( src->basevelocity, dst->basevelocity ); + + dst->friction = src->friction; + dst->gravity = src->gravity; + dst->gaitsequence = src->gaitsequence; + dst->spectator = src->spectator; + dst->usehull = src->usehull; + dst->playerclass = src->playerclass; + dst->team = src->team; + dst->colormap = src->colormap; + + // Save off some data so other areas of the Client DLL can get to it + cl_entity_t *player = gEngfuncs.GetLocalPlayer(); // Get the local player's index + if ( dst->number == player->index ) + { + g_iPlayerClass = dst->playerclass; + g_iTeamNumber = dst->team; + + g_iUser1 = src->iuser1; + g_iUser2 = src->iuser2; + g_iUser3 = src->iuser3; + } +} + +/* +========================= +HUD_TxferPredictionData + +Because we can predict an arbitrary number of frames before the server responds with an update, we need to be able to copy client side prediction data in + from the state that the server ack'd receiving, which can be anywhere along the predicted frame path ( i.e., we could predict 20 frames into the future and the server ack's + up through 10 of those frames, so we need to copy persistent client-side only state from the 10th predicted frame to the slot the server + update is occupying. +========================= +*/ +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 ) +{ + ps->oldbuttons = pps->oldbuttons; + ps->flFallVelocity = pps->flFallVelocity; + ps->iStepLeft = pps->iStepLeft; + ps->playerclass = pps->playerclass; + + pcd->viewmodel = ppcd->viewmodel; + pcd->m_iId = ppcd->m_iId; + pcd->ammo_shells = ppcd->ammo_shells; + pcd->ammo_nails = ppcd->ammo_nails; + pcd->ammo_cells = ppcd->ammo_cells; + pcd->ammo_rockets = ppcd->ammo_rockets; + pcd->m_flNextAttack = ppcd->m_flNextAttack; + pcd->fov = ppcd->fov; + pcd->weaponanim = ppcd->weaponanim; + pcd->tfstate = ppcd->tfstate; + pcd->maxspeed = ppcd->maxspeed; + + pcd->deadflag = ppcd->deadflag; + + // Spectating or not dead == get control over view angles. + g_iAlive = ( ppcd->iuser1 || ( pcd->deadflag == DEAD_NO ) ) ? 1 : 0; + + // Spectator + pcd->iuser1 = ppcd->iuser1; + pcd->iuser2 = ppcd->iuser2; + + // Duck prevention + pcd->iuser3 = ppcd->iuser3; + + if ( gEngfuncs.IsSpectateOnly() ) + { + // in specator mode we tell the engine who we want to spectate and how + // iuser3 is not used for duck prevention (since the spectator can't duck at all) + 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 ); + VectorCopy( ppcd->vuser4, pcd->vuser4 ); + + memcpy( wd, pwd, 32 * sizeof( weapon_data_t ) ); +} + +/* +//#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 +*/ + +/* +void ParticleCallback( struct particle_s *particle, float frametime ) +{ + int i; + + for ( i = 0; i < 3; i++ ) + { + particle->org[ i ] += particle->vel[ i ] * frametime; + } +} + +cvar_t *color = NULL; +void Particles( void ) +{ + static float lasttime; + float curtime; + + curtime = gEngfuncs.GetClientTime(); + + if ( ( curtime - lasttime ) < 2.0 ) + return; + + if ( !color ) + { + color = gEngfuncs.pfnRegisterVariable ( "color","255 0 0", 0 ); + } + + lasttime = curtime; + + // Create a few particles + particle_t *p; + int i, j; + + for ( i = 0; i < 1000; i++ ) + { + int r, g, b; + p = gEngfuncs.pEfxAPI->R_AllocParticle( ParticleCallback ); + if ( !p ) + break; + + for ( j = 0; j < 3; j++ ) + { + p->org[ j ] = v_origin[ j ] + gEngfuncs.pfnRandomFloat( -32.0, 32.0 );; + p->vel[ j ] = gEngfuncs.pfnRandomFloat( -100.0, 100.0 ); + } + + if ( color ) + { + sscanf( color->string, "%i %i %i", &r, &g, &b ); + } + else + { + r = 192; + g = 0; + b = 0; + } + + p->color = gEngfuncs.pEfxAPI->R_LookupColor( r, g, b ); + gEngfuncs.pEfxAPI->R_GetPackedColor( &p->packedColor, p->color ); + + // p->die is set to current time so all you have to do is add an additional time to it + p->die += 3.0; + } +} +*/ + +/* +void TempEntCallback ( struct tempent_s *ent, float frametime, float currenttime ) +{ + int i; + + for ( i = 0; i < 3; i++ ) + { + ent->entity.curstate.origin[ i ] += ent->entity.baseline.origin[ i ] * frametime; + } +} + +void TempEnts( void ) +{ + static float lasttime; + float curtime; + + curtime = gEngfuncs.GetClientTime(); + + if ( ( curtime - lasttime ) < 10.0 ) + return; + + lasttime = curtime; + + TEMPENTITY *p; + int i, j; + struct model_s *mod; + vec3_t origin; + int index; + + mod = gEngfuncs.CL_LoadModel( "sprites/laserdot.spr", &index ); + + for ( i = 0; i < 100; i++ ) + { + for ( j = 0; j < 3; j++ ) + { + origin[ j ] = v_origin[ j ]; + if ( j != 2 ) + { + origin[ j ] += 75; + } + } + + p = gEngfuncs.pEfxAPI->CL_TentEntAllocCustom( (float *)&origin, mod, 0, TempEntCallback ); + if ( !p ) + break; + + for ( j = 0; j < 3; j++ ) + { + p->entity.curstate.origin[ j ] = origin[ j ]; + + // Store velocity in baseline origin + p->entity.baseline.origin[ j ] = gEngfuncs.pfnRandomFloat( -100, 100 ); + } + + // p->die is set to current time so all you have to do is add an additional time to it + p->die += 10.0; + } +} +*/ + +#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 + +Simulation and cleanup of temporary entities +================= +*/ +void DLLEXPORT HUD_TempEntUpdate ( + double frametime, // Simulation time + double client_time, // Absolute time on client + double cl_gravity, // True gravity on client + TEMPENTITY **ppTempEntFree, // List of freed temporary ents + TEMPENTITY **ppTempEntActive, // List + int ( *Callback_AddVisibleEntity )( cl_entity_t *pEntity ), + void ( *Callback_TempEntPlaySound )( TEMPENTITY *pTemp, float damp ) ) +{ + static int gTempEntFrame = 0; + int i; + TEMPENTITY *pTemp, *pnext, *pprev; + float freq, gravity, gravitySlow, life, fastFreq; + + // Nothing to simulate + if ( !*ppTempEntActive ) + return; + + // in order to have tents collide with players, we have to run the player prediction code so + // that the client has the player list. We run this code once when we detect any COLLIDEALL + // tent, then set this BOOL to true so the code doesn't get run again if there's more than + // one COLLIDEALL ent for this update. (often are). + 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 ( -1 ); + + // !!!BUGBUG -- This needs to be time based + gTempEntFrame = (gTempEntFrame+1) & 31; + + pTemp = *ppTempEntActive; + + // !!! Don't simulate while paused.... This is sort of a hack, revisit. + if ( frametime <= 0 ) + { + while ( pTemp ) + { + if ( !(pTemp->flags & FTENT_NOMODEL ) ) + { + Callback_AddVisibleEntity( &pTemp->entity ); + } + pTemp = pTemp->next; + } + goto finish; + } + + pprev = NULL; + freq = client_time * 0.01; + fastFreq = client_time * 5.5; + gravity = -frametime * cl_gravity; + gravitySlow = gravity * 0.5; + + while ( pTemp ) + { + int active; + + active = 1; + + life = pTemp->die - client_time; + pnext = pTemp->next; + if ( life < 0 ) + { + if ( pTemp->flags & FTENT_FADEOUT ) + { + if (pTemp->entity.curstate.rendermode == kRenderNormal) + pTemp->entity.curstate.rendermode = kRenderTransTexture; + pTemp->entity.curstate.renderamt = pTemp->entity.baseline.renderamt * ( 1 + life * pTemp->fadeSpeed ); + if ( pTemp->entity.curstate.renderamt <= 0 ) + active = 0; + + } + else + active = 0; + } + if ( !active ) // Kill it + { + pTemp->next = *ppTempEntFree; + *ppTempEntFree = pTemp; + if ( !pprev ) // Deleting at head of list + *ppTempEntActive = pnext; + else + pprev->next = pnext; + } + else + { + pprev = pTemp; + + VectorCopy( pTemp->entity.origin, pTemp->entity.prevstate.origin ); + + if ( pTemp->flags & FTENT_SPARKSHOWER ) + { + // Adjust speed if it's time + // Scale is next think time + if ( client_time > pTemp->entity.baseline.scale ) + { + // Show Sparks + gEngfuncs.pEfxAPI->R_SparkEffect( pTemp->entity.origin, 8, -200, 200 ); + + // Reduce life + pTemp->entity.baseline.framerate -= 0.1; + + if ( pTemp->entity.baseline.framerate <= 0.0 ) + { + pTemp->die = client_time; + } + else + { + // So it will die no matter what + pTemp->die = client_time + 0.5; + + // Next think + pTemp->entity.baseline.scale = client_time + 0.1; + } + } + } + else if ( pTemp->flags & FTENT_PLYRATTACHMENT ) + { + cl_entity_t *pClient; + + pClient = gEngfuncs.GetEntityByIndex( pTemp->clientIndex ); + + VectorAdd( pClient->origin, pTemp->tentOffset, pTemp->entity.origin ); + } + else if ( pTemp->flags & FTENT_SINEWAVE ) + { + pTemp->x += pTemp->entity.baseline.origin[0] * frametime; + pTemp->y += pTemp->entity.baseline.origin[1] * frametime; + + pTemp->entity.origin[0] = pTemp->x + sin( pTemp->entity.baseline.origin[2] + client_time * pTemp->entity.prevstate.frame ) * (10*pTemp->entity.curstate.framerate); + pTemp->entity.origin[1] = pTemp->y + sin( pTemp->entity.baseline.origin[2] + fastFreq + 0.7 ) * (8*pTemp->entity.curstate.framerate); + pTemp->entity.origin[2] += pTemp->entity.baseline.origin[2] * frametime; + } + else if ( pTemp->flags & FTENT_SPIRAL ) + { + float s, c; + s = sin( pTemp->entity.baseline.origin[2] + fastFreq ); + c = cos( pTemp->entity.baseline.origin[2] + fastFreq ); + + pTemp->entity.origin[0] += pTemp->entity.baseline.origin[0] * frametime + 8 * sin( client_time * 20 + (int)pTemp ); + 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++ ) + pTemp->entity.origin[i] += pTemp->entity.baseline.origin[i] * frametime; + } + + if ( pTemp->flags & FTENT_SPRANIMATE ) + { + pTemp->entity.curstate.frame += frametime * pTemp->entity.curstate.framerate; + if ( pTemp->entity.curstate.frame >= pTemp->frameMax ) + { + pTemp->entity.curstate.frame = pTemp->entity.curstate.frame - (int)(pTemp->entity.curstate.frame); + + if ( !(pTemp->flags & FTENT_SPRANIMATELOOP) ) + { + // this animating sprite isn't set to loop, so destroy it. + pTemp->die = client_time; + pTemp = pnext; + continue; + } + } + } + else if ( pTemp->flags & FTENT_SPRCYCLE ) + { + pTemp->entity.curstate.frame += frametime * 10; + if ( pTemp->entity.curstate.frame >= pTemp->frameMax ) + { + pTemp->entity.curstate.frame = pTemp->entity.curstate.frame - (int)(pTemp->entity.curstate.frame); + } + } +// Experiment +#if 0 + if ( pTemp->flags & FTENT_SCALE ) + pTemp->entity.curstate.framerate += 20.0 * (frametime / pTemp->entity.curstate.framerate); +#endif + + if ( pTemp->flags & FTENT_ROTATE ) + { + pTemp->entity.angles[0] += pTemp->entity.baseline.angles[0] * frametime; + pTemp->entity.angles[1] += pTemp->entity.baseline.angles[1] * frametime; + pTemp->entity.angles[2] += pTemp->entity.baseline.angles[2] * frametime; + + VectorCopy( pTemp->entity.angles, pTemp->entity.latched.prevangles ); + } + + if ( pTemp->flags & (FTENT_COLLIDEALL | FTENT_COLLIDEWORLD) ) + { + vec3_t traceNormal; + float traceFraction = 1; + + if ( pTemp->flags & FTENT_COLLIDEALL ) + { + pmtrace_t pmtrace; + physent_t *pe; + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + + gEngfuncs.pEventAPI->EV_PlayerTrace( pTemp->entity.prevstate.origin, pTemp->entity.origin, PM_STUDIO_BOX, -1, &pmtrace ); + + + if ( pmtrace.fraction != 1 ) + { + pe = gEngfuncs.pEventAPI->EV_GetPhysent( pmtrace.ent ); + + if ( !pmtrace.ent || ( pe->info != pTemp->clientIndex ) ) + { + traceFraction = pmtrace.fraction; + VectorCopy( pmtrace.plane.normal, traceNormal ); + + if ( pTemp->hitcallback ) + { + (*pTemp->hitcallback)( pTemp, &pmtrace ); + } + } + } + } + else if ( pTemp->flags & FTENT_COLLIDEWORLD ) + { + pmtrace_t pmtrace; + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + + gEngfuncs.pEventAPI->EV_PlayerTrace( pTemp->entity.prevstate.origin, pTemp->entity.origin, PM_STUDIO_BOX | PM_WORLD_ONLY, -1, &pmtrace ); + + if ( pmtrace.fraction != 1 ) + { + traceFraction = pmtrace.fraction; + VectorCopy( pmtrace.plane.normal, traceNormal ); + + if ( pTemp->flags & FTENT_SPARKSHOWER ) + { + // Chop spark speeds a bit more + // + VectorScale( pTemp->entity.baseline.origin, 0.6, pTemp->entity.baseline.origin ); + + if ( Length( pTemp->entity.baseline.origin ) < 10 ) + { + pTemp->entity.baseline.framerate = 0.0; + } + } + + if ( pTemp->hitcallback ) + { + (*pTemp->hitcallback)( pTemp, &pmtrace ); + } + } + } + + if ( traceFraction != 1 ) // Decent collision now, and damping works + { + float proj, damp; + + // Place at contact point + VectorMA( pTemp->entity.prevstate.origin, traceFraction*frametime, pTemp->entity.baseline.origin, pTemp->entity.origin ); + // Damp velocity + damp = pTemp->bounceFactor; + if ( pTemp->flags & (FTENT_GRAVITY|FTENT_SLOWGRAVITY) ) + { + damp *= 0.5; + if ( traceNormal[2] > 0.9 ) // Hit floor? + { + if ( pTemp->entity.baseline.origin[2] <= 0 && pTemp->entity.baseline.origin[2] >= gravity*3 ) + { + damp = 0; // Stop + pTemp->flags &= ~(FTENT_ROTATE|FTENT_GRAVITY|FTENT_SLOWGRAVITY|FTENT_COLLIDEWORLD|FTENT_SMOKETRAIL); + pTemp->entity.angles[0] = 0; + pTemp->entity.angles[2] = 0; + } + } + } + + if (pTemp->hitSound) + { + Callback_TempEntPlaySound(pTemp, damp); + } + + if (pTemp->flags & FTENT_COLLIDEKILL) + { + // die on impact + pTemp->flags &= ~FTENT_FADEOUT; + pTemp->die = client_time; + } + else + { + // Reflect velocity + if ( damp != 0 ) + { + proj = DotProduct( pTemp->entity.baseline.origin, traceNormal ); + VectorMA( pTemp->entity.baseline.origin, -proj*2, traceNormal, pTemp->entity.baseline.origin ); + // Reflect rotation (fake) + + pTemp->entity.angles[1] = -pTemp->entity.angles[1]; + } + + if ( damp != 1 ) + { + + VectorScale( pTemp->entity.baseline.origin, damp, pTemp->entity.baseline.origin ); + VectorScale( pTemp->entity.angles, 0.9, pTemp->entity.angles ); + } + } + } + } + + + if ( (pTemp->flags & FTENT_FLICKER) && gTempEntFrame == pTemp->entity.curstate.effects ) + { + dlight_t *dl = gEngfuncs.pEfxAPI->CL_AllocDlight (0); + VectorCopy (pTemp->entity.origin, dl->origin); + dl->radius = 60; + dl->color.r = 255; + dl->color.g = 120; + dl->color.b = 0; + dl->die = client_time + 0.01; + } + + if ( pTemp->flags & FTENT_SMOKETRAIL ) + { + gEngfuncs.pEfxAPI->R_RocketTrail (pTemp->entity.prevstate.origin, pTemp->entity.origin, 1); + } + + 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 ) + { + if ( pTemp->callback ) + { + ( *pTemp->callback )( pTemp, frametime, client_time ); + } + } + + // Cull to PVS (not frustum cull, just PVS) + if ( !(pTemp->flags & FTENT_NOMODEL ) ) + { + if ( !Callback_AddVisibleEntity( &pTemp->entity ) ) + { + if ( !(pTemp->flags & FTENT_PERSIST) ) + { + pTemp->die = client_time; // If we can't draw it this frame, just dump it. + pTemp->flags &= ~FTENT_FADEOUT; // Don't fade out, just die + } + } + } + } + pTemp = pnext; + } + +finish: + // Restore state info + gEngfuncs.pEventAPI->EV_PopPMStates(); +} + +/* +================= +HUD_GetUserEntity + +If you specify negative numbers for beam start and end point entities, then + the engine will call back into this function requesting a pointer to a cl_entity_t + object that describes the entity to attach the beam onto. + +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 ]; + } + else + { + return NULL; + } +#else + return NULL; +#endif +} + diff --git a/hlsdk/cl_dll/ev_common.cpp b/hlsdk/cl_dll/ev_common.cpp new file mode 100644 index 00000000..224633dd --- /dev/null +++ b/hlsdk/cl_dll/ev_common.cpp @@ -0,0 +1,205 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// shared event functions +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" + +#include "r_efx.h" + +#include "eventscripts.h" +#include "event_api.h" +#include "pm_shared.h" + +#define IS_FIRSTPERSON_SPEC ( g_iUser1 == OBS_IN_EYE || (g_iUser1 && (gHUD.m_Spectator.m_pip->value == INSET_IN_EYE)) ) +/* +================= +GetEntity + +Return's the requested cl_entity_t +================= +*/ +struct cl_entity_s *GetEntity( int idx ) +{ + return gEngfuncs.GetEntityByIndex( idx ); +} + +/* +================= +GetViewEntity + +Return's the current weapon/view model +================= +*/ +struct cl_entity_s *GetViewEntity( void ) +{ + return gEngfuncs.GetViewModel(); +} + +/* +================= +EV_CreateTracer + +Creates a tracer effect +================= +*/ +void EV_CreateTracer( float *start, float *end ) +{ + gEngfuncs.pEfxAPI->R_TracerEffect( start, end ); +} + +/* +================= +EV_IsPlayer + +Is the entity's index in the player range? +================= +*/ +qboolean EV_IsPlayer( int idx ) +{ + if ( idx >= 1 && idx <= gEngfuncs.GetMaxClients() ) + return true; + + return false; +} + +/* +================= +EV_IsLocal + +Is the entity == the local player +================= +*/ +qboolean EV_IsLocal( int idx ) +{ + // check if we are in some way in first person spec mode + if ( IS_FIRSTPERSON_SPEC ) + return (g_iUser2 == idx); + else + return gEngfuncs.pEventAPI->EV_IsLocal( idx - 1 ) ? true : false; +} + +/* +================= +EV_GetGunPosition + +Figure out the height of the gun +================= +*/ +void EV_GetGunPosition( event_args_t *args, float *pos, float *origin ) +{ + int idx; + vec3_t view_ofs; + + idx = args->entindex; + + VectorClear( view_ofs ); + view_ofs[2] = DEFAULT_VIEWHEIGHT; + + if ( EV_IsPlayer( idx ) ) + { + // in spec mode use entity viewheigh, not own + if ( EV_IsLocal( idx ) && !IS_FIRSTPERSON_SPEC ) + { + // Grab predicted result for local player + gEngfuncs.pEventAPI->EV_LocalPlayerViewheight( view_ofs ); + } + else if ( args->ducking == 1 ) + { + view_ofs[2] = VEC_DUCK_VIEW; + } + } + + VectorAdd( origin, view_ofs, pos ); +} + +/* +================= +EV_EjectBrass + +Bullet shell casings +================= +*/ +void EV_EjectBrass( float *origin, float *velocity, float rotation, int model, int soundtype ) +{ + vec3_t endpos; + VectorClear( endpos ); + endpos[1] = rotation; + gEngfuncs.pEfxAPI->R_TempModel( origin, velocity, endpos, 2.5, model, soundtype ); +} + +/* +================= +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 ) +{ + int i; + vec3_t view_ofs; + float fR, fU; + + int idx; + + idx = args->entindex; + + VectorClear( view_ofs ); + view_ofs[2] = DEFAULT_VIEWHEIGHT; + + if ( EV_IsPlayer( idx ) ) + { + if ( EV_IsLocal( idx ) ) + { + gEngfuncs.pEventAPI->EV_LocalPlayerViewheight( view_ofs ); + } + else if ( args->ducking == 1 ) + { + view_ofs[2] = VEC_DUCK_VIEW; + } + } + + fR = gEngfuncs.pfnRandomFloat( 50, 70 ); + fU = gEngfuncs.pfnRandomFloat( 100, 150 ); + + 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; + } +} + +/* +================= +EV_MuzzleFlash + +Flag weapon/view model for muzzle flash +================= +*/ +void EV_MuzzleFlash( void ) +{ + // Add muzzle flash to current weapon model + cl_entity_t *ent = GetViewEntity(); + if ( !ent ) + { + return; + } + + // Or in the muzzle flash + ent->curstate.effects |= EF_MUZZLEFLASH; +} \ No newline at end of file diff --git a/hlsdk/cl_dll/ev_hldm.cpp b/hlsdk/cl_dll/ev_hldm.cpp new file mode 100644 index 00000000..ad9ba305 --- /dev/null +++ b/hlsdk/cl_dll/ev_hldm.cpp @@ -0,0 +1,1700 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#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_hldm.h" + +#include "r_efx.h" +#include "event_api.h" +#include "event_args.h" +#include "in_defs.h" + +#include + +#include "r_studioint.h" +#include "com_model.h" + +extern engine_studio_api_t IEngineStudio; + +static int tracerCount[ 32 ]; + +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; + +extern "C" +{ + +// HLDM +void EV_FireGlock1( struct event_args_s *args ); +void EV_FireGlock2( struct event_args_s *args ); +void EV_FireShotGunSingle( struct event_args_s *args ); +void EV_FireShotGunDouble( struct event_args_s *args ); +void EV_FireMP5( struct event_args_s *args ); +void EV_FireMP52( struct event_args_s *args ); +void EV_FirePython( struct event_args_s *args ); +void EV_FireGauss( struct event_args_s *args ); +void EV_SpinGauss( struct event_args_s *args ); +void EV_Crowbar( struct event_args_s *args ); +void EV_FireCrossbow( struct event_args_s *args ); +void EV_FireCrossbow2( struct event_args_s *args ); +void EV_FireRpg( struct event_args_s *args ); +void EV_EgonFire( struct event_args_s *args ); +void EV_EgonStop( struct event_args_s *args ); +void EV_HornetGunFire( struct event_args_s *args ); +void EV_TripmineFire( struct event_args_s *args ); +void EV_SnarkFire( struct event_args_s *args ); + + +void EV_TrainPitchAdjust( 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 +float EV_HLDM_PlayTextureSound( int idx, pmtrace_t *ptr, float *vecSrc, float *vecEnd, int iBulletType ) +{ + // hit the world, try to play sound based on texture material type + char chTextureType = CHAR_TEX_CONCRETE; + float fvol; + float fvolbar; + char *rgsz[4]; + int cnt; + float fattn = ATTN_NORM; + int entity; + char *pTextureName; + char texname[ 64 ]; + char szbuffer[ 64 ]; + + entity = gEngfuncs.pEventAPI->EV_IndexFromTrace( ptr ); + + // FIXME check if playtexture sounds movevar is set + // + + chTextureType = 0; + + // Player + if ( entity >= 1 && entity <= gEngfuncs.GetMaxClients() ) + { + // hit body + chTextureType = CHAR_TEX_FLESH; + } + else if ( entity == 0 ) + { + // get texture from entity or world (world is ent(0)) + pTextureName = (char *)gEngfuncs.pEventAPI->EV_TraceTexture( ptr->ent, vecSrc, vecEnd ); + + if ( pTextureName ) + { + strcpy( texname, pTextureName ); + pTextureName = texname; + + // strip leading '-0' or '+0~' or '{' or '!' + if (*pTextureName == '-' || *pTextureName == '+') + { + pTextureName += 2; + } + + if (*pTextureName == '{' || *pTextureName == '!' || *pTextureName == '~' || *pTextureName == ' ') + { + pTextureName++; + } + + // '}}' + strcpy( szbuffer, pTextureName ); + szbuffer[ CBTEXTURENAMEMAX - 1 ] = 0; + + // get texture type + chTextureType = PM_FindTextureType( szbuffer ); + } + } + + switch (chTextureType) + { + default: + case CHAR_TEX_CONCRETE: fvol = 0.9; fvolbar = 0.6; + rgsz[0] = "player/pl_step1.wav"; + rgsz[1] = "player/pl_step2.wav"; + cnt = 2; + break; + case CHAR_TEX_METAL: fvol = 0.9; fvolbar = 0.3; + rgsz[0] = "player/pl_metal1.wav"; + rgsz[1] = "player/pl_metal2.wav"; + cnt = 2; + break; + case CHAR_TEX_DIRT: fvol = 0.9; fvolbar = 0.1; + rgsz[0] = "player/pl_dirt1.wav"; + rgsz[1] = "player/pl_dirt2.wav"; + rgsz[2] = "player/pl_dirt3.wav"; + cnt = 3; + break; + case CHAR_TEX_VENT: fvol = 0.5; fvolbar = 0.3; + rgsz[0] = "player/pl_duct1.wav"; + rgsz[1] = "player/pl_duct1.wav"; + cnt = 2; + break; + case CHAR_TEX_GRATE: fvol = 0.9; fvolbar = 0.5; + rgsz[0] = "player/pl_grate1.wav"; + rgsz[1] = "player/pl_grate4.wav"; + cnt = 2; + break; + case CHAR_TEX_TILE: fvol = 0.8; fvolbar = 0.2; + rgsz[0] = "player/pl_tile1.wav"; + rgsz[1] = "player/pl_tile3.wav"; + rgsz[2] = "player/pl_tile2.wav"; + rgsz[3] = "player/pl_tile4.wav"; + cnt = 4; + break; + case CHAR_TEX_SLOSH: fvol = 0.9; fvolbar = 0.0; + rgsz[0] = "player/pl_slosh1.wav"; + rgsz[1] = "player/pl_slosh3.wav"; + rgsz[2] = "player/pl_slosh2.wav"; + rgsz[3] = "player/pl_slosh4.wav"; + cnt = 4; + break; + case CHAR_TEX_WOOD: fvol = 0.9; fvolbar = 0.2; + rgsz[0] = "debris/wood1.wav"; + rgsz[1] = "debris/wood2.wav"; + rgsz[2] = "debris/wood3.wav"; + cnt = 3; + break; + case CHAR_TEX_GLASS: + case CHAR_TEX_COMPUTER: + fvol = 0.8; fvolbar = 0.2; + rgsz[0] = "debris/glass1.wav"; + rgsz[1] = "debris/glass2.wav"; + rgsz[2] = "debris/glass3.wav"; + 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"; + fattn = 1.0; + cnt = 2; + break; + } + + // play material hit sound + gEngfuncs.pEventAPI->EV_PlaySound( 0, ptr->endpos, CHAN_STATIC, rgsz[gEngfuncs.pfnRandomLong(0,cnt-1)], fvol, fattn, 0, 96 + gEngfuncs.pfnRandomLong(0,0xf) ); + return fvolbar; +} + +char *EV_HLDM_DamageDecal( physent_t *pe ) +{ + static char decalname[ 32 ]; + int idx; + + if ( pe->classnumber == 1 ) + { + idx = gEngfuncs.pfnRandomLong( 0, 2 ); + sprintf( decalname, "{break%i", idx + 1 ); + } + else if ( pe->rendermode != kRenderNormal ) + { + sprintf( decalname, "{bproof1" ); + } + else + { + idx = gEngfuncs.pfnRandomLong( 0, 4 ); + sprintf( decalname, "{shot%i", idx + 1 ); + } + return decalname; +} + +void EV_HLDM_GunshotDecalTrace( pmtrace_t *pTrace, char *decalName ) +{ + int iRand; + physent_t *pe; + + gEngfuncs.pEfxAPI->R_BulletImpactParticles( pTrace->endpos ); + + iRand = gEngfuncs.pfnRandomLong(0,0x7FFF); + if ( iRand < (0x7fff/2) )// not every bullet makes a sound. + { + switch( iRand % 5) + { + case 0: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "weapons/ric1.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "weapons/ric2.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 2: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "weapons/ric3.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "weapons/ric4.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 4: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "weapons/ric5.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + } + } + + pe = gEngfuncs.pEventAPI->EV_GetPhysent( pTrace->ent ); + + // 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 ); + } + } +} + +void EV_HLDM_DecalGunshot( pmtrace_t *pTrace, int iBulletType ) +{ + physent_t *pe; + + pe = gEngfuncs.pEventAPI->EV_GetPhysent( pTrace->ent ); + + if ( pe && pe->solid == SOLID_BSP ) + { + switch( iBulletType ) + { + 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 + EV_HLDM_GunshotDecalTrace( pTrace, EV_HLDM_DamageDecal( pe ) ); + break; + } + } +} + +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 ) + { + case BULLET_PLAYER_MP5: + case BULLET_MONSTER_MP5: + case BULLET_MONSTER_9MM: + case BULLET_MONSTER_12MM: + default: + EV_CreateTracer( vecTracerSrc, end ); + break; + } + } + + return tracer; +} + + +/* +================ +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 i; + pmtrace_t tr; + int iShot; + int tracer; + + for ( iShot = 1; iShot <= cShots; iShot++ ) + { + vec3_t vecDir, vecEnd; + + float x, y, z; + //We randomize for the Shotgun. + if ( iBulletType == BULLET_PLAYER_BUCKSHOT ) + { + 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 ); + + // do damage, paint decals + if ( tr.fraction != 1.0 ) + { + switch(iBulletType) + { + default: + case BULLET_PLAYER_9MM: + + EV_HLDM_PlayTextureSound( idx, &tr, vecSrc, vecEnd, iBulletType ); + EV_HLDM_DecalGunshot( &tr, iBulletType ); + + break; + case BULLET_PLAYER_MP5: + + if ( !tracer ) + { + EV_HLDM_PlayTextureSound( idx, &tr, vecSrc, vecEnd, iBulletType ); + EV_HLDM_DecalGunshot( &tr, iBulletType ); + } + break; + case BULLET_PLAYER_BUCKSHOT: + + EV_HLDM_DecalGunshot( &tr, iBulletType ); + + break; + case BULLET_PLAYER_357: + + EV_HLDM_PlayTextureSound( idx, &tr, vecSrc, vecEnd, iBulletType ); + EV_HLDM_DecalGunshot( &tr, iBulletType ); + + break; + + } + } + + gEngfuncs.pEventAPI->EV_PopPMStates(); + } +} + +//====================== +// GLOCK START +//====================== +void EV_FireGlock1( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + int empty; + + vec3_t ShellVelocity; + vec3_t ShellOrigin; + int shell; + 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 ); + + shell = gEngfuncs.pEventAPI->EV_FindModelIndex ("models/shell.mdl");// brass shell + + if ( EV_IsLocal( idx ) ) + { + EV_MuzzleFlash(); + gEngfuncs.pEventAPI->EV_WeaponAnimation( empty ? GLOCK_SHOOT_EMPTY : GLOCK_SHOOT, 2 ); + + V_PunchAxis( 0, -2.0 ); + } + + EV_GetDefaultShellInfo( args, origin, velocity, ShellVelocity, ShellOrigin, forward, right, up, 20, -12, 4 ); + + EV_EjectBrass ( ShellOrigin, ShellVelocity, angles[ YAW ], shell, TE_BOUNCE_SHELL ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/pl_gun3.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_PLAYER_9MM, 0, 0, args->fparam1, args->fparam2 ); +} + +void EV_FireGlock2( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + + vec3_t ShellVelocity; + vec3_t ShellOrigin; + int shell; + vec3_t vecSrc, vecAiming; + vec3_t vecSpread; + vec3_t up, right, forward; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + + AngleVectors( angles, forward, right, up ); + + shell = gEngfuncs.pEventAPI->EV_FindModelIndex ("models/shell.mdl");// brass shell + + if ( EV_IsLocal( idx ) ) + { + // Add muzzle flash to current weapon model + EV_MuzzleFlash(); + gEngfuncs.pEventAPI->EV_WeaponAnimation( GLOCK_SHOOT, 2 ); + + V_PunchAxis( 0, -2.0 ); + } + + EV_GetDefaultShellInfo( args, origin, velocity, ShellVelocity, ShellOrigin, forward, right, up, 20, -12, 4 ); + + EV_EjectBrass ( ShellOrigin, ShellVelocity, angles[ YAW ], shell, TE_BOUNCE_SHELL ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/pl_gun3.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_PLAYER_9MM, 0, &tracerCount[idx-1], args->fparam1, args->fparam2 ); + +} +//====================== +// GLOCK END +//====================== + +//====================== +// SHOTGUN START +//====================== +void EV_FireShotGunDouble( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + + int j; + vec3_t ShellVelocity; + vec3_t ShellOrigin; + int shell; + vec3_t vecSrc, vecAiming; + vec3_t vecSpread; + vec3_t up, right, forward; + float flSpread = 0.01; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + + AngleVectors( angles, forward, right, up ); + + shell = gEngfuncs.pEventAPI->EV_FindModelIndex ("models/shotgunshell.mdl");// brass shell + + if ( EV_IsLocal( idx ) ) + { + // Add muzzle flash to current weapon model + EV_MuzzleFlash(); + gEngfuncs.pEventAPI->EV_WeaponAnimation( SHOTGUN_FIRE2, 2 ); + V_PunchAxis( 0, -10.0 ); + } + + for ( j = 0; j < 2; j++ ) + { + EV_GetDefaultShellInfo( args, origin, velocity, ShellVelocity, ShellOrigin, forward, right, up, 32, -12, 6 ); + + EV_EjectBrass ( ShellOrigin, ShellVelocity, angles[ YAW ], shell, TE_BOUNCE_SHOTSHELL ); + } + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/dbarrel1.wav", gEngfuncs.pfnRandomFloat(0.98, 1.0), ATTN_NORM, 0, 85 + gEngfuncs.pfnRandomLong( 0, 0x1f ) ); + + EV_GetGunPosition( args, vecSrc, origin ); + VectorCopy( forward, vecAiming ); + + if ( gEngfuncs.GetMaxClients() > 1 ) + { + EV_HLDM_FireBullets( idx, forward, right, up, 8, vecSrc, vecAiming, 2048, BULLET_PLAYER_BUCKSHOT, 0, &tracerCount[idx-1], 0.17365, 0.04362 ); + } + else + { + EV_HLDM_FireBullets( idx, forward, right, up, 12, vecSrc, vecAiming, 2048, BULLET_PLAYER_BUCKSHOT, 0, &tracerCount[idx-1], 0.08716, 0.08716 ); + } +} + +void EV_FireShotGunSingle( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + + vec3_t ShellVelocity; + vec3_t ShellOrigin; + int shell; + vec3_t vecSrc, vecAiming; + vec3_t vecSpread; + vec3_t up, right, forward; + float flSpread = 0.01; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + + AngleVectors( angles, forward, right, up ); + + shell = gEngfuncs.pEventAPI->EV_FindModelIndex ("models/shotgunshell.mdl");// brass shell + + if ( EV_IsLocal( idx ) ) + { + // Add muzzle flash to current weapon model + EV_MuzzleFlash(); + gEngfuncs.pEventAPI->EV_WeaponAnimation( SHOTGUN_FIRE, 2 ); + + V_PunchAxis( 0, -5.0 ); + } + + EV_GetDefaultShellInfo( args, origin, velocity, ShellVelocity, ShellOrigin, forward, right, up, 32, -12, 6 ); + + EV_EjectBrass ( ShellOrigin, ShellVelocity, angles[ YAW ], shell, TE_BOUNCE_SHOTSHELL ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/sbarrel1.wav", gEngfuncs.pfnRandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + gEngfuncs.pfnRandomLong( 0, 0x1f ) ); + + EV_GetGunPosition( args, vecSrc, origin ); + VectorCopy( forward, vecAiming ); + + if ( gEngfuncs.GetMaxClients() > 1 ) + { + EV_HLDM_FireBullets( idx, forward, right, up, 4, vecSrc, vecAiming, 2048, BULLET_PLAYER_BUCKSHOT, 0, &tracerCount[idx-1], 0.08716, 0.04362 ); + } + else + { + EV_HLDM_FireBullets( idx, forward, right, up, 6, vecSrc, vecAiming, 2048, BULLET_PLAYER_BUCKSHOT, 0, &tracerCount[idx-1], 0.08716, 0.08716 ); + } +} +//====================== +// SHOTGUN END +//====================== + +//====================== +// MP5 START +//====================== +void EV_FireMP5( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + + vec3_t ShellVelocity; + vec3_t ShellOrigin; + int shell; + vec3_t vecSrc, vecAiming; + vec3_t up, right, forward; + float flSpread = 0.01; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + + AngleVectors( angles, forward, right, up ); + + shell = gEngfuncs.pEventAPI->EV_FindModelIndex ("models/shell.mdl");// brass shell + + if ( EV_IsLocal( idx ) ) + { + // Add muzzle flash to current weapon model + EV_MuzzleFlash(); + gEngfuncs.pEventAPI->EV_WeaponAnimation( MP5_FIRE1 + gEngfuncs.pfnRandomLong(0,2), 2 ); + + V_PunchAxis( 0, gEngfuncs.pfnRandomFloat( -2, 2 ) ); + } + + EV_GetDefaultShellInfo( args, origin, velocity, ShellVelocity, ShellOrigin, forward, right, up, 20, -12, 4 ); + + EV_EjectBrass ( ShellOrigin, ShellVelocity, angles[ YAW ], shell, TE_BOUNCE_SHELL ); + + switch( gEngfuncs.pfnRandomLong( 0, 1 ) ) + { + case 0: + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/hks1.wav", 1, ATTN_NORM, 0, 94 + gEngfuncs.pfnRandomLong( 0, 0xf ) ); + break; + case 1: + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/hks2.wav", 1, ATTN_NORM, 0, 94 + gEngfuncs.pfnRandomLong( 0, 0xf ) ); + break; + } + + EV_GetGunPosition( args, vecSrc, origin ); + VectorCopy( forward, vecAiming ); + + if ( gEngfuncs.GetMaxClients() > 1 ) + { + EV_HLDM_FireBullets( idx, forward, right, up, 1, vecSrc, vecAiming, 8192, BULLET_PLAYER_MP5, 2, &tracerCount[idx-1], args->fparam1, args->fparam2 ); + } + else + { + EV_HLDM_FireBullets( idx, forward, right, up, 1, vecSrc, vecAiming, 8192, BULLET_PLAYER_MP5, 2, &tracerCount[idx-1], args->fparam1, args->fparam2 ); + } +} + +// We only predict the animation and sound +// The grenade is still launched from the server. +void EV_FireMP52( event_args_t *args ) +{ + int idx; + vec3_t origin; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + + if ( EV_IsLocal( idx ) ) + { + gEngfuncs.pEventAPI->EV_WeaponAnimation( MP5_LAUNCH, 2 ); + V_PunchAxis( 0, -10 ); + } + + switch( gEngfuncs.pfnRandomLong( 0, 1 ) ) + { + case 0: + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/glauncher.wav", 1, ATTN_NORM, 0, 94 + gEngfuncs.pfnRandomLong( 0, 0xf ) ); + break; + case 1: + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/glauncher2.wav", 1, ATTN_NORM, 0, 94 + gEngfuncs.pfnRandomLong( 0, 0xf ) ); + break; + } +} +//====================== +// MP5 END +//====================== + +//====================== +// PHYTON START +// ( .357 ) +//====================== +void EV_FirePython( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + + vec3_t vecSrc, vecAiming; + vec3_t up, right, forward; + float flSpread = 0.01; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + + AngleVectors( angles, forward, right, up ); + + if ( EV_IsLocal( idx ) ) + { + // Python uses different body in multiplayer versus single player + int multiplayer = gEngfuncs.GetMaxClients() == 1 ? 0 : 1; + + // Add muzzle flash to current weapon model + EV_MuzzleFlash(); + gEngfuncs.pEventAPI->EV_WeaponAnimation( PYTHON_FIRE1, multiplayer ? 1 : 0 ); + + V_PunchAxis( 0, -10.0 ); + } + + switch( gEngfuncs.pfnRandomLong( 0, 1 ) ) + { + case 0: + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/357_shot1.wav", gEngfuncs.pfnRandomFloat(0.8, 0.9), ATTN_NORM, 0, PITCH_NORM ); + break; + case 1: + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/357_shot2.wav", gEngfuncs.pfnRandomFloat(0.8, 0.9), ATTN_NORM, 0, PITCH_NORM ); + break; + } + + EV_GetGunPosition( args, vecSrc, origin ); + + VectorCopy( forward, vecAiming ); + + EV_HLDM_FireBullets( idx, forward, right, up, 1, vecSrc, vecAiming, 8192, BULLET_PLAYER_357, 0, 0, args->fparam1, args->fparam2 ); +} +//====================== +// PHYTON END +// ( .357 ) +//====================== + +//====================== +// GAUSS START +//====================== +#define SND_CHANGE_PITCH (1<<7) // duplicated in protocol.h change sound pitch + +void EV_SpinGauss( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + int iSoundState = 0; + + int pitch; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + + pitch = args->iparam1; + + iSoundState = args->bparam1 ? SND_CHANGE_PITCH : 0; + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "ambience/pulsemachine.wav", 1.0, ATTN_NORM, iSoundState, pitch ); +} + +/* +============================== +EV_StopPreviousGauss + +============================== +*/ +void EV_StopPreviousGauss( int idx ) +{ + // Make sure we don't have a gauss spin event in the queue for this guy + gEngfuncs.pEventAPI->EV_KillEvents( idx, "events/gaussspin.sc" ); + gEngfuncs.pEventAPI->EV_StopSound( idx, CHAN_WEAPON, "ambience/pulsemachine.wav" ); +} + +extern float g_flApplyVel; + +void EV_FireGauss( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + float flDamage = args->fparam1; + int primaryfire = args->bparam1; + + int m_fPrimaryFire = args->bparam1; + int m_iWeaponVolume = GAUSS_PRIMARY_FIRE_VOLUME; + vec3_t vecSrc; + vec3_t vecDest; + edict_t *pentIgnore; + pmtrace_t tr, beam_tr; + float flMaxFrac = 1.0; + int nTotal = 0; + int fHasPunched = 0; + int fFirstBeam = 1; + int nMaxHits = 10; + physent_t *pEntity; + int m_iBeam, m_iGlow, m_iBalls; + vec3_t up, right, forward; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + + if ( args->bparam2 ) + { + EV_StopPreviousGauss( idx ); + return; + } + +// Con_Printf( "Firing gauss with %f\n", flDamage ); + EV_GetGunPosition( args, vecSrc, origin ); + + m_iBeam = gEngfuncs.pEventAPI->EV_FindModelIndex( "sprites/smoke.spr" ); + m_iBalls = m_iGlow = gEngfuncs.pEventAPI->EV_FindModelIndex( "sprites/hotglow.spr" ); + + AngleVectors( angles, forward, right, up ); + + VectorMA( vecSrc, 8192, forward, vecDest ); + + if ( EV_IsLocal( idx ) ) + { + V_PunchAxis( 0, -2.0 ); + gEngfuncs.pEventAPI->EV_WeaponAnimation( GAUSS_FIRE2, 2 ); + + if ( m_fPrimaryFire == false ) + g_flApplyVel = flDamage; + + } + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/gauss2.wav", 0.5 + flDamage * (1.0 / 400.0), ATTN_NORM, 0, 85 + gEngfuncs.pfnRandomLong( 0, 0x1f ) ); + + while (flDamage > 10 && nMaxHits > 0) + { + nMaxHits--; + + 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, vecDest, PM_STUDIO_BOX, -1, &tr ); + + gEngfuncs.pEventAPI->EV_PopPMStates(); + + if ( tr.allsolid ) + break; + + if (fFirstBeam) + { + if ( EV_IsLocal( idx ) ) + { + // Add muzzle flash to current weapon model + EV_MuzzleFlash(); + } + fFirstBeam = 0; + + gEngfuncs.pEfxAPI->R_BeamEntPoint( + idx | 0x1000, + tr.endpos, + m_iBeam, + 0.1, + m_fPrimaryFire ? 1.0 : 2.5, + 0.0, + m_fPrimaryFire ? 128.0 : flDamage, + 0, + 0, + 0, + m_fPrimaryFire ? 255 : 255, + m_fPrimaryFire ? 128 : 255, + m_fPrimaryFire ? 0 : 255 + ); + } + else + { + gEngfuncs.pEfxAPI->R_BeamPoints( vecSrc, + tr.endpos, + m_iBeam, + 0.1, + m_fPrimaryFire ? 1.0 : 2.5, + 0.0, + m_fPrimaryFire ? 128.0 : flDamage, + 0, + 0, + 0, + m_fPrimaryFire ? 255 : 255, + m_fPrimaryFire ? 128 : 255, + m_fPrimaryFire ? 0 : 255 + ); + } + + pEntity = gEngfuncs.pEventAPI->EV_GetPhysent( tr.ent ); + if ( pEntity == NULL ) + break; + + if ( pEntity->solid == SOLID_BSP ) + { + float n; + + pentIgnore = NULL; + + n = -DotProduct( tr.plane.normal, forward ); + + if (n < 0.5) // 60 degrees + { + // ALERT( at_console, "reflect %f\n", n ); + // reflect + vec3_t r; + + VectorMA( forward, 2.0 * n, tr.plane.normal, r ); + + flMaxFrac = flMaxFrac - tr.fraction; + + VectorCopy( r, forward ); + + VectorMA( tr.endpos, 8.0, forward, vecSrc ); + VectorMA( vecSrc, 8192.0, forward, vecDest ); + + gEngfuncs.pEfxAPI->R_TempSprite( tr.endpos, vec3_origin, 0.2, m_iGlow, kRenderGlow, kRenderFxNoDissipation, flDamage * n / 255.0, flDamage * n * 0.5 * 0.1, FTENT_FADEOUT ); + + vec3_t fwd; + VectorAdd( tr.endpos, tr.plane.normal, fwd ); + + gEngfuncs.pEfxAPI->R_Sprite_Trail( TE_SPRITETRAIL, tr.endpos, fwd, m_iBalls, 3, 0.1, gEngfuncs.pfnRandomFloat( 10, 20 ) / 100.0, 100, + 255, 100 ); + + // lose energy + if ( n == 0 ) + { + n = 0.1; + } + + flDamage = flDamage * (1 - n); + + } + else + { + // tunnel + EV_HLDM_DecalGunshot( &tr, BULLET_MONSTER_12MM ); + + gEngfuncs.pEfxAPI->R_TempSprite( tr.endpos, vec3_origin, 1.0, m_iGlow, kRenderGlow, kRenderFxNoDissipation, flDamage / 255.0, 6.0, FTENT_FADEOUT ); + + // limit it to one hole punch + if (fHasPunched) + { + break; + } + fHasPunched = 1; + + // try punching through wall if secondary attack (primary is incapable of breaking through) + if ( !m_fPrimaryFire ) + { + vec3_t start; + + VectorMA( tr.endpos, 8.0, forward, start ); + + // 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( start, vecDest, PM_STUDIO_BOX, -1, &beam_tr ); + + if ( !beam_tr.allsolid ) + { + vec3_t delta; + float n; + + // trace backwards to find exit point + + gEngfuncs.pEventAPI->EV_PlayerTrace( beam_tr.endpos, tr.endpos, PM_STUDIO_BOX, -1, &beam_tr ); + + VectorSubtract( beam_tr.endpos, tr.endpos, delta ); + + n = Length( delta ); + + if (n < flDamage) + { + if (n == 0) + n = 1; + flDamage -= n; + + // absorption balls + { + vec3_t fwd; + VectorSubtract( tr.endpos, forward, fwd ); + gEngfuncs.pEfxAPI->R_Sprite_Trail( TE_SPRITETRAIL, tr.endpos, fwd, m_iBalls, 3, 0.1, gEngfuncs.pfnRandomFloat( 10, 20 ) / 100.0, 100, + 255, 100 ); + } + + //////////////////////////////////// WHAT TO DO HERE + // CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, NORMAL_EXPLOSION_VOLUME, 3.0 ); + + EV_HLDM_DecalGunshot( &beam_tr, BULLET_MONSTER_12MM ); + + gEngfuncs.pEfxAPI->R_TempSprite( beam_tr.endpos, vec3_origin, 0.1, m_iGlow, kRenderGlow, kRenderFxNoDissipation, flDamage / 255.0, 6.0, FTENT_FADEOUT ); + + // balls + { + vec3_t fwd; + VectorSubtract( beam_tr.endpos, forward, fwd ); + gEngfuncs.pEfxAPI->R_Sprite_Trail( TE_SPRITETRAIL, beam_tr.endpos, fwd, m_iBalls, (int)(flDamage * 0.3), 0.1, gEngfuncs.pfnRandomFloat( 10, 20 ) / 100.0, 200, + 255, 40 ); + } + + VectorAdd( beam_tr.endpos, forward, vecSrc ); + } + } + else + { + flDamage = 0; + } + + gEngfuncs.pEventAPI->EV_PopPMStates(); + } + else + { + if ( m_fPrimaryFire ) + { + // slug doesn't punch through ever with primary + // fire, so leave a little glowy bit and make some balls + gEngfuncs.pEfxAPI->R_TempSprite( tr.endpos, vec3_origin, 0.2, m_iGlow, kRenderGlow, kRenderFxNoDissipation, 200.0 / 255.0, 0.3, FTENT_FADEOUT ); + + { + vec3_t fwd; + VectorAdd( tr.endpos, tr.plane.normal, fwd ); + gEngfuncs.pEfxAPI->R_Sprite_Trail( TE_SPRITETRAIL, tr.endpos, fwd, m_iBalls, 8, 0.6, gEngfuncs.pfnRandomFloat( 10, 20 ) / 100.0, 100, + 255, 200 ); + } + } + + flDamage = 0; + } + } + } + else + { + VectorAdd( tr.endpos, forward, vecSrc ); + } + } +} +//====================== +// GAUSS END +//====================== + +//====================== +// CROWBAR START +//====================== + +enum crowbar_e { + CROWBAR_IDLE = 0, + CROWBAR_DRAW, + CROWBAR_HOLSTER, + CROWBAR_ATTACK1HIT, + CROWBAR_ATTACK1MISS, + CROWBAR_ATTACK2MISS, + CROWBAR_ATTACK2HIT, + CROWBAR_ATTACK3MISS, + CROWBAR_ATTACK3HIT +}; + +int g_iSwing; + +//Only predict the miss sounds, hit sounds are still played +//server side, so players don't get the wrong idea. +void EV_Crowbar( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + + //Play Swing sound + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/cbar_miss1.wav", 1, ATTN_NORM, 0, PITCH_NORM); + + if ( EV_IsLocal( idx ) ) + { + gEngfuncs.pEventAPI->EV_WeaponAnimation( CROWBAR_ATTACK1MISS, 1 ); + + switch( (g_iSwing++) % 3 ) + { + case 0: + gEngfuncs.pEventAPI->EV_WeaponAnimation ( CROWBAR_ATTACK1MISS, 1 ); break; + case 1: + gEngfuncs.pEventAPI->EV_WeaponAnimation ( CROWBAR_ATTACK2MISS, 1 ); break; + case 2: + gEngfuncs.pEventAPI->EV_WeaponAnimation ( CROWBAR_ATTACK3MISS, 1 ); break; + } + } +} +//====================== +// CROWBAR END +//====================== + +//====================== +// CROSSBOW START +//====================== +enum crossbow_e { + CROSSBOW_IDLE1 = 0, // full + CROSSBOW_IDLE2, // empty + CROSSBOW_FIDGET1, // full + CROSSBOW_FIDGET2, // empty + CROSSBOW_FIRE1, // full + CROSSBOW_FIRE2, // reload + CROSSBOW_FIRE3, // empty + CROSSBOW_RELOAD, // from empty + CROSSBOW_DRAW1, // full + CROSSBOW_DRAW2, // empty + CROSSBOW_HOLSTER1, // full + CROSSBOW_HOLSTER2, // empty +}; + +//===================== +// EV_BoltCallback +// This function is used to correct the origin and angles +// of the bolt, so it looks like it's stuck on the wall. +//===================== +void EV_BoltCallback ( struct tempent_s *ent, float frametime, float currenttime ) +{ + ent->entity.origin = ent->entity.baseline.vuser1; + ent->entity.angles = ent->entity.baseline.vuser2; +} + +void EV_FireCrossbow2( event_args_t *args ) +{ + vec3_t vecSrc, vecEnd; + vec3_t up, right, forward; + pmtrace_t tr; + + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + + VectorCopy( args->velocity, velocity ); + + AngleVectors( angles, forward, right, up ); + + EV_GetGunPosition( args, vecSrc, origin ); + + VectorMA( vecSrc, 8192, forward, vecEnd ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/xbow_fire1.wav", 1, ATTN_NORM, 0, 93 + gEngfuncs.pfnRandomLong(0,0xF) ); + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_ITEM, "weapons/xbow_reload1.wav", gEngfuncs.pfnRandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + gEngfuncs.pfnRandomLong(0,0xF) ); + + if ( EV_IsLocal( idx ) ) + { + if ( args->iparam1 ) + gEngfuncs.pEventAPI->EV_WeaponAnimation( CROSSBOW_FIRE1, 1 ); + else if ( args->iparam2 ) + gEngfuncs.pEventAPI->EV_WeaponAnimation( CROSSBOW_FIRE3, 1 ); + } + + // 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 ); + + //We hit something + if ( tr.fraction < 1.0 ) + { + physent_t *pe = gEngfuncs.pEventAPI->EV_GetPhysent( tr.ent ); + + //Not the world, let's assume we hit something organic ( dog, cat, uncle joe, etc ). + if ( pe->solid != SOLID_BSP ) + { + switch( gEngfuncs.pfnRandomLong(0,1) ) + { + case 0: + gEngfuncs.pEventAPI->EV_PlaySound( idx, tr.endpos, CHAN_BODY, "weapons/xbow_hitbod1.wav", 1, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: + gEngfuncs.pEventAPI->EV_PlaySound( idx, tr.endpos, CHAN_BODY, "weapons/xbow_hitbod2.wav", 1, ATTN_NORM, 0, PITCH_NORM ); break; + } + } + //Stick to world but don't stick to glass, it might break and leave the bolt floating. It can still stick to other non-transparent breakables though. + else if ( pe->rendermode == kRenderNormal ) + { + gEngfuncs.pEventAPI->EV_PlaySound( 0, tr.endpos, CHAN_BODY, "weapons/xbow_hit1.wav", gEngfuncs.pfnRandomFloat(0.95, 1.0), ATTN_NORM, 0, PITCH_NORM ); + + //Not underwater, do some sparks... + if ( gEngfuncs.PM_PointContents( tr.endpos, NULL ) != CONTENTS_WATER) + gEngfuncs.pEfxAPI->R_SparkShower( tr.endpos ); + + vec3_t vBoltAngles; + int iModelIndex = gEngfuncs.pEventAPI->EV_FindModelIndex( "models/crossbow_bolt.mdl" ); + + VectorAngles( forward, vBoltAngles ); + + TEMPENTITY *bolt = gEngfuncs.pEfxAPI->R_TempModel( tr.endpos - forward * 10, Vector( 0, 0, 0), vBoltAngles , 5, iModelIndex, TE_BOUNCE_NULL ); + + if ( bolt ) + { + bolt->flags |= ( FTENT_CLIENTCUSTOM ); //So it calls the callback function. + bolt->entity.baseline.vuser1 = tr.endpos - forward * 10; // Pull out a little bit + bolt->entity.baseline.vuser2 = vBoltAngles; //Look forward! + bolt->callback = EV_BoltCallback; //So we can set the angles and origin back. (Stick the bolt to the wall) + } + } + } + + gEngfuncs.pEventAPI->EV_PopPMStates(); +} + +//TODO: Fully predict the fliying bolt. +void EV_FireCrossbow( event_args_t *args ) +{ + int idx; + vec3_t origin; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/xbow_fire1.wav", 1, ATTN_NORM, 0, 93 + gEngfuncs.pfnRandomLong(0,0xF) ); + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_ITEM, "weapons/xbow_reload1.wav", gEngfuncs.pfnRandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + gEngfuncs.pfnRandomLong(0,0xF) ); + + //Only play the weapon anims if I shot it. + if ( EV_IsLocal( idx ) ) + { + if ( args->iparam1 ) + gEngfuncs.pEventAPI->EV_WeaponAnimation( CROSSBOW_FIRE1, 1 ); + else if ( args->iparam2 ) + gEngfuncs.pEventAPI->EV_WeaponAnimation( CROSSBOW_FIRE3, 1 ); + + V_PunchAxis( 0, -2.0 ); + } +} +//====================== +// CROSSBOW END +//====================== + +//====================== +// RPG START +//====================== +enum rpg_e { + RPG_IDLE = 0, + RPG_FIDGET, + RPG_RELOAD, // to reload + RPG_FIRE2, // to empty + RPG_HOLSTER1, // loaded + RPG_DRAW1, // loaded + RPG_HOLSTER2, // unloaded + RPG_DRAW_UL, // unloaded + RPG_IDLE_UL, // unloaded idle + RPG_FIDGET_UL, // unloaded fidget +}; + +void EV_FireRpg( event_args_t *args ) +{ + int idx; + vec3_t origin; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/rocketfire1.wav", 0.9, ATTN_NORM, 0, PITCH_NORM ); + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_ITEM, "weapons/glauncher.wav", 0.7, ATTN_NORM, 0, PITCH_NORM ); + + //Only play the weapon anims if I shot it. + if ( EV_IsLocal( idx ) ) + { + gEngfuncs.pEventAPI->EV_WeaponAnimation( RPG_FIRE2, 1 ); + + V_PunchAxis( 0, -5.0 ); + } +} +//====================== +// RPG END +//====================== + +//====================== +// EGON END +//====================== +enum egon_e { + EGON_IDLE1 = 0, + EGON_FIDGET1, + EGON_ALTFIREON, + EGON_ALTFIRECYCLE, + EGON_ALTFIREOFF, + EGON_FIRE1, + EGON_FIRE2, + EGON_FIRE3, + EGON_FIRE4, + EGON_DRAW, + EGON_HOLSTER +}; + +int g_fireAnims1[] = { EGON_FIRE1, EGON_FIRE2, EGON_FIRE3, EGON_FIRE4 }; +int g_fireAnims2[] = { EGON_ALTFIRECYCLE }; + +enum EGON_FIRESTATE { FIRE_OFF, FIRE_CHARGE }; +enum EGON_FIREMODE { FIRE_NARROW, FIRE_WIDE}; + +#define EGON_PRIMARY_VOLUME 450 +#define EGON_BEAM_SPRITE "sprites/xbeam1.spr" +#define EGON_FLARE_SPRITE "sprites/XSpark1.spr" +#define EGON_SOUND_OFF "weapons/egon_off1.wav" +#define EGON_SOUND_RUN "weapons/egon_run3.wav" +#define EGON_SOUND_STARTUP "weapons/egon_windup2.wav" + +#define ARRAYSIZE(p) (sizeof(p)/sizeof(p[0])) + +BEAM *pBeam; +BEAM *pBeam2; + +void EV_EgonFire( event_args_t *args ) +{ + int idx, iFireState, iFireMode; + vec3_t origin; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + iFireState = args->iparam1; + iFireMode = args->iparam2; + int iStartup = args->bparam1; + + + if ( iStartup ) + { + if ( iFireMode == FIRE_WIDE ) + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, EGON_SOUND_STARTUP, 0.98, ATTN_NORM, 0, 125 ); + else + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, EGON_SOUND_STARTUP, 0.9, ATTN_NORM, 0, 100 ); + } + else + { + if ( iFireMode == FIRE_WIDE ) + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_STATIC, EGON_SOUND_RUN, 0.98, ATTN_NORM, 0, 125 ); + else + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_STATIC, EGON_SOUND_RUN, 0.9, ATTN_NORM, 0, 100 ); + } + + //Only play the weapon anims if I shot it. + if ( EV_IsLocal( idx ) ) + gEngfuncs.pEventAPI->EV_WeaponAnimation ( g_fireAnims1[ gEngfuncs.pfnRandomLong( 0, 3 ) ], 1 ); + + if ( iStartup == 1 && EV_IsLocal( idx ) && !pBeam && !pBeam2 && cl_lw->value ) //Adrian: Added the cl_lw check for those lital people that hate weapon prediction. + { + vec3_t vecSrc, vecEnd, origin, angles, forward, right, up; + pmtrace_t tr; + + cl_entity_t *pl = gEngfuncs.GetEntityByIndex( idx ); + + if ( pl ) + { + VectorCopy( gHUD.m_vecAngles, angles ); + + AngleVectors( angles, forward, right, up ); + + EV_GetGunPosition( args, vecSrc, pl->origin ); + + 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(); + + int iBeamModelIndex = gEngfuncs.pEventAPI->EV_FindModelIndex( EGON_BEAM_SPRITE ); + + float r = 50.0f; + float g = 50.0f; + float b = 125.0f; + + if ( IEngineStudio.IsHardware() ) + { + r /= 100.0f; + g /= 100.0f; + } + + + pBeam = gEngfuncs.pEfxAPI->R_BeamEntPoint ( idx | 0x1000, tr.endpos, iBeamModelIndex, 99999, 3.5, 0.2, 0.7, 55, 0, 0, r, g, b ); + + if ( pBeam ) + pBeam->flags |= ( FBEAM_SINENOISE ); + + pBeam2 = gEngfuncs.pEfxAPI->R_BeamEntPoint ( idx | 0x1000, tr.endpos, iBeamModelIndex, 99999, 5.0, 0.08, 0.7, 25, 0, 0, r, g, b ); + } + } +} + +void EV_EgonStop( event_args_t *args ) +{ + int idx; + vec3_t origin; + + idx = args->entindex; + VectorCopy ( args->origin, origin ); + + gEngfuncs.pEventAPI->EV_StopSound( idx, CHAN_STATIC, EGON_SOUND_RUN ); + + if ( args->iparam1 ) + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, EGON_SOUND_OFF, 0.98, ATTN_NORM, 0, 100 ); + + if ( EV_IsLocal( idx ) ) + { + if ( pBeam ) + { + pBeam->die = 0.0; + pBeam = NULL; + } + + + if ( pBeam2 ) + { + pBeam2->die = 0.0; + pBeam2 = NULL; + } + } +} +//====================== +// EGON END +//====================== + +//====================== +// HORNET START +//====================== +enum hgun_e { + HGUN_IDLE1 = 0, + HGUN_FIDGETSWAY, + HGUN_FIDGETSHAKE, + HGUN_DOWN, + HGUN_UP, + HGUN_SHOOT +}; + +void EV_HornetGunFire( event_args_t *args ) +{ + int idx, iFireMode; + vec3_t origin, angles, vecSrc, forward, right, up; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + iFireMode = args->iparam1; + + //Only play the weapon anims if I shot it. + if ( EV_IsLocal( idx ) ) + { + V_PunchAxis( 0, gEngfuncs.pfnRandomLong ( 0, 2 ) ); + gEngfuncs.pEventAPI->EV_WeaponAnimation ( HGUN_SHOOT, 1 ); + } + + switch ( gEngfuncs.pfnRandomLong ( 0 , 2 ) ) + { + case 0: gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "agrunt/ag_fire1.wav", 1, ATTN_NORM, 0, 100 ); break; + case 1: gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "agrunt/ag_fire2.wav", 1, ATTN_NORM, 0, 100 ); break; + case 2: gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "agrunt/ag_fire3.wav", 1, ATTN_NORM, 0, 100 ); break; + } +} +//====================== +// HORNET END +//====================== + +//====================== +// TRIPMINE START +//====================== +enum tripmine_e { + TRIPMINE_IDLE1 = 0, + TRIPMINE_IDLE2, + TRIPMINE_ARM1, + TRIPMINE_ARM2, + TRIPMINE_FIDGET, + TRIPMINE_HOLSTER, + TRIPMINE_DRAW, + TRIPMINE_WORLD, + TRIPMINE_GROUND, +}; + +//We only check if it's possible to put a trip mine +//and if it is, then we play the animation. Server still places it. +void EV_TripmineFire( event_args_t *args ) +{ + int idx; + vec3_t vecSrc, angles, view_ofs, forward; + pmtrace_t tr; + + idx = args->entindex; + VectorCopy( args->origin, vecSrc ); + VectorCopy( args->angles, angles ); + + AngleVectors ( angles, forward, NULL, NULL ); + + if ( !EV_IsLocal ( idx ) ) + return; + + // Grab predicted result for local player + gEngfuncs.pEventAPI->EV_LocalPlayerViewheight( view_ofs ); + + vecSrc = vecSrc + view_ofs; + + // 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, vecSrc + forward * 128, PM_NORMAL, -1, &tr ); + + //Hit something solid + if ( tr.fraction < 1.0 ) + gEngfuncs.pEventAPI->EV_WeaponAnimation ( TRIPMINE_DRAW, 0 ); + + gEngfuncs.pEventAPI->EV_PopPMStates(); +} +//====================== +// TRIPMINE END +//====================== + +//====================== +// SQUEAK START +//====================== +enum squeak_e { + SQUEAK_IDLE1 = 0, + SQUEAK_FIDGETFIT, + SQUEAK_FIDGETNIP, + SQUEAK_DOWN, + SQUEAK_UP, + SQUEAK_THROW +}; + +#define VEC_HULL_MIN Vector(-16, -16, -36) +#define VEC_DUCK_HULL_MIN Vector(-16, -16, -18 ) + +void EV_SnarkFire( event_args_t *args ) +{ + int idx; + vec3_t vecSrc, angles, view_ofs, forward; + pmtrace_t tr; + + idx = args->entindex; + VectorCopy( args->origin, vecSrc ); + VectorCopy( args->angles, angles ); + + AngleVectors ( angles, forward, NULL, NULL ); + + if ( !EV_IsLocal ( idx ) ) + return; + + if ( args->ducking ) + vecSrc = vecSrc - ( VEC_HULL_MIN - VEC_DUCK_HULL_MIN ); + + // 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 + forward * 20, vecSrc + forward * 64, PM_NORMAL, -1, &tr ); + + //Find space to drop the thing. + if ( tr.allsolid == 0 && tr.startsolid == 0 && tr.fraction > 0.25 ) + gEngfuncs.pEventAPI->EV_WeaponAnimation ( SQUEAK_THROW, 0 ); + + gEngfuncs.pEventAPI->EV_PopPMStates(); +} +//====================== +// SQUEAK END +//====================== + +void EV_TrainPitchAdjust( event_args_t *args ) +{ + int idx; + vec3_t origin; + + unsigned short us_params; + int noise; + float m_flVolume; + int pitch; + int stop; + + char sz[ 256 ]; + + idx = args->entindex; + + VectorCopy( args->origin, origin ); + + us_params = (unsigned short)args->iparam1; + stop = args->bparam1; + + m_flVolume = (float)(us_params & 0x003f)/40.0; + noise = (int)(((us_params) >> 12 ) & 0x0007); + pitch = (int)( 10.0 * (float)( ( us_params >> 6 ) & 0x003f ) ); + + switch ( noise ) + { + case 1: strcpy( sz, "plats/ttrain1.wav"); break; + case 2: strcpy( sz, "plats/ttrain2.wav"); break; + case 3: strcpy( sz, "plats/ttrain3.wav"); break; + case 4: strcpy( sz, "plats/ttrain4.wav"); break; + case 5: strcpy( sz, "plats/ttrain6.wav"); break; + case 6: strcpy( sz, "plats/ttrain7.wav"); break; + default: + // no sound + strcpy( sz, "" ); + return; + } + + if ( stop ) + { + gEngfuncs.pEventAPI->EV_StopSound( idx, CHAN_STATIC, sz ); + } + else + { + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_STATIC, sz, m_flVolume, ATTN_NORM, SND_CHANGE_PITCH, pitch ); + } +} + +int EV_TFC_IsAllyTeam( int iTeam1, int iTeam2 ) +{ + return 0; +} \ No newline at end of file diff --git a/hlsdk/cl_dll/ev_hldm.h b/hlsdk/cl_dll/ev_hldm.h new file mode 100644 index 00000000..567e6877 --- /dev/null +++ b/hlsdk/cl_dll/ev_hldm.h @@ -0,0 +1,95 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined ( EV_HLDMH ) +#define EV_HLDMH + +// bullet types +typedef enum +{ + BULLET_NONE = 0, + BULLET_PLAYER_9MM, // glock + BULLET_PLAYER_MP5, // mp5 + BULLET_PLAYER_357, // python + BULLET_PLAYER_BUCKSHOT, // shotgun + BULLET_PLAYER_CROWBAR, // crowbar swipe + + BULLET_MONSTER_9MM, + BULLET_MONSTER_MP5, + BULLET_MONSTER_12MM, +} Bullet; + +enum glock_e { + GLOCK_IDLE1 = 0, + GLOCK_IDLE2, + GLOCK_IDLE3, + GLOCK_SHOOT, + GLOCK_SHOOT_EMPTY, + GLOCK_RELOAD, + GLOCK_RELOAD_NOT_EMPTY, + GLOCK_DRAW, + GLOCK_HOLSTER, + GLOCK_ADD_SILENCER +}; + +enum shotgun_e { + SHOTGUN_IDLE = 0, + SHOTGUN_FIRE, + SHOTGUN_FIRE2, + SHOTGUN_RELOAD, + SHOTGUN_PUMP, + SHOTGUN_START_RELOAD, + SHOTGUN_DRAW, + SHOTGUN_HOLSTER, + SHOTGUN_IDLE4, + SHOTGUN_IDLE_DEEP +}; + +enum mp5_e +{ + MP5_LONGIDLE = 0, + MP5_IDLE1, + MP5_LAUNCH, + MP5_RELOAD, + MP5_DEPLOY, + MP5_FIRE1, + MP5_FIRE2, + MP5_FIRE3, +}; + +enum python_e { + PYTHON_IDLE1 = 0, + PYTHON_FIDGET, + PYTHON_FIRE1, + PYTHON_RELOAD, + PYTHON_HOLSTER, + PYTHON_DRAW, + PYTHON_IDLE2, + PYTHON_IDLE3 +}; + +#define GAUSS_PRIMARY_CHARGE_VOLUME 256// how loud gauss is while charging +#define GAUSS_PRIMARY_FIRE_VOLUME 450// how loud gauss is when discharged + +enum gauss_e { + GAUSS_IDLE = 0, + GAUSS_IDLE2, + GAUSS_FIDGET, + GAUSS_SPINUP, + GAUSS_SPIN, + GAUSS_FIRE, + GAUSS_FIRE2, + GAUSS_HOLSTER, + GAUSS_DRAW +}; + +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 ); + +#endif // EV_HLDMH \ No newline at end of file diff --git a/hlsdk/cl_dll/events.cpp b/hlsdk/cl_dll/events.cpp new file mode 100644 index 00000000..65c842bc --- /dev/null +++ b/hlsdk/cl_dll/events.cpp @@ -0,0 +1,23 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "hud.h" +#include "cl_util.h" + +void Game_HookEvents( void ); + +/* +=================== +EV_HookEvents + +See if game specific code wants to hook any events. +=================== +*/ +void EV_HookEvents( void ) +{ + Game_HookEvents(); +} \ No newline at end of file diff --git a/hlsdk/cl_dll/eventscripts.h b/hlsdk/cl_dll/eventscripts.h new file mode 100644 index 00000000..e8b145be --- /dev/null +++ b/hlsdk/cl_dll/eventscripts.h @@ -0,0 +1,73 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// eventscripts.h +#if !defined ( EVENTSCRIPTSH ) +#define EVENTSCRIPTSH + +// defaults for clientinfo messages +#define DEFAULT_VIEWHEIGHT 28 +#define VEC_DUCK_VIEW 12 + +#define FTENT_FADEOUT 0x00000080 + +#define DMG_GENERIC 0 // generic damage was done +#define DMG_CRUSH (1 << 0) // crushed by falling or moving object +#define DMG_BULLET (1 << 1) // shot +#define DMG_SLASH (1 << 2) // cut, clawed, stabbed +#define DMG_BURN (1 << 3) // heat burned +#define DMG_FREEZE (1 << 4) // frozen +#define DMG_FALL (1 << 5) // fell too far +#define DMG_BLAST (1 << 6) // explosive blast damage +#define DMG_CLUB (1 << 7) // crowbar, punch, headbutt +#define DMG_SHOCK (1 << 8) // electric shock +#define DMG_SONIC (1 << 9) // sound pulse shockwave +#define DMG_ENERGYBEAM (1 << 10) // laser or other high energy beam +#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 + +#define DMG_DROWN (1 << 14) // Drowning +#define DMG_FIRSTTIMEBASED DMG_DROWN + +#define DMG_PARALYZE (1 << 15) // slows affected creature down +#define DMG_NERVEGAS (1 << 16) // nerve toxins, very bad +#define DMG_POISON (1 << 17) // blood poisioning +#define DMG_RADIATION (1 << 18) // radiation exposure +#define DMG_DROWNRECOVER (1 << 19) // drowning recovery +#define DMG_ACID (1 << 20) // toxic chemicals or acid burns +#define DMG_SLOWBURN (1 << 21) // in an oven +#define DMG_SLOWFREEZE (1 << 22) // in a subzero freezer +#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_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 ); +qboolean EV_IsLocal( int idx ); +qboolean EV_IsPlayer( int idx ); +void EV_CreateTracer( float *start, float *end ); + +struct cl_entity_s *GetEntity( int idx ); +struct cl_entity_s *GetViewEntity( void ); +void EV_MuzzleFlash( void ); + +#endif // EVENTSCRIPTSH diff --git a/hlsdk/cl_dll/flashlight.cpp b/hlsdk/cl_dll/flashlight.cpp new file mode 100644 index 00000000..dab2421b --- /dev/null +++ b/hlsdk/cl_dll/flashlight.cpp @@ -0,0 +1,149 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// flashlight.cpp +// +// implementation of CHudFlashlight class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + + + +DECLARE_MESSAGE(m_Flash, FlashBat) +DECLARE_MESSAGE(m_Flash, Flashlight) + +#define BAT_NAME "sprites/%d_Flashlight.spr" + +int CHudFlashlight::Init(void) +{ + m_fFade = 0; + m_fOn = 0; + + HOOK_MESSAGE(Flashlight); + HOOK_MESSAGE(FlashBat); + + m_iFlags |= HUD_ACTIVE; + + gHUD.AddHudElem(this); + + return 1; +}; + +void CHudFlashlight::Reset(void) +{ + m_fFade = 0; + m_fOn = 0; +} + +int CHudFlashlight::VidInit(void) +{ + int HUD_flash_empty = gHUD.GetSpriteIndex( "flash_empty" ); + int HUD_flash_full = gHUD.GetSpriteIndex( "flash_full" ); + int HUD_flash_beam = gHUD.GetSpriteIndex( "flash_beam" ); + + m_hSprite1 = gHUD.GetSprite(HUD_flash_empty); + m_hSprite2 = gHUD.GetSprite(HUD_flash_full); + m_hBeam = gHUD.GetSprite(HUD_flash_beam); + m_prc1 = &gHUD.GetSpriteRect(HUD_flash_empty); + m_prc2 = &gHUD.GetSpriteRect(HUD_flash_full); + m_prcBeam = &gHUD.GetSpriteRect(HUD_flash_beam); + m_iWidth = m_prc2->right - m_prc2->left; + + return 1; +}; + +int CHudFlashlight:: MsgFunc_FlashBat(const char *pszName, int iSize, void *pbuf ) +{ + + + BEGIN_READ( pbuf, iSize ); + int x = READ_BYTE(); + m_iBat = x; + m_flBat = ((float)x)/100.0; + + return 1; +} + +int CHudFlashlight:: MsgFunc_Flashlight(const char *pszName, int iSize, void *pbuf ) +{ + + BEGIN_READ( pbuf, iSize ); + m_fOn = READ_BYTE(); + int x = READ_BYTE(); + m_iBat = x; + m_flBat = ((float)x)/100.0; + + return 1; +} + +int CHudFlashlight::Draw(float flTime) +{ + if ( gHUD.m_iHideHUDDisplay & ( HIDEHUD_FLASHLIGHT | HIDEHUD_ALL ) ) + return 1; + + 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 + a = MIN_ALPHA; + + if (m_flBat < 0.20) + UnpackRGB(r,g,b, RGB_REDISH); + else + UnpackRGB(r,g,b, RGB_YELLOWISH); + + ScaleColors(r, g, b, a); + + y = (m_prc1->bottom - m_prc2->top)/2; + x = ScreenWidth - m_iWidth - m_iWidth/2 ; + + // Draw the flashlight casing + SPR_Set(m_hSprite1, r, g, b ); + SPR_DrawAdditive( 0, x, y, m_prc1); + + if ( m_fOn ) + { // draw the flashlight beam + x = ScreenWidth - m_iWidth/2; + + SPR_Set( m_hBeam, r, g, b ); + SPR_DrawAdditive( 0, x, y, m_prcBeam ); + } + + // draw the flashlight energy level + x = ScreenWidth - m_iWidth - m_iWidth/2 ; + int iOffset = m_iWidth * (1.0 - m_flBat); + if (iOffset < m_iWidth) + { + rc = *m_prc2; + rc.left += iOffset; + + SPR_Set(m_hSprite2, r, g, b ); + SPR_DrawAdditive( 0, x + iOffset, y, &rc); + } + + + return 1; +} \ No newline at end of file diff --git a/hlsdk/cl_dll/geiger.cpp b/hlsdk/cl_dll/geiger.cpp new file mode 100644 index 00000000..6f5f984f --- /dev/null +++ b/hlsdk/cl_dll/geiger.cpp @@ -0,0 +1,184 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Geiger.cpp +// +// implementation of CHudAmmo class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include + +#include "parsemsg.h" + +DECLARE_MESSAGE(m_Geiger, Geiger ) + +int CHudGeiger::Init(void) +{ + HOOK_MESSAGE( Geiger ); + + m_iGeigerRange = 0; + m_iFlags = 0; + + gHUD.AddHudElem(this); + + srand( (unsigned)time( NULL ) ); + + return 1; +}; + +int CHudGeiger::VidInit(void) +{ + return 1; +}; + +int CHudGeiger::MsgFunc_Geiger(const char *pszName, int iSize, void *pbuf) +{ + + BEGIN_READ( pbuf, iSize ); + + // update geiger data + m_iGeigerRange = READ_BYTE(); + m_iGeigerRange = m_iGeigerRange << 2; + + m_iFlags |= HUD_ACTIVE; + + return 1; +} + +int CHudGeiger::Draw (float flTime) +{ + int pct; + float flvol; + int rg[3]; + int i; + + if (m_iGeigerRange < 1000 && m_iGeigerRange > 0) + { + // peicewise linear is better than continuous formula for this + if (m_iGeigerRange > 800) + { + pct = 0; //Con_Printf ( "range > 800\n"); + } + else if (m_iGeigerRange > 600) + { + pct = 2; + flvol = 0.4; //Con_Printf ( "range > 600\n"); + rg[0] = 1; + rg[1] = 1; + i = 2; + } + else if (m_iGeigerRange > 500) + { + pct = 4; + flvol = 0.5; //Con_Printf ( "range > 500\n"); + rg[0] = 1; + rg[1] = 2; + i = 2; + } + else if (m_iGeigerRange > 400) + { + pct = 8; + flvol = 0.6; //Con_Printf ( "range > 400\n"); + rg[0] = 1; + rg[1] = 2; + rg[2] = 3; + i = 3; + } + else if (m_iGeigerRange > 300) + { + pct = 8; + flvol = 0.7; //Con_Printf ( "range > 300\n"); + rg[0] = 2; + rg[1] = 3; + rg[2] = 4; + i = 3; + } + else if (m_iGeigerRange > 200) + { + pct = 28; + flvol = 0.78; //Con_Printf ( "range > 200\n"); + rg[0] = 2; + rg[1] = 3; + rg[2] = 4; + i = 3; + } + else if (m_iGeigerRange > 150) + { + pct = 40; + flvol = 0.80; //Con_Printf ( "range > 150\n"); + rg[0] = 3; + rg[1] = 4; + rg[2] = 5; + i = 3; + } + else if (m_iGeigerRange > 100) + { + pct = 60; + flvol = 0.85; //Con_Printf ( "range > 100\n"); + rg[0] = 3; + rg[1] = 4; + rg[2] = 5; + i = 3; + } + else if (m_iGeigerRange > 75) + { + pct = 80; + flvol = 0.9; //Con_Printf ( "range > 75\n"); + //gflGeigerDelay = cl.time + GEIGERDELAY * 0.75; + rg[0] = 4; + rg[1] = 5; + rg[2] = 6; + i = 3; + } + else if (m_iGeigerRange > 50) + { + pct = 90; + flvol = 0.95; //Con_Printf ( "range > 50\n"); + rg[0] = 5; + rg[1] = 6; + i = 2; + } + else + { + pct = 95; + flvol = 1.0; //Con_Printf ( "range < 50\n"); + rg[0] = 5; + rg[1] = 6; + i = 2; + } + + flvol = (flvol * ((rand() & 127)) / 255) + 0.25; // UTIL_RandomFloat(0.25, 0.5); + + if ((rand() & 127) < pct || (rand() & 127) < pct) + { + //S_StartDynamicSound (-1, 0, rgsfx[rand() % i], r_origin, flvol, 1.0, 0, 100); + char sz[256]; + + int j = rand() & 1; + if (i > 2) + j += rand() & 1; + + sprintf(sz, "player/geiger%d.wav", j + 1); + PlaySound(sz, flvol); + + } + } + + return 1; +} diff --git a/hlsdk/cl_dll/health.cpp b/hlsdk/cl_dll/health.cpp new file mode 100644 index 00000000..a138595a --- /dev/null +++ b/hlsdk/cl_dll/health.cpp @@ -0,0 +1,472 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Health.cpp +// +// implementation of CHudHealth class +// + +#include "STDIO.H" +#include "STDLIB.H" +#include "MATH.H" + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" +#include + + +DECLARE_MESSAGE(m_Health, Health ) +DECLARE_MESSAGE(m_Health, Damage ) + +#define PAIN_NAME "sprites/%d_pain.spr" +#define DAMAGE_NAME "sprites/%d_dmg.spr" + +int giDmgHeight, giDmgWidth; + +int giDmgFlags[NUM_DMG_TYPES] = +{ + DMG_POISON, + DMG_ACID, + DMG_FREEZE|DMG_SLOWFREEZE, + DMG_DROWN, + DMG_BURN|DMG_SLOWBURN, + 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_bitsDamage = 0; + m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 0; + giDmgHeight = 0; + giDmgWidth = 0; + + memset(m_dmg, 0, sizeof(DAMAGE_IMAGE) * NUM_DMG_TYPES); + + + gHUD.AddHudElem(this); + return 1; +} + +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++ ) + { + m_dmg[i].fExpire = 0; + } +} + +int CHudHealth::VidInit(void) +{ + m_hSprite = 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; + 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; + } + + return 1; +} + + +int CHudHealth:: MsgFunc_Damage(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int armor = READ_BYTE(); // armor + int damageTaken = READ_BYTE(); // health + long bitsDamage = READ_LONG(); // damage bits + + vec3_t vecFrom; + + for ( int i = 0 ; i < 3 ; i++) + vecFrom[i] = READ_COORD(); + + UpdateTiles(gHUD.m_flTime, bitsDamage); + + // 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 ) +{ + int iHealth = m_iHealth; + + if (iHealth > 25) + iHealth -= 25; + else if ( iHealth < 0 ) + iHealth = 0; +#if 0 + g = iHealth * 255 / 100; + r = 255 - g; + b = 0; +#else + if (m_iHealth > 25) + { + UnpackRGB(r,g,b, RGB_YELLOWISH); + } + else + { + r = 250; + g = 0; + b = 0; + } +#endif +} + +int CHudHealth::Draw(float flTime) +{ + int r, g, b; + int a = 0, x, y; + int HealthWidth; + + if ( (gHUD.m_iHideHUDDisplay & HIDEHUD_HEALTH) || gEngfuncs.IsSpectateOnly() ) + return 1; + + if ( !m_hSprite ) + m_hSprite = LoadSprite(PAIN_NAME); + + // Has health changed? Flash the health # + if (m_fFade) + { + m_fFade -= (gHUD.m_flTimeDelta * 20); + if (m_fFade <= 0) + { + a = MIN_ALPHA; + m_fFade = 0; + } + + // Fade the health number back to dim + + a = MIN_ALPHA + (m_fFade/FADE_TIME) * 128; + + } + else + a = MIN_ALPHA; + + // If health is getting low, make it bright red + if (m_iHealth <= 15) + a = 255; + + GetPainColor( r, g, b ); + ScaleColors(r, g, b, a ); + + // 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; + + 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); + } + + DrawDamage(flTime); + return DrawPain(flTime); +} + +void CHudHealth::CalcDamageDirection(vec3_t vecFrom) +{ + vec3_t forward, right, up; + float side, front; + vec3_t vecOrigin, vecAngles; + + if (!vecFrom[0] && !vecFrom[1] && !vecFrom[2]) + { + m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 0; + 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(); + + vecFrom = vecFrom.Normalize(); + AngleVectors (vecAngles, forward, right, up); + + front = DotProduct (vecFrom, right); + side = DotProduct (vecFrom, forward); + + if (flDistToTarget <= 50) + { + m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 1; + } + else + { + if (side > 0) + { + if (side > 0.3) + m_fAttackFront = max(m_fAttackFront, side); + } + else + { + float f = fabs(side); + if (f > 0.3) + m_fAttackRear = max(m_fAttackRear, f); + } + + if (front > 0) + { + if (front > 0.3) + m_fAttackRight = max(m_fAttackRight, front); + } + else + { + float f = fabs(front); + if (f > 0.3) + m_fAttackLeft = max(m_fAttackLeft, f); + } + } +} + +int CHudHealth::DrawPain(float flTime) +{ + if (!(m_fAttackFront || m_fAttackRear || m_fAttackLeft || m_fAttackRight)) + return 1; + + int r, g, b; + int x, y, a, shade; + + // TODO: get the shift value of the health + a = 255; // max brightness until then + + float fFade = gHUD.m_flTimeDelta * 2; + + // SPR_Draw top + if (m_fAttackFront > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackFront, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 - SPR_Width(m_hSprite, 0)/2; + y = ScreenHeight/2 - SPR_Height(m_hSprite,0) * 3; + SPR_DrawAdditive(0, x, y, NULL); + m_fAttackFront = max( 0, m_fAttackFront - fFade ); + } else + m_fAttackFront = 0; + + if (m_fAttackRight > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackRight, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 + SPR_Width(m_hSprite, 1) * 2; + y = ScreenHeight/2 - SPR_Height(m_hSprite,1)/2; + SPR_DrawAdditive(1, x, y, NULL); + m_fAttackRight = max( 0, m_fAttackRight - fFade ); + } else + m_fAttackRight = 0; + + if (m_fAttackRear > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackRear, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 - SPR_Width(m_hSprite, 2)/2; + y = ScreenHeight/2 + SPR_Height(m_hSprite,2) * 2; + SPR_DrawAdditive(2, x, y, NULL); + m_fAttackRear = max( 0, m_fAttackRear - fFade ); + } else + m_fAttackRear = 0; + + if (m_fAttackLeft > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackLeft, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 - SPR_Width(m_hSprite, 3) * 3; + y = ScreenHeight/2 - SPR_Height(m_hSprite,3)/2; + SPR_DrawAdditive(3, x, y, NULL); + + m_fAttackLeft = max( 0, m_fAttackLeft - fFade ); + } else + m_fAttackLeft = 0; + + return 1; +} + +int CHudHealth::DrawDamage(float flTime) +{ + int r, g, b, a; + DAMAGE_IMAGE *pdmg; + + if (!m_bitsDamage) + return 1; + + UnpackRGB(r,g,b, RGB_YELLOWISH); + + a = (int)( fabs(sin(flTime*2)) * 256.0); + + ScaleColors(r, g, b, a); + + // Draw all the items + for (int i = 0; i < NUM_DMG_TYPES; i++) + { + if (m_bitsDamage & giDmgFlags[i]) + { + pdmg = &m_dmg[i]; + SPR_Set(gHUD.GetSprite(m_HUD_dmg_bio + i), r, g, b ); + SPR_DrawAdditive(0, pdmg->x, pdmg->y, &gHUD.GetSpriteRect(m_HUD_dmg_bio + i)); + } + } + + + // check for bits that should be expired + for ( int i = 0; i < NUM_DMG_TYPES; i++ ) + { + DAMAGE_IMAGE *pdmg = &m_dmg[i]; + + if ( m_bitsDamage & giDmgFlags[i] ) + { + pdmg->fExpire = min( flTime + DMG_IMAGE_LIFE, pdmg->fExpire ); + + if ( pdmg->fExpire <= flTime // when the time has expired + && a < 40 ) // and the flash is at the low point of the cycle + { + pdmg->fExpire = 0; + + int y = pdmg->y; + pdmg->x = pdmg->y = 0; + + // move everyone above down + for (int j = 0; j < NUM_DMG_TYPES; j++) + { + pdmg = &m_dmg[j]; + if ((pdmg->y) && (pdmg->y < y)) + pdmg->y += giDmgHeight; + + } + + m_bitsDamage &= ~giDmgFlags[i]; // clear the bits + } + } + } + + return 1; +} + + +void CHudHealth::UpdateTiles(float flTime, long bitsDamage) +{ + DAMAGE_IMAGE *pdmg; + + // Which types are new? + long bitsOn = ~m_bitsDamage & bitsDamage; + + for (int i = 0; i < NUM_DMG_TYPES; i++) + { + pdmg = &m_dmg[i]; + + // Is this one already on? + if (m_bitsDamage & giDmgFlags[i]) + { + pdmg->fExpire = flTime + DMG_IMAGE_LIFE; // extend the duration + if (!pdmg->fBaseline) + pdmg->fBaseline = flTime; + } + + // Are we just turning it on? + if (bitsOn & giDmgFlags[i]) + { + // put this one at the bottom + pdmg->x = giDmgWidth/8; + pdmg->y = ScreenHeight - giDmgHeight * 2; + pdmg->fExpire=flTime + DMG_IMAGE_LIFE; + + // move everyone else up + for (int j = 0; j < NUM_DMG_TYPES; j++) + { + if (j == i) + continue; + + pdmg = &m_dmg[j]; + if (pdmg->y) + pdmg->y -= giDmgHeight; + + } + pdmg = &m_dmg[i]; + } + } + + // damage bits are only turned on here; they are turned off when the draw time has expired (in DrawDamage()) + m_bitsDamage |= bitsDamage; +} diff --git a/hlsdk/cl_dll/health.h b/hlsdk/cl_dll/health.h new file mode 100644 index 00000000..41717e78 --- /dev/null +++ b/hlsdk/cl_dll/health.h @@ -0,0 +1,127 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#define DMG_IMAGE_LIFE 2 // seconds that image is up + +#define DMG_IMAGE_POISON 0 +#define DMG_IMAGE_ACID 1 +#define DMG_IMAGE_COLD 2 +#define DMG_IMAGE_DROWN 3 +#define DMG_IMAGE_BURN 4 +#define DMG_IMAGE_NERVE 5 +#define DMG_IMAGE_RAD 6 +#define DMG_IMAGE_SHOCK 7 +//tf defines +#define DMG_IMAGE_CALTROP 8 +#define DMG_IMAGE_TRANQ 9 +#define DMG_IMAGE_CONCUSS 10 +#define DMG_IMAGE_HALLUC 11 +#define NUM_DMG_TYPES 12 +// instant damage + +#define DMG_GENERIC 0 // generic damage was done +#define DMG_CRUSH (1 << 0) // crushed by falling or moving object +#define DMG_BULLET (1 << 1) // shot +#define DMG_SLASH (1 << 2) // cut, clawed, stabbed +#define DMG_BURN (1 << 3) // heat burned +#define DMG_FREEZE (1 << 4) // frozen +#define DMG_FALL (1 << 5) // fell too far +#define DMG_BLAST (1 << 6) // explosive blast damage +#define DMG_CLUB (1 << 7) // crowbar, punch, headbutt +#define DMG_SHOCK (1 << 8) // electric shock +#define DMG_SONIC (1 << 9) // sound pulse shockwave +#define DMG_ENERGYBEAM (1 << 10) // laser or other high energy beam +#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 + + +#define DMG_DROWN (1 << 14) // Drowning +#define DMG_FIRSTTIMEBASED DMG_DROWN + +#define DMG_PARALYZE (1 << 15) // slows affected creature down +#define DMG_NERVEGAS (1 << 16) // nerve toxins, very bad +#define DMG_POISON (1 << 17) // blood poisioning +#define DMG_RADIATION (1 << 18) // radiation exposure +#define DMG_DROWNRECOVER (1 << 19) // drowning recovery +#define DMG_ACID (1 << 20) // toxic chemicals or acid burns +#define DMG_SLOWBURN (1 << 21) // in an oven +#define DMG_SLOWFREEZE (1 << 22) // in a subzero freezer +#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_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) + +// 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; + float fBaseline; + int x, y; +} DAMAGE_IMAGE; + +// +//----------------------------------------------------- +// +class CHudHealth: public CHudBase +{ +public: + virtual int Init( void ); + 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; + +private: + HSPRITE m_hSprite; + HSPRITE m_hDamage; + + DAMAGE_IMAGE m_dmg[NUM_DMG_TYPES]; + int m_bitsDamage; + int DrawPain(float fTime); + int DrawDamage(float fTime); + void CalcDamageDirection(vec3_t vecFrom); + void UpdateTiles(float fTime, long bits); +}; diff --git a/hlsdk/cl_dll/hl/hl_baseentity.cpp b/hlsdk/cl_dll/hl/hl_baseentity.cpp new file mode 100644 index 00000000..e07b5ad3 --- /dev/null +++ b/hlsdk/cl_dll/hl/hl_baseentity.cpp @@ -0,0 +1,267 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +/* +========================== +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" +#include "skill.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 ){ } +struct skilldata_t gSkillData; +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 :: Look ( int iDistance ) { } +float CBaseAnimating :: StudioFrameAdvance ( float flInterval ) { return 0.0; } +int CBaseMonster::IRelationship ( CBaseEntity *pTarget ) { return 0; } +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::ChangeYaw ( int yawSpeed ) { return 0; } +int CBaseAnimating :: LookupActivity ( int activity ) { return 0; } +int CBaseAnimating :: LookupActivityHeaviest ( int activity ) { return 0; } +int CBaseAnimating :: LookupSequence ( const char *label ) { return 0; } +void CBaseAnimating :: ResetSequenceInfo ( ) { } +BOOL CBaseAnimating :: GetSequenceFlags( ) { return FALSE; } +void CBaseAnimating :: DispatchAnimEvents ( float flInterval ) { } +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; } +void CBaseEntity::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) { } +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 ) { } +void CBaseMonster::ReportAIState( void ) { } +void CBaseMonster :: KeyValue( KeyValueData *pkvd ) { } +BOOL CBaseMonster :: FCheckAITrigger ( void ) { return FALSE; } +void CBaseMonster::CorpseFallThink( void ) { } +void CBaseMonster :: MonsterInitDead( void ) { } +void CBaseMonster :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) { } +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::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 TrainSpeed(int iSpeed, int iMax) { return 0; } +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 CBasePlayer :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { return 0; } +void CBasePlayer::RemoveAllItems( BOOL removeSuit ) { } +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 :: UpdateGeigerCounter( void ) { } +void CBasePlayer::CheckSuitUpdate() { } +void CBasePlayer::SetSuitUpdate(char *name, int fgroup, int iNoRepeatTime) { } +void CBasePlayer :: UpdatePlayerSound ( void ) { } +void CBasePlayer::PostThink() { } +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() { } +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; } +void CBasePlayer :: BarnacleVictimBitten ( entvars_t *pevBarnacle ) { } +void CBasePlayer :: BarnacleVictimReleased ( void ) { } +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/hlsdk/cl_dll/hl/hl_events.cpp b/hlsdk/cl_dll/hl/hl_events.cpp new file mode 100644 index 00000000..5ad4459e --- /dev/null +++ b/hlsdk/cl_dll/hl/hl_events.cpp @@ -0,0 +1,80 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "../hud.h" +#include "../cl_util.h" +#include "event_api.h" + +extern "C" +{ +// HLDM +void EV_FireGlock1( struct event_args_s *args ); +void EV_FireGlock2( struct event_args_s *args ); +void EV_FireShotGunSingle( struct event_args_s *args ); +void EV_FireShotGunDouble( struct event_args_s *args ); +void EV_FireMP5( struct event_args_s *args ); +void EV_FireMP52( struct event_args_s *args ); +void EV_FirePython( struct event_args_s *args ); +void EV_FireGauss( struct event_args_s *args ); +void EV_SpinGauss( struct event_args_s *args ); +void EV_Crowbar( struct event_args_s *args ); +void EV_FireCrossbow( struct event_args_s *args ); +void EV_FireCrossbow2( struct event_args_s *args ); +void EV_FireRpg( struct event_args_s *args ); +void EV_EgonFire( struct event_args_s *args ); +void EV_EgonStop( struct event_args_s *args ); +void EV_HornetGunFire( struct event_args_s *args ); +void EV_TripmineFire( struct event_args_s *args ); +void EV_SnarkFire( struct event_args_s *args ); + + + +void EV_TrainPitchAdjust( 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/glock1.sc", EV_FireGlock1 ); + gEngfuncs.pfnHookEvent( "events/glock2.sc", EV_FireGlock2 ); + gEngfuncs.pfnHookEvent( "events/shotgun1.sc", EV_FireShotGunSingle ); + gEngfuncs.pfnHookEvent( "events/shotgun2.sc", EV_FireShotGunDouble ); + gEngfuncs.pfnHookEvent( "events/mp5.sc", EV_FireMP5 ); + gEngfuncs.pfnHookEvent( "events/mp52.sc", EV_FireMP52 ); + gEngfuncs.pfnHookEvent( "events/python.sc", EV_FirePython ); + gEngfuncs.pfnHookEvent( "events/gauss.sc", EV_FireGauss ); + gEngfuncs.pfnHookEvent( "events/gaussspin.sc", EV_SpinGauss ); + gEngfuncs.pfnHookEvent( "events/train.sc", EV_TrainPitchAdjust ); + gEngfuncs.pfnHookEvent( "events/crowbar.sc", EV_Crowbar ); + gEngfuncs.pfnHookEvent( "events/crossbow1.sc", EV_FireCrossbow ); + gEngfuncs.pfnHookEvent( "events/crossbow2.sc", EV_FireCrossbow2 ); + gEngfuncs.pfnHookEvent( "events/rpg.sc", EV_FireRpg ); + gEngfuncs.pfnHookEvent( "events/egon_fire.sc", EV_EgonFire ); + gEngfuncs.pfnHookEvent( "events/egon_stop.sc", EV_EgonStop ); + gEngfuncs.pfnHookEvent( "events/firehornet.sc", EV_HornetGunFire ); + gEngfuncs.pfnHookEvent( "events/tripfire.sc", EV_TripmineFire ); + gEngfuncs.pfnHookEvent( "events/snarkfire.sc", EV_SnarkFire ); +} diff --git a/hlsdk/cl_dll/hl/hl_objects.cpp b/hlsdk/cl_dll/hl/hl_objects.cpp new file mode 100644 index 00000000..e95dfae7 --- /dev/null +++ b/hlsdk/cl_dll/hl/hl_objects.cpp @@ -0,0 +1,90 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#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" + +extern BEAM *pBeam; +extern BEAM *pBeam2; +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(); + + if ( pBeam ) + { + pBeam->target = tr.endpos; + pBeam->die = gEngfuncs.GetClientTime() + 0.1; // We keep it alive just a little bit forward in the future, just in case. + } + + if ( pBeam2 ) + { + pBeam2->target = tr.endpos; + pBeam2->die = gEngfuncs.GetClientTime() + 0.1; // We keep it alive just a little bit forward in the future, just in case. + } +} + +/* +===================== +Game_AddObjects + +Add game specific, client-side objects here +===================== +*/ +void Game_AddObjects( void ) +{ + if ( pBeam && pBeam2 ) + UpdateBeams(); +} diff --git a/hlsdk/cl_dll/hl/hl_weapons.cpp b/hlsdk/cl_dll/hl/hl_weapons.cpp new file mode 100644 index 00000000..820e49ff --- /dev/null +++ b/hlsdk/cl_dll/hl/hl_weapons.cpp @@ -0,0 +1,1095 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.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 "../hud_iface.h" +#include "../com_weapons.h" +#include "../demo.h" + +extern globalvars_t *gpGlobals; +extern int g_iUser1; + +// 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 CBasePlayerWeapon *g_pWpns[ 32 ]; + +float g_flApplyVel = 0.0; +int g_irunninggausspred = 0; + +vec3_t previousorigin; + +// HLDM Weapon placeholder entities. +CGlock g_Glock; +CCrowbar g_Crowbar; +CPython g_Python; +CMP5 g_Mp5; +CCrossbow g_Crossbow; +CShotgun g_Shotgun; +CRpg g_Rpg; +CGauss g_Gauss; +CEgon g_Egon; +CHgun g_HGun; +CHandGrenade g_HandGren; +CSatchel g_Satchel; +CTripmine g_Tripmine; +CSqueak g_Snark; + + +/* +====================== +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 ] = (CBasePlayerWeapon *)pEntity; + } +} + +/* +===================== +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 ); + + g_irunninggausspred = false; + 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. + g_irunninggausspred = false; + 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 ( ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq, int iDamage, entvars_t *pevAttacker, int shared_rand ) +{ + 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; + + // 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 < 0.0 ) + { + Reload(); + return; + } + + 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( ); + + g_irunninggausspred = false; +} + +/* +===================== +CBasePlayer::Spawn + +===================== +*/ +void CBasePlayer::Spawn( void ) +{ + if (m_pActiveItem) + m_pActiveItem->Deploy( ); + + g_irunninggausspred = false; +} + +/* +===================== +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 ); + + // Allocate slot(s) for each weapon that we are going to be predicting + HUD_PrepEntity( &g_Glock , &player ); + HUD_PrepEntity( &g_Crowbar , &player ); + HUD_PrepEntity( &g_Python , &player ); + HUD_PrepEntity( &g_Mp5 , &player ); + HUD_PrepEntity( &g_Crossbow , &player ); + HUD_PrepEntity( &g_Shotgun , &player ); + HUD_PrepEntity( &g_Rpg , &player ); + HUD_PrepEntity( &g_Gauss , &player ); + HUD_PrepEntity( &g_Egon , &player ); + HUD_PrepEntity( &g_HGun , &player ); + HUD_PrepEntity( &g_HandGren , &player ); + HUD_PrepEntity( &g_Satchel , &player ); + HUD_PrepEntity( &g_Tripmine , &player ); + HUD_PrepEntity( &g_Snark , &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; + CBasePlayerWeapon *pWeapon = NULL; + CBasePlayerWeapon *pCurrent; + weapon_data_t nulldata, *pfrom, *pto; + static int lasthealth; + + memset( &nulldata, 0, sizeof( nulldata ) ); + + HUD_InitClientWeapons(); + + // Get current clock + gpGlobals->time = time; + + // 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 ) + { + case WEAPON_CROWBAR: + pWeapon = &g_Crowbar; + break; + + case WEAPON_GLOCK: + pWeapon = &g_Glock; + break; + + case WEAPON_PYTHON: + pWeapon = &g_Python; + break; + + case WEAPON_MP5: + pWeapon = &g_Mp5; + break; + + case WEAPON_CROSSBOW: + pWeapon = &g_Crossbow; + break; + + case WEAPON_SHOTGUN: + pWeapon = &g_Shotgun; + break; + + case WEAPON_RPG: + pWeapon = &g_Rpg; + break; + + case WEAPON_GAUSS: + pWeapon = &g_Gauss; + break; + + case WEAPON_EGON: + pWeapon = &g_Egon; + break; + + case WEAPON_HORNETGUN: + pWeapon = &g_HGun; + break; + + case WEAPON_HANDGRENADE: + pWeapon = &g_HandGren; + break; + + case WEAPON_SATCHEL: + pWeapon = &g_Satchel; + break; + + case WEAPON_TRIPMINE: + pWeapon = &g_Tripmine; + break; + + case WEAPON_SNARK: + pWeapon = &g_Snark; + break; + } + + // 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_chargeReady = pfrom->iuser1; + pCurrent->m_fInAttack = pfrom->iuser2; + pCurrent->m_fireState = pfrom->iuser3; + + 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; + player.m_flNextAmmoBurn = from->client.fuser2; + player.m_flAmmoStartCharge = from->client.fuser3; + + //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 ]; + } + + if ( player.m_pActiveItem->m_iId == WEAPON_RPG ) + { + ( ( CRpg * )player.m_pActiveItem)->m_fSpotActive = (int)from->client.vuser2[ 1 ]; + ( ( CRpg * )player.m_pActiveItem)->m_cActiveRockets = (int)from->client.vuser2[ 2 ]; + } + + // 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 && !g_iUser1 ) + { + 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.fuser2 = player.m_flNextAmmoBurn; + to->client.fuser3 = player.m_flAmmoStartCharge; + 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; + + if ( player.m_pActiveItem->m_iId == WEAPON_RPG ) + { + from->client.vuser2[ 1 ] = ( ( CRpg * )player.m_pActiveItem)->m_fSpotActive; + from->client.vuser2[ 2 ] = ( ( CRpg * )player.m_pActiveItem)->m_cActiveRockets; + } + + // 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; + + //Pop the model to body 0. + if ( pWeapon == &g_Tripmine ) + body = 0; + + //Show laser sight/scope combo + if ( pWeapon == &g_Python && bIsMultiplayer() ) + body = 1; + + // 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; + } + + 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->iuser1 = pCurrent->m_chargeReady; + pto->iuser2 = pCurrent->m_fInAttack; + pto->iuser3 = pCurrent->m_fireState; + + // 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_flPumpTime != -9999 ) + { + pto->m_flPumpTime -= cmd->msec / 1000.0; + if ( pto->m_flPumpTime < -0.001 ) + pto->m_flPumpTime = -0.001; + }*/ + + 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; + } + + // 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; + } + + if ( g_irunninggausspred == 1 ) + { + Vector forward; + gEngfuncs.pfnAngleVectors( v_angles, forward, NULL, NULL ); + to->client.velocity = to->client.velocity - forward * g_flApplyVel * 5; + g_irunninggausspred = false; + } + + // All games can use FOV state + g_lastFOV = to->client.fov; +} diff --git a/hlsdk/cl_dll/hud.cpp b/hlsdk/cl_dll/hud.cpp new file mode 100644 index 00000000..4b3180df --- /dev/null +++ b/hlsdk/cl_dll/hud.cpp @@ -0,0 +1,679 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud.cpp +// +// implementation of CHud class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" +#include "hud_servers.h" +#include "vgui_int.h" +#include "vgui_TeamFortressViewport.h" + +#include "demo.h" +#include "demo_api.h" +#include "vgui_scorepanel.h" + + + +class CHLVoiceStatusHelper : public IVoiceStatusHelper +{ +public: + virtual void GetPlayerTextColor(int entindex, int color[3]) + { + color[0] = color[1] = color[2] = 255; + + if( entindex >= 0 && entindex < sizeof(g_PlayerExtraInfo)/sizeof(g_PlayerExtraInfo[0]) ) + { + int iTeam = g_PlayerExtraInfo[entindex].teamnumber; + + if ( iTeam < 0 ) + { + iTeam = 0; + } + + iTeam = iTeam % iNumberOfTeamColors; + + color[0] = iTeamColors[iTeam][0]; + color[1] = iTeamColors[iTeam][1]; + color[2] = iTeamColors[iTeam][2]; + } + } + + virtual void UpdateCursorState() + { + gViewPort->UpdateCursorState(); + } + + virtual int GetAckIconHeight() + { + return ScreenHeight - gHUD.m_iFontHeight*3 - 6; + } + + virtual bool CanShowSpeakerLabels() + { + if( gViewPort && gViewPort->m_pScoreBoard ) + return !gViewPort->m_pScoreBoard->isVisible(); + else + return false; + } +}; +static CHLVoiceStatusHelper g_VoiceStatusHelper; + + +extern client_sprite_t *GetSpriteList(client_sprite_t *pList, const char *psz, int iRes, int iCount); + +extern cvar_t *sensitivity; +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) +{ + return gHUD.MsgFunc_ResetHUD(pszName, iSize, pbuf ); +} + +int __MsgFunc_InitHUD(const char *pszName, int iSize, void *pbuf) +{ + gHUD.MsgFunc_InitHUD( pszName, iSize, pbuf ); + return 1; +} + +int __MsgFunc_ViewMode(const char *pszName, int iSize, void *pbuf) +{ + gHUD.MsgFunc_ViewMode( pszName, iSize, pbuf ); + return 1; +} + +int __MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_SetFOV( pszName, iSize, pbuf ); +} + +int __MsgFunc_Concuss(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_Concuss( pszName, iSize, pbuf ); +} + +int __MsgFunc_GameMode(const char *pszName, int iSize, void *pbuf ) +{ + return gHUD.MsgFunc_GameMode( pszName, iSize, pbuf ); +} + +// TFFree Command Menu +void __CmdFunc_OpenCommandMenu(void) +{ + if ( gViewPort ) + { + gViewPort->ShowCommandMenu( gViewPort->m_StandardMenu ); + } +} + +// TFC "special" command +void __CmdFunc_InputPlayerSpecial(void) +{ + if ( gViewPort ) + { + gViewPort->InputPlayerSpecial(); + } +} + +void __CmdFunc_CloseCommandMenu(void) +{ + if ( gViewPort ) + { + gViewPort->InputSignalHideCommandMenu(); + } +} + +void __CmdFunc_ForceCloseCommandMenu( void ) +{ + if ( gViewPort ) + { + gViewPort->HideCommandMenu(); + } +} + +void __CmdFunc_ToggleServerBrowser( void ) +{ + if ( gViewPort ) + { + gViewPort->ToggleServerBrowser(); + } +} + +// TFFree Command Menu Message Handlers +int __MsgFunc_ValClass(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_ValClass( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_TeamNames(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_TeamNames( pszName, iSize, 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) + return gViewPort->MsgFunc_VGUIMenu( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_MOTD(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_MOTD( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_BuildSt(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_BuildSt( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_RandomPC(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_RandomPC( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_ServerName(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_ServerName( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_ScoreInfo(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_ScoreInfo( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_TeamScore(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_TeamScore( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_TeamInfo(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_TeamInfo( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_Spectator(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_Spectator( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_AllowSpec(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_AllowSpec( 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 ); + HOOK_MESSAGE( ViewMode ); + HOOK_MESSAGE( SetFOV ); + HOOK_MESSAGE( Concuss ); + + // TFFree CommandMenu + HOOK_COMMAND( "+commandmenu", OpenCommandMenu ); + HOOK_COMMAND( "-commandmenu", CloseCommandMenu ); + 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( BuildSt ); + HOOK_MESSAGE( RandomPC ); + HOOK_MESSAGE( ServerName ); + HOOK_MESSAGE( ScoreInfo ); + HOOK_MESSAGE( TeamScore ); + HOOK_MESSAGE( TeamInfo ); + + HOOK_MESSAGE( Spectator ); + HOOK_MESSAGE( AllowSpec ); + + // VGUI Menus + HOOK_MESSAGE( VGUIMenu ); + + 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 ); + default_fov = CVAR_CREATE( "default_fov", "90", 0 ); + m_pCvarStealMouse = CVAR_CREATE( "hud_capturemouse", "1", FCVAR_ARCHIVE ); + m_pCvarDraw = CVAR_CREATE( "hud_draw", "1", FCVAR_ARCHIVE ); + cl_lw = gEngfuncs.pfnGetCvarPointer( "cl_lw" ); + + m_pSpriteList = NULL; + + // Clear any old HUD list + if ( m_pHudList ) + { + HUDLIST *pList; + while ( m_pHudList ) + { + pList = m_pHudList; + m_pHudList = m_pHudList->pNext; + free( pList ); + } + m_pHudList = NULL; + } + + // In case we get messages before the first update -- time will be valid + m_flTime = 1.0; + + m_Ammo.Init(); + m_Health.Init(); + m_SayText.Init(); + m_Spectator.Init(); + m_Geiger.Init(); + m_Train.Init(); + m_Battery.Init(); + m_Flash.Init(); + m_Message.Init(); + m_StatusBar.Init(); + m_DeathNotice.Init(); + m_AmmoSecondary.Init(); + m_TextMessage.Init(); + m_StatusIcons.Init(); + GetClientVoiceMgr()->Init(&g_VoiceStatusHelper, (vgui::Panel**)&gViewPort); + + m_Menu.Init(); + + ServersInit(); + + MsgFunc_ResetHUD(0, 0, NULL ); +} + +// CHud destructor +// cleans up memory allocated for m_rg* arrays +CHud :: ~CHud() +{ + delete [] m_rghSprites; + delete [] m_rgrcRects; + delete [] m_rgszSpriteNames; + + if ( m_pHudList ) + { + HUDLIST *pList; + while ( m_pHudList ) + { + pList = m_pHudList; + m_pHudList = m_pHudList->pNext; + free( pList ); + } + m_pHudList = NULL; + } + + ServersShutdown(); +} + +// GetSpriteIndex() +// searches through the sprite list loaded from hud.txt for a name matching SpriteName +// returns an index into the gHUD.m_rghSprites[] array +// returns 0 if sprite not found +int CHud :: GetSpriteIndex( const char *SpriteName ) +{ + // look through the loaded sprite name list for SpriteName + for ( int i = 0; i < m_iSpriteCount; i++ ) + { + if ( strncmp( SpriteName, m_rgszSpriteNames + (i * MAX_SPRITE_NAME_LENGTH), MAX_SPRITE_NAME_LENGTH ) == 0 ) + return i; + } + + return -1; // invalid sprite +} + +void CHud :: VidInit( void ) +{ + m_scrinfo.iSize = sizeof(m_scrinfo); + GetScreenInfo(&m_scrinfo); + + // ---------- + // Load Sprites + // --------- +// m_hsprFont = LoadSprite("sprites/%d_font.spr"); + + m_hsprLogo = 0; + m_hsprCursor = 0; + + if (ScreenWidth < 640) + m_iRes = 320; + else + m_iRes = 640; + + // Only load this once + if ( !m_pSpriteList ) + { + // we need to load the hud.txt, and all sprites within + m_pSpriteList = SPR_GetList("sprites/hud.txt", &m_iSpriteCountAllRes); + + if (m_pSpriteList) + { + // count the number of sprites of the appropriate res + m_iSpriteCount = 0; + client_sprite_t *p = m_pSpriteList; + for ( int j = 0; j < m_iSpriteCountAllRes; j++ ) + { + if ( p->iRes == m_iRes ) + m_iSpriteCount++; + p++; + } + + // allocated memory for sprite handle arrays + m_rghSprites = new HSPRITE[m_iSpriteCount]; + m_rgrcRects = new wrect_t[m_iSpriteCount]; + m_rgszSpriteNames = new char[m_iSpriteCount * MAX_SPRITE_NAME_LENGTH]; + + p = m_pSpriteList; + int index = 0; + for ( int j = 0; j < m_iSpriteCountAllRes; j++ ) + { + if ( p->iRes == m_iRes ) + { + char sz[256]; + sprintf(sz, "sprites/%s.spr", p->szSprite); + m_rghSprites[index] = SPR_Load(sz); + m_rgrcRects[index] = p->rc; + strncpy( &m_rgszSpriteNames[index * MAX_SPRITE_NAME_LENGTH], p->szName, MAX_SPRITE_NAME_LENGTH ); + + index++; + } + + p++; + } + } + } + else + { + // we have already have loaded the sprite reference from hud.txt, but + // we need to make sure all the sprites have been loaded (we've gone through a transition, or loaded a save game) + client_sprite_t *p = m_pSpriteList; + int index = 0; + for ( int j = 0; j < m_iSpriteCountAllRes; j++ ) + { + if ( p->iRes == m_iRes ) + { + char sz[256]; + sprintf( sz, "sprites/%s.spr", p->szSprite ); + m_rghSprites[index] = SPR_Load(sz); + index++; + } + + p++; + } + } + + // assumption: number_1, number_2, etc, are all listed and loaded sequentially + m_HUD_number_0 = GetSpriteIndex( "number_0" ); + + m_iFontHeight = m_rgrcRects[m_HUD_number_0].bottom - m_rgrcRects[m_HUD_number_0].top; + + m_Ammo.VidInit(); + m_Health.VidInit(); + m_Spectator.VidInit(); + m_Geiger.VidInit(); + m_Train.VidInit(); + m_Battery.VidInit(); + m_Flash.VidInit(); + m_Message.VidInit(); + m_StatusBar.VidInit(); + m_DeathNotice.VidInit(); + m_SayText.VidInit(); + m_Menu.VidInit(); + m_AmmoSecondary.VidInit(); + m_TextMessage.VidInit(); + m_StatusIcons.VidInit(); + GetClientVoiceMgr()->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; +} + +float g_lastFOV = 0.0; + +/* +============ +COM_FileBase +============ +*/ +// Extracts the base name of a file (no path, no extension, assumes '/' as path separator) +void COM_FileBase ( const char *in, char *out) +{ + int len, start, end; + + len = strlen( in ); + + // scan backward for '.' + end = len - 1; + while ( end && in[end] != '.' && in[end] != '/' && in[end] != '\\' ) + end--; + + if ( in[end] != '.' ) // no '.', copy to end + end = len-1; + else + end--; // Found ',', copy to left of '.' + + + // Scan backward for '/' + start = len-1; + while ( start >= 0 && in[start] != '/' && in[start] != '\\' ) + start--; + + if ( in[start] != '/' && in[start] != '\\' ) + start = 0; + else + start++; + + // Length of new sting + len = end - start + 1; + + // Copy partial string + strncpy( out, &in[start], len ); + // Terminate it + out[len] = 0; +} + +/* +================= +HUD_IsGame + +================= +*/ +int HUD_IsGame( const char *game ) +{ + const char *gamedir; + char gd[ 1024 ]; + + gamedir = gEngfuncs.pfnGetGameDirectory(); + if ( gamedir && gamedir[0] ) + { + COM_FileBase( gamedir, gd ); + if ( !stricmp( gd, game ) ) + return 1; + } + return 0; +} + +/* +===================== +HUD_GetFOV + +Returns last FOV +===================== +*/ +float HUD_GetFOV( void ) +{ + if ( gEngfuncs.pDemoAPI->IsRecording() ) + { + // Write it + int i = 0; + unsigned char buf[ 100 ]; + + // Active + *( float * )&buf[ i ] = g_lastFOV; + i += sizeof( float ); + + Demo_WriteBuffer( TYPE_ZOOM, i, buf ); + } + + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + { + g_lastFOV = g_demozoom; + } + return g_lastFOV; +} + +int CHud::MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + int newfov = READ_BYTE(); + int def_fov = CVAR_GET_FLOAT( "default_fov" ); + + //Weapon prediction already takes care of changing the fog. ( g_lastFOV ). + if ( cl_lw && cl_lw->value ) + return 1; + + g_lastFOV = newfov; + + if ( newfov == 0 ) + { + m_iFOV = def_fov; + } + else + { + m_iFOV = newfov; + } + + // the clients fov is actually set in the client data update section of the hud + + // Set a new sensitivity + if ( m_iFOV == def_fov ) + { + // reset to saved sensitivity + m_flMouseSensitivity = 0; + } + else + { + // set a new sensitivity that is proportional to the change from the FOV default + m_flMouseSensitivity = sensitivity->value * ((float)newfov / (float)def_fov) * CVAR_GET_FLOAT("zoom_sensitivity_ratio"); + } + + return 1; +} + + +void CHud::AddHudElem(CHudBase *phudelem) +{ + HUDLIST *pdl, *ptemp; + +//phudelem->Think(); + + if (!phudelem) + return; + + pdl = (HUDLIST *)malloc(sizeof(HUDLIST)); + if (!pdl) + return; + + memset(pdl, 0, sizeof(HUDLIST)); + pdl->p = phudelem; + + if (!m_pHudList) + { + m_pHudList = pdl; + return; + } + + ptemp = m_pHudList; + + while (ptemp->pNext) + ptemp = ptemp->pNext; + + ptemp->pNext = pdl; +} + +float CHud::GetSensitivity( void ) +{ + return m_flMouseSensitivity; +} + + diff --git a/hlsdk/cl_dll/hud.h b/hlsdk/cl_dll/hud.h new file mode 100644 index 00000000..7d49e537 --- /dev/null +++ b/hlsdk/cl_dll/hud.h @@ -0,0 +1,667 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud.h +// +// class CHud declaration +// +// CHud handles the message, calculation, and drawing the HUD +// + + +#define RGB_YELLOWISH 0x00FFA000 //255,160,0 +#define RGB_REDISH 0x00FF1010 //255,160,0 +#define RGB_GREENISH 0x0000A000 //0,160,0 + +#include "wrect.h" +#include "cl_dll.h" +#include "ammo.h" + +#define DHN_DRAWZERO 1 +#define DHN_2DIGITS 2 +#define DHN_3DIGITS 4 +#define MIN_ALPHA 100 + +#define HUDELEM_ACTIVE 1 + +typedef struct { + int x, y; +} POSITION; + +enum +{ + MAX_PLAYERS = 64, + MAX_TEAMS = 64, + MAX_TEAM_NAME = 16, +}; + +typedef struct { + unsigned char r,g,b,a; +} RGBA; + +typedef struct cvar_s cvar_t; + + +#define HUD_ACTIVE 1 +#define HUD_INTERMISSION 2 + +#define MAX_PLAYER_NAME_LENGTH 32 + +#define MAX_MOTD_LENGTH 1536 + +// +//----------------------------------------------------- +// +class CHudBase +{ +public: + POSITION m_pos; + int m_type; + int m_iFlags; // active, moving, + virtual ~CHudBase() {} + virtual int Init( void ) {return 0;} + virtual int VidInit( void ) {return 0;} + virtual int Draw(float flTime) {return 0;} + virtual void Think(void) {return;} + virtual void Reset(void) {return;} + virtual void InitHUDData( void ) {} // called every time a server is connected to + +}; + +struct HUDLIST { + CHudBase *p; + HUDLIST *pNext; +}; + + + +// +//----------------------------------------------------- +// +#include "..\game_shared\voice_status.h" +#include "hud_spectator.h" + + +// +//----------------------------------------------------- +// +class CHudAmmo: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + void Think(void); + void Reset(void); + int DrawWList(float flTime); + int MsgFunc_CurWeapon(const char *pszName, int iSize, void *pbuf); + int MsgFunc_WeaponList(const char *pszName, int iSize, void *pbuf); + int MsgFunc_AmmoX(const char *pszName, int iSize, void *pbuf); + int MsgFunc_AmmoPickup( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_WeapPickup( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_ItemPickup( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_HideWeapon( const char *pszName, int iSize, void *pbuf ); + + void SlotInput( int iSlot ); + void _cdecl UserCmd_Slot1( void ); + void _cdecl UserCmd_Slot2( void ); + void _cdecl UserCmd_Slot3( void ); + void _cdecl UserCmd_Slot4( void ); + void _cdecl UserCmd_Slot5( void ); + void _cdecl UserCmd_Slot6( void ); + void _cdecl UserCmd_Slot7( void ); + void _cdecl UserCmd_Slot8( void ); + void _cdecl UserCmd_Slot9( void ); + void _cdecl UserCmd_Slot10( void ); + void _cdecl UserCmd_Close( void ); + void _cdecl UserCmd_NextWeapon( void ); + void _cdecl UserCmd_PrevWeapon( void ); + +private: + float m_fFade; + RGBA m_rgba; + WEAPON *m_pWeapon; + int m_HUD_bucket0; + int m_HUD_selection; + +}; + +// +//----------------------------------------------------- +// + +class CHudAmmoSecondary: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + void Reset( void ); + int Draw(float flTime); + + int MsgFunc_SecAmmoVal( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_SecAmmoIcon( const char *pszName, int iSize, void *pbuf ); + +private: + enum { + MAX_SEC_AMMO_VALUES = 4 + }; + + int m_HUD_ammoicon; // sprite indices + int m_iAmmoAmounts[MAX_SEC_AMMO_VALUES]; + float m_fFade; +}; + + +#include "health.h" + + +#define FADE_TIME 100 + + +// +//----------------------------------------------------- +// +class CHudGeiger: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_Geiger(const char *pszName, int iSize, void *pbuf); + +private: + int m_iGeigerRange; + +}; + +// +//----------------------------------------------------- +// +class CHudTrain: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_Train(const char *pszName, int iSize, void *pbuf); + +private: + HSPRITE m_hSprite; + int m_iPos; + +}; + +// +//----------------------------------------------------- +// +// REMOVED: Vgui has replaced this. +// +/* +class CHudMOTD : public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw( float flTime ); + void Reset( void ); + + int MsgFunc_MOTD( const char *pszName, int iSize, void *pbuf ); + +protected: + static int MOTD_DISPLAY_TIME; + char m_szMOTD[ MAX_MOTD_LENGTH ]; + float m_flActiveRemaining; + int m_iLines; +}; +*/ + +// +//----------------------------------------------------- +// +class CHudStatusBar : public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw( float flTime ); + void Reset( void ); + void ParseStatusString( int line_num ); + + int MsgFunc_StatusText( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_StatusValue( const char *pszName, int iSize, void *pbuf ); + +protected: + enum { + MAX_STATUSTEXT_LENGTH = 128, + MAX_STATUSBAR_VALUES = 8, + MAX_STATUSBAR_LINES = 2, + }; + + char m_szStatusText[MAX_STATUSBAR_LINES][MAX_STATUSTEXT_LENGTH]; // a text string describing how the status bar is to be drawn + char m_szStatusBar[MAX_STATUSBAR_LINES][MAX_STATUSTEXT_LENGTH]; // the constructed bar that is drawn + int m_iStatusValues[MAX_STATUSBAR_VALUES]; // an array of values for use in the status bar + + int m_bReparseString; // set to TRUE whenever the m_szStatusBar needs to be recalculated + + // an array of colors...one color for each line + float *m_pflNameColors[MAX_STATUSBAR_LINES]; +}; + +// +//----------------------------------------------------- +// +// REMOVED: Vgui has replaced this. +// +/* +class CHudScoreboard: public CHudBase +{ +public: + int Init( void ); + void InitHUDData( void ); + int VidInit( void ); + int Draw( float flTime ); + int DrawPlayers( int xoffset, float listslot, int nameoffset = 0, char *team = NULL ); // returns the ypos where it finishes drawing + void UserCmd_ShowScores( void ); + void UserCmd_HideScores( void ); + int MsgFunc_ScoreInfo( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_TeamInfo( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_TeamScore( const char *pszName, int iSize, void *pbuf ); + void DeathMsg( int killer, int victim ); + + int m_iNumTeams; + + int m_iLastKilledBy; + int m_fLastKillTime; + int m_iPlayerNum; + int m_iShowscoresHeld; + + void GetAllPlayersInfo( void ); +private: + struct cvar_s *cl_showpacketloss; + +}; +*/ + +struct extra_player_info_t +{ + short frags; + short deaths; + short playerclass; + short teamnumber; + char teamname[MAX_TEAM_NAME]; +}; + +struct team_info_t +{ + char name[MAX_TEAM_NAME]; + short frags; + short deaths; + short ping; + short packetloss; + short ownteam; + short players; + int already_drawn; + int scores_overriden; + int teamnumber; +}; + +extern hud_player_info_t g_PlayerInfoList[MAX_PLAYERS+1]; // player info from the engine +extern extra_player_info_t g_PlayerExtraInfo[MAX_PLAYERS+1]; // additional player info sent directly to the client dll +extern team_info_t g_TeamInfo[MAX_TEAMS+1]; +extern int g_IsSpectator[MAX_PLAYERS+1]; + + +// +//----------------------------------------------------- +// +class CHudDeathNotice : public CHudBase +{ +public: + int Init( void ); + void InitHUDData( void ); + int VidInit( void ); + int Draw( float flTime ); + int MsgFunc_DeathMsg( const char *pszName, int iSize, void *pbuf ); + +private: + int m_HUD_d_skull; // sprite index of skull icon +}; + +// +//----------------------------------------------------- +// +class CHudMenu : public CHudBase +{ +public: + int Init( void ); + void InitHUDData( void ); + int VidInit( void ); + void Reset( void ); + int Draw( float flTime ); + int MsgFunc_ShowMenu( const char *pszName, int iSize, void *pbuf ); + + void SelectMenuItem( int menu_item ); + + int m_fMenuDisplayed; + int m_bitsValidSlots; + float m_flShutoffTime; + int m_fWaitingForMore; +}; + +// +//----------------------------------------------------- +// +class CHudSayText : public CHudBase +{ +public: + int Init( void ); + void InitHUDData( void ); + int VidInit( void ); + int Draw( float flTime ); + int MsgFunc_SayText( const char *pszName, int iSize, void *pbuf ); + void SayTextPrint( const char *pszBuf, int iBufSize, int clientIndex = -1 ); + void EnsureTextFitsInOneLineAndWrapIfHaveTo( int line ); +friend class CHudSpectator; + +private: + + struct cvar_s * m_HUD_saytext; + struct cvar_s * m_HUD_saytext_time; +}; + +// +//----------------------------------------------------- +// +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 +}; + + +// +//----------------------------------------------------- +// +class CHudFlashlight: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + void Reset( void ); + int MsgFunc_Flashlight(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_FlashBat(const char *pszName, int iSize, void *pbuf ); + +private: + HSPRITE m_hSprite1; + HSPRITE m_hSprite2; + HSPRITE m_hBeam; + wrect_t *m_prc1; + wrect_t *m_prc2; + wrect_t *m_prcBeam; + float m_flBat; + int m_iBat; + int m_fOn; + float m_fFade; + int m_iWidth; // width of the battery innards +}; + +// +//----------------------------------------------------- +// +const int maxHUDMessages = 16; +struct message_parms_t +{ + client_textmessage_t *pMessage; + float time; + int x, y; + int totalWidth, totalHeight; + int width; + int lines; + int lineLength; + int length; + int r, g, b; + int text; + int fadeBlend; + float charTime; + float fadeTime; +}; + +// +//----------------------------------------------------- +// + +class CHudTextMessage: public CHudBase +{ +public: + int Init( void ); + static char *LocaliseTextString( const char *msg, char *dst_buffer, int buffer_size ); + static char *BufferedLocaliseTextString( const char *msg ); + char *LookupString( const char *msg_name, int *msg_dest = NULL ); + int MsgFunc_TextMsg(const char *pszName, int iSize, void *pbuf); +}; + +// +//----------------------------------------------------- +// + +class CHudMessage: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_HudText(const char *pszName, int iSize, void *pbuf); + int MsgFunc_GameTitle(const char *pszName, int iSize, void *pbuf); + + float FadeBlend( float fadein, float fadeout, float hold, float localTime ); + int XPosition( float x, int width, int lineWidth ); + int YPosition( float y, int height ); + + void MessageAdd( const char *pName, float time ); + void MessageAdd(client_textmessage_t * newMessage ); + void MessageDrawScan( client_textmessage_t *pMessage, float time ); + void MessageScanStart( void ); + void MessageScanNextChar( void ); + void Reset( void ); + +private: + client_textmessage_t *m_pMessages[maxHUDMessages]; + float m_startTime[maxHUDMessages]; + message_parms_t m_parms; + float m_gameTitleTime; + client_textmessage_t *m_pGameTitle; + + int m_HUD_title_life; + int m_HUD_title_half; +}; + +// +//----------------------------------------------------- +// +#define MAX_SPRITE_NAME_LENGTH 24 + +class CHudStatusIcons: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + void Reset( void ); + int Draw(float flTime); + int MsgFunc_StatusIcon(const char *pszName, int iSize, void *pbuf); + + enum { + MAX_ICONSPRITENAME_LENGTH = MAX_SPRITE_NAME_LENGTH, + MAX_ICONSPRITES = 4, + }; + + + //had to make these public so CHud could access them (to enable concussion icon) + //could use a friend declaration instead... + void EnableIcon( char *pszIconName, unsigned char red, unsigned char green, unsigned char blue ); + void DisableIcon( char *pszIconName ); + +private: + + typedef struct + { + char szSpriteName[MAX_ICONSPRITENAME_LENGTH]; + HSPRITE spr; + wrect_t rc; + unsigned char r, g, b; + } icon_sprite_t; + + icon_sprite_t m_IconList[MAX_ICONSPRITES]; + +}; + +// +//----------------------------------------------------- +// + + + +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; + float m_flTime; // the current client time + float m_fOldTime; // the time at which the HUD was last redrawn + double m_flTimeDelta; // the difference between flTime and fOldTime + Vector m_vecOrigin; + Vector m_vecAngles; + int m_iKeyBits; + int m_iHideHUDDisplay; + int m_iFOV; + int m_Teamplay; + int m_iRes; + cvar_t *m_pCvarStealMouse; + cvar_t *m_pCvarDraw; + + int m_iFontHeight; + int DrawHudNumber(int x, int y, int iFlags, int iNumber, int r, int g, int b ); + int DrawHudString(int x, int y, int iMaxX, char *szString, int r, int g, int b ); + int DrawHudStringReverse( int xpos, int ypos, int iMinX, char *szString, int r, int g, int b ); + int DrawHudNumberString( int xpos, int ypos, int iMinX, int iNumber, int r, int g, int b ); + int GetNumWidth(int iNumber, int iFlags); + +private: + // the memory for these arrays are allocated in the first call to CHud::VidInit(), when the hud.txt and associated sprites are loaded. + // freed in ~CHud() + HSPRITE *m_rghSprites; /*[HUD_SPRITE_COUNT]*/ // the sprites loaded from hud.txt + wrect_t *m_rgrcRects; /*[HUD_SPRITE_COUNT]*/ + char *m_rgszSpriteNames; /*[HUD_SPRITE_COUNT][MAX_SPRITE_NAME_LENGTH]*/ + + struct cvar_s *default_fov; +public: + HSPRITE GetSprite( int index ) + { + return (index < 0) ? 0 : m_rghSprites[index]; + } + + wrect_t& GetSpriteRect( int index ) + { + return m_rgrcRects[index]; + } + + + int GetSpriteIndex( const char *SpriteName ); // gets a sprite index, for use in the m_rghSprites[] array + + CHudAmmo m_Ammo; + CHudHealth m_Health; + CHudSpectator m_Spectator; + 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; + CHudAmmoSecondary m_AmmoSecondary; + CHudTextMessage m_TextMessage; + CHudStatusIcons m_StatusIcons; + + void Init( void ); + void VidInit( void ); + void Think(void); + int Redraw( float flTime, int intermission ); + int UpdateClientData( client_data_t *cdata, float time ); + + CHud() : m_iSpriteCount(0), m_pHudList(NULL) {} + ~CHud(); // destructor, frees allocated memory + + // 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 ); + void _cdecl MsgFunc_ViewMode( const char *pszName, int iSize, void *pbuf ); + int _cdecl MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf); + int _cdecl MsgFunc_Concuss( const char *pszName, int iSize, void *pbuf ); + + // Screen information + SCREENINFO m_scrinfo; + + int m_iWeaponBits; + int m_fPlayerDead; + int m_iIntermission; + + // sprite indexes + int m_HUD_number_0; + + + void AddHudElem(CHudBase *p); + + float GetSensitivity(); + +}; + +class TeamFortressViewport; + +extern CHud gHUD; +extern TeamFortressViewport *gViewPort; + +extern int g_iPlayerClass; +extern int g_iTeamNumber; +extern int g_iUser1; +extern int g_iUser2; +extern int g_iUser3; + diff --git a/hlsdk/cl_dll/hud_iface.h b/hlsdk/cl_dll/hud_iface.h new file mode 100644 index 00000000..d08763c0 --- /dev/null +++ b/hlsdk/cl_dll/hud_iface.h @@ -0,0 +1,20 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( HUD_IFACEH ) +#define HUD_IFACEH +#pragma once + +#define EXPORT _declspec( dllexport ) +#define _DLLEXPORT __declspec( dllexport ) + +typedef int (*pfnUserMsgHook)(const char *pszName, int iSize, void *pbuf); +#include "wrect.h" +#include "../engine/cdll_int.h" +extern cl_enginefunc_t gEngfuncs; + +#endif \ No newline at end of file diff --git a/hlsdk/cl_dll/hud_msg.cpp b/hlsdk/cl_dll/hud_msg.cpp new file mode 100644 index 00000000..0505d172 --- /dev/null +++ b/hlsdk/cl_dll/hud_msg.cpp @@ -0,0 +1,120 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud_msg.cpp +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" +#include "r_efx.h" + +#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 ) +{ + ASSERT( iSize == 0 ); + + // clear all hud data + HUDLIST *pList = m_pHudList; + + while ( pList ) + { + if ( pList->p ) + pList->p->Reset(); + pList = pList->pNext; + } + + // reset sensitivity + m_flMouseSensitivity = 0; + + // reset concussion effect + m_iConcussionEffect = 0; + + return 1; +} + +void CAM_ToFirstPerson(void); + +void CHud :: MsgFunc_ViewMode( const char *pszName, int iSize, void *pbuf ) +{ + CAM_ToFirstPerson(); +} + +void CHud :: MsgFunc_InitHUD( const char *pszName, int iSize, void *pbuf ) +{ + // prepare all hud data + HUDLIST *pList = m_pHudList; + + while (pList) + { + if ( pList->p ) + 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(); + + return 1; +} + + +int CHud :: MsgFunc_Damage(const char *pszName, int iSize, void *pbuf ) +{ + int armor, blood; + Vector from; + int i; + float count; + + BEGIN_READ( pbuf, iSize ); + armor = READ_BYTE(); + blood = READ_BYTE(); + + for (i=0 ; i<3 ; i++) + from[i] = READ_COORD(); + + count = (blood * 0.5) + (armor * 0.5); + + if (count < 10) + count = 10; + + // TODO: kick viewangles, show damage visually + + return 1; +} + +int CHud :: MsgFunc_Concuss( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + m_iConcussionEffect = READ_BYTE(); + if (m_iConcussionEffect) + this->m_StatusIcons.EnableIcon("dmg_concuss",255,160,0); + else + this->m_StatusIcons.DisableIcon("dmg_concuss"); + return 1; +} diff --git a/hlsdk/cl_dll/hud_redraw.cpp b/hlsdk/cl_dll/hud_redraw.cpp new file mode 100644 index 00000000..a08712fc --- /dev/null +++ b/hlsdk/cl_dll/hud_redraw.cpp @@ -0,0 +1,350 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud_redraw.cpp +// +#include +#include "hud.h" +#include "cl_util.h" + +#include "vgui_TeamFortressViewport.h" + +#define MAX_LOGO_FRAMES 56 + +int grgLogoFrame[MAX_LOGO_FRAMES] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 13, 13, 13, 13, 13, 12, 11, 10, 9, 8, 14, 15, + 16, 17, 18, 19, 20, 20, 20, 20, 20, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 29, 29, 29, 29, 29, 28, 27, 26, 25, 24, 30, 31 +}; + + +extern int g_iVisibleMouse; + +float HUD_GetFOV( void ); + +extern cvar_t *sensitivity; + +// Think +void CHud::Think(void) +{ + int newfov; + HUDLIST *pList = m_pHudList; + + while (pList) + { + if (pList->p->m_iFlags & HUD_ACTIVE) + pList->p->Think(); + pList = pList->pNext; + } + + newfov = HUD_GetFOV(); + if ( newfov == 0 ) + { + m_iFOV = default_fov->value; + } + else + { + m_iFOV = newfov; + } + + // the clients fov is actually set in the client data update section of the hud + + // Set a new sensitivity + if ( m_iFOV == default_fov->value ) + { + // reset to saved sensitivity + m_flMouseSensitivity = 0; + } + else + { + // set a new sensitivity that is proportional to the change from the FOV default + m_flMouseSensitivity = sensitivity->value * ((float)newfov / (float)default_fov->value) * CVAR_GET_FLOAT("zoom_sensitivity_ratio"); + } + + // think about default fov + if ( m_iFOV == 0 ) + { // only let players adjust up in fov, and only if they are not overriden by something else + m_iFOV = max( default_fov->value, 90 ); + } +} + +// Redraw +// step through the local data, placing the appropriate graphics & text as appropriate +// returns 1 if they've changed, 0 otherwise +int CHud :: Redraw( float flTime, int intermission ) +{ + m_fOldTime = m_flTime; // save time of previous redraw + m_flTime = flTime; + m_flTimeDelta = (double)m_flTime - m_fOldTime; + static int m_flShotTime = 0; + + // Clock was reset, reset delta + if ( m_flTimeDelta < 0 ) + m_flTimeDelta = 0; + + // Bring up the scoreboard during intermission + if (gViewPort) + { + if ( m_iIntermission && !intermission ) + { + // Have to do this here so the scoreboard goes away + m_iIntermission = intermission; + gViewPort->HideCommandMenu(); + gViewPort->HideScoreBoard(); + gViewPort->UpdateSpectatorPanel(); + } + else if ( !m_iIntermission && intermission ) + { + m_iIntermission = intermission; + gViewPort->HideCommandMenu(); + gViewPort->HideVGUIMenu(); + gViewPort->ShowScoreBoard(); + gViewPort->UpdateSpectatorPanel(); + + // Take a screenshot if the client's got the cvar set + if ( CVAR_GET_FLOAT( "hud_takesshots" ) != 0 ) + m_flShotTime = flTime + 1.0; // Take a screenshot in a second + } + } + + if (m_flShotTime && m_flShotTime < flTime) + { + gEngfuncs.pfnClientCmd("snapshot\n"); + m_flShotTime = 0; + } + + m_iIntermission = intermission; + + // if no redrawing is necessary + // return 0; + + if ( m_pCvarDraw->value ) + { + HUDLIST *pList = m_pHudList; + + while (pList) + { + if ( !intermission ) + { + if ( (pList->p->m_iFlags & HUD_ACTIVE) && !(m_iHideHUDDisplay & HIDEHUD_ALL) ) + pList->p->Draw(flTime); + } + else + { // it's an intermission, so only draw hud elements that are set to draw during intermissions + if ( pList->p->m_iFlags & HUD_INTERMISSION ) + pList->p->Draw( flTime ); + } + + 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); + } + + /* + if ( g_iVisibleMouse ) + { + void IN_GetMousePos( int *mx, int *my ); + int mx, my; + + IN_GetMousePos( &mx, &my ); + + if (m_hsprCursor == 0) + { + char sz[256]; + sprintf( sz, "sprites/cursor.spr" ); + m_hsprCursor = SPR_Load( sz ); + } + + SPR_Set(m_hsprCursor, 250, 250, 250 ); + + // Draw the logo at 20 fps + SPR_DrawAdditive( 0, mx, my, NULL ); + } + */ + + return 1; +} + +void ScaleColors( int &r, int &g, int &b, int a ) +{ + float x = (float)a / 255; + r = (int)(r * x); + g = (int)(g * x); + b = (int)(b * x); +} + +int CHud :: DrawHudString(int xpos, int ypos, int iMaxX, char *szIt, int r, int g, int b ) +{ + // draw the string until we hit the null character or a newline character + for ( ; *szIt != 0 && *szIt != '\n'; szIt++ ) + { + int next = xpos + gHUD.m_scrinfo.charWidths[ *szIt ]; // variable-width fonts look cool + if ( next > iMaxX ) + return xpos; + + TextMessageDrawChar( xpos, ypos, *szIt, r, g, b ); + xpos = next; + } + + return xpos; +} + +int CHud :: DrawHudNumberString( int xpos, int ypos, int iMinX, int iNumber, int r, int g, int b ) +{ + char szString[32]; + sprintf( szString, "%d", iNumber ); + return DrawHudStringReverse( xpos, ypos, iMinX, szString, r, g, b ); + +} + +// draws a string from right to left (right-aligned) +int CHud :: DrawHudStringReverse( int xpos, int ypos, int iMinX, char *szString, int r, int g, int b ) +{ + // find the end of the string + char *szIt = NULL; + for ( szIt = szString; *szIt != 0; szIt++ ) + { // we should count the length? + } + + // iterate throug the string in reverse + for ( szIt--; szIt != (szString-1); szIt-- ) + { + int next = xpos - gHUD.m_scrinfo.charWidths[ *szIt ]; // variable-width fonts look cool + if ( next < iMinX ) + return xpos; + xpos = next; + + TextMessageDrawChar( xpos, ypos, *szIt, r, g, b ); + } + + return xpos; +} + +int CHud :: DrawHudNumber( int x, int y, int iFlags, int iNumber, int r, int g, int b) +{ + int iWidth = GetSpriteRect(m_HUD_number_0).right - GetSpriteRect(m_HUD_number_0).left; + int k; + + if (iNumber > 0) + { + // SPR_Draw 100's + if (iNumber >= 100) + { + k = iNumber/100; + SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); + SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); + x += iWidth; + } + else if (iFlags & (DHN_3DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + // SPR_Draw 10's + if (iNumber >= 10) + { + k = (iNumber % 100)/10; + SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); + SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); + x += iWidth; + } + else if (iFlags & (DHN_3DIGITS | DHN_2DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + // SPR_Draw ones + k = iNumber % 10; + SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); + SPR_DrawAdditive(0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); + x += iWidth; + } + else if (iFlags & DHN_DRAWZERO) + { + SPR_Set(GetSprite(m_HUD_number_0), r, g, b ); + + // SPR_Draw 100's + if (iFlags & (DHN_3DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + if (iFlags & (DHN_3DIGITS | DHN_2DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + // SPR_Draw ones + + SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0)); + x += iWidth; + } + + return x; +} + + +int CHud::GetNumWidth( int iNumber, int iFlags ) +{ + if (iFlags & (DHN_3DIGITS)) + return 3; + + if (iFlags & (DHN_2DIGITS)) + return 2; + + if (iNumber <= 0) + { + if (iFlags & (DHN_DRAWZERO)) + return 1; + else + return 0; + } + + if (iNumber < 10) + return 1; + + if (iNumber < 100) + return 2; + + return 3; + +} + + diff --git a/hlsdk/cl_dll/hud_servers.cpp b/hlsdk/cl_dll/hud_servers.cpp new file mode 100644 index 00000000..8bd42d33 --- /dev/null +++ b/hlsdk/cl_dll/hud_servers.cpp @@ -0,0 +1,1230 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// hud_servers.cpp +#include "hud.h" +#include "cl_util.h" +#include "hud_servers_priv.h" +#include "hud_servers.h" +#include "net_api.h" +#include +#include + +static int context_id; + +// Default master server address in case we can't read any from woncomm.lst file +#define VALVE_MASTER_ADDRESS "half-life.east.won.net" +#define PORT_MASTER 27010 +#define PORT_SERVER 27015 + +// File where we really should look for master servers +#define MASTER_PARSE_FILE "woncomm.lst" + +#define MAX_QUERIES 20 + +#define NET_API gEngfuncs.pNetAPI + +static CHudServers *g_pServers = NULL; + +/* +=================== +ListResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK ListResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->ListResponse( response ); + } +} + +/* +=================== +ServerResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK ServerResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->ServerResponse( response ); + } +} + +/* +=================== +PingResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK PingResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->PingResponse( response ); + } +} + +/* +=================== +RulesResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK RulesResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->RulesResponse( response ); + } +} +/* +=================== +PlayersResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK PlayersResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->PlayersResponse( response ); + } +} +/* +=================== +ListResponse + +=================== +*/ +void CHudServers::ListResponse( struct net_response_s *response ) +{ + request_t *list; + request_t *p; + int c = 0; + + if ( !( response->error == NET_SUCCESS ) ) + return; + + if ( response->type != NETAPI_REQUEST_SERVERLIST ) + return; + + if ( response->response ) + { + list = ( request_t * ) response->response; + while ( list ) + { + c++; + + //if ( c < 40 ) + { + // Copy from parsed stuff + p = new request_t; + p->context = -1; + p->remote_address = list->remote_address; + p->next = m_pServerList; + m_pServerList = p; + } + + // Move on + list = list->next; + } + } + + gEngfuncs.Con_Printf( "got list\n" ); + + m_nQuerying = 1; + m_nActiveQueries = 0; +} + +/* +=================== +ServerResponse + +=================== +*/ +void CHudServers::ServerResponse( struct net_response_s *response ) +{ + char *szresponse; + request_t *p; + server_t *browser; + int len; + char sz[ 32 ]; + + // Remove from active list + p = FindRequest( response->context, m_pActiveList ); + if ( p ) + { + RemoveServerFromList( &m_pActiveList, p ); + m_nActiveQueries--; + } + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_DETAILS: + if ( response->response ) + { + szresponse = (char *)response->response; + len = strlen( szresponse ) + 100 + 1; + sprintf( sz, "%i", (int)( 1000.0 * response->ping ) ); + + browser = new server_t; + browser->remote_address = response->remote_address; + browser->info = new char[ len ]; + browser->ping = (int)( 1000.0 * response->ping ); + strcpy( browser->info, szresponse ); + + NET_API->SetValueForKey( browser->info, "address", gEngfuncs.pNetAPI->AdrToString( &response->remote_address ), len ); + NET_API->SetValueForKey( browser->info, "ping", sz, len ); + + AddServer( &m_pServers, browser ); + } + break; + default: + break; + } +} + +/* +=================== +PingResponse + +=================== +*/ +void CHudServers::PingResponse( struct net_response_s *response ) +{ + char sz[ 32 ]; + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_PING: + sprintf( sz, "%.2f", 1000.0 * response->ping ); + + gEngfuncs.Con_Printf( "ping == %s\n", sz ); + break; + default: + break; + } +} + +/* +=================== +RulesResponse + +=================== +*/ +void CHudServers::RulesResponse( struct net_response_s *response ) +{ + char *szresponse; + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_RULES: + if ( response->response ) + { + szresponse = (char *)response->response; + + gEngfuncs.Con_Printf( "rules %s\n", szresponse ); + } + break; + default: + break; + } +} + +/* +=================== +PlayersResponse + +=================== +*/ +void CHudServers::PlayersResponse( struct net_response_s *response ) +{ + char *szresponse; + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_PLAYERS: + if ( response->response ) + { + szresponse = (char *)response->response; + + gEngfuncs.Con_Printf( "players %s\n", szresponse ); + } + break; + default: + break; + } +} + +/* +=================== +CompareServers + +Return 1 if p1 is "less than" p2, 0 otherwise +=================== +*/ +int CHudServers::CompareServers( server_t *p1, server_t *p2 ) +{ + const char *n1, *n2; + + if ( p1->ping < p2->ping ) + return 1; + + if ( p1->ping == p2->ping ) + { + // Pings equal, sort by second key: hostname + if ( p1->info && p2->info ) + { + n1 = NET_API->ValueForKey( p1->info, "hostname" ); + n2 = NET_API->ValueForKey( p2->info, "hostname" ); + + if ( n1 && n2 ) + { + if ( stricmp( n1, n2 ) < 0 ) + return 1; + } + } + } + + return 0; +} + +/* +=================== +AddServer + +=================== +*/ +void CHudServers::AddServer( server_t **ppList, server_t *p ) +{ +server_t *list; + + if ( !ppList || ! p ) + return; + + m_nServerCount++; + + // What sort key? Ping? + list = *ppList; + + // Head of list? + if ( !list ) + { + p->next = NULL; + *ppList = p; + return; + } + + // Put on head of list + if ( CompareServers( p, list ) ) + { + p->next = *ppList; + *ppList = p; + } + else + { + while ( list->next ) + { + // Insert before list next + if ( CompareServers( p, list->next ) ) + { + p->next = list->next->next; + list->next = p; + return; + } + + list = list->next; + } + + // Just add at end + p->next = NULL; + list->next = p; + } +} + +/* +=================== +Think + +=================== +*/ +void CHudServers::Think( double time ) +{ + m_fElapsed += time; + + if ( !m_nRequesting ) + return; + + if ( !m_nQuerying ) + return; + + QueryThink(); + + if ( ServerListSize() > 0 ) + return; + + m_dStarted = 0.0; + m_nRequesting = 0; + m_nDone = 0; + m_nQuerying = 0; + m_nActiveQueries = 0; +} + +/* +=================== +QueryThink + +=================== +*/ +void CHudServers::QueryThink( void ) +{ + request_t *p; + + if ( !m_nRequesting || m_nDone ) + return; + + if ( !m_nQuerying ) + return; + + if ( m_nActiveQueries > MAX_QUERIES ) + return; + + // Nothing left + if ( !m_pServerList ) + return; + + while ( 1 ) + { + p = m_pServerList; + + // No more in list? + if ( !p ) + break; + + // Move to next + m_pServerList = m_pServerList->next; + + // Setup context_id + p->context = context_id; + + // Start up query on this one + NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, 0, 2.0, &p->remote_address, ::ServerResponse ); + + // Increment active list + m_nActiveQueries++; + + // Add to active list + p->next = m_pActiveList; + m_pActiveList = p; + + // Too many active? + if ( m_nActiveQueries > MAX_QUERIES ) + break; + } +} + +/* +================== +ServerListSize + +# of servers in active query and in pending to be queried lists +================== +*/ +int CHudServers::ServerListSize( void ) +{ + int c = 0; + request_t *p; + + p = m_pServerList; + while ( p ) + { + c++; + p = p->next; + } + + p = m_pActiveList; + while ( p ) + { + c++; + p = p->next; + } + + return c; +} + +/* +=================== +FindRequest + +Look up a request by context id +=================== +*/ +CHudServers::request_t *CHudServers::FindRequest( int context, request_t *pList ) +{ + request_t *p; + p = pList; + while ( p ) + { + if ( context == p->context ) + return p; + + p = p->next; + } + return NULL; +} + +/* +=================== +RemoveServerFromList + +Remote, but don't delete, item from *ppList +=================== +*/ +void CHudServers::RemoveServerFromList( request_t **ppList, request_t *item ) +{ + request_t *p, *n; + request_t *newlist = NULL; + + if ( !ppList ) + return; + + p = *ppList; + while ( p ) + { + n = p->next; + if ( p != item ) + { + p->next = newlist; + newlist = p; + } + p = n; + } + *ppList = newlist; +} + +/* +=================== +ClearRequestList + +=================== +*/ +void CHudServers::ClearRequestList( request_t **ppList ) +{ + request_t *p, *n; + + if ( !ppList ) + return; + + p = *ppList; + while ( p ) + { + n = p->next; + delete p; + p = n; + } + *ppList = NULL; +} + +/* +=================== +ClearServerList + +=================== +*/ +void CHudServers::ClearServerList( server_t **ppList ) +{ + server_t *p, *n; + + if ( !ppList ) + return; + + p = *ppList; + while ( p ) + { + n = p->next; + delete[] p->info; + delete p; + p = n; + } + *ppList = NULL; +} + +int CompareField( CHudServers::server_t *p1, CHudServers::server_t *p2, const char *fieldname, int iSortOrder ) +{ + const char *sz1, *sz2; + float fv1, fv2; + + sz1 = NET_API->ValueForKey( p1->info, fieldname ); + sz2 = NET_API->ValueForKey( p2->info, fieldname ); + + fv1 = atof( sz1 ); + fv2 = atof( sz2 ); + + if ( fv1 && fv2 ) + { + if ( fv1 > fv2 ) + return iSortOrder; + else if ( fv1 < fv2 ) + return -iSortOrder; + else + return 0; + } + + // String compare + return stricmp( sz1, sz2 ); +} + +int CALLBACK ServerListCompareFunc( CHudServers::server_t *p1, CHudServers::server_t *p2, const char *fieldname ) +{ + if (!p1 || !p2) // No meaningful comparison + return 0; + + int iSortOrder = 1; + + int retval = 0; + + retval = CompareField( p1, p2, fieldname, iSortOrder ); + + return retval; +} + +static char g_fieldname[ 256 ]; +int __cdecl FnServerCompare(const void *elem1, const void *elem2 ) +{ + CHudServers::server_t *list1, *list2; + + list1 = *(CHudServers::server_t **)elem1; + list2 = *(CHudServers::server_t **)elem2; + + return ServerListCompareFunc( list1, list2, g_fieldname ); +} + +void CHudServers::SortServers( const char *fieldname ) +{ + server_t *p; + // Create a list + if ( !m_pServers ) + return; + + strcpy( g_fieldname, fieldname ); + + int i; + int c = 0; + + p = m_pServers; + while ( p ) + { + c++; + p = p->next; + } + + server_t **pSortArray; + + pSortArray = new server_t *[ c ]; + memset( pSortArray, 0, c * sizeof( server_t * ) ); + + // Now copy the list into the pSortArray: + p = m_pServers; + i = 0; + while ( p ) + { + pSortArray[ i++ ] = p; + p = p->next; + } + + // Now do that actual sorting. + size_t nCount = c; + size_t nSize = sizeof( server_t * ); + + qsort( + pSortArray, + (size_t)nCount, + (size_t)nSize, + FnServerCompare + ); + + // Now rebuild the list. + m_pServers = pSortArray[0]; + for ( i = 0; i < c - 1; i++ ) + { + pSortArray[ i ]->next = pSortArray[ i + 1 ]; + } + pSortArray[ c - 1 ]->next = NULL; + + // Clean Up. + delete[] pSortArray; +} + +/* +=================== +GetServer + +Return particular server +=================== +*/ +CHudServers::server_t *CHudServers::GetServer( int server ) +{ + int c = 0; + server_t *p; + + p = m_pServers; + while ( p ) + { + if ( c == server ) + return p; + + c++; + p = p->next; + } + return NULL; +} + +/* +=================== +GetServerInfo + +Return info ( key/value ) string for particular server +=================== +*/ +char *CHudServers::GetServerInfo( int server ) +{ + server_t *p = GetServer( server ); + if ( p ) + { + return p->info; + } + return NULL; +} + +/* +=================== +CancelRequest + +Kill all pending requests in engine +=================== +*/ +void CHudServers::CancelRequest( void ) +{ + m_nRequesting = 0; + m_nQuerying = 0; + m_nDone = 1; + + NET_API->CancelAllRequests(); +} + +/* +================== +LoadMasterAddresses + +Loads the master server addresses from file and into the passed in array +================== +*/ +int CHudServers::LoadMasterAddresses( int maxservers, int *count, netadr_t *padr ) +{ + int i; + char szMaster[ 256 ]; + char szMasterFile[256]; + char *pbuffer = NULL; + char *pstart = NULL ; + netadr_t adr; + char szAdr[64]; + int nPort; + int nCount = 0; + bool bIgnore; + int nDefaultPort; + + // Assume default master and master file + strcpy( szMaster, VALVE_MASTER_ADDRESS ); // IP:PORT string + strcpy( szMasterFile, MASTER_PARSE_FILE ); + + // See if there is a command line override + i = gEngfuncs.CheckParm( "-comm", &pstart ); + if ( i && pstart ) + { + strcpy (szMasterFile, pstart ); + } + + // Read them in from proper file + pbuffer = (char *)gEngfuncs.COM_LoadFile( szMasterFile, 5, NULL ); // Use malloc + if ( !pbuffer ) + { + goto finish_master; + } + + pstart = pbuffer; + + while ( nCount < maxservers ) + { + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if ( strlen(m_szToken) <= 0) + break; + + bIgnore = true; + + if ( !stricmp( m_szToken, "Master" ) ) + { + nDefaultPort = PORT_MASTER; + bIgnore = FALSE; + } + + // Now parse all addresses between { } + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + if ( strlen(m_szToken) <= 0 ) + break; + + if ( stricmp ( m_szToken, "{" ) ) + break; + + // Parse addresses until we get to "}" + while ( nCount < maxservers ) + { + char base[256]; + + // Now parse all addresses between { } + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if (strlen(m_szToken) <= 0) + break; + + if ( !stricmp ( m_szToken, "}" ) ) + break; + + sprintf( base, "%s", m_szToken ); + + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if (strlen(m_szToken) <= 0) + break; + + if ( stricmp( m_szToken, ":" ) ) + break; + + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if (strlen(m_szToken) <= 0) + break; + + nPort = atoi ( m_szToken ); + if ( !nPort ) + nPort = nDefaultPort; + + sprintf( szAdr, "%s:%i", base, nPort ); + + // Can we resolve it any better + if ( !NET_API->StringToAdr( szAdr, &adr ) ) + bIgnore = true; + + if ( !bIgnore ) + { + padr[ nCount++ ] = adr; + } + } + } + +finish_master: + if ( !nCount ) + { + sprintf( szMaster, VALVE_MASTER_ADDRESS ); // IP:PORT string + + // Convert to netadr_t + if ( NET_API->StringToAdr ( szMaster, &adr ) ) + { + + padr[ nCount++ ] = adr; + } + } + + *count = nCount; + + if ( pbuffer ) + { + gEngfuncs.COM_FreeFile( pbuffer ); + } + + return ( nCount > 0 ) ? 1 : 0; +} + +/* +=================== +RequestList + +Request list of game servers from master +=================== +*/ +void CHudServers::RequestList( void ) +{ + m_nRequesting = 1; + m_nDone = 0; + m_dStarted = m_fElapsed; + + int count = 0; + netadr_t adr; + + if ( !LoadMasterAddresses( 1, &count, &adr ) ) + { + gEngfuncs.Con_DPrintf( "SendRequest: Unable to read master server addresses\n" ); + return; + } + + ClearRequestList( &m_pActiveList ); + ClearRequestList( &m_pServerList ); + ClearServerList( &m_pServers ); + + m_nServerCount = 0; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Kill off left overs if any + NET_API->CancelAllRequests(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_SERVERLIST, 0, 5.0, &adr, ::ListResponse ); +} + +void CHudServers::RequestBroadcastList( int clearpending ) +{ + m_nRequesting = 1; + m_nDone = 0; + m_dStarted = m_fElapsed; + + netadr_t adr; + memset( &adr, 0, sizeof( adr ) ); + + if ( clearpending ) + { + ClearRequestList( &m_pActiveList ); + ClearRequestList( &m_pServerList ); + ClearServerList( &m_pServers ); + + m_nServerCount = 0; + } + + // Make sure to byte swap server if necessary ( using "host" to "net" conversion + adr.port = htons( PORT_SERVER ); + + // Make sure networking system has started. + NET_API->InitNetworking(); + + if ( clearpending ) + { + // Kill off left overs if any + NET_API->CancelAllRequests(); + } + + adr.type = NA_BROADCAST; + + // Request Servers from LAN via IP + NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, FNETAPI_MULTIPLE_RESPONSE, 5.0, &adr, ::ServerResponse ); + + adr.type = NA_BROADCAST_IPX; + + // Request Servers from LAN via IPX ( if supported ) + NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, FNETAPI_MULTIPLE_RESPONSE, 5.0, &adr, ::ServerResponse ); +} + +void CHudServers::ServerPing( int server ) +{ + server_t *p; + + p = GetServer( server ); + if ( !p ) + return; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_PING, 0, 5.0, &p->remote_address, ::PingResponse ); +} + +void CHudServers::ServerRules( int server ) +{ + server_t *p; + + p = GetServer( server ); + if ( !p ) + return; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_RULES, 0, 5.0, &p->remote_address, ::RulesResponse ); +} + +void CHudServers::ServerPlayers( int server ) +{ + server_t *p; + + p = GetServer( server ); + if ( !p ) + return; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_PLAYERS, 0, 5.0, &p->remote_address, ::PlayersResponse ); +} + +int CHudServers::isQuerying() +{ + return m_nRequesting ? 1 : 0; +} + + +/* +=================== +GetServerCount + +Return number of servers in browser list +=================== +*/ +int CHudServers::GetServerCount( void ) +{ + return m_nServerCount; +} + +/* +=================== +CHudServers + +=================== +*/ +CHudServers::CHudServers( void ) +{ + m_nRequesting = 0; + m_dStarted = 0.0; + m_nDone = 0; + m_pServerList = NULL; + m_pServers = NULL; + m_pActiveList = NULL; + m_nQuerying = 0; + m_nActiveQueries = 0; + + m_fElapsed = 0.0; + + + m_pPingRequest = NULL; + m_pRulesRequest = NULL; + m_pPlayersRequest = NULL; +} + +/* +=================== +~CHudServers + +=================== +*/ +CHudServers::~CHudServers( void ) +{ + ClearRequestList( &m_pActiveList ); + ClearRequestList( &m_pServerList ); + ClearServerList( &m_pServers ); + + m_nServerCount = 0; + + if ( m_pPingRequest ) + { + delete m_pPingRequest; + m_pPingRequest = NULL; + + } + + if ( m_pRulesRequest ) + { + delete m_pRulesRequest; + m_pRulesRequest = NULL; + } + + if ( m_pPlayersRequest ) + { + delete m_pPlayersRequest; + m_pPlayersRequest = NULL; + } +} + +/////////////////////////////// +// +// PUBLIC APIs +// +/////////////////////////////// + +/* +=================== +ServersGetCount + +=================== +*/ +int ServersGetCount( void ) +{ + if ( g_pServers ) + { + return g_pServers->GetServerCount(); + } + return 0; +} + +int ServersIsQuerying( void ) +{ + if ( g_pServers ) + { + return g_pServers->isQuerying(); + } + return 0; +} + +/* +=================== +ServersGetInfo + +=================== +*/ +const char *ServersGetInfo( int server ) +{ + if ( g_pServers ) + { + return g_pServers->GetServerInfo( server ); + } + + return NULL; +} + +void SortServers( const char *fieldname ) +{ + if ( g_pServers ) + { + g_pServers->SortServers( fieldname ); + } +} + +/* +=================== +ServersShutdown + +=================== +*/ +void ServersShutdown( void ) +{ + if ( g_pServers ) + { + delete g_pServers; + g_pServers = NULL; + } +} + +/* +=================== +ServersInit + +=================== +*/ +void ServersInit( void ) +{ + // Kill any previous instance + ServersShutdown(); + + g_pServers = new CHudServers(); +} + +/* +=================== +ServersThink + +=================== +*/ +void ServersThink( double time ) +{ + if ( g_pServers ) + { + g_pServers->Think( time ); + } +} + +/* +=================== +ServersCancel + +=================== +*/ +void ServersCancel( void ) +{ + if ( g_pServers ) + { + g_pServers->CancelRequest(); + } +} + +// Requests +/* +=================== +ServersList + +=================== +*/ +void ServersList( void ) +{ + if ( g_pServers ) + { + g_pServers->RequestList(); + } +} + +void BroadcastServersList( int clearpending ) +{ + if ( g_pServers ) + { + g_pServers->RequestBroadcastList( clearpending ); + } +} + +void ServerPing( int server ) +{ + if ( g_pServers ) + { + g_pServers->ServerPing( server ); + } +} + +void ServerRules( int server ) +{ + if ( g_pServers ) + { + g_pServers->ServerRules( server ); + } +} + +void ServerPlayers( int server ) +{ + if ( g_pServers ) + { + g_pServers->ServerPlayers( server ); + } +} \ No newline at end of file diff --git a/hlsdk/cl_dll/hud_servers.h b/hlsdk/cl_dll/hud_servers.h new file mode 100644 index 00000000..02bc8555 --- /dev/null +++ b/hlsdk/cl_dll/hud_servers.h @@ -0,0 +1,41 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( HUD_SERVERSH ) +#define HUD_SERVERSH +#pragma once + +#define NET_CALLBACK /* */ + +// Dispatchers +void NET_CALLBACK ListResponse( struct net_response_s *response ); +void NET_CALLBACK ServerResponse( struct net_response_s *response ); +void NET_CALLBACK PingResponse( struct net_response_s *response ); +void NET_CALLBACK RulesResponse( struct net_response_s *response ); +void NET_CALLBACK PlayersResponse( struct net_response_s *response ); + +void ServersInit( void ); +void ServersShutdown( void ); +void ServersThink( double time ); +void ServersCancel( void ); + +// Get list and get server info from each +void ServersList( void ); + +// Query for IP / IPX LAN servers +void BroadcastServersList( int clearpending ); + +void ServerPing( int server ); +void ServerRules( int server ); +void ServerPlayers( int server ); + +int ServersGetCount( void ); +const char *ServersGetInfo( int server ); +int ServersIsQuerying( void ); +void SortServers( const char *fieldname ); + +#endif // HUD_SERVERSH \ No newline at end of file diff --git a/hlsdk/cl_dll/hud_servers_priv.h b/hlsdk/cl_dll/hud_servers_priv.h new file mode 100644 index 00000000..1919a0a7 --- /dev/null +++ b/hlsdk/cl_dll/hud_servers_priv.h @@ -0,0 +1,98 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( HUD_SERVERS_PRIVH ) +#define HUD_SERVERS_PRIVH +#pragma once + +#include "netadr.h" + +class CHudServers +{ +public: + typedef struct request_s + { + struct request_s *next; + netadr_t remote_address; + int context; + } request_t; + + typedef struct server_s + { + struct server_s *next; + netadr_t remote_address; + char *info; + int ping; + } server_t; + + CHudServers(); + ~CHudServers(); + + void Think( double time ); + void QueryThink( void ); + int isQuerying( void ); + + int LoadMasterAddresses( int maxservers, int *count, netadr_t *padr ); + + void RequestList( void ); + void RequestBroadcastList( int clearpending ); + + void ServerPing( int server ); + void ServerRules( int server ); + void ServerPlayers( int server ); + + void CancelRequest( void ); + + int CompareServers( server_t *p1, server_t *p2 ); + + void ClearServerList( server_t **ppList ); + void ClearRequestList( request_t **ppList ); + + void AddServer( server_t **ppList, server_t *p ); + + void RemoveServerFromList( request_t **ppList, request_t *item ); + + request_t *FindRequest( int context, request_t *pList ); + + int ServerListSize( void ); + char *GetServerInfo( int server ); + int GetServerCount( void ); + void SortServers( const char *fieldname ); + + void ListResponse( struct net_response_s *response ); + void ServerResponse( struct net_response_s *response ); + void PingResponse( struct net_response_s *response ); + void RulesResponse( struct net_response_s *response ); + void PlayersResponse( struct net_response_s *response ); +private: + + server_t *GetServer( int server ); + + // + char m_szToken[ 1024 ]; + int m_nRequesting; + int m_nDone; + + double m_dStarted; + + request_t *m_pServerList; + request_t *m_pActiveList; + + server_t *m_pServers; + + int m_nServerCount; + + int m_nActiveQueries; + int m_nQuerying; + double m_fElapsed; + + request_t *m_pPingRequest; + request_t *m_pRulesRequest; + request_t *m_pPlayersRequest; +}; + +#endif // HUD_SERVERS_PRIVH \ No newline at end of file diff --git a/hlsdk/cl_dll/hud_spectator.cpp b/hlsdk/cl_dll/hud_spectator.cpp new file mode 100644 index 00000000..59ff6ad3 --- /dev/null +++ b/hlsdk/cl_dll/hud_spectator.cpp @@ -0,0 +1,1623 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "hud.h" +#include "cl_util.h" +#include "cl_entity.h" +#include "triangleapi.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_SpectatorPanel.h" +#include "hltv.h" + +#include "pm_shared.h" +#include "pm_defs.h" +#include "pmtrace.h" +#include "parsemsg.h" +#include "entity_types.h" + +// these are included for the math functions +#include "com_model.h" +#include "demo_api.h" +#include "event_api.h" +#include "studio_util.h" +#include "screenfade.h" + + +#pragma warning(disable: 4244) + +extern "C" int iJumpSpectator; +extern "C" float vJumpOrigin[3]; +extern "C" float vJumpAngles[3]; + + +extern void V_GetInEyePos(int entity, float * origin, float * angles ); +extern void V_ResetChaseCam(); +extern void V_GetChasePos(int target, float * cl_angles, float * origin, float * angles); +extern void VectorAngles( const float *forward, float *angles ); +extern "C" void NormalizeAngles( float *angles ); +extern float * GetClientColor( int clientIndex ); + +extern vec3_t v_origin; // last view origin +extern vec3_t v_angles; // last view angle +extern vec3_t v_cl_angles; // last client/mouse angle +extern vec3_t v_sim_org; // last sim origin + +void SpectatorMode(void) +{ + + + if ( gEngfuncs.Cmd_Argc() <= 1 ) + { + gEngfuncs.Con_Printf( "usage: spec_mode
[]\n" ); + return; + } + + // SetModes() will decide if command is executed on server or local + if ( gEngfuncs.Cmd_Argc() == 2 ) + gHUD.m_Spectator.SetModes( atoi( gEngfuncs.Cmd_Argv(1) ), -1 ); + else if ( gEngfuncs.Cmd_Argc() == 3 ) + gHUD.m_Spectator.SetModes( atoi( gEngfuncs.Cmd_Argv(1) ), atoi( gEngfuncs.Cmd_Argv(2) ) ); +} + +void SpectatorSpray(void) +{ + vec3_t forward; + char string[128]; + + if ( !gEngfuncs.IsSpectateOnly() ) + return; + + AngleVectors(v_angles,forward,NULL,NULL); + VectorScale(forward, 128, forward); + VectorAdd(forward, v_origin, forward); + pmtrace_t * trace = gEngfuncs.PM_TraceLine( v_origin, forward, PM_TRACELINE_PHYSENTSONLY, 2, -1 ); + if ( trace->fraction != 1.0 ) + { + sprintf(string, "drc_spray %.2f %.2f %.2f %i", + trace->endpos[0], trace->endpos[1], trace->endpos[2], trace->ent ); + gEngfuncs.pfnServerCmd(string); + } + +} +void SpectatorHelp(void) +{ + if ( gViewPort ) + { + gViewPort->ShowVGUIMenu( MENU_SPECHELP ); + } + else + { + char *text = CHudTextMessage::BufferedLocaliseTextString( "#Spec_Help_Text" ); + + if ( text ) + { + while ( *text ) + { + if ( *text != 13 ) + gEngfuncs.Con_Printf( "%c", *text ); + text++; + } + } + } +} + +void SpectatorMenu( void ) +{ + if ( gEngfuncs.Cmd_Argc() <= 1 ) + { + gEngfuncs.Con_Printf( "usage: spec_menu <0|1>\n" ); + return; + } + + gViewPort->m_pSpectatorPanel->ShowMenu( atoi( gEngfuncs.Cmd_Argv(1))!=0 ); +} + +void ToggleScores( void ) +{ + if ( gViewPort ) + { + if (gViewPort->IsScoreBoardVisible() ) + { + gViewPort->HideScoreBoard(); + } + else + { + gViewPort->ShowScoreBoard(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CHudSpectator::Init() +{ + gHUD.AddHudElem(this); + + m_iFlags |= HUD_ACTIVE; + m_flNextObserverInput = 0.0f; + m_zoomDelta = 0.0f; + m_moveDelta = 0.0f; + m_chatEnabled = (gHUD.m_SayText.m_HUD_saytext->value!=0); + iJumpSpectator = 0; + + memset( &m_OverviewData, 0, sizeof(m_OverviewData)); + memset( &m_OverviewEntities, 0, sizeof(m_OverviewEntities)); + m_lastPrimaryObject = m_lastSecondaryObject = 0; + + gEngfuncs.pfnAddCommand ("spec_mode", SpectatorMode ); + gEngfuncs.pfnAddCommand ("spec_decal", SpectatorSpray ); + gEngfuncs.pfnAddCommand ("spec_help", SpectatorHelp ); + gEngfuncs.pfnAddCommand ("spec_menu", SpectatorMenu ); + gEngfuncs.pfnAddCommand ("togglescores", ToggleScores ); + + m_drawnames = gEngfuncs.pfnRegisterVariable("spec_drawnames","1",0); + m_drawcone = gEngfuncs.pfnRegisterVariable("spec_drawcone","1",0); + m_drawstatus = gEngfuncs.pfnRegisterVariable("spec_drawstatus","1",0); + m_autoDirector = gEngfuncs.pfnRegisterVariable("spec_autodirector","1",0); + m_pip = gEngfuncs.pfnRegisterVariable("spec_pip","1",0); + + if ( !m_drawnames || !m_drawcone || !m_drawstatus || !m_autoDirector || !m_pip) + { + gEngfuncs.Con_Printf("ERROR! Couldn't register all spectator variables.\n"); + return 0; + } + + return 1; +} + + +//----------------------------------------------------------------------------- +// UTIL_StringToVector originally from ..\dlls\util.cpp, slightly changed +//----------------------------------------------------------------------------- + +void UTIL_StringToVector( float * pVector, const char *pString ) +{ + char *pstr, *pfront, tempString[128]; + int j; + + strcpy( tempString, pString ); + pstr = pfront = tempString; + + for ( j = 0; j < 3; j++ ) + { + pVector[j] = atof( pfront ); + + while ( *pstr && *pstr != ' ' ) + pstr++; + if (!*pstr) + break; + pstr++; + pfront = pstr; + } + + if (j < 2) + { + for (j = j+1;j < 3; j++) + pVector[j] = 0; + } +} + +int UTIL_FindEntityInMap(char * name, float * origin, float * angle) +{ + int n,found = 0; + char keyname[256]; + char token[1024]; + + cl_entity_t * pEnt = gEngfuncs.GetEntityByIndex( 0 ); // get world model + + if ( !pEnt ) return 0; + + if ( !pEnt->model ) return 0; + + char * data = pEnt->model->entities; + + while (data) + { + data = gEngfuncs.COM_ParseFile(data, token); + + if ( (token[0] == '}') || (token[0]==0) ) + break; + + if (!data) + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: EOF without closing brace\n"); + return 0; + } + + if (token[0] != '{') + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: expected {\n"); + return 0; + } + + // we parse the first { now parse entities properties + + while ( 1 ) + { + // parse key + data = gEngfuncs.COM_ParseFile(data, token); + if (token[0] == '}') + break; // finish parsing this entity + + if (!data) + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: EOF without closing brace\n"); + return 0; + }; + + strcpy (keyname, token); + + // another hack to fix keynames with trailing spaces + n = strlen(keyname); + while (n && keyname[n-1] == ' ') + { + keyname[n-1] = 0; + n--; + } + + // parse value + data = gEngfuncs.COM_ParseFile(data, token); + if (!data) + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: EOF without closing brace\n"); + return 0; + }; + + if (token[0] == '}') + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: closing brace without data"); + return 0; + } + + if (!strcmp(keyname,"classname")) + { + if (!strcmp(token, name )) + { + found = 1; // thats our entity + } + }; + + if( !strcmp( keyname, "angle" ) ) + { + float y = atof( token ); + + if (y >= 0) + { + angle[0] = 0.0f; + angle[1] = y; + } + else if ((int)y == -1) + { + angle[0] = -90.0f; + angle[1] = 0.0f;; + } + else + { + angle[0] = 90.0f; + angle[1] = 0.0f; + } + + angle[2] = 0.0f; + } + + if( !strcmp( keyname, "angles" ) ) + { + UTIL_StringToVector(angle, token); + } + + if (!strcmp(keyname,"origin")) + { + UTIL_StringToVector(origin, token); + + }; + + } // while (1) + + if (found) + return 1; + + } + + return 0; // we search all entities, but didn't found the correct + +} + +//----------------------------------------------------------------------------- +// SetSpectatorStartPosition(): +// Get valid map position and 'beam' spectator to this position +//----------------------------------------------------------------------------- + +void CHudSpectator::SetSpectatorStartPosition() +{ + // search for info_player start + if ( UTIL_FindEntityInMap( "trigger_camera", m_cameraOrigin, m_cameraAngles ) ) + iJumpSpectator = 1; + + else if ( UTIL_FindEntityInMap( "info_player_start", m_cameraOrigin, m_cameraAngles ) ) + iJumpSpectator = 1; + + else if ( UTIL_FindEntityInMap( "info_player_deathmatch", m_cameraOrigin, m_cameraAngles ) ) + iJumpSpectator = 1; + + else if ( UTIL_FindEntityInMap( "info_player_coop", m_cameraOrigin, m_cameraAngles ) ) + iJumpSpectator = 1; + else + { + // jump to 0,0,0 if no better position was found + VectorCopy(vec3_origin, m_cameraOrigin); + VectorCopy(vec3_origin, m_cameraAngles); + } + + VectorCopy(m_cameraOrigin, vJumpOrigin); + VectorCopy(m_cameraAngles, vJumpAngles); + + iJumpSpectator = 1; // jump anyway +} + +//----------------------------------------------------------------------------- +// Purpose: Loads new icons +//----------------------------------------------------------------------------- +int CHudSpectator::VidInit() +{ + m_hsprPlayer = SPR_Load("sprites/iplayer.spr"); + m_hsprPlayerBlue = SPR_Load("sprites/iplayerblue.spr"); + m_hsprPlayerRed = SPR_Load("sprites/iplayerred.spr"); + m_hsprPlayerDead = SPR_Load("sprites/iplayerdead.spr"); + m_hsprUnkownMap = SPR_Load("sprites/tile.spr"); + m_hsprBeam = SPR_Load("sprites/laserbeam.spr"); + m_hsprCamera = SPR_Load("sprites/camera.spr"); + m_hCrosshair = SPR_Load("sprites/crosshairs.spr"); + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flTime - +// intermission - +//----------------------------------------------------------------------------- +int CHudSpectator::Draw(float flTime) +{ + int lx; + + char string[256]; + float * color; + + // draw only in spectator mode + if ( !g_iUser1 ) + return 0; + + // if user pressed zoom, aplly changes + if ( (m_zoomDelta != 0.0f) && ( g_iUser1 == OBS_MAP_FREE ) ) + { + m_mapZoom += m_zoomDelta; + + if ( m_mapZoom > 3.0f ) + m_mapZoom = 3.0f; + + if ( m_mapZoom < 0.5f ) + m_mapZoom = 0.5f; + } + + // if user moves in map mode, change map origin + if ( (m_moveDelta != 0.0f) && (g_iUser1 != OBS_ROAMING) ) + { + vec3_t right; + AngleVectors(v_angles, NULL, right, NULL); + VectorNormalize(right); + VectorScale(right, m_moveDelta, right ); + + VectorAdd( m_mapOrigin, right, m_mapOrigin ) + + } + + // Only draw the icon names only if map mode is in Main Mode + if ( g_iUser1 < OBS_MAP_FREE ) + return 1; + + if ( !m_drawnames->value ) + return 1; + + // 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++) + { + + if ( m_vPlayerPos[i][2]<0 ) // marked as invisible ? + continue; + + // check if name would be in inset window + if ( m_pip->value != INSET_OFF ) + { + if ( m_vPlayerPos[i][0] > XRES( m_OverviewData.insetWindowX ) && + m_vPlayerPos[i][1] > YRES( m_OverviewData.insetWindowY ) && + m_vPlayerPos[i][0] < XRES( m_OverviewData.insetWindowX + m_OverviewData.insetWindowWidth ) && + m_vPlayerPos[i][1] < YRES( m_OverviewData.insetWindowY + m_OverviewData.insetWindowHeight) + ) continue; + } + + color = GetClientColor( i+1 ); + + // draw the players name and health underneath + sprintf(string, "%s", g_PlayerInfoList[i+1].name ); + + lx = strlen(string)*3; // 3 is avg. character length :) + + gEngfuncs.pfnDrawSetTextColor( color[0], color[1], color[2] ); + DrawConsoleString( m_vPlayerPos[i][0]-lx,m_vPlayerPos[i][1], string); + + } + + return 1; +} + + +void CHudSpectator::DirectorMessage( int iSize, void *pbuf ) +{ + float value; + char * string; + + BEGIN_READ( pbuf, iSize ); + + int cmd = READ_BYTE(); + + switch ( cmd ) // director command byte + { + 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; + + // fake a InitHUD & ResetHUD message + gHUD.MsgFunc_InitHUD(NULL,0, NULL); + gHUD.MsgFunc_ResetHUD(NULL, 0, NULL); + + break; + + case DRC_CMD_EVENT : + m_lastPrimaryObject = READ_WORD(); + m_lastSecondaryObject = READ_WORD(); + m_iObserverFlags = READ_LONG(); + + if ( m_autoDirector->value ) + { + if ( (g_iUser2 != m_lastPrimaryObject) || (g_iUser3 != m_lastSecondaryObject) ) + V_ResetChaseCam(); + + g_iUser2 = m_lastPrimaryObject; + g_iUser3 = m_lastSecondaryObject; + } + + // gEngfuncs.Con_Printf("Director Camera: %i %i\n", firstObject, secondObject); + break; + + case DRC_CMD_MODE : + if ( m_autoDirector->value ) + { + SetModes( READ_BYTE(), -1 ); + } + break; + + case DRC_CMD_CAMERA : + if ( m_autoDirector->value ) + { + vJumpOrigin[0] = READ_COORD(); // position + vJumpOrigin[1] = READ_COORD(); + vJumpOrigin[2] = READ_COORD(); + + vJumpAngles[0] = READ_COORD(); // view angle + vJumpAngles[1] = READ_COORD(); + vJumpAngles[2] = READ_COORD(); + + gEngfuncs.SetViewAngles( vJumpAngles ); + + iJumpSpectator = 1; + } + break; + + case DRC_CMD_MESSAGE: + { + client_textmessage_t * msg = &m_HUDMessages[m_lastHudMessage]; + + msg->effect = READ_BYTE(); // effect + + UnpackRGB( (int&)msg->r1, (int&)msg->g1, (int&)msg->b1, READ_LONG() ); // color + msg->r2 = msg->r1; + msg->g2 = msg->g1; + msg->b2 = msg->b1; + msg->a2 = msg->a1 = 0xFF; // not transparent + + msg->x = READ_FLOAT(); // x pos + msg->y = READ_FLOAT(); // y pos + + msg->fadein = READ_FLOAT(); // fadein + msg->fadeout = READ_FLOAT(); // fadeout + msg->holdtime = READ_FLOAT(); // holdtime + msg->fxtime = READ_FLOAT(); // fxtime; + + strncpy( m_HUDMessageText[m_lastHudMessage], READ_STRING(), 128 ); + m_HUDMessageText[m_lastHudMessage][127]=0; // text + + msg->pMessage = m_HUDMessageText[m_lastHudMessage]; + msg->pName = "HUD_MESSAGE"; + + gHUD.m_Message.MessageAdd( msg ); + + m_lastHudMessage++; + m_lastHudMessage %= MAX_SPEC_HUD_MESSAGES; + + } + + break; + + case DRC_CMD_SOUND : + string = READ_STRING(); + value = READ_FLOAT(); + + // gEngfuncs.Con_Printf("DRC_CMD_FX_SOUND: %s %.2f\n", string, value ); + gEngfuncs.pEventAPI->EV_PlaySound(0, v_origin, CHAN_BODY, string, value, ATTN_NORM, 0, PITCH_NORM ); + + break; + + case DRC_CMD_TIMESCALE : + value = READ_FLOAT(); + break; + + + + case DRC_CMD_STATUS: + READ_LONG(); // total number of spectator slots + m_iSpectatorNumber = READ_LONG(); // total number of spectator + READ_WORD(); // total number of relay proxies + + gViewPort->UpdateSpectatorPanel(); + break; + + case DRC_CMD_BANNER: + // gEngfuncs.Con_DPrintf("GUI: Banner %s\n",READ_STRING() ); // name of banner tga eg gfx/temp/7454562234563475.tga + gViewPort->m_pSpectatorPanel->m_TopBanner->LoadImage( READ_STRING() ); + gViewPort->UpdateSpectatorPanel(); + break; + + case DRC_CMD_FADE: + break; + + case DRC_CMD_STUFFTEXT: + ClientCmd( READ_STRING() ); + break; + + default : gEngfuncs.Con_DPrintf("CHudSpectator::DirectorMessage: unknown command %i.\n", cmd ); + } +} + +void CHudSpectator::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; + cl_entity_t * pEnt = NULL; + + // if we are NOT in HLTV mode, spectator targets are set on server + if ( !gEngfuncs.IsSpectateOnly() ) + { + char cmdstring[32]; + // forward command to server + sprintf(cmdstring,"follownext %i",bReverse?1:0); + gEngfuncs.pfnServerCmd(cmdstring); + return; + } + + if ( g_iUser2 ) + iStart = g_iUser2; + else + iStart = 1; + + g_iUser2 = 0; + + int iCurrent = iStart; + + int iDir = bReverse ? -1 : 1; + + // make sure we have player info + gViewPort->GetAllPlayersInfo(); + + + do + { + iCurrent += iDir; + + // Loop through the clients + if (iCurrent > MAX_PLAYERS) + iCurrent = 1; + if (iCurrent < 1) + iCurrent = MAX_PLAYERS; + + pEnt = gEngfuncs.GetEntityByIndex( iCurrent ); + + if ( !IsActivePlayer( pEnt ) ) + continue; + + // MOD AUTHORS: Add checks on target here. + + g_iUser2 = iCurrent; + break; + + } while ( iCurrent != iStart ); + + // Did we find a target? + if ( !g_iUser2 ) + { + gEngfuncs.Con_DPrintf( "No observer targets.\n" ); + // take save camera position + VectorCopy(m_cameraOrigin, vJumpOrigin); + VectorCopy(m_cameraAngles, vJumpAngles); + } + else + { + // use new entity position for roaming + VectorCopy ( pEnt->origin, vJumpOrigin ); + VectorCopy ( pEnt->angles, vJumpAngles ); + } + iJumpSpectator = 1; +} + +void CHudSpectator::HandleButtonsDown( int ButtonPressed ) +{ + double time = gEngfuncs.GetClientTime(); + + int newMainMode = g_iUser1; + int newInsetMode = m_pip->value; + + // gEngfuncs.Con_Printf(" HandleButtons:%i\n", ButtonPressed ); + if ( !gViewPort ) + return; + + //Not in intermission. + if ( gHUD.m_iIntermission ) + return; + + if ( !g_iUser1 ) + return; // dont do anything if not in spectator mode + + // don't handle buttons during normal demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() && !gEngfuncs.IsSpectateOnly() ) + return; + // Slow down mouse clicks. + if ( m_flNextObserverInput > time ) + return; + + // enable spectator screen + if ( ButtonPressed & IN_DUCK ) + gViewPort->m_pSpectatorPanel->ShowMenu(!gViewPort->m_pSpectatorPanel->m_menuVisible); + + // 'Use' changes inset window mode + if ( ButtonPressed & IN_USE ) + { + newInsetMode = ToggleInset(true); + } + + // if not in HLTV mode, buttons are handled server side + if ( gEngfuncs.IsSpectateOnly() ) + { + // changing target or chase mode not in overviewmode without inset window + + // Jump changes main window modes + if ( ButtonPressed & IN_JUMP ) + { + if ( g_iUser1 == OBS_CHASE_LOCKED ) + newMainMode = OBS_CHASE_FREE; + + else if ( g_iUser1 == OBS_CHASE_FREE ) + newMainMode = OBS_IN_EYE; + + else if ( g_iUser1 == OBS_IN_EYE ) + newMainMode = OBS_ROAMING; + + else if ( g_iUser1 == OBS_ROAMING ) + newMainMode = OBS_MAP_FREE; + + else if ( g_iUser1 == OBS_MAP_FREE ) + newMainMode = OBS_MAP_CHASE; + + else + newMainMode = OBS_CHASE_FREE; // don't use OBS_CHASE_LOCKED anymore + } + + // Attack moves to the next player + if ( ButtonPressed & (IN_ATTACK | IN_ATTACK2) ) + { + FindNextPlayer( (ButtonPressed & IN_ATTACK2) ? true:false ); + + if ( g_iUser1 == OBS_ROAMING ) + { + gEngfuncs.SetViewAngles( vJumpAngles ); + iJumpSpectator = 1; + + } + // lease directed mode if player want to see another player + m_autoDirector->value = 0.0f; + } + } + + SetModes(newMainMode, newInsetMode); + + if ( g_iUser1 == OBS_MAP_FREE ) + { + if ( ButtonPressed & IN_FORWARD ) + m_zoomDelta = 0.01f; + + if ( ButtonPressed & IN_BACK ) + m_zoomDelta = -0.01f; + + if ( ButtonPressed & IN_MOVELEFT ) + m_moveDelta = -12.0f; + + if ( ButtonPressed & IN_MOVERIGHT ) + m_moveDelta = 12.0f; + } + + m_flNextObserverInput = time + 0.2; +} + +void CHudSpectator::HandleButtonsUp( int ButtonPressed ) +{ + if ( !gViewPort ) + return; + + if ( !gViewPort->m_pSpectatorPanel->isVisible() ) + return; // dont do anything if not in spectator mode + + if ( ButtonPressed & (IN_FORWARD | IN_BACK) ) + m_zoomDelta = 0.0f; + + if ( ButtonPressed & (IN_MOVELEFT | IN_MOVERIGHT) ) + m_moveDelta = 0.0f; +} + +void CHudSpectator::SetModes(int iNewMainMode, int iNewInsetMode) +{ + // if value == -1 keep old value + if ( iNewMainMode == -1 ) + iNewMainMode = g_iUser1; + + if ( iNewInsetMode == -1 ) + iNewInsetMode = m_pip->value; + + // inset mode is handled only clients side + m_pip->value = iNewInsetMode; + + if ( iNewMainMode < OBS_CHASE_LOCKED || iNewMainMode > OBS_MAP_CHASE ) + { + gEngfuncs.Con_Printf("Invalid spectator mode.\n"); + return; + } + + // main modes ettings will override inset window settings + if ( iNewMainMode != g_iUser1 ) + { + // if we are NOT in HLTV mode, main spectator mode is set on server + if ( !gEngfuncs.IsSpectateOnly() ) + { + return; + } + + if ( !g_iUser2 && (iNewMainMode !=OBS_ROAMING ) ) // make sure we have a target + { + // choose last Director object if still available + if ( IsActivePlayer( gEngfuncs.GetEntityByIndex( m_lastPrimaryObject ) ) ) + { + g_iUser2 = m_lastPrimaryObject; + g_iUser3 = m_lastSecondaryObject; + } + else + FindNextPlayer(false); // find any target + } + + switch ( iNewMainMode ) + { + case OBS_CHASE_LOCKED: g_iUser1 = OBS_CHASE_LOCKED; + break; + + case OBS_CHASE_FREE : g_iUser1 = OBS_CHASE_FREE; + break; + + case OBS_ROAMING : // jump to current vJumpOrigin/angle + g_iUser1 = OBS_ROAMING; + if ( g_iUser2 ) + { + V_GetChasePos( g_iUser2, v_cl_angles, vJumpOrigin, vJumpAngles ); + gEngfuncs.SetViewAngles( vJumpAngles ); + iJumpSpectator = 1; + } + break; + + case OBS_IN_EYE : g_iUser1 = OBS_IN_EYE; + break; + + case OBS_MAP_FREE : g_iUser1 = OBS_MAP_FREE; + // reset user values + m_mapZoom = m_OverviewData.zoom; + m_mapOrigin = m_OverviewData.origin; + break; + + case OBS_MAP_CHASE : g_iUser1 = OBS_MAP_CHASE; + // reset user values + m_mapZoom = m_OverviewData.zoom; + m_mapOrigin = m_OverviewData.origin; + break; + } + + if ( (g_iUser1 == OBS_IN_EYE) || (g_iUser1 == OBS_ROAMING) ) + { + m_crosshairRect.left = 24; + m_crosshairRect.top = 0; + m_crosshairRect.right = 48; + m_crosshairRect.bottom = 24; + + SetCrosshair( m_hCrosshair, m_crosshairRect, 255, 255, 255 ); + } + else + { + memset( &m_crosshairRect,0,sizeof(m_crosshairRect) ); + SetCrosshair( 0, m_crosshairRect, 0, 0, 0 ); + } + + char string[128]; + sprintf(string, "#Spec_Mode%d", g_iUser1 ); + sprintf(string, "%c%s", HUD_PRINTCENTER, CHudTextMessage::BufferedLocaliseTextString( string )); + gHUD.m_TextMessage.MsgFunc_TextMsg(NULL, strlen(string)+1, string ); + } + + gViewPort->UpdateSpectatorPanel(); + +} + +bool CHudSpectator::IsActivePlayer(cl_entity_t * ent) +{ + return ( ent && + ent->player && + ent->curstate.solid != SOLID_NOT && + ent != gEngfuncs.GetLocalPlayer() && + g_PlayerInfoList[ent->index].name != NULL + ); +} + + +bool CHudSpectator::ParseOverviewFile( ) +{ + char filename[255]; + char levelname[255]; + char token[1024]; + float height; + + char *pfile = NULL; + + memset( &m_OverviewData, 0, sizeof(m_OverviewData)); + + // fill in standrd values + m_OverviewData.insetWindowX = 4; // upper left corner + m_OverviewData.insetWindowY = 4; + m_OverviewData.insetWindowHeight = 180; + m_OverviewData.insetWindowWidth = 240; + m_OverviewData.origin[0] = 0.0f; + m_OverviewData.origin[1] = 0.0f; + m_OverviewData.origin[2] = 0.0f; + m_OverviewData.zoom = 1.0f; + m_OverviewData.layers = 0; + m_OverviewData.layersHeights[0] = 0.0f; + strcpy( m_OverviewData.map, gEngfuncs.pfnGetLevelName() ); + + if ( strlen( m_OverviewData.map ) == 0 ) + return false; // not active yet + + strcpy(levelname, m_OverviewData.map + 5); + levelname[strlen(levelname)-4] = 0; + + sprintf(filename, "overviews/%s.txt", levelname ); + + pfile = (char *)gEngfuncs.COM_LoadFile( filename, 5, NULL); + + if (!pfile) + { + gEngfuncs.Con_Printf("Couldn't open file %s. Using default values for overiew mode.\n", filename ); + return false; + } + + + while (true) + { + pfile = gEngfuncs.COM_ParseFile(pfile, token); + + if (!pfile) + break; + + if ( !stricmp( token, "global" ) ) + { + // parse the global data + pfile = gEngfuncs.COM_ParseFile(pfile, token); + if ( stricmp( token, "{" ) ) + { + gEngfuncs.Con_Printf("Error parsing overview file %s. (expected { )\n", filename ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); + + while (stricmp( token, "}") ) + { + if ( !stricmp( token, "zoom" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.zoom = atof( token ); + } + else if ( !stricmp( token, "origin" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile, token); + m_OverviewData.origin[0] = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.origin[1] = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile, token); + m_OverviewData.origin[2] = atof( token ); + } + else if ( !stricmp( token, "rotated" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.rotated = atoi( token ); + } + else if ( !stricmp( token, "inset" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.insetWindowX = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.insetWindowY = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.insetWindowWidth = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.insetWindowHeight = atof( token ); + + } + else + { + gEngfuncs.Con_Printf("Error parsing overview file %s. (%s unkown)\n", filename, token ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); // parse next token + + } + } + else if ( !stricmp( token, "layer" ) ) + { + // parse a layer data + + if ( m_OverviewData.layers == OVERVIEW_MAX_LAYERS ) + { + gEngfuncs.Con_Printf("Error parsing overview file %s. ( too many layers )\n", filename ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); + + + if ( stricmp( token, "{" ) ) + { + gEngfuncs.Con_Printf("Error parsing overview file %s. (expected { )\n", filename ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); + + while (stricmp( token, "}") ) + { + if ( !stricmp( token, "image" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + strcpy(m_OverviewData.layersImages[ m_OverviewData.layers ], token); + + + } + else if ( !stricmp( token, "height" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + height = atof(token); + m_OverviewData.layersHeights[ m_OverviewData.layers ] = height; + } + else + { + gEngfuncs.Con_Printf("Error parsing overview file %s. (%s unkown)\n", filename, token ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); // parse next token + } + + m_OverviewData.layers++; + + } + } + + gEngfuncs.COM_FreeFile( pfile ); + + m_mapZoom = m_OverviewData.zoom; + m_mapOrigin = m_OverviewData.origin; + + return true; + +} + +void CHudSpectator::LoadMapSprites() +{ + // right now only support for one map layer + if (m_OverviewData.layers > 0 ) + { + m_MapSprite = gEngfuncs.LoadMapSprite( m_OverviewData.layersImages[0] ); + } + else + m_MapSprite = NULL; // the standard "unkown map" sprite will be used instead +} + +void CHudSpectator::DrawOverviewLayer() +{ + float screenaspect, xs, ys, xStep, yStep, x,y,z; + int ix,iy,i,xTiles,yTiles,frame; + + qboolean hasMapImage = m_MapSprite?TRUE:FALSE; + model_t * dummySprite = (struct model_s *)gEngfuncs.GetSpritePointer( m_hsprUnkownMap); + + if ( hasMapImage) + { + i = m_MapSprite->numframes / (4*3); + i = static_cast(sqrt(static_cast(i))); + xTiles = i*4; + yTiles = i*3; + } + else + { + xTiles = 8; + yTiles = 6; + } + + + screenaspect = 4.0f/3.0f; + + + xs = m_OverviewData.origin[0]; + ys = m_OverviewData.origin[1]; + z = ( 90.0f - v_angles[0] ) / 90.0f; + z *= m_OverviewData.layersHeights[0]; // gOverviewData.z_min - 32; + + // i = r_overviewTexture + ( layer*OVERVIEW_X_TILES*OVERVIEW_Y_TILES ); + + gEngfuncs.pTriAPI->RenderMode( kRenderTransTexture ); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); + gEngfuncs.pTriAPI->Color4f( 1.0, 1.0, 1.0, 1.0 ); + + frame = 0; + + + // rotated view ? + if ( m_OverviewData.rotated ) + { + xStep = (2*4096.0f / m_OverviewData.zoom ) / xTiles; + yStep = -(2*4096.0f / (m_OverviewData.zoom* screenaspect) ) / yTiles; + + y = ys + (4096.0f / (m_OverviewData.zoom * screenaspect)); + + for (iy = 0; iy < yTiles; iy++) + { + x = xs - (4096.0f / (m_OverviewData.zoom)); + + for (ix = 0; ix < xTiles; ix++) + { + if (hasMapImage) + gEngfuncs.pTriAPI->SpriteTexture( m_MapSprite, frame ); + else + gEngfuncs.pTriAPI->SpriteTexture( dummySprite, 0 ); + + gEngfuncs.pTriAPI->Begin( TRI_QUADS ); + gEngfuncs.pTriAPI->TexCoord2f( 0, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x, y, z); + + gEngfuncs.pTriAPI->TexCoord2f( 1, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x+xStep ,y, z); + + gEngfuncs.pTriAPI->TexCoord2f( 1, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x+xStep, y+yStep, z); + + gEngfuncs.pTriAPI->TexCoord2f( 0, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x, y+yStep, z); + gEngfuncs.pTriAPI->End(); + + frame++; + x+= xStep; + } + + y+=yStep; + } + } + else + { + xStep = -(2*4096.0f / m_OverviewData.zoom ) / xTiles; + yStep = -(2*4096.0f / (m_OverviewData.zoom* screenaspect) ) / yTiles; + + + x = xs + (4096.0f / (m_OverviewData.zoom * screenaspect )); + + + + for (ix = 0; ix < yTiles; ix++) + { + + y = ys + (4096.0f / (m_OverviewData.zoom)); + + for (iy = 0; iy < xTiles; iy++) + { + if (hasMapImage) + gEngfuncs.pTriAPI->SpriteTexture( m_MapSprite, frame ); + else + gEngfuncs.pTriAPI->SpriteTexture( dummySprite, 0 ); + + gEngfuncs.pTriAPI->Begin( TRI_QUADS ); + gEngfuncs.pTriAPI->TexCoord2f( 0, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x, y, z); + + gEngfuncs.pTriAPI->TexCoord2f( 0, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x+xStep ,y, z); + + gEngfuncs.pTriAPI->TexCoord2f( 1, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x+xStep, y+yStep, z); + + gEngfuncs.pTriAPI->TexCoord2f( 1, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x, y+yStep, z); + gEngfuncs.pTriAPI->End(); + + frame++; + + y+=yStep; + } + + x+= xStep; + + } + } +} + +void CHudSpectator::DrawOverviewEntities() +{ + int i,ir,ig,ib; + struct model_s *hSpriteModel; + vec3_t origin, angles, point, forward, right, left, up, world, screen, offset; + float x,y,z, r,g,b, sizeScale = 4.0f; + cl_entity_t * ent; + float rmatrix[3][4]; // transformation matrix + + float zScale = (90.0f - v_angles[0] ) / 90.0f; + + + z = m_OverviewData.layersHeights[0] * zScale; + // get yellow/brown HUD color + UnpackRGB(ir,ig,ib, RGB_YELLOWISH); + r = (float)ir/255.0f; + g = (float)ig/255.0f; + b = (float)ib/255.0f; + + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); + + for (i=0; i < MAX_PLAYERS; i++ ) + m_vPlayerPos[i][2] = -1; // mark as invisible + + // draw all players + for (i=0 ; i < MAX_OVERVIEW_ENTITIES ; i++) + { + if ( !m_OverviewEntities[i].hSprite ) + continue; + + hSpriteModel = (struct model_s *)gEngfuncs.GetSpritePointer( m_OverviewEntities[i].hSprite ); + ent = m_OverviewEntities[i].entity; + + gEngfuncs.pTriAPI->SpriteTexture( hSpriteModel, 0 ); + gEngfuncs.pTriAPI->RenderMode( kRenderTransTexture ); + + // see R_DrawSpriteModel + // draws players sprite + + AngleVectors(ent->angles, right, up, NULL ); + + VectorCopy(ent->origin,origin); + + gEngfuncs.pTriAPI->Begin( TRI_QUADS ); + + gEngfuncs.pTriAPI->Color4f( 1.0, 1.0, 1.0, 1.0 ); + + gEngfuncs.pTriAPI->TexCoord2f (1, 0); + VectorMA (origin, 16.0f * sizeScale, up, point); + VectorMA (point, 16.0f * sizeScale, right, point); + point[2] *= zScale; + gEngfuncs.pTriAPI->Vertex3fv (point); + + gEngfuncs.pTriAPI->TexCoord2f (0, 0); + + VectorMA (origin, 16.0f * sizeScale, up, point); + VectorMA (point, -16.0f * sizeScale, right, point); + point[2] *= zScale; + gEngfuncs.pTriAPI->Vertex3fv (point); + + gEngfuncs.pTriAPI->TexCoord2f (0,1); + VectorMA (origin, -16.0f * sizeScale, up, point); + VectorMA (point, -16.0f * sizeScale, right, point); + point[2] *= zScale; + gEngfuncs.pTriAPI->Vertex3fv (point); + + gEngfuncs.pTriAPI->TexCoord2f (1,1); + VectorMA (origin, -16.0f * sizeScale, up, point); + VectorMA (point, 16.0f * sizeScale, right, point); + point[2] *= zScale; + gEngfuncs.pTriAPI->Vertex3fv (point); + + gEngfuncs.pTriAPI->End (); + + + if ( !ent->player) + continue; + // draw line under player icons + origin[2] *= zScale; + + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + + hSpriteModel = (struct model_s *)gEngfuncs.GetSpritePointer( m_hsprBeam ); + gEngfuncs.pTriAPI->SpriteTexture( hSpriteModel, 0 ); + + gEngfuncs.pTriAPI->Color4f(r, g, b, 0.3); + + gEngfuncs.pTriAPI->Begin ( TRI_QUADS ); + gEngfuncs.pTriAPI->TexCoord2f (1, 0); + gEngfuncs.pTriAPI->Vertex3f (origin[0]+4, origin[1]+4, origin[2]-zScale); + gEngfuncs.pTriAPI->TexCoord2f (0, 0); + gEngfuncs.pTriAPI->Vertex3f (origin[0]-4, origin[1]-4, origin[2]-zScale); + gEngfuncs.pTriAPI->TexCoord2f (0, 1); + gEngfuncs.pTriAPI->Vertex3f (origin[0]-4, origin[1]-4,z); + gEngfuncs.pTriAPI->TexCoord2f (1, 1); + gEngfuncs.pTriAPI->Vertex3f (origin[0]+4, origin[1]+4,z); + gEngfuncs.pTriAPI->End (); + + gEngfuncs.pTriAPI->Begin ( TRI_QUADS ); + gEngfuncs.pTriAPI->TexCoord2f (1, 0); + gEngfuncs.pTriAPI->Vertex3f (origin[0]-4, origin[1]+4, origin[2]-zScale); + gEngfuncs.pTriAPI->TexCoord2f (0, 0); + gEngfuncs.pTriAPI->Vertex3f (origin[0]+4, origin[1]-4, origin[2]-zScale); + gEngfuncs.pTriAPI->TexCoord2f (0, 1); + gEngfuncs.pTriAPI->Vertex3f (origin[0]+4, origin[1]-4,z); + gEngfuncs.pTriAPI->TexCoord2f (1, 1); + gEngfuncs.pTriAPI->Vertex3f (origin[0]-4, origin[1]+4,z); + gEngfuncs.pTriAPI->End (); + + // calculate screen position for name and infromation in hud::draw() + if ( gEngfuncs.pTriAPI->WorldToScreen(origin,screen) ) + continue; // object is behind viewer + + screen[0] = XPROJECT(screen[0]); + screen[1] = YPROJECT(screen[1]); + screen[2] = 0.0f; + + // calculate some offset under the icon + origin[0]+=32.0f; + origin[1]+=32.0f; + + gEngfuncs.pTriAPI->WorldToScreen(origin,offset); + + offset[0] = XPROJECT(offset[0]); + offset[1] = YPROJECT(offset[1]); + offset[2] = 0.0f; + + VectorSubtract(offset, screen, offset ); + + int playerNum = ent->index - 1; + + m_vPlayerPos[playerNum][0] = screen[0]; + m_vPlayerPos[playerNum][1] = screen[1] + Length(offset); + m_vPlayerPos[playerNum][2] = 1; // mark player as visible + } + + if ( !m_pip->value || !m_drawcone->value ) + return; + + // get current camera position and angle + + if ( m_pip->value == INSET_IN_EYE || g_iUser1 == OBS_IN_EYE ) + { + V_GetInEyePos( g_iUser2, origin, angles ); + } + else if ( m_pip->value == INSET_CHASE_FREE || g_iUser1 == OBS_CHASE_FREE ) + { + V_GetChasePos( g_iUser2, v_cl_angles, origin, angles ); + } + else if ( g_iUser1 == OBS_ROAMING ) + { + VectorCopy( v_sim_org, origin ); + VectorCopy( v_cl_angles, angles ); + } + else + V_GetChasePos( g_iUser2, NULL, origin, angles ); + + + // draw camera sprite + + x = origin[0]; + y = origin[1]; + z = origin[2]; + + angles[0] = 0; // always show horizontal camera sprite + + hSpriteModel = (struct model_s *)gEngfuncs.GetSpritePointer( m_hsprCamera ); + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + gEngfuncs.pTriAPI->SpriteTexture( hSpriteModel, 0 ); + + + gEngfuncs.pTriAPI->Color4f( r, g, b, 1.0 ); + + AngleVectors(angles, forward, NULL, NULL ); + VectorScale (forward, 512.0f, forward); + + offset[0] = 0.0f; + offset[1] = 45.0f; + offset[2] = 0.0f; + + AngleMatrix(offset, rmatrix ); + VectorTransform(forward, rmatrix , right ); + + offset[1]= -45.0f; + AngleMatrix(offset, rmatrix ); + VectorTransform(forward, rmatrix , left ); + + gEngfuncs.pTriAPI->Begin (TRI_TRIANGLES); + gEngfuncs.pTriAPI->TexCoord2f( 0, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x+right[0], y+right[1], (z+right[2]) * zScale); + + gEngfuncs.pTriAPI->TexCoord2f( 0, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x, y, z * zScale); + + gEngfuncs.pTriAPI->TexCoord2f( 1, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x+left[0], y+left[1], (z+left[2]) * zScale); + gEngfuncs.pTriAPI->End (); + +} + + + +void CHudSpectator::DrawOverview() +{ + // draw only in sepctator mode + if ( !g_iUser1 ) + return; + + // Only draw the overview if Map Mode is selected for this view + if ( m_iDrawCycle == 0 && ( (g_iUser1 != OBS_MAP_FREE) && (g_iUser1 != OBS_MAP_CHASE) ) ) + return; + + if ( m_iDrawCycle == 1 && m_pip->value < INSET_MAP_FREE ) + return; + + DrawOverviewLayer(); + DrawOverviewEntities(); + CheckOverviewEntities(); +} +void CHudSpectator::CheckOverviewEntities() +{ + double time = gEngfuncs.GetClientTime(); + + // removes old entities from list + for ( int i = 0; i< MAX_OVERVIEW_ENTITIES; i++ ) + { + // remove entity from list if it is too old + if ( m_OverviewEntities[i].killTime < time ) + { + memset( &m_OverviewEntities[i], 0, sizeof (overviewEntity_t) ); + } + } +} + +bool CHudSpectator::AddOverviewEntity( int type, struct cl_entity_s *ent, const char *modelname) +{ + HSPRITE hSprite = 0; + double duration = -1.0f; // duration -1 means show it only this frame; + + if ( !ent ) + return false; + + if ( type == ET_PLAYER ) + { + if ( ent->curstate.solid != SOLID_NOT) + { + switch ( g_PlayerExtraInfo[ent->index].teamnumber ) + { + // blue and red teams are swapped in CS and TFC + case 1 : hSprite = m_hsprPlayerBlue; break; + case 2 : hSprite = m_hsprPlayerRed; break; + default : hSprite = m_hsprPlayer; break; + } + } + else + return false; // it's an spectator + } + else if (type == ET_NORMAL) + { + return false; + } + else + return false; + + return AddOverviewEntityToList(hSprite, ent, gEngfuncs.GetClientTime() + duration ); +} + +void CHudSpectator::DeathMessage(int victim) +{ + // find out where the victim is + cl_entity_t *pl = gEngfuncs.GetEntityByIndex(victim); + + if (pl && pl->player) + AddOverviewEntityToList(m_hsprPlayerDead, pl, gEngfuncs.GetClientTime() + 2.0f ); +} + +bool CHudSpectator::AddOverviewEntityToList(HSPRITE sprite, cl_entity_t *ent, double killTime) +{ + for ( int i = 0; i< MAX_OVERVIEW_ENTITIES; i++ ) + { + // find empty entity slot + if ( m_OverviewEntities[i].entity == NULL) + { + m_OverviewEntities[i].entity = ent; + m_OverviewEntities[i].hSprite = sprite; + m_OverviewEntities[i].killTime = killTime; + return true; + } + } + + return false; // maximum overview entities reached +} +void CHudSpectator::CheckSettings() +{ + // disallow same inset mode as main mode: + + m_pip->value = (int)m_pip->value; + + if ( ( g_iUser1 < OBS_MAP_FREE ) && ( m_pip->value == INSET_CHASE_FREE || m_pip->value == INSET_IN_EYE ) ) + { + // otherwise both would show in World picures + m_pip->value = INSET_MAP_FREE; + } + + if ( ( g_iUser1 >= OBS_MAP_FREE ) && ( m_pip->value >= INSET_MAP_FREE ) ) + { + // both would show map views + m_pip->value = INSET_CHASE_FREE; + } + + // disble in intermission screen + if ( gHUD.m_iIntermission ) + m_pip->value = INSET_OFF; + + // check chat mode + if ( m_chatEnabled != (gHUD.m_SayText.m_HUD_saytext->value!=0) ) + { + // hud_saytext changed + m_chatEnabled = (gHUD.m_SayText.m_HUD_saytext->value!=0); + + if ( gEngfuncs.IsSpectateOnly() ) + { + // tell proxy our new chat mode + char chatcmd[32]; + sprintf(chatcmd, "ignoremsg %i", m_chatEnabled?0:1 ); + gEngfuncs.pfnServerCmd(chatcmd); + } + } + + // HL/TFC has no oberserver corsshair, so set it client side + if ( (g_iUser1 == OBS_IN_EYE) || (g_iUser1 == OBS_ROAMING) ) + { + m_crosshairRect.left = 24; + m_crosshairRect.top = 0; + m_crosshairRect.right = 48; + m_crosshairRect.bottom = 24; + + SetCrosshair( m_hCrosshair, m_crosshairRect, 255, 255, 255 ); + } + else + { + memset( &m_crosshairRect,0,sizeof(m_crosshairRect) ); + SetCrosshair( 0, m_crosshairRect, 0, 0, 0 ); + } + + + + // if we are a real player on server don't allow inset window + // in First Person mode since this is our resticted forcecamera mode 2 + // team number 3 = SPECTATOR see player.h + + if ( ( (g_iTeamNumber == 1) || (g_iTeamNumber == 2)) && (g_iUser1 == OBS_IN_EYE) ) + m_pip->value = INSET_OFF; + + // draw small border around inset view, adjust upper black bar + gViewPort->m_pSpectatorPanel->EnableInsetView( m_pip->value != INSET_OFF ); +} + +int CHudSpectator::ToggleInset(bool allowOff) +{ + int newInsetMode = (int)m_pip->value + 1; + + if ( g_iUser1 < OBS_MAP_FREE ) + { + if ( newInsetMode > INSET_MAP_CHASE ) + { + if (allowOff) + newInsetMode = INSET_OFF; + else + newInsetMode = INSET_MAP_FREE; + } + + if ( newInsetMode == INSET_CHASE_FREE ) + newInsetMode = INSET_MAP_FREE; + } + else + { + if ( newInsetMode > INSET_IN_EYE ) + { + if (allowOff) + newInsetMode = INSET_OFF; + else + newInsetMode = INSET_CHASE_FREE; + } + } + + return newInsetMode; +} +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)); + + if ( gEngfuncs.IsSpectateOnly() || gEngfuncs.pDemoAPI->IsPlayingback() ) + m_autoDirector->value = 1.0f; + else + m_autoDirector->value = 0.0f; + + Reset(); + + SetModes( OBS_CHASE_FREE, INSET_OFF ); + + g_iUser2 = 0; // fake not target until first camera command + + // reset HUD FOV + gHUD.m_iFOV = CVAR_GET_FLOAT("default_fov"); +} + diff --git a/hlsdk/cl_dll/hud_spectator.h b/hlsdk/cl_dll/hud_spectator.h new file mode 100644 index 00000000..7f1a3610 --- /dev/null +++ b/hlsdk/cl_dll/hud_spectator.h @@ -0,0 +1,132 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef SPECTATOR_H +#define SPECTATOR_H +#pragma once + +#include "cl_entity.h" + + + +#define INSET_OFF 0 +#define INSET_CHASE_FREE 1 +#define INSET_IN_EYE 2 +#define INSET_MAP_FREE 3 +#define INSET_MAP_CHASE 4 + +#define MAX_SPEC_HUD_MESSAGES 8 + + + +#define OVERVIEW_TILE_SIZE 128 // don't change this +#define OVERVIEW_MAX_LAYERS 1 + +//----------------------------------------------------------------------------- +// Purpose: Handles the drawing of the spectator stuff (camera & top-down map and all the things on it ) +//----------------------------------------------------------------------------- + +typedef struct overviewInfo_s { + char map[64]; // cl.levelname or empty + vec3_t origin; // center of map + float zoom; // zoom of map images + int layers; // how may layers do we have + float layersHeights[OVERVIEW_MAX_LAYERS]; + char layersImages[OVERVIEW_MAX_LAYERS][255]; + qboolean rotated; // are map images rotated (90 degrees) ? + + int insetWindowX; + int insetWindowY; + int insetWindowHeight; + int insetWindowWidth; +} overviewInfo_t; + +typedef struct overviewEntity_s { + + HSPRITE hSprite; + struct cl_entity_s * entity; + double killTime; +} overviewEntity_t; + +#define MAX_OVERVIEW_ENTITIES 128 + +class CHudSpectator : public CHudBase +{ +public: + void Reset(); + int ToggleInset(bool allowOff); + void CheckSettings(); + void InitHUDData( void ); + bool AddOverviewEntityToList( HSPRITE sprite, cl_entity_t * ent, double killTime); + void DeathMessage(int victim); + bool AddOverviewEntity( int type, struct cl_entity_s *ent, const char *modelname ); + void CheckOverviewEntities(); + void DrawOverview(); + void DrawOverviewEntities(); + void GetMapPosition( float * returnvec ); + void DrawOverviewLayer(); + void LoadMapSprites(); + bool ParseOverviewFile(); + bool IsActivePlayer(cl_entity_t * ent); + void SetModes(int iMainMode, int iInsetMode); + void HandleButtonsDown(int ButtonPressed); + void HandleButtonsUp(int ButtonPressed); + void FindNextPlayer( bool bReverse ); + void DirectorMessage( int iSize, void *pbuf ); + void SetSpectatorStartPosition(); + int Init(); + int VidInit(); + + int Draw(float flTime); + + int m_iDrawCycle; + client_textmessage_t m_HUDMessages[MAX_SPEC_HUD_MESSAGES]; + char m_HUDMessageText[MAX_SPEC_HUD_MESSAGES][128]; + int m_lastHudMessage; + overviewInfo_t m_OverviewData; + overviewEntity_t m_OverviewEntities[MAX_OVERVIEW_ENTITIES]; + int m_iObserverFlags; + int m_iSpectatorNumber; + + float m_mapZoom; // zoom the user currently uses + vec3_t m_mapOrigin; // origin where user rotates around + cvar_t * m_drawnames; + cvar_t * m_drawcone; + cvar_t * m_drawstatus; + cvar_t * m_autoDirector; + cvar_t * m_pip; + + + qboolean m_chatEnabled; + + vec3_t m_cameraOrigin; // a help camera + vec3_t m_cameraAngles; // and it's angles + + +private: + vec3_t m_vPlayerPos[MAX_PLAYERS]; + HSPRITE m_hsprPlayerBlue; + HSPRITE m_hsprPlayerRed; + HSPRITE m_hsprPlayer; + HSPRITE m_hsprCamera; + HSPRITE m_hsprPlayerDead; + HSPRITE m_hsprViewcone; + HSPRITE m_hsprUnkownMap; + HSPRITE m_hsprBeam; + HSPRITE m_hCrosshair; + + wrect_t m_crosshairRect; + + struct model_s * m_MapSprite; // each layer image is saved in one sprite, where each tile is a sprite frame + float m_flNextObserverInput; + float m_zoomDelta; + float m_moveDelta; + int m_lastPrimaryObject; + int m_lastSecondaryObject; +}; + +#endif // SPECTATOR_H diff --git a/hlsdk/cl_dll/hud_update.cpp b/hlsdk/cl_dll/hud_update.cpp new file mode 100644 index 00000000..b3da6721 --- /dev/null +++ b/hlsdk/cl_dll/hud_update.cpp @@ -0,0 +1,54 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud_update.cpp +// + +#include +#include "hud.h" +#include "cl_util.h" +#include +#include + +int CL_ButtonBits( int ); +void CL_ResetButtonBits( int bits ); + +extern float v_idlescale; +float in_fov; +extern void HUD_SetCmdBits( int bits ); + +int CHud::UpdateClientData(client_data_t *cdata, float time) +{ + memcpy(m_vecOrigin, cdata->origin, sizeof(vec3_t)); + memcpy(m_vecAngles, cdata->viewangles, sizeof(vec3_t)); + + m_iKeyBits = CL_ButtonBits( 0 ); + m_iWeaponBits = cdata->iWeaponBits; + + in_fov = cdata->fov; + + Think(); + + cdata->fov = m_iFOV; + + v_idlescale = m_iConcussionEffect; + + CL_ResetButtonBits( m_iKeyBits ); + + // return 1 if in anything in the client_data struct has been changed, 0 otherwise + return 1; +} + + diff --git a/hlsdk/cl_dll/in_camera.cpp b/hlsdk/cl_dll/in_camera.cpp new file mode 100644 index 00000000..96dbdc7c --- /dev/null +++ b/hlsdk/cl_dll/in_camera.cpp @@ -0,0 +1,621 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#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 "windows.h" + +float CL_KeyState (kbutton_t *key); + +extern "C" +{ + void DLLEXPORT CAM_Think( void ); + int DLLEXPORT CL_IsThirdPerson( void ); + void DLLEXPORT CL_CameraOffset( float *ofs ); +} + +extern cl_enginefunc_t gEngfuncs; + +//-------------------------------------------------- Constants + +#define CAM_DIST_DELTA 1.0 +#define CAM_ANGLE_DELTA 2.5 +#define CAM_ANGLE_SPEED 2.5 +#define CAM_MIN_DIST 30.0 +#define CAM_ANGLE_MOVE .5 +#define MAX_ANGLE_DIFF 10.0 +#define PITCH_MAX 90.0 +#define PITCH_MIN 0 +#define YAW_MAX 135.0 +#define YAW_MIN -135.0 + +enum ECAM_Command +{ + CAM_COMMAND_NONE = 0, + CAM_COMMAND_TOTHIRDPERSON = 1, + CAM_COMMAND_TOFIRSTPERSON = 2 +}; + +//-------------------------------------------------- Global Variables + +cvar_t *cam_command; +cvar_t *cam_snapto; +cvar_t *cam_idealyaw; +cvar_t *cam_idealpitch; +cvar_t *cam_idealdist; +cvar_t *cam_contain; + +cvar_t *c_maxpitch; +cvar_t *c_minpitch; +cvar_t *c_maxyaw; +cvar_t *c_minyaw; +cvar_t *c_maxdistance; +cvar_t *c_mindistance; + +// pitch, yaw, dist +vec3_t cam_ofs; + + +// In third person +int cam_thirdperson; +int cam_mousemove; //true if we are moving the cam with the mouse, False if not +int iMouseInUse=0; +int cam_distancemove; +extern int mouse_x, mouse_y; //used to determine what the current x and y values are +int cam_old_mouse_x, cam_old_mouse_y; //holds the last ticks mouse movement +POINT cam_mouse; +//-------------------------------------------------- Local Variables + +static kbutton_t cam_pitchup, cam_pitchdown, cam_yawleft, cam_yawright; +static kbutton_t cam_in, cam_out, cam_move; + +//-------------------------------------------------- Prototypes + +void CAM_ToThirdPerson(void); +void CAM_ToFirstPerson(void); +void CAM_StartDistance(void); +void CAM_EndDistance(void); + + +//-------------------------------------------------- Local Functions + +float MoveToward( float cur, float goal, float maxspeed ) +{ + if( cur != goal ) + { + if( abs( cur - goal ) > 180.0 ) + { + if( cur < goal ) + cur += 360.0; + else + cur -= 360.0; + } + + if( cur < goal ) + { + if( cur < goal - 1.0 ) + cur += ( goal - cur ) / 4.0; + else + cur = goal; + } + else + { + if( cur > goal + 1.0 ) + cur -= ( cur - goal ) / 4.0; + else + cur = goal; + } + } + + + // bring cur back into range + if( cur < 0 ) + cur += 360.0; + else if( cur >= 360 ) + cur -= 360; + + return cur; +} + + +//-------------------------------------------------- Gobal Functions + +typedef struct +{ + vec3_t boxmins, boxmaxs;// enclose the test object along entire move + float *mins, *maxs; // size of the moving object + vec3_t mins2, maxs2; // size when clipping against mosnters + float *start, *end; + trace_t trace; + int type; + edict_t *passedict; + qboolean monsterclip; +} moveclip_t; + +extern trace_t SV_ClipMoveToEntity (edict_t *ent, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end); + +void DLLEXPORT CAM_Think( void ) +{ + vec3_t origin; + vec3_t ext, pnt, camForward, camRight, camUp; + moveclip_t clip; + float dist; + vec3_t camAngles; + float flSensitivity; +#ifdef LATER + int i; +#endif + vec3_t viewangles; + + switch( (int) cam_command->value ) + { + case CAM_COMMAND_TOTHIRDPERSON: + CAM_ToThirdPerson(); + break; + + case CAM_COMMAND_TOFIRSTPERSON: + CAM_ToFirstPerson(); + break; + + case CAM_COMMAND_NONE: + default: + break; + } + + if( !cam_thirdperson ) + return; + +#ifdef LATER + if ( cam_contain->value ) + { + gEngfuncs.GetClientOrigin( origin ); + ext[0] = ext[1] = ext[2] = 0.0; + } +#endif + + camAngles[ PITCH ] = cam_idealpitch->value; + camAngles[ YAW ] = cam_idealyaw->value; + dist = cam_idealdist->value; + // + //movement of the camera with the mouse + // + if (cam_mousemove) + { + //get windows cursor position + GetCursorPos (&cam_mouse); + //check for X delta values and adjust accordingly + //eventually adjust YAW based on amount of movement + //don't do any movement of the cam using YAW/PITCH if we are zooming in/out the camera + if (!cam_distancemove) + { + + //keep the camera within certain limits around the player (ie avoid certain bad viewing angles) + if (cam_mouse.x>gEngfuncs.GetWindowCenterX()) + { + //if ((camAngles[YAW]>=225.0)||(camAngles[YAW]<135.0)) + if (camAngles[YAW]value) + { + camAngles[ YAW ] += (CAM_ANGLE_MOVE)*((cam_mouse.x-gEngfuncs.GetWindowCenterX())/2); + } + if (camAngles[YAW]>c_maxyaw->value) + { + + camAngles[YAW]=c_maxyaw->value; + } + } + else if (cam_mouse.x225.0)) + if (camAngles[YAW]>c_minyaw->value) + { + camAngles[ YAW ] -= (CAM_ANGLE_MOVE)* ((gEngfuncs.GetWindowCenterX()-cam_mouse.x)/2); + + } + if (camAngles[YAW]value) + { + camAngles[YAW]=c_minyaw->value; + + } + } + + //check for y delta values and adjust accordingly + //eventually adjust PITCH based on amount of movement + //also make sure camera is within bounds + if (cam_mouse.y>gEngfuncs.GetWindowCenterY()) + { + if(camAngles[PITCH]value) + { + camAngles[PITCH] +=(CAM_ANGLE_MOVE)* ((cam_mouse.y-gEngfuncs.GetWindowCenterY())/2); + } + if (camAngles[PITCH]>c_maxpitch->value) + { + camAngles[PITCH]=c_maxpitch->value; + } + } + else if (cam_mouse.yc_minpitch->value) + { + camAngles[PITCH] -= (CAM_ANGLE_MOVE)*((gEngfuncs.GetWindowCenterY()-cam_mouse.y)/2); + } + if (camAngles[PITCH]value) + { + camAngles[PITCH]=c_minpitch->value; + } + } + + //set old mouse coordinates to current mouse coordinates + //since we are done with the mouse + + if ( ( flSensitivity = gHUD.GetSensitivity() ) != 0 ) + { + cam_old_mouse_x=cam_mouse.x*flSensitivity; + cam_old_mouse_y=cam_mouse.y*flSensitivity; + } + else + { + cam_old_mouse_x=cam_mouse.x; + cam_old_mouse_y=cam_mouse.y; + } + SetCursorPos (gEngfuncs.GetWindowCenterX(), gEngfuncs.GetWindowCenterY()); + } + } + + //Nathan code here + if( CL_KeyState( &cam_pitchup ) ) + camAngles[ PITCH ] += CAM_ANGLE_DELTA; + else if( CL_KeyState( &cam_pitchdown ) ) + camAngles[ PITCH ] -= CAM_ANGLE_DELTA; + + if( CL_KeyState( &cam_yawleft ) ) + camAngles[ YAW ] -= CAM_ANGLE_DELTA; + else if( CL_KeyState( &cam_yawright ) ) + camAngles[ YAW ] += CAM_ANGLE_DELTA; + + if( CL_KeyState( &cam_in ) ) + { + dist -= CAM_DIST_DELTA; + if( dist < CAM_MIN_DIST ) + { + // If we go back into first person, reset the angle + camAngles[ PITCH ] = 0; + camAngles[ YAW ] = 0; + dist = CAM_MIN_DIST; + } + + } + else if( CL_KeyState( &cam_out ) ) + dist += CAM_DIST_DELTA; + + if (cam_distancemove) + { + if (cam_mouse.y>gEngfuncs.GetWindowCenterY()) + { + if(distvalue) + { + dist +=CAM_DIST_DELTA * ((cam_mouse.y-gEngfuncs.GetWindowCenterY())/2); + } + if (dist>c_maxdistance->value) + { + dist=c_maxdistance->value; + } + } + else if (cam_mouse.yc_mindistance->value) + { + dist -= (CAM_DIST_DELTA)*((gEngfuncs.GetWindowCenterY()-cam_mouse.y)/2); + } + if (distvalue) + { + dist=c_mindistance->value; + } + } + //set old mouse coordinates to current mouse coordinates + //since we are done with the mouse + cam_old_mouse_x=cam_mouse.x*gHUD.GetSensitivity(); + cam_old_mouse_y=cam_mouse.y*gHUD.GetSensitivity(); + SetCursorPos (gEngfuncs.GetWindowCenterX(), gEngfuncs.GetWindowCenterY()); + } +#ifdef LATER + if( cam_contain->value ) + { + // check new ideal + VectorCopy( origin, pnt ); + AngleVectors( camAngles, camForward, camRight, camUp ); + for (i=0 ; i<3 ; i++) + pnt[i] += -dist*camForward[i]; + + // check line from r_refdef.vieworg to pnt + memset ( &clip, 0, sizeof ( moveclip_t ) ); + clip.trace = SV_ClipMoveToEntity( sv.edicts, r_refdef.vieworg, ext, ext, pnt ); + if( clip.trace.fraction == 1.0 ) + { + // update ideal + cam_idealpitch->value = camAngles[ PITCH ]; + cam_idealyaw->value = camAngles[ YAW ]; + cam_idealdist->value = dist; + } + } + else +#endif + { + // update ideal + cam_idealpitch->value = camAngles[ PITCH ]; + cam_idealyaw->value = camAngles[ YAW ]; + cam_idealdist->value = dist; + } + + // Move towards ideal + VectorCopy( cam_ofs, camAngles ); + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + if( cam_snapto->value ) + { + camAngles[ YAW ] = cam_idealyaw->value + viewangles[ YAW ]; + camAngles[ PITCH ] = cam_idealpitch->value + viewangles[ PITCH ]; + camAngles[ 2 ] = cam_idealdist->value; + } + else + { + if( camAngles[ YAW ] - viewangles[ YAW ] != cam_idealyaw->value ) + camAngles[ YAW ] = MoveToward( camAngles[ YAW ], cam_idealyaw->value + viewangles[ YAW ], CAM_ANGLE_SPEED ); + + if( camAngles[ PITCH ] - viewangles[ PITCH ] != cam_idealpitch->value ) + camAngles[ PITCH ] = MoveToward( camAngles[ PITCH ], cam_idealpitch->value + viewangles[ PITCH ], CAM_ANGLE_SPEED ); + + if( abs( camAngles[ 2 ] - cam_idealdist->value ) < 2.0 ) + camAngles[ 2 ] = cam_idealdist->value; + else + camAngles[ 2 ] += ( cam_idealdist->value - camAngles[ 2 ] ) / 4.0; + } +#ifdef LATER + if( cam_contain->value ) + { + // Test new position + dist = camAngles[ ROLL ]; + camAngles[ ROLL ] = 0; + + VectorCopy( origin, pnt ); + AngleVectors( camAngles, camForward, camRight, camUp ); + for (i=0 ; i<3 ; i++) + pnt[i] += -dist*camForward[i]; + + // check line from r_refdef.vieworg to pnt + memset ( &clip, 0, sizeof ( moveclip_t ) ); + ext[0] = ext[1] = ext[2] = 0.0; + clip.trace = SV_ClipMoveToEntity( sv.edicts, r_refdef.vieworg, ext, ext, pnt ); + if( clip.trace.fraction != 1.0 ) + return; + } +#endif + cam_ofs[ 0 ] = camAngles[ 0 ]; + cam_ofs[ 1 ] = camAngles[ 1 ]; + cam_ofs[ 2 ] = dist; +} + +extern void KeyDown (kbutton_t *b); // HACK +extern void KeyUp (kbutton_t *b); // HACK + +void CAM_PitchUpDown(void) { KeyDown( &cam_pitchup ); } +void CAM_PitchUpUp(void) { KeyUp( &cam_pitchup ); } +void CAM_PitchDownDown(void) { KeyDown( &cam_pitchdown ); } +void CAM_PitchDownUp(void) { KeyUp( &cam_pitchdown ); } +void CAM_YawLeftDown(void) { KeyDown( &cam_yawleft ); } +void CAM_YawLeftUp(void) { KeyUp( &cam_yawleft ); } +void CAM_YawRightDown(void) { KeyDown( &cam_yawright ); } +void CAM_YawRightUp(void) { KeyUp( &cam_yawright ); } +void CAM_InDown(void) { KeyDown( &cam_in ); } +void CAM_InUp(void) { KeyUp( &cam_in ); } +void CAM_OutDown(void) { KeyDown( &cam_out ); } +void CAM_OutUp(void) { KeyUp( &cam_out ); } + +void CAM_ToThirdPerson(void) +{ + vec3_t viewangles; + +#if !defined( _DEBUG ) + if ( gEngfuncs.GetMaxClients() > 1 ) + { + // no thirdperson in multiplayer. + return; + } +#endif + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + if( !cam_thirdperson ) + { + cam_thirdperson = 1; + + cam_ofs[ YAW ] = viewangles[ YAW ]; + cam_ofs[ PITCH ] = viewangles[ PITCH ]; + cam_ofs[ 2 ] = CAM_MIN_DIST; + } + + gEngfuncs.Cvar_SetValue( "cam_command", 0 ); +} + +void CAM_ToFirstPerson(void) +{ + cam_thirdperson = 0; + + gEngfuncs.Cvar_SetValue( "cam_command", 0 ); +} + +void CAM_ToggleSnapto( void ) +{ + cam_snapto->value = !cam_snapto->value; +} + +void CAM_Init( void ) +{ + gEngfuncs.pfnAddCommand( "+campitchup", CAM_PitchUpDown ); + gEngfuncs.pfnAddCommand( "-campitchup", CAM_PitchUpUp ); + gEngfuncs.pfnAddCommand( "+campitchdown", CAM_PitchDownDown ); + gEngfuncs.pfnAddCommand( "-campitchdown", CAM_PitchDownUp ); + gEngfuncs.pfnAddCommand( "+camyawleft", CAM_YawLeftDown ); + gEngfuncs.pfnAddCommand( "-camyawleft", CAM_YawLeftUp ); + gEngfuncs.pfnAddCommand( "+camyawright", CAM_YawRightDown ); + gEngfuncs.pfnAddCommand( "-camyawright", CAM_YawRightUp ); + gEngfuncs.pfnAddCommand( "+camin", CAM_InDown ); + gEngfuncs.pfnAddCommand( "-camin", CAM_InUp ); + gEngfuncs.pfnAddCommand( "+camout", CAM_OutDown ); + gEngfuncs.pfnAddCommand( "-camout", CAM_OutUp ); + gEngfuncs.pfnAddCommand( "thirdperson", CAM_ToThirdPerson ); + gEngfuncs.pfnAddCommand( "firstperson", CAM_ToFirstPerson ); + gEngfuncs.pfnAddCommand( "+cammousemove",CAM_StartMouseMove); + gEngfuncs.pfnAddCommand( "-cammousemove",CAM_EndMouseMove); + gEngfuncs.pfnAddCommand( "+camdistance", CAM_StartDistance ); + gEngfuncs.pfnAddCommand( "-camdistance", CAM_EndDistance ); + gEngfuncs.pfnAddCommand( "snapto", CAM_ToggleSnapto ); + + cam_command = gEngfuncs.pfnRegisterVariable ( "cam_command", "0", 0 ); // tells camera to go to thirdperson + cam_snapto = gEngfuncs.pfnRegisterVariable ( "cam_snapto", "0", 0 ); // snap to thirdperson view + cam_idealyaw = gEngfuncs.pfnRegisterVariable ( "cam_idealyaw", "90", 0 ); // thirdperson yaw + cam_idealpitch = gEngfuncs.pfnRegisterVariable ( "cam_idealpitch", "0", 0 ); // thirperson pitch + cam_idealdist = gEngfuncs.pfnRegisterVariable ( "cam_idealdist", "64", 0 ); // thirdperson distance + cam_contain = gEngfuncs.pfnRegisterVariable ( "cam_contain", "0", 0 ); // contain camera to world + + c_maxpitch = gEngfuncs.pfnRegisterVariable ( "c_maxpitch", "90.0", 0 ); + c_minpitch = gEngfuncs.pfnRegisterVariable ( "c_minpitch", "0.0", 0 ); + c_maxyaw = gEngfuncs.pfnRegisterVariable ( "c_maxyaw", "135.0", 0 ); + c_minyaw = gEngfuncs.pfnRegisterVariable ( "c_minyaw", "-135.0", 0 ); + c_maxdistance = gEngfuncs.pfnRegisterVariable ( "c_maxdistance", "200.0", 0 ); + c_mindistance = gEngfuncs.pfnRegisterVariable ( "c_mindistance", "30.0", 0 ); +} + +void CAM_ClearStates( void ) +{ + vec3_t viewangles; + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + cam_pitchup.state = 0; + cam_pitchdown.state = 0; + cam_yawleft.state = 0; + cam_yawright.state = 0; + cam_in.state = 0; + cam_out.state = 0; + + cam_thirdperson = 0; + cam_command->value = 0; + cam_mousemove=0; + + cam_snapto->value = 0; + cam_distancemove = 0; + + cam_ofs[ 0 ] = 0.0; + cam_ofs[ 1 ] = 0.0; + cam_ofs[ 2 ] = CAM_MIN_DIST; + + cam_idealpitch->value = viewangles[ PITCH ]; + cam_idealyaw->value = viewangles[ YAW ]; + cam_idealdist->value = CAM_MIN_DIST; +} + +void CAM_StartMouseMove(void) +{ + float flSensitivity; + + //only move the cam with mouse if we are in third person. + if (cam_thirdperson) + { + //set appropriate flags and initialize the old mouse position + //variables for mouse camera movement + if (!cam_mousemove) + { + cam_mousemove=1; + iMouseInUse=1; + GetCursorPos (&cam_mouse); + + if ( ( flSensitivity = gHUD.GetSensitivity() ) != 0 ) + { + cam_old_mouse_x=cam_mouse.x*flSensitivity; + cam_old_mouse_y=cam_mouse.y*flSensitivity; + } + else + { + cam_old_mouse_x=cam_mouse.x; + cam_old_mouse_y=cam_mouse.y; + } + } + } + //we are not in 3rd person view..therefore do not allow camera movement + else + { + cam_mousemove=0; + iMouseInUse=0; + } +} + +//the key has been released for camera movement +//tell the engine that mouse camera movement is off +void CAM_EndMouseMove(void) +{ + cam_mousemove=0; + iMouseInUse=0; +} + + +//---------------------------------------------------------- +//routines to start the process of moving the cam in or out +//using the mouse +//---------------------------------------------------------- +void CAM_StartDistance(void) +{ + //only move the cam with mouse if we are in third person. + if (cam_thirdperson) + { + //set appropriate flags and initialize the old mouse position + //variables for mouse camera movement + if (!cam_distancemove) + { + cam_distancemove=1; + cam_mousemove=1; + iMouseInUse=1; + GetCursorPos (&cam_mouse); + cam_old_mouse_x=cam_mouse.x*gHUD.GetSensitivity(); + cam_old_mouse_y=cam_mouse.y*gHUD.GetSensitivity(); + } + } + //we are not in 3rd person view..therefore do not allow camera movement + else + { + cam_distancemove=0; + cam_mousemove=0; + iMouseInUse=0; + } +} + +//the key has been released for camera movement +//tell the engine that mouse camera movement is off +void CAM_EndDistance(void) +{ + cam_distancemove=0; + cam_mousemove=0; + iMouseInUse=0; +} + +int DLLEXPORT CL_IsThirdPerson( void ) +{ + return (cam_thirdperson ? 1 : 0) || (g_iUser1 && (g_iUser2 == gEngfuncs.GetLocalPlayer()->index) ); +} + +void DLLEXPORT CL_CameraOffset( float *ofs ) +{ + VectorCopy( cam_ofs, ofs ); +} \ No newline at end of file diff --git a/hlsdk/cl_dll/in_defs.h b/hlsdk/cl_dll/in_defs.h new file mode 100644 index 00000000..f5591896 --- /dev/null +++ b/hlsdk/cl_dll/in_defs.h @@ -0,0 +1,21 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( IN_DEFSH ) +#define IN_DEFSH +#pragma once + +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 + +#define DLLEXPORT __declspec( dllexport ) + +#endif \ No newline at end of file diff --git a/hlsdk/cl_dll/input.cpp b/hlsdk/cl_dll/input.cpp new file mode 100644 index 00000000..ab9efba8 --- /dev/null +++ b/hlsdk/cl_dll/input.cpp @@ -0,0 +1,1028 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// cl.input.c -- builds an intended movement command to send to the server + +//xxxxxx Move bob and pitch drifting code here and other stuff from view if needed + +// Quake is a trademark of Id Software, Inc., (c) 1996 Id Software, Inc. All +// rights reserved. +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +extern "C" +{ +#include "kbutton.h" +} +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "view.h" +#include +#include + +#include "vgui_TeamFortressViewport.h" + + +extern "C" +{ + struct kbutton_s DLLEXPORT *KB_Find( const char *name ); + void DLLEXPORT CL_CreateMove ( float frametime, struct usercmd_s *cmd, int active ); + void DLLEXPORT HUD_Shutdown( void ); + int DLLEXPORT HUD_Key_Event( int eventcode, int keynum, const char *pszCurrentBinding ); +} + +extern int g_iAlive; + +extern int g_weaponselect; +extern cl_enginefunc_t gEngfuncs; + +// Defined in pm_math.c +extern "C" float anglemod( float a ); + +void IN_Init (void); +void IN_Move ( float frametime, usercmd_t *cmd); +void IN_Shutdown( void ); +void V_Init( void ); +void VectorAngles( const float *forward, float *angles ); +int CL_ButtonBits( int ); + +// xxx need client dll function to get and clear impuse +extern cvar_t *in_joystick; + +int in_impulse = 0; +int in_cancel = 0; + +cvar_t *m_pitch; +cvar_t *m_yaw; +cvar_t *m_forward; +cvar_t *m_side; + +cvar_t *lookstrafe; +cvar_t *lookspring; +cvar_t *cl_pitchup; +cvar_t *cl_pitchdown; +cvar_t *cl_upspeed; +cvar_t *cl_forwardspeed; +cvar_t *cl_backspeed; +cvar_t *cl_sidespeed; +cvar_t *cl_movespeedkey; +cvar_t *cl_yawspeed; +cvar_t *cl_pitchspeed; +cvar_t *cl_anglespeedkey; +cvar_t *cl_vsmoothing; +/* +=============================================================================== + +KEY BUTTONS + +Continuous button event tracking is complicated by the fact that two different +input sources (say, mouse button 1 and the control key) can both press the +same button, but the button should only be released when both of the +pressing key have been released. + +When a key event issues a button command (+forward, +attack, etc), it appends +its key number as a parameter to the command so it can be matched up with +the release. + +state bit 0 is the current state of the key +state bit 1 is edge triggered on the up to down transition +state bit 2 is edge triggered on the down to up transition + +=============================================================================== +*/ + + +kbutton_t in_mlook; +kbutton_t in_klook; +kbutton_t in_jlook; +kbutton_t in_left; +kbutton_t in_right; +kbutton_t in_forward; +kbutton_t in_back; +kbutton_t in_lookup; +kbutton_t in_lookdown; +kbutton_t in_moveleft; +kbutton_t in_moveright; +kbutton_t in_strafe; +kbutton_t in_speed; +kbutton_t in_use; +kbutton_t in_jump; +kbutton_t in_attack; +kbutton_t in_attack2; +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 + +typedef struct kblist_s +{ + struct kblist_s *next; + kbutton_t *pkey; + char name[32]; +} kblist_t; + +kblist_t *g_kbkeys = NULL; + +/* +============ +KB_ConvertString + +Removes references to +use and replaces them with the keyname in the output string. If + a binding is unfound, then the original text is retained. +NOTE: Only works for text with +word in it. +============ +*/ +int KB_ConvertString( char *in, char **ppout ) +{ + char sz[ 4096 ]; + char binding[ 64 ]; + char *p; + char *pOut; + char *pEnd; + const char *pBinding; + + if ( !ppout ) + return 0; + + *ppout = NULL; + p = in; + pOut = sz; + while ( *p ) + { + if ( *p == '+' ) + { + pEnd = binding; + while ( *p && ( isalnum( *p ) || ( pEnd == binding ) ) && ( ( pEnd - binding ) < 63 ) ) + { + *pEnd++ = *p++; + } + + *pEnd = '\0'; + + pBinding = NULL; + if ( strlen( binding + 1 ) > 0 ) + { + // See if there is a binding for binding? + pBinding = gEngfuncs.Key_LookupBinding( binding + 1 ); + } + + if ( pBinding ) + { + *pOut++ = '['; + pEnd = (char *)pBinding; + } + else + { + pEnd = binding; + } + + while ( *pEnd ) + { + *pOut++ = *pEnd++; + } + + if ( pBinding ) + { + *pOut++ = ']'; + } + } + else + { + *pOut++ = *p++; + } + } + + *pOut = '\0'; + + pOut = ( char * )malloc( strlen( sz ) + 1 ); + strcpy( pOut, sz ); + *ppout = pOut; + + return 1; +} + +/* +============ +KB_Find + +Allows the engine to get a kbutton_t directly ( so it can check +mlook state, etc ) for saving out to .cfg files +============ +*/ +struct kbutton_s DLLEXPORT *KB_Find( const char *name ) +{ + kblist_t *p; + p = g_kbkeys; + while ( p ) + { + if ( !stricmp( name, p->name ) ) + return p->pkey; + + p = p->next; + } + return NULL; +} + +/* +============ +KB_Add + +Add a kbutton_t * to the list of pointers the engine can retrieve via KB_Find +============ +*/ +void KB_Add( const char *name, kbutton_t *pkb ) +{ + kblist_t *p; + kbutton_t *kb; + + kb = KB_Find( name ); + + if ( kb ) + return; + + p = ( kblist_t * )malloc( sizeof( kblist_t ) ); + memset( p, 0, sizeof( *p ) ); + + strcpy( p->name, name ); + p->pkey = pkb; + + p->next = g_kbkeys; + g_kbkeys = p; +} + +/* +============ +KB_Init + +Add kbutton_t definitions that the engine can query if needed +============ +*/ +void KB_Init( void ) +{ + g_kbkeys = NULL; + + KB_Add( "in_graph", &in_graph ); + KB_Add( "in_mlook", &in_mlook ); + KB_Add( "in_jlook", &in_jlook ); +} + +/* +============ +KB_Shutdown + +Clear kblist +============ +*/ +void KB_Shutdown( void ) +{ + kblist_t *p, *n; + p = g_kbkeys; + while ( p ) + { + n = p->next; + free( p ); + p = n; + } + g_kbkeys = NULL; +} + +/* +============ +KeyDown +============ +*/ +void KeyDown (kbutton_t *b) +{ + int k; + char *c; + + c = gEngfuncs.Cmd_Argv(1); + if (c[0]) + k = atoi(c); + else + k = -1; // typed manually at the console for continuous down + + if (k == b->down[0] || k == b->down[1]) + return; // repeating key + + if (!b->down[0]) + b->down[0] = k; + else if (!b->down[1]) + b->down[1] = k; + else + { + gEngfuncs.Con_DPrintf ("Three keys down for a button '%c' '%c' '%c'!\n", b->down[0], b->down[1], c); + return; + } + + if (b->state & 1) + return; // still down + b->state |= 1 + 2; // down + impulse down +} + +/* +============ +KeyUp +============ +*/ +void KeyUp (kbutton_t *b) +{ + int k; + char *c; + + c = gEngfuncs.Cmd_Argv(1); + if (c[0]) + k = atoi(c); + else + { // typed manually at the console, assume for unsticking, so clear all + b->down[0] = b->down[1] = 0; + b->state = 4; // impulse up + return; + } + + if (b->down[0] == k) + b->down[0] = 0; + else if (b->down[1] == k) + b->down[1] = 0; + else + return; // key up without coresponding down (menu pass through) + if (b->down[0] || b->down[1]) + { + //Con_Printf ("Keys down for button: '%c' '%c' '%c' (%d,%d,%d)!\n", b->down[0], b->down[1], c, b->down[0], b->down[1], c); + return; // some other key is still holding it down + } + + if (!(b->state & 1)) + return; // still up (this should not happen) + + b->state &= ~1; // now up + b->state |= 4; // impulse up +} + +/* +============ +HUD_Key_Event + +Return 1 to allow engine to process the key, otherwise, act on it as needed +============ +*/ +int DLLEXPORT HUD_Key_Event( int down, int keynum, const char *pszCurrentBinding ) +{ + if (gViewPort) + return gViewPort->KeyInput(down, keynum, pszCurrentBinding); + + return 1; +} + +void IN_BreakDown( void ) { KeyDown( &in_break );}; +void IN_BreakUp( void ) { KeyUp( &in_break ); }; +void IN_KLookDown (void) {KeyDown(&in_klook);} +void IN_KLookUp (void) {KeyUp(&in_klook);} +void IN_JLookDown (void) {KeyDown(&in_jlook);} +void IN_JLookUp (void) {KeyUp(&in_jlook);} +void IN_MLookDown (void) {KeyDown(&in_mlook);} +void IN_UpDown(void) {KeyDown(&in_up);} +void IN_UpUp(void) {KeyUp(&in_up);} +void IN_DownDown(void) {KeyDown(&in_down);} +void IN_DownUp(void) {KeyUp(&in_down);} +void IN_LeftDown(void) {KeyDown(&in_left);} +void IN_LeftUp(void) {KeyUp(&in_left);} +void IN_RightDown(void) {KeyDown(&in_right);} +void IN_RightUp(void) {KeyUp(&in_right);} + +void IN_ForwardDown(void) +{ + KeyDown(&in_forward); + gHUD.m_Spectator.HandleButtonsDown( IN_FORWARD ); +} + +void IN_ForwardUp(void) +{ + KeyUp(&in_forward); + gHUD.m_Spectator.HandleButtonsUp( IN_FORWARD ); +} + +void IN_BackDown(void) +{ + KeyDown(&in_back); + gHUD.m_Spectator.HandleButtonsDown( IN_BACK ); +} + +void IN_BackUp(void) +{ + KeyUp(&in_back); + gHUD.m_Spectator.HandleButtonsUp( IN_BACK ); +} +void IN_LookupDown(void) {KeyDown(&in_lookup);} +void IN_LookupUp(void) {KeyUp(&in_lookup);} +void IN_LookdownDown(void) {KeyDown(&in_lookdown);} +void IN_LookdownUp(void) {KeyUp(&in_lookdown);} +void IN_MoveleftDown(void) +{ + KeyDown(&in_moveleft); + gHUD.m_Spectator.HandleButtonsDown( IN_MOVELEFT ); +} + +void IN_MoveleftUp(void) +{ + KeyUp(&in_moveleft); + gHUD.m_Spectator.HandleButtonsUp( IN_MOVELEFT ); +} + +void IN_MoverightDown(void) +{ + KeyDown(&in_moveright); + gHUD.m_Spectator.HandleButtonsDown( IN_MOVERIGHT ); +} + +void IN_MoverightUp(void) +{ + KeyUp(&in_moveright); + gHUD.m_Spectator.HandleButtonsUp( IN_MOVERIGHT ); +} +void IN_SpeedDown(void) {KeyDown(&in_speed);} +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); + + gHUD.m_Spectator.HandleButtonsDown( IN_ATTACK2 ); +} + +void IN_Attack2Up(void) {KeyUp(&in_attack2);} +void IN_UseDown (void) +{ + KeyDown(&in_use); + gHUD.m_Spectator.HandleButtonsDown( IN_USE ); +} +void IN_UseUp (void) {KeyUp(&in_use);} +void IN_JumpDown (void) +{ + KeyDown(&in_jump); + gHUD.m_Spectator.HandleButtonsDown( IN_JUMP ); + +} +void IN_JumpUp (void) {KeyUp(&in_jump);} +void IN_DuckDown(void) +{ + KeyDown(&in_duck); + gHUD.m_Spectator.HandleButtonsDown( IN_DUCK ); + +} +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);} + +void IN_AttackDown(void) +{ + KeyDown( &in_attack ); + gHUD.m_Spectator.HandleButtonsDown( IN_ATTACK ); +} + +void IN_AttackUp(void) +{ + KeyUp( &in_attack ); + in_cancel = 0; +} + +// Special handling +void IN_Cancel(void) +{ + in_cancel = 1; +} + +void IN_Impulse (void) +{ + in_impulse = atoi( gEngfuncs.Cmd_Argv(1) ); +} + +void IN_ScoreDown(void) +{ + KeyDown(&in_score); + if ( gViewPort ) + { + gViewPort->ShowScoreBoard(); + } +} + +void IN_ScoreUp(void) +{ + KeyUp(&in_score); + if ( gViewPort ) + { + gViewPort->HideScoreBoard(); + } +} + +void IN_MLookUp (void) +{ + KeyUp( &in_mlook ); + if ( !( in_mlook.state & 1 ) && lookspring->value ) + { + V_StartPitchDrift(); + } +} + +/* +=============== +CL_KeyState + +Returns 0.25 if a key was pressed and released during the frame, +0.5 if it was pressed and held +0 if held then released, and +1.0 if held for the entire time +=============== +*/ +float CL_KeyState (kbutton_t *key) +{ + float val = 0.0; + int impulsedown, impulseup, down; + + impulsedown = key->state & 2; + impulseup = key->state & 4; + down = key->state & 1; + + if ( impulsedown && !impulseup ) + { + // pressed and held this frame? + val = down ? 0.5 : 0.0; + } + + if ( impulseup && !impulsedown ) + { + // released this frame? + val = down ? 0.0 : 0.0; + } + + if ( !impulsedown && !impulseup ) + { + // held the entire frame? + val = down ? 1.0 : 0.0; + } + + if ( impulsedown && impulseup ) + { + if ( down ) + { + // released and re-pressed this frame + val = 0.75; + } + else + { + // pressed and released this frame + val = 0.25; + } + } + + // clear impulses + key->state &= 1; + return val; +} + +/* +================ +CL_AdjustAngles + +Moves the local angle positions +================ +*/ +void CL_AdjustAngles ( float frametime, float *viewangles ) +{ + float speed; + float up, down; + + if (in_speed.state & 1) + { + speed = frametime * cl_anglespeedkey->value; + } + else + { + speed = frametime; + } + + if (!(in_strafe.state & 1)) + { + viewangles[YAW] -= speed*cl_yawspeed->value*CL_KeyState (&in_right); + viewangles[YAW] += speed*cl_yawspeed->value*CL_KeyState (&in_left); + viewangles[YAW] = anglemod(viewangles[YAW]); + } + if (in_klook.state & 1) + { + V_StopPitchDrift (); + viewangles[PITCH] -= speed*cl_pitchspeed->value * CL_KeyState (&in_forward); + viewangles[PITCH] += speed*cl_pitchspeed->value * CL_KeyState (&in_back); + } + + up = CL_KeyState (&in_lookup); + down = CL_KeyState(&in_lookdown); + + viewangles[PITCH] -= speed*cl_pitchspeed->value * up; + viewangles[PITCH] += speed*cl_pitchspeed->value * down; + + if (up || down) + V_StopPitchDrift (); + + if (viewangles[PITCH] > cl_pitchdown->value) + viewangles[PITCH] = cl_pitchdown->value; + if (viewangles[PITCH] < -cl_pitchup->value) + viewangles[PITCH] = -cl_pitchup->value; + + if (viewangles[ROLL] > 50) + viewangles[ROLL] = 50; + if (viewangles[ROLL] < -50) + viewangles[ROLL] = -50; +} + +/* +================ +CL_CreateMove + +Send the intended movement message to the server +if active == 1 then we are 1) not playing back demos ( where our commands are ignored ) and +2 ) we have finished signing on to server +================ +*/ +void DLLEXPORT CL_CreateMove ( float frametime, struct usercmd_s *cmd, int active ) +{ + float spd; + vec3_t viewangles; + static vec3_t oldangles; + + if ( active ) + { + //memset( viewangles, 0, sizeof( vec3_t ) ); + //viewangles[ 0 ] = viewangles[ 1 ] = viewangles[ 2 ] = 0.0; + gEngfuncs.GetViewAngles( (float *)viewangles ); + + CL_AdjustAngles ( frametime, viewangles ); + + memset (cmd, 0, sizeof(*cmd)); + + gEngfuncs.SetViewAngles( (float *)viewangles ); + + if ( in_strafe.state & 1 ) + { + cmd->sidemove += cl_sidespeed->value * CL_KeyState (&in_right); + cmd->sidemove -= cl_sidespeed->value * CL_KeyState (&in_left); + } + + cmd->sidemove += cl_sidespeed->value * CL_KeyState (&in_moveright); + cmd->sidemove -= cl_sidespeed->value * CL_KeyState (&in_moveleft); + + cmd->upmove += cl_upspeed->value * CL_KeyState (&in_up); + cmd->upmove -= cl_upspeed->value * CL_KeyState (&in_down); + + if ( !(in_klook.state & 1 ) ) + { + cmd->forwardmove += cl_forwardspeed->value * CL_KeyState (&in_forward); + cmd->forwardmove -= cl_backspeed->value * CL_KeyState (&in_back); + } + + // adjust for speed key + if ( in_speed.state & 1 ) + { + cmd->forwardmove *= cl_movespeedkey->value; + cmd->sidemove *= cl_movespeedkey->value; + cmd->upmove *= cl_movespeedkey->value; + } + + // clip to maxspeed + spd = gEngfuncs.GetClientMaxspeed(); + if ( spd != 0.0 ) + { + // scale the 3 speeds so that the total velocity is not > cl.maxspeed + float fmov = sqrt( (cmd->forwardmove*cmd->forwardmove) + (cmd->sidemove*cmd->sidemove) + (cmd->upmove*cmd->upmove) ); + + if ( fmov > spd ) + { + float fratio = spd / fmov; + cmd->forwardmove *= fratio; + cmd->sidemove *= fratio; + cmd->upmove *= fratio; + } + } + + // Allow mice and other controllers to add their inputs + IN_Move ( frametime, cmd ); + } + + cmd->impulse = in_impulse; + in_impulse = 0; + + cmd->weaponselect = g_weaponselect; + g_weaponselect = 0; + // + // set button and flag bits + // + cmd->buttons = CL_ButtonBits( 1 ); + + // If they're in a modal dialog, ignore the attack button. + if(GetClientVoiceMgr()->IsInSquelchMode()) + cmd->buttons &= ~IN_ATTACK; + + // Using joystick? + if ( in_joystick->value ) + { + if ( cmd->forwardmove > 0 ) + { + cmd->buttons |= IN_FORWARD; + } + else if ( cmd->forwardmove < 0 ) + { + cmd->buttons |= IN_BACK; + } + } + + gEngfuncs.GetViewAngles( (float *)viewangles ); + // Set current view angles. + + if ( g_iAlive ) + { + VectorCopy( viewangles, cmd->viewangles ); + VectorCopy( viewangles, oldangles ); + } + else + { + VectorCopy( oldangles, cmd->viewangles ); + } + +} + +/* +============ +CL_IsDead + +Returns 1 if health is <= 0 +============ +*/ +int CL_IsDead( void ) +{ + return ( gHUD.m_Health.m_iHealth <= 0 ) ? 1 : 0; +} + +/* +============ +CL_ButtonBits + +Returns appropriate button info for keyboard and mouse state +Set bResetState to 1 to clear old state info +============ +*/ +int CL_ButtonBits( int bResetState ) +{ + int bits = 0; + + if ( in_attack.state & 3 ) + { + bits |= IN_ATTACK; + } + + if (in_duck.state & 3) + { + bits |= IN_DUCK; + } + + if (in_jump.state & 3) + { + bits |= IN_JUMP; + } + + if ( in_forward.state & 3 ) + { + bits |= IN_FORWARD; + } + + if (in_back.state & 3) + { + bits |= IN_BACK; + } + + if (in_use.state & 3) + { + bits |= IN_USE; + } + + if (in_cancel) + { + bits |= IN_CANCEL; + } + + if ( in_left.state & 3 ) + { + bits |= IN_LEFT; + } + + if (in_right.state & 3) + { + bits |= IN_RIGHT; + } + + if ( in_moveleft.state & 3 ) + { + bits |= IN_MOVELEFT; + } + + if (in_moveright.state & 3) + { + bits |= IN_MOVERIGHT; + } + + if (in_attack2.state & 3) + { + bits |= IN_ATTACK2; + } + + if (in_reload.state & 3) + { + bits |= IN_RELOAD; + } + + if (in_alt1.state & 3) + { + bits |= IN_ALT1; + } + + if ( in_score.state & 3 ) + { + bits |= IN_SCORE; + } + + // Dead or in intermission? Shore scoreboard, too + if ( CL_IsDead() || gHUD.m_iIntermission ) + { + bits |= IN_SCORE; + } + + if ( bResetState ) + { + in_attack.state &= ~2; + in_duck.state &= ~2; + in_jump.state &= ~2; + in_forward.state &= ~2; + in_back.state &= ~2; + in_use.state &= ~2; + in_left.state &= ~2; + in_right.state &= ~2; + in_moveleft.state &= ~2; + in_moveright.state &= ~2; + in_attack2.state &= ~2; + in_reload.state &= ~2; + in_alt1.state &= ~2; + in_score.state &= ~2; + } + + return bits; +} + +/* +============ +CL_ResetButtonBits + +============ +*/ +void CL_ResetButtonBits( int bits ) +{ + int bitsNew = CL_ButtonBits( 0 ) ^ bits; + + // Has the attack button been changed + if ( bitsNew & IN_ATTACK ) + { + // Was it pressed? or let go? + if ( bits & IN_ATTACK ) + { + KeyDown( &in_attack ); + } + else + { + // totally clear state + in_attack.state &= ~7; + } + } +} + +/* +============ +InitInput +============ +*/ +void InitInput (void) +{ + gEngfuncs.pfnAddCommand ("+moveup",IN_UpDown); + gEngfuncs.pfnAddCommand ("-moveup",IN_UpUp); + gEngfuncs.pfnAddCommand ("+movedown",IN_DownDown); + gEngfuncs.pfnAddCommand ("-movedown",IN_DownUp); + gEngfuncs.pfnAddCommand ("+left",IN_LeftDown); + gEngfuncs.pfnAddCommand ("-left",IN_LeftUp); + gEngfuncs.pfnAddCommand ("+right",IN_RightDown); + gEngfuncs.pfnAddCommand ("-right",IN_RightUp); + gEngfuncs.pfnAddCommand ("+forward",IN_ForwardDown); + gEngfuncs.pfnAddCommand ("-forward",IN_ForwardUp); + gEngfuncs.pfnAddCommand ("+back",IN_BackDown); + gEngfuncs.pfnAddCommand ("-back",IN_BackUp); + gEngfuncs.pfnAddCommand ("+lookup", IN_LookupDown); + gEngfuncs.pfnAddCommand ("-lookup", IN_LookupUp); + gEngfuncs.pfnAddCommand ("+lookdown", IN_LookdownDown); + gEngfuncs.pfnAddCommand ("-lookdown", IN_LookdownUp); + gEngfuncs.pfnAddCommand ("+strafe", IN_StrafeDown); + gEngfuncs.pfnAddCommand ("-strafe", IN_StrafeUp); + gEngfuncs.pfnAddCommand ("+moveleft", IN_MoveleftDown); + gEngfuncs.pfnAddCommand ("-moveleft", IN_MoveleftUp); + gEngfuncs.pfnAddCommand ("+moveright", IN_MoverightDown); + gEngfuncs.pfnAddCommand ("-moveright", IN_MoverightUp); + gEngfuncs.pfnAddCommand ("+speed", IN_SpeedDown); + gEngfuncs.pfnAddCommand ("-speed", IN_SpeedUp); + gEngfuncs.pfnAddCommand ("+attack", IN_AttackDown); + gEngfuncs.pfnAddCommand ("-attack", IN_AttackUp); + gEngfuncs.pfnAddCommand ("+attack2", IN_Attack2Down); + gEngfuncs.pfnAddCommand ("-attack2", IN_Attack2Up); + gEngfuncs.pfnAddCommand ("+use", IN_UseDown); + gEngfuncs.pfnAddCommand ("-use", IN_UseUp); + gEngfuncs.pfnAddCommand ("+jump", IN_JumpDown); + gEngfuncs.pfnAddCommand ("-jump", IN_JumpUp); + gEngfuncs.pfnAddCommand ("impulse", IN_Impulse); + gEngfuncs.pfnAddCommand ("+klook", IN_KLookDown); + gEngfuncs.pfnAddCommand ("-klook", IN_KLookUp); + gEngfuncs.pfnAddCommand ("+mlook", IN_MLookDown); + gEngfuncs.pfnAddCommand ("-mlook", IN_MLookUp); + gEngfuncs.pfnAddCommand ("+jlook", IN_JLookDown); + gEngfuncs.pfnAddCommand ("-jlook", IN_JLookUp); + gEngfuncs.pfnAddCommand ("+duck", IN_DuckDown); + 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); + gEngfuncs.pfnAddCommand ("-showscores", IN_ScoreUp); + gEngfuncs.pfnAddCommand ("+graph", IN_GraphDown); + gEngfuncs.pfnAddCommand ("-graph", IN_GraphUp); + gEngfuncs.pfnAddCommand ("+break",IN_BreakDown); + gEngfuncs.pfnAddCommand ("-break",IN_BreakUp); + + lookstrafe = gEngfuncs.pfnRegisterVariable ( "lookstrafe", "0", FCVAR_ARCHIVE ); + lookspring = gEngfuncs.pfnRegisterVariable ( "lookspring", "0", FCVAR_ARCHIVE ); + cl_anglespeedkey = gEngfuncs.pfnRegisterVariable ( "cl_anglespeedkey", "0.67", 0 ); + cl_yawspeed = gEngfuncs.pfnRegisterVariable ( "cl_yawspeed", "210", 0 ); + cl_pitchspeed = gEngfuncs.pfnRegisterVariable ( "cl_pitchspeed", "225", 0 ); + cl_upspeed = gEngfuncs.pfnRegisterVariable ( "cl_upspeed", "320", 0 ); + cl_forwardspeed = gEngfuncs.pfnRegisterVariable ( "cl_forwardspeed", "400", FCVAR_ARCHIVE ); + cl_backspeed = gEngfuncs.pfnRegisterVariable ( "cl_backspeed", "400", FCVAR_ARCHIVE ); + cl_sidespeed = gEngfuncs.pfnRegisterVariable ( "cl_sidespeed", "400", 0 ); + 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 ); + m_yaw = gEngfuncs.pfnRegisterVariable ( "m_yaw","0.022", FCVAR_ARCHIVE ); + m_forward = gEngfuncs.pfnRegisterVariable ( "m_forward","1", FCVAR_ARCHIVE ); + m_side = gEngfuncs.pfnRegisterVariable ( "m_side","0.8", FCVAR_ARCHIVE ); + + // Initialize third person camera controls. + CAM_Init(); + // Initialize inputs + IN_Init(); + // Initialize keyboard + KB_Init(); + // Initialize view system + V_Init(); +} + +/* +============ +ShutdownInput +============ +*/ +void ShutdownInput (void) +{ + IN_Shutdown(); + KB_Shutdown(); +} + +void DLLEXPORT HUD_Shutdown( void ) +{ + ShutdownInput(); +} diff --git a/hlsdk/cl_dll/inputw32.cpp b/hlsdk/cl_dll/inputw32.cpp new file mode 100644 index 00000000..7176746b --- /dev/null +++ b/hlsdk/cl_dll/inputw32.cpp @@ -0,0 +1,947 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// in_win.c -- windows 95 mouse and joystick code +// 02/21/97 JCB Added extended DirectInput code to support external controllers. + +#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 "../engine/keydefs.h" +#include "view.h" +#include "windows.h" + +#define MOUSE_BUTTON_COUNT 5 + +// Set this to 1 to show mouse cursor. Experimental +int g_iVisibleMouse = 0; + +extern "C" +{ + void DLLEXPORT IN_ActivateMouse( void ); + void DLLEXPORT IN_DeactivateMouse( void ); + void DLLEXPORT IN_MouseEvent (int mstate); + void DLLEXPORT IN_Accumulate (void); + void DLLEXPORT IN_ClearStates (void); +} + +extern cl_enginefunc_t gEngfuncs; + +extern int iMouseInUse; + +extern kbutton_t in_strafe; +extern kbutton_t in_mlook; +extern kbutton_t in_speed; +extern kbutton_t in_jlook; + +extern cvar_t *m_pitch; +extern cvar_t *m_yaw; +extern cvar_t *m_forward; +extern cvar_t *m_side; + +extern cvar_t *lookstrafe; +extern cvar_t *lookspring; +extern cvar_t *cl_pitchdown; +extern cvar_t *cl_pitchup; +extern cvar_t *cl_yawspeed; +extern cvar_t *cl_sidespeed; +extern cvar_t *cl_forwardspeed; +extern cvar_t *cl_pitchspeed; +extern cvar_t *cl_movespeedkey; + +// mouse variables +cvar_t *m_filter; +cvar_t *sensitivity; + +int mouse_buttons; +int mouse_oldbuttonstate; +POINT current_pos; +int mouse_x, mouse_y, old_mouse_x, old_mouse_y, mx_accum, my_accum; + +static int restore_spi; +static int originalmouseparms[3], newmouseparms[3] = {0, 0, 1}; +static int mouseactive; +int mouseinitialized; +static int mouseparmsvalid; +static int mouseshowtoggle = 1; + +// joystick defines and variables +// where should defines be moved? +#define JOY_ABSOLUTE_AXIS 0x00000000 // control like a joystick +#define JOY_RELATIVE_AXIS 0x00000010 // control like a mouse, spinner, trackball +#define JOY_MAX_AXES 6 // X, Y, Z, R, U, V +#define JOY_AXIS_X 0 +#define JOY_AXIS_Y 1 +#define JOY_AXIS_Z 2 +#define JOY_AXIS_R 3 +#define JOY_AXIS_U 4 +#define JOY_AXIS_V 5 + +enum _ControlList +{ + AxisNada = 0, + AxisForward, + AxisLook, + AxisSide, + AxisTurn +}; + +DWORD dwAxisFlags[JOY_MAX_AXES] = +{ + JOY_RETURNX, + JOY_RETURNY, + JOY_RETURNZ, + JOY_RETURNR, + JOY_RETURNU, + JOY_RETURNV +}; + +DWORD dwAxisMap[ JOY_MAX_AXES ]; +DWORD dwControlMap[ JOY_MAX_AXES ]; +PDWORD pdwRawValue[ JOY_MAX_AXES ]; + +// none of these cvars are saved over a session +// this means that advanced controller configuration needs to be executed +// each time. this avoids any problems with getting back to a default usage +// or when changing from one controller to another. this way at least something +// works. +cvar_t *in_joystick; +cvar_t *joy_name; +cvar_t *joy_advanced; +cvar_t *joy_advaxisx; +cvar_t *joy_advaxisy; +cvar_t *joy_advaxisz; +cvar_t *joy_advaxisr; +cvar_t *joy_advaxisu; +cvar_t *joy_advaxisv; +cvar_t *joy_forwardthreshold; +cvar_t *joy_sidethreshold; +cvar_t *joy_pitchthreshold; +cvar_t *joy_yawthreshold; +cvar_t *joy_forwardsensitivity; +cvar_t *joy_sidesensitivity; +cvar_t *joy_pitchsensitivity; +cvar_t *joy_yawsensitivity; +cvar_t *joy_wwhack1; +cvar_t *joy_wwhack2; + +int joy_avail, joy_advancedinit, joy_haspov; +DWORD joy_oldbuttonstate, joy_oldpovstate; + +int joy_id; +DWORD joy_flags; +DWORD joy_numbuttons; + +static JOYINFOEX ji; + +/* +=========== +Force_CenterView_f +=========== +*/ +void Force_CenterView_f (void) +{ + vec3_t viewangles; + + if (!iMouseInUse) + { + gEngfuncs.GetViewAngles( (float *)viewangles ); + viewangles[PITCH] = 0; + gEngfuncs.SetViewAngles( (float *)viewangles ); + } +} + +/* +=========== +IN_ActivateMouse +=========== +*/ +void DLLEXPORT IN_ActivateMouse (void) +{ + if (mouseinitialized) + { + if (mouseparmsvalid) + restore_spi = SystemParametersInfo (SPI_SETMOUSE, 0, newmouseparms, 0); + mouseactive = 1; + } +} + +/* +=========== +IN_DeactivateMouse +=========== +*/ +void DLLEXPORT IN_DeactivateMouse (void) +{ + if (mouseinitialized) + { + if (restore_spi) + SystemParametersInfo (SPI_SETMOUSE, 0, originalmouseparms, 0); + + mouseactive = 0; + } +} + +/* +=========== +IN_StartupMouse +=========== +*/ +void IN_StartupMouse (void) +{ + if ( gEngfuncs.CheckParm ("-nomouse", NULL ) ) + return; + + mouseinitialized = 1; + mouseparmsvalid = SystemParametersInfo (SPI_GETMOUSE, 0, originalmouseparms, 0); + + if (mouseparmsvalid) + { + if ( gEngfuncs.CheckParm ("-noforcemspd", NULL ) ) + newmouseparms[2] = originalmouseparms[2]; + + if ( gEngfuncs.CheckParm ("-noforcemaccel", NULL ) ) + { + newmouseparms[0] = originalmouseparms[0]; + newmouseparms[1] = originalmouseparms[1]; + } + + if ( gEngfuncs.CheckParm ("-noforcemparms", NULL ) ) + { + newmouseparms[0] = originalmouseparms[0]; + newmouseparms[1] = originalmouseparms[1]; + newmouseparms[2] = originalmouseparms[2]; + } + } + + mouse_buttons = MOUSE_BUTTON_COUNT; +} + +/* +=========== +IN_Shutdown +=========== +*/ +void IN_Shutdown (void) +{ + IN_DeactivateMouse (); +} + +/* +=========== +IN_GetMousePos + +Ask for mouse position from engine +=========== +*/ +void IN_GetMousePos( int *mx, int *my ) +{ + gEngfuncs.GetMousePosition( mx, my ); +} + +/* +=========== +IN_ResetMouse + +FIXME: Call through to engine? +=========== +*/ +void IN_ResetMouse( void ) +{ + SetCursorPos ( gEngfuncs.GetWindowCenterX(), gEngfuncs.GetWindowCenterY() ); +} + +/* +=========== +IN_MouseEvent +=========== +*/ +void DLLEXPORT IN_MouseEvent (int mstate) +{ + int i; + + if ( iMouseInUse || g_iVisibleMouse ) + return; + + // perform button actions + for (i=0 ; ivalue) + { + 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() ) + { + cmd->upmove -= m_forward->value * mouse_y; + } + else + { + cmd->forwardmove -= m_forward->value * mouse_y; + } + } + + // if the mouse has moved, force it to the center, so there's room to move + if ( mx || my ) + { + IN_ResetMouse(); + } + } + + gEngfuncs.SetViewAngles( (float *)viewangles ); + +/* +//#define TRACE_TEST +#if defined( TRACE_TEST ) + { + int mx, my; + void V_Move( int mx, int my ); + IN_GetMousePos( &mx, &my ); + V_Move( mx, my ); + } +#endif +*/ +} + +/* +=========== +IN_Accumulate +=========== +*/ +void DLLEXPORT IN_Accumulate (void) +{ + //only accumulate mouse if we are not moving the camera with the mouse + if ( !iMouseInUse && !g_iVisibleMouse ) + { + if (mouseactive) + { + GetCursorPos (¤t_pos); + + mx_accum += current_pos.x - gEngfuncs.GetWindowCenterX(); + my_accum += current_pos.y - gEngfuncs.GetWindowCenterY(); + + // force the mouse to the center, so there's room to move + IN_ResetMouse(); + } + } + +} + +/* +=================== +IN_ClearStates +=================== +*/ +void DLLEXPORT IN_ClearStates (void) +{ + if ( !mouseactive ) + return; + + mx_accum = 0; + my_accum = 0; + mouse_oldbuttonstate = 0; +} + +/* +=============== +IN_StartupJoystick +=============== +*/ +void IN_StartupJoystick (void) +{ + int numdevs; + JOYCAPS jc; + MMRESULT mmr; + + // assume no joystick + joy_avail = 0; + + // abort startup if user requests no joystick + if ( gEngfuncs.CheckParm ("-nojoy", NULL ) ) + return; + + // verify joystick driver is present + if ((numdevs = joyGetNumDevs ()) == 0) + { + gEngfuncs.Con_DPrintf ("joystick not found -- driver not present\n\n"); + return; + } + + // cycle through the joystick ids for the first valid one + for (joy_id=0 ; joy_idvalue == 0.0) + { + // default joystick initialization + // 2 axes only with joystick control + dwAxisMap[JOY_AXIS_X] = AxisTurn; + // dwControlMap[JOY_AXIS_X] = JOY_ABSOLUTE_AXIS; + dwAxisMap[JOY_AXIS_Y] = AxisForward; + // dwControlMap[JOY_AXIS_Y] = JOY_ABSOLUTE_AXIS; + } + else + { + if ( strcmp ( joy_name->string, "joystick") != 0 ) + { + // notify user of advanced controller + gEngfuncs.Con_Printf ("\n%s configured\n\n", joy_name->string); + } + + // advanced initialization here + // data supplied by user via joy_axisn cvars + dwTemp = (DWORD) joy_advaxisx->value; + dwAxisMap[JOY_AXIS_X] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_X] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisy->value; + dwAxisMap[JOY_AXIS_Y] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_Y] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisz->value; + dwAxisMap[JOY_AXIS_Z] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_Z] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisr->value; + dwAxisMap[JOY_AXIS_R] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_R] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisu->value; + dwAxisMap[JOY_AXIS_U] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_U] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisv->value; + dwAxisMap[JOY_AXIS_V] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_V] = dwTemp & JOY_RELATIVE_AXIS; + } + + // compute the axes to collect from DirectInput + joy_flags = JOY_RETURNCENTERED | JOY_RETURNBUTTONS | JOY_RETURNPOV; + for (i = 0; i < JOY_MAX_AXES; i++) + { + if (dwAxisMap[i] != AxisNada) + { + joy_flags |= dwAxisFlags[i]; + } + } +} + + +/* +=========== +IN_Commands +=========== +*/ +void IN_Commands (void) +{ + int i, key_index; + DWORD buttonstate, povstate; + + if (!joy_avail) + { + return; + } + + + // loop through the joystick buttons + // key a joystick event or auxillary event for higher number buttons for each state change + buttonstate = ji.dwButtons; + for (i=0 ; i < (int)joy_numbuttons ; i++) + { + if ( (buttonstate & (1<value != 0.0) + { + ji.dwUpos += 100; + } + return 1; + } + else + { + // read error occurred + // turning off the joystick seems too harsh for 1 read error,\ + // but what should be done? + // Con_Printf ("IN_ReadJoystick: no response\n"); + // joy_avail = 0; + return 0; + } +} + + +/* +=========== +IN_JoyMove +=========== +*/ +void IN_JoyMove ( float frametime, usercmd_t *cmd ) +{ + float speed, aspeed; + float fAxisValue, fTemp; + int i; + vec3_t viewangles; + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + + // complete initialization if first time in + // this is needed as cvars are not available at initialization time + if( joy_advancedinit != 1 ) + { + Joy_AdvancedUpdate_f(); + joy_advancedinit = 1; + } + + // verify joystick is available and that the user wants to use it + if (!joy_avail || !in_joystick->value) + { + return; + } + + // collect the joystick data, if possible + if (IN_ReadJoystick () != 1) + { + return; + } + + if (in_speed.state & 1) + speed = cl_movespeedkey->value; + else + speed = 1; + + aspeed = speed * frametime; + + // loop through the axes + for (i = 0; i < JOY_MAX_AXES; i++) + { + // get the floating point zero-centered, potentially-inverted data for the current axis + fAxisValue = (float) *pdwRawValue[i]; + // move centerpoint to zero + fAxisValue -= 32768.0; + + if (joy_wwhack2->value != 0.0) + { + if (dwAxisMap[i] == AxisTurn) + { + // this is a special formula for the Logitech WingMan Warrior + // y=ax^b; where a = 300 and b = 1.3 + // also x values are in increments of 800 (so this is factored out) + // then bounds check result to level out excessively high spin rates + fTemp = 300.0 * pow(abs(fAxisValue) / 800.0, 1.3); + if (fTemp > 14000.0) + fTemp = 14000.0; + // restore direction information + fAxisValue = (fAxisValue > 0.0) ? fTemp : -fTemp; + } + } + + // convert range from -32768..32767 to -1..1 + fAxisValue /= 32768.0; + + switch (dwAxisMap[i]) + { + case AxisForward: + if ((joy_advanced->value == 0.0) && (in_jlook.state & 1)) + { + // user wants forward control to become look control + if (fabs(fAxisValue) > joy_pitchthreshold->value) + { + // if mouse invert is on, invert the joystick pitch value + // only absolute control support here (joy_advanced is 0) + if (m_pitch->value < 0.0) + { + viewangles[PITCH] -= (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value; + } + else + { + viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value; + } + V_StopPitchDrift(); + } + else + { + // no pitch movement + // disable pitch return-to-center unless requested by user + // *** this code can be removed when the lookspring bug is fixed + // *** the bug always has the lookspring feature on + if(lookspring->value == 0.0) + { + V_StopPitchDrift(); + } + } + } + else + { + // user wants forward control to be forward control + if (fabs(fAxisValue) > joy_forwardthreshold->value) + { + cmd->forwardmove += (fAxisValue * joy_forwardsensitivity->value) * speed * cl_forwardspeed->value; + } + } + break; + + case AxisSide: + if (fabs(fAxisValue) > joy_sidethreshold->value) + { + cmd->sidemove += (fAxisValue * joy_sidesensitivity->value) * speed * cl_sidespeed->value; + } + break; + + case AxisTurn: + if ((in_strafe.state & 1) || (lookstrafe->value && (in_jlook.state & 1))) + { + // user wants turn control to become side control + if (fabs(fAxisValue) > joy_sidethreshold->value) + { + cmd->sidemove -= (fAxisValue * joy_sidesensitivity->value) * speed * cl_sidespeed->value; + } + } + else + { + // user wants turn control to be turn control + if (fabs(fAxisValue) > joy_yawthreshold->value) + { + if(dwControlMap[i] == JOY_ABSOLUTE_AXIS) + { + viewangles[YAW] += (fAxisValue * joy_yawsensitivity->value) * aspeed * cl_yawspeed->value; + } + else + { + viewangles[YAW] += (fAxisValue * joy_yawsensitivity->value) * speed * 180.0; + } + + } + } + break; + + case AxisLook: + if (in_jlook.state & 1) + { + if (fabs(fAxisValue) > joy_pitchthreshold->value) + { + // pitch movement detected and pitch movement desired by user + if(dwControlMap[i] == JOY_ABSOLUTE_AXIS) + { + viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value; + } + else + { + viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * speed * 180.0; + } + V_StopPitchDrift(); + } + else + { + // no pitch movement + // disable pitch return-to-center unless requested by user + // *** this code can be removed when the lookspring bug is fixed + // *** the bug always has the lookspring feature on + if( lookspring->value == 0.0 ) + { + V_StopPitchDrift(); + } + } + } + break; + + default: + break; + } + } + + // bounds check pitch + if (viewangles[PITCH] > cl_pitchdown->value) + viewangles[PITCH] = cl_pitchdown->value; + if (viewangles[PITCH] < -cl_pitchup->value) + viewangles[PITCH] = -cl_pitchup->value; + + gEngfuncs.SetViewAngles( (float *)viewangles ); + +} + +/* +=========== +IN_Move +=========== +*/ +void IN_Move ( float frametime, usercmd_t *cmd) +{ + if ( !iMouseInUse && mouseactive ) + { + IN_MouseMove ( frametime, cmd); + } + + IN_JoyMove ( frametime, cmd); +} + +/* +=========== +IN_Init +=========== +*/ +void IN_Init (void) +{ + m_filter = gEngfuncs.pfnRegisterVariable ( "m_filter","0", FCVAR_ARCHIVE ); + sensitivity = gEngfuncs.pfnRegisterVariable ( "sensitivity","3", FCVAR_ARCHIVE ); // user mouse sensitivity setting. + + in_joystick = gEngfuncs.pfnRegisterVariable ( "joystick","0", FCVAR_ARCHIVE ); + joy_name = gEngfuncs.pfnRegisterVariable ( "joyname", "joystick", 0 ); + joy_advanced = gEngfuncs.pfnRegisterVariable ( "joyadvanced", "0", 0 ); + joy_advaxisx = gEngfuncs.pfnRegisterVariable ( "joyadvaxisx", "0", 0 ); + joy_advaxisy = gEngfuncs.pfnRegisterVariable ( "joyadvaxisy", "0", 0 ); + joy_advaxisz = gEngfuncs.pfnRegisterVariable ( "joyadvaxisz", "0", 0 ); + joy_advaxisr = gEngfuncs.pfnRegisterVariable ( "joyadvaxisr", "0", 0 ); + joy_advaxisu = gEngfuncs.pfnRegisterVariable ( "joyadvaxisu", "0", 0 ); + joy_advaxisv = gEngfuncs.pfnRegisterVariable ( "joyadvaxisv", "0", 0 ); + joy_forwardthreshold = gEngfuncs.pfnRegisterVariable ( "joyforwardthreshold", "0.15", 0 ); + joy_sidethreshold = gEngfuncs.pfnRegisterVariable ( "joysidethreshold", "0.15", 0 ); + joy_pitchthreshold = gEngfuncs.pfnRegisterVariable ( "joypitchthreshold", "0.15", 0 ); + joy_yawthreshold = gEngfuncs.pfnRegisterVariable ( "joyyawthreshold", "0.15", 0 ); + joy_forwardsensitivity = gEngfuncs.pfnRegisterVariable ( "joyforwardsensitivity", "-1.0", 0 ); + joy_sidesensitivity = gEngfuncs.pfnRegisterVariable ( "joysidesensitivity", "-1.0", 0 ); + joy_pitchsensitivity = gEngfuncs.pfnRegisterVariable ( "joypitchsensitivity", "1.0", 0 ); + joy_yawsensitivity = gEngfuncs.pfnRegisterVariable ( "joyyawsensitivity", "-1.0", 0 ); + joy_wwhack1 = gEngfuncs.pfnRegisterVariable ( "joywwhack1", "0.0", 0 ); + joy_wwhack2 = gEngfuncs.pfnRegisterVariable ( "joywwhack2", "0.0", 0 ); + + gEngfuncs.pfnAddCommand ("force_centerview", Force_CenterView_f); + gEngfuncs.pfnAddCommand ("joyadvancedupdate", Joy_AdvancedUpdate_f); + + IN_StartupMouse (); + IN_StartupJoystick (); +} \ No newline at end of file diff --git a/hlsdk/cl_dll/kbutton.h b/hlsdk/cl_dll/kbutton.h new file mode 100644 index 00000000..a772b3d3 --- /dev/null +++ b/hlsdk/cl_dll/kbutton.h @@ -0,0 +1,18 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( KBUTTONH ) +#define KBUTTONH +#pragma once + +typedef struct kbutton_s +{ + int down[2]; // key nums holding it down + int state; // low bit is down state +} kbutton_t; + +#endif // !KBUTTONH \ No newline at end of file diff --git a/hlsdk/cl_dll/menu.cpp b/hlsdk/cl_dll/menu.cpp new file mode 100644 index 00000000..0cd92333 --- /dev/null +++ b/hlsdk/cl_dll/menu.cpp @@ -0,0 +1,188 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// menu.cpp +// +// generic menu handler +// +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "vgui_TeamFortressViewport.h" + +#define MAX_MENU_STRING 512 +char g_szMenuString[MAX_MENU_STRING]; +char g_szPrelocalisedMenuString[MAX_MENU_STRING]; + +int KB_ConvertString( char *in, char **ppout ); + +DECLARE_MESSAGE( m_Menu, ShowMenu ); + +int CHudMenu :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( ShowMenu ); + + InitHUDData(); + + return 1; +} + +void CHudMenu :: InitHUDData( void ) +{ + m_fMenuDisplayed = 0; + m_bitsValidSlots = 0; + Reset(); +} + +void CHudMenu :: Reset( void ) +{ + g_szPrelocalisedMenuString[0] = 0; + m_fWaitingForMore = FALSE; +} + +int CHudMenu :: VidInit( void ) +{ + return 1; +} + +int CHudMenu :: Draw( float flTime ) +{ + // check for if menu is set to disappear + if ( m_flShutoffTime > 0 ) + { + if ( m_flShutoffTime <= gHUD.m_flTime ) + { // times up, shutoff + m_fMenuDisplayed = 0; + m_iFlags &= ~HUD_ACTIVE; + return 1; + } + } + + // don't draw the menu if the scoreboard is being shown + if ( gViewPort && gViewPort->IsScoreBoardVisible() ) + return 1; + + // draw the menu, along the left-hand side of the screen + + // count the number of newlines + int nlc = 0; + int i; + for ( i = 0; i < MAX_MENU_STRING && g_szMenuString[i] != '\0'; i++ ) + { + if ( g_szMenuString[i] == '\n' ) + nlc++; + } + + // center it + int y = (ScreenHeight/2) - ((nlc/2)*12) - 40; // make sure it is above the say text + int x = 20; + + i = 0; + while ( i < MAX_MENU_STRING && g_szMenuString[i] != '\0' ) + { + gHUD.DrawHudString( x, y, 320, g_szMenuString + i, 255, 255, 255 ); + y += 12; + + while ( i < MAX_MENU_STRING && g_szMenuString[i] != '\0' && g_szMenuString[i] != '\n' ) + i++; + if ( g_szMenuString[i] == '\n' ) + i++; + } + + return 1; +} + +// selects an item from the menu +void CHudMenu :: SelectMenuItem( int menu_item ) +{ + // if menu_item is in a valid slot, send a menuselect command to the server + if ( (menu_item > 0) && (m_bitsValidSlots & (1 << (menu_item-1))) ) + { + char szbuf[32]; + sprintf( szbuf, "menuselect %d\n", menu_item ); + ClientCmd( szbuf ); + + // remove the menu + m_fMenuDisplayed = 0; + m_iFlags &= ~HUD_ACTIVE; + } +} + + +// Message handler for ShowMenu message +// takes four values: +// short: a bitfield of keys that are valid input +// char : the duration, in seconds, the menu should stay up. -1 means is stays until something is chosen. +// byte : a boolean, TRUE if there is more string yet to be received before displaying the menu, FALSE if it's the last string +// string: menu string to display +// if this message is never received, then scores will simply be the combined totals of the players. +int CHudMenu :: MsgFunc_ShowMenu( const char *pszName, int iSize, void *pbuf ) +{ + char *temp = NULL; + + BEGIN_READ( pbuf, iSize ); + + m_bitsValidSlots = READ_SHORT(); + int DisplayTime = READ_CHAR(); + int NeedMore = READ_BYTE(); + + if ( DisplayTime > 0 ) + m_flShutoffTime = DisplayTime + gHUD.m_flTime; + else + m_flShutoffTime = -1; + + if ( m_bitsValidSlots ) + { + if ( !m_fWaitingForMore ) // this is the start of a new menu + { + strncpy( g_szPrelocalisedMenuString, READ_STRING(), MAX_MENU_STRING ); + } + else + { // append to the current menu string + strncat( g_szPrelocalisedMenuString, READ_STRING(), MAX_MENU_STRING - strlen(g_szPrelocalisedMenuString) ); + } + g_szPrelocalisedMenuString[MAX_MENU_STRING-1] = 0; // ensure null termination (strncat/strncpy does not) + + if ( !NeedMore ) + { // we have the whole string, so we can localise it now + strcpy( g_szMenuString, gHUD.m_TextMessage.BufferedLocaliseTextString( g_szPrelocalisedMenuString ) ); + + // Swap in characters + if ( KB_ConvertString( g_szMenuString, &temp ) ) + { + strcpy( g_szMenuString, temp ); + free( temp ); + } + } + + m_fMenuDisplayed = 1; + m_iFlags |= HUD_ACTIVE; + } + else + { + m_fMenuDisplayed = 0; // no valid slots means that the menu should be turned off + m_iFlags &= ~HUD_ACTIVE; + } + + m_fWaitingForMore = NeedMore; + + return 1; +} diff --git a/hlsdk/cl_dll/message.cpp b/hlsdk/cl_dll/message.cpp new file mode 100644 index 00000000..61ea55a7 --- /dev/null +++ b/hlsdk/cl_dll/message.cpp @@ -0,0 +1,536 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Message.cpp +// +// implementation of CHudMessage class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE( m_Message, HudText ) +DECLARE_MESSAGE( m_Message, GameTitle ) + +// 1 Global client_textmessage_t for custom messages that aren't in the titles.txt +client_textmessage_t g_pCustomMessage; +char *g_pCustomName = "Custom"; +char g_pCustomText[1024]; + +int CHudMessage::Init(void) +{ + HOOK_MESSAGE( HudText ); + HOOK_MESSAGE( GameTitle ); + + gHUD.AddHudElem(this); + + Reset(); + + return 1; +}; + +int CHudMessage::VidInit( void ) +{ + m_HUD_title_half = gHUD.GetSpriteIndex( "title_half" ); + m_HUD_title_life = gHUD.GetSpriteIndex( "title_life" ); + + return 1; +}; + + +void CHudMessage::Reset( void ) +{ + memset( m_pMessages, 0, sizeof( m_pMessages[0] ) * maxHUDMessages ); + memset( m_startTime, 0, sizeof( m_startTime[0] ) * maxHUDMessages ); + + m_gameTitleTime = 0; + m_pGameTitle = NULL; +} + + +float CHudMessage::FadeBlend( float fadein, float fadeout, float hold, float localTime ) +{ + float fadeTime = fadein + hold; + float fadeBlend; + + if ( localTime < 0 ) + return 0; + + if ( localTime < fadein ) + { + fadeBlend = 1 - ((fadein - localTime) / fadein); + } + else if ( localTime > fadeTime ) + { + if ( fadeout > 0 ) + fadeBlend = 1 - ((localTime - fadeTime) / fadeout); + else + fadeBlend = 0; + } + else + fadeBlend = 1; + + return fadeBlend; +} + + +int CHudMessage::XPosition( float x, int width, int totalWidth ) +{ + int xPos; + + if ( x == -1 ) + { + xPos = (ScreenWidth - width) / 2; + } + else + { + if ( x < 0 ) + xPos = (1.0 + x) * ScreenWidth - totalWidth; // Alight right + else + xPos = x * ScreenWidth; + } + + if ( xPos + width > ScreenWidth ) + xPos = ScreenWidth - width; + else if ( xPos < 0 ) + xPos = 0; + + return xPos; +} + + +int CHudMessage::YPosition( float y, int height ) +{ + int yPos; + + if ( y == -1 ) // Centered? + yPos = (ScreenHeight - height) * 0.5; + else + { + // Alight bottom? + if ( y < 0 ) + yPos = (1.0 + y) * ScreenHeight - height; // Alight bottom + else // align top + yPos = y * ScreenHeight; + } + + if ( yPos + height > ScreenHeight ) + yPos = ScreenHeight - height; + else if ( yPos < 0 ) + yPos = 0; + + return yPos; +} + + +void CHudMessage::MessageScanNextChar( void ) +{ + int srcRed, srcGreen, srcBlue, destRed, destGreen, destBlue; + int blend; + + srcRed = m_parms.pMessage->r1; + srcGreen = m_parms.pMessage->g1; + srcBlue = m_parms.pMessage->b1; + blend = 0; // Pure source + + switch( m_parms.pMessage->effect ) + { + // Fade-in / Fade-out + case 0: + case 1: + destRed = destGreen = destBlue = 0; + blend = m_parms.fadeBlend; + break; + + case 2: + m_parms.charTime += m_parms.pMessage->fadein; + if ( m_parms.charTime > m_parms.time ) + { + srcRed = srcGreen = srcBlue = 0; + blend = 0; // pure source + } + else + { + float deltaTime = m_parms.time - m_parms.charTime; + + destRed = destGreen = destBlue = 0; + if ( m_parms.time > m_parms.fadeTime ) + { + blend = m_parms.fadeBlend; + } + else if ( deltaTime > m_parms.pMessage->fxtime ) + blend = 0; // pure dest + else + { + destRed = m_parms.pMessage->r2; + destGreen = m_parms.pMessage->g2; + destBlue = m_parms.pMessage->b2; + blend = 255 - (deltaTime * (1.0/m_parms.pMessage->fxtime) * 255.0 + 0.5); + } + } + break; + } + if ( blend > 255 ) + blend = 255; + else if ( blend < 0 ) + blend = 0; + + m_parms.r = ((srcRed * (255-blend)) + (destRed * blend)) >> 8; + m_parms.g = ((srcGreen * (255-blend)) + (destGreen * blend)) >> 8; + m_parms.b = ((srcBlue * (255-blend)) + (destBlue * blend)) >> 8; + + if ( m_parms.pMessage->effect == 1 && m_parms.charTime != 0 ) + { + if ( m_parms.x >= 0 && m_parms.y >= 0 && (m_parms.x + gHUD.m_scrinfo.charWidths[ m_parms.text ]) <= ScreenWidth ) + TextMessageDrawChar( m_parms.x, m_parms.y, m_parms.text, m_parms.pMessage->r2, m_parms.pMessage->g2, m_parms.pMessage->b2 ); + } +} + + +void CHudMessage::MessageScanStart( void ) +{ + switch( m_parms.pMessage->effect ) + { + // Fade-in / out with flicker + case 1: + case 0: + m_parms.fadeTime = m_parms.pMessage->fadein + m_parms.pMessage->holdtime; + + + if ( m_parms.time < m_parms.pMessage->fadein ) + { + m_parms.fadeBlend = ((m_parms.pMessage->fadein - m_parms.time) * (1.0/m_parms.pMessage->fadein) * 255); + } + else if ( m_parms.time > m_parms.fadeTime ) + { + if ( m_parms.pMessage->fadeout > 0 ) + m_parms.fadeBlend = (((m_parms.time - m_parms.fadeTime) / m_parms.pMessage->fadeout) * 255); + else + m_parms.fadeBlend = 255; // Pure dest (off) + } + else + m_parms.fadeBlend = 0; // Pure source (on) + m_parms.charTime = 0; + + if ( m_parms.pMessage->effect == 1 && (rand()%100) < 10 ) + m_parms.charTime = 1; + break; + + case 2: + m_parms.fadeTime = (m_parms.pMessage->fadein * m_parms.length) + m_parms.pMessage->holdtime; + + if ( m_parms.time > m_parms.fadeTime && m_parms.pMessage->fadeout > 0 ) + m_parms.fadeBlend = (((m_parms.time - m_parms.fadeTime) / m_parms.pMessage->fadeout) * 255); + else + m_parms.fadeBlend = 0; + break; + } +} + + +void CHudMessage::MessageDrawScan( client_textmessage_t *pMessage, float time ) +{ + int i, j, length, width; + const char *pText; + unsigned char line[80]; + + pText = pMessage->pMessage; + // Count lines + m_parms.lines = 1; + m_parms.time = time; + m_parms.pMessage = pMessage; + length = 0; + width = 0; + m_parms.totalWidth = 0; + while ( *pText ) + { + if ( *pText == '\n' ) + { + m_parms.lines++; + if ( width > m_parms.totalWidth ) + m_parms.totalWidth = width; + width = 0; + } + else + width += gHUD.m_scrinfo.charWidths[*pText]; + pText++; + length++; + } + m_parms.length = length; + m_parms.totalHeight = (m_parms.lines * gHUD.m_scrinfo.iCharHeight); + + + m_parms.y = YPosition( pMessage->y, m_parms.totalHeight ); + pText = pMessage->pMessage; + + m_parms.charTime = 0; + + MessageScanStart(); + + for ( i = 0; i < m_parms.lines; i++ ) + { + m_parms.lineLength = 0; + m_parms.width = 0; + while ( *pText && *pText != '\n' ) + { + unsigned char c = *pText; + line[m_parms.lineLength] = c; + m_parms.width += gHUD.m_scrinfo.charWidths[c]; + m_parms.lineLength++; + pText++; + } + pText++; // Skip LF + line[m_parms.lineLength] = 0; + + m_parms.x = XPosition( pMessage->x, m_parms.width, m_parms.totalWidth ); + + for ( j = 0; j < m_parms.lineLength; j++ ) + { + m_parms.text = line[j]; + int next = m_parms.x + gHUD.m_scrinfo.charWidths[ m_parms.text ]; + MessageScanNextChar(); + + if ( m_parms.x >= 0 && m_parms.y >= 0 && next <= ScreenWidth ) + TextMessageDrawChar( m_parms.x, m_parms.y, m_parms.text, m_parms.r, m_parms.g, m_parms.b ); + m_parms.x = next; + } + + m_parms.y += gHUD.m_scrinfo.iCharHeight; + } +} + + +int CHudMessage::Draw( float fTime ) +{ + int i, drawn; + client_textmessage_t *pMessage; + float endTime; + + drawn = 0; + + if ( m_gameTitleTime > 0 ) + { + float localTime = gHUD.m_flTime - m_gameTitleTime; + float brightness; + + // Maybe timer isn't set yet + if ( m_gameTitleTime > gHUD.m_flTime ) + m_gameTitleTime = gHUD.m_flTime; + + if ( localTime > (m_pGameTitle->fadein + m_pGameTitle->holdtime + m_pGameTitle->fadeout) ) + m_gameTitleTime = 0; + else + { + brightness = FadeBlend( m_pGameTitle->fadein, m_pGameTitle->fadeout, m_pGameTitle->holdtime, localTime ); + + int halfWidth = gHUD.GetSpriteRect(m_HUD_title_half).right - gHUD.GetSpriteRect(m_HUD_title_half).left; + int fullWidth = halfWidth + gHUD.GetSpriteRect(m_HUD_title_life).right - gHUD.GetSpriteRect(m_HUD_title_life).left; + int fullHeight = gHUD.GetSpriteRect(m_HUD_title_half).bottom - gHUD.GetSpriteRect(m_HUD_title_half).top; + + int x = XPosition( m_pGameTitle->x, fullWidth, fullWidth ); + int y = YPosition( m_pGameTitle->y, fullHeight ); + + + SPR_Set( gHUD.GetSprite(m_HUD_title_half), brightness * m_pGameTitle->r1, brightness * m_pGameTitle->g1, brightness * m_pGameTitle->b1 ); + SPR_DrawAdditive( 0, x, y, &gHUD.GetSpriteRect(m_HUD_title_half) ); + + SPR_Set( gHUD.GetSprite(m_HUD_title_life), brightness * m_pGameTitle->r1, brightness * m_pGameTitle->g1, brightness * m_pGameTitle->b1 ); + SPR_DrawAdditive( 0, x + halfWidth, y, &gHUD.GetSpriteRect(m_HUD_title_life) ); + + drawn = 1; + } + } + // Fixup level transitions + for ( i = 0; i < maxHUDMessages; i++ ) + { + // Assume m_parms.time contains last time + if ( m_pMessages[i] ) + { + pMessage = m_pMessages[i]; + if ( m_startTime[i] > gHUD.m_flTime ) + m_startTime[i] = gHUD.m_flTime + m_parms.time - m_startTime[i] + 0.2; // Server takes 0.2 seconds to spawn, adjust for this + } + } + + for ( i = 0; i < maxHUDMessages; i++ ) + { + if ( m_pMessages[i] ) + { + pMessage = m_pMessages[i]; + + // This is when the message is over + switch( pMessage->effect ) + { + case 0: + case 1: + endTime = m_startTime[i] + pMessage->fadein + pMessage->fadeout + pMessage->holdtime; + break; + + // Fade in is per character in scanning messages + case 2: + endTime = m_startTime[i] + (pMessage->fadein * strlen( pMessage->pMessage )) + pMessage->fadeout + pMessage->holdtime; + break; + } + + if ( fTime <= endTime ) + { + float messageTime = fTime - m_startTime[i]; + + // Draw the message + // effect 0 is fade in/fade out + // effect 1 is flickery credits + // effect 2 is write out (training room) + MessageDrawScan( pMessage, messageTime ); + + drawn++; + } + else + { + // The message is over + m_pMessages[i] = NULL; + } + } + } + + // Remember the time -- to fix up level transitions + m_parms.time = gHUD.m_flTime; + // Don't call until we get another message + if ( !drawn ) + m_iFlags &= ~HUD_ACTIVE; + + return 1; +} + + +void CHudMessage::MessageAdd( const char *pName, float time ) +{ + int i,j; + client_textmessage_t *tempMessage; + + for ( i = 0; i < maxHUDMessages; i++ ) + { + if ( !m_pMessages[i] ) + { + // Trim off a leading # if it's there + if ( pName[0] == '#' ) + tempMessage = TextMessageGet( pName+1 ); + else + tempMessage = TextMessageGet( pName ); + // If we couldnt find it in the titles.txt, just create it + if ( !tempMessage ) + { + g_pCustomMessage.effect = 2; + g_pCustomMessage.r1 = g_pCustomMessage.g1 = g_pCustomMessage.b1 = g_pCustomMessage.a1 = 100; + g_pCustomMessage.r2 = 240; + g_pCustomMessage.g2 = 110; + g_pCustomMessage.b2 = 0; + g_pCustomMessage.a2 = 0; + g_pCustomMessage.x = -1; // Centered + g_pCustomMessage.y = 0.7; + g_pCustomMessage.fadein = 0.01; + g_pCustomMessage.fadeout = 1.5; + g_pCustomMessage.fxtime = 0.25; + g_pCustomMessage.holdtime = 5; + g_pCustomMessage.pName = g_pCustomName; + strcpy( g_pCustomText, pName ); + g_pCustomMessage.pMessage = g_pCustomText; + + tempMessage = &g_pCustomMessage; + } + + for ( j = 0; j < maxHUDMessages; j++ ) + { + if ( m_pMessages[j] ) + { + // is this message already in the list + if ( !strcmp( tempMessage->pMessage, m_pMessages[j]->pMessage ) ) + { + return; + } + + // get rid of any other messages in same location (only one displays at a time) + if ( fabs( tempMessage->y - m_pMessages[j]->y ) < 0.0001 ) + { + if ( fabs( tempMessage->x - m_pMessages[j]->x ) < 0.0001 ) + { + m_pMessages[j] = NULL; + } + } + } + } + + m_pMessages[i] = tempMessage; + m_startTime[i] = time; + return; + } + } +} + + +int CHudMessage::MsgFunc_HudText( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + char *pString = READ_STRING(); + + MessageAdd( pString, gHUD.m_flTime ); + // Remember the time -- to fix up level transitions + m_parms.time = gHUD.m_flTime; + + // Turn on drawing + if ( !(m_iFlags & HUD_ACTIVE) ) + m_iFlags |= HUD_ACTIVE; + + return 1; +} + + +int CHudMessage::MsgFunc_GameTitle( const char *pszName, int iSize, void *pbuf ) +{ + m_pGameTitle = TextMessageGet( "GAMETITLE" ); + if ( m_pGameTitle != NULL ) + { + m_gameTitleTime = gHUD.m_flTime; + + // Turn on drawing + if ( !(m_iFlags & HUD_ACTIVE) ) + m_iFlags |= HUD_ACTIVE; + } + + return 1; +} + +void CHudMessage::MessageAdd(client_textmessage_t * newMessage ) +{ + m_parms.time = gHUD.m_flTime; + + // Turn on drawing + if ( !(m_iFlags & HUD_ACTIVE) ) + m_iFlags |= HUD_ACTIVE; + + for ( int i = 0; i < maxHUDMessages; i++ ) + { + if ( !m_pMessages[i] ) + { + m_pMessages[i] = newMessage; + m_startTime[i] = gHUD.m_flTime; + return; + } + } + +} diff --git a/hlsdk/cl_dll/msvc6/cl_dll.dsp b/hlsdk/cl_dll/msvc6/cl_dll.dsp new file mode 100644 index 00000000..45cd2da4 --- /dev/null +++ b/hlsdk/cl_dll/msvc6/cl_dll.dsp @@ -0,0 +1,608 @@ +# Microsoft Developer Studio Project File - Name="cl_dll" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=cl_dll - 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 "cl_dll.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 "cl_dll.mak" CFG="cl_dll - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "cl_dll - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "cl_dll - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "cl_dll - 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 ".\Release" +# PROP Intermediate_Dir ".\Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /MT /W3 /GX /Zi /O2 /I "..\..\utils\vgui\include" /I "..\..\engine" /I "..\..\common" /I "..\..\pm_shared" /I "..\..\dlls" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "CLIENT_DLL" /D "CLIENT_WEAPONS" /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 winmm.lib ../../utils/vgui/lib/win32_vc6/vgui.lib wsock32.lib /nologo /subsystem:windows /dll /map /machine:I386 /out:".\Release\client.dll" + +!ELSEIF "$(CFG)" == "cl_dll - 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 ".\Debug" +# PROP Intermediate_Dir ".\Debug" +# 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 /GR /GX /ZI /Od /I "..\..\dlls" /I "..\..\common" /I "..\..\pm_shared" /I "..\..\engine" /I "..\..\utils\vgui\include" /I "..\..\game_shared" /D "_DEBUG" /D "_MBCS" /D "WIN32" /D "_WINDOWS" /D "CLIENT_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 /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 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" + +!ENDIF + +# Begin Target + +# Name "cl_dll - Win32 Release" +# Name "cl_dll - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Group "hl" + +# PROP Default_Filter "*.CPP" +# Begin Source File + +SOURCE=..\..\dlls\crossbow.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\dlls\crowbar.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\dlls\egon.cpp +# End Source File +# Begin Source File + +SOURCE=..\ev_hldm.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 +# End Source File +# Begin Source File + +SOURCE=..\..\common\interface.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\dlls\mp5.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\dlls\python.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\dlls\rpg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\dlls\satchel.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\dlls\shotgun.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\dlls\squeakgrenade.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\dlls\tripmine.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vgui_scrollbar2.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vgui_slider2.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\voice_banmgr.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\voice_status.cpp +# End Source File +# End Group +# Begin Source File + +SOURCE=..\ammo.cpp +# End Source File +# Begin Source File + +SOURCE=..\ammo_secondary.cpp +# End Source File +# Begin Source File + +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 + +SOURCE=..\com_weapons.cpp +# End Source File +# Begin Source File + +SOURCE=..\death.cpp +# End Source File +# Begin Source File + +SOURCE=..\demo.cpp +# End Source File +# Begin Source File + +SOURCE=..\entity.cpp +# End Source File +# Begin Source File + +SOURCE=..\ev_common.cpp +# End Source File +# Begin Source File + +SOURCE=..\events.cpp +# End Source File +# Begin Source File + +SOURCE=..\flashlight.cpp +# End Source File +# Begin Source File + +SOURCE=..\GameStudioModelRenderer.cpp +# End Source File +# Begin Source File + +SOURCE=..\geiger.cpp +# End Source File +# Begin Source File + +SOURCE=..\health.cpp +# End Source File +# Begin Source File + +SOURCE=..\hud.cpp +# End Source File +# Begin Source File + +SOURCE=..\hud_msg.cpp +# End Source File +# Begin Source File + +SOURCE=..\hud_redraw.cpp +# End Source File +# Begin Source File + +SOURCE=..\hud_servers.cpp +# End Source File +# Begin Source File + +SOURCE=..\hud_spectator.cpp +# End Source File +# Begin Source File + +SOURCE=..\hud_update.cpp +# End Source File +# Begin Source File + +SOURCE=..\in_camera.cpp +# End Source File +# Begin Source File + +SOURCE=..\input.cpp +# End Source File +# Begin Source File + +SOURCE=..\inputw32.cpp +# End Source File +# Begin Source File + +SOURCE=..\menu.cpp +# End Source File +# Begin Source File + +SOURCE=..\message.cpp +# End Source File +# Begin Source File + +SOURCE=..\overview.cpp +# PROP Exclude_From_Build 1 +# End Source File +# Begin Source File + +SOURCE=..\parsemsg.cpp +# End Source File +# Begin Source File + +SOURCE=..\parsemsg.h +# 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=..\saytext.cpp +# End Source File +# Begin Source File + +SOURCE=..\status_icons.cpp +# End Source File +# Begin Source File + +SOURCE=..\statusbar.cpp +# End Source File +# Begin Source File + +SOURCE=..\studio_util.cpp +# End Source File +# Begin Source File + +SOURCE=..\StudioModelRenderer.cpp +# End Source File +# Begin Source File + +SOURCE=..\text_message.cpp +# End Source File +# Begin Source File + +SOURCE=..\train.cpp +# End Source File +# Begin Source File + +SOURCE=..\tri.cpp +# End Source File +# Begin Source File + +SOURCE=..\util.cpp +# End Source File +# Begin Source File + +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 + +SOURCE=..\vgui_ControlConfigPanel.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_CustomObjects.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vgui_grid.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vgui_helpers.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_int.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vgui_listbox.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vgui_loadtga.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_MOTDWindow.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_SchemeManager.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_ScorePanel.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_ServerBrowser.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_SpectatorPanel.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_TeamFortressViewport.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_teammenu.cpp +# End Source File +# Begin Source File + +SOURCE=..\view.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=..\ammo.h +# End Source File +# Begin Source File + +SOURCE=..\ammohistory.h +# End Source File +# Begin Source File + +SOURCE=..\camera.h +# End Source File +# Begin Source File + +SOURCE=..\cl_dll.h +# End Source File +# Begin Source File + +SOURCE=..\com_weapons.h +# End Source File +# Begin Source File + +SOURCE=..\demo.h +# End Source File +# Begin Source File + +SOURCE=..\ev_hldm.h +# End Source File +# Begin Source File + +SOURCE=..\eventscripts.h +# End Source File +# Begin Source File + +SOURCE=..\GameStudioModelRenderer.h +# End Source File +# Begin Source File + +SOURCE=..\health.h +# End Source File +# Begin Source File + +SOURCE=..\hud.h +# End Source File +# Begin Source File + +SOURCE=..\hud_iface.h +# End Source File +# Begin Source File + +SOURCE=..\hud_servers.h +# End Source File +# Begin Source File + +SOURCE=..\hud_servers_priv.h +# End Source File +# Begin Source File + +SOURCE=..\hud_spectator.h +# End Source File +# Begin Source File + +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 + +SOURCE=..\overview.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=..\studio_util.h +# End Source File +# Begin Source File + +SOURCE=..\StudioModelRenderer.h +# End Source File +# Begin Source File + +SOURCE=..\util.h +# End Source File +# Begin Source File + +SOURCE=..\util_vector.h +# End Source File +# Begin Source File + +SOURCE=..\vgui_ConsolePanel.h +# End Source File +# Begin Source File + +SOURCE=..\vgui_ControlConfigPanel.h +# End Source File +# Begin Source File + +SOURCE=..\vgui_int.h +# End Source File +# Begin Source File + +SOURCE=..\vgui_SchemeManager.h +# End Source File +# Begin Source File + +SOURCE=..\vgui_ScorePanel.h +# End Source File +# Begin Source File + +SOURCE=..\vgui_ServerBrowser.h +# End Source File +# Begin Source File + +SOURCE=..\vgui_SpectatorPanel.h +# End Source File +# Begin Source File + +SOURCE=..\vgui_TeamFortressViewport.h +# End Source File +# Begin Source File + +SOURCE=..\view.h +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\voice_banmgr.h +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\voice_status.h +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\voice_vgui_tweakdlg.h +# End Source File +# Begin Source File + +SOURCE=..\wrect.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/hlsdk/cl_dll/msvc7/cl_dll.vcproj b/hlsdk/cl_dll/msvc7/cl_dll.vcproj new file mode 100644 index 00000000..619c0b50 --- /dev/null +++ b/hlsdk/cl_dll/msvc7/cl_dll.vcproj @@ -0,0 +1,1944 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hlsdk/cl_dll/msvc8/cl_dll.vcproj b/hlsdk/cl_dll/msvc8/cl_dll.vcproj new file mode 100644 index 00000000..a2c3789b --- /dev/null +++ b/hlsdk/cl_dll/msvc8/cl_dll.vcproj @@ -0,0 +1,2462 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hlsdk/cl_dll/overview.cpp b/hlsdk/cl_dll/overview.cpp new file mode 100644 index 00000000..dd7741f0 --- /dev/null +++ b/hlsdk/cl_dll/overview.cpp @@ -0,0 +1,160 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "hud.h" +#include "cl_util.h" +#include "cl_entity.h" +#include "triangleapi.h" +#include "vgui_TeamFortressViewport.h" + +// these are included for the math functions +#include "com_model.h" +#include "studio_util.h" + +#pragma warning(disable: 4244) + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CHudOverview::Init() +{ + gHUD.AddHudElem(this); + + m_iFlags |= HUD_ACTIVE; + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Loads new icons +//----------------------------------------------------------------------------- +int CHudOverview::VidInit() +{ + m_hsprPlayer = gEngfuncs.pfnSPR_Load("sprites/ring.spr"); + m_hsprViewcone = gEngfuncs.pfnSPR_Load("sprites/camera.spr"); + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flTime - +// intermission - +//----------------------------------------------------------------------------- +int CHudOverview::Draw(float flTime) +{ + // only draw in overview mode + if (!gEngfuncs.Overview_GetOverviewState()) + return 1; + + // make sure we have player info + gViewPort->GetAllPlayersInfo(); + + // calculate player size on the overview + int x1, y1, x2, y2; + float v0[3]={0,0,0}, v1[3]={64,64,0}; + gEngfuncs.Overview_WorldToScreen(v0, &x1, &y1); + gEngfuncs.Overview_WorldToScreen(v1, &x2, &y2); + float scale = abs(x2 - x1); + + // loop through all the players and draw them on the map + for (int i = 1; i < MAX_PLAYERS; i++) + { + cl_entity_t *pl = gEngfuncs.GetEntityByIndex(i); + + if (pl && pl->player && pl->curstate.health > 0 && pl->curstate.solid != SOLID_NOT) + { + int x, y, z = 0; + float v[3]={pl->origin[0], pl->origin[1], 0}; + gEngfuncs.Overview_WorldToScreen(v, &x, &y); + + // hack in some team colors + float r, g, bc; + if (g_PlayerExtraInfo[i].teamnumber == 1) + { + r = 0.0f; g = 0.0f; bc = 1.0f; + } + else if (g_PlayerExtraInfo[i].teamnumber == 2) + { + r = 1.0f; g = 0.0f; bc = 0.0f; + } + else + { + // just use the default orange color if the team isn't set + r = 1.0f; g = 0.7f; bc = 0.0f; + } + + // set the current texture + gEngfuncs.pTriAPI->SpriteTexture((struct model_s *)gEngfuncs.GetSpritePointer(m_hsprPlayer), 0); + + // additive render mode + gEngfuncs.pTriAPI->RenderMode(kRenderTransAdd); + + // no culling + gEngfuncs.pTriAPI->CullFace(TRI_NONE); + + // draw a square + gEngfuncs.pTriAPI->Begin(TRI_QUADS); + + // set the color to be that of the team + gEngfuncs.pTriAPI->Color4f(r, g, bc, 1.0f); + + // calculate rotational matrix + vec3_t a, b, angles; + float rmatrix[3][4]; // transformation matrix + VectorCopy(pl->angles, angles); + angles[0] = 0.0f; + angles[1] += 90.f; + angles[1] = -angles[1]; + angles[2] = 0.0f; + AngleMatrix(angles, rmatrix); + a[2] = 0; + + a[0] = -scale; a[1] = -scale; + VectorTransform(a, rmatrix , b ); + gEngfuncs.pTriAPI->TexCoord2f( 0, 0 ); + gEngfuncs.pTriAPI->Vertex3f(x + b[0], y + b[1], z); + + a[0]=-scale; a[1] = scale; + VectorTransform(a, rmatrix , b ); + gEngfuncs.pTriAPI->TexCoord2f( 0, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x + b[0], y + b[1], z); + + a[0]=scale; a[1] = scale; + VectorTransform(a, rmatrix , b ); + gEngfuncs.pTriAPI->TexCoord2f( 1, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x + b[0], y + b[1], z); + + a[0]=scale; a[1] = -scale; + VectorTransform(a, rmatrix , b ); + gEngfuncs.pTriAPI->TexCoord2f( 1, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x + b[0], y + b[1], z); + + // finish up + gEngfuncs.pTriAPI->End(); + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); + + // draw the players name and health underneath + char string[256]; + sprintf(string, "%s (%i%%)", g_PlayerInfoList[i].name, pl->curstate.health); + DrawConsoleString(x, y + (1.1 * scale), string); + } + } + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: called every time a server is connected to +//----------------------------------------------------------------------------- +void CHudOverview::InitHUDData() +{ +// this block would force the spectator view to be on +// gEngfuncs.Overview_SetDrawOverview( 1 ); +// gEngfuncs.Overview_SetDrawInset( 0 ); +} + diff --git a/hlsdk/cl_dll/overview.h b/hlsdk/cl_dll/overview.h new file mode 100644 index 00000000..7f0502e3 --- /dev/null +++ b/hlsdk/cl_dll/overview.h @@ -0,0 +1,31 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef OVERVIEW_H +#define OVERVIEW_H +#pragma once + + +//----------------------------------------------------------------------------- +// Purpose: Handles the drawing of the top-down map and all the things on it +//----------------------------------------------------------------------------- +class CHudOverview : public CHudBase +{ +public: + int Init(); + int VidInit(); + + int Draw(float flTime); + void InitHUDData( void ); + +private: + HSPRITE m_hsprPlayer; + HSPRITE m_hsprViewcone; +}; + + +#endif // OVERVIEW_H diff --git a/hlsdk/cl_dll/parsemsg.cpp b/hlsdk/cl_dll/parsemsg.cpp new file mode 100644 index 00000000..bdfa4136 --- /dev/null +++ b/hlsdk/cl_dll/parsemsg.cpp @@ -0,0 +1,166 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// parsemsg.cpp +// +typedef unsigned char byte; +#define true 1 + +static byte *gpBuf; +static int giSize; +static int giRead; +static int giBadRead; + +void BEGIN_READ( void *buf, int size ) +{ + giRead = 0; + giBadRead = 0; + giSize = size; + gpBuf = (byte*)buf; +} + + +int READ_CHAR( void ) +{ + int c; + + if (giRead + 1 > giSize) + { + giBadRead = true; + return -1; + } + + c = (signed char)gpBuf[giRead]; + giRead++; + + return c; +} + +int READ_BYTE( void ) +{ + int c; + + if (giRead+1 > giSize) + { + giBadRead = true; + return -1; + } + + c = (unsigned char)gpBuf[giRead]; + giRead++; + + return c; +} + +int READ_SHORT( void ) +{ + int c; + + if (giRead+2 > giSize) + { + giBadRead = true; + return -1; + } + + c = (short)( gpBuf[giRead] + ( gpBuf[giRead+1] << 8 ) ); + + giRead += 2; + + return c; +} + +int READ_WORD( void ) +{ + return READ_SHORT(); +} + + +int READ_LONG( void ) +{ + int c; + + if (giRead+4 > giSize) + { + giBadRead = true; + return -1; + } + + c = gpBuf[giRead] + (gpBuf[giRead + 1] << 8) + (gpBuf[giRead + 2] << 16) + (gpBuf[giRead + 3] << 24); + + giRead += 4; + + return c; +} + +float READ_FLOAT( void ) +{ + union + { + byte b[4]; + float f; + int l; + } dat; + + dat.b[0] = gpBuf[giRead]; + dat.b[1] = gpBuf[giRead+1]; + dat.b[2] = gpBuf[giRead+2]; + dat.b[3] = gpBuf[giRead+3]; + giRead += 4; + +// dat.l = LittleLong (dat.l); + + return dat.f; +} + +char* READ_STRING( void ) +{ + static char string[2048]; + int l,c; + + string[0] = 0; + + l = 0; + do + { + if ( giRead+1 > giSize ) + break; // no more characters + + c = READ_CHAR(); + if (c == -1 || c == 0) + break; + string[l] = c; + l++; + } while (l < sizeof(string)-1); + + string[l] = 0; + + return string; +} + +float READ_COORD( void ) +{ + return (float)(READ_SHORT() * (1.0/8)); +} + +float READ_ANGLE( void ) +{ + return (float)(READ_CHAR() * (360.0/256)); +} + +float READ_HIRESANGLE( void ) +{ + return (float)(READ_SHORT() * (360.0/65536)); +} + diff --git a/hlsdk/cl_dll/parsemsg.h b/hlsdk/cl_dll/parsemsg.h new file mode 100644 index 00000000..ab8ff154 --- /dev/null +++ b/hlsdk/cl_dll/parsemsg.h @@ -0,0 +1,40 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// parsemsg.h +// + +#define ASSERT( x ) + +void BEGIN_READ( void *buf, int size ); +int READ_CHAR( void ); +int READ_BYTE( void ); +int READ_SHORT( void ); +int READ_WORD( void ); +int READ_LONG( void ); +float READ_FLOAT( void ); +char* READ_STRING( void ); +float READ_COORD( void ); +float READ_ANGLE( void ); +float READ_HIRESANGLE( void ); + + + + + + + + + diff --git a/hlsdk/cl_dll/readme.txt b/hlsdk/cl_dll/readme.txt new file mode 100644 index 00000000..249205ca --- /dev/null +++ b/hlsdk/cl_dll/readme.txt @@ -0,0 +1,107 @@ + client dll readme.txt +------------------------- + +This file details the structure of the half-life client dll, and +how it communicates with the half-life game engine. + + +Engine callback functions: + +Drawing functions: + HSPRITE SPR_Load( char *picname ); + Loads a sprite into memory, and returns a handle to it. + + int SPR_Frames( HSPRITE sprite ); + Returns the number of frames stored in the specified sprite. + + int SPR_Height( HSPRITE x, int frame ) + Returns the height, in pixels, of a sprite at the specified frame. + Returns 0 is the frame number or the sprite handle is invalid. + + int SPR_Width( HSPRITE x, int f ) + Returns the width, in pixels, of a sprite at the specified frame. + Returns 0 is the frame number or the sprite handle is invalid. + + int SPR_Set( HSPRITE sprite, int r, int g, int b ); + Prepares a sprite about to be drawn. RBG color values are applied to the sprite at this time. + + + void SPR_Draw( int frame, int x, int y ); + Precondition: SPR_Set has already been called for a sprite. + Draws the currently active sprite to the screen, at position (x,y), where (0,0) is + the top left-hand corner of the screen. + + + void SPR_DrawHoles( int frame, int x, int y ); + Precondition: SPR_Set has already been called for a sprite. + Draws the currently active sprite to the screen. Color index #255 is treated as transparent. + + void SPR_DrawAdditive( int frame, int x, int y ); + Precondition: SPR_Set has already been called for a sprite. + Draws the currently active sprite to the screen, adding it's color values to the background. + + void SPR_EnableScissor( int x, int y, int width, int height ); + Creates a clipping rectangle. No pixels will be drawn outside the specified area. Will + stay in effect until either the next frame, or SPR_DisableScissor is called. + + void SPR_DisableScissor( void ); + Disables the effect of an SPR_EnableScissor call. + + int IsHighRes( void ); + returns 1 if the res mode is 640x480 or higher; 0 otherwise. + + int ScreenWidth( void ); + returns the screen width, in pixels. + + int ScreenHeight( void ); + returns the screen height, in pixels. + +// Sound functions + void PlaySound( char *szSound, int volume ) + plays the sound 'szSound' at the specified volume. Loads the sound if it hasn't been cached. + If it can't find the sound, it displays an error message and plays no sound. + + void PlaySound( int iSound, int volume ) + Precondition: iSound has been precached. + Plays the sound, from the precache list. + + +// Communication functions + void SendClientCmd( char *szCmdString ); + sends a command to the server, just as if the client had typed the szCmdString at the console. + + char *GetPlayerName( int entity_number ); + returns a pointer to a string, that contains the name of the specified client. + Returns NULL if the entity_number is not a client. + + + DECLARE_MESSAGE(), HOOK_MESSAGE() + These two macros bind the message sending between the entity DLL and the client DLL to + the CHud object. + + HOOK_MESSAGE( message_name ) + This is used inside CHud::Init(). It calls into the engine to hook that message + from the incoming message stream. + Precondition: There must be a function of name UserMsg_message_name declared + for CHud. Eg, CHud::UserMsg_Health() must be declared if you want to + use HOOK_MESSAGE( Health ); + + DECLARE_MESSAGE( message_name ) + For each HOOK_MESSAGE you must have an equivalent DECLARE_MESSAGE. This creates + a function which passes the hooked messages into the CHud object. + + + HOOK_COMMAND(), DECLARE_COMMAND() + These two functions declare and hook console commands into the client dll. + + HOOK_COMMAND( char *command, command_name ) + Whenever the user types the 'command' at the console, the function 'command_name' + will be called. + Precondition: There must be a function of the name UserCmd_command_name declared + for CHud. Eg, CHud::UserMsg_ShowScores() must be declared if you want to + use HOOK_COMMAND( "+showscores", ShowScores ); + + DECLARE_COMMAND( command_name ) + For each HOOK_COMMAND you must have an equivelant DECLARE_COMMAND. This creates + a function which passes the hooked commands into the CHud object. + diff --git a/hlsdk/cl_dll/saytext.cpp b/hlsdk/cl_dll/saytext.cpp new file mode 100644 index 00000000..85e2c5ca --- /dev/null +++ b/hlsdk/cl_dll/saytext.cpp @@ -0,0 +1,322 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// saytext.cpp +// +// implementation of CHudSayText class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "vgui_TeamFortressViewport.h" + +extern float *GetClientColor( int clientIndex ); + +#define MAX_LINES 5 +#define MAX_CHARS_PER_LINE 256 /* it can be less than this, depending on char size */ + +// allow 20 pixels on either side of the text +#define MAX_LINE_WIDTH ( ScreenWidth - 40 ) +#define LINE_START 10 +static float SCROLL_SPEED = 5; + +static char g_szLineBuffer[ MAX_LINES + 1 ][ MAX_CHARS_PER_LINE ]; +static float *g_pflNameColors[ MAX_LINES + 1 ]; +static int g_iNameLengths[ MAX_LINES + 1 ]; +static float flScrollTime = 0; // the time at which the lines next scroll up + +static int Y_START = 0; +static int line_height = 0; + +DECLARE_MESSAGE( m_SayText, SayText ); + +int CHudSayText :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( SayText ); + + InitHUDData(); + + m_HUD_saytext = gEngfuncs.pfnRegisterVariable( "hud_saytext", "1", 0 ); + m_HUD_saytext_time = gEngfuncs.pfnRegisterVariable( "hud_saytext_time", "5", 0 ); + + m_iFlags |= HUD_INTERMISSION; // is always drawn during an intermission + + return 1; +} + + +void CHudSayText :: InitHUDData( void ) +{ + memset( g_szLineBuffer, 0, sizeof g_szLineBuffer ); + memset( g_pflNameColors, 0, sizeof g_pflNameColors ); + memset( g_iNameLengths, 0, sizeof g_iNameLengths ); +} + +int CHudSayText :: VidInit( void ) +{ + return 1; +} + + +int ScrollTextUp( void ) +{ + ConsolePrint( g_szLineBuffer[0] ); // move the first line into the console buffer + g_szLineBuffer[MAX_LINES][0] = 0; + memmove( g_szLineBuffer[0], g_szLineBuffer[1], sizeof(g_szLineBuffer) - sizeof(g_szLineBuffer[0]) ); // overwrite the first line + memmove( &g_pflNameColors[0], &g_pflNameColors[1], sizeof(g_pflNameColors) - sizeof(g_pflNameColors[0]) ); + memmove( &g_iNameLengths[0], &g_iNameLengths[1], sizeof(g_iNameLengths) - sizeof(g_iNameLengths[0]) ); + g_szLineBuffer[MAX_LINES-1][0] = 0; + + if ( g_szLineBuffer[0][0] == ' ' ) // also scroll up following lines + { + g_szLineBuffer[0][0] = 2; + return 1 + ScrollTextUp(); + } + + return 1; +} + +int CHudSayText :: Draw( float flTime ) +{ + int y = Y_START; + + if ( ( gViewPort && gViewPort->AllowedToPrintText() == FALSE) || !m_HUD_saytext->value ) + return 1; + + // make sure the scrolltime is within reasonable bounds, to guard against the clock being reset + flScrollTime = min( flScrollTime, flTime + m_HUD_saytext_time->value ); + + // make sure the scrolltime is within reasonable bounds, to guard against the clock being reset + flScrollTime = min( flScrollTime, flTime + m_HUD_saytext_time->value ); + + if ( flScrollTime <= flTime ) + { + if ( *g_szLineBuffer[0] ) + { + flScrollTime = flTime + m_HUD_saytext_time->value; + // push the console up + ScrollTextUp(); + } + else + { // buffer is empty, just disable drawing of this section + m_iFlags &= ~HUD_ACTIVE; + } + } + + for ( int i = 0; i < MAX_LINES; i++ ) + { + if ( *g_szLineBuffer[i] ) + { + if ( *g_szLineBuffer[i] == 2 && g_pflNameColors[i] ) + { + // it's a saytext string + static char buf[MAX_PLAYER_NAME_LENGTH+32]; + + // draw the first x characters in the player color + strncpy( buf, g_szLineBuffer[i], min(g_iNameLengths[i], MAX_PLAYER_NAME_LENGTH+32) ); + buf[ min(g_iNameLengths[i], MAX_PLAYER_NAME_LENGTH+31) ] = 0; + gEngfuncs.pfnDrawSetTextColor( g_pflNameColors[i][0], g_pflNameColors[i][1], g_pflNameColors[i][2] ); + int x = DrawConsoleString( LINE_START, y, buf ); + + // color is reset after each string draw + DrawConsoleString( x, y, g_szLineBuffer[i] + g_iNameLengths[i] ); + } + else + { + // normal draw + DrawConsoleString( LINE_START, y, g_szLineBuffer[i] ); + } + } + + y += line_height; + } + + + return 1; +} + +int CHudSayText :: MsgFunc_SayText( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int client_index = READ_BYTE(); // the client who spoke the message + SayTextPrint( READ_STRING(), iSize - 1, client_index ); + + return 1; +} + +void CHudSayText :: SayTextPrint( const char *pszBuf, int iBufSize, int clientIndex ) +{ + if ( gViewPort && gViewPort->AllowedToPrintText() == FALSE ) + { + // Print it straight to the console + ConsolePrint( pszBuf ); + return; + } + + // find an empty string slot + int i; + for ( i = 0; i < MAX_LINES; i++ ) + { + if ( ! *g_szLineBuffer[i] ) + break; + } + if ( i == MAX_LINES ) + { + // force scroll buffer up + ScrollTextUp(); + i = MAX_LINES - 1; + } + + g_iNameLengths[i] = 0; + g_pflNameColors[i] = NULL; + + // if it's a say message, search for the players name in the string + if ( *pszBuf == 2 && clientIndex > 0 ) + { + GetPlayerInfo( clientIndex, &g_PlayerInfoList[clientIndex] ); + const char *pName = g_PlayerInfoList[clientIndex].name; + + if ( pName ) + { + const char *nameInString = strstr( pszBuf, pName ); + + if ( nameInString ) + { + g_iNameLengths[i] = strlen( pName ) + (nameInString - pszBuf); + g_pflNameColors[i] = GetClientColor( clientIndex ); + } + } + } + + strncpy( g_szLineBuffer[i], pszBuf, max(iBufSize -1, MAX_CHARS_PER_LINE-1) ); + + // make sure the text fits in one line + EnsureTextFitsInOneLineAndWrapIfHaveTo( i ); + + // Set scroll time + if ( i == 0 ) + { + flScrollTime = gHUD.m_flTime + m_HUD_saytext_time->value; + } + + m_iFlags |= HUD_ACTIVE; + PlaySound( "misc/talk.wav", 1 ); + + if ( ScreenHeight >= 480 ) + Y_START = ScreenHeight - 60; + else + Y_START = ScreenHeight - 45; + Y_START -= (line_height * (MAX_LINES+1)); + +} + +void CHudSayText :: EnsureTextFitsInOneLineAndWrapIfHaveTo( int line ) +{ + int line_width = 0; + GetConsoleStringSize( g_szLineBuffer[line], &line_width, &line_height ); + + if ( (line_width + LINE_START) > MAX_LINE_WIDTH ) + { // string is too long to fit on line + // scan the string until we find what word is too long, and wrap the end of the sentence after the word + int length = LINE_START; + int tmp_len = 0; + char *last_break = NULL; + for ( char *x = g_szLineBuffer[line]; *x != 0; x++ ) + { + // check for a color change, if so skip past it + if ( x[0] == '/' && x[1] == '(' ) + { + x += 2; + // skip forward until past mode specifier + while ( *x != 0 && *x != ')' ) + x++; + + if ( *x != 0 ) + x++; + + if ( *x == 0 ) + break; + } + + char buf[2]; + buf[1] = 0; + + if ( *x == ' ' && x != g_szLineBuffer[line] ) // store each line break, except for the very first character + last_break = x; + + buf[0] = *x; // get the length of the current character + GetConsoleStringSize( buf, &tmp_len, &line_height ); + length += tmp_len; + + if ( length > MAX_LINE_WIDTH ) + { // needs to be broken up + if ( !last_break ) + last_break = x-1; + + x = last_break; + + // find an empty string slot + int j; + do + { + for ( j = 0; j < MAX_LINES; j++ ) + { + if ( ! *g_szLineBuffer[j] ) + break; + } + if ( j == MAX_LINES ) + { + // need to make more room to display text, scroll stuff up then fix the pointers + int linesmoved = ScrollTextUp(); + line -= linesmoved; + last_break = last_break - (sizeof(g_szLineBuffer[0]) * linesmoved); + } + } + while ( j == MAX_LINES ); + + // copy remaining string into next buffer, making sure it starts with a space character + if ( (char)*last_break == (char)' ' ) + { + int linelen = strlen(g_szLineBuffer[j]); + int remaininglen = strlen(last_break); + + if ( (linelen - remaininglen) <= MAX_CHARS_PER_LINE ) + strcat( g_szLineBuffer[j], last_break ); + } + else + { + if ( (strlen(g_szLineBuffer[j]) - strlen(last_break) - 2) < MAX_CHARS_PER_LINE ) + { + strcat( g_szLineBuffer[j], " " ); + strcat( g_szLineBuffer[j], last_break ); + } + } + + *last_break = 0; // cut off the last string + + EnsureTextFitsInOneLineAndWrapIfHaveTo( j ); + break; + } + } + } +} \ No newline at end of file diff --git a/hlsdk/cl_dll/soundsystem.cpp b/hlsdk/cl_dll/soundsystem.cpp new file mode 100644 index 00000000..a25084c4 --- /dev/null +++ b/hlsdk/cl_dll/soundsystem.cpp @@ -0,0 +1,162 @@ +//======== (C) Copyright 1996-2002 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: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= +#include +#include +#include +#include "r_studioint.h" + +extern engine_studio_api_t IEngineStudio; + +#define RENDERTYPE_UNDEFINED 0 +#define RENDERTYPE_SOFTWARE 1 +#define RENDERTYPE_HARDWARE 2 + +#define ENGINE_LAUNCHER_API_VERSION 1 + +LPDIRECTSOUND lpDS = NULL; +LPDIRECTSOUNDBUFFER lpDSBuf = NULL; +LPHWAVEOUT lpHW = NULL; + +static HMODULE hEngine = 0; + +typedef struct engine_api_s +{ + int version; + int rendertype; + int size; + + // Functions + void ( *unused1 ) ( void ); + void ( *unused2 ) ( void ); + void ( *unused3 ) ( void ); + void ( *unused4 ) ( void ); + void ( *unused5 ) ( void ); + void ( *unused6 ) ( void ); + void ( *unused7 ) ( void ); + void ( *unused8 ) ( void ); + void ( *unused9 ) ( void ); + void ( *unused10 ) ( void ); + void ( *unused11 ) ( void ); + void ( *unused12 ) ( void ); + void ( *unused13 ) ( void ); + void ( *unused14 ) ( void ); + void ( *unused15 ) ( void ); + void ( *unused16 ) ( void ); + void ( *unused17 ) ( void ); + void ( *unused18 ) ( void ); + void ( *unused19 ) ( void ); + void ( *unused20 ) ( void ); + void ( *unused21 ) ( void ); + void ( *unused22 ) ( void ); + void ( *unused23 ) ( void ); + void ( *unused24 ) ( void ); + void ( *unused25 ) ( void ); + void ( *unused26 ) ( void ); + void ( *unused27 ) ( void ); + void ( *unused28 ) ( void ); + void ( *unused29 ) ( void ); + void ( *unused30 ) ( void ); + void ( *unused31 ) ( void ); + void ( *unused32 ) ( void ); + void ( *unused33 ) ( void ); + void ( *unused34 ) ( void ); + + void ( *S_GetDSPointer ) ( struct IDirectSound **lpDS, struct IDirectSoundBuffer **lpDSBuf ); + void *( *S_GetWAVPointer ) ( void ); + + void ( *unused35 ) ( void ); + void ( *unused36 ) ( void ); + void ( *unused37 ) ( void ); + void ( *unused38 ) ( void ); + void ( *unused39 ) ( void ); + void ( *unused40 ) ( void ); + void ( *unused41 ) ( void ); + void ( *unused42 ) ( void ); + void ( *unused43 ) ( void ); + void ( *unused44 ) ( void ); + void ( *unused45 ) ( void ); + void ( *unused46 ) ( void ); + void ( *unused47 ) ( void ); + void ( *unused48 ) ( void ); + void ( *unused49 ) ( void ); + void ( *unused50 ) ( void ); + void ( *unused51 ) ( void ); + void ( *unused52 ) ( void ); + void ( *unused53 ) ( void ); + void ( *unused54 ) ( void ); + void ( *unused55 ) ( void ); +} engine_api_t; + +static engine_api_t engineapi; + +typedef int (*engine_api_func)( int version, int size, struct engine_api_s *api ); + +//----------------------------------------------------------------------------- +// Purpose: Get launcher/engine interface from engine module +// Input : hMod - +// Output : int +//----------------------------------------------------------------------------- +int Eng_LoadFunctions( HMODULE hMod ) +{ + engine_api_func pfnEngineAPI; + + pfnEngineAPI = ( engine_api_func )GetProcAddress( hMod, "Sys_EngineAPI" ); + if ( !pfnEngineAPI ) + return 0; + + if ( !(*pfnEngineAPI)( ENGINE_LAUNCHER_API_VERSION, sizeof( engine_api_t ), &engineapi ) ) + return 0; + + // All is okay + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Load proper engine .dll and get pointer to either DSound and primary buffer or HWAVEOUT ( NT 4.0, e.g. ) +//----------------------------------------------------------------------------- +void LoadSoundAPIs( void ) +{ + hEngine = ::LoadLibrary( IEngineStudio.IsHardware() ? "hw.dll" : "sw.dll" ); + if ( hEngine ) + { + if ( Eng_LoadFunctions( hEngine ) ) + { + if ( engineapi.S_GetDSPointer && engineapi.S_GetWAVPointer ) + { + engineapi.S_GetDSPointer(&lpDS, &lpDSBuf); + lpHW = (HWAVEOUT FAR *)engineapi.S_GetWAVPointer(); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Close engine library, release sound pointers +//----------------------------------------------------------------------------- +void ShutdownSoundAPIs( void ) +{ + if( hEngine ) + { + FreeLibrary( hEngine ); + hEngine = 0; + } + + lpDS = 0; + lpDSBuf = 0; + lpHW = 0; +} diff --git a/hlsdk/cl_dll/status_icons.cpp b/hlsdk/cl_dll/status_icons.cpp new file mode 100644 index 00000000..7425629c --- /dev/null +++ b/hlsdk/cl_dll/status_icons.cpp @@ -0,0 +1,163 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// status_icons.cpp +// +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include +#include +#include "parsemsg.h" +#include "event_api.h" + +DECLARE_MESSAGE( m_StatusIcons, StatusIcon ); + +int CHudStatusIcons::Init( void ) +{ + HOOK_MESSAGE( StatusIcon ); + + gHUD.AddHudElem( this ); + + Reset(); + + return 1; +} + +int CHudStatusIcons::VidInit( void ) +{ + + return 1; +} + +void CHudStatusIcons::Reset( void ) +{ + memset( m_IconList, 0, sizeof m_IconList ); + m_iFlags &= ~HUD_ACTIVE; +} + +// Draw status icons along the left-hand side of the screen +int CHudStatusIcons::Draw( float flTime ) +{ + if (gEngfuncs.IsSpectateOnly()) + return 1; + // find starting position to draw from, along right-hand side of screen + int x = 5; + int y = ScreenHeight / 2; + + // loop through icon list, and draw any valid icons drawing up from the middle of screen + for ( int i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( m_IconList[i].spr ) + { + y -= ( m_IconList[i].rc.bottom - m_IconList[i].rc.top ) + 5; + + SPR_Set( m_IconList[i].spr, m_IconList[i].r, m_IconList[i].g, m_IconList[i].b ); + SPR_DrawAdditive( 0, x, y, &m_IconList[i].rc ); + } + } + + return 1; +} + +// Message handler for StatusIcon message +// accepts five values: +// byte : TRUE = ENABLE icon, FALSE = DISABLE icon +// string : the sprite name to display +// byte : red +// byte : green +// byte : blue +int CHudStatusIcons::MsgFunc_StatusIcon( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int ShouldEnable = READ_BYTE(); + char *pszIconName = READ_STRING(); + if ( ShouldEnable ) + { + int r = READ_BYTE(); + int g = READ_BYTE(); + int b = READ_BYTE(); + EnableIcon( pszIconName, r, g, b ); + m_iFlags |= HUD_ACTIVE; + } + else + { + DisableIcon( pszIconName ); + } + + return 1; +} + +// add the icon to the icon list, and set it's drawing color +void CHudStatusIcons::EnableIcon( char *pszIconName, unsigned char red, unsigned char green, unsigned char blue ) +{ + // check to see if the sprite is in the current list + int i; + for ( i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( !stricmp( m_IconList[i].szSpriteName, pszIconName ) ) + break; + } + + if ( i == MAX_ICONSPRITES ) + { + // icon not in list, so find an empty slot to add to + for ( i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( !m_IconList[i].spr ) + break; + } + } + + // if we've run out of space in the list, overwrite the first icon + if ( i == MAX_ICONSPRITES ) + { + i = 0; + } + + // Load the sprite and add it to the list + // the sprite must be listed in hud.txt + int spr_index = gHUD.GetSpriteIndex( pszIconName ); + m_IconList[i].spr = gHUD.GetSprite( spr_index ); + m_IconList[i].rc = gHUD.GetSpriteRect( spr_index ); + m_IconList[i].r = red; + m_IconList[i].g = green; + m_IconList[i].b = blue; + strcpy( m_IconList[i].szSpriteName, pszIconName ); + + // Hack: Play Timer sound when a grenade icon is played (in 0.8 seconds) + if ( strstr(m_IconList[i].szSpriteName, "grenade") ) + { + cl_entity_t *pthisplayer = gEngfuncs.GetLocalPlayer(); + gEngfuncs.pEventAPI->EV_PlaySound( pthisplayer->index, pthisplayer->origin, CHAN_STATIC, "weapons/timer.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); + } +} + +void CHudStatusIcons::DisableIcon( char *pszIconName ) +{ + // find the sprite is in the current list + for ( int i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( !stricmp( m_IconList[i].szSpriteName, pszIconName ) ) + { + // clear the item from the list + memset( &m_IconList[i], 0, sizeof icon_sprite_t ); + return; + } + } +} diff --git a/hlsdk/cl_dll/statusbar.cpp b/hlsdk/cl_dll/statusbar.cpp new file mode 100644 index 00000000..28018da9 --- /dev/null +++ b/hlsdk/cl_dll/statusbar.cpp @@ -0,0 +1,265 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// statusbar.cpp +// +// generic text status bar, set by game dll +// runs across bottom of screen +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +DECLARE_MESSAGE( m_StatusBar, StatusText ); +DECLARE_MESSAGE( m_StatusBar, StatusValue ); + +#define STATUSBAR_ID_LINE 1 + +float *GetClientColor( int clientIndex ); +extern float g_ColorYellow[3]; + +int CHudStatusBar :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( StatusText ); + HOOK_MESSAGE( StatusValue ); + + Reset(); + + CVAR_CREATE( "hud_centerid", "0", FCVAR_ARCHIVE ); + + return 1; +} + +int CHudStatusBar :: VidInit( void ) +{ + // Load sprites here + + return 1; +} + +void CHudStatusBar :: Reset( void ) +{ + int i = 0; + + m_iFlags &= ~HUD_ACTIVE; // start out inactive + for ( i = 0; i < MAX_STATUSBAR_LINES; i++ ) + m_szStatusText[i][0] = 0; + memset( m_iStatusValues, 0, sizeof m_iStatusValues ); + + m_iStatusValues[0] = 1; // 0 is the special index, which always returns true + + // reset our colors for the status bar lines (yellow is default) + for ( i = 0; i < MAX_STATUSBAR_LINES; i++ ) + m_pflNameColors[i] = g_ColorYellow; +} + +void CHudStatusBar :: ParseStatusString( int line_num ) +{ + // localise string first + char szBuffer[MAX_STATUSTEXT_LENGTH]; + memset( szBuffer, 0, sizeof szBuffer ); + gHUD.m_TextMessage.LocaliseTextString( m_szStatusText[line_num], szBuffer, MAX_STATUSTEXT_LENGTH ); + + // parse m_szStatusText & m_iStatusValues into m_szStatusBar + memset( m_szStatusBar[line_num], 0, MAX_STATUSTEXT_LENGTH ); + char *src = szBuffer; + char *dst = m_szStatusBar[line_num]; + + char *src_start = src, *dst_start = dst; + + while ( *src != 0 ) + { + while ( *src == '\n' ) + src++; // skip over any newlines + + if ( ((src - src_start) >= MAX_STATUSTEXT_LENGTH) || ((dst - dst_start) >= MAX_STATUSTEXT_LENGTH) ) + break; + + int index = atoi( src ); + // should we draw this line? + if ( (index >= 0 && index < MAX_STATUSBAR_VALUES) && (m_iStatusValues[index] != 0) ) + { // parse this line and append result to the status bar + while ( *src >= '0' && *src <= '9' ) + src++; + + if ( *src == '\n' || *src == 0 ) + continue; // no more left in this text line + + // copy the text, char by char, until we hit a % or a \n + while ( *src != '\n' && *src != 0 ) + { + if ( *src != '%' ) + { // just copy the character + *dst = *src; + dst++, src++; + } + else + { + // get the descriptor + char valtype = *(++src); // move over % + + // if it's a %, draw a % sign + if ( valtype == '%' ) + { + *dst = valtype; + dst++, src++; + continue; + } + + // move over descriptor, then get and move over the index + index = atoi( ++src ); + while ( *src >= '0' && *src <= '9' ) + src++; + + if ( index >= 0 && index < MAX_STATUSBAR_VALUES ) + { + int indexval = m_iStatusValues[index]; + + // get the string to substitute in place of the %XX + char szRepString[MAX_PLAYER_NAME_LENGTH]; + switch ( valtype ) + { + case 'p': // player name + GetPlayerInfo( indexval, &g_PlayerInfoList[indexval] ); + if ( g_PlayerInfoList[indexval].name != NULL ) + { + strncpy( szRepString, g_PlayerInfoList[indexval].name, MAX_PLAYER_NAME_LENGTH ); + m_pflNameColors[line_num] = GetClientColor( indexval ); + } + else + { + strcpy( szRepString, "******" ); + } + + break; + case 'i': // number + sprintf( szRepString, "%d", indexval ); + break; + default: + szRepString[0] = 0; + } + + for ( char *cp = szRepString; *cp != 0 && ((dst - dst_start) < MAX_STATUSTEXT_LENGTH); cp++, dst++ ) + *dst = *cp; + } + } + } + } + else + { + // skip to next line of text + while ( *src != 0 && *src != '\n' ) + src++; + } + } +} + +int CHudStatusBar :: Draw( float fTime ) +{ + if ( m_bReparseString ) + { + for ( int i = 0; i < MAX_STATUSBAR_LINES; i++ ) + { + m_pflNameColors[i] = g_ColorYellow; + ParseStatusString( i ); + } + m_bReparseString = FALSE; + } + + int Y_START = ScreenHeight - YRES(32 + 4); + + // Draw the status bar lines + for ( int i = 0; i < MAX_STATUSBAR_LINES; i++ ) + { + int TextHeight, TextWidth; + GetConsoleStringSize( m_szStatusBar[i], &TextWidth, &TextHeight ); + + int x = 4; + int y = Y_START - ( 4 + TextHeight * i ); // draw along bottom of screen + + // let user set status ID bar centering + if ( (i == STATUSBAR_ID_LINE) && CVAR_GET_FLOAT("hud_centerid") ) + { + x = max( 0, max(2, (ScreenWidth - TextWidth)) / 2 ); + y = (ScreenHeight / 2) + (TextHeight*CVAR_GET_FLOAT("hud_centerid")); + } + + if ( m_pflNameColors[i] ) + gEngfuncs.pfnDrawSetTextColor( m_pflNameColors[i][0], m_pflNameColors[i][1], m_pflNameColors[i][2] ); + + DrawConsoleString( x, y, m_szStatusBar[i] ); + } + + return 1; +} + +// Message handler for StatusText message +// accepts two values: +// byte: line number of status bar text +// string: status bar text +// this string describes how the status bar should be drawn +// a semi-regular expression: +// ( slotnum ([a..z] [%pX] [%iX])*)* +// where slotnum is an index into the Value table (see below) +// if slotnum is 0, the string is always drawn +// if StatusValue[slotnum] != 0, the following string is drawn, upto the next newline - otherwise the text is skipped upto next newline +// %pX, where X is an integer, will substitute a player name here, getting the player index from StatusValue[X] +// %iX, where X is an integer, will substitute a number here, getting the number from StatusValue[X] +int CHudStatusBar :: MsgFunc_StatusText( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int line = READ_BYTE(); + + if ( line < 0 || line >= MAX_STATUSBAR_LINES ) + return 1; + + strncpy( m_szStatusText[line], READ_STRING(), MAX_STATUSTEXT_LENGTH ); + m_szStatusText[line][MAX_STATUSTEXT_LENGTH-1] = 0; // ensure it's null terminated ( strncpy() won't null terminate if read string too long) + + if ( m_szStatusText[0] == 0 ) + m_iFlags &= ~HUD_ACTIVE; + else + m_iFlags |= HUD_ACTIVE; // we have status text, so turn on the status bar + + m_bReparseString = TRUE; + + return 1; +} + +// Message handler for StatusText message +// accepts two values: +// byte: index into the status value array +// short: value to store +int CHudStatusBar :: MsgFunc_StatusValue( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int index = READ_BYTE(); + if ( index < 1 || index >= MAX_STATUSBAR_VALUES ) + return 1; // index out of range + + m_iStatusValues[index] = READ_SHORT(); + + m_bReparseString = TRUE; + + return 1; +} \ No newline at end of file diff --git a/hlsdk/cl_dll/studio_util.cpp b/hlsdk/cl_dll/studio_util.cpp new file mode 100644 index 00000000..ab14d96e --- /dev/null +++ b/hlsdk/cl_dll/studio_util.cpp @@ -0,0 +1,251 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "com_model.h" +#include "studio_util.h" + +/* +==================== +AngleMatrix + +==================== +*/ +void AngleMatrix (const float *angles, float (*matrix)[4] ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + 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); + + // matrix = (YAW * PITCH) * ROLL + matrix[0][0] = cp*cy; + matrix[1][0] = cp*sy; + matrix[2][0] = -sp; + matrix[0][1] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[2][1] = sr*cp; + matrix[0][2] = (cr*sp*cy+-sr*-sy); + matrix[1][2] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.0; + matrix[1][3] = 0.0; + matrix[2][3] = 0.0; +} + +/* +==================== +VectorCompare + +==================== +*/ +int VectorCompare (const float *v1, const float *v2) +{ + int i; + + for (i=0 ; i<3 ; i++) + if (v1[i] != v2[i]) + return 0; + + return 1; +} + +/* +==================== +CrossProduct + +==================== +*/ +void CrossProduct (const float *v1, const float *v2, float *cross) +{ + cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; + cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; + cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} + +/* +==================== +VectorTransform + +==================== +*/ +void VectorTransform (const float *in1, float in2[3][4], float *out) +{ + out[0] = DotProduct(in1, in2[0]) + in2[0][3]; + out[1] = DotProduct(in1, in2[1]) + in2[1][3]; + out[2] = DotProduct(in1, in2[2]) + in2[2][3]; +} + +/* +================ +ConcatTransforms + +================ +*/ +void ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] + + in1[0][2] * in2[2][3] + in1[0][3]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] + + in1[1][2] * in2[2][3] + in1[1][3]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; + out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + + in1[2][2] * in2[2][3] + in1[2][3]; +} + +// angles index are not the same as ROLL, PITCH, YAW + +/* +==================== +AngleQuaternion + +==================== +*/ +void AngleQuaternion( float *angles, vec4_t quaternion ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + // FIXME: rescale the inputs to 1/2 angle + angle = angles[2] * 0.5; + sy = sin(angle); + cy = cos(angle); + angle = angles[1] * 0.5; + sp = sin(angle); + cp = cos(angle); + angle = angles[0] * 0.5; + sr = sin(angle); + cr = cos(angle); + + quaternion[0] = sr*cp*cy-cr*sp*sy; // X + quaternion[1] = cr*sp*cy+sr*cp*sy; // Y + quaternion[2] = cr*cp*sy-sr*sp*cy; // Z + quaternion[3] = cr*cp*cy+sr*sp*sy; // W +} + +/* +==================== +QuaternionSlerp + +==================== +*/ +void QuaternionSlerp( vec4_t p, vec4_t q, float t, vec4_t qt ) +{ + int i; + float omega, cosom, sinom, sclp, sclq; + + // decide if one of the quaternions is backwards + float a = 0; + float b = 0; + + for (i = 0; i < 4; i++) + { + a += (p[i]-q[i])*(p[i]-q[i]); + b += (p[i]+q[i])*(p[i]+q[i]); + } + if (a > b) + { + for (i = 0; i < 4; i++) + { + q[i] = -q[i]; + } + } + + cosom = p[0]*q[0] + p[1]*q[1] + p[2]*q[2] + p[3]*q[3]; + + if ((1.0 + cosom) > 0.000001) + { + if ((1.0 - cosom) > 0.000001) + { + omega = acos( cosom ); + sinom = sin( omega ); + sclp = sin( (1.0 - t)*omega) / sinom; + sclq = sin( t*omega ) / sinom; + } + else + { + sclp = 1.0 - t; + sclq = t; + } + for (i = 0; i < 4; i++) { + qt[i] = sclp * p[i] + sclq * q[i]; + } + } + else + { + qt[0] = -q[1]; + qt[1] = q[0]; + qt[2] = -q[3]; + qt[3] = q[2]; + sclp = sin( (1.0 - t) * (0.5 * M_PI)); + sclq = sin( t * (0.5 * M_PI)); + for (i = 0; i < 3; i++) + { + qt[i] = sclp * p[i] + sclq * qt[i]; + } + } +} + +/* +==================== +QuaternionMatrix + +==================== +*/ +void QuaternionMatrix( vec4_t quaternion, float (*matrix)[4] ) +{ + matrix[0][0] = 1.0 - 2.0 * quaternion[1] * quaternion[1] - 2.0 * quaternion[2] * quaternion[2]; + matrix[1][0] = 2.0 * quaternion[0] * quaternion[1] + 2.0 * quaternion[3] * quaternion[2]; + matrix[2][0] = 2.0 * quaternion[0] * quaternion[2] - 2.0 * quaternion[3] * quaternion[1]; + + matrix[0][1] = 2.0 * quaternion[0] * quaternion[1] - 2.0 * quaternion[3] * quaternion[2]; + matrix[1][1] = 1.0 - 2.0 * quaternion[0] * quaternion[0] - 2.0 * quaternion[2] * quaternion[2]; + matrix[2][1] = 2.0 * quaternion[1] * quaternion[2] + 2.0 * quaternion[3] * quaternion[0]; + + matrix[0][2] = 2.0 * quaternion[0] * quaternion[2] + 2.0 * quaternion[3] * quaternion[1]; + matrix[1][2] = 2.0 * quaternion[1] * quaternion[2] - 2.0 * quaternion[3] * quaternion[0]; + matrix[2][2] = 1.0 - 2.0 * quaternion[0] * quaternion[0] - 2.0 * quaternion[1] * quaternion[1]; +} + +/* +==================== +MatrixCopy + +==================== +*/ +void MatrixCopy( float in[3][4], float out[3][4] ) +{ + memcpy( out, in, sizeof( float ) * 3 * 4 ); +} \ No newline at end of file diff --git a/hlsdk/cl_dll/studio_util.h b/hlsdk/cl_dll/studio_util.h new file mode 100644 index 00000000..e1cb980e --- /dev/null +++ b/hlsdk/cl_dll/studio_util.h @@ -0,0 +1,40 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( STUDIO_UTIL_H ) +#define STUDIO_UTIL_H +#if defined( WIN32 ) +#pragma once +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +#ifndef PITCH +// MOVEMENT INFO +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 +#endif + +#define FDotProduct( a, b ) (fabs((a[0])*(b[0])) + fabs((a[1])*(b[1])) + fabs((a[2])*(b[2]))) + +void AngleMatrix (const float *angles, float (*matrix)[4] ); +int VectorCompare (const float *v1, const float *v2); +void CrossProduct (const float *v1, const float *v2, float *cross); +void VectorTransform (const float *in1, float in2[3][4], float *out); +void ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]); +void MatrixCopy( float in[3][4], float out[3][4] ); +void QuaternionMatrix( vec4_t quaternion, float (*matrix)[4] ); +void QuaternionSlerp( vec4_t p, vec4_t q, float t, vec4_t qt ); +void AngleQuaternion( float *angles, vec4_t quaternion ); + +#endif // STUDIO_UTIL_H \ No newline at end of file diff --git a/hlsdk/cl_dll/text_message.cpp b/hlsdk/cl_dll/text_message.cpp new file mode 100644 index 00000000..c18dd25f --- /dev/null +++ b/hlsdk/cl_dll/text_message.cpp @@ -0,0 +1,213 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// text_message.cpp +// +// implementation of CHudTextMessage class +// +// this class routes messages through titles.txt for localisation +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +#include "vgui_TeamFortressViewport.h" + +DECLARE_MESSAGE( m_TextMessage, TextMsg ); + +int CHudTextMessage::Init(void) +{ + HOOK_MESSAGE( TextMsg ); + + gHUD.AddHudElem( this ); + + Reset(); + + return 1; +}; + +// Searches through the string for any msg names (indicated by a '#') +// any found are looked up in titles.txt and the new message substituted +// the new value is pushed into dst_buffer +char *CHudTextMessage::LocaliseTextString( const char *msg, char *dst_buffer, int buffer_size ) +{ + char *dst = dst_buffer; + for ( char *src = (char*)msg; *src != 0 && buffer_size > 0; buffer_size-- ) + { + if ( *src == '#' ) + { + // cut msg name out of string + static char word_buf[255]; + char *wdst = word_buf, *word_start = src; + for ( ++src ; (*src >= 'A' && *src <= 'z') || (*src >= '0' && *src <= '9'); wdst++, src++ ) + { + *wdst = *src; + } + *wdst = 0; + + // lookup msg name in titles.txt + client_textmessage_t *clmsg = TextMessageGet( word_buf ); + if ( !clmsg || !(clmsg->pMessage) ) + { + src = word_start; + *dst = *src; + dst++, src++; + continue; + } + + // copy string into message over the msg name + for ( char *wsrc = (char*)clmsg->pMessage; *wsrc != 0; wsrc++, dst++ ) + { + *dst = *wsrc; + } + *dst = 0; + } + else + { + *dst = *src; + dst++, src++; + *dst = 0; + } + } + + dst_buffer[buffer_size-1] = 0; // ensure null termination + return dst_buffer; +} + +// As above, but with a local static buffer +char *CHudTextMessage::BufferedLocaliseTextString( const char *msg ) +{ + static char dst_buffer[1024]; + LocaliseTextString( msg, dst_buffer, 1024 ); + return dst_buffer; +} + +// Simplified version of LocaliseTextString; assumes string is only one word +char *CHudTextMessage::LookupString( const char *msg, int *msg_dest ) +{ + if ( !msg ) + return ""; + + // '#' character indicates this is a reference to a string in titles.txt, and not the string itself + if ( msg[0] == '#' ) + { + // this is a message name, so look up the real message + client_textmessage_t *clmsg = TextMessageGet( msg+1 ); + + if ( !clmsg || !(clmsg->pMessage) ) + return (char*)msg; // lookup failed, so return the original string + + if ( msg_dest ) + { + // check to see if titles.txt info overrides msg destination + // if clmsg->effect is less than 0, then clmsg->effect holds -1 * message_destination + if ( clmsg->effect < 0 ) // + *msg_dest = -clmsg->effect; + } + + return (char*)clmsg->pMessage; + } + else + { // nothing special about this message, so just return the same string + return (char*)msg; + } +} + +void StripEndNewlineFromString( char *str ) +{ + int s = strlen( str ) - 1; + if ( str[s] == '\n' || str[s] == '\r' ) + str[s] = 0; +} + +// converts all '\r' characters to '\n', so that the engine can deal with the properly +// returns a pointer to str +char* ConvertCRtoNL( char *str ) +{ + for ( char *ch = str; *ch != 0; ch++ ) + if ( *ch == '\r' ) + *ch = '\n'; + return str; +} + +// Message handler for text messages +// displays a string, looking them up from the titles.txt file, which can be localised +// parameters: +// byte: message direction ( HUD_PRINTCONSOLE, HUD_PRINTNOTIFY, HUD_PRINTCENTER, HUD_PRINTTALK ) +// string: message +// optional parameters: +// string: message parameter 1 +// string: message parameter 2 +// string: message parameter 3 +// string: message parameter 4 +// any string that starts with the character '#' is a message name, and is used to look up the real message in titles.txt +// the next (optional) one to four strings are parameters for that string (which can also be message names if they begin with '#') +int CHudTextMessage::MsgFunc_TextMsg( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int msg_dest = READ_BYTE(); + + static char szBuf[6][128]; + char *msg_text = LookupString( READ_STRING(), &msg_dest ); + msg_text = strcpy( szBuf[0], msg_text ); + + // keep reading strings and using C format strings for subsituting the strings into the localised text string + char *sstr1 = LookupString( READ_STRING() ); + sstr1 = strcpy( szBuf[1], sstr1 ); + StripEndNewlineFromString( sstr1 ); // these strings are meant for subsitution into the main strings, so cull the automatic end newlines + char *sstr2 = LookupString( READ_STRING() ); + sstr2 = strcpy( szBuf[2], sstr2 ); + StripEndNewlineFromString( sstr2 ); + char *sstr3 = LookupString( READ_STRING() ); + sstr3 = strcpy( szBuf[3], sstr3 ); + StripEndNewlineFromString( sstr3 ); + char *sstr4 = LookupString( READ_STRING() ); + sstr4 = strcpy( szBuf[4], sstr4 ); + StripEndNewlineFromString( sstr4 ); + char *psz = szBuf[5]; + + if ( gViewPort && gViewPort->AllowedToPrintText() == FALSE ) + return 1; + + switch ( msg_dest ) + { + case HUD_PRINTCENTER: + sprintf( psz, msg_text, sstr1, sstr2, sstr3, sstr4 ); + CenterPrint( ConvertCRtoNL( psz ) ); + break; + + case HUD_PRINTNOTIFY: + psz[0] = 1; // mark this message to go into the notify buffer + sprintf( psz+1, msg_text, sstr1, sstr2, sstr3, sstr4 ); + ConsolePrint( ConvertCRtoNL( psz ) ); + break; + + case HUD_PRINTTALK: + sprintf( psz, msg_text, sstr1, sstr2, sstr3, sstr4 ); + gHUD.m_SayText.SayTextPrint( ConvertCRtoNL( psz ), 128 ); + break; + + case HUD_PRINTCONSOLE: + sprintf( psz, msg_text, sstr1, sstr2, sstr3, sstr4 ); + ConsolePrint( ConvertCRtoNL( psz ) ); + break; + } + + return 1; +} diff --git a/hlsdk/cl_dll/tf_defs.h b/hlsdk/cl_dll/tf_defs.h new file mode 100644 index 00000000..f178c55b --- /dev/null +++ b/hlsdk/cl_dll/tf_defs.h @@ -0,0 +1,1389 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#ifndef __TF_DEFS_H +#define __TF_DEFS_H + +//=========================================================================== +// OLD OPTIONS.QC +//=========================================================================== +#define DEFAULT_AUTOZOOM FALSE +#define WEINER_SNIPER // autoaiming for sniper rifle +#define FLAME_MAXWORLDNUM 20 // maximum number of flames in the world. DO NOT PUT BELOW 20. + +//#define MAX_WORLD_PIPEBOMBS 15 // This is divided between teams - this is the most you should have on a net server +#define MAX_PLAYER_PIPEBOMBS 8 // maximum number of pipebombs any 1 player can have active +#define MAX_PLAYER_AMMOBOXES 3 // maximum number of ammoboxes any 1 player can have active + +//#define MAX_WORLD_FLARES 9 // This is the total number of flares allowed in the world at one time +//#define MAX_WORLD_AMMOBOXES 20 // This is divided between teams - this is the most you should have on a net server +#define GR_TYPE_MIRV_NO 4 // Number of Mirvs a Mirv Grenade breaks into +#define GR_TYPE_NAPALM_NO 8 // Number of flames napalm grenade breaks into (unused if net server) +#define MEDIKIT_IS_BIOWEAPON // Medikit acts as a bioweapon against enemies + +#define TEAM_HELP_RATE 60 // used only if teamplay bit 64 (help team with lower score) is set. + // 60 is a mild setting, and won't make too much difference + // increasing it _decreases_ the amount of help the losing team gets + // Minimum setting is 1, which would really help the losing team + +#define DISPLAY_CLASS_HELP TRUE // Change this to #OFF if you don't want the class help to + // appear whenever a player connects +#define NEVER_TEAMFRAGS FALSE // teamfrags options always off +#define ALWAYS_TEAMFRAGS FALSE // teamfrags options always on +#define CHECK_SPEEDS TRUE // makes sure players aren't moving too fast +#define SNIPER_RIFLE_RELOAD_TIME 1.5 // seconds + +#define MAPBRIEFING_MAXTEXTLENGTH 512 +#define PLAYER_PUSH_VELOCITY 50 // Players push teammates if they're moving under this speed + +// Debug Options +//#define MAP_DEBUG // Debug for Map code. I suggest running in a hi-res + // mode and/or piping the output from the server to a file. +#ifdef MAP_DEBUG + #define MDEBUG(x) x +#else + #define MDEBUG(x) +#endif +//#define VERBOSE // Verbose Debugging on/off + +//=========================================================================== +// OLD QUAKE Defs +//=========================================================================== +// items +#define IT_AXE 4096 +#define IT_SHOTGUN 1 +#define IT_SUPER_SHOTGUN 2 +#define IT_NAILGUN 4 +#define IT_SUPER_NAILGUN 8 +#define IT_GRENADE_LAUNCHER 16 +#define IT_ROCKET_LAUNCHER 32 +#define IT_LIGHTNING 64 +#define IT_EXTRA_WEAPON 128 + +#define IT_SHELLS 256 +#define IT_NAILS 512 +#define IT_ROCKETS 1024 +#define IT_CELLS 2048 + +#define IT_ARMOR1 8192 +#define IT_ARMOR2 16384 +#define IT_ARMOR3 32768 +#define IT_SUPERHEALTH 65536 + +#define IT_KEY1 131072 +#define IT_KEY2 262144 + +#define IT_INVISIBILITY 524288 +#define IT_INVULNERABILITY 1048576 +#define IT_SUIT 2097152 +#define IT_QUAD 4194304 +#define IT_HOOK 8388608 + +#define IT_KEY3 16777216 // Stomp invisibility +#define IT_KEY4 33554432 // Stomp invulnerability + +//=========================================================================== +// TEAMFORTRESS Defs +//=========================================================================== +// TeamFortress State Flags +#define TFSTATE_GRENPRIMED 1 // Whether the player has a primed grenade +#define TFSTATE_RELOADING 2 // Whether the player is reloading +#define TFSTATE_ALTKILL 4 // #TRUE if killed with a weapon not in self.weapon: NOT USED ANYMORE +#define TFSTATE_RANDOMPC 8 // Whether Playerclass is random, new one each respawn +#define TFSTATE_INFECTED 16 // set when player is infected by the bioweapon +#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 +#define TFSTATE_ZOOMOFF 4096 // doesn't want the FOV changed when zooming +#define TFSTATE_RESPAWN_READY 8192 // is waiting for respawn, and has pressed fire +#define TFSTATE_HALLUCINATING 16384 // set when player is hallucinating +#define TFSTATE_TRANQUILISED 32768 // set when player is tranquilised +#define TFSTATE_CANT_MOVE 65536 // set when player is setting a detpack +#define TFSTATE_RESET_FLAMETIME 131072 // set when the player has to have his flames increased in health + +// Defines used by TF_T_Damage (see combat.qc) +#define TF_TD_IGNOREARMOUR 1 // Bypasses the armour of the target +#define TF_TD_NOTTEAM 2 // Doesn't damage a team member (indicates direct fire weapon) +#define TF_TD_NOTSELF 4 // Doesn't damage self + +#define TF_TD_OTHER 0 // Ignore armorclass +#define TF_TD_SHOT 1 // Bullet damage +#define TF_TD_NAIL 2 // Nail damage +#define TF_TD_EXPLOSION 4 // Explosion damage +#define TF_TD_ELECTRICITY 8 // Electric damage +#define TF_TD_FIRE 16 // Fire damage +#define TF_TD_NOSOUND 256 // Special damage. Makes no sound/painframe, etc + +/*==================================================*/ +/* Toggleable Game Settings */ +/*==================================================*/ +#define TF_RESPAWNDELAY1 5 // seconds of waiting before player can respawn +#define TF_RESPAWNDELAY2 10 // seconds of waiting before player can respawn +#define TF_RESPAWNDELAY3 20 // seconds of waiting before player can respawn + +#define TEAMPLAY_NORMAL 1 +#define TEAMPLAY_HALFDIRECT 2 +#define TEAMPLAY_NODIRECT 4 +#define TEAMPLAY_HALFEXPLOSIVE 8 +#define TEAMPLAY_NOEXPLOSIVE 16 +#define TEAMPLAY_LESSPLAYERSHELP 32 +#define TEAMPLAY_LESSSCOREHELP 64 +#define TEAMPLAY_HALFDIRECTARMOR 128 +#define TEAMPLAY_NODIRECTARMOR 256 +#define TEAMPLAY_HALFEXPARMOR 512 +#define TEAMPLAY_NOEXPARMOR 1024 +#define TEAMPLAY_HALFDIRMIRROR 2048 +#define TEAMPLAY_FULLDIRMIRROR 4096 +#define TEAMPLAY_HALFEXPMIRROR 8192 +#define TEAMPLAY_FULLEXPMIRROR 16384 + +#define TEAMPLAY_TEAMDAMAGE (TEAMPLAY_NODIRECT | TEAMPLAY_HALFDIRECT | TEAMPLAY_HALFEXPLOSIVE | TEAMPLAY_NOEXPLOSIVE) +// FortressMap stuff +#define TEAM1_CIVILIANS 1 +#define TEAM2_CIVILIANS 2 +#define TEAM3_CIVILIANS 4 +#define TEAM4_CIVILIANS 8 + +// Defines for the playerclass +#define PC_UNDEFINED 0 + +#define PC_SCOUT 1 +#define PC_SNIPER 2 +#define PC_SOLDIER 3 +#define PC_DEMOMAN 4 +#define PC_MEDIC 5 +#define PC_HVYWEAP 6 +#define PC_PYRO 7 +#define PC_SPY 8 +#define PC_ENGINEER 9 + +// Insert new class definitions here + +// PC_RANDOM _MUST_ be the third last class +#define PC_RANDOM 10 // Random playerclass +#define PC_CIVILIAN 11 // Civilians are a special class. They cannot + // be chosen by players, only enforced by maps +#define PC_LASTCLASS 12 // Use this as the high-boundary for any loops + // through the playerclass. + +#define SENTRY_COLOR 10 // will be in the PC_RANDOM slot for team colors + +// These are just for the scanner +#define SCAN_SENTRY 13 +#define SCAN_GOALITEM 14 + +// Values returned by CheckArea +enum +{ + CAREA_CLEAR, + CAREA_BLOCKED, + CAREA_NOBUILD +}; + +/*==================================================*/ +/* Impulse Defines */ +/*==================================================*/ +// Alias check to see whether they already have the aliases +#define TF_ALIAS_CHECK 13 + +// CTF Support Impulses +#define HOOK_IMP1 22 +#define FLAG_INFO 23 +#define HOOK_IMP2 39 + +// Axe +#define AXE_IMP 40 + +// Camera Impulse +#define TF_CAM_TARGET 50 +#define TF_CAM_ZOOM 51 +#define TF_CAM_ANGLE 52 +#define TF_CAM_VEC 53 +#define TF_CAM_PROJECTILE 54 +#define TF_CAM_PROJECTILE_Z 55 +#define TF_CAM_REVANGLE 56 +#define TF_CAM_OFFSET 57 +#define TF_CAM_DROP 58 +#define TF_CAM_FADETOBLACK 59 +#define TF_CAM_FADEFROMBLACK 60 +#define TF_CAM_FADETOWHITE 61 +#define TF_CAM_FADEFROMWHITE 62 + +// Last Weapon impulse +#define TF_LAST_WEAPON 69 + +// Status Bar Resolution Settings. Same as CTF to maintain ease of use. +#define TF_STATUSBAR_RES_START 71 +#define TF_STATUSBAR_RES_END 81 + +// Clan Messages +#define TF_MESSAGE_1 82 +#define TF_MESSAGE_2 83 +#define TF_MESSAGE_3 84 +#define TF_MESSAGE_4 85 +#define TF_MESSAGE_5 86 + +#define TF_CHANGE_CLASS 99 // Bring up the Class Change menu + +// Added to PC_??? to get impulse to use if this clashes with your +// own impulses, just change this value, not the PC_?? +#define TF_CHANGEPC 100 +// The next few impulses are all the class selections +//PC_SCOUT 101 +//PC_SNIPER 102 +//PC_SOLDIER 103 +//PC_DEMOMAN 104 +//PC_MEDIC 105 +//PC_HVYWEAP 106 +//PC_PYRO 107 +//PC_RANDOM 108 +//PC_CIVILIAN 109 // Cannot be used +//PC_SPY 110 +//PC_ENGINEER 111 + +// Help impulses +#define TF_DISPLAYLOCATION 118 +#define TF_STATUS_QUERY 119 + +#define TF_HELP_MAP 131 + +// Information impulses +#define TF_INVENTORY 135 +#define TF_SHOWTF 136 +#define TF_SHOWLEGALCLASSES 137 + +// Team Impulses +#define TF_TEAM_1 140 // Join Team 1 +#define TF_TEAM_2 141 // Join Team 2 +#define TF_TEAM_3 142 // Join Team 3 +#define TF_TEAM_4 143 // Join Team 4 +#define TF_TEAM_CLASSES 144 // Impulse to display team classes +#define TF_TEAM_SCORES 145 // Impulse to display team scores +#define TF_TEAM_LIST 146 // Impulse to display the players in each team. + +// Grenade Impulses +#define TF_GRENADE_1 150 // Prime grenade type 1 +#define TF_GRENADE_2 151 // Prime grenade type 2 +#define TF_GRENADE_T 152 // Throw primed grenade + +// Impulses for new items +//#define TF_SCAN 159 // Scanner Pre-Impulse +#define TF_AUTO_SCAN 159 // Scanner On/Off +#define TF_SCAN_ENEMY 160 // Impulses to toggle scanning of enemies +#define TF_SCAN_FRIENDLY 161 // Impulses to toggle scanning of friendlies +//#define TF_SCAN_10 162 // Scan using 10 enery (1 cell) +#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 +#define TF_SPECIAL_SKILL 171 + +// Ammo Drop impulse +#define TF_DROP_AMMO 172 + +// Reload impulse +#define TF_RELOAD 173 + +// auto-zoom toggle +#define TF_AUTOZOOM 174 + +// drop/pass commands +#define TF_DROPKEY 175 + +// 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 + +// Medic +#define TF_MEDIC_HELPME 181 + +// Status bar +#define TF_STATUSBAR_ON 182 +#define TF_STATUSBAR_OFF 183 + +// Discard impulse +#define TF_DISCARD 184 + +// ID Player impulse +#define TF_ID 185 + +// Clan Battle impulses +#define TF_SHOWIDS 186 + +// More Engineer Impulses +#define TF_ENGINEER_DETDISP 187 +#define TF_ENGINEER_DETSENT 188 + +// Admin Commands +#define TF_ADMIN_DEAL_CYCLE 189 +#define TF_ADMIN_KICK 190 +#define TF_ADMIN_BAN 191 +#define TF_ADMIN_COUNTPLAYERS 192 +#define TF_ADMIN_CEASEFIRE 193 + +// Drop Goal Items +#define TF_DROPGOALITEMS 194 + +// More Admin Commands +#define TF_ADMIN_NEXT 195 + +// More Engineer Impulses +#define TF_ENGINEER_DETEXIT 196 +#define TF_ENGINEER_DETENTRANCE 197 + +// Yet MORE Admin Commands +#define TF_ADMIN_LISTIPS 198 + +// Silent Spy Feign +#define TF_SPY_SILENTDIE 199 + + +/*==================================================*/ +/* Defines for the ENGINEER's Building ability */ +/*==================================================*/ +// Ammo costs +#define AMMO_COST_SHELLS 2 // Metal needed to make 1 shell +#define AMMO_COST_NAILS 1 +#define AMMO_COST_ROCKETS 2 +#define AMMO_COST_CELLS 2 + +// Building types +#define BUILD_DISPENSER 1 +#define BUILD_SENTRYGUN 2 +#define BUILD_MORTAR 3 +#define BUILD_TELEPORTER_ENTRANCE 4 +#define BUILD_TELEPORTER_EXIT 5 + +// Building metal costs +#define BUILD_COST_DISPENSER 100 // Metal needed to built +#define BUILD_COST_SENTRYGUN 130 +#define BUILD_COST_MORTAR 150 +#define BUILD_COST_TELEPORTER 125 + +#define BUILD_COST_SANDBAG 20 // Built with a separate alias + +// Building times +#define BUILD_TIME_DISPENSER 2 // seconds to build +#define BUILD_TIME_SENTRYGUN 5 +#define BUILD_TIME_MORTAR 5 +#define BUILD_TIME_TELEPORTER 4 + +// Building health levels +#define BUILD_HEALTH_DISPENSER 150 // Health of the building +#define BUILD_HEALTH_SENTRYGUN 150 +#define BUILD_HEALTH_MORTAR 200 +#define BUILD_HEALTH_TELEPORTER 80 + +// Dispenser's maximum carrying capability +#define BUILD_DISPENSER_MAX_SHELLS 400 +#define BUILD_DISPENSER_MAX_NAILS 600 +#define BUILD_DISPENSER_MAX_ROCKETS 300 +#define BUILD_DISPENSER_MAX_CELLS 400 +#define BUILD_DISPENSER_MAX_ARMOR 500 + +// Build state sent down to client +#define BS_BUILDING (1<<0) +#define BS_HAS_DISPENSER (1<<1) +#define BS_HAS_SENTRYGUN (1<<2) +#define BS_CANB_DISPENSER (1<<3) +#define BS_CANB_SENTRYGUN (1<<4) +/*==================================================*/ +/* Ammo quantities for dropping & dispenser use */ +/*==================================================*/ +#define DROP_SHELLS 20 +#define DROP_NAILS 20 +#define DROP_ROCKETS 10 +#define DROP_CELLS 10 +#define DROP_ARMOR 40 + +/*==================================================*/ +/* Team Defines */ +/*==================================================*/ +#define TM_MAX_NO 4 // Max number of teams. Simply changing this value isn't enough. + // A new global to hold new team colors is needed, and more flags + // in the spawnpoint spawnflags may need to be used. + // Basically, don't change this unless you know what you're doing :) + +/*==================================================*/ +/* New Weapon Defines */ +/*==================================================*/ +#define WEAP_HOOK 1 +#define WEAP_BIOWEAPON 2 +#define WEAP_MEDIKIT 4 +#define WEAP_SPANNER 8 +#define WEAP_AXE 16 +#define WEAP_SNIPER_RIFLE 32 +#define WEAP_AUTO_RIFLE 64 +#define WEAP_SHOTGUN 128 +#define WEAP_SUPER_SHOTGUN 256 +#define WEAP_NAILGUN 512 +#define WEAP_SUPER_NAILGUN 1024 +#define WEAP_GRENADE_LAUNCHER 2048 +#define WEAP_FLAMETHROWER 4096 +#define WEAP_ROCKET_LAUNCHER 8192 +#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 +// but we can remove some by giving the weapons +// a weapon mode (like the rifle) + +// HL-compatible weapon numbers +#define WEAPON_HOOK 1 +#define WEAPON_BIOWEAPON (WEAPON_HOOK+1) +#define WEAPON_MEDIKIT (WEAPON_HOOK+2) +#define WEAPON_SPANNER (WEAPON_HOOK+3) +#define WEAPON_AXE (WEAPON_HOOK+4) +#define WEAPON_SNIPER_RIFLE (WEAPON_HOOK+5) +#define WEAPON_AUTO_RIFLE (WEAPON_HOOK+6) +#define WEAPON_TF_SHOTGUN (WEAPON_HOOK+7) +#define WEAPON_SUPER_SHOTGUN (WEAPON_HOOK+8) +#define WEAPON_NAILGUN (WEAPON_HOOK+9) +#define WEAPON_SUPER_NAILGUN (WEAPON_HOOK+10) +#define WEAPON_GRENADE_LAUNCHER (WEAPON_HOOK+11) +#define WEAPON_FLAMETHROWER (WEAPON_HOOK+12) +#define WEAPON_ROCKET_LAUNCHER (WEAPON_HOOK+13) +#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) +#define WEAPON_KNIFE (WEAPON_HOOK+22) +#define WEAPON_BENCHMARK (WEAPON_HOOK+23) + +/*==================================================*/ +/* New Weapon Related Defines */ +/*==================================================*/ +// shots per reload +#define RE_SHOTGUN 8 +#define RE_SUPER_SHOTGUN 16 // 8 shots +#define RE_GRENADE_LAUNCHER 6 +#define RE_ROCKET_LAUNCHER 4 + +// reload times +#define RE_SHOTGUN_TIME 2 +#define RE_SUPER_SHOTGUN_TIME 3 +#define RE_GRENADE_LAUNCHER_TIME 4 +#define RE_ROCKET_LAUNCHER_TIME 5 + +// Maximum velocity you can move and fire the Sniper Rifle +#define WEAP_SNIPER_RIFLE_MAX_MOVE 50 + +// Medikit +#define WEAP_MEDIKIT_HEAL 200 // Amount medikit heals per hit +#define WEAP_MEDIKIT_OVERHEAL 50 // Amount of superhealth over max_health the medikit will dispense + +// 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 + +// Grenades +#define GR_PRIMETIME 3 +#define GR_CALTROP_PRIME 0.5 +#define GR_TYPE_NONE 0 +#define GR_TYPE_NORMAL 1 +#define GR_TYPE_CONCUSSION 2 +#define GR_TYPE_NAIL 3 +#define GR_TYPE_MIRV 4 +#define GR_TYPE_NAPALM 5 +//#define GR_TYPE_FLARE 6 +#define GR_TYPE_GAS 7 +#define GR_TYPE_EMP 8 +#define GR_TYPE_CALTROP 9 +//#define GR_TYPE_FLASH 10 + +// Defines for WeaponMode +#define GL_NORMAL 0 +#define GL_PIPEBOMB 1 + +// Defines for OLD Concussion Grenade +#define GR_OLD_CONCUSS_TIME 5 +#define GR_OLD_CONCUSS_DEC 20 + +// Defines for Concussion Grenade +#define GR_CONCUSS_TIME 0.25 +#define GR_CONCUSS_DEC 10 +#define MEDIUM_PING 150 +#define HIGH_PING 200 + +// Defines for the Gas Grenade +#define GR_HALLU_TIME 0.3 +#define GR_OLD_HALLU_TIME 0.5 +#define GR_HALLU_DEC 2.5 + +// Defines for the BioInfection +#define BIO_JUMP_RADIUS 128 // The distance the bioinfection can jump between players + +/*==================================================*/ +/* New Items */ +/*==================================================*/ +#define NIT_SCANNER 1 + +#define NIT_SILVER_DOOR_OPENED #IT_KEY1 // 131072 +#define NIT_GOLD_DOOR_OPENED #IT_KEY2 // 262144 + +/*==================================================*/ +/* New Item Flags */ +/*==================================================*/ +#define NIT_SCANNER_ENEMY 1 // Detect enemies +#define NIT_SCANNER_FRIENDLY 2 // Detect friendlies (team members) +#define NIT_SCANNER_SOUND 4 // Motion detection. Only report moving entities. + +/*==================================================*/ +/* New Item Related Defines */ +/*==================================================*/ +#define NIT_SCANNER_POWER 25 // The amount of power spent on a scan with the scanner + // is multiplied by this to get the scanrange. +#define NIT_SCANNER_MAXCELL 50 // The maximum number of cells than can be used in one scan +#define NIT_SCANNER_MIN_MOVEMENT 50 // The minimum velocity an entity must have to be detected + // by scanners that only detect movement + +/*==================================================*/ +/* Variables used for New Weapons and Reloading */ +/*==================================================*/ +// Armor Classes : Bitfields. Use the "armorclass" of armor for the Armor Type. +#define AT_SAVESHOT 1 // Kevlar : Reduces bullet damage by 15% +#define AT_SAVENAIL 2 // Wood :) : Reduces nail damage by 15% +#define AT_SAVEEXPLOSION 4 // Blast : Reduces explosion damage by 15% +#define AT_SAVEELECTRICITY 8 // Shock : Reduces electricity damage by 15% +#define AT_SAVEFIRE 16 // Asbestos : Reduces fire damage by 15% + +/*==========================================================================*/ +/* TEAMFORTRESS CLASS DETAILS */ +/*==========================================================================*/ +// Class Details for SCOUT +#define PC_SCOUT_SKIN 4 // Skin for this class when Classkin is on. +#define PC_SCOUT_MAXHEALTH 75 // Maximum Health Level +#define PC_SCOUT_MAXSPEED 400 // Maximum movement speed +#define PC_SCOUT_MAXSTRAFESPEED 400 // Maximum strafing movement speed +#define PC_SCOUT_MAXARMOR 50 // Maximum Armor Level, of any armor class +#define PC_SCOUT_INITARMOR 25 // Armor level when respawned +#define PC_SCOUT_MAXARMORTYPE 0.3 // Maximum level of Armor absorption +#define PC_SCOUT_INITARMORTYPE 0.3 // Absorption Level of armor when respawned +#define PC_SCOUT_ARMORCLASSES 3 // #AT_SAVESHOT | #AT_SAVENAIL <-Armor Classes allowed for this class +#define PC_SCOUT_INITARMORCLASS 0 // Armorclass worn when respawned +#define PC_SCOUT_WEAPONS WEAP_AXE | WEAP_SHOTGUN | WEAP_NAILGUN +#define PC_SCOUT_MAXAMMO_SHOT 50 // Maximum amount of shot ammo this class can carry +#define PC_SCOUT_MAXAMMO_NAIL 200 // Maximum amount of nail ammo this class can carry +#define PC_SCOUT_MAXAMMO_CELL 100 // Maximum amount of cell ammo this class can carry +#define PC_SCOUT_MAXAMMO_ROCKET 25 // Maximum amount of rocket ammo this class can carry +#define PC_SCOUT_INITAMMO_SHOT 25 // Amount of shot ammo this class has when respawned +#define PC_SCOUT_INITAMMO_NAIL 100 // Amount of nail ammo this class has when respawned +#define PC_SCOUT_INITAMMO_CELL 50 // Amount of cell ammo this class has when respawned +#define PC_SCOUT_INITAMMO_ROCKET 0 // Amount of rocket ammo this class has when respawned +#define PC_SCOUT_GRENADE_TYPE_1 GR_TYPE_CALTROP // <- 1st Type of Grenade this class has +#define PC_SCOUT_GRENADE_TYPE_2 GR_TYPE_CONCUSSION // <- 2nd Type of Grenade this class has +#define PC_SCOUT_GRENADE_INIT_1 2 // Number of grenades of Type 1 this class has when respawned +#define PC_SCOUT_GRENADE_INIT_2 3 // Number of grenades of Type 2 this class has when respawned +#define PC_SCOUT_TF_ITEMS NIT_SCANNER // <- TeamFortress Items this class has + +#define PC_SCOUT_MOTION_MIN_I 0.5 // < Short range +#define PC_SCOUT_MOTION_MIN_MOVE 50 // Minimum vlen of player velocity to be picked up by motion detector +#define PC_SCOUT_SCAN_TIME 2 // # of seconds between each scan pulse +#define PC_SCOUT_SCAN_RANGE 100 // Default scanner range +#define PC_SCOUT_SCAN_COST 2 // Default scanner cell useage per scan + +// Class Details for SNIPER +#define PC_SNIPER_SKIN 5 +#define PC_SNIPER_MAXHEALTH 90 +#define PC_SNIPER_MAXSPEED 300 +#define PC_SNIPER_MAXSTRAFESPEED 300 +#define PC_SNIPER_MAXARMOR 50 +#define PC_SNIPER_INITARMOR 0 +#define PC_SNIPER_MAXARMORTYPE 0.3 +#define PC_SNIPER_INITARMORTYPE 0.3 +#define PC_SNIPER_ARMORCLASSES 3 // #AT_SAVESHOT | #AT_SAVENAIL +#define PC_SNIPER_INITARMORCLASS 0 +#define PC_SNIPER_WEAPONS WEAP_SNIPER_RIFLE | WEAP_AUTO_RIFLE | WEAP_AXE | WEAP_NAILGUN +#define PC_SNIPER_MAXAMMO_SHOT 75 +#define PC_SNIPER_MAXAMMO_NAIL 100 +#define PC_SNIPER_MAXAMMO_CELL 50 +#define PC_SNIPER_MAXAMMO_ROCKET 25 +#define PC_SNIPER_INITAMMO_SHOT 60 +#define PC_SNIPER_INITAMMO_NAIL 50 +#define PC_SNIPER_INITAMMO_CELL 0 +#define PC_SNIPER_INITAMMO_ROCKET 0 +#define PC_SNIPER_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_SNIPER_GRENADE_TYPE_2 GR_TYPE_NONE +#define PC_SNIPER_GRENADE_INIT_1 2 +#define PC_SNIPER_GRENADE_INIT_2 0 +#define PC_SNIPER_TF_ITEMS 0 + +// Class Details for SOLDIER +#define PC_SOLDIER_SKIN 6 +#define PC_SOLDIER_MAXHEALTH 100 +#define PC_SOLDIER_MAXSPEED 240 +#define PC_SOLDIER_MAXSTRAFESPEED 240 +#define PC_SOLDIER_MAXARMOR 200 +#define PC_SOLDIER_INITARMOR 100 +#define PC_SOLDIER_MAXARMORTYPE 0.8 +#define PC_SOLDIER_INITARMORTYPE 0.8 +#define PC_SOLDIER_ARMORCLASSES 31 // ALL +#define PC_SOLDIER_INITARMORCLASS 0 +#define PC_SOLDIER_WEAPONS WEAP_AXE | WEAP_SHOTGUN | WEAP_SUPER_SHOTGUN | WEAP_ROCKET_LAUNCHER +#define PC_SOLDIER_MAXAMMO_SHOT 100 +#define PC_SOLDIER_MAXAMMO_NAIL 100 +#define PC_SOLDIER_MAXAMMO_CELL 50 +#define PC_SOLDIER_MAXAMMO_ROCKET 50 +#define PC_SOLDIER_INITAMMO_SHOT 50 +#define PC_SOLDIER_INITAMMO_NAIL 0 +#define PC_SOLDIER_INITAMMO_CELL 0 +#define PC_SOLDIER_INITAMMO_ROCKET 10 +#define PC_SOLDIER_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_SOLDIER_GRENADE_TYPE_2 GR_TYPE_NAIL +#define PC_SOLDIER_GRENADE_INIT_1 2 +#define PC_SOLDIER_GRENADE_INIT_2 1 +#define PC_SOLDIER_TF_ITEMS 0 + +#define MAX_NAIL_GRENS 2 // Can only have 2 Nail grens active +#define MAX_NAPALM_GRENS 2 // Can only have 2 Napalm grens active +#define MAX_GAS_GRENS 2 // Can only have 2 Gas grenades active +#define MAX_MIRV_GRENS 2 // Can only have 2 Mirv's +#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 2 +#define PC_DEMOMAN_GRENADE_INIT_2 2 +#define PC_DEMOMAN_TF_ITEMS 0 + +// Class Details for COMBAT MEDIC +#define PC_MEDIC_SKIN 3 +#define PC_MEDIC_MAXHEALTH 90 +#define PC_MEDIC_MAXSPEED 320 +#define PC_MEDIC_MAXSTRAFESPEED 320 +#define PC_MEDIC_MAXARMOR 100 +#define PC_MEDIC_INITARMOR 50 +#define PC_MEDIC_MAXARMORTYPE 0.6 +#define PC_MEDIC_INITARMORTYPE 0.3 +#define PC_MEDIC_ARMORCLASSES 11 // ALL except EXPLOSION +#define PC_MEDIC_INITARMORCLASS 0 +#define PC_MEDIC_WEAPONS WEAP_BIOWEAPON | WEAP_MEDIKIT | WEAP_SHOTGUN | WEAP_SUPER_SHOTGUN | WEAP_SUPER_NAILGUN +#define PC_MEDIC_MAXAMMO_SHOT 75 +#define PC_MEDIC_MAXAMMO_NAIL 150 +#define PC_MEDIC_MAXAMMO_CELL 50 +#define PC_MEDIC_MAXAMMO_ROCKET 25 +#define PC_MEDIC_MAXAMMO_MEDIKIT 100 +#define PC_MEDIC_INITAMMO_SHOT 50 +#define PC_MEDIC_INITAMMO_NAIL 50 +#define PC_MEDIC_INITAMMO_CELL 0 +#define PC_MEDIC_INITAMMO_ROCKET 0 +#define PC_MEDIC_INITAMMO_MEDIKIT 50 +#define PC_MEDIC_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_MEDIC_GRENADE_TYPE_2 GR_TYPE_CONCUSSION +#define PC_MEDIC_GRENADE_INIT_1 2 +#define PC_MEDIC_GRENADE_INIT_2 2 +#define PC_MEDIC_TF_ITEMS 0 +#define PC_MEDIC_REGEN_TIME 3 // Number of seconds between each regen. +#define PC_MEDIC_REGEN_AMOUNT 2 // Amount of health regenerated each regen. + +// Class Details for HVYWEAP +#define PC_HVYWEAP_SKIN 2 +#define PC_HVYWEAP_MAXHEALTH 100 +#define PC_HVYWEAP_MAXSPEED 230 +#define PC_HVYWEAP_MAXSTRAFESPEED 230 +#define PC_HVYWEAP_MAXARMOR 300 +#define PC_HVYWEAP_INITARMOR 150 +#define PC_HVYWEAP_MAXARMORTYPE 0.8 +#define PC_HVYWEAP_INITARMORTYPE 0.8 +#define PC_HVYWEAP_ARMORCLASSES 31 // ALL +#define PC_HVYWEAP_INITARMORCLASS 0 +#define PC_HVYWEAP_WEAPONS WEAP_ASSAULT_CANNON | WEAP_AXE | WEAP_SHOTGUN | WEAP_SUPER_SHOTGUN +#define PC_HVYWEAP_MAXAMMO_SHOT 200 +#define PC_HVYWEAP_MAXAMMO_NAIL 200 +#define PC_HVYWEAP_MAXAMMO_CELL 50 +#define PC_HVYWEAP_MAXAMMO_ROCKET 25 +#define PC_HVYWEAP_INITAMMO_SHOT 200 +#define PC_HVYWEAP_INITAMMO_NAIL 0 +#define PC_HVYWEAP_INITAMMO_CELL 30 +#define PC_HVYWEAP_INITAMMO_ROCKET 0 +#define PC_HVYWEAP_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_HVYWEAP_GRENADE_TYPE_2 GR_TYPE_MIRV +#define PC_HVYWEAP_GRENADE_INIT_1 2 +#define PC_HVYWEAP_GRENADE_INIT_2 1 +#define PC_HVYWEAP_TF_ITEMS 0 +#define PC_HVYWEAP_CELL_USAGE 7 // Amount of cells spent to power up assault cannon + + + +// Class Details for PYRO +#define PC_PYRO_SKIN 21 +#define PC_PYRO_MAXHEALTH 100 +#define PC_PYRO_MAXSPEED 300 +#define PC_PYRO_MAXSTRAFESPEED 300 +#define PC_PYRO_MAXARMOR 150 +#define PC_PYRO_INITARMOR 50 +#define PC_PYRO_MAXARMORTYPE 0.6 +#define PC_PYRO_INITARMORTYPE 0.6 +#define PC_PYRO_ARMORCLASSES 27 // ALL except EXPLOSION +#define PC_PYRO_INITARMORCLASS 16 // #AT_SAVEFIRE +#define PC_PYRO_WEAPONS WEAP_INCENDIARY | WEAP_FLAMETHROWER | WEAP_AXE | WEAP_SHOTGUN +#define PC_PYRO_MAXAMMO_SHOT 40 +#define PC_PYRO_MAXAMMO_NAIL 50 +#define PC_PYRO_MAXAMMO_CELL 200 +#define PC_PYRO_MAXAMMO_ROCKET 20 +#define PC_PYRO_INITAMMO_SHOT 20 +#define PC_PYRO_INITAMMO_NAIL 0 +#define PC_PYRO_INITAMMO_CELL 120 +#define PC_PYRO_INITAMMO_ROCKET 5 +#define PC_PYRO_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_PYRO_GRENADE_TYPE_2 GR_TYPE_NAPALM +#define PC_PYRO_GRENADE_INIT_1 2 +#define PC_PYRO_GRENADE_INIT_2 4 +#define PC_PYRO_TF_ITEMS 0 +#define PC_PYRO_ROCKET_USAGE 3 // Number of rockets per incendiary cannon shot + +// Class Details for SPY +#define PC_SPY_SKIN 22 +#define PC_SPY_MAXHEALTH 90 +#define PC_SPY_MAXSPEED 300 +#define PC_SPY_MAXSTRAFESPEED 300 +#define PC_SPY_MAXARMOR 100 +#define PC_SPY_INITARMOR 25 +#define PC_SPY_MAXARMORTYPE 0.6 // Was 0.3 +#define PC_SPY_INITARMORTYPE 0.6 // Was 0.3 +#define PC_SPY_ARMORCLASSES 27 // ALL except EXPLOSION +#define PC_SPY_INITARMORCLASS 0 +#define PC_SPY_WEAPONS WEAP_AXE | WEAP_TRANQ | WEAP_SUPER_SHOTGUN | WEAP_NAILGUN +#define PC_SPY_MAXAMMO_SHOT 40 +#define PC_SPY_MAXAMMO_NAIL 100 +#define PC_SPY_MAXAMMO_CELL 30 +#define PC_SPY_MAXAMMO_ROCKET 15 +#define PC_SPY_INITAMMO_SHOT 40 +#define PC_SPY_INITAMMO_NAIL 50 +#define PC_SPY_INITAMMO_CELL 10 +#define PC_SPY_INITAMMO_ROCKET 0 +#define PC_SPY_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_SPY_GRENADE_TYPE_2 GR_TYPE_GAS +#define PC_SPY_GRENADE_INIT_1 2 +#define PC_SPY_GRENADE_INIT_2 2 +#define PC_SPY_TF_ITEMS 0 +#define PC_SPY_CELL_REGEN_TIME 5 +#define PC_SPY_CELL_REGEN_AMOUNT 1 +#define PC_SPY_CELL_USAGE 3 // Amount of cells spent while invisible +#define PC_SPY_GO_UNDERCOVER_TIME 4 // Time it takes to go undercover + +// Class Details for ENGINEER +#define PC_ENGINEER_SKIN 22 // Not used anymore +#define PC_ENGINEER_MAXHEALTH 80 +#define PC_ENGINEER_MAXSPEED 300 +#define PC_ENGINEER_MAXSTRAFESPEED 300 +#define PC_ENGINEER_MAXARMOR 50 +#define PC_ENGINEER_INITARMOR 25 +#define PC_ENGINEER_MAXARMORTYPE 0.6 +#define PC_ENGINEER_INITARMORTYPE 0.3 +#define PC_ENGINEER_ARMORCLASSES 31 // ALL +#define PC_ENGINEER_INITARMORCLASS 0 +#define PC_ENGINEER_WEAPONS WEAP_SPANNER | WEAP_LASER | WEAP_SUPER_SHOTGUN +#define PC_ENGINEER_MAXAMMO_SHOT 50 +#define PC_ENGINEER_MAXAMMO_NAIL 50 +#define PC_ENGINEER_MAXAMMO_CELL 200 // synonymous with metal +#define PC_ENGINEER_MAXAMMO_ROCKET 30 +#define PC_ENGINEER_INITAMMO_SHOT 20 +#define PC_ENGINEER_INITAMMO_NAIL 25 +#define PC_ENGINEER_INITAMMO_CELL 100 // synonymous with metal +#define PC_ENGINEER_INITAMMO_ROCKET 0 +#define PC_ENGINEER_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_ENGINEER_GRENADE_TYPE_2 GR_TYPE_EMP +#define PC_ENGINEER_GRENADE_INIT_1 2 +#define PC_ENGINEER_GRENADE_INIT_2 2 +#define PC_ENGINEER_TF_ITEMS 0 + +// Class Details for CIVILIAN +#define PC_CIVILIAN_SKIN 22 +#define PC_CIVILIAN_MAXHEALTH 50 +#define PC_CIVILIAN_MAXSPEED 240 +#define PC_CIVILIAN_MAXSTRAFESPEED 240 +#define PC_CIVILIAN_MAXARMOR 0 +#define PC_CIVILIAN_INITARMOR 0 +#define PC_CIVILIAN_MAXARMORTYPE 0 +#define PC_CIVILIAN_INITARMORTYPE 0 +#define PC_CIVILIAN_ARMORCLASSES 0 +#define PC_CIVILIAN_INITARMORCLASS 0 +#define PC_CIVILIAN_WEAPONS WEAP_AXE +#define PC_CIVILIAN_MAXAMMO_SHOT 0 +#define PC_CIVILIAN_MAXAMMO_NAIL 0 +#define PC_CIVILIAN_MAXAMMO_CELL 0 +#define PC_CIVILIAN_MAXAMMO_ROCKET 0 +#define PC_CIVILIAN_INITAMMO_SHOT 0 +#define PC_CIVILIAN_INITAMMO_NAIL 0 +#define PC_CIVILIAN_INITAMMO_CELL 0 +#define PC_CIVILIAN_INITAMMO_ROCKET 0 +#define PC_CIVILIAN_GRENADE_TYPE_1 0 +#define PC_CIVILIAN_GRENADE_TYPE_2 0 +#define PC_CIVILIAN_GRENADE_INIT_1 0 +#define PC_CIVILIAN_GRENADE_INIT_2 0 +#define PC_CIVILIAN_TF_ITEMS 0 + + +/*==========================================================================*/ +/* TEAMFORTRESS GOALS */ +/*==========================================================================*/ +// For all these defines, see the tfortmap.txt that came with the zip +// 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 + +// Defines for Goal Effects types : goal_effect +#define TFGE_AP 1 // AP is affected. Default. +#define TFGE_AP_TEAM 2 // All of the AP's team. +#define TFGE_NOT_AP_TEAM 4 // All except AP's team. +#define TFGE_NOT_AP 8 // All except AP. +#define TFGE_WALL 16 // If set, walls stop the Radius effects +#define TFGE_SAME_ENVIRONMENT 32 // If set, players in a different environment to the Goal are not affected +#define TFGE_TIMER_CHECK_AP 64 // If set, Timer Goals check their critera for all players fitting their effects + +// Defines for Goal Result types : goal_result +#define TFGR_SINGLE 1 // Goal can only be activated once +#define TFGR_ADD_BONUSES 2 // Any Goals activated by this one give their bonuses +#define TFGR_ENDGAME 4 // Goal fires Intermission, displays scores, and ends level +#define TFGR_NO_ITEM_RESULTS 8 // GoalItems given by this Goal don't do results +#define TFGR_REMOVE_DISGUISE 16 // Prevent/Remove undercover from any Spy +#define TFGR_FORCE_RESPAWN 32 // Forces the player to teleport to a respawn point +#define TFGR_DESTROY_BUILDINGS 64 // Destroys this player's buildings, if anys + +// Defines for Goal Group Result types : goal_group +// None! +// But I'm leaving this variable in there, since it's fairly likely +// that some will show up sometime. + +// Defines for Goal Item types, : goal_activation (in items) +#define TFGI_GLOW 1 // Players carrying this GoalItem will glow +#define TFGI_SLOW 2 // Players carrying this GoalItem will move at half-speed +#define TFGI_DROP 4 // Players dying with this item will drop it +#define TFGI_RETURN_DROP 8 // Return if a player with it dies +#define TFGI_RETURN_GOAL 16 // Return if a player with it has it removed by a goal's activation +#define TFGI_RETURN_REMOVE 32 // Return if it is removed by TFGI_REMOVE +#define TFGI_REVERSE_AP 64 // Only pickup if the player _doesn't_ match AP Details +#define TFGI_REMOVE 128 // Remove if left untouched for 2 minutes after being dropped +#define TFGI_KEEP 256 // Players keep this item even when they die +#define TFGI_ITEMGLOWS 512 // Item glows when on the ground +#define TFGI_DONTREMOVERES 1024 // Don't remove results when the item is removed +#define TFGI_DROPTOGROUND 2048 // Drop To Ground when spawning +#define TFGI_CANBEDROPPED 4096 // Can be voluntarily dropped by players +#define TFGI_SOLID 8192 // Is solid... blocks bullets, etc + +// Defines for methods of GoalItem returning +#define GI_RET_DROP_DEAD 0 // Dropped by a dead player +#define GI_RET_DROP_LIVING 1 // Dropped by a living player +#define GI_RET_GOAL 2 // Returned by a Goal +#define GI_RET_TIME 3 // Returned due to timeout + +// Defines for TeamSpawnpoints : goal_activation (in teamspawns) +#define TFSP_MULTIPLEITEMS 1 // Give out the GoalItem multiple times +#define TFSP_MULTIPLEMSGS 2 // Display the message multiple times + +// Defines for TeamSpawnpoints : goal_effects (in teamspawns) +#define TFSP_REMOVESELF 1 // Remove itself after being spawned on + +// Defines for Goal States +#define TFGS_ACTIVE 1 +#define TFGS_INACTIVE 2 +#define TFGS_REMOVED 3 +#define TFGS_DELAYED 4 + +// Defines for GoalItem Removing from Player Methods +#define GI_DROP_PLAYERDEATH 0 // Dropped by a dying player +#define GI_DROP_REMOVEGOAL 1 // Removed by a Goal +#define GI_DROP_PLAYERDROP 2 // Dropped by a player + +// Legal Playerclass Handling +#define TF_ILL_SCOUT 1 +#define TF_ILL_SNIPER 2 +#define TF_ILL_SOLDIER 4 +#define TF_ILL_DEMOMAN 8 +#define TF_ILL_MEDIC 16 +#define TF_ILL_HVYWEP 32 +#define TF_ILL_PYRO 64 +#define TF_ILL_RANDOMPC 128 +#define TF_ILL_SPY 256 +#define TF_ILL_ENGINEER 512 + +// Addition classes +#define CLASS_TFGOAL 128 +#define CLASS_TFGOAL_TIMER 129 +#define CLASS_TFGOAL_ITEM 130 +#define CLASS_TFSPAWN 131 + +/*==========================================================================*/ +/* Flamethrower */ +/*==========================================================================*/ +#define FLAME_PLYRMAXTIME 5.0 // lifetime in seconds of a flame on a player +#define FLAME_MAXBURNTIME 8 // lifetime in seconds of a flame on the world (big ones) +#define NAPALM_MAXBURNTIME 20 // lifetime in seconds of flame from a napalm grenade +#define FLAME_MAXPLYRFLAMES 4 // maximum number of flames on a player +#define FLAME_NUMLIGHTS 1 // maximum number of light flame +#define FLAME_BURNRATIO 0.3 // the chance of a flame not 'sticking' +#define GR_TYPE_FLAMES_NO 15 // number of flames spawned when a grenade explode +#define FLAME_DAMAGE_TIME 1 // Interval between damage burns from flames +#define FLAME_EFFECT_TIME 0.2 // frequency at which we display flame effects. +#define FLAME_THINK_TIME 0.1 // Seconds between times the flame checks burn +#define PER_FLAME_DAMAGE 2 // Damage taken per second per flame by burning players + +/*==================================================*/ +/* CTF Support defines */ +/*==================================================*/ +#define CTF_FLAG1 1 +#define CTF_FLAG2 2 +#define CTF_DROPOFF1 3 +#define CTF_DROPOFF2 4 +#define CTF_SCORE1 5 +#define CTF_SCORE2 6 + +//.float hook_out; + +/*==================================================*/ +/* Camera defines */ +/*==================================================*/ +/* +float live_camera; +.float camdist; +.vector camangle; +.entity camera_list; +*/ + +/*==================================================*/ +/* QuakeWorld defines */ +/*==================================================*/ +/* +float already_chosen_map; + +// grappling hook variables +.entity hook; +.float on_hook; +.float fire_held_down;// flag - TRUE if player is still holding down the + // fire button after throwing a hook. +*/ +/*==================================================*/ +/* Server Settings */ +/*==================================================*/ +// Admin modes +#define ADMIN_MODE_NONE 0 +#define ADMIN_MODE_DEAL 1 + +/*==================================================*/ +/* Death Message defines */ +/*==================================================*/ +#define DMSG_SHOTGUN 1 +#define DMSG_SSHOTGUN 2 +#define DMSG_NAILGUN 3 +#define DMSG_SNAILGUN 4 +#define DMSG_GRENADEL 5 +#define DMSG_ROCKETL 6 +#define DMSG_LIGHTNING 7 +#define DMSG_GREN_HAND 8 +#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 +#define DMSG_ASSAULTCANNON 20 +#define DMSG_HOOK 21 +#define DMSG_BACKSTAB 22 +#define DMSG_MEDIKIT 23 +#define DMSG_GREN_GAS 24 +#define DMSG_TRANQ 25 +#define DMSG_LASERBOLT 26 +#define DMSG_SENTRYGUN_BULLET 27 +#define DMSG_SNIPERLEGSHOT 28 +#define DMSG_SNIPERHEADSHOT 29 +#define DMSG_GREN_EMP 30 +#define DMSG_GREN_EMP_AMMO 31 +#define DMSG_SPANNER 32 +#define DMSG_INCENDIARY 33 +#define DMSG_SENTRYGUN_ROCKET 34 +#define DMSG_GREN_FLASH 35 +#define DMSG_TRIGGER 36 +#define DMSG_MIRROR 37 +#define DMSG_SENTRYDEATH 38 +#define DMSG_DISPENSERDEATH 39 +#define DMSG_GREN_AIRPIPE 40 +#define DMSG_CALTROP 41 + +/*==================================================*/ +// TOGGLEFLAGS +/*==================================================*/ +// Some of the toggleflags aren't used anymore, but the bits are still +// there to provide compatability with old maps +#define TFLAG_CLASS_PERSIST (1 << 0) // Persistent Classes Bit +#define TFLAG_CHEATCHECK (1 << 1) // Cheatchecking Bit +#define TFLAG_RESPAWNDELAY (1 << 2) // RespawnDelay bit +//#define TFLAG_UN (1 << 3) // NOT USED ANYMORE +#define TFLAG_OLD_GRENS (1 << 3) // Use old concussion grenade and flash grenade +#define TFLAG_UN2 (1 << 4) // NOT USED ANYMORE +#define TFLAG_UN3 (1 << 5) // NOT USED ANYMORE +#define TFLAG_UN4 (1 << 6) // NOT USED ANYMORE: Was Autoteam. CVAR tfc_autoteam used now. +#define TFLAG_TEAMFRAGS (1 << 7) // Individual Frags, or Frags = TeamScore +#define TFLAG_FIRSTENTRY (1 << 8) // Used to determine the first time toggleflags is set + // In a map. Cannot be toggled by players. +#define TFLAG_SPYINVIS (1 << 9) // Spy invisible only +#define TFLAG_GRAPPLE (1 << 10) // Grapple on/off +//#define TFLAG_FULLTEAMSCORE (1 << 11) // Each Team's score is TeamScore + Frags +#define TFLAG_FLAGEMULATION (1 << 12) // Flag emulation on for old TF maps +#define TFLAG_USE_STANDARD (1 << 13) // Use the TF War standard for Flag emulation + +#define TFLAG_FRAGSCORING (1 << 14) // Use frag scoring only + +/*======================*/ +// Menu stuff // +/*======================*/ + +#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_SPECHELP 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 + +#define MENU_VOICETWEAK 50 + +//============================ +// Timer Types +#define TF_TIMER_ANY 0 +#define TF_TIMER_CONCUSSION 1 +#define TF_TIMER_INFECTION 2 +#define TF_TIMER_HALLUCINATION 3 +#define TF_TIMER_TRANQUILISATION 4 +#define TF_TIMER_ROTHEALTH 5 +#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 +#define TF_TIMER_DISPENSERREFILL 14 + +// Non Player timers +#define TF_TIMER_RETURNITEM 100 +#define TF_TIMER_DELAYEDGOAL 101 +#define TF_TIMER_ENDROUND 102 + +//============================ +// Teamscore printing +#define TS_PRINT_SHORT 1 +#define TS_PRINT_LONG 2 +#define TS_PRINT_LONG_TO_ALL 3 + +#ifndef TF_DEFS_ONLY + +typedef struct +{ + int topColor; + int bottomColor; +} team_color_t; + + +/*==================================================*/ +/* GLOBAL VARIABLES */ +/*==================================================*/ +// FortressMap stuff +extern float number_of_teams; // number of teams supported by the map +extern int illegalclasses[5]; // Illegal playerclasses for all teams +extern int civilianteams; // Bitfield holding Civilian teams +extern Vector rgbcolors[5]; // RGB colors for each of the 4 teams + +extern team_color_t teamcolors[5][PC_LASTCLASS]; // Colors for each of the 4 teams + +extern int teamscores[5]; // Goal Score of each team +extern int g_iOrderedTeams[5]; // Teams ordered into order of winners->losers +extern int teamfrags[5]; // Total Frags for each team +extern int teamlives[5]; // Number of lives each team's players have +extern int teammaxplayers[5]; // Max number of players allowed in each team +extern float teamadvantage[5]; // only used if the teamplay equalisation bits are set + // stores the damage ratio players take/give +extern int teamallies[5]; // Keeps track of which teams are allied +extern string_t team_names[5]; + +extern BOOL CTF_Map; +extern BOOL birthday; +extern BOOL christmas; + +extern float num_world_flames; + +// Clan Battle stuff +extern float clan_scores_dumped; +extern float cb_prematch_time; +extern float fOldPrematch; +extern float fOldCeaseFire; +extern float cb_ceasefire_time; +extern float last_id; +extern float spy_off; +extern float old_grens; +extern float flagem_checked; +extern float flNextEqualisationCalc; +extern BOOL cease_fire; +extern BOOL no_cease_fire_text; +extern BOOL initial_cease_fire; +extern BOOL last_cease_fire; +// Autokick stuff +extern float autokick_kills; + +extern float deathmsg; // Global, which is set before every T_Damage, to indicate + // the death message that should be used. + +extern char *sTeamSpawnNames[]; +extern char *sClassNames[]; +extern char *sNewClassModelFiles[]; +extern char *sOldClassModelFiles[]; +extern char *sClassModels[]; +extern char *sClassCfgs[]; +extern char *sGrenadeNames[]; +extern string_t team_menu_string; + +extern int toggleflags; // toggleable flags + +extern CBaseEntity* g_pLastSpawns[5]; +extern BOOL g_bFirstClient; + +extern float g_fNextPrematchAlert; + +typedef struct +{ + int ip; + edict_t *pEdict; +} ip_storage_t; + +extern ip_storage_t g_IpStorage[32]; + +class CGhost; +/*==========================================================================*/ +BOOL ClassIsRestricted(float tno, int pc); +char* GetTeamName(int tno); +int TeamFortress_GetNoPlayers(); +void DestroyBuilding(CBaseEntity *eng, char *bld); +void teamsprint( int tno, CBaseEntity *ignore, int msg_dest, const char *st, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL ); +float anglemod( float v ); + +// Team Funcs +BOOL TeamFortress_TeamIsCivilian(float tno); +void TeamFortress_TeamShowScores(BOOL bLong, CBasePlayer *pPlayer); +BOOL TeamFortress_TeamPutPlayerInTeam(); +void TeamFortress_TeamSetColor(int tno); +void TeamFortress_TeamIncreaseScore(int tno, int scoretoadd); +int TeamFortress_TeamGetScoreFrags(int tno); +int TeamFortress_TeamGetNoPlayers(int tno); +float TeamEqualiseDamage(CBaseEntity *targ, CBaseEntity *attacker, float damage); +BOOL IsSpawnPointValid( Vector &pos ); +BOOL TeamFortress_SortTeams( void ); +void DumpClanScores( void ); +void CalculateTeamEqualiser(); + +// mapscript funcs +void ParseTFServerSettings(); +void ParseTFMapSettings(); +CBaseEntity* Finditem(int ino); +CBaseEntity* Findgoal(int gno); +CBaseEntity* Findteamspawn(int gno); +void RemoveGoal(CBaseEntity *Goal); +void tfgoalitem_GiveToPlayer(CBaseEntity *Item, CBasePlayer *AP, CBaseEntity *Goal); +void dremove( CBaseEntity *te ); +void tfgoalitem_RemoveFromPlayer(CBaseEntity *Item, CBasePlayer *AP, int iMethod); +void tfgoalitem_drop(CBaseEntity *Item, BOOL PAlive, CBasePlayer *P); +void DisplayItemStatus(CBaseEntity *Goal, CBasePlayer *Player, CBaseEntity *Item); +void tfgoalitem_checkgoalreturn(CBaseEntity *Item); +void DoGoalWork(CBaseEntity *Goal, CBasePlayer *AP); +void DoResults(CBaseEntity *Goal, CBasePlayer *AP, BOOL bAddBonuses); +void DoGroupWork(CBaseEntity *Goal, CBasePlayer *AP); +// hooks into the mapscript for all entities +BOOL ActivateDoResults(CBaseEntity *Goal, CBasePlayer *AP, CBaseEntity *ActivatingGoal); +BOOL ActivationSucceeded(CBaseEntity *Goal, CBasePlayer *AP, CBaseEntity *ActivatingGoal); + +// prematch & ceasefire +void Display_Prematch(); +void Check_Ceasefire(); + +// admin +void KickPlayer( CBaseEntity *pTarget ); +void BanPlayer( CBaseEntity *pTarget ); +CGhost *FindGhost( int iGhostID ); +int GetBattleID( edict_t *pEntity ); + +extern cvar_t tfc_spam_penalty1;// the initial gag penalty for a spammer (seconds) +extern cvar_t tfc_spam_penalty2;// incremental gag penalty (seconds) for each time gagged spammer continues to speak. +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 allow_spectators; + +/*==========================================================================*/ +class CTFFlame : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT FlameThink( void ); + static CTFFlame *FlameSpawn( CBaseEntity *pOwner, CBaseEntity *pTarget ); + void FlameDestroy( void ); + + float m_flNextDamageTime; +}; + +/*==========================================================================*/ +// MAPSCRIPT CLASSES +class CTFGoal : public CBaseAnimating +{ +public: + void Spawn( void ); + void StartGoal( void ); + void EXPORT PlaceGoal( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int Classify ( void ) { return CLASS_TFGOAL; } + + void SetObjectCollisionBox( void ); +}; + +class CTFGoalItem : public CTFGoal +{ +public: + void Spawn( void ); + void StartItem( void ); + void EXPORT PlaceItem( void ); + int Classify ( void ) { return CLASS_TFGOAL_ITEM; } + + float m_flDroppedAt; +}; + +class CTFTimerGoal : public CTFGoal +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_TFGOAL_TIMER; } +}; + +class CTFSpawn : public CBaseEntity +{ +public: + void Spawn( void ); + void Activate( void ); + int Classify ( void ) { return CLASS_TFSPAWN; } + BOOL CheckTeam( int iTeamNo ); + + EHANDLE m_pTeamCheck; +}; + +class CTFDetect : public CBaseEntity +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_TFGOAL; } +}; + +class CTelefragDeath : public CBaseEntity +{ +public: + void Spawn( void ); + void EXPORT DeathTouch( CBaseEntity *pOther ); +}; + +class CTeamCheck : public CBaseDelay +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + BOOL TeamMatches( int iTeam ); +}; + +class CTeamSet : public CBaseDelay +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; + +#endif // TF_DEFS_ONLY +#endif // __TF_DEFS_H + + diff --git a/hlsdk/cl_dll/train.cpp b/hlsdk/cl_dll/train.cpp new file mode 100644 index 00000000..d16a1046 --- /dev/null +++ b/hlsdk/cl_dll/train.cpp @@ -0,0 +1,85 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Train.cpp +// +// implementation of CHudAmmo class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE(m_Train, Train ) + + +int CHudTrain::Init(void) +{ + HOOK_MESSAGE( Train ); + + m_iPos = 0; + m_iFlags = 0; + gHUD.AddHudElem(this); + + return 1; +}; + +int CHudTrain::VidInit(void) +{ + m_hSprite = 0; + + return 1; +}; + +int CHudTrain::Draw(float fTime) +{ + if ( !m_hSprite ) + m_hSprite = LoadSprite("sprites/%d_train.spr"); + + if (m_iPos) + { + int r, g, b, x, y; + + UnpackRGB(r,g,b, RGB_YELLOWISH); + SPR_Set(m_hSprite, r, g, b ); + + // This should show up to the right and part way up the armor number + y = ScreenHeight - SPR_Height(m_hSprite,0) - gHUD.m_iFontHeight; + x = ScreenWidth/3 + SPR_Width(m_hSprite,0)/4; + + SPR_DrawAdditive( m_iPos - 1, x, y, NULL); + + } + + return 1; +} + + +int CHudTrain::MsgFunc_Train(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + // update Train data + m_iPos = READ_BYTE(); + + if (m_iPos) + m_iFlags |= HUD_ACTIVE; + else + m_iFlags &= ~HUD_ACTIVE; + + return 1; +} diff --git a/hlsdk/cl_dll/tri.cpp b/hlsdk/cl_dll/tri.cpp new file mode 100644 index 00000000..fc7fb8fb --- /dev/null +++ b/hlsdk/cl_dll/tri.cpp @@ -0,0 +1,124 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// Triangle rendering, if any + +#include "hud.h" +#include "cl_util.h" + +// Triangle rendering apis are in gEngfuncs.pTriAPI + +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "triangleapi.h" + +#define DLLEXPORT __declspec( dllexport ) + +extern "C" +{ + void DLLEXPORT HUD_DrawNormalTriangles( void ); + void DLLEXPORT HUD_DrawTransparentTriangles( void ); +}; + +//#define TEST_IT +#if defined( TEST_IT ) + +/* +================= +Draw_Triangles + +Example routine. Draws a sprite offset from the player origin. +================= +*/ +void Draw_Triangles( void ) +{ + cl_entity_t *player; + vec3_t org; + + // Load it up with some bogus data + player = gEngfuncs.GetLocalPlayer(); + if ( !player ) + return; + + org = player->origin; + + org.x += 50; + org.y += 50; + + if (gHUD.m_hsprCursor == 0) + { + char sz[256]; + sprintf( sz, "sprites/cursor.spr" ); + gHUD.m_hsprCursor = SPR_Load( sz ); + } + + 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 ); +} + +#endif + +/* +================= +HUD_DrawNormalTriangles + +Non-transparent triangles-- add them here +================= +*/ +void DLLEXPORT HUD_DrawNormalTriangles( void ) +{ + + gHUD.m_Spectator.DrawOverview(); + +#if defined( TEST_IT ) +// Draw_Triangles(); +#endif +} + +/* +================= +HUD_DrawTransparentTriangles + +Render any triangles with transparent rendermode needs here +================= +*/ +void DLLEXPORT HUD_DrawTransparentTriangles( void ) +{ + +#if defined( TEST_IT ) +// Draw_Triangles(); +#endif +} \ No newline at end of file diff --git a/hlsdk/cl_dll/util.cpp b/hlsdk/cl_dll/util.cpp new file mode 100644 index 00000000..f2e3f318 --- /dev/null +++ b/hlsdk/cl_dll/util.cpp @@ -0,0 +1,133 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// util.cpp +// +// implementation of class-less helper functions +// + +#include "STDIO.H" +#include "STDLIB.H" +#include "MATH.H" + +#include "hud.h" +#include "cl_util.h" +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +vec3_t vec3_origin( 0, 0, 0 ); + +double sqrt(double x); + +float Length(const float *v) +{ + int i; + float length; + + length = 0; + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = sqrt (length); // FIXME + + return length; +} + +void VectorAngles( 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; +} + +float VectorNormalize (float *v) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; + +} + +void VectorInverse ( float *v ) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void VectorScale (const float *in, float scale, float *out) +{ + out[0] = in[0]*scale; + out[1] = in[1]*scale; + out[2] = in[2]*scale; +} + +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]; +} + +HSPRITE LoadSprite(const char *pszName) +{ + int i; + char sz[256]; + + if (ScreenWidth < 640) + i = 320; + else + i = 640; + + sprintf(sz, pszName, i); + + return SPR_Load(sz); +} + diff --git a/hlsdk/cl_dll/util_vector.h b/hlsdk/cl_dll/util_vector.h new file mode 100644 index 00000000..4a1ba9cf --- /dev/null +++ b/hlsdk/cl_dll/util_vector.h @@ -0,0 +1,121 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Vector.h +// A subset of the extdll.h in the project HL Entity DLL +// + +// Misc C-runtime library headers +#include "STDIO.H" +#include "STDLIB.H" +#include "MATH.H" + +// Header file containing definition of globalvars_t and entvars_t +typedef int func_t; // +typedef int string_t; // from engine's pr_comp.h; +typedef float vec_t; // needed before including progdefs.h + +//========================================================= +// 2DVector - used for many pathfinding and many other +// operations that are treated as planar rather than 3d. +//========================================================= +class Vector2D +{ +public: + inline Vector2D(void) { } + inline Vector2D(float X, float Y) { x = X; y = Y; } + inline Vector2D operator+(const Vector2D& v) const { return Vector2D(x+v.x, y+v.y); } + inline Vector2D operator-(const Vector2D& v) const { return Vector2D(x-v.x, y-v.y); } + inline Vector2D operator*(float fl) const { return Vector2D(x*fl, y*fl); } + inline Vector2D operator/(float fl) const { return Vector2D(x/fl, y/fl); } + + inline float Length(void) const { return (float)sqrt(x*x + y*y ); } + + inline Vector2D Normalize ( void ) const + { + Vector2D vec2; + + float flLen = Length(); + if ( flLen == 0 ) + { + return Vector2D( (float)0, (float)0 ); + } + else + { + flLen = 1 / flLen; + return Vector2D( x * flLen, y * flLen ); + } + } + + vec_t x, y; +}; + +inline float DotProduct(const Vector2D& a, const Vector2D& b) { return( a.x*b.x + a.y*b.y ); } +inline Vector2D operator*(float fl, const Vector2D& v) { return v * fl; } + +//========================================================= +// 3D Vector +//========================================================= +class Vector // same data-layout as engine's vec3_t, +{ // which is a vec_t[3] +public: + // Construction/destruction + inline Vector(void) { } + inline Vector(float X, float Y, float Z) { x = X; y = Y; z = Z; } + inline Vector(double X, double Y, double Z) { x = (float)X; y = (float)Y; z = (float)Z; } + inline Vector(int X, int Y, int Z) { x = (float)X; y = (float)Y; z = (float)Z; } + inline Vector(const Vector& v) { x = v.x; y = v.y; z = v.z; } + inline Vector(float rgfl[3]) { x = rgfl[0]; y = rgfl[1]; z = rgfl[2]; } + + // Operators + inline Vector operator-(void) const { return Vector(-x,-y,-z); } + inline int operator==(const Vector& v) const { return x==v.x && y==v.y && z==v.z; } + inline int operator!=(const Vector& v) const { return !(*this==v); } + inline Vector operator+(const Vector& v) const { return Vector(x+v.x, y+v.y, z+v.z); } + inline Vector operator-(const Vector& v) const { return Vector(x-v.x, y-v.y, z-v.z); } + inline Vector operator*(float fl) const { return Vector(x*fl, y*fl, z*fl); } + inline Vector operator/(float fl) const { return Vector(x/fl, y/fl, z/fl); } + + // Methods + inline void CopyToArray(float* rgfl) const { rgfl[0] = x, rgfl[1] = y, rgfl[2] = z; } + inline float Length(void) const { return (float)sqrt(x*x + y*y + z*z); } + operator float *() { return &x; } // Vectors will now automatically convert to float * when needed + operator const float *() const { return &x; } // Vectors will now automatically convert to float * when needed + inline Vector Normalize(void) const + { + float flLen = Length(); + if (flLen == 0) return Vector(0,0,1); // ???? + flLen = 1 / flLen; + return Vector(x * flLen, y * flLen, z * flLen); + } + + inline Vector2D Make2D ( void ) const + { + Vector2D Vec2; + + Vec2.x = x; + Vec2.y = y; + + return Vec2; + } + inline float Length2D(void) const { return (float)sqrt(x*x + y*y); } + + // Members + vec_t x, y, z; +}; +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 ); } + +#define vec3_t Vector diff --git a/hlsdk/cl_dll/vgui_ClassMenu.cpp b/hlsdk/cl_dll/vgui_ClassMenu.cpp new file mode 100644 index 00000000..ba94fd01 --- /dev/null +++ b/hlsdk/cl_dll/vgui_ClassMenu.cpp @@ -0,0 +1,432 @@ +//=========== (C) Copyright 1996-2002 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: TFC Class Menu +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "VGUI_Font.h" +#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 "vgui_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ServerBrowser.h" + +// Class Menu Dimensions +#define CLASSMENU_TITLE_X XRES(40) +#define CLASSMENU_TITLE_Y YRES(32) +#define CLASSMENU_TOPLEFT_BUTTON_X XRES(40) +#define CLASSMENU_TOPLEFT_BUTTON_Y YRES(80) +#define CLASSMENU_BUTTON_SIZE_X XRES(124) +#define CLASSMENU_BUTTON_SIZE_Y YRES(24) +#define CLASSMENU_BUTTON_SPACER_Y YRES(8) +#define CLASSMENU_WINDOW_X XRES(176) +#define CLASSMENU_WINDOW_Y YRES(80) +#define CLASSMENU_WINDOW_SIZE_X XRES(424) +#define CLASSMENU_WINDOW_SIZE_Y YRES(312) +#define CLASSMENU_WINDOW_TEXT_X XRES(150) +#define CLASSMENU_WINDOW_TEXT_Y YRES(80) +#define CLASSMENU_WINDOW_NAME_X XRES(150) +#define CLASSMENU_WINDOW_NAME_Y YRES(8) +#define CLASSMENU_WINDOW_PLAYERS_Y YRES(42) + +// Creation +CClassMenuPanel::CClassMenuPanel(int iTrans, int iRemoveMe, int x,int y,int wide,int tall) : CMenuPanel(iTrans, iRemoveMe, x,y,wide,tall) +{ + // don't show class graphics at below 640x480 resolution + bool bShowClassGraphic = true; + if ( ScreenWidth < 640 ) + { + bShowClassGraphic = false; + } + + memset( m_pClassImages, 0, sizeof(m_pClassImages) ); + + // Get the scheme used for the Titles + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + + // schemes + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle( "Title Font" ); + SchemeHandle_t hClassWindowText = pSchemes->getSchemeHandle( "Briefing Text" ); + + // color schemes + int r, g, b, a; + + // Create the title + Label *pLabel = new Label( "", CLASSMENU_TITLE_X, CLASSMENU_TITLE_Y ); + pLabel->setParent( this ); + pLabel->setFont( pSchemes->getFont(hTitleScheme) ); + pSchemes->getFgColor( hTitleScheme, r, g, b, a ); + pLabel->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hTitleScheme, r, g, b, a ); + pLabel->setBgColor( r, g, b, a ); + pLabel->setContentAlignment( vgui::Label::a_west ); + pLabel->setText(gHUD.m_TextMessage.BufferedLocaliseTextString("#Title_SelectYourClass")); + + // Create the Scroll panel + m_pScrollPanel = new CTFScrollPanel( CLASSMENU_WINDOW_X, CLASSMENU_WINDOW_Y, CLASSMENU_WINDOW_SIZE_X, CLASSMENU_WINDOW_SIZE_Y ); + m_pScrollPanel->setParent(this); + //force the scrollbars on, so after the validate clientClip will be smaller + m_pScrollPanel->setScrollBarAutoVisible(false, false); + m_pScrollPanel->setScrollBarVisible(true, true); + m_pScrollPanel->setBorder( new LineBorder( Color(255 * 0.7,170 * 0.7,0,0) ) ); + m_pScrollPanel->validate(); + + int clientWide=m_pScrollPanel->getClient()->getWide(); + + //turn scrollpanel back into auto show scrollbar mode and validate + m_pScrollPanel->setScrollBarAutoVisible(false,true); + m_pScrollPanel->setScrollBarVisible(false,false); + m_pScrollPanel->validate(); + + // Create the Class buttons + for (int i = 0; i <= PC_RANDOM; i++) + { + char sz[256]; + int iYPos = CLASSMENU_TOPLEFT_BUTTON_Y + ( (CLASSMENU_BUTTON_SIZE_Y + CLASSMENU_BUTTON_SPACER_Y) * i ); + + ActionSignal *pASignal = new CMenuHandler_StringCommandClassSelect( sTFClassSelection[i], true ); + + // Class button + sprintf(sz, "%s", CHudTextMessage::BufferedLocaliseTextString( sLocalisedClasses[i] ) ); + m_pButtons[i] = new ClassButton( i, sz, CLASSMENU_TOPLEFT_BUTTON_X, iYPos, CLASSMENU_BUTTON_SIZE_X, CLASSMENU_BUTTON_SIZE_Y, true); + // RandomPC uses '0' + if ( i >= 1 && i <= 9 ) + { + sprintf(sz,"%d",i); + } + else + { + sprintf(sz,"0"); + } + m_pButtons[i]->setBoundKey( sz[0] ); + m_pButtons[i]->setContentAlignment( vgui::Label::a_west ); + m_pButtons[i]->addActionSignal( pASignal ); + m_pButtons[i]->addInputSignal( new CHandler_MenuButtonOver(this, i) ); + m_pButtons[i]->setParent( this ); + + // Create the Class Info Window + //m_pClassInfoPanel[i] = new CTransparentPanel( 255, CLASSMENU_WINDOW_X, CLASSMENU_WINDOW_Y, CLASSMENU_WINDOW_SIZE_X, CLASSMENU_WINDOW_SIZE_Y ); + m_pClassInfoPanel[i] = new CTransparentPanel( 255, 0, 0, clientWide, CLASSMENU_WINDOW_SIZE_Y ); + m_pClassInfoPanel[i]->setParent( m_pScrollPanel->getClient() ); + //m_pClassInfoPanel[i]->setVisible( false ); + + // don't show class pic in lower resolutions + int textOffs = XRES(8); + + if ( bShowClassGraphic ) + { + textOffs = CLASSMENU_WINDOW_NAME_X; + } + + // Create the Class Name Label + sprintf(sz, "#Title_%s", sTFClassSelection[i]); + char* localName=CHudTextMessage::BufferedLocaliseTextString( sz ); + Label *pNameLabel = new Label( "", textOffs, CLASSMENU_WINDOW_NAME_Y ); + pNameLabel->setFont( pSchemes->getFont(hTitleScheme) ); + pNameLabel->setParent( m_pClassInfoPanel[i] ); + pSchemes->getFgColor( hTitleScheme, r, g, b, a ); + pNameLabel->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hTitleScheme, r, g, b, a ); + pNameLabel->setBgColor( r, g, b, a ); + pNameLabel->setContentAlignment( vgui::Label::a_west ); + //pNameLabel->setBorder(new LineBorder()); + pNameLabel->setText(localName); + + // Create the Class Image + if ( bShowClassGraphic ) + { + for ( int team = 0; team < 2; team++ ) + { + if ( team == 1 ) + { + sprintf( sz, "%sred", sTFClassSelection[i] ); + } + else + { + sprintf( sz, "%sblue", sTFClassSelection[i] ); + } + + m_pClassImages[team][i] = new CImageLabel( sz, 0, 0, CLASSMENU_WINDOW_TEXT_X, CLASSMENU_WINDOW_TEXT_Y ); + + CImageLabel *pLabel = m_pClassImages[team][i]; + pLabel->setParent( m_pClassInfoPanel[i] ); + //pLabel->setBorder(new LineBorder()); + + if ( team != 1 ) + { + pLabel->setVisible( false ); + } + + // Reposition it based upon it's size + int xOut, yOut; + pNameLabel->getTextSize( xOut, yOut ); + pLabel->setPos( (CLASSMENU_WINDOW_TEXT_X - pLabel->getWide()) / 2, yOut /2 ); + } + } + + // Create the Player count string + gHUD.m_TextMessage.LocaliseTextString( "#Title_CurrentlyOnYourTeam", m_sPlayersOnTeamString, STRLENMAX_PLAYERSONTEAM ); + m_pPlayers[i] = new Label( "", textOffs, CLASSMENU_WINDOW_PLAYERS_Y ); + m_pPlayers[i]->setParent( m_pClassInfoPanel[i] ); + m_pPlayers[i]->setBgColor( 0, 0, 0, 255 ); + m_pPlayers[i]->setContentAlignment( vgui::Label::a_west ); + m_pPlayers[i]->setFont( pSchemes->getFont(hClassWindowText) ); + + // Open up the Class Briefing File + sprintf(sz, "classes/short_%s.txt", sTFClassSelection[i]); + char *cText = "Class Description not available."; + char *pfile = (char *)gEngfuncs.COM_LoadFile( sz, 5, NULL ); + if (pfile) + { + cText = pfile; + } + + // Create the Text info window + TextPanel *pTextWindow = new TextPanel(cText, textOffs, CLASSMENU_WINDOW_TEXT_Y, (CLASSMENU_WINDOW_SIZE_X - textOffs)-5, CLASSMENU_WINDOW_SIZE_Y - CLASSMENU_WINDOW_TEXT_Y); + pTextWindow->setParent( m_pClassInfoPanel[i] ); + pTextWindow->setFont( pSchemes->getFont(hClassWindowText) ); + pSchemes->getFgColor( hClassWindowText, r, g, b, a ); + pTextWindow->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hClassWindowText, r, g, b, a ); + pTextWindow->setBgColor( r, g, b, a ); + + // Resize the Info panel to fit it all + int wide,tall; + pTextWindow->getTextImage()->getTextSizeWrapped( wide,tall); + pTextWindow->setSize(wide,tall); + + int xx,yy; + pTextWindow->getPos(xx,yy); + int maxX=xx+wide; + int maxY=yy+tall; + + //check to see if the image goes lower than the text + //just use the red teams [0] images + if(m_pClassImages[0][i]!=null) + { + m_pClassImages[0][i]->getPos(xx,yy); + if((yy+m_pClassImages[0][i]->getTall())>maxY) + { + maxY=yy+m_pClassImages[0][i]->getTall(); + } + } + + m_pClassInfoPanel[i]->setSize( maxX , maxY ); + if (pfile) gEngfuncs.COM_FreeFile( pfile ); + //m_pClassInfoPanel[i]->setBorder(new LineBorder()); + + } + + // Create the Cancel button + m_pCancelButton = new CommandButton( gHUD.m_TextMessage.BufferedLocaliseTextString( "#Menu_Cancel" ), CLASSMENU_TOPLEFT_BUTTON_X, 0, CLASSMENU_BUTTON_SIZE_X, CLASSMENU_BUTTON_SIZE_Y); + m_pCancelButton->setParent( this ); + m_pCancelButton->addActionSignal( new CMenuHandler_TextWindow(HIDE_TEXTWINDOW) ); + + m_iCurrentInfo = 0; + +} + + +// Update +void CClassMenuPanel::Update() +{ + // Don't allow the player to join a team if they're not in a team + if (!g_iTeamNumber) + return; + + int iYPos = CLASSMENU_TOPLEFT_BUTTON_Y; + + // Cycle through the rest of the buttons + for (int i = 0; i <= PC_RANDOM; i++) + { + bool bCivilian = (gViewPort->GetValidClasses(g_iTeamNumber) == -1); + + if ( bCivilian ) + { + // If this team can only be civilians, only the civilian button's visible + if (i == 0) + { + m_pButtons[0]->setVisible( true ); + SetActiveInfo( 0 ); + iYPos += CLASSMENU_BUTTON_SIZE_Y + CLASSMENU_BUTTON_SPACER_Y; + } + else + { + m_pButtons[i]->setVisible( false ); + } + } + else + { + if ( m_pButtons[i]->IsNotValid() || i == 0 ) + { + m_pButtons[i]->setVisible( false ); + } + else + { + m_pButtons[i]->setVisible( true ); + m_pButtons[i]->setPos( CLASSMENU_TOPLEFT_BUTTON_X, iYPos ); + iYPos += CLASSMENU_BUTTON_SIZE_Y + CLASSMENU_BUTTON_SPACER_Y; + + // Start with the first option up + if (!m_iCurrentInfo) + SetActiveInfo( i ); + } + } + + // Now count the number of teammembers of this class + int iTotal = 0; + for ( int j = 1; j < MAX_PLAYERS; j++ ) + { + if ( g_PlayerInfoList[j].name == NULL ) + continue; // empty player slot, skip + if ( g_PlayerExtraInfo[j].teamname[0] == 0 ) + continue; // skip over players who are not in a team + if ( g_PlayerInfoList[j].thisplayer ) + continue; // skip this player + if ( g_PlayerExtraInfo[j].teamnumber != g_iTeamNumber ) + continue; // skip over players in other teams + + // If this team is forced to be civilians, just count the number of teammates + if ( g_PlayerExtraInfo[j].playerclass != i && !bCivilian ) + continue; + + iTotal++; + } + + char sz[256]; + sprintf(sz, m_sPlayersOnTeamString, iTotal); + m_pPlayers[i]->setText( sz ); + + // Set the text color to the teamcolor + m_pPlayers[i]->setFgColor( iTeamColors[g_iTeamNumber % iNumberOfTeamColors][0], + iTeamColors[g_iTeamNumber % iNumberOfTeamColors][1], + iTeamColors[g_iTeamNumber % iNumberOfTeamColors][2], + 0 ); + + // set the graphic to be the team pick + for ( int team = 0; team < MAX_TEAMS; team++ ) + { + // unset all the other images + if ( m_pClassImages[team][i] ) + { + m_pClassImages[team][i]->setVisible( false ); + } + + // set the current team image + if ( m_pClassImages[g_iTeamNumber-1][i] != NULL ) + { + m_pClassImages[g_iTeamNumber-1][i]->setVisible( true ); + } + else if ( m_pClassImages[0][i] ) + { + m_pClassImages[0][i]->setVisible( true ); + } + } + } + + // If the player already has a class, make the cancel button visible + if ( g_iPlayerClass ) + { + m_pCancelButton->setPos( CLASSMENU_TOPLEFT_BUTTON_X, iYPos ); + m_pCancelButton->setVisible( true ); + } + else + { + m_pCancelButton->setVisible( false ); + } +} + +//====================================== +// Key inputs for the Class Menu +bool CClassMenuPanel::SlotInput( int iSlot ) +{ + if ( (iSlot < 0) || (iSlot > 9) ) + return false; + if ( !m_pButtons[ iSlot ] ) + return false; + + // Is the button pushable? (0 is special case) + if (iSlot == 0) + { + // Selects Civilian and RandomPC + if ( gViewPort->GetValidClasses(g_iTeamNumber) == -1 ) + { + m_pButtons[ 0 ]->fireActionSignal(); + return true; + } + + // Select RandomPC + iSlot = 10; + } + + if ( !(m_pButtons[ iSlot ]->IsNotValid()) ) + { + m_pButtons[ iSlot ]->fireActionSignal(); + return true; + } + + return false; +} + +//====================================== +// Update the Class menu before opening it +void CClassMenuPanel::Open( void ) +{ + Update(); + CMenuPanel::Open(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called each time a new level is started. +//----------------------------------------------------------------------------- +void CClassMenuPanel::Initialize( void ) +{ + setVisible( false ); + m_pScrollPanel->setScrollValue( 0, 0 ); +} + +//====================================== +// Mouse is over a class button, bring up the class info +void CClassMenuPanel::SetActiveInfo( int iInput ) +{ + // Remove all the Info panels and bring up the specified one + for (int i = 0; i <= PC_RANDOM; i++) + { + m_pButtons[i]->setArmed( false ); + m_pClassInfoPanel[i]->setVisible( false ); + } + + if ( iInput > PC_RANDOM || iInput < 0 ) + iInput = 0; + + m_pButtons[iInput]->setArmed( true ); + m_pClassInfoPanel[iInput]->setVisible( true ); + m_iCurrentInfo = iInput; + + m_pScrollPanel->setScrollValue(0,0); + m_pScrollPanel->validate(); +} + diff --git a/hlsdk/cl_dll/vgui_ConsolePanel.cpp b/hlsdk/cl_dll/vgui_ConsolePanel.cpp new file mode 100644 index 00000000..a8b304c5 --- /dev/null +++ b/hlsdk/cl_dll/vgui_ConsolePanel.cpp @@ -0,0 +1,101 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include"vgui_ConsolePanel.h" +#include"hud.h" +#include +#include +#include +#include +#include + +using namespace vgui; + + +namespace +{ + +class Handler : public ActionSignal +{ +private: + + ConsolePanel* _consolePanel; + +public: + + Handler(ConsolePanel* consolePanel) + { + _consolePanel=consolePanel; + } + +public: + + virtual void actionPerformed(Panel* panel) + { + _consolePanel->doExecCommand(); + } + +}; + +} + + + +ConsolePanel::ConsolePanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) +{ + setBorder(new EtchedBorder()); + + _textGrid=new TextGrid(80,21,5,5,200,100); + _textGrid->setBorder(new LoweredBorder()); + _textGrid->setParent(this); + + _textEntry=new TextEntry("",5,5,200,20); + _textEntry->setParent(this); + _textEntry->addActionSignal(new Handler(this)); +} + +int ConsolePanel::print(const char* text) +{ + return _textGrid->printf("%s",text); +} + +int ConsolePanel::vprintf(const char* format,va_list argList) +{ + return _textGrid->vprintf(format,argList); +} + +int ConsolePanel::printf(const char* format,...) +{ + va_list argList; + va_start(argList,format); + int ret=vprintf(format,argList); + va_end(argList); + return ret; +} + +void ConsolePanel::doExecCommand() +{ + char buf[2048]; + _textEntry->getText(0,buf,2048); + _textEntry->setText(null,0); + gEngfuncs.pfnClientCmd(buf); +} + +void ConsolePanel::setSize(int wide,int tall) +{ + Panel::setSize(wide,tall); + + getPaintSize(wide,tall); + + _textGrid->setBounds(5,5,wide-10,tall-35); + _textEntry->setBounds(5,tall-25,wide-10,20); +} + + + + + diff --git a/hlsdk/cl_dll/vgui_ConsolePanel.h b/hlsdk/cl_dll/vgui_ConsolePanel.h new file mode 100644 index 00000000..84821f7f --- /dev/null +++ b/hlsdk/cl_dll/vgui_ConsolePanel.h @@ -0,0 +1,38 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef CONSOLEPANEL_H +#define CONSOLEPANEL_H + +#include +#include + +namespace vgui +{ +class TextGrid; +class TextEntry; +} + + +class ConsolePanel : public vgui::Panel +{ +private: + vgui::TextGrid* _textGrid; + vgui::TextEntry* _textEntry; +public: + ConsolePanel(int x,int y,int wide,int tall); +public: + virtual void setSize(int wide,int tall); + virtual int print(const char* text); + virtual int vprintf(const char* format,va_list argList); + virtual int printf(const char* format,...); + virtual void doExecCommand(); +}; + + + +#endif \ No newline at end of file diff --git a/hlsdk/cl_dll/vgui_ControlConfigPanel.cpp b/hlsdk/cl_dll/vgui_ControlConfigPanel.cpp new file mode 100644 index 00000000..0fd52641 --- /dev/null +++ b/hlsdk/cl_dll/vgui_ControlConfigPanel.cpp @@ -0,0 +1,212 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include"vgui_ControlConfigPanel.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace vgui; + +namespace +{ +class FooTablePanel : public TablePanel +{ +private: + Label* _label; + TextEntry* _textEntry; + ControlConfigPanel* _controlConfigPanel; +public: + FooTablePanel(ControlConfigPanel* controlConfigPanel,int x,int y,int wide,int tall,int columnCount) : TablePanel(x,y,wide,tall,columnCount) + { + _controlConfigPanel=controlConfigPanel; + _label=new Label("You are a dumb monkey",0,0,100,20); + _label->setBgColor(Scheme::sc_primary3); + _label->setFgColor(Scheme::sc_primary1); + _label->setFont(Scheme::sf_primary3); + + _textEntry=new TextEntry("",0,0,100,20); + //_textEntry->setFont(Scheme::sf_primary3); + } +public: + virtual int getRowCount() + { + return _controlConfigPanel->GetCVarCount(); + } + virtual int getCellTall(int row) + { + return 12; + } + virtual Panel* getCellRenderer(int column,int row,bool columnSelected,bool rowSelected,bool cellSelected) + { + char cvar[128],desc[128],bind[128],bindAlt[128]; + _controlConfigPanel->GetCVar(row,cvar,128,desc,128); + + if(cellSelected) + { + _label->setBgColor(Scheme::sc_primary1); + _label->setFgColor(Scheme::sc_primary3); + } + else + if(rowSelected) + { + _label->setBgColor(Scheme::sc_primary2); + _label->setFgColor(Scheme::sc_primary1); + } + else + { + _label->setBgColor(Scheme::sc_primary3); + _label->setFgColor(Scheme::sc_primary1); + } + + switch(column) + { + case 0: + { + _label->setText(desc); + _label->setContentAlignment(Label::a_west); + break; + } + case 1: + { + _controlConfigPanel->GetCVarBind(cvar,bind,128,bindAlt,128); + _label->setText(bind); + _label->setContentAlignment(Label::a_center); + break; + } + case 2: + { + _controlConfigPanel->GetCVarBind(cvar,bind,128,bindAlt,128); + _label->setText(bindAlt); + _label->setContentAlignment(Label::a_center); + break; + } + default: + { + _label->setText(""); + break; + } + } + + return _label; + } + virtual Panel* startCellEditing(int column,int row) + { + _textEntry->setText("Goat",strlen("Goat")); + _textEntry->requestFocus(); + return _textEntry; + } +}; +} + +ControlConfigPanel::ControlConfigPanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) +{ + setPaintBorderEnabled(false); + setPaintBackgroundEnabled(false); + setPaintEnabled(false); + + _actionLabel=new Label("Action"); + _actionLabel->setBgColor(Scheme::sc_primary3); + _actionLabel->setFgColor(Scheme::sc_primary3); + + _keyButtonLabel=new Label("Key / Button"); + _keyButtonLabel->setBgColor(Scheme::sc_primary3); + _keyButtonLabel->setFgColor(Scheme::sc_primary3); + + _alternateLabel=new Label("Alternate"); + _alternateLabel->setBgColor(Scheme::sc_primary3); + _alternateLabel->setFgColor(Scheme::sc_primary3); + + _headerPanel=new HeaderPanel(0,0,wide,20); + _headerPanel->setParent(this); + + _headerPanel->addSectionPanel(_actionLabel); + _headerPanel->addSectionPanel(_keyButtonLabel); + _headerPanel->addSectionPanel(_alternateLabel); + + _headerPanel->setSliderPos( 0, wide/2 ); + _headerPanel->setSliderPos( 1, (wide/2) + (wide/4) ); + _headerPanel->setSliderPos( 2, wide ); + + _scrollPanel=new ScrollPanel(0,20,wide,tall-20); + _scrollPanel->setParent(this); + _scrollPanel->setPaintBorderEnabled(false); + _scrollPanel->setPaintBackgroundEnabled(false); + _scrollPanel->setPaintEnabled(false); + _scrollPanel->getClient()->setPaintBorderEnabled(false); + _scrollPanel->getClient()->setPaintBackgroundEnabled(false); + _scrollPanel->getClient()->setPaintEnabled(false); + _scrollPanel->setScrollBarVisible(false,true); + + _tablePanel=new FooTablePanel(this,0,0,_scrollPanel->getClient()->getWide(),800, 3); + _tablePanel->setParent(_scrollPanel->getClient()); + _tablePanel->setHeaderPanel(_headerPanel); + _tablePanel->setBgColor(Color(200,0,0,255)); + _tablePanel->setFgColor(Color(Scheme::sc_primary2)); + _tablePanel->setGridVisible(true,true); + _tablePanel->setGridSize(1,1); +} + +void ControlConfigPanel::AddCVar(const char* cvar,const char* desc) +{ + _cvarDar.addElement(vgui_strdup(cvar)); + _descDar.addElement(vgui_strdup(desc)); +} + +int ControlConfigPanel::GetCVarCount() +{ + return _cvarDar.getCount(); +} + +void ControlConfigPanel::GetCVar(int index,char* cvar,int cvarLen,char* desc,int descLen) +{ + vgui_strcpy(cvar,cvarLen,_cvarDar[index]); + vgui_strcpy(desc,descLen,_descDar[index]); +} + +void ControlConfigPanel::AddCVarFromInputStream(InputStream* is) +{ + if(is==null) + { + return; + } + + DataInputStream dis(is); + + bool success; + + while(1) + { + char buf[256],cvar[128],desc[128]; + dis.readLine(buf,256,success); + if(!success) + { + break; + } + if(sscanf(buf,"\"%[^\"]\" \"%[^\"]\"",cvar,desc)==2) + { + AddCVar(cvar,desc); + } + } +} + +void ControlConfigPanel::GetCVarBind(const char* cvar,char* bind,int bindLen,char* bindAlt,int bindAltLen) +{ + sprintf(bind,"%s : Bind",cvar); + sprintf(bindAlt,"%s : BindAlt",cvar); +} + +void ControlConfigPanel::SetCVarBind(const char* cvar,const char* bind,const char* bindAlt) +{ +} + diff --git a/hlsdk/cl_dll/vgui_ControlConfigPanel.h b/hlsdk/cl_dll/vgui_ControlConfigPanel.h new file mode 100644 index 00000000..15ca3fdc --- /dev/null +++ b/hlsdk/cl_dll/vgui_ControlConfigPanel.h @@ -0,0 +1,47 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef CONTROLCONFIGPANEL_H +#define CONTROLCONFIGPANEL_H + +#include +#include + +namespace vgui +{ +class HeaderPanel; +class TablePanel; +class ScrollPanel; +class InputStream; +class Label; +} + +class ControlConfigPanel : public vgui::Panel +{ +private: + vgui::HeaderPanel* _headerPanel; + vgui::TablePanel* _tablePanel; + vgui::ScrollPanel* _scrollPanel; + vgui::Dar _cvarDar; + vgui::Dar _descDar; + vgui::Label* _actionLabel; + vgui::Label* _keyButtonLabel; + vgui::Label* _alternateLabel; +public: + ControlConfigPanel(int x,int y,int wide,int tall); +public: + void AddCVar(const char* cvar,const char* desc); + void AddCVarFromInputStream(vgui::InputStream* is); + int GetCVarCount(); + void GetCVar(int index,char* cvar,int cvarLen,char* desc,int descLen); + void GetCVarBind(const char* cvar,char* bind,int bindLen,char* bindAlt,int bindAltLen); + void SetCVarBind(const char* cvar,const char* bind,const char* bindAlt); +}; + + + +#endif \ No newline at end of file diff --git a/hlsdk/cl_dll/vgui_CustomObjects.cpp b/hlsdk/cl_dll/vgui_CustomObjects.cpp new file mode 100644 index 00000000..441a50e2 --- /dev/null +++ b/hlsdk/cl_dll/vgui_CustomObjects.cpp @@ -0,0 +1,536 @@ +//=========== (C) Copyright 1996-2002 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: Contains implementation of various VGUI-derived objects +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "VGUI_Font.h" + +#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 "vgui_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ServerBrowser.h" +#include "..\game_shared\vgui_LoadTGA.h" + +// Arrow filenames +char *sArrowFilenames[] = +{ + "arrowup", + "arrowdn", + "arrowlt", + "arrowrt", +}; + +// Get the name of TGA file, without a gamedir +char *GetTGANameForRes(const char *pszName) +{ + int i; + char sz[256]; + static char gd[256]; + if (ScreenWidth < 640) + i = 320; + else + i = 640; + sprintf(sz, pszName, i); + sprintf(gd, "gfx/vgui/%s.tga", sz); + return gd; +} + +//----------------------------------------------------------------------------- +// Purpose: Loads a .tga file and returns a pointer to the VGUI tga object +//----------------------------------------------------------------------------- +BitmapTGA *LoadTGAForRes( const char* pImageName ) +{ + BitmapTGA *pTGA; + + char sz[256]; + sprintf(sz, "%%d_%s", pImageName); + pTGA = vgui_LoadTGA(GetTGANameForRes(sz)); + + return pTGA; +} + +//=========================================================== +// All TFC Hud buttons are derived from this one. +CommandButton::CommandButton( const char* text,int x,int y,int wide,int tall, bool bNoHighlight) : Button("",x,y,wide,tall) +{ + m_iPlayerClass = 0; + m_bNoHighlight = bNoHighlight; + m_bFlat = false; + Init(); + setText( text ); +} + +CommandButton::CommandButton( int iPlayerClass, const char* text,int x,int y,int wide,int tall, bool bFlat) : Button("",x,y,wide,tall) +{ + m_iPlayerClass = iPlayerClass; + m_bNoHighlight = false; + m_bFlat = bFlat; + Init(); + setText( text ); +} + +CommandButton::CommandButton(const char *text, int x, int y, int wide, int tall, bool bNoHighlight, bool bFlat) : Button("",x,y,wide,tall) +{ + m_iPlayerClass = 0; + m_bFlat = bFlat; + m_bNoHighlight = bNoHighlight; + Init(); + setText( text ); +} + +void CommandButton::Init( void ) +{ + m_pSubMenu = NULL; + m_pSubLabel = NULL; + m_pParentMenu = NULL; + + // Set text color to orange + setFgColor(Scheme::sc_primary1); + + // left align + setContentAlignment( vgui::Label::a_west ); + + // Add the Highlight signal + if (!m_bNoHighlight) + addInputSignal( new CHandler_CommandButtonHighlight(this) ); + + // not bound to any button yet + m_cBoundKey = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Prepends the button text with the current bound key +// if no bound key, then a clear space ' ' instead +//----------------------------------------------------------------------------- +void CommandButton::RecalculateText( void ) +{ + char szBuf[128]; + + if ( m_cBoundKey != 0 ) + { + if ( m_cBoundKey == (char)255 ) + { + strcpy( szBuf, m_sMainText ); + } + else + { + sprintf( szBuf, " %c %s", m_cBoundKey, m_sMainText ); + } + szBuf[MAX_BUTTON_SIZE-1] = 0; + } + else + { + // just draw a space if no key bound + sprintf( szBuf, " %s", m_sMainText ); + szBuf[MAX_BUTTON_SIZE-1] = 0; + } + + Button::setText( szBuf ); +} + +void CommandButton::setText( const char *text ) +{ + strncpy( m_sMainText, text, MAX_BUTTON_SIZE ); + m_sMainText[MAX_BUTTON_SIZE-1] = 0; + + RecalculateText(); +} + +void CommandButton::setBoundKey( char boundKey ) +{ + m_cBoundKey = boundKey; + RecalculateText(); +} + +char CommandButton::getBoundKey( void ) +{ + return m_cBoundKey; +} + +void CommandButton::AddSubMenu( CCommandMenu *pNewMenu ) +{ + m_pSubMenu = pNewMenu; + + // Prevent this button from being pushed + setMouseClickEnabled( MOUSE_LEFT, false ); +} + +void CommandButton::UpdateSubMenus( int iAdjustment ) +{ + if ( m_pSubMenu ) + m_pSubMenu->RecalculatePositions( iAdjustment ); +} + +void CommandButton::paint() +{ + // Make the sub label paint the same as the button + if ( m_pSubLabel ) + { + if ( isSelected() ) + m_pSubLabel->PushDown(); + else + m_pSubLabel->PushUp(); + } + + // draw armed button text in white + if ( isArmed() ) + { + setFgColor( Scheme::sc_secondary2 ); + } + else + { + setFgColor( Scheme::sc_primary1 ); + } + + Button::paint(); +} + +void CommandButton::paintBackground() +{ + if ( m_bFlat ) + { + if ( isArmed() ) + { + // Orange Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect(0,0,_size[0],_size[1]); + } + } + else + { + if ( isArmed() ) + { + // Orange highlight background + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect(0,0,_size[0],_size[1]); + } + + // Orange Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect(0,0,_size[0],_size[1]); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Highlights the current button, and all it's parent menus +//----------------------------------------------------------------------------- +void CommandButton::cursorEntered( void ) +{ + // unarm all the other buttons in this menu + CCommandMenu *containingMenu = getParentMenu(); + if ( containingMenu ) + { + containingMenu->ClearButtonsOfArmedState(); + + // make all our higher buttons armed + CCommandMenu *pCParent = containingMenu->GetParentMenu(); + if ( pCParent ) + { + CommandButton *pParentButton = pCParent->FindButtonWithSubmenu( containingMenu ); + + pParentButton->cursorEntered(); + } + } + + // arm ourselves + setArmed( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CommandButton::cursorExited( void ) +{ + // only clear ourselves if we have do not have a containing menu + // only stay armed if we have a sub menu + // the buttons only unarm themselves when another button is armed instead + if ( !getParentMenu() || !GetSubMenu() ) + { + setArmed( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the command menu that the button is part of, if any +// Output : CCommandMenu * +//----------------------------------------------------------------------------- +CCommandMenu *CommandButton::getParentMenu( void ) +{ + return m_pParentMenu; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the menu that contains this button +// Input : *pParentMenu - +//----------------------------------------------------------------------------- +void CommandButton::setParentMenu( CCommandMenu *pParentMenu ) +{ + m_pParentMenu = pParentMenu; +} + + +//=========================================================== +int ClassButton::IsNotValid() +{ + // If this is the main ChangeClass button, remove it if the player's only able to be civilians + if ( m_iPlayerClass == -1 ) + { + if (gViewPort->GetValidClasses(g_iTeamNumber) == -1) + return true; + + return false; + } + + // Is it an illegal class? + if ((gViewPort->GetValidClasses(0) & sTFValidClassInts[ m_iPlayerClass ]) || (gViewPort->GetValidClasses(g_iTeamNumber) & sTFValidClassInts[ m_iPlayerClass ])) + return true; + + // Only check current class if they've got autokill on + bool bAutoKill = CVAR_GET_FLOAT( "hud_classautokill" ) != 0; + if ( bAutoKill ) + { + // Is it the player's current class? + if ( (gViewPort->IsRandomPC() && m_iPlayerClass == PC_RANDOM) || (!gViewPort->IsRandomPC() && (m_iPlayerClass == g_iPlayerClass)) ) + return true; + } + + return false; +} + +//=========================================================== +// Button with Class image beneath it +CImageLabel::CImageLabel( const char* pImageName,int x,int y ) : Label( "", x,y ) +{ + setContentFitted(true); + m_pTGA = LoadTGAForRes(pImageName); + setImage( m_pTGA ); +} + +CImageLabel::CImageLabel( const char* pImageName,int x,int y,int wide,int tall ) : Label( "", x,y,wide,tall ) +{ + setContentFitted(true); + m_pTGA = LoadTGAForRes(pImageName); + setImage( m_pTGA ); +} + +//=========================================================== +// Image size +int CImageLabel::getImageWide( void ) +{ + if( m_pTGA ) + { + int iXSize, iYSize; + m_pTGA->getSize( iXSize, iYSize ); + return iXSize; + } + else + { + return 1; + } +} + +int CImageLabel::getImageTall( void ) +{ + if( m_pTGA ) + { + int iXSize, iYSize; + m_pTGA->getSize( iXSize, iYSize ); + return iYSize; + } + else + { + return 1; + } +} + +void CImageLabel::LoadImage(const char * pImageName) +{ + if ( m_pTGA ) + delete m_pTGA; + + // Load the Image + m_pTGA = LoadTGAForRes(pImageName); + + if ( m_pTGA == NULL ) + { + // we didn't find a matching image file for this resolution + // try to load file resolution independent + + char sz[256]; + sprintf(sz, "%s/%s",gEngfuncs.pfnGetGameDirectory(), pImageName ); + FileInputStream* fis = new FileInputStream( sz, false ); + m_pTGA = new BitmapTGA(fis,true); + fis->close(); + } + + if ( m_pTGA == NULL ) + return; // unable to load image + + int w,t; + + m_pTGA->getSize( w, t ); + + setSize( XRES (w),YRES (t) ); + setImage( m_pTGA ); +} + +//=========================================================== +// Various overloaded paint functions for Custom VGUI objects +void CCommandMenu::paintBackground() +{ + // Transparent black background + + if ( m_iSpectCmdMenu ) + drawSetColor( 0, 0, 0, 64 ); + else + drawSetColor(Scheme::sc_primary3); + + drawFilledRect(0,0,_size[0],_size[1]); +} + +//================================================================================= +// CUSTOM SCROLLPANEL +//================================================================================= +CTFScrollButton::CTFScrollButton(int iArrow, const char* text,int x,int y,int wide,int tall) : CommandButton(text,x,y,wide,tall) +{ + // Set text color to orange + setFgColor(Scheme::sc_primary1); + + // Load in the arrow + m_pTGA = LoadTGAForRes( sArrowFilenames[iArrow] ); + setImage( m_pTGA ); + + // Highlight signal + InputSignal *pISignal = new CHandler_CommandButtonHighlight(this); + addInputSignal(pISignal); +} + +void CTFScrollButton::paint( void ) +{ + if (!m_pTGA) + return; + + // draw armed button text in white + if ( isArmed() ) + { + m_pTGA->setColor( Color(255,255,255, 0) ); + } + else + { + m_pTGA->setColor( Color(255,255,255, 128) ); + } + + m_pTGA->doPaint(this); +} + +void CTFScrollButton::paintBackground( void ) +{ +/* + if ( isArmed() ) + { + // Orange highlight background + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect(0,0,_size[0],_size[1]); + } + + // Orange Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect(0,0,_size[0]-1,_size[1]); +*/ +} + +void CTFSlider::paintBackground( void ) +{ + int wide,tall,nobx,noby; + getPaintSize(wide,tall); + getNobPos(nobx,noby); + + // Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect( 0,0,wide,tall ); + + if( isVertical() ) + { + // Nob Fill + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect( 0,nobx,wide,noby ); + + // Nob Outline + drawSetColor( Scheme::sc_primary1 ); + drawOutlinedRect( 0,nobx,wide,noby ); + } + else + { + // Nob Fill + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect( nobx,0,noby,tall ); + + // Nob Outline + drawSetColor( Scheme::sc_primary1 ); + drawOutlinedRect( nobx,0,noby,tall ); + } +} + +CTFScrollPanel::CTFScrollPanel(int x,int y,int wide,int tall) : ScrollPanel(x,y,wide,tall) +{ + ScrollBar *pScrollBar = getVerticalScrollBar(); + pScrollBar->setButton( new CTFScrollButton( ARROW_UP, "", 0,0,16,16 ), 0 ); + pScrollBar->setButton( new CTFScrollButton( ARROW_DOWN, "", 0,0,16,16 ), 1 ); + pScrollBar->setSlider( new CTFSlider(0,wide-1,wide,(tall-(wide*2))+2,true) ); + pScrollBar->setPaintBorderEnabled(false); + pScrollBar->setPaintBackgroundEnabled(false); + pScrollBar->setPaintEnabled(false); + + pScrollBar = getHorizontalScrollBar(); + pScrollBar->setButton( new CTFScrollButton( ARROW_LEFT, "", 0,0,16,16 ), 0 ); + pScrollBar->setButton( new CTFScrollButton( ARROW_RIGHT, "", 0,0,16,16 ), 1 ); + pScrollBar->setSlider( new CTFSlider(tall,0,wide-(tall*2),tall,false) ); + pScrollBar->setPaintBorderEnabled(false); + pScrollBar->setPaintBackgroundEnabled(false); + pScrollBar->setPaintEnabled(false); +} + + +//================================================================================= +// CUSTOM HANDLERS +//================================================================================= +void CHandler_MenuButtonOver::cursorEntered(Panel *panel) +{ + if ( gViewPort && m_pMenuPanel ) + { + m_pMenuPanel->SetActiveInfo( m_iButton ); + } +} + +void CMenuHandler_StringCommandClassSelect::actionPerformed(Panel* panel) +{ + CMenuHandler_StringCommand::actionPerformed( panel ); + + bool bAutoKill = CVAR_GET_FLOAT( "hud_classautokill" ) != 0; + if ( bAutoKill && g_iPlayerClass != 0 ) + gEngfuncs.pfnClientCmd("kill"); +} + diff --git a/hlsdk/cl_dll/vgui_MOTDWindow.cpp b/hlsdk/cl_dll/vgui_MOTDWindow.cpp new file mode 100644 index 00000000..b6f46dd7 --- /dev/null +++ b/hlsdk/cl_dll/vgui_MOTDWindow.cpp @@ -0,0 +1,154 @@ +//=========== (C) Copyright 1996-2002 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: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "VGUI_Font.h" +#include "VGUI_ScrollPanel.h" +#include "VGUI_TextImage.h" + +#include + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "const.h" + +#include "vgui_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ServerBrowser.h" + +#define MOTD_TITLE_X XRES(16) +#define MOTD_TITLE_Y YRES(16) + +#define MOTD_WINDOW_X XRES(112) +#define MOTD_WINDOW_Y YRES(80) +#define MOTD_WINDOW_SIZE_X XRES(424) +#define MOTD_WINDOW_SIZE_Y YRES(312) + +//----------------------------------------------------------------------------- +// Purpose: Displays the MOTD and basic server information +//----------------------------------------------------------------------------- +class CMessageWindowPanel : public CMenuPanel +{ +public: + CMessageWindowPanel( const char *szMOTD, const char *szTitle, int iShadeFullScreen, int iRemoveMe, int x, int y, int wide, int tall ); + +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 ) +{ + return new CMessageWindowPanel( szMOTD, szTitle, iShadeFullscreen, iRemoveMe, 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 ) +{ + // 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; + + // 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->setVisible( true ); + + int iXSize,iYSize,iXPos,iYPos; + m_pBackgroundPanel->getPos( iXPos,iYPos ); + m_pBackgroundPanel->getSize( iXSize,iYSize ); + + // Create the title + Label *pLabel = new Label( "", iXPos + MOTD_TITLE_X, iYPos + MOTD_TITLE_Y ); + pLabel->setParent( this ); + pLabel->setFont( pSchemes->getFont(hTitleScheme) ); + pLabel->setFont( Scheme::sf_primary1 ); + + pSchemes->getFgColor( hTitleScheme, r, g, b, a ); + pLabel->setFgColor( r, g, b, a ); + pLabel->setFgColor( Scheme::sc_primary1 ); + pSchemes->getBgColor( hTitleScheme, r, g, b, a ); + pLabel->setBgColor( r, g, b, a ); + pLabel->setContentAlignment( vgui::Label::a_west ); + pLabel->setText(szTitle); + + // Create the Scroll panel + ScrollPanel *pScrollPanel = new CTFScrollPanel( iXPos + XRES(16), iYPos + MOTD_TITLE_Y*2 + YRES(16), iXSize - XRES(32), iYSize - (YRES(48) + BUTTON_SIZE_Y*2) ); + pScrollPanel->setParent(this); + + //force the scrollbars on so clientClip will take them in account after the validate + pScrollPanel->setScrollBarAutoVisible(false, false); + pScrollPanel->setScrollBarVisible(true, true); + pScrollPanel->validate(); + + // Create the text panel + TextPanel *pText = new TextPanel( "", 0,0, 64,64); + pText->setParent( pScrollPanel->getClient() ); + + // get the font and colors from the scheme + pText->setFont( pSchemes->getFont(hMOTDText) ); + pSchemes->getFgColor( hMOTDText, r, g, b, a ); + pText->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hMOTDText, r, g, b, a ); + pText->setBgColor( r, g, b, a ); + pText->setText(szMOTD); + + // 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 wdith 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. + pText->getTextImage()->setSize(pScrollPanel->getClientClip()->getWide(), pScrollPanel->getClientClip()->getTall()); + pText->getTextImage()->getTextSizeWrapped( iScrollSizeX, iScrollSizeY ); + + // Now resize the textpanel to fit the scrolled size + pText->setSize( iScrollSizeX , iScrollSizeY ); + + //turn the scrollbars back into automode + pScrollPanel->setScrollBarAutoVisible(true, true); + pScrollPanel->setScrollBarVisible(false, false); + + 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); + +} + + + + + + diff --git a/hlsdk/cl_dll/vgui_SchemeManager.cpp b/hlsdk/cl_dll/vgui_SchemeManager.cpp new file mode 100644 index 00000000..571dca37 --- /dev/null +++ b/hlsdk/cl_dll/vgui_SchemeManager.cpp @@ -0,0 +1,544 @@ +//=========== (C) Copyright 1996-2002 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: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "hud.h" +#include "vgui_SchemeManager.h" +#include "cvardef.h" + +#include + + +cvar_t *g_CV_BitmapFonts; + + +void Scheme_Init() +{ + g_CV_BitmapFonts = gEngfuncs.pfnRegisterVariable("bitmapfonts", "1", 0); +} + + + +//----------------------------------------------------------------------------- +// Purpose: Scheme managers data container +//----------------------------------------------------------------------------- +class CSchemeManager::CScheme +{ +public: + enum { + SCHEME_NAME_LENGTH = 32, + FONT_NAME_LENGTH = 48, + FONT_FILENAME_LENGTH = 64, + }; + + // name + char schemeName[SCHEME_NAME_LENGTH]; + + // font + char fontName[FONT_NAME_LENGTH]; + + int fontSize; + int fontWeight; + + vgui::Font *font; + int ownFontPointer; // true if the font is ours to delete + + // scheme + byte fgColor[4]; + byte bgColor[4]; + byte armedFgColor[4]; + byte armedBgColor[4]; + byte mousedownFgColor[4]; + byte mousedownBgColor[4]; + byte borderColor[4]; + + // construction/destruction + CScheme(); + ~CScheme(); +}; + +CSchemeManager::CScheme::CScheme() +{ + schemeName[0] = 0; + fontName[0] = 0; + fontSize = 0; + fontWeight = 0; + font = NULL; + ownFontPointer = false; +} + +CSchemeManager::CScheme::~CScheme() +{ + // only delete our font pointer if we own it + if ( ownFontPointer ) + { + delete font; + } +} + +//----------------------------------------------------------------------------- +// Purpose: resolution information +// !! needs to be shared out +//----------------------------------------------------------------------------- +static int g_ResArray[] = +{ + 320, + 400, + 512, + 640, + 800, + 1024, + 1152, + 1280, + 1600 +}; +static int g_NumReses = sizeof(g_ResArray) / sizeof(int); + +static byte *LoadFileByResolution( const char *filePrefix, int xRes, const char *filePostfix ) +{ + // find our resolution in the res array + int resNum = g_NumReses - 1; + while ( g_ResArray[resNum] > xRes ) + { + resNum--; + + if ( resNum < 0 ) + return NULL; + } + + // try open the file + byte *pFile = NULL; + while ( 1 ) + { + + // try load + char fname[256]; + sprintf( fname, "%s%d%s", filePrefix, g_ResArray[resNum], filePostfix ); + pFile = gEngfuncs.COM_LoadFile( fname, 5, NULL ); + + if ( pFile ) + break; + + if ( resNum == 0 ) + return NULL; + + resNum--; + }; + + return pFile; +} + +static void ParseRGBAFromString( byte colorArray[4], const char *colorVector ) +{ + int r, g, b, a; + sscanf( colorVector, "%d %d %d %d", &r, &g, &b, &a ); + colorArray[0] = r; + colorArray[1] = g; + colorArray[2] = b; + colorArray[3] = a; +} + +//----------------------------------------------------------------------------- +// Purpose: initializes the scheme manager +// loading the scheme files for the current resolution +// Input : xRes - +// yRes - dimensions of output window +//----------------------------------------------------------------------------- +CSchemeManager::CSchemeManager( int xRes, int yRes ) +{ + // basic setup + m_pSchemeList = NULL; + m_iNumSchemes = 0; + + // find the closest matching scheme file to our resolution + char token[1024]; + char *pFile = (char*)LoadFileByResolution( "", xRes, "_textscheme.txt" ); + m_xRes = xRes; + + char *pFileStart = pFile; + + byte *pFontData; + int fontFileLength; + char fontFilename[512]; + + // + // Read the scheme descriptions from the text file, into a temporary array + // format is simply: + // = + // + // a of "SchemeName" signals a new scheme is being described + // + + const static int numTmpSchemes = 64; + static CScheme tmpSchemes[numTmpSchemes]; + memset( tmpSchemes, 0, sizeof(tmpSchemes) ); + int currentScheme = -1; + CScheme *pScheme = NULL; + + if ( !pFile ) + { + gEngfuncs.Con_DPrintf( "Unable to find *_textscheme.txt\n"); + goto buildDefaultFont; + } + + // record what has been entered so we can create defaults from the different values + bool hasFgColor, hasBgColor, hasArmedFgColor, hasArmedBgColor, hasMouseDownFgColor, hasMouseDownBgColor; + + pFile = gEngfuncs.COM_ParseFile( pFile, token ); + while ( strlen(token) > 0 && (currentScheme < numTmpSchemes) ) + { + // get the paramName name + static const int tokenSize = 64; + char paramName[tokenSize], paramValue[tokenSize]; + + strncpy( paramName, token, tokenSize ); + paramName[tokenSize-1] = 0; // ensure null termination + + // get the '=' character + pFile = gEngfuncs.COM_ParseFile( pFile, token ); + if ( stricmp( token, "=" ) ) + { + if ( currentScheme < 0 ) + { + gEngfuncs.Con_Printf( "error parsing font scheme text file at file start - expected '=', found '%s''\n", token ); + } + else + { + gEngfuncs.Con_Printf( "error parsing font scheme text file at scheme '%s' - expected '=', found '%s''\n", tmpSchemes[currentScheme].schemeName, token ); + } + break; + } + + // get paramValue + pFile = gEngfuncs.COM_ParseFile( pFile, token ); + strncpy( paramValue, token, tokenSize ); + paramValue[tokenSize-1] = 0; // ensure null termination + + // is this a new scheme? + if ( !stricmp(paramName, "SchemeName") ) + { + // setup the defaults for the current scheme + if ( pScheme ) + { + // foreground color defaults (normal -> armed -> mouse down) + if ( !hasFgColor ) + { + pScheme->fgColor[0] = pScheme->fgColor[1] = pScheme->fgColor[2] = pScheme->fgColor[3] = 255; + } + if ( !hasArmedFgColor ) + { + memcpy( pScheme->armedFgColor, pScheme->fgColor, sizeof(pScheme->armedFgColor) ); + } + if ( !hasMouseDownFgColor ) + { + memcpy( pScheme->mousedownFgColor, pScheme->armedFgColor, sizeof(pScheme->mousedownFgColor) ); + } + + // background color (normal -> armed -> mouse down) + if ( !hasBgColor ) + { + pScheme->bgColor[0] = pScheme->bgColor[1] = pScheme->bgColor[2] = pScheme->bgColor[3] = 0; + } + if ( !hasArmedBgColor ) + { + memcpy( pScheme->armedBgColor, pScheme->bgColor, sizeof(pScheme->armedBgColor) ); + } + if ( !hasMouseDownBgColor ) + { + memcpy( pScheme->mousedownBgColor, pScheme->armedBgColor, sizeof(pScheme->mousedownBgColor) ); + } + + // font size + if ( !pScheme->fontSize ) + { + pScheme->fontSize = 17; + } + if ( !pScheme->fontName[0] ) + { + strcpy( pScheme->fontName, "Arial" ); + } + } + + // create the new scheme + currentScheme++; + pScheme = &tmpSchemes[currentScheme]; + hasFgColor = hasBgColor = hasArmedFgColor = hasArmedBgColor = hasMouseDownFgColor = hasMouseDownBgColor = false; + + strncpy( pScheme->schemeName, paramValue, CScheme::SCHEME_NAME_LENGTH ); + pScheme->schemeName[CScheme::SCHEME_NAME_LENGTH-1] = '\0'; // ensure null termination of string + } + + if ( !pScheme ) + { + gEngfuncs.Con_Printf( "font scheme text file MUST start with a 'SchemeName'\n"); + break; + } + + // pull the data out into the scheme + if ( !stricmp(paramName, "FontName") ) + { + strncpy( pScheme->fontName, paramValue, CScheme::FONT_NAME_LENGTH ); + pScheme->fontName[CScheme::FONT_NAME_LENGTH-1] = 0; + } + else if ( !stricmp(paramName, "FontSize") ) + { + pScheme->fontSize = atoi( paramValue ); + } + else if ( !stricmp(paramName, "FontWeight") ) + { + pScheme->fontWeight = atoi( paramValue ); + } + else if ( !stricmp(paramName, "FgColor") ) + { + ParseRGBAFromString( pScheme->fgColor, paramValue ); + hasFgColor = true; + } + else if ( !stricmp(paramName, "BgColor") ) + { + ParseRGBAFromString( pScheme->bgColor, paramValue ); + hasBgColor = true; + } + else if ( !stricmp(paramName, "FgColorArmed") ) + { + ParseRGBAFromString( pScheme->armedFgColor, paramValue ); + hasArmedFgColor = true; + } + else if ( !stricmp(paramName, "BgColorArmed") ) + { + ParseRGBAFromString( pScheme->armedBgColor, paramValue ); + hasArmedBgColor = true; + } + else if ( !stricmp(paramName, "FgColorMousedown") ) + { + ParseRGBAFromString( pScheme->mousedownFgColor, paramValue ); + hasMouseDownFgColor = true; + } + else if ( !stricmp(paramName, "BgColorMousedown") ) + { + ParseRGBAFromString( pScheme->mousedownBgColor, paramValue ); + hasMouseDownBgColor = true; + } + else if ( !stricmp(paramName, "BorderColor") ) + { + ParseRGBAFromString( pScheme->borderColor, paramValue ); + hasMouseDownBgColor = true; + } + + // get the new token last, so we now if the loop needs to be continued or not + pFile = gEngfuncs.COM_ParseFile( pFile, token ); + } + + // free the file + gEngfuncs.COM_FreeFile( pFileStart ); + + +buildDefaultFont: + + // make sure we have at least 1 valid font + if ( currentScheme < 0 ) + { + currentScheme = 0; + strcpy( tmpSchemes[0].schemeName, "Default Scheme" ); + strcpy( tmpSchemes[0].fontName, "Arial" ); + tmpSchemes[0].fontSize = 0; + tmpSchemes[0].fgColor[0] = tmpSchemes[0].fgColor[1] = tmpSchemes[0].fgColor[2] = tmpSchemes[0].fgColor[3] = 255; + tmpSchemes[0].armedFgColor[0] = tmpSchemes[0].armedFgColor[1] = tmpSchemes[0].armedFgColor[2] = tmpSchemes[0].armedFgColor[3] = 255; + tmpSchemes[0].mousedownFgColor[0] = tmpSchemes[0].mousedownFgColor[1] = tmpSchemes[0].mousedownFgColor[2] = tmpSchemes[0].mousedownFgColor[3] = 255; + } + + // we have the full list of schemes in the tmpSchemes array + // now allocate the correct sized list + m_iNumSchemes = currentScheme + 1; // 0-based index + m_pSchemeList = new CScheme[ m_iNumSchemes ]; + + // copy in the data + memcpy( m_pSchemeList, tmpSchemes, sizeof(CScheme) * m_iNumSchemes ); + + // create the fonts + for ( int i = 0; i < m_iNumSchemes; i++ ) + { + m_pSchemeList[i].font = NULL; + + // see if the current font values exist in a previously loaded font + for ( int j = 0; j < i; j++ ) + { + // check if the font name, size, and weight are the same + if ( !stricmp(m_pSchemeList[i].fontName, m_pSchemeList[j].fontName) + && m_pSchemeList[i].fontSize == m_pSchemeList[j].fontSize + && m_pSchemeList[i].fontWeight == m_pSchemeList[j].fontWeight ) + { + // copy the pointer, but mark i as not owning it + m_pSchemeList[i].font = m_pSchemeList[j].font; + m_pSchemeList[i].ownFontPointer = false; + } + } + + // if we haven't found the font already, load it ourselves + if ( !m_pSchemeList[i].font ) + { + fontFileLength = -1; + pFontData = NULL; + + if(g_CV_BitmapFonts && g_CV_BitmapFonts->value) + { + sprintf(fontFilename, "gfx\\vgui\\fonts\\%d_%s.tga", m_xRes, m_pSchemeList[i].schemeName); + pFontData = gEngfuncs.COM_LoadFile( fontFilename, 5, &fontFileLength ); + if(!pFontData) + gEngfuncs.Con_Printf("Missing bitmap font: %s\n", fontFilename); + } + + m_pSchemeList[i].font = new vgui::Font( + m_pSchemeList[i].fontName, + pFontData, + fontFileLength, + m_pSchemeList[i].fontSize, + 0, + 0, + m_pSchemeList[i].fontWeight, + false, + false, + false, + false); + + m_pSchemeList[i].ownFontPointer = true; + } + + // fix up alpha values; VGUI uses 1-A (A=0 being solid, A=255 transparent) + m_pSchemeList[i].fgColor[3] = 255 - m_pSchemeList[i].fgColor[3]; + m_pSchemeList[i].bgColor[3] = 255 - m_pSchemeList[i].bgColor[3]; + m_pSchemeList[i].armedFgColor[3] = 255 - m_pSchemeList[i].armedFgColor[3]; + m_pSchemeList[i].armedBgColor[3] = 255 - m_pSchemeList[i].armedBgColor[3]; + m_pSchemeList[i].mousedownFgColor[3] = 255 - m_pSchemeList[i].mousedownFgColor[3]; + m_pSchemeList[i].mousedownBgColor[3] = 255 - m_pSchemeList[i].mousedownBgColor[3]; + } +} + +//----------------------------------------------------------------------------- +// Purpose: frees all the memory used by the scheme manager +//----------------------------------------------------------------------------- +CSchemeManager::~CSchemeManager() +{ + delete [] m_pSchemeList; + m_iNumSchemes = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Finds a scheme in the list, by name +// Input : char *schemeName - string name of the scheme +// Output : SchemeHandle_t handle to the scheme +//----------------------------------------------------------------------------- +SchemeHandle_t CSchemeManager::getSchemeHandle( const char *schemeName ) +{ + // iterate through the list + for ( int i = 0; i < m_iNumSchemes; i++ ) + { + if ( !stricmp(schemeName, m_pSchemeList[i].schemeName) ) + return i; + } + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: always returns a valid scheme handle +// Input : schemeHandle - +// Output : CScheme +//----------------------------------------------------------------------------- +CSchemeManager::CScheme *CSchemeManager::getSafeScheme( SchemeHandle_t schemeHandle ) +{ + if ( schemeHandle < m_iNumSchemes ) + return m_pSchemeList + schemeHandle; + + return m_pSchemeList; +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns the schemes pointer to a font +// Input : schemeHandle - +// Output : vgui::Font +//----------------------------------------------------------------------------- +vgui::Font *CSchemeManager::getFont( SchemeHandle_t schemeHandle ) +{ + return getSafeScheme( schemeHandle )->font; +} + +void CSchemeManager::getFgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->fgColor[0]; + g = pScheme->fgColor[1]; + b = pScheme->fgColor[2]; + a = pScheme->fgColor[3]; +} + +void CSchemeManager::getBgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->bgColor[0]; + g = pScheme->bgColor[1]; + b = pScheme->bgColor[2]; + a = pScheme->bgColor[3]; +} + +void CSchemeManager::getFgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->armedFgColor[0]; + g = pScheme->armedFgColor[1]; + b = pScheme->armedFgColor[2]; + a = pScheme->armedFgColor[3]; +} + +void CSchemeManager::getBgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->armedBgColor[0]; + g = pScheme->armedBgColor[1]; + b = pScheme->armedBgColor[2]; + a = pScheme->armedBgColor[3]; +} + +void CSchemeManager::getFgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->mousedownFgColor[0]; + g = pScheme->mousedownFgColor[1]; + b = pScheme->mousedownFgColor[2]; + a = pScheme->mousedownFgColor[3]; +} + +void CSchemeManager::getBgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->mousedownBgColor[0]; + g = pScheme->mousedownBgColor[1]; + b = pScheme->mousedownBgColor[2]; + a = pScheme->mousedownBgColor[3]; +} + +void CSchemeManager::getBorderColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->borderColor[0]; + g = pScheme->borderColor[1]; + b = pScheme->borderColor[2]; + a = pScheme->borderColor[3]; +} + + + diff --git a/hlsdk/cl_dll/vgui_SchemeManager.h b/hlsdk/cl_dll/vgui_SchemeManager.h new file mode 100644 index 00000000..45c2a06b --- /dev/null +++ b/hlsdk/cl_dll/vgui_SchemeManager.h @@ -0,0 +1,54 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include + + +// handle to an individual scheme +typedef int SchemeHandle_t; + + +// Register console variables, etc.. +void Scheme_Init(); + + +//----------------------------------------------------------------------------- +// Purpose: Handles the loading of text scheme description from disk +// supports different font/color/size schemes at different resolutions +//----------------------------------------------------------------------------- +class CSchemeManager +{ +public: + // initialization + CSchemeManager( int xRes, int yRes ); + virtual ~CSchemeManager(); + + // scheme handling + SchemeHandle_t getSchemeHandle( const char *schemeName ); + + // getting info from schemes + vgui::Font *getFont( SchemeHandle_t schemeHandle ); + void getFgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getFgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getFgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBorderColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + +private: + class CScheme; + CScheme *m_pSchemeList; + int m_iNumSchemes; + + // Resolution we were initted at. + int m_xRes; + + CScheme *getSafeScheme( SchemeHandle_t schemeHandle ); +}; + + diff --git a/hlsdk/cl_dll/vgui_ScorePanel.cpp b/hlsdk/cl_dll/vgui_ScorePanel.cpp new file mode 100644 index 00000000..da70beae --- /dev/null +++ b/hlsdk/cl_dll/vgui_ScorePanel.cpp @@ -0,0 +1,1097 @@ +//=========== (C) Copyright 1996-2002 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: VGUI scoreboard +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + + +#include + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ScorePanel.h" +#include "..\game_shared\vgui_helpers.h" +#include "..\game_shared\vgui_loadtga.h" +#include "vgui_SpectatorPanel.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 +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) + +#define X_BORDER XRES(4) + +// Column sizes +class SBColumnInfo +{ +public: + char *m_pTitle; // If null, ignore, if starts with #, it's localized, otherwise use the string directly. + int m_Width; // Based on 640 width. Scaled to fit other resolutions. + Label::Alignment m_Alignment; +}; + +// grid size is marked out for 640x480 screen + +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}, + {"#VOICE", 40, Label::a_east}, + {NULL, 2, Label::a_east}, // blank column to take up the slack +}; + + +#define TEAM_NO 0 +#define TEAM_YES 1 +#define TEAM_SPECTATORS 2 +#define TEAM_BLANK 3 + + +//----------------------------------------------------------------------------- +// ScorePanel::HitTestPanel. +//----------------------------------------------------------------------------- + +void ScorePanel::HitTestPanel::internalMousePressed(MouseCode code) +{ + for(int i=0;i<_inputSignalDar.getCount();i++) + { + _inputSignalDar[i]->mousePressed(code,this); + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: Create the ScoreBoard panel +//----------------------------------------------------------------------------- +ScorePanel::ScorePanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) +{ + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle("Scoreboard Title Text"); + SchemeHandle_t hSmallScheme = pSchemes->getSchemeHandle("Scoreboard Small Text"); + Font *tfont = pSchemes->getFont(hTitleScheme); + Font *smallfont = pSchemes->getFont(hSmallScheme); + + setBgColor(0, 0, 0, 96); + m_pCurrentHighlightLabel = NULL; + m_iHighlightRow = -1; + + // Initialize the top title. + m_TitleLabel.setFont(tfont); + m_TitleLabel.setText(""); + m_TitleLabel.setBgColor( 0, 0, 0, 255 ); + 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) + { + // only expand column size for res greater than 640 + xpos = XRES(xpos); + } + m_TitleLabel.setBounds(xpos, 4, wide, SBOARD_TITLE_SIZE_Y); + m_TitleLabel.setContentFitted(false); + m_TitleLabel.setParent(this); + + // Setup the header (labels like "name", "class", etc..). + m_HeaderGrid.SetDimensions(NUM_COLUMNS, 1); + m_HeaderGrid.SetSpacing(0, 0); + + for(int i=0; i < NUM_COLUMNS; i++) + { + if (g_ColumnInfo[i].m_pTitle && g_ColumnInfo[i].m_pTitle[0] == '#') + m_HeaderLabels[i].setText(CHudTextMessage::BufferedLocaliseTextString(g_ColumnInfo[i].m_pTitle)); + else if(g_ColumnInfo[i].m_pTitle) + m_HeaderLabels[i].setText(g_ColumnInfo[i].m_pTitle); + + int xwide = g_ColumnInfo[i].m_Width; + if (ScreenWidth >= 640) + { + xwide = XRES(xwide); + } + else if (ScreenWidth == 400) + { + // hack to make 400x300 resolution scoreboard fit + if (i == 1) + { + // reduces size of player name cell + xwide -= 28; + } + else if (i == 0) + { + xwide -= 8; + } + } + + m_HeaderGrid.SetColumnWidth(i, xwide); + m_HeaderGrid.SetEntry(i, 0, &m_HeaderLabels[i]); + + m_HeaderLabels[i].setBgColor(0,0,0,255); + m_HeaderLabels[i].setFgColor(Scheme::sc_primary1); + m_HeaderLabels[i].setFont(smallfont); + m_HeaderLabels[i].setContentAlignment(g_ColumnInfo[i].m_Alignment); + + int yres = 12; + if (ScreenHeight >= 480) + { + yres = YRES(yres); + } + m_HeaderLabels[i].setSize(50, yres); + } + + // Set the width of the last column to be the remaining space. + int ex, ey, ew, eh; + m_HeaderGrid.GetEntryBox(NUM_COLUMNS - 2, 0, ex, ey, ew, eh); + m_HeaderGrid.SetColumnWidth(NUM_COLUMNS - 1, (wide - X_BORDER) - (ex + ew)); + + m_HeaderGrid.AutoSetRowHeights(); + m_HeaderGrid.setBounds(X_BORDER, SBOARD_TITLE_SIZE_Y, wide - X_BORDER*2, m_HeaderGrid.GetRowHeight(0)); + m_HeaderGrid.setParent(this); + m_HeaderGrid.setBgColor(0,0,0,255); + + + // Now setup the listbox with the actual player data in it. + int headerX, headerY, headerWidth, headerHeight; + m_HeaderGrid.getBounds(headerX, headerY, headerWidth, headerHeight); + m_PlayerList.setBounds(headerX, headerY+headerHeight, headerWidth, tall - headerY - headerHeight - 6); + m_PlayerList.setBgColor(0,0,0,255); + m_PlayerList.setParent(this); + + for(int row=0; row < NUM_ROWS; row++) + { + CGrid *pGridRow = &m_PlayerGrids[row]; + + pGridRow->SetDimensions(NUM_COLUMNS, 1); + + for(int col=0; col < NUM_COLUMNS; col++) + { + m_PlayerEntries[col][row].setContentFitted(false); + m_PlayerEntries[col][row].setRow(row); + m_PlayerEntries[col][row].addInputSignal(this); + pGridRow->SetEntry(col, 0, &m_PlayerEntries[col][row]); + } + + pGridRow->setBgColor(0,0,0,255); +// pGridRow->SetSpacing(2, 0); + pGridRow->SetSpacing(0, 0); + pGridRow->CopyColumnWidths(&m_HeaderGrid); + pGridRow->AutoSetRowHeights(); + pGridRow->setSize(PanelWidth(pGridRow), pGridRow->CalcDrawHeight()); + pGridRow->RepositionContents(); + + m_PlayerList.AddItem(pGridRow); + } + + + // Add the hit test panel. It is invisible and traps mouse clicks so we can go into squelch mode. + m_HitTestPanel.setBgColor(0,0,0,255); + m_HitTestPanel.setParent(this); + m_HitTestPanel.setBounds(0, 0, wide, tall); + m_HitTestPanel.addInputSignal(this); + + m_pCloseButton = new CommandButton( "x", wide-XRES(12 + 4), YRES(2), XRES( 12 ) , YRES( 12 ) ); + m_pCloseButton->setParent( this ); + m_pCloseButton->addActionSignal( new CMenuHandler_StringCommandWatch( "-showscores", true ) ); + m_pCloseButton->setBgColor(0,0,0,255); + m_pCloseButton->setFgColor( 255, 255, 255, 0 ); + m_pCloseButton->setFont(tfont); + m_pCloseButton->setBoundKey( (char)255 ); + m_pCloseButton->setContentAlignment(Label::a_center); + + + Initialize(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Called each time a new level is started. +//----------------------------------------------------------------------------- +void ScorePanel::Initialize( void ) +{ + // Clear out scoreboard data + m_iLastKilledBy = 0; + m_fLastKillTime = 0; + m_iPlayerNum = 0; + m_iNumTeams = 0; + memset( g_PlayerExtraInfo, 0, sizeof g_PlayerExtraInfo ); + memset( g_TeamInfo, 0, sizeof g_TeamInfo ); +} + +bool HACK_GetPlayerUniqueID( int iPlayer, char playerID[16] ) +{ + return !!gEngfuncs.GetPlayerUniqueID( iPlayer, playerID ); +} + +//----------------------------------------------------------------------------- +// Purpose: Recalculate the internal scoreboard data +//----------------------------------------------------------------------------- +void ScorePanel::Update() +{ + // Set the title + if (gViewPort->m_szServerName) + { + char sz[MAX_SERVERNAME_LENGTH + 16]; + sprintf(sz, "%s", gViewPort->m_szServerName ); + m_TitleLabel.setText(sz); + } + + m_iRows = 0; + gViewPort->GetAllPlayersInfo(); + + // Clear out sorts + for (int i = 0; i < NUM_ROWS; i++) + { + m_iSortedRows[i] = 0; + m_iIsATeam[i] = TEAM_NO; + m_bHasBeenSorted[i] = false; + } + + // If it's not teamplay, sort all the players. Otherwise, sort the teams. + if ( !gHUD.m_Teamplay ) + SortPlayers( 0, NULL ); + else + SortTeams(); + + // set scrollbar range + m_PlayerList.SetScrollRange(m_iRows); + + FillGrid(); + + if ( gViewPort->m_pSpectatorPanel->m_menuVisible ) + { + m_pCloseButton->setVisible ( true ); + } + else + { + m_pCloseButton->setVisible ( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Sort all the teams +//----------------------------------------------------------------------------- +void ScorePanel::SortTeams() +{ + // clear out team scores + for ( int i = 1; i <= m_iNumTeams; i++ ) + { + if ( !g_TeamInfo[i].scores_overriden ) + g_TeamInfo[i].frags = g_TeamInfo[i].deaths = 0; + g_TeamInfo[i].ping = g_TeamInfo[i].packetloss = 0; + } + + // recalc the team scores, then draw them + for ( int i = 1; i < MAX_PLAYERS; i++ ) + { + if ( g_PlayerInfoList[i].name == NULL ) + continue; // empty player slot, skip + + if ( g_PlayerExtraInfo[i].teamname[0] == 0 ) + continue; // skip over players who are not in a team + + // find what team this player is in + int j; + for ( j = 1; j <= m_iNumTeams; j++ ) + { + if ( !stricmp( g_PlayerExtraInfo[i].teamname, g_TeamInfo[j].name ) ) + break; + } + if ( j > m_iNumTeams ) // player is not in a team, skip to the next guy + continue; + + if ( !g_TeamInfo[j].scores_overriden ) + { + g_TeamInfo[j].frags += g_PlayerExtraInfo[i].frags; + g_TeamInfo[j].deaths += g_PlayerExtraInfo[i].deaths; + } + + g_TeamInfo[j].ping += g_PlayerInfoList[i].ping; + g_TeamInfo[j].packetloss += g_PlayerInfoList[i].packetloss; + + if ( g_PlayerInfoList[i].thisplayer ) + g_TeamInfo[j].ownteam = TRUE; + else + g_TeamInfo[j].ownteam = FALSE; + + // Set the team's number (used for team colors) + g_TeamInfo[j].teamnumber = g_PlayerExtraInfo[i].teamnumber; + } + + // find team ping/packetloss averages + for ( int i = 1; i <= m_iNumTeams; i++ ) + { + g_TeamInfo[i].already_drawn = FALSE; + + if ( g_TeamInfo[i].players > 0 ) + { + g_TeamInfo[i].ping /= g_TeamInfo[i].players; // use the average ping of all the players in the team as the teams ping + g_TeamInfo[i].packetloss /= g_TeamInfo[i].players; // use the average ping of all the players in the team as the teams ping + } + } + + // Draw the teams + while ( 1 ) + { + int highest_frags = -99999; int lowest_deaths = 99999; + int best_team = 0; + + for ( int i = 1; i <= m_iNumTeams; i++ ) + { + if ( g_TeamInfo[i].players < 1 ) + continue; + + if ( !g_TeamInfo[i].already_drawn && g_TeamInfo[i].frags >= highest_frags ) + { + if ( g_TeamInfo[i].frags > highest_frags || g_TeamInfo[i].deaths < lowest_deaths ) + { + best_team = i; + lowest_deaths = g_TeamInfo[i].deaths; + highest_frags = g_TeamInfo[i].frags; + } + } + } + + // draw the best team on the scoreboard + if ( !best_team ) + break; + + // Put this team in the sorted list + m_iSortedRows[ m_iRows ] = best_team; + m_iIsATeam[ m_iRows ] = TEAM_YES; + g_TeamInfo[best_team].already_drawn = TRUE; // set the already_drawn to be TRUE, so this team won't get sorted again + m_iRows++; + + // Now sort all the players on this team + SortPlayers( 0, g_TeamInfo[best_team].name ); + } + + // Add all the players who aren't in a team yet into spectators + SortPlayers( TEAM_SPECTATORS, NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sort a list of players +//----------------------------------------------------------------------------- +void ScorePanel::SortPlayers( int iTeam, char *team ) +{ + bool bCreatedTeam = false; + + // draw the players, in order, and restricted to team if set + while ( 1 ) + { + // Find the top ranking player + int highest_frags = -99999; int lowest_deaths = 99999; + int best_player; + best_player = 0; + + for ( int i = 1; i < MAX_PLAYERS; i++ ) + { + if ( m_bHasBeenSorted[i] == false && g_PlayerInfoList[i].name && g_PlayerExtraInfo[i].frags >= highest_frags ) + { + cl_entity_t *ent = gEngfuncs.GetEntityByIndex( i ); + + if ( ent && !(team && stricmp(g_PlayerExtraInfo[i].teamname, team)) ) + { + extra_player_info_t *pl_info = &g_PlayerExtraInfo[i]; + if ( pl_info->frags > highest_frags || pl_info->deaths < lowest_deaths ) + { + best_player = i; + lowest_deaths = pl_info->deaths; + highest_frags = pl_info->frags; + } + } + } + } + + if ( !best_player ) + break; + + // If we haven't created the Team yet, do it first + if (!bCreatedTeam && iTeam) + { + m_iIsATeam[ m_iRows ] = iTeam; + m_iRows++; + + bCreatedTeam = true; + } + + // Put this player in the sorted list + m_iSortedRows[ m_iRows ] = best_player; + m_bHasBeenSorted[ best_player ] = true; + m_iRows++; + } + + if (team) + { + m_iIsATeam[m_iRows++] = TEAM_BLANK; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Recalculate the existing teams in the match +//----------------------------------------------------------------------------- +void ScorePanel::RebuildTeams() +{ + // clear out player counts from teams + for ( int i = 1; i <= m_iNumTeams; i++ ) + { + g_TeamInfo[i].players = 0; + } + + // rebuild the team list + gViewPort->GetAllPlayersInfo(); + m_iNumTeams = 0; + for ( int i = 1; i < MAX_PLAYERS; i++ ) + { + if ( g_PlayerInfoList[i].name == NULL ) + continue; + + if ( g_PlayerExtraInfo[i].teamname[0] == 0 ) + continue; // skip over players who are not in a team + + // is this player in an existing team? + int j; + for ( j = 1; j <= m_iNumTeams; j++ ) + { + if ( g_TeamInfo[j].name[0] == '\0' ) + break; + + if ( !stricmp( g_PlayerExtraInfo[i].teamname, g_TeamInfo[j].name ) ) + break; + } + + if ( j > m_iNumTeams ) + { // they aren't in a listed team, so make a new one + // search through for an empty team slot + for ( j = 1; j <= m_iNumTeams; j++ ) + { + if ( g_TeamInfo[j].name[0] == '\0' ) + break; + } + m_iNumTeams = max( j, m_iNumTeams ); + + strncpy( g_TeamInfo[j].name, g_PlayerExtraInfo[i].teamname, MAX_TEAM_NAME ); + g_TeamInfo[j].players = 0; + } + + g_TeamInfo[j].players++; + } + + // clear out any empty teams + for ( int i = 1; i <= m_iNumTeams; i++ ) + { + if ( g_TeamInfo[i].players < 1 ) + memset( &g_TeamInfo[i], 0, sizeof(team_info_t) ); + } + + // Update the scoreboard + Update(); +} + + +void ScorePanel::FillGrid() +{ + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + SchemeHandle_t hScheme = pSchemes->getSchemeHandle("Scoreboard Text"); + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle("Scoreboard Title Text"); + SchemeHandle_t hSmallScheme = pSchemes->getSchemeHandle("Scoreboard Small Text"); + + Font *sfont = pSchemes->getFont(hScheme); + Font *tfont = pSchemes->getFont(hTitleScheme); + Font *smallfont = pSchemes->getFont(hSmallScheme); + + // update highlight position + int x, y; + getApp()->getCursorPos(x, y); + cursorMoved(x, y, this); + + // remove highlight row if we're not in squelch mode + if (!GetClientVoiceMgr()->IsInSquelchMode()) + { + m_iHighlightRow = -1; + } + + bool bNextRowIsGap = false; + + for(int row=0; row < NUM_ROWS; row++) + { + CGrid *pGridRow = &m_PlayerGrids[row]; + pGridRow->SetRowUnderline(0, false, 0, 0, 0, 0, 0); + + if(row >= m_iRows) + { + for(int col=0; col < NUM_COLUMNS; col++) + m_PlayerEntries[col][row].setVisible(false); + + continue; + } + + bool bRowIsGap = false; + if (bNextRowIsGap) + { + bNextRowIsGap = false; + bRowIsGap = true; + } + + for(int col=0; col < NUM_COLUMNS; col++) + { + CLabelHeader *pLabel = &m_PlayerEntries[col][row]; + + pLabel->setVisible(true); + pLabel->setText2(""); + pLabel->setImage(NULL); + pLabel->setFont(sfont); + pLabel->setTextOffset(0, 0); + + int rowheight = 13; + if (ScreenHeight > 480) + { + rowheight = YRES(rowheight); + } + else + { + // more tweaking, make sure icons fit at low res + rowheight = 15; + } + pLabel->setSize(pLabel->getWide(), rowheight); + pLabel->setBgColor(0, 0, 0, 255); + + char sz[128]; + hud_player_info_t *pl_info = NULL; + team_info_t *team_info = NULL; + + if (m_iIsATeam[row] == TEAM_BLANK) + { + pLabel->setText(" "); + continue; + } + else if ( m_iIsATeam[row] == TEAM_YES ) + { + // Get the team's data + team_info = &g_TeamInfo[ m_iSortedRows[row] ]; + + // team color text for team names + pLabel->setFgColor( iTeamColors[team_info->teamnumber % iNumberOfTeamColors][0], + iTeamColors[team_info->teamnumber % iNumberOfTeamColors][1], + iTeamColors[team_info->teamnumber % iNumberOfTeamColors][2], + 0 ); + + // different height for team header rows + rowheight = 20; + if (ScreenHeight >= 480) + { + rowheight = YRES(rowheight); + } + pLabel->setSize(pLabel->getWide(), rowheight); + pLabel->setFont(tfont); + + pGridRow->SetRowUnderline( 0, + true, + YRES(3), + iTeamColors[team_info->teamnumber % iNumberOfTeamColors][0], + iTeamColors[team_info->teamnumber % iNumberOfTeamColors][1], + iTeamColors[team_info->teamnumber % iNumberOfTeamColors][2], + 0 ); + } + else if ( m_iIsATeam[row] == TEAM_SPECTATORS ) + { + // grey text for spectators + pLabel->setFgColor(100, 100, 100, 0); + + // different height for team header rows + rowheight = 20; + if (ScreenHeight >= 480) + { + rowheight = YRES(rowheight); + } + pLabel->setSize(pLabel->getWide(), rowheight); + pLabel->setFont(tfont); + + pGridRow->SetRowUnderline(0, true, YRES(3), 100, 100, 100, 0); + } + else + { + // team color text for player names + pLabel->setFgColor( 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], + 0 ); + + // Get the player's data + pl_info = &g_PlayerInfoList[ m_iSortedRows[row] ]; + + // Set background color + if ( pl_info->thisplayer ) // if it is their name, draw it a different color + { + // Highlight this player + pLabel->setFgColor(Scheme::sc_white); + 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 ); + } + else if ( m_iSortedRows[row] == m_iLastKilledBy && m_fLastKillTime && m_fLastKillTime > gHUD.m_flTime ) + { + // Killer's name + pLabel->setBgColor( 255,0,0, 255 - ((float)15 * (float)(m_fLastKillTime - gHUD.m_flTime)) ); + } + } + + // Align + if (col == COLUMN_NAME || col == COLUMN_CLASS) + { + pLabel->setContentAlignment( vgui::Label::a_west ); + } + else if (col == COLUMN_TRACKER) + { + pLabel->setContentAlignment( vgui::Label::a_center ); + } + else + { + pLabel->setContentAlignment( vgui::Label::a_east ); + } + + // Fill out with the correct data + strcpy(sz, ""); + if ( m_iIsATeam[row] ) + { + char sz2[128]; + + switch (col) + { + case COLUMN_NAME: + if ( m_iIsATeam[row] == TEAM_SPECTATORS ) + { + sprintf( sz2, CHudTextMessage::BufferedLocaliseTextString( "#Spectators" ) ); + } + else + { + sprintf( sz2, gViewPort->GetTeamName(team_info->teamnumber) ); + } + + strcpy(sz, sz2); + + // Append the number of players + if ( m_iIsATeam[row] == TEAM_YES ) + { + if (team_info->players == 1) + { + sprintf(sz2, "(%d %s)", team_info->players, CHudTextMessage::BufferedLocaliseTextString( "#Player" ) ); + } + else + { + sprintf(sz2, "(%d %s)", team_info->players, CHudTextMessage::BufferedLocaliseTextString( "#Player_plural" ) ); + } + + pLabel->setText2(sz2); + pLabel->setFont2(smallfont); + } + break; + case COLUMN_VOICE: + break; + case COLUMN_CLASS: + break; + case COLUMN_KILLS: + if ( m_iIsATeam[row] == TEAM_YES ) + sprintf(sz, "%d", team_info->frags ); + break; + case COLUMN_DEATHS: + if ( m_iIsATeam[row] == TEAM_YES ) + sprintf(sz, "%d", team_info->deaths ); + break; + case COLUMN_LATENCY: + if ( m_iIsATeam[row] == TEAM_YES ) + sprintf(sz, "%d", team_info->ping ); + break; + default: + break; + } + } + else + { + bool bShowClass = false; + + switch (col) + { + case COLUMN_NAME: + sprintf(sz, "%s ", pl_info->name); + break; + case COLUMN_VOICE: + sz[0] = 0; + // in HLTV mode allow spectator to turn on/off commentator voice + if (!pl_info->thisplayer || gEngfuncs.IsSpectateOnly() ) + { + 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: + sprintf(sz, "%d", g_PlayerExtraInfo[ m_iSortedRows[row] ].frags ); + break; + case COLUMN_DEATHS: + sprintf(sz, "%d", g_PlayerExtraInfo[ m_iSortedRows[row] ].deaths ); + break; + case COLUMN_LATENCY: + sprintf(sz, "%d", g_PlayerInfoList[ m_iSortedRows[row] ].ping ); + break; + default: + break; + } + } + + pLabel->setText(sz); + } + } + + for(int row = 0; row < NUM_ROWS; row++) + { + CGrid *pGridRow = &m_PlayerGrids[row]; + + pGridRow->AutoSetRowHeights(); + pGridRow->setSize(PanelWidth(pGridRow), pGridRow->CalcDrawHeight()); + pGridRow->RepositionContents(); + } + + // hack, for the thing to resize + m_PlayerList.getSize(x, y); + m_PlayerList.setSize(x, y); +} + + +//----------------------------------------------------------------------------- +// Purpose: Setup highlights for player names in scoreboard +//----------------------------------------------------------------------------- +void ScorePanel::DeathMsg( int killer, int victim ) +{ + // if we were the one killed, or the world killed us, set the scoreboard to indicate suicide + if ( victim == m_iPlayerNum || killer == 0 ) + { + m_iLastKilledBy = killer ? killer : m_iPlayerNum; + m_fLastKillTime = gHUD.m_flTime + 10; // display who we were killed by for 10 seconds + + if ( killer == m_iPlayerNum ) + m_iLastKilledBy = m_iPlayerNum; + } +} + + +void ScorePanel::Open( void ) +{ + RebuildTeams(); + setVisible(true); + m_HitTestPanel.setVisible(true); +} + + +void ScorePanel::mousePressed(MouseCode code, Panel* panel) +{ + if(gHUD.m_iIntermission) + return; + + if (!GetClientVoiceMgr()->IsInSquelchMode()) + { + GetClientVoiceMgr()->StartSquelchMode(); + m_HitTestPanel.setVisible(false); + } + else if (m_iHighlightRow >= 0) + { + // mouse has been pressed, toggle mute state + int iPlayer = m_iSortedRows[m_iHighlightRow]; + if (iPlayer > 0) + { + // print text message + hud_player_info_t *pl_info = &g_PlayerInfoList[iPlayer]; + + if (pl_info && pl_info->name && pl_info->name[0]) + { + char string[256]; + if (GetClientVoiceMgr()->IsPlayerBlocked(iPlayer)) + { + char string1[1024]; + + // remove mute + GetClientVoiceMgr()->SetPlayerBlockedState(iPlayer, false); + + sprintf( string1, CHudTextMessage::BufferedLocaliseTextString( "#Unmuted" ), pl_info->name ); + sprintf( string, "%c** %s\n", HUD_PRINTTALK, string1 ); + + gHUD.m_TextMessage.MsgFunc_TextMsg(NULL, strlen(string)+1, string ); + } + else + { + char string1[1024]; + char string2[1024]; + + // mute the player + GetClientVoiceMgr()->SetPlayerBlockedState(iPlayer, true); + + sprintf( string1, CHudTextMessage::BufferedLocaliseTextString( "#Muted" ), pl_info->name ); + sprintf( string2, CHudTextMessage::BufferedLocaliseTextString( "#No_longer_hear_that_player" ) ); + sprintf( string, "%c** %s %s\n", HUD_PRINTTALK, string1, string2 ); + + gHUD.m_TextMessage.MsgFunc_TextMsg(NULL, strlen(string)+1, string ); + } + } + } + } +} + +void ScorePanel::cursorMoved(int x, int y, Panel *panel) +{ + if (GetClientVoiceMgr()->IsInSquelchMode()) + { + // look for which cell the mouse is currently over + for (int i = 0; i < NUM_ROWS; i++) + { + int row, col; + if (m_PlayerGrids[i].getCellAtPoint(x, y, row, col)) + { + MouseOverCell(i, col); + return; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handles mouse movement over a cell +// Input : row - +// col - +//----------------------------------------------------------------------------- +void ScorePanel::MouseOverCell(int row, int col) +{ + CLabelHeader *label = &m_PlayerEntries[col][row]; + + // clear the previously highlighted label + if (m_pCurrentHighlightLabel != label) + { + m_pCurrentHighlightLabel = NULL; + m_iHighlightRow = -1; + } + if (!label) + return; + + // don't act on teams + if (m_iIsATeam[row] != TEAM_NO) + return; + + // don't act on disconnected players or ourselves + hud_player_info_t *pl_info = &g_PlayerInfoList[ m_iSortedRows[row] ]; + if (!pl_info->name || !pl_info->name[0]) + return; + + if (pl_info->thisplayer && !gEngfuncs.IsSpectateOnly() ) + return; + + // setup the new highlight + m_pCurrentHighlightLabel = label; + m_iHighlightRow = row; +} + +//----------------------------------------------------------------------------- +// Purpose: Label paint functions - take into account current highligh status +//----------------------------------------------------------------------------- +void CLabelHeader::paintBackground() +{ + Color oldBg; + getBgColor(oldBg); + + if (gViewPort->GetScoreBoard()->m_iHighlightRow == _row) + { + setBgColor(134, 91, 19, 0); + } + + Panel::paintBackground(); + + setBgColor(oldBg); +} + + +//----------------------------------------------------------------------------- +// Purpose: Label paint functions - take into account current highligh status +//----------------------------------------------------------------------------- +void CLabelHeader::paint() +{ + Color oldFg; + getFgColor(oldFg); + + if (gViewPort->GetScoreBoard()->m_iHighlightRow == _row) + { + setFgColor(255, 255, 255, 0); + } + + // draw text + int x, y, iwide, itall; + getTextSize(iwide, itall); + calcAlignment(iwide, itall, x, y); + _dualImage->setPos(x, y); + + int x1, y1; + _dualImage->GetImage(1)->getPos(x1, y1); + _dualImage->GetImage(1)->setPos(_gap, y1); + + _dualImage->doPaint(this); + + // get size of the panel and the image + if (_image) + { + Color imgColor; + getFgColor( imgColor ); + if( _useFgColorAsImageColor ) + { + _image->setColor( imgColor ); + } + + _image->getSize(iwide, itall); + calcAlignment(iwide, itall, x, y); + _image->setPos(x, y); + _image->doPaint(this); + } + + setFgColor(oldFg[0], oldFg[1], oldFg[2], oldFg[3]); +} + + +void CLabelHeader::calcAlignment(int iwide, int itall, int &x, int &y) +{ + // calculate alignment ourselves, since vgui is so broken + int wide, tall; + getSize(wide, tall); + + x = 0, y = 0; + + // align left/right + switch (_contentAlignment) + { + // left + case Label::a_northwest: + case Label::a_west: + case Label::a_southwest: + { + x = 0; + break; + } + + // center + case Label::a_north: + case Label::a_center: + case Label::a_south: + { + x = (wide - iwide) / 2; + break; + } + + // right + case Label::a_northeast: + case Label::a_east: + case Label::a_southeast: + { + x = wide - iwide; + break; + } + } + + // top/down + switch (_contentAlignment) + { + // top + case Label::a_northwest: + case Label::a_north: + case Label::a_northeast: + { + y = 0; + break; + } + + // center + case Label::a_west: + case Label::a_center: + case Label::a_east: + { + y = (tall - itall) / 2; + break; + } + + // south + case Label::a_southwest: + case Label::a_south: + case Label::a_southeast: + { + y = tall - itall; + break; + } + } + +// don't clip to Y +// if (y < 0) +// { +// y = 0; +// } + if (x < 0) + { + x = 0; + } + + x += _offset[0]; + y += _offset[1]; +} diff --git a/hlsdk/cl_dll/vgui_ScorePanel.h b/hlsdk/cl_dll/vgui_ScorePanel.h new file mode 100644 index 00000000..f7a8543a --- /dev/null +++ b/hlsdk/cl_dll/vgui_ScorePanel.h @@ -0,0 +1,310 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef SCOREPANEL_H +#define SCOREPANEL_H + +#include +#include +#include +#include +#include +#include +#include "..\game_shared\vgui_listbox.h" + +#include + +#define MAX_SCORES 10 +#define MAX_SCOREBOARD_TEAMS 5 + +// 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 NUM_ROWS (MAX_PLAYERS + (MAX_SCOREBOARD_TEAMS * 2)) + +using namespace vgui; + +class CTextImage2 : public Image +{ +public: + CTextImage2() + { + _image[0] = new TextImage(""); + _image[1] = new TextImage(""); + } + + ~CTextImage2() + { + delete _image[0]; + delete _image[1]; + } + + TextImage *GetImage(int image) + { + return _image[image]; + } + + void getSize(int &wide, int &tall) + { + int w1, w2, t1, t2; + _image[0]->getTextSize(w1, t1); + _image[1]->getTextSize(w2, t2); + + wide = w1 + w2; + tall = max(t1, t2); + setSize(wide, tall); + } + + void doPaint(Panel *panel) + { + _image[0]->doPaint(panel); + _image[1]->doPaint(panel); + } + + void setPos(int x, int y) + { + _image[0]->setPos(x, y); + + int swide, stall; + _image[0]->getSize(swide, stall); + + int wide, tall; + _image[1]->getSize(wide, tall); + _image[1]->setPos(x + wide, y + (stall * 0.9) - tall); + } + + void setColor(Color color) + { + _image[0]->setColor(color); + } + + void setColor2(Color color) + { + _image[1]->setColor(color); + } + +private: + TextImage *_image[2]; + +}; + +//----------------------------------------------------------------------------- +// Purpose: Custom label for cells in the Scoreboard's Table Header +//----------------------------------------------------------------------------- +class CLabelHeader : public Label +{ +public: + CLabelHeader() : Label("") + { + _dualImage = new CTextImage2(); + _dualImage->setColor2(Color(255, 170, 0, 0)); + _row = -2; + _useFgColorAsImageColor = true; + _offset[0] = 0; + _offset[1] = 0; + } + + ~CLabelHeader() + { + delete _dualImage; + } + + void setRow(int row) + { + _row = row; + } + + void setFgColorAsImageColor(bool state) + { + _useFgColorAsImageColor = state; + } + + virtual void setText(int textBufferLen, const char* text) + { + _dualImage->GetImage(0)->setText(text); + + // calculate the text size + Font *font = _dualImage->GetImage(0)->getFont(); + _gap = 0; + for (const char *ch = text; *ch != 0; ch++) + { + int a, b, c; + font->getCharABCwide(*ch, a, b, c); + _gap += (a + b + c); + } + + _gap += XRES(5); + } + + virtual void setText(const char* text) + { + // strip any non-alnum characters from the end + char buf[512]; + strcpy(buf, text); + + int len = strlen(buf); + while (len && isspace(buf[--len])) + { + buf[len] = 0; + } + + CLabelHeader::setText(0, buf); + } + + void setText2(const char *text) + { + _dualImage->GetImage(1)->setText(text); + } + + void getTextSize(int &wide, int &tall) + { + _dualImage->getSize(wide, tall); + } + + void setFgColor(int r,int g,int b,int a) + { + Label::setFgColor(r,g,b,a); + Color color(r,g,b,a); + _dualImage->setColor(color); + _dualImage->setColor2(color); + repaint(); + } + + void setFgColor(Scheme::SchemeColor sc) + { + int r, g, b, a; + Label::setFgColor(sc); + Label::getFgColor( r, g, b, a ); + + // Call the r,g,b,a version so it sets the color in the dualImage.. + setFgColor( r, g, b, a ); + } + + void setFont(Font *font) + { + _dualImage->GetImage(0)->setFont(font); + } + + void setFont2(Font *font) + { + _dualImage->GetImage(1)->setFont(font); + } + + // this adjust the absolute position of the text after alignment is calculated + void setTextOffset(int x, int y) + { + _offset[0] = x; + _offset[1] = y; + } + + void paint(); + void paintBackground(); + void calcAlignment(int iwide, int itall, int &x, int &y); + +private: + CTextImage2 *_dualImage; + int _row; + int _gap; + int _offset[2]; + bool _useFgColorAsImageColor; +}; + +class ScoreTablePanel; + +#include "..\game_shared\vgui_grid.h" +#include "..\game_shared\vgui_defaultinputsignal.h" + +//----------------------------------------------------------------------------- +// Purpose: Scoreboard back panel +//----------------------------------------------------------------------------- +class ScorePanel : public Panel, public vgui::CDefaultInputSignal +{ +private: + // Default panel implementation doesn't forward mouse messages when there is no cursor and we need them. + class HitTestPanel : public Panel + { + public: + virtual void internalMousePressed(MouseCode code); + }; + + +private: + + Label m_TitleLabel; + + // Here is how these controls are arranged hierarchically. + // m_HeaderGrid + // m_HeaderLabels + + // m_PlayerGridScroll + // m_PlayerGrid + // m_PlayerEntries + + CGrid m_HeaderGrid; + CLabelHeader m_HeaderLabels[NUM_COLUMNS]; // Labels above the + CLabelHeader *m_pCurrentHighlightLabel; + int m_iHighlightRow; + + 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. + + ScorePanel::HitTestPanel m_HitTestPanel; + CommandButton *m_pCloseButton; + CLabelHeader* GetPlayerEntry(int x, int y) {return &m_PlayerEntries[x][y];} + +public: + + int m_iNumTeams; + int m_iPlayerNum; + int m_iShowscoresHeld; + + int m_iRows; + int m_iSortedRows[NUM_ROWS]; + int m_iIsATeam[NUM_ROWS]; + bool m_bHasBeenSorted[MAX_PLAYERS]; + int m_iLastKilledBy; + int m_fLastKillTime; + + +public: + + ScorePanel(int x,int y,int wide,int tall); + + void Update( void ); + + void SortTeams( void ); + void SortPlayers( int iTeam, char *team ); + void RebuildTeams( void ); + + void FillGrid(); + + void DeathMsg( int killer, int victim ); + + void Initialize( void ); + + void Open( void ); + + void MouseOverCell(int row, int col); + +// InputSignal overrides. +public: + + virtual void mousePressed(MouseCode code, Panel* panel); + virtual void cursorMoved(int x, int y, Panel *panel); + + friend CLabelHeader; +}; + +#endif diff --git a/hlsdk/cl_dll/vgui_ServerBrowser.cpp b/hlsdk/cl_dll/vgui_ServerBrowser.cpp new file mode 100644 index 00000000..6e902594 --- /dev/null +++ b/hlsdk/cl_dll/vgui_ServerBrowser.cpp @@ -0,0 +1,623 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include +#include +#include +#include +#include + +#include "hud.h" +#include "cl_util.h" +#include "hud_servers.h" +#include "net_api.h" + +#include "vgui_TeamFortressViewport.h" +#include "vgui_ServerBrowser.h" + +using namespace vgui; + +namespace +{ + +#define MAX_SB_ROWS 24 + +#define NUM_COLUMNS 5 + +#define HEADER_SIZE_Y YRES(18) + +// Column sizes +#define CSIZE_ADDRESS XRES(200) +#define CSIZE_SERVER XRES(400) +#define CSIZE_MAP XRES(500) +#define CSIZE_CURRENT XRES(570) +#define CSIZE_PING XRES(640) + +#define CELL_HEIGHT YRES(15) + +class ServerBrowserTablePanel; + +class CBrowser_InputSignal : public InputSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; +public: + CBrowser_InputSignal( ServerBrowserTablePanel *pBrowser ) + { + m_pBrowser = pBrowser; + } + + 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); + + 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 ServerBrowserTablePanel : public TablePanel +{ +private: + Label *m_pLabel; + int m_nMouseOverRow; + +public: + + ServerBrowserTablePanel( int x,int y,int wide,int tall,int columnCount) : TablePanel( x,y,wide,tall,columnCount) + { + m_pLabel = new Label( "", 0, 0 /*,wide, tall*/ ); + + m_nMouseOverRow = 0; + } + +public: + void setMouseOverRow( int row ) + { + m_nMouseOverRow = row; + } + + void DoSort( char *sortkey ) + { + // Request server list and refresh servers... + SortServers( sortkey ); + } + + void DoRefresh( void ) + { + // Request server list and refresh servers... + ServersList(); + BroadcastServersList( 0 ); + } + + void DoBroadcastRefresh( void ) + { + // Request server list and refresh servers... + BroadcastServersList( 1 ); + } + + void DoStop( void ) + { + // Stop requesting + ServersCancel(); + } + + void DoCancel( void ) + { + ClientCmd( "togglebrowser\n" ); + } + + void DoConnect( void ) + { + const char *info; + const char *address; + char sz[ 256 ]; + + info = ServersGetInfo( m_nMouseOverRow ); + if ( !info ) + return; + + address = gEngfuncs.pNetAPI->ValueForKey( info, "address" ); + //gEngfuncs.Con_Printf( "Connecting to %s\n", address ); + + sprintf( sz, "connect %s\n", address ); + + ClientCmd( sz ); + + DoCancel(); + } + + void DoPing( void ) + { + ServerPing( 0 ); + ServerRules( 0 ); + ServerPlayers( 0 ); + } + + virtual int getRowCount() + { + int rowcount; + int height, width; + + getSize( width, height ); + + // Space for buttons + height -= YRES(20); + height = max( 0, height ); + + rowcount = height / CELL_HEIGHT; + + return rowcount; + } + + virtual int getCellTall(int row) + { + return CELL_HEIGHT - 2; + } + + virtual Panel* getCellRenderer(int column,int row,bool columnSelected,bool rowSelected,bool cellSelected) + { + const char *info; + const char *val, *val2; + char sz[ 32 ]; + + info = ServersGetInfo( row ); + + if ( row == m_nMouseOverRow ) + { + m_pLabel->setFgColor( 200, 240, 63, 100 ); + } + else + { + m_pLabel->setFgColor( 255, 255, 255, 0 ); + } + m_pLabel->setBgColor( 0, 0, 0, 200 ); + m_pLabel->setContentAlignment( vgui::Label::a_west ); + m_pLabel->setFont( Scheme::sf_primary2 ); + + if ( info ) + { + // Fill out with the correct data + switch ( column ) + { + case 0: + val = gEngfuncs.pNetAPI->ValueForKey( info, "address" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Name; + m_pLabel->setText( sz ); + } + break; + case 1: + val = gEngfuncs.pNetAPI->ValueForKey( info, "hostname" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Map; + m_pLabel->setText( sz ); + } + break; + case 2: + val = gEngfuncs.pNetAPI->ValueForKey( info, "map" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Name; + m_pLabel->setText( sz ); + } + break; + case 3: + val = gEngfuncs.pNetAPI->ValueForKey( info, "current" ); + val2 = gEngfuncs.pNetAPI->ValueForKey( info, "max" ); + if ( val && val2 ) + { + sprintf( sz, "%s/%s", val, val2 ); + sz[ 31 ] = '\0'; + // Server Map; + m_pLabel->setText( sz ); + } + break; + case 4: + val = gEngfuncs.pNetAPI->ValueForKey( info, "ping" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Name; + m_pLabel->setText( sz ); + } + break; + default: + break; + } + } + else + { + if ( !row && !column ) + { + if ( ServersIsQuerying() ) + { + m_pLabel->setText( "Waiting for servers to respond..." ); + } + else + { + m_pLabel->setText( "Press 'Refresh' to search for servers..." ); + } + } + else + { + m_pLabel->setText( "" ); + } + } + + return m_pLabel; + } + + virtual Panel* startCellEditing(int column,int row) + { + return null; + } + +}; + +class ConnectHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + ConnectHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoConnect(); + } +}; + +class RefreshHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + RefreshHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoRefresh(); + } +}; + +class BroadcastRefreshHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + BroadcastRefreshHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoBroadcastRefresh(); + } +}; + +class StopHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + StopHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoStop(); + } +}; + +class CancelHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + CancelHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoCancel(); + } +}; + +class PingHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + PingHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoPing(); + } +}; + +class SortHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + SortHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoSort( "map" ); + } +}; + +} + +class LabelSortInputHandler : public InputSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + char m_szSortKey[ 64 ]; + +public: + LabelSortInputHandler( ServerBrowserTablePanel *pBrowser, char *name ) + { + m_pBrowser = pBrowser; + strcpy( m_szSortKey, name ); + } + + 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) + { + m_pBrowser->DoSort( m_szSortKey ); + } + + virtual void mouseDoublePressed(MouseCode code,Panel* panel) + { + m_pBrowser->DoSort( m_szSortKey ); + } + + 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 CSBLabel : public Label +{ + +private: + char m_szSortKey[ 64 ]; + ServerBrowserTablePanel *m_pBrowser; + +public: + CSBLabel( char *name, char *sortkey ) : Label( name ) + { + m_pBrowser = NULL; + + strcpy( m_szSortKey, sortkey ); + + int label_bg_r = 120, + label_bg_g = 75, + label_bg_b = 32, + label_bg_a = 200; + + int label_fg_r = 255, + label_fg_g = 0, + label_fg_b = 0, + label_fg_a = 0; + + setContentAlignment( vgui::Label::a_west ); + setFgColor( label_fg_r, label_fg_g, label_fg_b, label_fg_a ); + setBgColor( label_bg_r, label_bg_g, label_bg_b, label_bg_a ); + setFont( Scheme::sf_primary2 ); + + } + + void setTable( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + + addInputSignal( new LabelSortInputHandler( (ServerBrowserTablePanel * )m_pBrowser, m_szSortKey ) ); + } +}; + +ServerBrowser::ServerBrowser(int x,int y,int wide,int tall) : CTransparentPanel( 100, x,y,wide,tall ) +{ + int i; + + _headerPanel = new HeaderPanel(0,0,wide,HEADER_SIZE_Y); + _headerPanel->setParent(this); + _headerPanel->setFgColor( 100,100,100, 100 ); + _headerPanel->setBgColor( 0, 0, 0, 100 ); + + CSBLabel *pLabel[5]; + + pLabel[0] = new CSBLabel( "Address", "address" ); + pLabel[1] = new CSBLabel( "Server", "hostname" ); + pLabel[2] = new CSBLabel( "Map", "map" ); + pLabel[3] = new CSBLabel( "Current", "current" ); + pLabel[4] = new CSBLabel( "Latency", "ping" ); + + for ( i = 0; i < 5; i++ ) + { + _headerPanel->addSectionPanel( pLabel[i] ); + } + + // _headerPanel->setFont( Scheme::sf_primary1 ); + + _headerPanel->setSliderPos( 0, CSIZE_ADDRESS ); + _headerPanel->setSliderPos( 1, CSIZE_SERVER ); + _headerPanel->setSliderPos( 2, CSIZE_MAP ); + _headerPanel->setSliderPos( 3, CSIZE_CURRENT ); + _headerPanel->setSliderPos( 4, CSIZE_PING ); + + _tablePanel = new ServerBrowserTablePanel( 0, HEADER_SIZE_Y, wide, tall - HEADER_SIZE_Y, NUM_COLUMNS ); + _tablePanel->setParent(this); + _tablePanel->setHeaderPanel(_headerPanel); + _tablePanel->setFgColor( 100,100,100, 100 ); + _tablePanel->setBgColor( 0, 0, 0, 100 ); + + _tablePanel->addInputSignal( new CBrowser_InputSignal( (ServerBrowserTablePanel *)_tablePanel ) ); + + for ( i = 0; i < 5; i++ ) + { + pLabel[i]->setTable( (ServerBrowserTablePanel * )_tablePanel ); + } + + int bw = 80, bh = 15; + int by = tall - HEADER_SIZE_Y; + + int btnx = 10; + + _connectButton = new CommandButton( "Connect", btnx, by, bw, bh ); + _connectButton->setParent( this ); + _connectButton->addActionSignal( new ConnectHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + btnx += bw; + + _refreshButton = new CommandButton( "Refresh", btnx, by, bw, bh ); + _refreshButton->setParent( this ); + _refreshButton->addActionSignal( new RefreshHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + /* + btnx += bw; + + _broadcastRefreshButton = new CommandButton( "LAN", btnx, by, bw, bh ); + _broadcastRefreshButton->setParent( this ); + _broadcastRefreshButton->addActionSignal( new BroadcastRefreshHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + */ + + btnx += bw; + + _stopButton = new CommandButton( "Stop", btnx, by, bw, bh ); + _stopButton->setParent( this ); + _stopButton->addActionSignal( new StopHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + /* + btnx += bw; + + _pingButton = new CommandButton( "Test", btnx, by, bw, bh ); + _pingButton->setParent( this ); + _pingButton->addActionSignal( new PingHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + btnx += bw; + + _sortButton = new CommandButton( "Sort", btnx, by, bw, bh ); + _sortButton->setParent( this ); + _sortButton->addActionSignal( new SortHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + */ + + btnx += bw; + + _cancelButton = new CommandButton( "Close", btnx, by, bw, bh ); + _cancelButton->setParent( this ); + _cancelButton->addActionSignal( new CancelHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + setPaintBorderEnabled(false); + setPaintBackgroundEnabled(false); + setPaintEnabled(false); + +} + +void ServerBrowser::setSize(int wide,int tall) +{ + Panel::setSize(wide,tall); + + _headerPanel->setBounds(0,0,wide,HEADER_SIZE_Y); + _tablePanel->setBounds(0,HEADER_SIZE_Y,wide,tall - HEADER_SIZE_Y); + + _connectButton->setBounds( 5, tall - HEADER_SIZE_Y, 75, 15 ); + _refreshButton->setBounds( 85, tall - HEADER_SIZE_Y, 75, 15 ); + /* + _broadcastRefreshButton->setBounds( 165, tall - HEADER_SIZE_Y, 75, 15 ); + */ + _stopButton->setBounds( 165, tall - HEADER_SIZE_Y, 75, 15 ); + /* + _pingButton->setBounds( 325, tall - HEADER_SIZE_Y, 75, 15 ); + */ + _cancelButton->setBounds( 245, tall - HEADER_SIZE_Y, 75, 15 ); +} + +void CBrowser_InputSignal::mousePressed(MouseCode code,Panel* panel) +{ + int x, y; + int therow = 2; + + if ( code != MOUSE_LEFT ) + return; + + panel->getApp()->getCursorPos(x,y); + panel->screenToLocal( x, y ); + + therow = y / CELL_HEIGHT; + + // Figure out which row it's on + m_pBrowser->setMouseOverRow( therow ); +} + +void CBrowser_InputSignal::mouseDoublePressed(MouseCode code,Panel* panel) +{ + int x, y; + int therow = 2; + + if ( code != MOUSE_LEFT ) + return; + + panel->getApp()->getCursorPos(x,y); + panel->screenToLocal( x, y ); + + therow = y / CELL_HEIGHT; + + // Figure out which row it's on + m_pBrowser->setMouseOverRow( therow ); + m_pBrowser->DoConnect(); +} diff --git a/hlsdk/cl_dll/vgui_ServerBrowser.h b/hlsdk/cl_dll/vgui_ServerBrowser.h new file mode 100644 index 00000000..43da4f4e --- /dev/null +++ b/hlsdk/cl_dll/vgui_ServerBrowser.h @@ -0,0 +1,50 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef ServerBrowser_H +#define ServerBrowser_H + +#include + +namespace vgui +{ +class Button; +class TablePanel; +class HeaderPanel; +} + +class CTransparentPanel; +class CommandButton; + +// Scoreboard positions +#define SB_X_INDENT (20 * ((float)ScreenHeight / 640)) +#define SB_Y_INDENT (20 * ((float)ScreenHeight / 480)) + +class ServerBrowser : public CTransparentPanel +{ +private: + HeaderPanel * _headerPanel; + TablePanel* _tablePanel; + + CommandButton* _connectButton; + CommandButton* _refreshButton; + CommandButton* _broadcastRefreshButton; + CommandButton* _stopButton; + CommandButton* _sortButton; + CommandButton* _cancelButton; + + CommandButton* _pingButton; + +public: + ServerBrowser(int x,int y,int wide,int tall); +public: + virtual void setSize(int wide,int tall); +}; + + + +#endif \ No newline at end of file diff --git a/hlsdk/cl_dll/vgui_SpectatorPanel.cpp b/hlsdk/cl_dll/vgui_SpectatorPanel.cpp new file mode 100644 index 00000000..582a319e --- /dev/null +++ b/hlsdk/cl_dll/vgui_SpectatorPanel.cpp @@ -0,0 +1,383 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// vgui_SpectatorPanel.cpp: implementation of the SpectatorPanel class. +// +////////////////////////////////////////////////////////////////////// + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "pm_shared.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_SpectatorPanel.h" +#include "vgui_scorepanel.h" + +#define PANEL_HEIGHT 32 + + +#define BANNER_WIDTH 256 +#define BANNER_HEIGHT 64 + + +#define OPTIONS_BUTTON_X 96 +#define CAMOPTIONS_BUTTON_X 200 + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +SpectatorPanel::SpectatorPanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) +{ +} + +SpectatorPanel::~SpectatorPanel() +{ + +} + +void SpectatorPanel::ActionSignal(int cmd) +{ + switch (cmd) + { + case SPECTATOR_PANEL_CMD_NONE : break; + + case SPECTATOR_PANEL_CMD_OPTIONS : gViewPort->ShowCommandMenu( gViewPort->m_SpectatorOptionsMenu ); + break; + + case SPECTATOR_PANEL_CMD_NEXTPLAYER : gHUD.m_Spectator.FindNextPlayer(true); + break; + + case SPECTATOR_PANEL_CMD_PREVPLAYER : gHUD.m_Spectator.FindNextPlayer(false); + break; + + case SPECTATOR_PANEL_CMD_HIDEMENU : ShowMenu(false); + break; + + case SPECTATOR_PANEL_CMD_CAMERA : gViewPort->ShowCommandMenu( gViewPort->m_SpectatorCameraMenu ); + break; + + case SPECTATOR_PANEL_CMD_TOGGLE_INSET : gHUD.m_Spectator.SetModes( -1, + gHUD.m_Spectator.ToggleInset(false) ); + break; + + + default : gEngfuncs.Con_DPrintf("Unknown SpectatorPanel ActionSingal %i.\n",cmd); break; + } + +} + + +void SpectatorPanel::Initialize() +{ + int x,y,wide,tall; + + getBounds(x,y,wide,tall); + + CSchemeManager * pSchemes = gViewPort->GetSchemeManager(); + + SchemeHandle_t hSmallScheme = pSchemes->getSchemeHandle( "Team Info Text" ); + + m_TopBorder = new CTransparentPanel(64, 0, 0, ScreenWidth, YRES(PANEL_HEIGHT)); + m_TopBorder->setParent(this); + + m_BottomBorder = new CTransparentPanel(64, 0, ScreenHeight - YRES(32), ScreenWidth, YRES(PANEL_HEIGHT)); + m_BottomBorder->setParent(this); + + setPaintBackgroundEnabled(false); + + m_ExtraInfo = new Label( "Extra Info", 0, 0, wide, YRES(PANEL_HEIGHT) ); + m_ExtraInfo->setParent(m_TopBorder); + m_ExtraInfo->setFont( pSchemes->getFont(hSmallScheme) ); + + m_ExtraInfo->setPaintBackgroundEnabled(false); + m_ExtraInfo->setFgColor( 143, 143, 54, 0 ); + m_ExtraInfo->setContentAlignment( vgui::Label::a_west ); + + + + m_TimerImage = new CImageLabel( "timer", 0, 0, 14, 14 ); + m_TimerImage->setParent(m_TopBorder); + + m_TopBanner = new CImageLabel( "banner", 0, 0, XRES(BANNER_WIDTH), YRES(BANNER_HEIGHT) ); + m_TopBanner->setParent(this); + + m_CurrentTime = new Label( "00:00", 0, 0, wide, YRES(PANEL_HEIGHT) ); + m_CurrentTime->setParent(m_TopBorder); + m_CurrentTime->setFont( pSchemes->getFont(hSmallScheme) ); + m_CurrentTime->setPaintBackgroundEnabled(false); + m_CurrentTime->setFgColor( 143, 143, 54, 0 ); + m_CurrentTime->setContentAlignment( vgui::Label::a_west ); + + m_Separator = new Panel( 0, 0, XRES( 64 ), YRES( 96 )); + m_Separator->setParent( m_TopBorder ); + m_Separator->setFgColor( 59, 58, 34, 48 ); + m_Separator->setBgColor( 59, 58, 34, 48 ); + + for ( int j= 0; j < TEAM_NUMBER; j++ ) + { + m_TeamScores[j] = new Label( " ", 0, 0, wide, YRES(PANEL_HEIGHT) ); + m_TeamScores[j]->setParent( m_TopBorder ); + m_TeamScores[j]->setFont( pSchemes->getFont(hSmallScheme) ); + m_TeamScores[j]->setPaintBackgroundEnabled(false); + m_TeamScores[j]->setFgColor( 143, 143, 54, 0 ); + m_TeamScores[j]->setContentAlignment( vgui::Label::a_west ); + m_TeamScores[j]->setVisible ( false ); + } + + + // Initialize command buttons. + m_OptionButton = new ColorButton( CHudTextMessage::BufferedLocaliseTextString( "#SPECT_OPTIONS" ), XRES(15), YRES(6), XRES(OPTIONS_BUTTON_X), YRES(20), false, false ); + m_OptionButton->setParent( m_BottomBorder ); + m_OptionButton->setContentAlignment( vgui::Label::a_center ); + m_OptionButton->setBoundKey( (char)255 ); // special no bound to avoid leading spaces in name + m_OptionButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_OPTIONS) ); + m_OptionButton->setUnArmedBorderColor ( 59, 58, 34, 48 ); + m_OptionButton->setArmedBorderColor ( 194, 202, 54, 0 ); + m_OptionButton->setUnArmedColor ( 143, 143, 54, 0 ); + m_OptionButton->setArmedColor ( 194, 202, 54, 0 ); + + m_CamButton = new ColorButton( CHudTextMessage::BufferedLocaliseTextString( "#CAM_OPTIONS" ), ScreenWidth - ( XRES ( CAMOPTIONS_BUTTON_X ) + 15 ), YRES(6), XRES ( CAMOPTIONS_BUTTON_X ), YRES(20), false, false ); + m_CamButton->setParent( m_BottomBorder ); + m_CamButton->setContentAlignment( vgui::Label::a_center ); + m_CamButton->setBoundKey( (char)255 ); // special no bound to avoid leading spaces in name + m_CamButton->addActionSignal( new CSpectatorHandler_Command( this, SPECTATOR_PANEL_CMD_CAMERA ) ); + m_CamButton->setUnArmedBorderColor ( 59, 58, 34, 48 ); + m_CamButton->setArmedBorderColor ( 194, 202, 54, 0 ); + m_CamButton->setUnArmedColor ( 143, 143, 54, 0 ); + m_CamButton->setArmedColor ( 194, 202, 54, 0 ); + + m_PrevPlayerButton= new ColorButton("<", XRES( 15 + OPTIONS_BUTTON_X + 15 ), YRES(6), XRES(24), YRES(20), false, false ); + m_PrevPlayerButton->setParent( m_BottomBorder ); + m_PrevPlayerButton->setContentAlignment( vgui::Label::a_center ); + m_PrevPlayerButton->setBoundKey( (char)255 ); // special no bound to avoid leading spaces in name + m_PrevPlayerButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_PREVPLAYER) ); + m_PrevPlayerButton->setUnArmedBorderColor ( 59, 58, 34, 48 ); + m_PrevPlayerButton->setArmedBorderColor ( 194, 202, 54, 0 ); + m_PrevPlayerButton->setUnArmedColor ( 143, 143, 54, 0 ); + m_PrevPlayerButton->setArmedColor ( 194, 202, 54, 0 ); + + m_NextPlayerButton= new ColorButton(">", (ScreenWidth - (XRES ( CAMOPTIONS_BUTTON_X ) + 15)) - XRES ( 24 + 15 ), YRES(6), XRES(24), YRES(20),false, false ); + m_NextPlayerButton->setParent( m_BottomBorder ); + m_NextPlayerButton->setContentAlignment( vgui::Label::a_center ); + m_NextPlayerButton->setBoundKey( (char)255 ); // special no bound to avoid leading spaces in name + m_NextPlayerButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_NEXTPLAYER) ); + m_NextPlayerButton->setUnArmedBorderColor ( 59, 58, 34, 48 ); + m_NextPlayerButton->setArmedBorderColor ( 194, 202, 54, 0 ); + m_NextPlayerButton->setUnArmedColor ( 143, 143, 54, 0 ); + m_NextPlayerButton->setArmedColor ( 194, 202, 54, 0 ); + + // Initialize the bottom title. + + float flLabelSize = ( (ScreenWidth - (XRES ( CAMOPTIONS_BUTTON_X ) + 15)) - XRES ( 24 + 15 ) ) - XRES( (15 + OPTIONS_BUTTON_X + 15) + 38 ); + + m_BottomMainLabel = new Label( "Spectator Bottom", XRES( ( 15 + OPTIONS_BUTTON_X + 15 ) + 31 ), YRES(6), flLabelSize, YRES(20) ); + m_BottomMainLabel->setParent(m_BottomBorder); + m_BottomMainLabel->setPaintBackgroundEnabled(false); + m_BottomMainLabel->setFgColor( Scheme::sc_primary1 ); + m_BottomMainLabel->setContentAlignment( vgui::Label::a_center ); + m_BottomMainLabel->setBorder( new LineBorder( Color( 59, 58, 34, 48 ) ) ); + + m_InsetViewButton = new ColorButton("", XRES(2), YRES(2), XRES(240), YRES(180), false, false ); + m_InsetViewButton->setParent( this ); + m_InsetViewButton->setBoundKey( (char)255 ); + m_InsetViewButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_TOGGLE_INSET) ); + m_InsetViewButton->setUnArmedBorderColor ( 59, 58, 34, 48 ); + m_InsetViewButton->setArmedBorderColor ( 194, 202, 54, 0 ); + m_InsetViewButton->setUnArmedColor ( 143, 143, 54, 0 ); + m_InsetViewButton->setArmedColor ( 194, 202, 54, 0 ); + + + m_menuVisible = false; + m_insetVisible = false; +// m_HideButton->setVisible(false); + m_CamButton->setVisible(false); + m_OptionButton->setVisible(false); + m_NextPlayerButton->setVisible(false); + m_PrevPlayerButton->setVisible(false); + m_TopBanner->setVisible( false ); + m_ExtraInfo->setVisible( false ); + m_Separator->setVisible( false ); + m_TimerImage->setVisible( false ); + +} + +void SpectatorPanel::ShowMenu(bool isVisible) +{ +// m_HideButton->setVisible(isVisible); m_HideButton->setArmed( false ); + m_OptionButton->setVisible(isVisible); m_OptionButton->setArmed( false ); + m_CamButton->setVisible(isVisible); m_CamButton->setArmed( false ); + m_NextPlayerButton->setVisible(isVisible); m_NextPlayerButton->setArmed( false ); + m_PrevPlayerButton->setVisible(isVisible); m_PrevPlayerButton->setArmed( false ); + + if ( !isVisible ) + { + int iLabelSizeX, iLabelSizeY; + m_BottomMainLabel->getSize( iLabelSizeX, iLabelSizeY ); + m_BottomMainLabel->setPos( ( ScreenWidth / 2 ) - (iLabelSizeX/2), YRES(6) ); + } + else + m_BottomMainLabel->setPos( XRES( ( 15 + OPTIONS_BUTTON_X + 15 ) + 31 ), YRES(6) ); + + if ( !isVisible ) + { + gViewPort->HideCommandMenu(); + + // if switching from visible menu to invisible menu, show help text + if ( m_menuVisible && this->isVisible() ) + { + char string[ 64 ]; + + _snprintf( string, sizeof( string ) - 1, "%c%s", HUD_PRINTCENTER, CHudTextMessage::BufferedLocaliseTextString( "#Spec_Duck" ) ); + string[ sizeof( string ) - 1 ] = '\0'; + + gHUD.m_TextMessage.MsgFunc_TextMsg( NULL, strlen( string ) + 1, string ); + } + } + + m_menuVisible = isVisible; + + gViewPort->UpdateCursorState(); +} + + +const char *GetSpectatorLabel ( int iMode ) +{ + switch ( iMode ) + { + case OBS_CHASE_LOCKED: + return "#OBS_CHASE_LOCKED"; + + case OBS_CHASE_FREE: + return "#OBS_CHASE_FREE"; + + case OBS_ROAMING: + return "#OBS_ROAMING"; + + case OBS_IN_EYE: + return "#OBS_IN_EYE"; + + case OBS_MAP_FREE: + return "#OBS_MAP_FREE"; + + case OBS_MAP_CHASE: + return "#OBS_MAP_CHASE"; + + case OBS_NONE: + default: + return "#OBS_NONE"; + } + + return ""; +} + +void SpectatorPanel::EnableInsetView(bool isEnabled) +{ + int x = gHUD.m_Spectator.m_OverviewData.insetWindowX; + int y = gHUD.m_Spectator.m_OverviewData.insetWindowY; + int wide = gHUD.m_Spectator.m_OverviewData.insetWindowWidth; + int tall = gHUD.m_Spectator.m_OverviewData.insetWindowHeight; + int offset = x + wide + 2; + + if ( isEnabled ) + { + // short black bar to see full inset + m_TopBorder->setBounds( XRES(offset), 0, XRES(640 - offset ), YRES(PANEL_HEIGHT) ); + + if ( gEngfuncs.IsSpectateOnly() ) + { + m_TopBanner->setVisible( true ); + m_TopBanner->setPos( XRES(offset), 0 ); + } + else + m_TopBanner->setVisible( false ); + + m_InsetViewButton->setBounds( XRES( x ), YRES( y ), + XRES( wide ), YRES( tall ) ); + m_InsetViewButton->setVisible(true); + } + else + { + // full black bar, no inset border + // show banner only in real HLTV mode + if ( gEngfuncs.IsSpectateOnly() ) + { + m_TopBanner->setVisible( true ); + m_TopBanner->setPos( 0,0 ); + } + else + m_TopBanner->setVisible( false ); + + m_TopBorder->setBounds( 0, 0, ScreenWidth, YRES(PANEL_HEIGHT) ); + + m_InsetViewButton->setVisible(false); + } + + m_insetVisible = isEnabled; + + Update(); + + m_CamButton->setText( CHudTextMessage::BufferedLocaliseTextString( GetSpectatorLabel( g_iUser1 ) ) ); +} + + + + +void SpectatorPanel::Update() +{ + int iTextWidth, iTextHeight; + int iTimeHeight, iTimeWidth; + int offset,j; + + if ( m_insetVisible ) + offset = gHUD.m_Spectator.m_OverviewData.insetWindowX + gHUD.m_Spectator.m_OverviewData.insetWindowWidth + 2; + else + offset = 0; + + bool visible = gHUD.m_Spectator.m_drawstatus->value != 0; + + m_ExtraInfo->setVisible( visible ); + m_TimerImage->setVisible( visible ); + m_CurrentTime->setVisible( visible ); + m_Separator->setVisible( visible ); + + for ( j= 0; j < TEAM_NUMBER; j++ ) + m_TeamScores[j]->setVisible( visible ); + + if ( !visible ) + return; + + m_ExtraInfo->getTextSize( iTextWidth, iTextHeight ); + m_CurrentTime->getTextSize( iTimeWidth, iTimeHeight ); + + iTimeWidth += XRES ( 14 ); // +timer icon + iTimeWidth += ( 4-(iTimeWidth%4) ); + + if ( iTimeWidth > iTextWidth ) + iTextWidth = iTimeWidth; + + int xPos = ScreenWidth - ( iTextWidth + XRES ( 4 + offset ) ); + + m_ExtraInfo->setBounds( xPos, YRES( 1 ), iTextWidth, iTextHeight ); + + m_TimerImage->setBounds( xPos, YRES( 2 ) + iTextHeight , XRES(14), YRES(14) ); + + m_CurrentTime->setBounds( xPos + XRES ( 14 + 1 ), YRES( 2 ) + iTextHeight , iTimeWidth, iTimeHeight ); + + m_Separator->setPos( ScreenWidth - ( iTextWidth + XRES ( 4+2+4+offset ) ) , YRES( 1 ) ); + m_Separator->setSize( XRES( 4 ), YRES( PANEL_HEIGHT - 2 ) ); + + for ( j= 0; j < TEAM_NUMBER; j++ ) + { + int iwidth, iheight; + + m_TeamScores[j]->getTextSize( iwidth, iheight ); + m_TeamScores[j]->setBounds( ScreenWidth - ( iTextWidth + XRES ( 4+2+4+2+offset ) + iwidth ), YRES( 1 ) + ( iheight * j ), iwidth, iheight ); + } +} diff --git a/hlsdk/cl_dll/vgui_SpectatorPanel.h b/hlsdk/cl_dll/vgui_SpectatorPanel.h new file mode 100644 index 00000000..d77e9003 --- /dev/null +++ b/hlsdk/cl_dll/vgui_SpectatorPanel.h @@ -0,0 +1,102 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// vgui_SpectatorPanel.h: interface for the SpectatorPanel class. +// +////////////////////////////////////////////////////////////////////// + +#ifndef SPECTATORPANEL_H +#define SPECTATORPANEL_H + +#include +#include +#include + +using namespace vgui; + +#define SPECTATOR_PANEL_CMD_NONE 0 + +#define SPECTATOR_PANEL_CMD_OPTIONS 1 +#define SPECTATOR_PANEL_CMD_PREVPLAYER 2 +#define SPECTATOR_PANEL_CMD_NEXTPLAYER 3 +#define SPECTATOR_PANEL_CMD_HIDEMENU 4 +#define SPECTATOR_PANEL_CMD_TOGGLE_INSET 5 +#define SPECTATOR_PANEL_CMD_CAMERA 6 + +#define TEAM_NUMBER 2 + +class SpectatorPanel : public Panel //, public vgui::CDefaultInputSignal +{ + +public: + SpectatorPanel(int x,int y,int wide,int tall); + virtual ~SpectatorPanel(); + + void ActionSignal(int cmd); + + // InputSignal overrides. +public: + void Initialize(); + void Update(); + + + +public: + + void EnableInsetView(bool isEnabled); + void ShowMenu(bool isVisible); + + + ColorButton * m_OptionButton; +// CommandButton * m_HideButton; + ColorButton * m_PrevPlayerButton; + ColorButton * m_NextPlayerButton; + ColorButton * m_CamButton; + + CTransparentPanel * m_TopBorder; + CTransparentPanel * m_BottomBorder; + + ColorButton *m_InsetViewButton; + + Label *m_BottomMainLabel; + CImageLabel *m_TimerImage; + Label *m_CurrentTime; + Label *m_ExtraInfo; + Panel *m_Separator; + + Label *m_TeamScores[TEAM_NUMBER]; + + CImageLabel *m_TopBanner; + + bool m_menuVisible; + bool m_insetVisible; +}; + + + +class CSpectatorHandler_Command : public ActionSignal +{ + +private: + SpectatorPanel * m_pFather; + int m_cmd; + +public: + CSpectatorHandler_Command( SpectatorPanel * panel, int cmd ) + { + m_pFather = panel; + m_cmd = cmd; + } + + virtual void actionPerformed( Panel * panel ) + { + m_pFather->ActionSignal(m_cmd); + } +}; + + +#endif // !defined SPECTATORPANEL_H diff --git a/hlsdk/cl_dll/vgui_TeamFortressViewport.cpp b/hlsdk/cl_dll/vgui_TeamFortressViewport.cpp new file mode 100644 index 00000000..8d49d203 --- /dev/null +++ b/hlsdk/cl_dll/vgui_TeamFortressViewport.cpp @@ -0,0 +1,2370 @@ +//=========== (C) Copyright 1996-2002 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 "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 "vgui_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ServerBrowser.h" +#include "vgui_ScorePanel.h" +#include "vgui_SpectatorPanel.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 x, int y, int wide, int tall ); +extern float * GetClientColor( int clientIndex ); + +using namespace vgui; + +// Team Colors +int iNumberOfTeamColors = 5; +int iTeamColors[5][3] = +{ + { 255, 170, 0 }, // HL orange (default) + { 125, 165, 210 }, // Blue + { 200, 90, 70 }, // Red + { 225, 205, 45 }, // Yellow + { 145, 215, 140 }, // Green +}; + + +// Used for Class specific buttons +char *sTFClasses[] = +{ + "", + "SCOUT", + "SNIPER", + "SOLDIER", + "DEMOMAN", + "MEDIC", + "HWGUY", + "PYRO", + "SPY", + "ENGINEER", + "CIVILIAN", +}; + +char *sLocalisedClasses[] = +{ + "#Civilian", + "#Scout", + "#Sniper", + "#Soldier", + "#Demoman", + "#Medic", + "#HWGuy", + "#Pyro", + "#Spy", + "#Engineer", + "#Random", + "#Civilian", +}; + +char *sTFClassSelection[] = +{ + "civilian", + "scout", + "sniper", + "soldier", + "demoman", + "medic", + "hwguy", + "pyro", + "spy", + "engineer", + "randompc", + "civilian", +}; + +int iBuildingCosts[] = +{ + BUILD_COST_DISPENSER, + BUILD_COST_SENTRYGUN +}; + +// 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 iYOffset, bool bHideAll ) +{ + int i, iCurrentY = 0; + int iVisibleButtons = 0; + + // Cycle through all the buttons in this menu, and see which will be visible + for (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( 0, 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( 0 , false ) ) + { + // The submenu had no visible buttons, so don't display this button + m_aButtons[i]->setVisible( false ); + continue; + } + } + + m_aButtons[i]->setVisible( true ); + iVisibleButtons++; + } + } + + // Set Size + setSize( _size[0], (iVisibleButtons * (m_flButtonSizeY-1)) + 1 ); + + if ( iYOffset ) + { + m_iYOffset = iYOffset; + } + + for (i = 0; i < m_iButtons; i++) + { + if ( m_aButtons[i]->isVisible() ) + { + if ( m_aButtons[i]->GetSubMenu() != NULL ) + (m_aButtons[i]->GetSubMenu())->RecalculateVisibles( iCurrentY + m_iYOffset, false ); + + + // Make sure it's at the right Y position + // m_aButtons[i]->getPos( iXPos, iYPos ); + + if ( m_iDirection ) + { + m_aButtons[i]->setPos( 0, (iVisibleButtons-1) * (m_flButtonSizeY-1) - iCurrentY ); + } + else + { + m_aButtons[i]->setPos( 0, iCurrentY ); + } + + iCurrentY += (m_flButtonSizeY-1); + } + } + + return iVisibleButtons?true:false; +} + +// Make sure all submenus can fit on the screen +void CCommandMenu::RecalculatePositions( int iYOffset ) +{ + int iTop; + int iAdjust = 0; + + m_iYOffset+= iYOffset; + + if ( m_iDirection ) + iTop = ScreenHeight - (m_iYOffset + _size[1] ); + else + iTop = m_iYOffset; + + if ( iTop < 0 ) + iTop = 0; + + // Calculate if this is going to fit onscreen, and shuffle it up if it won't + int iBottom = iTop + _size[1]; + + if ( iBottom > ScreenHeight ) + { + // Move in increments of button sizes + while (iAdjust < (iBottom - ScreenHeight)) + { + iAdjust += m_flButtonSizeY - 1; + } + + iTop -= iAdjust; + + // Make sure it doesn't move off the top of the screen (the menu's too big to fit it all) + if ( iTop < 0 ) + { + iAdjust -= (0 - iTop); + iTop = 0; + } + } + + setPos( _pos[0], iTop ); + + // 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 ); +} + + +// 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 *TeamFortressViewport::CreateSubMenu( CommandButton *pButton, CCommandMenu *pParentMenu, int iYOffset, int iXOffset ) +{ + int iXPos = 0; + int iYPos = 0; + int iWide = CMENU_SIZE_X; + int iTall = 0; + int iDirection = 0; + + if (pParentMenu) + { + iXPos = m_pCurrentCommandMenu->GetXOffset() + (CMENU_SIZE_X - 1) + iXOffset; + iYPos = m_pCurrentCommandMenu->GetYOffset() + iYOffset; + iDirection = pParentMenu->GetDirection(); + } + + CCommandMenu *pMenu = new CCommandMenu(pParentMenu, iDirection, iXPos, iYPos, iWide, iTall ); + pMenu->setParent(this); + pButton->AddSubMenu( pMenu ); + pButton->setFont( Scheme::sf_primary3 ); + pMenu->m_flButtonSizeY = m_pCurrentCommandMenu->m_flButtonSizeY; + + // 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 TeamFortressViewport is nulled out +// Input : stAllocateBlock - +// Output : void * +//----------------------------------------------------------------------------- +void *TeamFortressViewport::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) {} +}; + + +//================================================================ +TeamFortressViewport::TeamFortressViewport(int x,int y,int wide,int tall) : Panel(x,y,wide,tall), m_SchemeManager(wide,tall) +{ + gViewPort = this; + m_iInitialized = false; + m_pTeamMenu = NULL; + m_pClassMenu = NULL; + m_pScoreBoard = NULL; + m_pSpectatorPanel = NULL; + m_pCurrentMenu = NULL; + m_pCurrentCommandMenu = 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(); + CreateClassMenu(); + CreateSpectatorMenu(); + CreateScoreBoard(); + // Init command menus + m_iNumMenus = 0; + m_iCurrentTeamNumber = m_iUser1 = m_iUser2 = m_iUser3 = 0; + + m_StandardMenu = CreateCommandMenu("commandmenu.txt", 0, CMENU_TOP, false, CMENU_SIZE_X, BUTTON_SIZE_Y, 0 ); + m_SpectatorOptionsMenu = CreateCommandMenu("spectatormenu.txt", 1, YRES(32), true, CMENU_SIZE_X, BUTTON_SIZE_Y / 2, 0 ); // above bottom bar, flat design + m_SpectatorCameraMenu = CreateCommandMenu("spectcammenu.txt", 1, YRES(32), true, XRES( 200 ), BUTTON_SIZE_Y / 2, ScreenWidth - ( XRES ( 200 ) + 15 ) ); // above bottom bar, flat design + CreateServerBrowser(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called everytime a new level is started. Viewport clears out it's data. +//----------------------------------------------------------------------------- +void TeamFortressViewport::Initialize( void ) +{ + // Force each menu to Initialize + if (m_pTeamMenu) + { + m_pTeamMenu->Initialize(); + } + if (m_pClassMenu) + { + m_pClassMenu->Initialize(); + } + if (m_pScoreBoard) + { + m_pScoreBoard->Initialize(); + HideScoreBoard(); + } + if (m_pSpectatorPanel) + { + // Spectator menu doesn't need initializing + m_pSpectatorPanel->setVisible( false ); + } + + // Make sure all menus are hidden + HideVGUIMenu(); + HideCommandMenu(); + + // Clear out some data + m_iGotAllMOTD = true; + m_iRandomPC = false; + m_flScoreBoardLastUpdated = 0; + m_flSpectatorPanelLastUpdated = 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::scu_none) ); +} + +class CException; +//----------------------------------------------------------------------------- +// Purpose: Read the Command Menu structure from the txt file and create the menu. +// Returns Index of menu in m_pCommandMenus +//----------------------------------------------------------------------------- +int TeamFortressViewport::CreateCommandMenu( char * menuFile, int direction, int yOffset, bool flatDesign, float flButtonSizeX, float flButtonSizeY, int xOffset ) +{ + // COMMAND MENU + // Create the root of this new Command Menu + + int newIndex = m_iNumMenus; + + m_pCommandMenus[newIndex] = new CCommandMenu(NULL, direction, xOffset, yOffset, flButtonSizeX, 300); // This will be resized once we know how many items are in it + m_pCommandMenus[newIndex]->setParent(this); + m_pCommandMenus[newIndex]->setVisible(false); + m_pCommandMenus[newIndex]->m_flButtonSizeY = flButtonSizeY; + m_pCommandMenus[newIndex]->m_iSpectCmdMenu = direction; + + m_iNumMenus++; + + // Read Command Menu from the txt file + char token[1024]; + char *pfile = (char*)gEngfuncs.COM_LoadFile( menuFile, 5, NULL); + if (!pfile) + { + gEngfuncs.Con_DPrintf( "Unable to open %s\n", menuFile); + SetCurrentCommandMenu( NULL ); + return newIndex; + } + +try +{ + // First, read in the localisation strings + + // Detpack strings + gHUD.m_TextMessage.LocaliseTextString( "#DetpackSet_For5Seconds", m_sDetpackStrings[0], MAX_BUTTON_SIZE ); + gHUD.m_TextMessage.LocaliseTextString( "#DetpackSet_For20Seconds", m_sDetpackStrings[1], MAX_BUTTON_SIZE ); + gHUD.m_TextMessage.LocaliseTextString( "#DetpackSet_For50Seconds", m_sDetpackStrings[2], MAX_BUTTON_SIZE ); + + // Now start parsing the menu structure + m_pCurrentCommandMenu = m_pCommandMenus[newIndex]; + 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 = -1; + int iToggle = 0; + int iButtonY; + bool bGetExtraToken = true; + CommandButton *pButton = NULL; + + // We should never be here without a Command Menu + if (!m_pCurrentCommandMenu) + { + gEngfuncs.Con_Printf("Error in %s file after '%s'.\n",menuFile, szLastButtonText ); + m_iInitialized = false; + return newIndex; + } + + // 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 if ( !strncmp(cCustom, "TOGGLE", 6) ) + { + iToggle = true; + // 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'; + + iButtonY = (BUTTON_SIZE_Y-1) * m_pCurrentCommandMenu->GetNumButtons(); + + // Custom button handling + if ( iCustom ) + { + pButton = CreateCustomButton( cText, cCommand, iButtonY ); + + // 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, xOffset, iButtonY, flButtonSizeX, flButtonSizeY ); + } + else if ( iTeamOnly != -1) + { + // button that only shows up if the player is on team iTeamOnly + pButton = new TeamOnlyCommandButton( iTeamOnly, cText, xOffset, iButtonY, flButtonSizeX, flButtonSizeY, flatDesign ); + } + else if ( iToggle && direction == 0 ) + { + pButton = new ToggleCommandButton( cCommand, cText, xOffset, iButtonY, flButtonSizeX, flButtonSizeY, flatDesign ); + } + else if ( direction == 1 ) + { + if ( iToggle ) + pButton = new SpectToggleButton( cCommand, cText, xOffset, iButtonY, flButtonSizeX, flButtonSizeY, flatDesign ); + else + pButton = new SpectButton( iPlayerClass, cText, xOffset, iButtonY, flButtonSizeX, flButtonSizeY ); + } + else + { + // normal button + pButton = new CommandButton( iPlayerClass, cText, xOffset, iButtonY, flButtonSizeX, flButtonSizeY, flatDesign ); + } + + // 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 %s past '%s'\n",menuFile, szLastButtonText ); + } + else + { + // Create the menu + m_pCommandMenus[m_iNumMenus] = CreateSubMenu(pButton, m_pCurrentCommandMenu, iButtonY ); + m_pCurrentCommandMenu = m_pCommandMenus[m_iNumMenus]; + m_iNumMenus++; + } + } + else if ( !iCustom ) + { + // Create the button and attach it to the current menu + if ( iToggle ) + pButton->addActionSignal(new CMenuHandler_ToggleCvar(cCommand)); + else + 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 newIndex; +} + + SetCurrentMenu( NULL ); + SetCurrentCommandMenu( NULL ); + gEngfuncs.COM_FreeFile( pfile ); + + m_iInitialized = true; + return newIndex; +} + +//----------------------------------------------------------------------------- +// Purpose: Creates all the class choices under a spy's disguise menus, and +// maps a command to them +// Output : CCommandMenu +//----------------------------------------------------------------------------- +CCommandMenu *TeamFortressViewport::CreateDisguiseSubmenu( CommandButton *pButton, CCommandMenu *pParentMenu, const char *commandText, int iYOffset, int iXOffset ) +{ + // create the submenu, under which the class choices will be listed + CCommandMenu *pMenu = CreateSubMenu( pButton, pParentMenu, iYOffset, iXOffset ); + m_pCommandMenus[m_iNumMenus] = pMenu; + m_iNumMenus++; + + // create the class choice buttons + for ( int i = PC_SCOUT; i <= PC_ENGINEER; i++ ) + { + CommandButton *pDisguiseButton = new CommandButton( CHudTextMessage::BufferedLocaliseTextString( sLocalisedClasses[i] ), 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y ); + + char sz[256]; + sprintf(sz, "%s %d", commandText, i ); + pDisguiseButton->addActionSignal(new CMenuHandler_StringCommand(sz)); + + pMenu->AddButton( pDisguiseButton ); + } + + return pMenu; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pButtonText - +// *pButtonName - +// Output : CommandButton +//----------------------------------------------------------------------------- +CommandButton *TeamFortressViewport::CreateCustomButton( char *pButtonText, char *pButtonName, int iYOffset ) +{ + 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, iYOffset ); + 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] ); + } + // ChangeClass + else if ( !strcmp( pButtonName, "!CHANGECLASS" ) ) + { + // Create the Change class menu + pButton = new ClassButton(-1, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y, false); + + // ChangeClass Submenu + pMenu = CreateSubMenu(pButton, m_pCurrentCommandMenu, iYOffset ); + m_pCommandMenus[m_iNumMenus] = pMenu; + m_iNumMenus++; + + for (int i = PC_SCOUT; i <= PC_RANDOM; i++ ) + { + char sz[256]; + + // ChangeClass buttons + CHudTextMessage::LocaliseTextString( sLocalisedClasses[i], sz, 256 ); + ClassButton *pClassButton = new ClassButton( i, sz, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y, false); + + sprintf(sz, "%s", sTFClassSelection[i]); + pClassButton->addActionSignal(new CMenuHandler_StringCommandClassSelect(sz)); + pMenu->AddButton( pClassButton ); + } + } + // 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) ); + } + // Class Descriptions + else if ( !strcmp( pButtonName, "!CLASSDESC" ) ) + { + 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_CLASSHELP)); + // 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) ); + } + // Spy abilities + else if ( !strcmp( pButtonName, "!SPY" ) ) + { + pButton = new DisguiseButton( 0, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y ); + } + // Feign + else if ( !strcmp( pButtonName, "!FEIGN" ) ) + { + pButton = new FeignButton(FALSE, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand( "feign" )); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Feign Silently + else if ( !strcmp( pButtonName, "!FEIGNSILENT" ) ) + { + pButton = new FeignButton(FALSE, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand( "sfeign" )); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Stop Feigning + else if ( !strcmp( pButtonName, "!FEIGNSTOP" ) ) + { + pButton = new FeignButton(TRUE, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand( "feign" )); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Disguise + else if ( !strcmp( pButtonName, "!DISGUISEENEMY" ) ) + { + // Create the disguise enemy button, which active only if there are 2 teams + pButton = new DisguiseButton(DISGUISE_TEAM2, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + CreateDisguiseSubmenu( pButton, m_pCurrentCommandMenu, "disguise_enemy", iYOffset); + } + else if ( !strcmp( pButtonName, "!DISGUISEFRIENDLY" ) ) + { + // Create the disguise friendly button, which active only if there are 1 or 2 teams + pButton = new DisguiseButton(DISGUISE_TEAM1 | DISGUISE_TEAM2, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + CreateDisguiseSubmenu( pButton, m_pCurrentCommandMenu, "disguise_friendly", iYOffset ); + } + else if ( !strcmp( pButtonName, "!DISGUISE" ) ) + { + // Create the Disguise button + pButton = new DisguiseButton( DISGUISE_TEAM3 | DISGUISE_TEAM4, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + CCommandMenu *pDisguiseMenu = CreateSubMenu( pButton, m_pCurrentCommandMenu, iYOffset ); + m_pCommandMenus[m_iNumMenus] = pDisguiseMenu; + m_iNumMenus++; + + // Disguise Enemy submenu buttons + for ( int i = 1; i <= 4; i++ ) + { + // only show the 4th disguise button if we have 4 teams + m_pDisguiseButtons[i] = new DisguiseButton( ((i < 4) ? DISGUISE_TEAM3 : 0) | DISGUISE_TEAM4, "Disguise", 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + + pDisguiseMenu->AddButton( m_pDisguiseButtons[i] ); + m_pDisguiseButtons[i]->setParentMenu( pDisguiseMenu ); + + char sz[256]; + sprintf( sz, "disguise %d", i ); + CreateDisguiseSubmenu( m_pDisguiseButtons[i], pDisguiseMenu, sz, iYOffset, CMENU_SIZE_X - 1 ); + } + } + // Start setting a Detpack + else if ( !strcmp( pButtonName, "!DETPACKSTART" ) ) + { + // Detpack Submenu + pButton = new DetpackButton(2, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + + // Create the submenu + pMenu = CreateSubMenu(pButton, m_pCurrentCommandMenu, iYOffset ); + m_pCommandMenus[m_iNumMenus] = pMenu; + m_iNumMenus++; + + // Set detpack buttons + CommandButton *pDetButton; + pDetButton = new CommandButton(m_sDetpackStrings[0], 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pDetButton->addActionSignal(new CMenuHandler_StringCommand("detstart 5")); + pMenu->AddButton( pDetButton ); + pDetButton = new CommandButton(m_sDetpackStrings[1], 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pDetButton->addActionSignal(new CMenuHandler_StringCommand("detstart 20")); + pMenu->AddButton( pDetButton ); + pDetButton = new CommandButton(m_sDetpackStrings[2], 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pDetButton->addActionSignal(new CMenuHandler_StringCommand("detstart 50")); + pMenu->AddButton( pDetButton ); + } + // Stop setting a Detpack + else if ( !strcmp( pButtonName, "!DETPACKSTOP" ) ) + { + pButton = new DetpackButton(1, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand( "detstop" )); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Engineer building + else if ( !strcmp( pButtonName, "!BUILD" ) ) + { + // only appears if the player is an engineer, and either they have built something or have enough metal to build + pButton = new BuildButton( BUILDSTATE_BASE, 0, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + } + else if ( !strcmp( pButtonName, "!BUILDSENTRY" ) ) + { + pButton = new BuildButton( BUILDSTATE_CANBUILD, BuildButton::SENTRYGUN, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("build 2")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!BUILDDISPENSER" ) ) + { + pButton = new BuildButton( BUILDSTATE_CANBUILD, BuildButton::DISPENSER, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("build 1")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!ROTATESENTRY180" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::SENTRYGUN, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("rotatesentry180")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!ROTATESENTRY" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::SENTRYGUN, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("rotatesentry")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!DISMANTLEDISPENSER" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::DISPENSER, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("dismantle 1")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!DISMANTLESENTRY" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::SENTRYGUN, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("dismantle 2")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!DETONATEDISPENSER" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::DISPENSER, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("detdispenser")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!DETONATESENTRY" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::SENTRYGUN, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("detsentry")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Stop building + else if ( !strcmp( pButtonName, "!BUILDSTOP" ) ) + { + pButton = new BuildButton( BUILDSTATE_BUILDING, 0, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("build")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + + return pButton; +} + +void TeamFortressViewport::ToggleServerBrowser() +{ + if (!m_iInitialized) + return; + + if ( !m_pServerBrowser ) + return; + + if ( m_pServerBrowser->isVisible() ) + { + m_pServerBrowser->setVisible( false ); + } + else + { + m_pServerBrowser->setVisible( true ); + } + + UpdateCursorState(); +} + +//======================================================================= +void TeamFortressViewport::ShowCommandMenu(int menuIndex) +{ + if (!m_iInitialized) + return; + + //Already have a menu open. + if ( m_pCurrentMenu ) + return; + + // is the command menu open? + if ( m_pCurrentCommandMenu == m_pCommandMenus[menuIndex] ) + { + HideCommandMenu(); + return; + } + + // Not visible while in intermission + if ( gHUD.m_iIntermission ) + return; + + // Recalculate visible menus + UpdateCommandMenu( menuIndex ); + HideVGUIMenu(); + + SetCurrentCommandMenu( m_pCommandMenus[menuIndex] ); + 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 TeamFortressViewport::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 TeamFortressViewport::HideCommandMenu() +{ + if (!m_iInitialized) + return; + + if ( m_pCommandMenus[m_StandardMenu] ) + { + m_pCommandMenus[m_StandardMenu]->ClearButtonsOfArmedState(); + } + + if ( m_pCommandMenus[m_SpectatorOptionsMenu] ) + { + m_pCommandMenus[m_SpectatorOptionsMenu]->ClearButtonsOfArmedState(); + } + + if ( m_pCommandMenus[m_SpectatorCameraMenu] ) + { + m_pCommandMenus[m_SpectatorCameraMenu]->ClearButtonsOfArmedState(); + } + + m_flMenuOpenTime = 0.0f; + SetCurrentCommandMenu( NULL ); + UpdateCursorState(); +} + +//----------------------------------------------------------------------------- +// Purpose: Bring up the scoreboard +//----------------------------------------------------------------------------- +void TeamFortressViewport::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 TeamFortressViewport::IsScoreBoardVisible( void ) +{ + if (m_pScoreBoard) + return m_pScoreBoard->isVisible(); + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Hide the scoreboard +//----------------------------------------------------------------------------- +void TeamFortressViewport::HideScoreBoard( void ) +{ + // Prevent removal of scoreboard during intermission + if ( gHUD.m_iIntermission ) + return; + + if (m_pScoreBoard) + { + m_pScoreBoard->setVisible(false); + + GetClientVoiceMgr()->StopSquelchMode(); + + UpdateCursorState(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Activate's the player special ability +// called when the player hits their "special" key +//----------------------------------------------------------------------------- +void TeamFortressViewport::InputPlayerSpecial( void ) +{ + if (!m_iInitialized) + return; + + if ( g_iPlayerClass == PC_ENGINEER || g_iPlayerClass == PC_SPY ) + { + ShowCommandMenu( gViewPort->m_StandardMenu ); + + if ( m_pCurrentCommandMenu ) + { + m_pCurrentCommandMenu->KeyInput( '7' ); + } + } + else + { + // if it's any other class, just send the command down to the server + ClientCmd( "_special" ); + } +} + +// Set the submenu of the Command Menu +void TeamFortressViewport::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 TeamFortressViewport::UpdateCommandMenu(int menuIndex) +{ + m_pCommandMenus[menuIndex]->RecalculateVisibles( 0, false ); + m_pCommandMenus[menuIndex]->RecalculatePositions( 0 ); +} + +void COM_FileBase ( const char *in, char *out); + +void TeamFortressViewport::UpdateSpectatorPanel() +{ + m_iUser1 = g_iUser1; + m_iUser2 = g_iUser2; + m_iUser3 = g_iUser3; + + if (!m_pSpectatorPanel) + return; + + if ( g_iUser1 && gHUD.m_pCvarDraw->value && !gHUD.m_iIntermission) // don't draw in dev_overview mode + { + char bottomText[128]; + char helpString2[128]; + char tempString[128]; + char * name; + int player = 0; + + // check if spectator combinations are still valid + gHUD.m_Spectator.CheckSettings(); + + if ( !m_pSpectatorPanel->isVisible() ) + { + m_pSpectatorPanel->setVisible( true ); // show spectator panel, but + m_pSpectatorPanel->ShowMenu( false ); // dsiable all menus/buttons + + _snprintf( tempString, sizeof( tempString ) - 1, "%c%s", HUD_PRINTCENTER, CHudTextMessage::BufferedLocaliseTextString( "#Spec_Duck" ) ); + tempString[ sizeof( tempString ) - 1 ] = '\0'; + + gHUD.m_TextMessage.MsgFunc_TextMsg( NULL, strlen( tempString ) + 1, tempString ); + } + + sprintf(bottomText,"#Spec_Mode%d", g_iUser1 ); + sprintf(helpString2,"#Spec_Mode%d", g_iUser1 ); + + if ( gEngfuncs.IsSpectateOnly() ) + strcat(helpString2, " - HLTV"); + + // check if we're locked onto a target, show the player's name + if ( (g_iUser2 > 0) && (g_iUser2 <= gEngfuncs.GetMaxClients()) && (g_iUser1 != OBS_ROAMING) ) + { + player = g_iUser2; + } + + // special case in free map and inset off, don't show names + if ( (g_iUser1 == OBS_MAP_FREE) && !gHUD.m_Spectator.m_pip->value ) + name = NULL; + else + name = g_PlayerInfoList[player].name; + + // create player & health string + if ( player && name ) + { + strcpy( bottomText, name ); + } + + // in first person mode colorize player names + if ( (g_iUser1 == OBS_IN_EYE) && player ) + { + float * color = GetClientColor( player ); + int r = color[0]*255; + int g = color[1]*255; + int b = color[2]*255; + + // set team color, a bit transparent + m_pSpectatorPanel->m_BottomMainLabel->setFgColor(r,g,b,0); + } + else + { // restore GUI color + m_pSpectatorPanel->m_BottomMainLabel->setFgColor( 143, 143, 54, 0 ); + } + + // add sting auto if we are in auto directed mode + if ( gHUD.m_Spectator.m_autoDirector->value ) + { + char tempString[128]; + sprintf(tempString, "#Spec_Auto %s", helpString2); + strcpy( helpString2, tempString ); + } + + m_pSpectatorPanel->m_BottomMainLabel->setText( CHudTextMessage::BufferedLocaliseTextString( bottomText ) ); + + + // update extra info field + char szText[64]; + + if ( gEngfuncs.IsSpectateOnly() ) + { + // in HLTV mode show number of spectators + _snprintf( szText, 63, "%s: %d", CHudTextMessage::BufferedLocaliseTextString( "#Spectators" ), gHUD.m_Spectator.m_iSpectatorNumber ); + } + else + { + // otherwise show map name + char szMapName[64]; + COM_FileBase( gEngfuncs.pfnGetLevelName(), szMapName ); + + _snprintf ( szText, 63, "%s: %s",CHudTextMessage::BufferedLocaliseTextString( "#Spec_Map" ), szMapName ); + } + + szText[63] = 0; + + m_pSpectatorPanel->m_ExtraInfo->setText ( szText ); + + /* + int timer = (int)( gHUD.m_roundTimer.m_flTimeEnd - gHUD.m_flTime ); + + if ( timer < 0 ) + timer = 0; + + _snprintf ( szText, 63, "%d:%02d\n", (timer / 60), (timer % 60) ); + + szText[63] = 0; + + m_pSpectatorPanel->m_CurrentTime->setText( szText ); */ + + // update spectator panel + gViewPort->m_pSpectatorPanel->Update(); + } + else + { + if ( m_pSpectatorPanel->isVisible() ) + { + m_pSpectatorPanel->setVisible( false ); + m_pSpectatorPanel->ShowMenu( false ); // dsiable all menus/buttons + } + } + + m_flSpectatorPanelLastUpdated = gHUD.m_flTime + 1.0; // update every seconds +} + +//====================================================================== +void TeamFortressViewport::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 TeamFortressViewport::CreateServerBrowser( void ) +{ + m_pServerBrowser = new ServerBrowser( 0, 0, ScreenWidth, ScreenHeight ); + m_pServerBrowser->setParent(this); + m_pServerBrowser->setVisible(false); +} + + +//====================================================================== +// Set the VGUI Menu +void TeamFortressViewport::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* TeamFortressViewport::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, "Half-Life" ); + 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 ); + + if (!pfile) + return NULL; + + cText = pfile; + + strncpy( cTitle, m_sMapName, MAX_TITLE_LENGTH ); + cTitle[MAX_TITLE_LENGTH-1] = 0; + } + else if ( iTextToShow == SHOW_CLASSDESC ) + { + switch ( g_iPlayerClass ) + { + case PC_SCOUT: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_scout" ); + CHudTextMessage::LocaliseTextString( "#Title_scout", cTitle, MAX_TITLE_LENGTH ); break; + case PC_SNIPER: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_sniper" ); + CHudTextMessage::LocaliseTextString( "#Title_sniper", cTitle, MAX_TITLE_LENGTH ); break; + case PC_SOLDIER: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_soldier" ); + CHudTextMessage::LocaliseTextString( "#Title_soldier", cTitle, MAX_TITLE_LENGTH ); break; + case PC_DEMOMAN: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_demoman" ); + CHudTextMessage::LocaliseTextString( "#Title_demoman", cTitle, MAX_TITLE_LENGTH ); break; + case PC_MEDIC: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_medic" ); + CHudTextMessage::LocaliseTextString( "#Title_medic", cTitle, MAX_TITLE_LENGTH ); break; + case PC_HVYWEAP: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_hwguy" ); + CHudTextMessage::LocaliseTextString( "#Title_hwguy", cTitle, MAX_TITLE_LENGTH ); break; + case PC_PYRO: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_pyro" ); + CHudTextMessage::LocaliseTextString( "#Title_pyro", cTitle, MAX_TITLE_LENGTH ); break; + case PC_SPY: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_spy" ); + CHudTextMessage::LocaliseTextString( "#Title_spy", cTitle, MAX_TITLE_LENGTH ); break; + case PC_ENGINEER: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_engineer" ); + CHudTextMessage::LocaliseTextString( "#Title_engineer", cTitle, MAX_TITLE_LENGTH ); break; + case PC_CIVILIAN: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_civilian" ); + CHudTextMessage::LocaliseTextString( "#Title_civilian", cTitle, MAX_TITLE_LENGTH ); break; + default: + return NULL; + } + + if ( g_iPlayerClass == PC_CIVILIAN ) + { + sprintf(sz, "classes/long_civilian.txt"); + } + else + { + sprintf(sz, "classes/long_%s.txt", sTFClassSelection[ g_iPlayerClass ]); + } + char *pfile = (char*)gEngfuncs.COM_LoadFile( sz, 5, NULL ); + if (pfile) + { + cText = pfile; + } + } + else if ( iTextToShow == SHOW_SPECHELP ) + { + CHudTextMessage::LocaliseTextString( "#Spec_Help_Title", cTitle, MAX_TITLE_LENGTH ); + cTitle[MAX_TITLE_LENGTH-1] = 0; + + char *pfile = CHudTextMessage::BufferedLocaliseTextString( "#Spec_Help_Text" ); + if ( pfile ) + { + cText = pfile; + } + } + + // 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, g_iPlayerClass == PC_UNDEFINED, false, 0, 0, ScreenWidth, ScreenHeight ); + pMOTDPanel->setParent( this ); + + if ( pfile ) + gEngfuncs.COM_FreeFile( pfile ); + + return pMOTDPanel; +} + +//================================================================ +// VGUI Menus +void TeamFortressViewport::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_CLASSHELP: + pNewMenu = CreateTextWindow( SHOW_CLASSDESC ); + break; + + case MENU_SPECHELP: + pNewMenu = CreateTextWindow( SHOW_SPECHELP ); + break; + case MENU_CLASS: + pNewMenu = ShowClassMenu(); + 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) + { + if ( m_pCurrentMenu->GetMenuID() == MENU_CLASS && iMenu == MENU_TEAM ) + { + CMenuPanel *temp = m_pCurrentMenu; + m_pCurrentMenu->Close(); + m_pCurrentMenu = pNewMenu; + m_pCurrentMenu->SetNextMenu( temp ); + m_pCurrentMenu->Open(); + UpdateCursorState(); + } + else + { + m_pCurrentMenu->SetNextMenu( pNewMenu ); + } + } + else + { + m_pCurrentMenu = pNewMenu; + m_pCurrentMenu->Open(); + UpdateCursorState(); + } +} + +// Removes all VGUI Menu's onscreen +void TeamFortressViewport::HideVGUIMenu() +{ + while (m_pCurrentMenu) + { + HideTopMenu(); + } +} + +// Remove the top VGUI menu, and bring up the next one +void TeamFortressViewport::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 TeamFortressViewport::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_CLASS || iId == MENU_INTRO || iId == MENU_CLASSHELP ) + return FALSE; + } + + return TRUE; +} + +//====================================================================================== +// TEAM MENU +//====================================================================================== +// Bring up the Team selection Menu +CMenuPanel* TeamFortressViewport::ShowTeamMenu() +{ + // Don't open menus in demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + return NULL; + + m_pTeamMenu->Reset(); + return m_pTeamMenu; +} + +void TeamFortressViewport::CreateTeamMenu() +{ + // Create the panel + m_pTeamMenu = new CTeamMenuPanel(100, false, 0, 0, ScreenWidth, ScreenHeight); + m_pTeamMenu->setParent( this ); + m_pTeamMenu->setVisible( false ); +} + +//====================================================================================== +// CLASS MENU +//====================================================================================== +// Bring up the Class selection Menu +CMenuPanel* TeamFortressViewport::ShowClassMenu() +{ + // Don't open menus in demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + return NULL; + + m_pClassMenu->Reset(); + return m_pClassMenu; +} + +void TeamFortressViewport::CreateClassMenu() +{ + // Create the panel + m_pClassMenu = new CClassMenuPanel(100, false, 0, 0, ScreenWidth, ScreenHeight); + m_pClassMenu->setParent(this); + m_pClassMenu->setVisible( false ); +} + +//====================================================================================== +//====================================================================================== +// SPECTATOR MENU +//====================================================================================== +// Spectator "Menu" explaining the Spectator buttons +void TeamFortressViewport::CreateSpectatorMenu() +{ + // Create the Panel + m_pSpectatorPanel = new SpectatorPanel(0, 0, ScreenWidth, ScreenHeight); + m_pSpectatorPanel->setParent(this); + m_pSpectatorPanel->setVisible(false); + m_pSpectatorPanel->Initialize(); +} + +//====================================================================================== +// UPDATE HUD SECTIONS +//====================================================================================== +// We've got an update on player info +// Recalculate any menus that use it. +void TeamFortressViewport::UpdateOnPlayerInfo() +{ + if (m_pTeamMenu) + m_pTeamMenu->Update(); + if (m_pClassMenu) + m_pClassMenu->Update(); + if (m_pScoreBoard) + m_pScoreBoard->Update(); +} + +void TeamFortressViewport::UpdateCursorState() +{ + // Need cursor if any VGUI window is up + if ( m_pSpectatorPanel->m_menuVisible || m_pCurrentMenu || m_pTeamMenu->isVisible() || m_pServerBrowser->isVisible() || GetClientVoiceMgr()->IsInSquelchMode() ) + { + g_iVisibleMouse = true; + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::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::scu_arrow) ); + return; + } + } + + // Don't reset mouse in demo playback + if ( !gEngfuncs.pDemoAPI->IsPlayingback() ) + { + IN_ResetMouse(); + } + + g_iVisibleMouse = false; + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::scu_none) ); +} + +void TeamFortressViewport::UpdateHighlights() +{ + if (m_pCurrentCommandMenu) + m_pCurrentCommandMenu->MakeVisible( NULL ); +} + +void TeamFortressViewport::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 TeamFortressViewport::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_StandardMenu ); + + if ( m_pClassMenu ) + { + m_pClassMenu->Update(); + } + + m_iCurrentTeamNumber = g_iTeamNumber; + } + + if ( g_iPlayerClass != m_iCurrentPlayerClass ) + { + UpdateCommandMenu( m_StandardMenu ); + + m_iCurrentPlayerClass = g_iPlayerClass; + } + + // See if the Spectator Menu needs to be update + if ( ( g_iUser1 != m_iUser1 || g_iUser2 != m_iUser2 ) || + ( m_flSpectatorPanelLastUpdated < gHUD.m_flTime ) ) + { + UpdateSpectatorPanel(); + } + + // 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 TeamFortressViewport::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 TeamFortressViewport::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 || iMenuID == MENU_CLASS) + { + // Escape gets you out of Team/Class menus if the Cancel button is visible + if ( keynum == K_ESCAPE ) + { + if ( (iMenuID == MENU_TEAM && g_iTeamNumber) || (iMenuID == MENU_CLASS && g_iPlayerClass) ) + { + 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 || iMenuID == MENU_CLASSHELP ) + { + HideTopMenu(); + 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 TeamFortressViewport::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( m_StandardMenu ); + + return 1; +} + +int TeamFortressViewport::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 TeamFortressViewport::MsgFunc_Feign(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iIsFeigning = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu( m_StandardMenu ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_Detpack(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iIsSettingDetpack = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu( m_StandardMenu ); + + return 1; +} + +int TeamFortressViewport::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 TeamFortressViewport::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'; + + // don't show MOTD for HLTV spectators + if ( m_iGotAllMOTD && !gEngfuncs.IsSpectateOnly() ) + { + ShowVGUIMenu( MENU_INTRO ); + } + + return 1; +} + +int TeamFortressViewport::MsgFunc_BuildSt( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iBuildState = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu( m_StandardMenu ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_RandomPC( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iRandomPC = READ_BYTE(); + + return 1; +} + +int TeamFortressViewport::MsgFunc_ServerName( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + strncpy( m_szServerName, READ_STRING(), MAX_SERVERNAME_LENGTH ); + + return 1; +} + +int TeamFortressViewport::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 playerclass = 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].playerclass = playerclass; + g_PlayerExtraInfo[cl].teamnumber = teamnumber; + + //Dont go bellow 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 TeamFortressViewport::MsgFunc_TeamScore( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + char *TeamName = READ_STRING(); + + // find the team matching the name + int i; + for ( 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 TeamFortressViewport::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 TeamFortressViewport::DeathMsg( int killer, int victim ) +{ + m_pScoreBoard->DeathMsg(killer,victim); +} + +int TeamFortressViewport::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 TeamFortressViewport::MsgFunc_AllowSpec( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iAllowSpectators = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu( m_StandardMenu ); + + // If the team menu is up, update it too + if (m_pTeamMenu) + m_pTeamMenu->Update(); + + return 1; +} diff --git a/hlsdk/cl_dll/vgui_TeamFortressViewport.h b/hlsdk/cl_dll/vgui_TeamFortressViewport.h new file mode 100644 index 00000000..c87258a6 --- /dev/null +++ b/hlsdk/cl_dll/vgui_TeamFortressViewport.h @@ -0,0 +1,1715 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef TEAMFORTRESSVIEWPORT_H +#define TEAMFORTRESSVIEWPORT_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 SpectatorPanel; +class CCommandMenu; +class CommandLabel; +class CommandButton; +class BuildButton; +class ClassButton; +class CMenuPanel; +class ServerBrowser; +class DragNDropPanel; +class CTransparentPanel; +class CClassMenuPanel; +class CTeamMenuPanel; + +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: + void LoadImage(const char * pImageName); + 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 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; + bool m_bFlat; + + // 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: + CommandButton(const char* text,int x,int y,int wide,int tall, bool bNoHighlight, bool bFlat); + // 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, bool bFlat ); + + 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 ); +}; + +class ColorButton : public CommandButton +{ +private: + + Color *ArmedColor; + Color *UnArmedColor; + + Color *ArmedBorderColor; + Color *UnArmedBorderColor; + +public: + ColorButton( const char* text,int x,int y,int wide,int tall, bool bNoHighlight, bool bFlat ) : + CommandButton( text, x, y, wide, tall, bNoHighlight, bFlat ) + { + ArmedColor = NULL; + UnArmedColor = NULL; + ArmedBorderColor = NULL; + UnArmedBorderColor = NULL; + } + + + virtual void paintBackground() + { + int r, g, b, a; + Color bgcolor; + + if ( isArmed() ) + { + // Highlight background + /* getBgColor(bgcolor); + bgcolor.getColor(r, g, b, a); + drawSetColor( r,g,b,a ); + drawFilledRect(0,0,_size[0],_size[1]);*/ + + if ( ArmedBorderColor ) + { + ArmedBorderColor->getColor( r, g, b, a); + drawSetColor( r, g, b, a ); + drawOutlinedRect(0,0,_size[0],_size[1]); + } + } + else + { + if ( UnArmedBorderColor ) + { + UnArmedBorderColor->getColor( r, g, b, a); + drawSetColor( r, g, b, a ); + drawOutlinedRect(0,0,_size[0],_size[1]); + } + } + } + void paint() + { + int r, g, b, a; + if ( isArmed() ) + { + if (ArmedColor) + { + ArmedColor->getColor(r, g, b, a); + setFgColor(r, g, b, a); + } + else + setFgColor( Scheme::sc_secondary2 ); + } + else + { + if (UnArmedColor) + { + UnArmedColor->getColor(r, g, b, a); + setFgColor(r, g, b, a); + } + else + setFgColor( Scheme::sc_primary1 ); + } + + Button::paint(); + } + + void setArmedColor ( int r, int g, int b, int a ) + { + ArmedColor = new Color( r, g, b, a ); + } + + void setUnArmedColor ( int r, int g, int b, int a ) + { + UnArmedColor = new Color( r, g, b, a ); + } + + void setArmedBorderColor ( int r, int g, int b, int a ) + { + ArmedBorderColor = new Color( r, g, b, a ); + } + + void setUnArmedBorderColor ( int r, int g, int b, int a ) + { + UnArmedBorderColor = new Color( r, g, b, a ); + } +}; + +class SpectButton : public CommandButton +{ +private: + +public: + SpectButton( int iPlayerClass, const char* text,int x,int y,int wide,int tall ) : + CommandButton( text, x, y, wide, tall, false) + { + Init(); + + setText( text ); + } + + virtual void paintBackground() + { + if ( isArmed()) + { + drawSetColor( 143,143, 54, 125 ); + drawFilledRect( 5, 0,_size[0] - 5,_size[1]); + } + } + + virtual void paint() + { + + if ( isArmed() ) + { + setFgColor( 194, 202, 54, 0 ); + } + else + { + setFgColor( 143, 143, 54, 15 ); + } + + Button::paint(); + } +}; +//============================================================ +// 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; + + // opens menu from top to bottom (0 = default), or from bottom to top (1)? + int m_iDirection; +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; + m_iDirection = 0; + } + + CCommandMenu( CCommandMenu *pParentMenu, int direction, 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; + m_iDirection = direction; + } + + float m_flButtonSizeY; + int m_iSpectCmdMenu; + 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 GetDirection() { return m_iDirection; }; + int GetNumButtons() { return m_iButtons; }; + CommandButton *FindButtonWithSubmenu( CCommandMenu *pSubMenu ); + + void ClearButtonsOfArmedState( void ); + + + bool KeyInput( int keyNum ); + + virtual void paintBackground(); +}; + +//============================================================================== +class TeamFortressViewport : 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; + float m_flSpectatorPanelLastUpdated; + 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 CreateClassMenu( void ); + CMenuPanel* ShowClassMenu( void ); + void CreateSpectatorMenu( 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; + + int m_iAllowSpectators; + + // Data for specific sections of the Command Menu + int m_iValidClasses[5]; + int m_iIsFeigning; + int m_iIsSettingDetpack; + int m_iNumberOfTeams; + int m_iBuildState; + int m_iRandomPC; + char m_sTeamNames[5][MAX_TEAMNAME_SIZE]; + + // Localisation strings + char m_sDetpackStrings[3][MAX_BUTTON_SIZE]; + + char m_sMapName[64]; +public: + TeamFortressViewport(int x,int y,int wide,int tall); + void Initialize( void ); + + int CreateCommandMenu( char * menuFile, int direction, int yOffset, bool flatDesign, float flButtonSizeX, float flButtonSizeY, int xOffset ); + void CreateScoreBoard( void ); + void CreateServerBrowser( void ); + CommandButton * CreateCustomButton( char *pButtonText, char * pButtonName, int iYOffset ); + CCommandMenu * CreateDisguiseSubmenu( CommandButton *pButton, CCommandMenu *pParentMenu, const char *commandText, int iYOffset, int iXOffset = 0 ); + + void UpdateCursorState( void ); + void UpdateCommandMenu(int menuIndex); + void UpdateOnPlayerInfo( void ); + void UpdateHighlights( void ); + void UpdateSpectatorPanel( void ); + + int KeyInput( int down, int keynum, const char *pszCurrentBinding ); + void InputPlayerSpecial( void ); + void GetAllPlayersInfo( void ); + void DeathMsg( int killer, int victim ); + + void ShowCommandMenu(int menuIndex); + 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, int iYOffset, int iXOffset = 0 ); + + // Data Handlers + int GetValidClasses(int iTeam) { return m_iValidClasses[iTeam]; }; + int GetNumberOfTeams() { return m_iNumberOfTeams; }; + int GetIsFeigning() { return m_iIsFeigning; }; + int GetIsSettingDetpack() { return m_iIsSettingDetpack; }; + 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_Feign(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_Detpack(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_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 ); + + // 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; + int m_StandardMenu; // indexs in m_pCommandMenus + int m_SpectatorOptionsMenu; + int m_SpectatorCameraMenu; + CClassMenuPanel *m_pClassMenu; + ScorePanel *m_pScoreBoard; + SpectatorPanel * m_pSpectatorPanel; + 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_CLASSDESC 2 +#define SHOW_MOTD 3 +#define SHOW_SPECHELP 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 CMenuHandler_ToggleCvar : public ActionSignal +{ +private: + struct cvar_s * m_cvar; + +public: + CMenuHandler_ToggleCvar( char * cvarname ) + { + m_cvar = gEngfuncs.pfnGetCvarPointer( cvarname ); + } + + virtual void actionPerformed(Panel* panel) + { + if ( m_cvar->value ) + m_cvar->value = 0.0f; + else + m_cvar->value = 1.0f; + + gViewPort->UpdateSpectatorPanel(); + } + + +}; +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 FeignButton : public CommandButton +{ +private: + int m_iFeignState; +public: + FeignButton( int iState, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + m_iFeignState = iState; + } + + virtual int IsNotValid() + { + // Only visible for spies + if (g_iPlayerClass != PC_SPY) + return true; + + if (m_iFeignState == gViewPort->GetIsFeigning()) + 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; + } +}; + +class DetpackButton : public CommandButton +{ +private: + int m_iDetpackState; +public: + DetpackButton( int iState, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + m_iDetpackState = iState; + } + + virtual int IsNotValid() + { + // Only visible for demomen + if (g_iPlayerClass != PC_DEMOMAN) + return true; + + if (m_iDetpackState == gViewPort->GetIsSettingDetpack()) + 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, bool flat ) : + CommandButton( text, x, y, wide, tall, false, flat ), m_iTeamNum(iTeamNum) {} + + virtual int IsNotValid() + { + if ( g_iTeamNumber != m_iTeamNum ) + return true; + + return CommandButton::IsNotValid(); + } + + virtual void paintBackground() + { + if ( isArmed() ) + { + drawSetColor( 143,143, 54, 125 ); + drawFilledRect( 5, 0,_size[0] - 5,_size[1]); + } + } + + virtual void paint( void ) + { + if ( isArmed() ) + { + setFgColor( 194, 202, 54, 0 ); + } + else + { + setFgColor( 143, 143, 54, 15 ); + } + + Button::paint(); + } +}; + +//----------------------------------------------------------------------------- +// Purpose: CommandButton which is only displayed if the player is on team X +//----------------------------------------------------------------------------- +class ToggleCommandButton : public CommandButton, public InputSignal +{ +private: + struct cvar_s * m_cvar; + CImageLabel * pLabelOn; + CImageLabel * pLabelOff; + + +public: + ToggleCommandButton( const char* cvarname, const char* text,int x,int y,int wide,int tall, bool flat ) : + CommandButton( text, x, y, wide, tall, false, flat ) + { + m_cvar = gEngfuncs.pfnGetCvarPointer( cvarname ); + + // Put a > to show it's a submenu + pLabelOn = new CImageLabel( "checked", 0, 0 ); + pLabelOn->setParent(this); + pLabelOn->addInputSignal(this); + + pLabelOff = new CImageLabel( "unchecked", 0, 0 ); + pLabelOff->setParent(this); + pLabelOff->setEnabled(true); + pLabelOff->addInputSignal(this); + + int textwide, texttall; + getTextSize( textwide, texttall); + + // Reposition + pLabelOn->setPos( textwide, (tall - pLabelOn->getTall()) / 2 ); + + pLabelOff->setPos( textwide, (tall - pLabelOff->getTall()) / 2 ); + + // Set text color to orange + setFgColor(Scheme::sc_primary1); + } + + virtual void cursorEntered(Panel* panel) + { + CommandButton::cursorEntered(); + } + + virtual void cursorExited(Panel* panel) + { + CommandButton::cursorExited(); + } + + virtual void mousePressed(MouseCode code,Panel* panel) + { + doClick(); + }; + + virtual void cursorMoved(int x,int y,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) {}; + + virtual void paint( void ) + { + if ( !m_cvar ) + { + pLabelOff->setVisible(false); + pLabelOn->setVisible(false); + } + else if ( m_cvar->value ) + { + pLabelOff->setVisible(false); + pLabelOn->setVisible(true); + } + else + { + pLabelOff->setVisible(true); + pLabelOn->setVisible(false); + } + + CommandButton::paint(); + + } +}; +class SpectToggleButton : public CommandButton, public InputSignal +{ +private: + struct cvar_s * m_cvar; + CImageLabel * pLabelOn; + +public: + SpectToggleButton( const char* cvarname, const char* text,int x,int y,int wide,int tall, bool flat ) : + CommandButton( text, x, y, wide, tall, false, flat ) + { + m_cvar = gEngfuncs.pfnGetCvarPointer( cvarname ); + + // Put a > to show it's a submenu + pLabelOn = new CImageLabel( "checked", 0, 0 ); + pLabelOn->setParent(this); + pLabelOn->addInputSignal(this); + + + int textwide, texttall; + getTextSize( textwide, texttall); + + // Reposition + pLabelOn->setPos( textwide, (tall - pLabelOn->getTall()) / 2 ); + } + + virtual void cursorEntered(Panel* panel) + { + CommandButton::cursorEntered(); + } + + virtual void cursorExited(Panel* panel) + { + CommandButton::cursorExited(); + } + + virtual void mousePressed(MouseCode code,Panel* panel) + { + doClick(); + }; + + virtual void cursorMoved(int x,int y,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) {}; + + virtual void paintBackground() + { + if ( isArmed() ) + { + drawSetColor( 143,143, 54, 125 ); + drawFilledRect( 5, 0,_size[0] - 5,_size[1]); + } + } + + virtual void paint( void ) + { + if ( isArmed() ) + { + setFgColor( 194, 202, 54, 0 ); + } + else + { + setFgColor( 143, 143, 54, 15 ); + } + + if ( !m_cvar ) + { + pLabelOn->setVisible(false); + } + else if ( m_cvar->value ) + { + pLabelOn->setVisible(true); + } + else + { + pLabelOn->setVisible(false); + } + + Button::paint(); + } +}; + +/* +class SpectToggleButton : public ToggleCommandButton +{ +private: + struct cvar_s * m_cvar; + CImageLabel * pLabelOn; + CImageLabel * pLabelOff; + +public: + + SpectToggleButton( const char* cvarname, const char* text,int x,int y,int wide,int tall, bool flat ) : + ToggleCommandButton( cvarname, text, x, y, wide, tall, flat, TRUE ) + { + m_cvar = gEngfuncs.pfnGetCvarPointer( cvarname ); + + // Put a > to show it's a submenu + pLabelOn = new CImageLabel( "checked", 0, 0 ); + pLabelOn->setParent(this); + pLabelOn->addInputSignal(this); + + pLabelOff = new CImageLabel( "unchecked", 0, 0 ); + pLabelOff->setParent(this); + pLabelOff->setEnabled(true); + pLabelOff->addInputSignal(this); + + int textwide, texttall; + getTextSize( textwide, texttall); + + // Reposition + pLabelOn->setPos( textwide, (tall - pLabelOn->getTall()) / 2 ); + + pLabelOff->setPos( textwide, (tall - pLabelOff->getTall()) / 2 ); + + // Set text color to orange + setFgColor(Scheme::sc_primary1); + } + + virtual void paintBackground() + { + if ( isArmed()) + { + drawSetColor( 143,143, 54, 125 ); + drawFilledRect( 5, 0,_size[0] - 5,_size[1]); + } + } + + virtual void paint() + { + + if ( isArmed() ) + { + setFgColor( 194, 202, 54, 0 ); + } + else + { + setFgColor( 143, 143, 54, 15 ); + } + + if ( !m_cvar ) + { + pLabelOff->setVisible(false); + pLabelOn->setVisible(false); + } + else if ( m_cvar->value ) + { + pLabelOff->setVisible(false); + pLabelOn->setVisible(true); + } + else + { + pLabelOff->setVisible(true); + pLabelOn->setVisible(false); + } + + Button::paint(); + } +}; +*/ +//============================================================ +// 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]); + } + } +}; + +//================================================================ +// 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 CClassMenuPanel : public CMenuPanel +{ +private: + CTransparentPanel *m_pClassInfoPanel[PC_LASTCLASS]; + Label *m_pPlayers[PC_LASTCLASS]; + ClassButton *m_pButtons[PC_LASTCLASS]; + CommandButton *m_pCancelButton; + ScrollPanel *m_pScrollPanel; + + CImageLabel *m_pClassImages[MAX_TEAMS][PC_LASTCLASS]; + + int m_iCurrentInfo; + + enum { STRLENMAX_PLAYERSONTEAM = 128 }; + char m_sPlayersOnTeamString[STRLENMAX_PLAYERSONTEAM]; + +public: + CClassMenuPanel(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 Initialize( void ); + + virtual void Reset( void ) + { + CMenuPanel::Reset(); + m_iCurrentInfo = 0; + } +}; + +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; + } +}; + +//========================================================= +// Specific Menus to handle old HUD sections +class CHealthPanel : public DragNDropPanel +{ +private: + BitmapTGA *m_pHealthTGA; + Label *m_pHealthLabel; +public: + CHealthPanel(int x,int y,int wide,int tall) : DragNDropPanel(x,y,wide,tall) + { + // Load the Health icon + FileInputStream* fis = new FileInputStream( GetVGUITGAName("%d_hud_health"), false); + m_pHealthTGA = new BitmapTGA(fis,true); + fis->close(); + + // Create the Health Label + int iXSize,iYSize; + m_pHealthTGA->getSize(iXSize,iYSize); + m_pHealthLabel = new Label("",0,0,iXSize,iYSize); + m_pHealthLabel->setImage(m_pHealthTGA); + m_pHealthLabel->setParent(this); + + // Set panel dimension + // Shouldn't be needed once Billy's fized setImage not recalculating the size + //setSize( iXSize + 100, gHUD.m_iFontHeight + 10 ); + //m_pHealthLabel->setPos( 10, (getTall() - iYSize) / 2 ); + } + + virtual void paintBackground() + { + } + + void paint() + { + // Get the paint color + int r,g,b,a; + // Has health changed? Flash the health # + if (gHUD.m_Health.m_fFade) + { + gHUD.m_Health.m_fFade -= (gHUD.m_flTimeDelta * 20); + if (gHUD.m_Health.m_fFade <= 0) + { + a = MIN_ALPHA; + gHUD.m_Health.m_fFade = 0; + } + + // Fade the health number back to dim + a = MIN_ALPHA + (gHUD.m_Health.m_fFade/FADE_TIME) * 128; + } + else + a = MIN_ALPHA; + + gHUD.m_Health.GetPainColor( r, g, b ); + ScaleColors(r, g, b, a ); + + // If health is getting low, make it bright red + if (gHUD.m_Health.m_iHealth <= 15) + a = 255; + + int iXSize,iYSize, iXPos, iYPos; + m_pHealthTGA->getSize(iXSize,iYSize); + m_pHealthTGA->getPos(iXPos, iYPos); + + // Paint the player's health + int x = gHUD.DrawHudNumber( iXPos + iXSize + 5, iYPos + 5, DHN_3DIGITS | DHN_DRAWZERO, gHUD.m_Health.m_iHealth, r, g, b); + + // Draw the vertical line + int HealthWidth = gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left; + x += HealthWidth / 2; + FillRGBA(x, iYPos + 5, HealthWidth / 10, gHUD.m_iFontHeight, 255, 160, 0, a); + } +}; + +#endif diff --git a/hlsdk/cl_dll/vgui_int.cpp b/hlsdk/cl_dll/vgui_int.cpp new file mode 100644 index 00000000..7d7bdaa4 --- /dev/null +++ b/hlsdk/cl_dll/vgui_int.cpp @@ -0,0 +1,128 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include"vgui_int.h" +#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 "vgui_TeamFortressViewport.h" +#include "vgui_ControlConfigPanel.h" + +namespace +{ + +class TexturePanel : public Panel , public ActionSignal +{ +private: + int _bindIndex; + TextEntry* _textEntry; +public: + TexturePanel() : Panel(0,0,256,276) + { + _bindIndex=2700; + _textEntry=new TextEntry("2700",0,0,128,20); + _textEntry->setParent(this); + _textEntry->addActionSignal(this); + } +public: + virtual bool isWithin(int x,int y) + { + return _textEntry->isWithin(x,y); + } +public: + virtual void actionPerformed(Panel* panel) + { + char buf[256]; + _textEntry->getText(0,buf,256); + sscanf(buf,"%d",&_bindIndex); + } +protected: + virtual void paintBackground() + { + Panel::paintBackground(); + + int wide,tall; + getPaintSize(wide,tall); + + drawSetColor(0,0,255,0); + drawSetTexture(_bindIndex); + drawTexturedRect(0,19,257,257); + } + +}; + +} + +using namespace vgui; + +void VGui_ViewportPaintBackground(int extents[4]) +{ + gEngfuncs.VGui_ViewportPaintBackground(extents); +} + +void* VGui_GetPanel() +{ + return (Panel*)gEngfuncs.VGui_GetPanel(); +} + +void VGui_Startup() +{ + Panel* root=(Panel*)VGui_GetPanel(); + root->setBgColor(128,128,0,0); + //root->setNonPainted(false); + //root->setBorder(new LineBorder()); + root->setLayout(new BorderLayout(0)); + + + //root->getSurfaceBase()->setEmulatedCursorVisible(true); + + if (gViewPort != NULL) + { +// root->removeChild(gViewPort); + + // free the memory +// delete gViewPort; +// gViewPort = NULL; + + gViewPort->Initialize(); + } + else + { + gViewPort = new TeamFortressViewport(0,0,root->getWide(),root->getTall()); + gViewPort->setParent(root); + } + + /* + TexturePanel* texturePanel=new TexturePanel(); + texturePanel->setParent(gViewPort); + */ + +} + +void VGui_Shutdown() +{ + delete gViewPort; + gViewPort = NULL; +} + + + + + diff --git a/hlsdk/cl_dll/vgui_int.h b/hlsdk/cl_dll/vgui_int.h new file mode 100644 index 00000000..b5302b28 --- /dev/null +++ b/hlsdk/cl_dll/vgui_int.h @@ -0,0 +1,21 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_INT_H +#define VGUI_INT_H + +extern "C" +{ +void VGui_Startup(); +void VGui_Shutdown(); + +//Only safe to call from inside subclass of Panel::paintBackground +void VGui_ViewportPaintBackground(int extents[4]); +} + + +#endif \ No newline at end of file diff --git a/hlsdk/cl_dll/vgui_teammenu.cpp b/hlsdk/cl_dll/vgui_teammenu.cpp new file mode 100644 index 00000000..a1370791 --- /dev/null +++ b/hlsdk/cl_dll/vgui_teammenu.cpp @@ -0,0 +1,393 @@ +//=========== (C) Copyright 1996-2002 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: TFC Team Menu +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "vgui_int.h" +#include "VGUI_Font.h" +#include "VGUI_ScrollPanel.h" +#include "VGUI_TextImage.h" + +#include "hud.h" +#include "cl_util.h" +#include "vgui_TeamFortressViewport.h" + +// Team Menu Dimensions +#define TEAMMENU_TITLE_X XRES(40) +#define TEAMMENU_TITLE_Y YRES(32) +#define TEAMMENU_TOPLEFT_BUTTON_X XRES(40) +#define TEAMMENU_TOPLEFT_BUTTON_Y YRES(80) +#define TEAMMENU_BUTTON_SIZE_X XRES(124) +#define TEAMMENU_BUTTON_SIZE_Y YRES(24) +#define TEAMMENU_BUTTON_SPACER_Y YRES(8) +#define TEAMMENU_WINDOW_X XRES(176) +#define TEAMMENU_WINDOW_Y YRES(80) +#define TEAMMENU_WINDOW_SIZE_X XRES(424) +#define TEAMMENU_WINDOW_SIZE_Y YRES(312) +#define TEAMMENU_WINDOW_TITLE_X XRES(16) +#define TEAMMENU_WINDOW_TITLE_Y YRES(16) +#define TEAMMENU_WINDOW_TEXT_X XRES(16) +#define TEAMMENU_WINDOW_TEXT_Y YRES(48) +#define TEAMMENU_WINDOW_TEXT_SIZE_Y YRES(178) +#define TEAMMENU_WINDOW_INFO_X XRES(16) +#define TEAMMENU_WINDOW_INFO_Y YRES(234) + +// Creation +CTeamMenuPanel::CTeamMenuPanel(int iTrans, int iRemoveMe, int x,int y,int wide,int tall) : CMenuPanel(iTrans, iRemoveMe, x,y,wide,tall) +{ + // Get the scheme used for the Titles + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + + // schemes + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle( "Title Font" ); + SchemeHandle_t hTeamWindowText = pSchemes->getSchemeHandle( "Briefing Text" ); + SchemeHandle_t hTeamInfoText = pSchemes->getSchemeHandle( "Team Info Text" ); + + // get the Font used for the Titles + Font *pTitleFont = pSchemes->getFont( hTitleScheme ); + int r, g, b, a; + + // Create the title + Label *pLabel = new Label( "", TEAMMENU_TITLE_X, TEAMMENU_TITLE_Y ); + pLabel->setParent( this ); + pLabel->setFont( pTitleFont ); + pSchemes->getFgColor( hTitleScheme, r, g, b, a ); + pLabel->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hTitleScheme, r, g, b, a ); + pLabel->setBgColor( r, g, b, a ); + pLabel->setContentAlignment( vgui::Label::a_west ); + pLabel->setText(gHUD.m_TextMessage.BufferedLocaliseTextString("#Title_SelectYourTeam")); + + // 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 )) ); + + // Create the Map Name Label + m_pMapTitle = new Label( "", TEAMMENU_WINDOW_TITLE_X, TEAMMENU_WINDOW_TITLE_Y ); + m_pMapTitle->setFont( pTitleFont ); + m_pMapTitle->setParent( m_pTeamWindow ); + pSchemes->getFgColor( hTitleScheme, r, g, b, a ); + m_pMapTitle->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hTitleScheme, r, g, b, a ); + m_pMapTitle->setBgColor( r, g, b, a ); + m_pMapTitle->setContentAlignment( vgui::Label::a_west ); + + // Create the Scroll panel + m_pScrollPanel = new CTFScrollPanel( TEAMMENU_WINDOW_TEXT_X, TEAMMENU_WINDOW_TEXT_Y, TEAMMENU_WINDOW_SIZE_X - (TEAMMENU_WINDOW_TEXT_X * 2), TEAMMENU_WINDOW_TEXT_SIZE_Y ); + m_pScrollPanel->setParent(m_pTeamWindow); + m_pScrollPanel->setScrollBarVisible(false, false); + + // Create the Map Briefing panel + m_pBriefing = new TextPanel("", 0,0, TEAMMENU_WINDOW_SIZE_X - TEAMMENU_WINDOW_TEXT_X, TEAMMENU_WINDOW_TEXT_SIZE_Y ); + m_pBriefing->setParent( m_pScrollPanel->getClient() ); + m_pBriefing->setFont( pSchemes->getFont(hTeamWindowText) ); + pSchemes->getFgColor( hTeamWindowText, r, g, b, a ); + m_pBriefing->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hTeamWindowText, r, g, b, a ); + m_pBriefing->setBgColor( r, g, b, a ); + + m_pBriefing->setText( gHUD.m_TextMessage.BufferedLocaliseTextString("#Map_Description_not_available") ); + + // Team Menu buttons + for (int i = 1; i <= 5; i++) + { + char sz[256]; + + int iYPos = TEAMMENU_TOPLEFT_BUTTON_Y + ( (TEAMMENU_BUTTON_SIZE_Y + TEAMMENU_BUTTON_SPACER_Y) * i ); + + // Team button + m_pButtons[i] = new CommandButton( "", TEAMMENU_TOPLEFT_BUTTON_X, iYPos, TEAMMENU_BUTTON_SIZE_X, TEAMMENU_BUTTON_SIZE_Y, true); + m_pButtons[i]->setParent( this ); + m_pButtons[i]->setContentAlignment( vgui::Label::a_west ); + m_pButtons[i]->setVisible( false ); + + // AutoAssign button uses special case + if (i == 5) + { + m_pButtons[5]->setBoundKey( '5' ); + m_pButtons[5]->setText( gHUD.m_TextMessage.BufferedLocaliseTextString("#Team_AutoAssign") ); + m_pButtons[5]->setVisible( true ); + } + + // Create the Signals + sprintf(sz, "jointeam %d", i); + m_pButtons[i]->addActionSignal( new CMenuHandler_StringCommandWatch( sz, true ) ); + m_pButtons[i]->addInputSignal( new CHandler_MenuButtonOver(this, i) ); + + // Create the Team Info panel + m_pTeamInfoPanel[i] = new TextPanel("", TEAMMENU_WINDOW_INFO_X, TEAMMENU_WINDOW_INFO_Y, TEAMMENU_WINDOW_SIZE_X - TEAMMENU_WINDOW_INFO_X, TEAMMENU_WINDOW_SIZE_X - TEAMMENU_WINDOW_INFO_Y ); + m_pTeamInfoPanel[i]->setParent( m_pTeamWindow ); + m_pTeamInfoPanel[i]->setFont( pSchemes->getFont(hTeamInfoText) ); + m_pTeamInfoPanel[i]->setFgColor( iTeamColors[i % iNumberOfTeamColors][0], + iTeamColors[i % iNumberOfTeamColors][1], + iTeamColors[i % iNumberOfTeamColors][2], + 0 ); + m_pTeamInfoPanel[i]->setBgColor( 0,0,0, 255 ); + } + + // Create the Cancel button + m_pCancelButton = new CommandButton( CHudTextMessage::BufferedLocaliseTextString( "#Menu_Cancel" ), TEAMMENU_TOPLEFT_BUTTON_X, 0, TEAMMENU_BUTTON_SIZE_X, TEAMMENU_BUTTON_SIZE_Y); + m_pCancelButton->setParent( this ); + m_pCancelButton->addActionSignal( new CMenuHandler_TextWindow(HIDE_TEXTWINDOW) ); + + // Create the Spectate button + m_pSpectateButton = new SpectateButton( CHudTextMessage::BufferedLocaliseTextString( "#Menu_Spectate" ), TEAMMENU_TOPLEFT_BUTTON_X, 0, TEAMMENU_BUTTON_SIZE_X, TEAMMENU_BUTTON_SIZE_Y, true); + m_pSpectateButton->setParent( this ); + m_pSpectateButton->addActionSignal( new CMenuHandler_StringCommand( "spectate", true ) ); + m_pSpectateButton->setBoundKey( '6' ); + m_pSpectateButton->addInputSignal( new CHandler_MenuButtonOver(this, 6) ); + + Initialize(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called each time a new level is started. +//----------------------------------------------------------------------------- +void CTeamMenuPanel::Initialize( void ) +{ + m_bUpdatedMapName = false; + m_iCurrentInfo = 0; + m_pScrollPanel->setScrollValue( 0, 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Called everytime the Team Menu is displayed +//----------------------------------------------------------------------------- +void CTeamMenuPanel::Update( void ) +{ + int iYPos = TEAMMENU_TOPLEFT_BUTTON_Y; + + // Set the team buttons + for (int i = 1; i <= 4; i++) + { + if (m_pButtons[i]) + { + if ( i <= gViewPort->GetNumberOfTeams() ) + { + m_pButtons[i]->setText( gViewPort->GetTeamName(i) ); + + // bound key replacement + char sz[32]; + sprintf( sz, "%d", i ); + m_pButtons[i]->setBoundKey( sz[0] ); + + m_pButtons[i]->setVisible( true ); + m_pButtons[i]->setPos( TEAMMENU_TOPLEFT_BUTTON_X, iYPos ); + iYPos += TEAMMENU_BUTTON_SIZE_Y + TEAMMENU_BUTTON_SPACER_Y; + + // Start with the first option up + if (!m_iCurrentInfo) + SetActiveInfo( i ); + + char szPlayerList[ (MAX_PLAYER_NAME_LENGTH + 3) * 31 ]; // name + ", " + strcpy(szPlayerList, "\n"); + // Update the Team Info + // Now count the number of teammembers of this class + int iTotal = 0; + for ( int j = 1; j < MAX_PLAYERS; j++ ) + { + if ( g_PlayerInfoList[j].name == NULL ) + continue; // empty player slot, skip + if ( g_PlayerInfoList[j].thisplayer ) + continue; // skip this player + if ( g_PlayerExtraInfo[j].teamnumber != i ) + continue; // skip over players in other teams + + iTotal++; + if (iTotal > 1) + strncat( szPlayerList, ", ", sizeof(szPlayerList) - strlen(szPlayerList) ); + strncat( szPlayerList, g_PlayerInfoList[j].name, sizeof(szPlayerList) - strlen(szPlayerList) ); + szPlayerList[ sizeof(szPlayerList) - 1 ] = '\0'; + } + + if (iTotal > 0) + { + // Set the text of the info Panel + char szText[ ((MAX_PLAYER_NAME_LENGTH + 3) * 31) + 256 ]; + if (iTotal == 1) + sprintf(szText, "%s: %d Player (%d points)", gViewPort->GetTeamName(i), iTotal, g_TeamInfo[i].frags ); + else + sprintf(szText, "%s: %d Players (%d points)", gViewPort->GetTeamName(i), iTotal, g_TeamInfo[i].frags ); + strncat( szText, szPlayerList, sizeof(szText) - strlen(szText) ); + szText[ sizeof(szText) - 1 ] = '\0'; + + m_pTeamInfoPanel[i]->setText( szText ); + } + else + { + m_pTeamInfoPanel[i]->setText( "" ); + } + } + else + { + // Hide the button (may be visible from previous maps) + m_pButtons[i]->setVisible( false ); + } + } + } + + // Move the AutoAssign button into place + m_pButtons[5]->setPos( TEAMMENU_TOPLEFT_BUTTON_X, iYPos ); + iYPos += TEAMMENU_BUTTON_SIZE_Y + TEAMMENU_BUTTON_SPACER_Y; + + // Spectate button + if (m_pSpectateButton->IsNotValid()) + { + m_pSpectateButton->setVisible( false ); + } + else + { + m_pSpectateButton->setPos( TEAMMENU_TOPLEFT_BUTTON_X, iYPos ); + m_pSpectateButton->setVisible( true ); + iYPos += TEAMMENU_BUTTON_SIZE_Y + TEAMMENU_BUTTON_SPACER_Y; + } + + // If the player is already in a team, make the cancel button visible + if ( g_iTeamNumber ) + { + m_pCancelButton->setPos( TEAMMENU_TOPLEFT_BUTTON_X, iYPos ); + iYPos += TEAMMENU_BUTTON_SIZE_Y + TEAMMENU_BUTTON_SPACER_Y; + m_pCancelButton->setVisible( true ); + } + else + { + m_pCancelButton->setVisible( false ); + } + + // Set the Map Title + if (!m_bUpdatedMapName) + { + const char *level = gEngfuncs.pfnGetLevelName(); + if (level && level[0]) + { + char sz[256]; + char szTitle[256]; + char *ch; + + // Update the level name + strcpy( sz, level ); + ch = strchr( sz, '/' ); + if (!ch) + ch = strchr( sz, '\\' ); + strcpy( szTitle, ch+1 ); + ch = strchr( szTitle, '.' ); + *ch = '\0'; + m_pMapTitle->setText( szTitle ); + *ch = '.'; + + // Update the map briefing + strcpy( sz, level ); + ch = strchr( sz, '.' ); + *ch = '\0'; + strcat( sz, ".txt" ); + char *pfile = (char*)gEngfuncs.COM_LoadFile( sz, 5, NULL ); + if (pfile) + { + m_pBriefing->setText( pfile ); + + // Get the total size of the Briefing text and resize the text panel + int iXSize, iYSize; + m_pBriefing->getTextImage()->getTextSize( iXSize, iYSize ); + m_pBriefing->setSize( iXSize, iYSize ); + gEngfuncs.COM_FreeFile( pfile ); + } + + m_bUpdatedMapName = true; + } + } + + m_pScrollPanel->validate(); +} + +//===================================== +// Key inputs +bool CTeamMenuPanel::SlotInput( int iSlot ) +{ + // Check for AutoAssign + if ( iSlot == 5) + { + m_pButtons[5]->fireActionSignal(); + return true; + } + + // Spectate + if ( iSlot == 6) + { + m_pSpectateButton->fireActionSignal(); + return true; + } + + // Otherwise, see if a particular team is selectable + if ( (iSlot < 1) || (iSlot > gViewPort->GetNumberOfTeams()) ) + return false; + if ( !m_pButtons[ iSlot ] ) + return false; + + // Is the button pushable? + if ( m_pButtons[ iSlot ]->isVisible() ) + { + m_pButtons[ iSlot ]->fireActionSignal(); + return true; + } + + return false; +} + +//====================================== +// Update the Team menu before opening it +void CTeamMenuPanel::Open( void ) +{ + Update(); + CMenuPanel::Open(); +} + +void CTeamMenuPanel::paintBackground() +{ + // make sure we get the map briefing up + if ( !m_bUpdatedMapName ) + Update(); + + CMenuPanel::paintBackground(); +} + +//====================================== +// Mouse is over a team button, bring up the class info +void CTeamMenuPanel::SetActiveInfo( int iInput ) +{ + // Remove all the Info panels and bring up the specified one + m_pSpectateButton->setArmed( false ); + for (int i = 1; i <= 5; i++) + { + m_pButtons[i]->setArmed( false ); + m_pTeamInfoPanel[i]->setVisible( false ); + } + + // 6 is Spectate + if (iInput == 6) + { + m_pSpectateButton->setArmed( true ); + } + else + { + m_pButtons[iInput]->setArmed( true ); + m_pTeamInfoPanel[iInput]->setVisible( true ); + } + + m_iCurrentInfo = iInput; + + m_pScrollPanel->validate(); +} diff --git a/hlsdk/cl_dll/view.cpp b/hlsdk/cl_dll/view.cpp new file mode 100644 index 00000000..02af24a3 --- /dev/null +++ b/hlsdk/cl_dll/view.cpp @@ -0,0 +1,1773 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// view/refresh setup functions + +#include "hud.h" +#include "cl_util.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" + +#include "entity_state.h" +#include "cl_entity.h" +#include "ref_params.h" +#include "in_defs.h" // PITCH YAW ROLL +#include "pm_movevars.h" +#include "pm_shared.h" +#include "pm_defs.h" +#include "event_api.h" +#include "pmtrace.h" +#include "screenfade.h" +#include "shake.h" +#include "hltv.h" + +// Spectator Mode +extern "C" +{ + float vecNewViewAngles[3]; + int iHasNewViewAngles; + float vecNewViewOrigin[3]; + int iHasNewViewOrigin; + int iIsSpectator; +} + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +extern "C" +{ + int CL_IsThirdPerson( void ); + void CL_CameraOffset( float *ofs ); + + void DLLEXPORT V_CalcRefdef( struct ref_params_s *pparams ); + + void PM_ParticleLine( float *start, float *end, int pcolor, float life, float vert); + int PM_GetVisEntInfo( int ent ); + int PM_GetPhysEntInfo( int ent ); + void InterpolateAngles( float * start, float * end, float * output, float frac ); + void NormalizeAngles( float * angles ); + float Distance(const float * v1, const float * v2); + float AngleBetweenVectors( const float * v1, const float * v2 ); + + float vJumpOrigin[3]; + float vJumpAngles[3]; +} + +void V_DropPunchAngle ( float frametime, float *ev_punchangle ); +void VectorAngles( const float *forward, float *angles ); + +#include "r_studioint.h" +#include "com_model.h" + +extern engine_studio_api_t IEngineStudio; + +/* +The view is allowed to move slightly from it's true position for bobbing, +but if it exceeds 8 pixels linear distance (spherical, not box), the list of +entities sent from the server may not include everything in the pvs, especially +when crossing a water boudnary. +*/ + +extern cvar_t *cl_forwardspeed; +extern cvar_t *chase_active; +extern cvar_t *scr_ofsx, *scr_ofsy, *scr_ofsz; +extern cvar_t *cl_vsmoothing; + +#define CAM_MODE_RELAX 1 +#define CAM_MODE_FOCUS 2 + +vec3_t v_origin, v_angles, v_cl_angles, v_sim_org, v_lastAngles; +float v_frametime, v_lastDistance; +float v_cameraRelaxAngle = 5.0f; +float v_cameraFocusAngle = 35.0f; +int v_cameraMode = CAM_MODE_FOCUS; +qboolean v_resetCamera = 1; + +vec3_t ev_punchangle; + +cvar_t *scr_ofsx; +cvar_t *scr_ofsy; +cvar_t *scr_ofsz; + +cvar_t *v_centermove; +cvar_t *v_centerspeed; + +cvar_t *cl_bobcycle; +cvar_t *cl_bob; +cvar_t *cl_bobup; +cvar_t *cl_waterdist; +cvar_t *cl_chasedist; + +// These cvars are not registered (so users can't cheat), so set the ->value field directly +// Register these cvars in V_Init() if needed for easy tweaking +cvar_t v_iyaw_cycle = {"v_iyaw_cycle", "2", 0, 2}; +cvar_t v_iroll_cycle = {"v_iroll_cycle", "0.5", 0, 0.5}; +cvar_t v_ipitch_cycle = {"v_ipitch_cycle", "1", 0, 1}; +cvar_t v_iyaw_level = {"v_iyaw_level", "0.3", 0, 0.3}; +cvar_t v_iroll_level = {"v_iroll_level", "0.1", 0, 0.1}; +cvar_t v_ipitch_level = {"v_ipitch_level", "0.3", 0, 0.3}; + +float v_idlescale; // used by TFC for concussion grenade effect + +//============================================================================= +/* +void V_NormalizeAngles( float *angles ) +{ + int i; + // Normalize angles + for ( i = 0; i < 3; i++ ) + { + if ( angles[i] > 180.0 ) + { + angles[i] -= 360.0; + } + else if ( angles[i] < -180.0 ) + { + angles[i] += 360.0; + } + } +} + +/* +=================== +V_InterpolateAngles + +Interpolate Euler angles. +FIXME: Use Quaternions to avoid discontinuities +Frac is 0.0 to 1.0 ( i.e., should probably be clamped, but doesn't have to be ) +=================== + +void V_InterpolateAngles( float *start, float *end, float *output, float frac ) +{ + int i; + float ang1, ang2; + float d; + + V_NormalizeAngles( start ); + V_NormalizeAngles( end ); + + for ( i = 0 ; i < 3 ; i++ ) + { + ang1 = start[i]; + ang2 = end[i]; + + d = ang2 - ang1; + if ( d > 180 ) + { + d -= 360; + } + else if ( d < -180 ) + { + d += 360; + } + + output[i] = ang1 + d * frac; + } + + V_NormalizeAngles( output ); +} */ + +// Quakeworld bob code, this fixes jitters in the mutliplayer since the clock (pparams->time) isn't quite linear +float V_CalcBob ( struct ref_params_s *pparams ) +{ + static double bobtime; + static float bob; + float cycle; + static float lasttime; + vec3_t vel; + + + if ( pparams->onground == -1 || + pparams->time == lasttime ) + { + // just use old value + return bob; + } + + lasttime = pparams->time; + + bobtime += pparams->frametime; + cycle = bobtime - (int)( bobtime / cl_bobcycle->value ) * cl_bobcycle->value; + cycle /= cl_bobcycle->value; + + if ( cycle < cl_bobup->value ) + { + cycle = M_PI * cycle / cl_bobup->value; + } + else + { + cycle = M_PI + M_PI * ( cycle - cl_bobup->value )/( 1.0 - cl_bobup->value ); + } + + // bob is proportional to simulated velocity in the xy plane + // (don't count Z, or jumping messes it up) + VectorCopy( pparams->simvel, vel ); + vel[2] = 0; + + bob = sqrt( vel[0] * vel[0] + vel[1] * vel[1] ) * cl_bob->value; + bob = bob * 0.3 + bob * 0.7 * sin(cycle); + bob = min( bob, 4 ); + bob = max( bob, -7 ); + return bob; + +} + +/* +=============== +V_CalcRoll +Used by view and sv_user +=============== +*/ +float V_CalcRoll (vec3_t angles, vec3_t velocity, float rollangle, float rollspeed ) +{ + float sign; + float side; + float value; + vec3_t forward, right, up; + + AngleVectors ( angles, forward, right, up ); + + side = DotProduct (velocity, right); + sign = side < 0 ? -1 : 1; + side = fabs( side ); + + value = rollangle; + if (side < rollspeed) + { + side = side * value / rollspeed; + } + else + { + side = value; + } + return side * sign; +} + +typedef struct pitchdrift_s +{ + float pitchvel; + int nodrift; + float driftmove; + double laststop; +} pitchdrift_t; + +static pitchdrift_t pd; + +void V_StartPitchDrift( void ) +{ + if ( pd.laststop == gEngfuncs.GetClientTime() ) + { + return; // something else is keeping it from drifting + } + + if ( pd.nodrift || !pd.pitchvel ) + { + pd.pitchvel = v_centerspeed->value; + pd.nodrift = 0; + pd.driftmove = 0; + } +} + +void V_StopPitchDrift ( void ) +{ + pd.laststop = gEngfuncs.GetClientTime(); + pd.nodrift = 1; + pd.pitchvel = 0; +} + +/* +=============== +V_DriftPitch + +Moves the client pitch angle towards idealpitch sent by the server. + +If the user is adjusting pitch manually, either with lookup/lookdown, +mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped. +=============== +*/ +void V_DriftPitch ( struct ref_params_s *pparams ) +{ + float delta, move; + + if ( gEngfuncs.IsNoClipping() || !pparams->onground || pparams->demoplayback || pparams->spectator ) + { + pd.driftmove = 0; + pd.pitchvel = 0; + return; + } + + // don't count small mouse motion + if (pd.nodrift) + { + if ( fabs( pparams->cmd->forwardmove ) < cl_forwardspeed->value ) + pd.driftmove = 0; + else + pd.driftmove += pparams->frametime; + + if ( pd.driftmove > v_centermove->value) + { + V_StartPitchDrift (); + } + return; + } + + delta = pparams->idealpitch - pparams->cl_viewangles[PITCH]; + + if (!delta) + { + pd.pitchvel = 0; + return; + } + + move = pparams->frametime * pd.pitchvel; + pd.pitchvel += pparams->frametime * v_centerspeed->value; + + if (delta > 0) + { + if (move > delta) + { + pd.pitchvel = 0; + move = delta; + } + pparams->cl_viewangles[PITCH] += move; + } + else if (delta < 0) + { + if (move > -delta) + { + pd.pitchvel = 0; + move = -delta; + } + pparams->cl_viewangles[PITCH] -= move; + } +} + +/* +============================================================================== + VIEW RENDERING +============================================================================== +*/ + +/* +================== +V_CalcGunAngle +================== +*/ +void V_CalcGunAngle ( struct ref_params_s *pparams ) +{ + cl_entity_t *viewent; + + viewent = gEngfuncs.GetViewModel(); + if ( !viewent ) + return; + + viewent->angles[YAW] = pparams->viewangles[YAW] + pparams->crosshairangle[YAW]; + viewent->angles[PITCH] = -pparams->viewangles[PITCH] + pparams->crosshairangle[PITCH] * 0.25; + viewent->angles[ROLL] -= v_idlescale * sin(pparams->time*v_iroll_cycle.value) * v_iroll_level.value; + + // don't apply all of the v_ipitch to prevent normally unseen parts of viewmodel from coming into view. + viewent->angles[PITCH] -= v_idlescale * sin(pparams->time*v_ipitch_cycle.value) * (v_ipitch_level.value * 0.5); + viewent->angles[YAW] -= v_idlescale * sin(pparams->time*v_iyaw_cycle.value) * v_iyaw_level.value; + + VectorCopy( viewent->angles, viewent->curstate.angles ); + VectorCopy( viewent->angles, viewent->latched.prevangles ); +} + +/* +============== +V_AddIdle + +Idle swaying +============== +*/ +void V_AddIdle ( struct ref_params_s *pparams ) +{ + pparams->viewangles[ROLL] += v_idlescale * sin(pparams->time*v_iroll_cycle.value) * v_iroll_level.value; + pparams->viewangles[PITCH] += v_idlescale * sin(pparams->time*v_ipitch_cycle.value) * v_ipitch_level.value; + pparams->viewangles[YAW] += v_idlescale * sin(pparams->time*v_iyaw_cycle.value) * v_iyaw_level.value; +} + + +/* +============== +V_CalcViewRoll + +Roll is induced by movement and damage +============== +*/ +void V_CalcViewRoll ( struct ref_params_s *pparams ) +{ + float side; + cl_entity_t *viewentity; + + viewentity = gEngfuncs.GetEntityByIndex( pparams->viewentity ); + if ( !viewentity ) + return; + + side = V_CalcRoll ( viewentity->angles, pparams->simvel, pparams->movevars->rollangle, pparams->movevars->rollspeed ); + + pparams->viewangles[ROLL] += side; + + if ( pparams->health <= 0 && ( pparams->viewheight[2] != 0 ) ) + { + // only roll the view if the player is dead and the viewheight[2] is nonzero + // this is so deadcam in multiplayer will work. + pparams->viewangles[ROLL] = 80; // dead view angle + return; + } +} + + +/* +================== +V_CalcIntermissionRefdef + +================== +*/ +void V_CalcIntermissionRefdef ( struct ref_params_s *pparams ) +{ + cl_entity_t *ent, *view; + float old; + + // ent is the player model ( visible when out of body ) + ent = gEngfuncs.GetLocalPlayer(); + + // view is the weapon model (only visible from inside body ) + view = gEngfuncs.GetViewModel(); + + VectorCopy ( pparams->simorg, pparams->vieworg ); + VectorCopy ( pparams->cl_viewangles, pparams->viewangles ); + + view->model = NULL; + + // allways idle in intermission + old = v_idlescale; + v_idlescale = 1; + + V_AddIdle ( pparams ); + + if ( gEngfuncs.IsSpectateOnly() ) + { + // in HLTV we must go to 'intermission' position by ourself + VectorCopy( gHUD.m_Spectator.m_cameraOrigin, pparams->vieworg ); + VectorCopy( gHUD.m_Spectator.m_cameraAngles, pparams->viewangles ); + } + + v_idlescale = old; + + v_cl_angles = pparams->cl_viewangles; + v_origin = pparams->vieworg; + v_angles = pparams->viewangles; +} + +#define ORIGIN_BACKUP 64 +#define ORIGIN_MASK ( ORIGIN_BACKUP - 1 ) + +typedef struct +{ + float Origins[ ORIGIN_BACKUP ][3]; + float OriginTime[ ORIGIN_BACKUP ]; + + float Angles[ ORIGIN_BACKUP ][3]; + float AngleTime[ ORIGIN_BACKUP ]; + + int CurrentOrigin; + int CurrentAngle; +} viewinterp_t; + +/* +================== +V_CalcRefdef + +================== +*/ +void V_CalcNormalRefdef ( struct ref_params_s *pparams ) +{ + cl_entity_t *ent, *view; + int i; + vec3_t angles; + float bob, waterOffset; + static viewinterp_t ViewInterp; + + static float oldz = 0; + static float lasttime; + + vec3_t camAngles, camForward, camRight, camUp; + cl_entity_t *pwater; + + V_DriftPitch ( pparams ); + + if ( gEngfuncs.IsSpectateOnly() ) + { + ent = gEngfuncs.GetEntityByIndex( g_iUser2 ); + } + else + { + // ent is the player model ( visible when out of body ) + ent = gEngfuncs.GetLocalPlayer(); + } + + // view is the weapon model (only visible from inside body ) + view = gEngfuncs.GetViewModel(); + + // transform the view offset by the model's matrix to get the offset from + // model origin for the view + bob = V_CalcBob ( pparams ); + + // refresh position + VectorCopy ( pparams->simorg, pparams->vieworg ); + pparams->vieworg[2] += ( bob ); + VectorAdd( pparams->vieworg, pparams->viewheight, pparams->vieworg ); + + VectorCopy ( pparams->cl_viewangles, pparams->viewangles ); + + gEngfuncs.V_CalcShake(); + gEngfuncs.V_ApplyShake( pparams->vieworg, pparams->viewangles, 1.0 ); + + // never let view origin sit exactly on a node line, because a water plane can + // dissapear when viewed with the eye exactly on it. + // FIXME, we send origin at 1/128 now, change this? + // the server protocol only specifies to 1/16 pixel, so add 1/32 in each axis + + pparams->vieworg[0] += 1.0/32; + pparams->vieworg[1] += 1.0/32; + pparams->vieworg[2] += 1.0/32; + + // Check for problems around water, move the viewer artificially if necessary + // -- this prevents drawing errors in GL due to waves + + waterOffset = 0; + if ( pparams->waterlevel >= 2 ) + { + int i, contents, waterDist, waterEntity; + vec3_t point; + waterDist = cl_waterdist->value; + + if ( pparams->hardware ) + { + waterEntity = gEngfuncs.PM_WaterEntity( pparams->simorg ); + if ( waterEntity >= 0 && waterEntity < pparams->max_entities ) + { + pwater = gEngfuncs.GetEntityByIndex( waterEntity ); + if ( pwater && ( pwater->model != NULL ) ) + { + waterDist += ( pwater->curstate.scale * 16 ); // Add in wave height + } + } + } + else + { + waterEntity = 0; // Don't need this in software + } + + VectorCopy( pparams->vieworg, point ); + + // Eyes are above water, make sure we're above the waves + if ( pparams->waterlevel == 2 ) + { + point[2] -= waterDist; + for ( i = 0; i < waterDist; i++ ) + { + contents = gEngfuncs.PM_PointContents( point, NULL ); + if ( contents > CONTENTS_WATER ) + break; + point[2] += 1; + } + waterOffset = (point[2] + waterDist) - pparams->vieworg[2]; + } + else + { + // eyes are under water. Make sure we're far enough under + point[2] += waterDist; + + for ( i = 0; i < waterDist; i++ ) + { + contents = gEngfuncs.PM_PointContents( point, NULL ); + if ( contents <= CONTENTS_WATER ) + break; + point[2] -= 1; + } + waterOffset = (point[2] - waterDist) - pparams->vieworg[2]; + } + } + + pparams->vieworg[2] += waterOffset; + + V_CalcViewRoll ( pparams ); + + V_AddIdle ( pparams ); + + // offsets + VectorCopy( pparams->cl_viewangles, angles ); + + AngleVectors ( angles, pparams->forward, pparams->right, pparams->up ); + + // don't allow cheats in multiplayer + if ( pparams->maxclients <= 1 ) + { + for ( i=0 ; i<3 ; i++ ) + { + pparams->vieworg[i] += scr_ofsx->value*pparams->forward[i] + scr_ofsy->value*pparams->right[i] + scr_ofsz->value*pparams->up[i]; + } + } + + // Treating cam_ofs[2] as the distance + if( CL_IsThirdPerson() ) + { + vec3_t ofs; + + ofs[0] = ofs[1] = ofs[2] = 0.0; + + CL_CameraOffset( (float *)&ofs ); + + VectorCopy( ofs, camAngles ); + camAngles[ ROLL ] = 0; + + AngleVectors( camAngles, camForward, camRight, camUp ); + + for ( i = 0; i < 3; i++ ) + { + pparams->vieworg[ i ] += -ofs[2] * camForward[ i ]; + } + } + + // Give gun our viewangles + VectorCopy ( pparams->cl_viewangles, view->angles ); + + // set up gun position + V_CalcGunAngle ( pparams ); + + // Use predicted origin as view origin. + VectorCopy ( pparams->simorg, view->origin ); + view->origin[2] += ( waterOffset ); + VectorAdd( view->origin, pparams->viewheight, view->origin ); + + // Let the viewmodel shake at about 10% of the amplitude + gEngfuncs.V_ApplyShake( view->origin, view->angles, 0.9 ); + + for ( i = 0; i < 3; i++ ) + { + view->origin[ i ] += bob * 0.4 * pparams->forward[ i ]; + } + view->origin[2] += bob; + + // throw in a little tilt. + view->angles[YAW] -= bob * 0.5; + view->angles[ROLL] -= bob * 1; + view->angles[PITCH] -= bob * 0.3; + + // pushing the view origin down off of the same X/Z plane as the ent's origin will give the + // gun a very nice 'shifting' effect when the player looks up/down. If there is a problem + // with view model distortion, this may be a cause. (SJB). + view->origin[2] -= 1; + + // fudge position around to keep amount of weapon visible + // roughly equal with different FOV + if (pparams->viewsize == 110) + { + view->origin[2] += 1; + } + else if (pparams->viewsize == 100) + { + view->origin[2] += 2; + } + else if (pparams->viewsize == 90) + { + view->origin[2] += 1; + } + else if (pparams->viewsize == 80) + { + view->origin[2] += 0.5; + } + + // Add in the punchangle, if any + VectorAdd ( pparams->viewangles, pparams->punchangle, pparams->viewangles ); + + // Include client side punch, too + VectorAdd ( pparams->viewangles, (float *)&ev_punchangle, pparams->viewangles); + + V_DropPunchAngle ( pparams->frametime, (float *)&ev_punchangle ); + + // smooth out stair step ups +#if 1 + if ( !pparams->smoothing && pparams->onground && pparams->simorg[2] - oldz > 0) + { + float steptime; + + steptime = pparams->time - lasttime; + if (steptime < 0) + //FIXME I_Error ("steptime < 0"); + steptime = 0; + + oldz += steptime * 150; + if (oldz > pparams->simorg[2]) + oldz = pparams->simorg[2]; + if (pparams->simorg[2] - oldz > 18) + oldz = pparams->simorg[2]- 18; + pparams->vieworg[2] += oldz - pparams->simorg[2]; + view->origin[2] += oldz - pparams->simorg[2]; + } + else + { + oldz = pparams->simorg[2]; + } +#endif + + { + static float lastorg[3]; + vec3_t delta; + + VectorSubtract( pparams->simorg, lastorg, delta ); + + if ( Length( delta ) != 0.0 ) + { + VectorCopy( pparams->simorg, ViewInterp.Origins[ ViewInterp.CurrentOrigin & ORIGIN_MASK ] ); + ViewInterp.OriginTime[ ViewInterp.CurrentOrigin & ORIGIN_MASK ] = pparams->time; + ViewInterp.CurrentOrigin++; + + VectorCopy( pparams->simorg, lastorg ); + } + } + + // Smooth out whole view in multiplayer when on trains, lifts + if ( cl_vsmoothing && cl_vsmoothing->value && + ( pparams->smoothing && ( pparams->maxclients > 1 ) ) ) + { + int foundidx; + int i; + float t; + + if ( cl_vsmoothing->value < 0.0 ) + { + gEngfuncs.Cvar_SetValue( "cl_vsmoothing", 0.0 ); + } + + t = pparams->time - cl_vsmoothing->value; + + for ( i = 1; i < ORIGIN_MASK; i++ ) + { + foundidx = ViewInterp.CurrentOrigin - 1 - i; + if ( ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ] <= t ) + break; + } + + if ( i < ORIGIN_MASK && ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ] != 0.0 ) + { + // Interpolate + vec3_t delta; + double frac; + double dt; + vec3_t neworg; + + dt = ViewInterp.OriginTime[ (foundidx + 1) & ORIGIN_MASK ] - ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ]; + if ( dt > 0.0 ) + { + frac = ( t - ViewInterp.OriginTime[ foundidx & ORIGIN_MASK] ) / dt; + frac = min( 1.0, frac ); + VectorSubtract( ViewInterp.Origins[ ( foundidx + 1 ) & ORIGIN_MASK ], ViewInterp.Origins[ foundidx & ORIGIN_MASK ], delta ); + VectorMA( ViewInterp.Origins[ foundidx & ORIGIN_MASK ], frac, delta, neworg ); + + // Dont interpolate large changes + if ( Length( delta ) < 64 ) + { + VectorSubtract( neworg, pparams->simorg, delta ); + + VectorAdd( pparams->simorg, delta, pparams->simorg ); + VectorAdd( pparams->vieworg, delta, pparams->vieworg ); + VectorAdd( view->origin, delta, view->origin ); + + } + } + } + } + + // Store off v_angles before munging for third person + v_angles = pparams->viewangles; + v_lastAngles = pparams->viewangles; +// v_cl_angles = pparams->cl_viewangles; // keep old user mouse angles ! + if ( CL_IsThirdPerson() ) + { + VectorCopy( camAngles, pparams->viewangles); + float pitch = camAngles[ 0 ]; + + // Normalize angles + if ( pitch > 180 ) + pitch -= 360.0; + else if ( pitch < -180 ) + pitch += 360; + + // Player pitch is inverted + pitch /= -3.0; + + // Slam local player's pitch value + ent->angles[ 0 ] = pitch; + ent->curstate.angles[ 0 ] = pitch; + ent->prevstate.angles[ 0 ] = pitch; + ent->latched.prevangles[ 0 ] = pitch; + } + + // override all previous settings if the viewent isn't the client + if ( pparams->viewentity > pparams->maxclients ) + { + cl_entity_t *viewentity; + viewentity = gEngfuncs.GetEntityByIndex( pparams->viewentity ); + if ( viewentity ) + { + VectorCopy( viewentity->origin, pparams->vieworg ); + VectorCopy( viewentity->angles, pparams->viewangles ); + + // Store off overridden viewangles + v_angles = pparams->viewangles; + } + } + + lasttime = pparams->time; + + v_origin = pparams->vieworg; +} + +void V_SmoothInterpolateAngles( float * startAngle, float * endAngle, float * finalAngle, float degreesPerSec ) +{ + float absd,frac,d,threshhold; + + NormalizeAngles( startAngle ); + NormalizeAngles( endAngle ); + + for ( int i = 0 ; i < 3 ; i++ ) + { + d = endAngle[i] - startAngle[i]; + + if ( d > 180.0f ) + { + d -= 360.0f; + } + else if ( d < -180.0f ) + { + d += 360.0f; + } + + absd = fabs(d); + + if ( absd > 0.01f ) + { + frac = degreesPerSec * v_frametime; + + threshhold= degreesPerSec / 4; + + if ( absd < threshhold ) + { + float h = absd / threshhold; + h *= h; + frac*= h; // slow down last degrees + } + + if ( frac > absd ) + { + finalAngle[i] = endAngle[i]; + } + else + { + if ( d>0) + finalAngle[i] = startAngle[i] + frac; + else + finalAngle[i] = startAngle[i] - frac; + } + } + else + { + finalAngle[i] = endAngle[i]; + } + + } + + NormalizeAngles( finalAngle ); +} + +// Get the origin of the Observer based around the target's position and angles +void V_GetChaseOrigin( float * angles, float * origin, float distance, float * returnvec ) +{ + vec3_t vecEnd; + vec3_t forward; + vec3_t vecStart; + pmtrace_t * trace; + int maxLoops = 8; + + int ignoreent = -1; // first, ignore no entity + + cl_entity_t * ent = NULL; + + // Trace back from the target using the player's view angles + AngleVectors(angles, forward, NULL, NULL); + + VectorScale(forward,-1,forward); + + VectorCopy( origin, vecStart ); + + VectorMA(vecStart, distance , forward, vecEnd); + + while ( maxLoops > 0) + { + trace = gEngfuncs.PM_TraceLine( vecStart, vecEnd, PM_TRACELINE_PHYSENTSONLY, 2, ignoreent ); + + // WARNING! trace->ent is is the number in physent list not the normal entity number + + if ( trace->ent <= 0) + break; // we hit the world or nothing, stop trace + + ent = gEngfuncs.GetEntityByIndex( PM_GetPhysEntInfo( trace->ent ) ); + + if ( ent == NULL ) + break; + + // hit non-player solid BSP , stop here + if ( ent->curstate.solid == SOLID_BSP && !ent->player ) + break; + + // if close enought to end pos, stop, otherwise continue trace + if( Distance(trace->endpos, vecEnd ) < 1.0f ) + { + break; + } + else + { + ignoreent = trace->ent; // ignore last hit entity + VectorCopy( trace->endpos, vecStart); + } + + maxLoops--; + } + +/* if ( ent ) + { + gEngfuncs.Con_Printf("Trace loops %i , entity %i, model %s, solid %i\n",(8-maxLoops),ent->curstate.number, ent->model->name , ent->curstate.solid ); + } */ + + VectorMA( trace->endpos, 4, trace->plane.normal, returnvec ); + + v_lastDistance = Distance(trace->endpos, origin); // real distance without offset +} + +/*void V_GetDeathCam(cl_entity_t * ent1, cl_entity_t * ent2, float * angle, float * origin) +{ + float newAngle[3]; float newOrigin[3]; + + float distance = 168.0f; + + v_lastDistance+= v_frametime * 96.0f; // move unit per seconds back + + if ( v_resetCamera ) + v_lastDistance = 64.0f; + + if ( distance > v_lastDistance ) + distance = v_lastDistance; + + VectorCopy(ent1->origin, newOrigin); + + if ( ent1->player ) + newOrigin[2]+= 17; // head level of living player + + // get new angle towards second target + if ( ent2 ) + { + VectorSubtract( ent2->origin, ent1->origin, newAngle ); + VectorAngles( newAngle, newAngle ); + newAngle[0] = -newAngle[0]; + } + else + { + // if no second target is given, look down to dead player + newAngle[0] = 90.0f; + newAngle[1] = 0.0f; + newAngle[2] = 0; + } + + // and smooth view + V_SmoothInterpolateAngles( v_lastAngles, newAngle, angle, 120.0f ); + + V_GetChaseOrigin( angle, newOrigin, distance, origin ); + + VectorCopy(angle, v_lastAngles); +}*/ + +void V_GetSingleTargetCam(cl_entity_t * ent1, float * angle, float * origin) +{ + float newAngle[3]; float newOrigin[3]; + + int flags = gHUD.m_Spectator.m_iObserverFlags; + + // see is target is a dead player + qboolean deadPlayer = ent1->player && (ent1->curstate.solid == SOLID_NOT); + + float dfactor = ( flags & DRC_FLAG_DRAMATIC )? -1.0f : 1.0f; + + float distance = 112.0f + ( 16.0f * dfactor ); // get close if dramatic; + + // go away in final scenes or if player just died + if ( flags & DRC_FLAG_FINAL ) + distance*=2.0f; + else if ( deadPlayer ) + distance*=1.5f; + + // let v_lastDistance float smoothly away + v_lastDistance+= v_frametime * 32.0f; // move unit per seconds back + + if ( distance > v_lastDistance ) + distance = v_lastDistance; + + VectorCopy(ent1->origin, newOrigin); + + if ( ent1->player ) + { + if ( deadPlayer ) + newOrigin[2]+= 2; //laying on ground + else + newOrigin[2]+= 17; // head level of living player + + } + else + newOrigin[2]+= 8; // object, tricky, must be above bomb in CS + + // we have no second target, choose view direction based on + // show front of primary target + VectorCopy(ent1->angles, newAngle); + + // show dead players from front, normal players back + if ( flags & DRC_FLAG_FACEPLAYER ) + newAngle[1]+= 180.0f; + + + newAngle[0]+= 12.5f * dfactor; // lower angle if dramatic + + // if final scene (bomb), show from real high pos + if ( flags & DRC_FLAG_FINAL ) + newAngle[0] = 22.5f; + + // choose side of object/player + if ( flags & DRC_FLAG_SIDE ) + newAngle[1]+=22.5f; + else + newAngle[1]-=22.5f; + + V_SmoothInterpolateAngles( v_lastAngles, newAngle, angle, 120.0f ); + + // HACK, if player is dead don't clip against his dead body, can't check this + V_GetChaseOrigin( angle, newOrigin, distance, origin ); +} + +float MaxAngleBetweenAngles( float * a1, float * a2 ) +{ + float d, maxd = 0.0f; + + NormalizeAngles( a1 ); + NormalizeAngles( a2 ); + + for ( int i = 0 ; i < 3 ; i++ ) + { + d = a2[i] - a1[i]; + if ( d > 180 ) + { + d -= 360; + } + else if ( d < -180 ) + { + d += 360; + } + + d = fabs(d); + + if ( d > maxd ) + maxd=d; + } + + return maxd; +} + +void V_GetDoubleTargetsCam(cl_entity_t * ent1, cl_entity_t * ent2,float * angle, float * origin) +{ + float newAngle[3]; float newOrigin[3]; float tempVec[3]; + + int flags = gHUD.m_Spectator.m_iObserverFlags; + + float dfactor = ( flags & DRC_FLAG_DRAMATIC )? -1.0f : 1.0f; + + float distance = 112.0f + ( 16.0f * dfactor ); // get close if dramatic; + + // go away in final scenes or if player just died + if ( flags & DRC_FLAG_FINAL ) + distance*=2.0f; + + // let v_lastDistance float smoothly away + v_lastDistance+= v_frametime * 32.0f; // move unit per seconds back + + if ( distance > v_lastDistance ) + distance = v_lastDistance; + + VectorCopy(ent1->origin, newOrigin); + + if ( ent1->player ) + newOrigin[2]+= 17; // head level of living player + else + newOrigin[2]+= 8; // object, tricky, must be above bomb in CS + + // get new angle towards second target + VectorSubtract( ent2->origin, ent1->origin, newAngle ); + + VectorAngles( newAngle, newAngle ); + newAngle[0] = -newAngle[0]; + + // set angle diffrent in Dramtaic scenes + newAngle[0]+= 12.5f * dfactor; // lower angle if dramatic + + if ( flags & DRC_FLAG_SIDE ) + newAngle[1]+=22.5f; + else + newAngle[1]-=22.5f; + + float d = MaxAngleBetweenAngles( v_lastAngles, newAngle ); + + if ( ( d < v_cameraFocusAngle) && ( v_cameraMode == CAM_MODE_RELAX ) ) + { + // difference is to small and we are in relax camera mode, keep viewangles + VectorCopy(v_lastAngles, newAngle ); + } + else if ( (d < v_cameraRelaxAngle) && (v_cameraMode == CAM_MODE_FOCUS) ) + { + // we catched up with our target, relax again + v_cameraMode = CAM_MODE_RELAX; + } + else + { + // target move too far away, focus camera again + v_cameraMode = CAM_MODE_FOCUS; + } + + // and smooth view, if not a scene cut + if ( v_resetCamera || (v_cameraMode == CAM_MODE_RELAX) ) + { + VectorCopy( newAngle, angle ); + } + else + { + V_SmoothInterpolateAngles( v_lastAngles, newAngle, angle, 180.0f ); + } + + V_GetChaseOrigin( newAngle, newOrigin, distance, origin ); + + // move position up, if very close at target + if ( v_lastDistance < 64.0f ) + origin[2]+= 16.0f*( 1.0f - (v_lastDistance / 64.0f ) ); + + // calculate angle to second target + VectorSubtract( ent2->origin, origin, tempVec ); + VectorAngles( tempVec, tempVec ); + tempVec[0] = -tempVec[0]; + + /* take middle between two viewangles + InterpolateAngles( newAngle, tempVec, newAngle, 0.5f); */ + + + +} + +void V_GetDirectedChasePosition(cl_entity_t * ent1, cl_entity_t * ent2,float * angle, float * origin) +{ + + if ( v_resetCamera ) + { + v_lastDistance = 4096.0f; + // v_cameraMode = CAM_MODE_FOCUS; + } + + if ( ( ent2 == (cl_entity_t*)0xFFFFFFFF ) || ( ent1->player && (ent1->curstate.solid == SOLID_NOT) ) ) + { + // we have no second target or player just died + V_GetSingleTargetCam(ent1, angle, origin); + } + else if ( ent2 ) + { + // keep both target in view + V_GetDoubleTargetsCam( ent1, ent2, angle, origin ); + } + else + { + // second target disappeard somehow (dead) + + // keep last good viewangle + float newOrigin[3]; + + int flags = gHUD.m_Spectator.m_iObserverFlags; + + float dfactor = ( flags & DRC_FLAG_DRAMATIC )? -1.0f : 1.0f; + + float distance = 112.0f + ( 16.0f * dfactor ); // get close if dramatic; + + // go away in final scenes or if player just died + if ( flags & DRC_FLAG_FINAL ) + distance*=2.0f; + + // let v_lastDistance float smoothly away + v_lastDistance+= v_frametime * 32.0f; // move unit per seconds back + + if ( distance > v_lastDistance ) + distance = v_lastDistance; + + VectorCopy(ent1->origin, newOrigin); + + if ( ent1->player ) + newOrigin[2]+= 17; // head level of living player + else + newOrigin[2]+= 8; // object, tricky, must be above bomb in CS + + V_GetChaseOrigin( angle, newOrigin, distance, origin ); + } + + VectorCopy(angle, v_lastAngles); +} + +void V_GetChasePos(int target, float * cl_angles, float * origin, float * angles) +{ + cl_entity_t * ent = NULL; + + if ( target ) + { + ent = gEngfuncs.GetEntityByIndex( target ); + }; + + if (!ent) + { + // just copy a save in-map position + VectorCopy ( vJumpAngles, angles ); + VectorCopy ( vJumpOrigin, origin ); + return; + } + + + + if ( gHUD.m_Spectator.m_autoDirector->value ) + { + if ( g_iUser3 ) + V_GetDirectedChasePosition( ent, gEngfuncs.GetEntityByIndex( g_iUser3 ), + angles, origin ); + else + V_GetDirectedChasePosition( ent, ( cl_entity_t*)0xFFFFFFFF, + angles, origin ); + } + else + { + if ( cl_angles == NULL ) // no mouse angles given, use entity angles ( locked mode ) + { + VectorCopy ( ent->angles, angles); + angles[0]*=-1; + } + else + VectorCopy ( cl_angles, angles); + + + VectorCopy ( ent->origin, origin); + + origin[2]+= 28; // DEFAULT_VIEWHEIGHT - some offset + + V_GetChaseOrigin( angles, origin, cl_chasedist->value, origin ); + } + + v_resetCamera = false; +} + +void V_ResetChaseCam() +{ + v_resetCamera = true; +} + + +void V_GetInEyePos(int target, float * origin, float * angles ) +{ + if ( !target) + { + // just copy a save in-map position + VectorCopy ( vJumpAngles, angles ); + VectorCopy ( vJumpOrigin, origin ); + return; + }; + + + cl_entity_t * ent = gEngfuncs.GetEntityByIndex( target ); + + if ( !ent ) + return; + + VectorCopy ( ent->origin, origin ); + VectorCopy ( ent->angles, angles ); + + angles[PITCH]*=-3.0f; // see CL_ProcessEntityUpdate() + + if ( ent->curstate.solid == SOLID_NOT ) + { + angles[ROLL] = 80; // dead view angle + origin[2]+= -8 ; // PM_DEAD_VIEWHEIGHT + } + else if (ent->curstate.usehull == 1 ) + origin[2]+= 12; // VEC_DUCK_VIEW; + else + // exacty eye position can't be caluculated since it depends on + // client values like cl_bobcycle, this offset matches the default values + origin[2]+= 28; // DEFAULT_VIEWHEIGHT +} + +void V_GetMapFreePosition( float * cl_angles, float * origin, float * angles ) +{ + vec3_t forward; + vec3_t zScaledTarget; + + VectorCopy(cl_angles, angles); + + // modify angles since we don't wanna see map's bottom + angles[0] = 51.25f + 38.75f*(angles[0]/90.0f); + + zScaledTarget[0] = gHUD.m_Spectator.m_mapOrigin[0]; + zScaledTarget[1] = gHUD.m_Spectator.m_mapOrigin[1]; + zScaledTarget[2] = gHUD.m_Spectator.m_mapOrigin[2] * (( 90.0f - angles[0] ) / 90.0f ); + + + AngleVectors(angles, forward, NULL, NULL); + + VectorNormalize(forward); + + VectorMA(zScaledTarget, -( 4096.0f / gHUD.m_Spectator.m_mapZoom ), forward , origin); +} + +void V_GetMapChasePosition(int target, float * cl_angles, float * origin, float * angles) +{ + vec3_t forward; + + if ( target ) + { + cl_entity_t * ent = gEngfuncs.GetEntityByIndex( target ); + + if ( gHUD.m_Spectator.m_autoDirector->value ) + { + // this is done to get the angles made by director mode + V_GetChasePos(target, cl_angles, origin, angles); + VectorCopy(ent->origin, origin); + + // keep fix chase angle horizontal + angles[0] = 45.0f; + } + else + { + VectorCopy(cl_angles, angles); + VectorCopy(ent->origin, origin); + + // modify angles since we don't wanna see map's bottom + angles[0] = 51.25f + 38.75f*(angles[0]/90.0f); + } + } + else + { + // keep out roaming position, but modify angles + VectorCopy(cl_angles, angles); + angles[0] = 51.25f + 38.75f*(angles[0]/90.0f); + } + + origin[2] *= (( 90.0f - angles[0] ) / 90.0f ); + angles[2] = 0.0f; // don't roll angle (if chased player is dead) + + AngleVectors(angles, forward, NULL, NULL); + + VectorNormalize(forward); + + VectorMA(origin, -1536, forward, origin); +} + +int V_FindViewModelByWeaponModel(int weaponindex) +{ + + static char * modelmap[][2] = { + { "models/p_crossbow.mdl", "models/v_crossbow.mdl" }, + { "models/p_crowbar.mdl", "models/v_crowbar.mdl" }, + { "models/p_egon.mdl", "models/v_egon.mdl" }, + { "models/p_gauss.mdl", "models/v_gauss.mdl" }, + { "models/p_9mmhandgun.mdl", "models/v_9mmhandgun.mdl" }, + { "models/p_grenade.mdl", "models/v_grenade.mdl" }, + { "models/p_hgun.mdl", "models/v_hgun.mdl" }, + { "models/p_9mmAR.mdl", "models/v_9mmAR.mdl" }, + { "models/p_357.mdl", "models/v_357.mdl" }, + { "models/p_rpg.mdl", "models/v_rpg.mdl" }, + { "models/p_shotgun.mdl", "models/v_shotgun.mdl" }, + { "models/p_squeak.mdl", "models/v_squeak.mdl" }, + { "models/p_tripmine.mdl", "models/v_tripmine.mdl" }, + { "models/p_satchel_radio.mdl", "models/v_satchel_radio.mdl"}, + { "models/p_satchel.mdl", "models/v_satchel.mdl" }, + { NULL, NULL } }; + + struct model_s * weaponModel = IEngineStudio.GetModelByIndex( weaponindex ); + + if ( weaponModel ) + { + int len = strlen( weaponModel->name ); + int i = 0; + + while ( modelmap[i] != NULL ) + { + if ( !strnicmp( weaponModel->name, modelmap[i][0], len ) ) + { + return gEngfuncs.pEventAPI->EV_FindModelIndex( modelmap[i][1] ); + } + i++; + } + + return 0; + } + else + return 0; + +} + + +/* +================== +V_CalcSpectatorRefdef + +================== +*/ +void V_CalcSpectatorRefdef ( struct ref_params_s * pparams ) +{ + static vec3_t velocity ( 0.0f, 0.0f, 0.0f); + + static int lastWeaponModelIndex = 0; + static int lastViewModelIndex = 0; + + cl_entity_t * ent = gEngfuncs.GetEntityByIndex( g_iUser2 ); + + pparams->onlyClientDraw = false; + + // refresh position + VectorCopy ( pparams->simorg, v_sim_org ); + + // get old values + VectorCopy ( pparams->cl_viewangles, v_cl_angles ); + VectorCopy ( pparams->viewangles, v_angles ); + VectorCopy ( pparams->vieworg, v_origin ); + + if ( ( g_iUser1 == OBS_IN_EYE || gHUD.m_Spectator.m_pip->value == INSET_IN_EYE ) && ent ) + { + // calculate player velocity + float timeDiff = ent->curstate.msg_time - ent->prevstate.msg_time; + + if ( timeDiff > 0 ) + { + vec3_t distance; + VectorSubtract(ent->prevstate.origin, ent->curstate.origin, distance); + VectorScale(distance, 1/timeDiff, distance ); + + velocity[0] = velocity[0]*0.9f + distance[0]*0.1f; + velocity[1] = velocity[1]*0.9f + distance[1]*0.1f; + velocity[2] = velocity[2]*0.9f + distance[2]*0.1f; + + VectorCopy(velocity, pparams->simvel); + } + + // predict missing client data and set weapon model ( in HLTV mode or inset in eye mode ) + if ( gEngfuncs.IsSpectateOnly() ) + { + V_GetInEyePos( g_iUser2, pparams->simorg, pparams->cl_viewangles ); + + pparams->health = 1; + + cl_entity_t * gunModel = gEngfuncs.GetViewModel(); + + if ( lastWeaponModelIndex != ent->curstate.weaponmodel ) + { + // weapon model changed + + lastWeaponModelIndex = ent->curstate.weaponmodel; + lastViewModelIndex = V_FindViewModelByWeaponModel( lastWeaponModelIndex ); + if ( lastViewModelIndex ) + { + gEngfuncs.pfnWeaponAnim(0,0); // reset weapon animation + } + else + { + // model not found + gunModel->model = NULL; // disable weapon model + lastWeaponModelIndex = lastViewModelIndex = 0; + } + } + + if ( lastViewModelIndex ) + { + gunModel->model = IEngineStudio.GetModelByIndex( lastViewModelIndex ); + gunModel->curstate.modelindex = lastViewModelIndex; + gunModel->curstate.frame = 0; + gunModel->curstate.colormap = 0; + gunModel->index = g_iUser2; + } + else + { + gunModel->model = NULL; // disable weaopn model + } + } + else + { + // only get viewangles from entity + VectorCopy ( ent->angles, pparams->cl_viewangles ); + pparams->cl_viewangles[PITCH]*=-3.0f; // see CL_ProcessEntityUpdate() + } + } + + v_frametime = pparams->frametime; + + if ( pparams->nextView == 0 ) + { + // first renderer cycle, full screen + + switch ( g_iUser1 ) + { + case OBS_CHASE_LOCKED: V_GetChasePos( g_iUser2, NULL, v_origin, v_angles ); + break; + + case OBS_CHASE_FREE: V_GetChasePos( g_iUser2, v_cl_angles, v_origin, v_angles ); + break; + + case OBS_ROAMING : VectorCopy (v_cl_angles, v_angles); + VectorCopy (v_sim_org, v_origin); + break; + + case OBS_IN_EYE : V_CalcNormalRefdef ( pparams ); + break; + + case OBS_MAP_FREE : pparams->onlyClientDraw = true; + V_GetMapFreePosition( v_cl_angles, v_origin, v_angles ); + break; + + case OBS_MAP_CHASE : pparams->onlyClientDraw = true; + V_GetMapChasePosition( g_iUser2, v_cl_angles, v_origin, v_angles ); + break; + } + + if ( gHUD.m_Spectator.m_pip->value ) + pparams->nextView = 1; // force a second renderer view + + gHUD.m_Spectator.m_iDrawCycle = 0; + + } + else + { + // second renderer cycle, inset window + + // set inset parameters + pparams->viewport[0] = XRES(gHUD.m_Spectator.m_OverviewData.insetWindowX); // change viewport to inset window + pparams->viewport[1] = YRES(gHUD.m_Spectator.m_OverviewData.insetWindowY); + pparams->viewport[2] = XRES(gHUD.m_Spectator.m_OverviewData.insetWindowWidth); + pparams->viewport[3] = YRES(gHUD.m_Spectator.m_OverviewData.insetWindowHeight); + pparams->nextView = 0; // on further view + + // override some settings in certain modes + switch ( (int)gHUD.m_Spectator.m_pip->value ) + { + case INSET_CHASE_FREE : V_GetChasePos( g_iUser2, v_cl_angles, v_origin, v_angles ); + break; + + case INSET_IN_EYE : V_CalcNormalRefdef ( pparams ); + break; + + case INSET_MAP_FREE : pparams->onlyClientDraw = true; + V_GetMapFreePosition( v_cl_angles, v_origin, v_angles ); + break; + + case INSET_MAP_CHASE : pparams->onlyClientDraw = true; + + if ( g_iUser1 == OBS_ROAMING ) + V_GetMapChasePosition( 0, v_cl_angles, v_origin, v_angles ); + else + V_GetMapChasePosition( g_iUser2, v_cl_angles, v_origin, v_angles ); + + break; + } + + gHUD.m_Spectator.m_iDrawCycle = 1; + } + + // write back new values into pparams + VectorCopy ( v_cl_angles, pparams->cl_viewangles ); + VectorCopy ( v_angles, pparams->viewangles ) + VectorCopy ( v_origin, pparams->vieworg ); + +} + + + +void DLLEXPORT V_CalcRefdef( struct ref_params_s *pparams ) +{ + // intermission / finale rendering + if ( pparams->intermission ) + { + V_CalcIntermissionRefdef ( pparams ); + } + else if ( pparams->spectator || g_iUser1 ) // g_iUser true if in spectator mode + { + V_CalcSpectatorRefdef ( pparams ); + } + else if ( !pparams->paused ) + { + V_CalcNormalRefdef ( pparams ); + } + +/* +// Example of how to overlay the whole screen with red at 50 % alpha +#define SF_TEST +#if defined SF_TEST + { + screenfade_t sf; + gEngfuncs.pfnGetScreenFade( &sf ); + + sf.fader = 255; + sf.fadeg = 0; + sf.fadeb = 0; + sf.fadealpha = 128; + sf.fadeFlags = FFADE_STAYOUT | FFADE_OUT; + + gEngfuncs.pfnSetScreenFade( &sf ); + } +#endif +*/ +} + +/* +============= +V_DropPunchAngle + +============= +*/ +void V_DropPunchAngle ( float frametime, float *ev_punchangle ) +{ + float len; + + len = VectorNormalize ( ev_punchangle ); + len -= (10.0 + len * 0.5) * frametime; + len = max( len, 0.0 ); + VectorScale ( ev_punchangle, len, ev_punchangle ); +} + +/* +============= +V_PunchAxis + +Client side punch effect +============= +*/ +void V_PunchAxis( int axis, float punch ) +{ + ev_punchangle[ axis ] = punch; +} + +/* +============= +V_Init +============= +*/ +void V_Init (void) +{ + gEngfuncs.pfnAddCommand ("centerview", V_StartPitchDrift ); + + scr_ofsx = gEngfuncs.pfnRegisterVariable( "scr_ofsx","0", 0 ); + scr_ofsy = gEngfuncs.pfnRegisterVariable( "scr_ofsy","0", 0 ); + scr_ofsz = gEngfuncs.pfnRegisterVariable( "scr_ofsz","0", 0 ); + + v_centermove = gEngfuncs.pfnRegisterVariable( "v_centermove", "0.15", 0 ); + v_centerspeed = gEngfuncs.pfnRegisterVariable( "v_centerspeed","500", 0 ); + + cl_bobcycle = gEngfuncs.pfnRegisterVariable( "cl_bobcycle","0.8", 0 );// best default for my experimental gun wag (sjb) + cl_bob = gEngfuncs.pfnRegisterVariable( "cl_bob","0.01", 0 );// best default for my experimental gun wag (sjb) + cl_bobup = gEngfuncs.pfnRegisterVariable( "cl_bobup","0.5", 0 ); + cl_waterdist = gEngfuncs.pfnRegisterVariable( "cl_waterdist","4", 0 ); + cl_chasedist = gEngfuncs.pfnRegisterVariable( "cl_chasedist","112", 0 ); +} + + +//#define TRACE_TEST +#if defined( TRACE_TEST ) + +extern float in_fov; +/* +==================== +CalcFov +==================== +*/ +float CalcFov (float fov_x, float width, float height) +{ + float a; + float x; + + if (fov_x < 1 || fov_x > 179) + fov_x = 90; // error, set to 90 + + x = width/tan(fov_x/360*M_PI); + + a = atan (height/x); + + a = a*360/M_PI; + + return a; +} + +int hitent = -1; + +void V_Move( int mx, int my ) +{ + float fov; + float fx, fy; + float dx, dy; + float c_x, c_y; + float dX, dY; + vec3_t forward, up, right; + vec3_t newangles; + + vec3_t farpoint; + pmtrace_t tr; + + fov = CalcFov( in_fov, (float)ScreenWidth, (float)ScreenHeight ); + + c_x = (float)ScreenWidth / 2.0; + c_y = (float)ScreenHeight / 2.0; + + dx = (float)mx - c_x; + dy = (float)my - c_y; + + // Proportion we moved in each direction + fx = dx / c_x; + fy = dy / c_y; + + dX = fx * in_fov / 2.0 ; + dY = fy * fov / 2.0; + + newangles = v_angles; + + newangles[ YAW ] -= dX; + newangles[ PITCH ] += dY; + + // Now rotate v_forward around that point + AngleVectors ( newangles, forward, right, up ); + + farpoint = v_origin + 8192 * forward; + + // Trace + tr = *(gEngfuncs.PM_TraceLine( (float *)&v_origin, (float *)&farpoint, PM_TRACELINE_PHYSENTSONLY, 2 /*point sized hull*/, -1 )); + + if ( tr.fraction != 1.0 && tr.ent != 0 ) + { + hitent = PM_GetPhysEntInfo( tr.ent ); + PM_ParticleLine( (float *)&v_origin, (float *)&tr.endpos, 5, 1.0, 0.0 ); + } + else + { + hitent = -1; + } +} + +#endif diff --git a/hlsdk/cl_dll/view.h b/hlsdk/cl_dll/view.h new file mode 100644 index 00000000..acd0bfb0 --- /dev/null +++ b/hlsdk/cl_dll/view.h @@ -0,0 +1,15 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined ( VIEWH ) +#define VIEWH +#pragma once + +void V_StartPitchDrift( void ); +void V_StopPitchDrift( void ); + +#endif // !VIEWH \ No newline at end of file diff --git a/hlsdk/cl_dll/wrect.h b/hlsdk/cl_dll/wrect.h new file mode 100644 index 00000000..620b8163 --- /dev/null +++ b/hlsdk/cl_dll/wrect.h @@ -0,0 +1,16 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( WRECTH ) +#define WRECTH + +typedef struct rect_s +{ + int left, right, top, bottom; +} wrect_t; + +#endif \ No newline at end of file diff --git a/hlsdk/common/beamdef.h b/hlsdk/common/beamdef.h new file mode 100644 index 00000000..acbe6def --- /dev/null +++ b/hlsdk/common/beamdef.h @@ -0,0 +1,64 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined ( BEAMDEFH ) +#define BEAMDEFH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +#define FBEAM_STARTENTITY 0x00000001 +#define FBEAM_ENDENTITY 0x00000002 +#define FBEAM_FADEIN 0x00000004 +#define FBEAM_FADEOUT 0x00000008 +#define FBEAM_SINENOISE 0x00000010 +#define FBEAM_SOLID 0x00000020 +#define FBEAM_SHADEIN 0x00000040 +#define FBEAM_SHADEOUT 0x00000080 +#define FBEAM_STARTVISIBLE 0x10000000 // Has this client actually seen this beam's start entity yet? +#define FBEAM_ENDVISIBLE 0x20000000 // Has this client actually seen this beam's end entity yet? +#define FBEAM_ISACTIVE 0x40000000 +#define FBEAM_FOREVER 0x80000000 + +typedef struct beam_s BEAM; +struct beam_s +{ + BEAM *next; + int type; + int flags; + vec3_t source; + vec3_t target; + vec3_t delta; + float t; // 0 .. 1 over lifetime of beam + float freq; + float die; + float width; + float amplitude; + float r, g, b; + float brightness; + float speed; + float frameRate; + float frame; + int segments; + int startEntity; + int endEntity; + int modelIndex; + int frameCount; + struct model_s *pFollowModel; + struct particle_s *particles; +}; + +#endif diff --git a/hlsdk/common/cl_entity.h b/hlsdk/common/cl_entity.h new file mode 100644 index 00000000..d3cc0dae --- /dev/null +++ b/hlsdk/common/cl_entity.h @@ -0,0 +1,117 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// cl_entity.h +#if !defined( CL_ENTITYH ) +#define CL_ENTITYH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +typedef struct efrag_s +{ + struct mleaf_s *leaf; + struct efrag_s *leafnext; + struct cl_entity_s *entity; + struct efrag_s *entnext; +} efrag_t; + +typedef struct +{ + byte mouthopen; // 0 = mouth closed, 255 = mouth agape + byte sndcount; // counter for running average + int sndavg; // running average +} mouth_t; + +typedef struct +{ + float prevanimtime; + float sequencetime; + byte prevseqblending[2]; + vec3_t prevorigin; + vec3_t prevangles; + + int prevsequence; + float prevframe; + + byte prevcontroller[4]; + byte prevblending[2]; +} latchedvars_t; + +typedef struct +{ + // Time stamp for this movement + float animtime; + + vec3_t origin; + vec3_t angles; +} position_history_t; + +typedef struct cl_entity_s cl_entity_t; + +#define HISTORY_MAX 64 // Must be power of 2 +#define HISTORY_MASK ( HISTORY_MAX - 1 ) + + +#if !defined( ENTITY_STATEH ) +#include "entity_state.h" +#endif + +#if !defined( PROGS_H ) +#include "progs.h" +#endif + +struct cl_entity_s +{ + int index; // Index into cl_entities ( should match actual slot, but not necessarily ) + + qboolean player; // True if this entity is a "player" + + entity_state_t baseline; // The original state from which to delta during an uncompressed message + entity_state_t prevstate; // The state information from the penultimate message received from the server + entity_state_t curstate; // The state information from the last message received from server + + int current_position; // Last received history update index + position_history_t ph[ HISTORY_MAX ]; // History of position and angle updates for this player + + mouth_t mouth; // For synchronizing mouth movements. + + latchedvars_t latched; // Variables used by studio model rendering routines + + // Information based on interplocation, extrapolation, prediction, or just copied from last msg received. + // + float lastmove; + + // Actual render position and angles + vec3_t origin; + vec3_t angles; + + // Attachment points + vec3_t attachment[4]; + + // Other entity local information + int trivial_accept; + + struct model_s *model; // cl.model_precache[ curstate.modelindes ]; all visible entities have a model + struct efrag_s *efrag; // linked list of efrags + struct mnode_s *topnode; // for bmodels, first world node that splits bmodel, or NULL if not split + + float syncbase; // for client-side animations -- used by obsolete alias animation system, remove? + int visframe; // last frame this entity was found in an active leaf + colorVec cvFloorColor; +}; + +#endif // !CL_ENTITYH diff --git a/hlsdk/common/com_model.h b/hlsdk/common/com_model.h new file mode 100644 index 00000000..c97d0e22 --- /dev/null +++ b/hlsdk/common/com_model.h @@ -0,0 +1,362 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// com_model.h +#if !defined( COM_MODEL_H ) +#define COM_MODEL_H +#if defined( _WIN32 ) +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +#define STUDIO_RENDER 1 +#define STUDIO_EVENTS 2 + +#define MAX_CLIENTS 32 +#define MAX_EDICTS 900 + +#define MAX_MODEL_NAME 64 +#define MAX_MAP_HULLS 4 +#define MIPLEVELS 4 +#define NUM_AMBIENTS 4 // automatic ambient sounds +#define MAXLIGHTMAPS 4 +#define PLANE_ANYZ 5 + +#define ALIAS_Z_CLIP_PLANE 5 + +// flags in finalvert_t.flags +#define ALIAS_LEFT_CLIP 0x0001 +#define ALIAS_TOP_CLIP 0x0002 +#define ALIAS_RIGHT_CLIP 0x0004 +#define ALIAS_BOTTOM_CLIP 0x0008 +#define ALIAS_Z_CLIP 0x0010 +#define ALIAS_ONSEAM 0x0020 +#define ALIAS_XY_CLIP_MASK 0x000F + +#define ZISCALE ((float)0x8000) + +#define CACHE_SIZE 32 // used to align key data structures + +typedef enum +{ + mod_brush, + mod_sprite, + mod_alias, + mod_studio +} modtype_t; + +// must match definition in modelgen.h +#ifndef SYNCTYPE_T +#define SYNCTYPE_T + +typedef enum +{ + ST_SYNC=0, + ST_RAND +} synctype_t; + +#endif + +typedef struct +{ + float mins[3], maxs[3]; + float origin[3]; + int headnode[MAX_MAP_HULLS]; + int visleafs; // not including the solid leaf 0 + int firstface, numfaces; +} dmodel_t; + +// plane_t structure +#ifndef _MPLANE_DEFINED_ +#define _MPLANE_DEFINED_ +typedef struct mplane_s +{ + vec3_t normal; // surface normal + float dist; // closest appoach to origin + byte type; // for texture axis selection and fast side tests + byte signbits; // signx + signy<<1 + signz<<1 + byte pad[2]; +} mplane_t; +#endif + +typedef struct +{ + vec3_t position; +} mvertex_t; + +typedef struct +{ + unsigned short v[2]; + unsigned int cachededgeoffset; +} medge_t; + +typedef struct texture_s +{ + char name[16]; + unsigned width, height; + int anim_total; // total tenths in sequence ( 0 = no) + int anim_min, anim_max; // time for this frame min <=time< max + struct texture_s *anim_next; // in the animation sequence + struct texture_s *alternate_anims; // bmodels in frame 1 use these + unsigned offsets[MIPLEVELS]; // four mip maps stored + unsigned paloffset; +} texture_t; + +typedef struct +{ + float vecs[2][4]; // [s/t] unit vectors in world space. + // [i][3] is the s/t offset relative to the origin. + // s or t = dot(3Dpoint,vecs[i])+vecs[i][3] + float mipadjust; // ?? mipmap limits for very small surfaces + texture_t *texture; + int flags; // sky or slime, no lightmap or 256 subdivision +} mtexinfo_t; + +typedef struct mnode_s +{ +// common with leaf + int contents; // 0, to differentiate from leafs + int visframe; // node needs to be traversed if current + + short minmaxs[6]; // for bounding box culling + + struct mnode_s *parent; + +// node specific + mplane_t *plane; + struct mnode_s *children[2]; + + unsigned short firstsurface; + unsigned short numsurfaces; +} mnode_t; + +typedef struct msurface_s msurface_t; +typedef struct decal_s decal_t; + +// JAY: Compress this as much as possible +struct decal_s +{ + decal_t *pnext; // linked list for each surface + msurface_t *psurface; // Surface id for persistence / unlinking + short dx; // Offsets into surface texture (in texture coordinates, so we don't need floats) + short dy; + short texture; // Decal texture + byte scale; // Pixel scale + byte flags; // Decal flags + + short entityIndex; // Entity this is attached to +}; + +typedef struct mleaf_s +{ +// common with node + int contents; // wil be a negative contents number + int visframe; // node needs to be traversed if current + + short minmaxs[6]; // for bounding box culling + + struct mnode_s *parent; + +// leaf specific + byte *compressed_vis; + struct efrag_s *efrags; + + msurface_t **firstmarksurface; + int nummarksurfaces; + int key; // BSP sequence number for leaf's contents + byte ambient_sound_level[NUM_AMBIENTS]; +} mleaf_t; + +struct msurface_s +{ + int visframe; // should be drawn when node is crossed + + int dlightframe; // last frame the surface was checked by an animated light + int dlightbits; // dynamically generated. Indicates if the surface illumination + // is modified by an animated light. + + mplane_t *plane; // pointer to shared plane + int flags; // see SURF_ #defines + + int firstedge; // look up in model->surfedges[], negative numbers + int numedges; // are backwards edges + +// surface generation data + struct surfcache_s *cachespots[MIPLEVELS]; + + short texturemins[2]; // smallest s/t position on the surface. + short extents[2]; // ?? s/t texture size, 1..256 for all non-sky surfaces + + mtexinfo_t *texinfo; + +// lighting info + byte styles[MAXLIGHTMAPS]; // index into d_lightstylevalue[] for animated lights + // no one surface can be effected by more than 4 + // animated lights. + color24 *samples; + + decal_t *pdecals; +}; + +#ifndef _DCLIPNODE_DEFINED_ +#define _DCLIPNODE_DEFINED_ +typedef struct +{ + int planenum; + short children[2]; // negative numbers are contents +} dclipnode_t; +#endif + +#ifndef _HULL_DEFINED_ +#define _HULL_DEFINED_ +typedef struct hull_s +{ + dclipnode_t *clipnodes; + mplane_t *planes; + int firstclipnode; + int lastclipnode; + vec3_t clip_mins; + vec3_t clip_maxs; +} hull_t; +#endif + +#if !defined( CACHE_USER ) && !defined( QUAKEDEF_H ) +#define CACHE_USER +typedef struct cache_user_s +{ + void *data; +} cache_user_t; +#endif + +typedef struct model_s +{ + char name[ MAX_MODEL_NAME ]; + qboolean needload; // bmodels and sprites don't cache normally + + modtype_t type; + int numframes; + synctype_t synctype; + + int flags; + +// +// volume occupied by the model +// + vec3_t mins, maxs; + float radius; + +// +// brush model +// + int firstmodelsurface, nummodelsurfaces; + + int numsubmodels; + dmodel_t *submodels; + + int numplanes; + mplane_t *planes; + + int numleafs; // number of visible leafs, not counting 0 + struct mleaf_s *leafs; + + int numvertexes; + mvertex_t *vertexes; + + int numedges; + medge_t *edges; + + int numnodes; + mnode_t *nodes; + + int numtexinfo; + mtexinfo_t *texinfo; + + int numsurfaces; + msurface_t *surfaces; + + int numsurfedges; + int *surfedges; + + int numclipnodes; + dclipnode_t *clipnodes; + + int nummarksurfaces; + msurface_t **marksurfaces; + + hull_t hulls[MAX_MAP_HULLS]; + + int numtextures; + texture_t **textures; + + byte *visdata; + + color24 *lightdata; + + char *entities; + +// +// additional model data +// + cache_user_t cache; // only access through Mod_Extradata + +} model_t; + +typedef vec_t vec4_t[4]; + +typedef struct alight_s +{ + int ambientlight; // clip at 128 + int shadelight; // clip at 192 - ambientlight + vec3_t color; + float *plightvec; +} alight_t; + +typedef struct auxvert_s +{ + float fv[3]; // viewspace x, y +} auxvert_t; + +#include "custom.h" + +#define MAX_INFO_STRING 256 +#define MAX_SCOREBOARDNAME 32 +typedef struct player_info_s +{ + // User id on server + int userid; + + // User info string + char userinfo[ MAX_INFO_STRING ]; + + // Name + char name[ MAX_SCOREBOARDNAME ]; + + // Spectator or not, unused + int spectator; + + int ping; + int packet_loss; + + // skin information + char model[MAX_QPATH]; + int topcolor; + int bottomcolor; + + // last frame rendered + int renderframe; + + // Gait frame estimation + int gaitsequence; + float gaitframe; + float gaityaw; + vec3_t prevgaitorigin; + + customization_t customdata; +} player_info_t; + +#endif // #define COM_MODEL_H diff --git a/hlsdk/common/con_nprint.h b/hlsdk/common/con_nprint.h new file mode 100644 index 00000000..aef6f0e1 --- /dev/null +++ b/hlsdk/common/con_nprint.h @@ -0,0 +1,33 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( CON_NPRINTH ) +#define CON_NPRINTH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +typedef struct con_nprint_s +{ + int index; // Row # + float time_to_live; // # of seconds before it dissappears + float color[ 3 ]; // RGB colors ( 0.0 -> 1.0 scale ) +} con_nprint_t; + +void Con_NPrintf( int idx, char *fmt, ... ); +void Con_NXPrintf( struct con_nprint_s *info, char *fmt, ... ); + +#endif diff --git a/hlsdk/common/const.h b/hlsdk/common/const.h new file mode 100644 index 00000000..e2cf6bc4 --- /dev/null +++ b/hlsdk/common/const.h @@ -0,0 +1,776 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef CONST_H +#define CONST_H +// +// Constants shared by the engine and dlls +// This header file included by engine files and DLL files. +// Most came from server.h + +// edict->flags +#define FL_FLY (1<<0) // Changes the SV_Movestep() behavior to not need to be on ground +#define FL_SWIM (1<<1) // Changes the SV_Movestep() behavior to not need to be on ground (but stay in water) +#define FL_CONVEYOR (1<<2) +#define FL_CLIENT (1<<3) +#define FL_INWATER (1<<4) +#define FL_MONSTER (1<<5) +#define FL_GODMODE (1<<6) +#define FL_NOTARGET (1<<7) +#define FL_SKIPLOCALHOST (1<<8) // Don't send entity to local host, it's predicting this entity itself +#define FL_ONGROUND (1<<9) // At rest / on the ground +#define FL_PARTIALGROUND (1<<10) // not all corners are valid +#define FL_WATERJUMP (1<<11) // player jumping out of water +#define FL_FROZEN (1<<12) // Player is frozen for 3rd person camera +#define FL_FAKECLIENT (1<<13) // JAC: fake client, simulated server side; don't send network messages to them +#define FL_DUCKING (1<<14) // Player flag -- Player is fully crouched +#define FL_FLOAT (1<<15) // Apply floating force to this entity when in water +#define FL_GRAPHED (1<<16) // worldgraph has this ent listed as something that blocks a connection + +// UNDONE: Do we need these? +#define FL_IMMUNE_WATER (1<<17) +#define FL_IMMUNE_SLIME (1<<18) +#define FL_IMMUNE_LAVA (1<<19) + +#define FL_PROXY (1<<20) // This is a spectator proxy +#define FL_ALWAYSTHINK (1<<21) // Brush model flag -- call think every frame regardless of nextthink - ltime (for constantly changing velocity/path) +#define FL_BASEVELOCITY (1<<22) // Base velocity has been applied this frame (used to convert base velocity into momentum) +#define FL_MONSTERCLIP (1<<23) // Only collide in with monsters who have FL_MONSTERCLIP set +#define FL_ONTRAIN (1<<24) // Player is _controlling_ a train, so movement commands should be ignored on client during prediction. +#define FL_WORLDBRUSH (1<<25) // Not moveable/removeable brush entity (really part of the world, but represented as an entity for transparency or something) +#define FL_SPECTATOR (1<<26) // This client is a spectator, don't run touch functions, etc. +#define FL_CUSTOMENTITY (1<<29) // This is a custom entity +#define FL_KILLME (1<<30) // This entity is marked for death -- This allows the engine to kill ents at the appropriate time +#define FL_DORMANT (1<<31) // Entity is dormant, no updates to client + + +// Goes into globalvars_t.trace_flags +#define FTRACE_SIMPLEBOX (1<<0) // Traceline with a simple box + + +// walkmove modes +#define WALKMOVE_NORMAL 0 // normal walkmove +#define WALKMOVE_WORLDONLY 1 // doesn't hit ANY entities, no matter what the solid type +#define WALKMOVE_CHECKONLY 2 // move, but don't touch triggers + +// edict->movetype values +#define MOVETYPE_NONE 0 // never moves +//#define MOVETYPE_ANGLENOCLIP 1 +//#define MOVETYPE_ANGLECLIP 2 +#define MOVETYPE_WALK 3 // Player only - moving on the ground +#define MOVETYPE_STEP 4 // gravity, special edge handling -- monsters use this +#define MOVETYPE_FLY 5 // No gravity, but still collides with stuff +#define MOVETYPE_TOSS 6 // gravity/collisions +#define MOVETYPE_PUSH 7 // no clip to world, push and crush +#define MOVETYPE_NOCLIP 8 // No gravity, no collisions, still do velocity/avelocity +#define MOVETYPE_FLYMISSILE 9 // extra size to monsters +#define MOVETYPE_BOUNCE 10 // Just like Toss, but reflect velocity when contacting surfaces +#define MOVETYPE_BOUNCEMISSILE 11 // bounce w/o gravity +#define MOVETYPE_FOLLOW 12 // track movement of aiment +#define MOVETYPE_PUSHSTEP 13 // BSP model that needs physics/world collisions (uses nearest hull for world collision) + +// edict->solid values +// NOTE: Some movetypes will cause collisions independent of SOLID_NOT/SOLID_TRIGGER when the entity moves +// SOLID only effects OTHER entities colliding with this one when they move - UGH! +#define SOLID_NOT 0 // no interaction with other objects +#define SOLID_TRIGGER 1 // touch on edge, but not blocking +#define SOLID_BBOX 2 // touch on edge, block +#define SOLID_SLIDEBOX 3 // touch on edge, but not an onground +#define SOLID_BSP 4 // bsp clip, touch on edge, block + +// edict->deadflag values +#define DEAD_NO 0 // alive +#define DEAD_DYING 1 // playing death animation or still falling off of a ledge waiting to hit ground +#define DEAD_DEAD 2 // dead. lying still. +#define DEAD_RESPAWNABLE 3 +#define DEAD_DISCARDBODY 4 + +#define DAMAGE_NO 0 +#define DAMAGE_YES 1 +#define DAMAGE_AIM 2 + +// entity effects +#define EF_BRIGHTFIELD 1 // swirling cloud of particles +#define EF_MUZZLEFLASH 2 // single frame ELIGHT on entity attachment 0 +#define EF_BRIGHTLIGHT 4 // DLIGHT centered at entity origin +#define EF_DIMLIGHT 8 // player flashlight +#define EF_INVLIGHT 16 // get lighting from ceiling +#define EF_NOINTERP 32 // don't interpolate the next frame +#define EF_LIGHT 64 // rocket flare glow sprite +#define EF_NODRAW 128 // don't draw entity + +// entity flags +#define EFLAG_SLERP 1 // do studio interpolation of this entity + +// +// temp entity events +// +#define TE_BEAMPOINTS 0 // beam effect between two points +// coord coord coord (start position) +// coord coord coord (end position) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_BEAMENTPOINT 1 // beam effect between point and entity +// short (start entity) +// coord coord coord (end position) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_GUNSHOT 2 // particle effect plus ricochet sound +// coord coord coord (position) + +#define TE_EXPLOSION 3 // additive sprite, 2 dynamic lights, flickering particles, explosion sound, move vertically 8 pps +// coord coord coord (position) +// short (sprite index) +// byte (scale in 0.1's) +// byte (framerate) +// byte (flags) +// +// The Explosion effect has some flags to control performance/aesthetic features: +#define TE_EXPLFLAG_NONE 0 // all flags clear makes default Half-Life explosion +#define TE_EXPLFLAG_NOADDITIVE 1 // sprite will be drawn opaque (ensure that the sprite you send is a non-additive sprite) +#define TE_EXPLFLAG_NODLIGHTS 2 // do not render dynamic lights +#define TE_EXPLFLAG_NOSOUND 4 // do not play client explosion sound +#define TE_EXPLFLAG_NOPARTICLES 8 // do not draw particles + + +#define TE_TAREXPLOSION 4 // Quake1 "tarbaby" explosion with sound +// coord coord coord (position) + +#define TE_SMOKE 5 // alphablend sprite, move vertically 30 pps +// coord coord coord (position) +// short (sprite index) +// byte (scale in 0.1's) +// byte (framerate) + +#define TE_TRACER 6 // tracer effect from point to point +// coord, coord, coord (start) +// coord, coord, coord (end) + +#define TE_LIGHTNING 7 // TE_BEAMPOINTS with simplified parameters +// coord, coord, coord (start) +// coord, coord, coord (end) +// byte (life in 0.1's) +// byte (width in 0.1's) +// byte (amplitude in 0.01's) +// short (sprite model index) + +#define TE_BEAMENTS 8 +// short (start entity) +// short (end entity) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_SPARKS 9 // 8 random tracers with gravity, ricochet sprite +// coord coord coord (position) + +#define TE_LAVASPLASH 10 // Quake1 lava splash +// coord coord coord (position) + +#define TE_TELEPORT 11 // Quake1 teleport splash +// coord coord coord (position) + +#define TE_EXPLOSION2 12 // Quake1 colormaped (base palette) particle explosion with sound +// coord coord coord (position) +// byte (starting color) +// byte (num colors) + +#define TE_BSPDECAL 13 // Decal from the .BSP file +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// short (texture index of precached decal texture name) +// short (entity index) +// [optional - only included if previous short is non-zero (not the world)] short (index of model of above entity) + +#define TE_IMPLOSION 14 // tracers moving toward a point +// coord, coord, coord (position) +// byte (radius) +// byte (count) +// byte (life in 0.1's) + +#define TE_SPRITETRAIL 15 // line of moving glow sprites with gravity, fadeout, and collisions +// coord, coord, coord (start) +// coord, coord, coord (end) +// short (sprite index) +// byte (count) +// byte (life in 0.1's) +// byte (scale in 0.1's) +// byte (velocity along vector in 10's) +// byte (randomness of velocity in 10's) + +#define TE_BEAM 16 // obsolete + +#define TE_SPRITE 17 // additive sprite, plays 1 cycle +// coord, coord, coord (position) +// short (sprite index) +// byte (scale in 0.1's) +// byte (brightness) + +#define TE_BEAMSPRITE 18 // A beam with a sprite at the end +// coord, coord, coord (start position) +// coord, coord, coord (end position) +// short (beam sprite index) +// short (end sprite index) + +#define TE_BEAMTORUS 19 // screen aligned beam ring, expands to max radius over lifetime +// coord coord coord (center position) +// coord coord coord (axis and radius) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_BEAMDISK 20 // disk that expands to max radius over lifetime +// coord coord coord (center position) +// coord coord coord (axis and radius) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_BEAMCYLINDER 21 // cylinder that expands to max radius over lifetime +// coord coord coord (center position) +// coord coord coord (axis and radius) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_BEAMFOLLOW 22 // create a line of decaying beam segments until entity stops moving +// short (entity:attachment to follow) +// short (sprite index) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte,byte,byte (color) +// byte (brightness) + +#define TE_GLOWSPRITE 23 +// coord, coord, coord (pos) short (model index) byte (scale / 10) + +#define TE_BEAMRING 24 // connect a beam ring to two entities +// short (start entity) +// short (end entity) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_STREAK_SPLASH 25 // oriented shower of tracers +// coord coord coord (start position) +// coord coord coord (direction vector) +// byte (color) +// short (count) +// short (base speed) +// short (ramdon velocity) + +#define TE_BEAMHOSE 26 // obsolete + +#define TE_DLIGHT 27 // dynamic light, effect world, minor entity effect +// coord, coord, coord (pos) +// byte (radius in 10's) +// byte byte byte (color) +// byte (brightness) +// byte (life in 10's) +// byte (decay rate in 10's) + +#define TE_ELIGHT 28 // point entity light, no world effect +// short (entity:attachment to follow) +// coord coord coord (initial position) +// coord (radius) +// byte byte byte (color) +// byte (life in 0.1's) +// coord (decay rate) + +#define TE_TEXTMESSAGE 29 +// short 1.2.13 x (-1 = center) +// short 1.2.13 y (-1 = center) +// byte Effect 0 = fade in/fade out + // 1 is flickery credits + // 2 is write out (training room) + +// 4 bytes r,g,b,a color1 (text color) +// 4 bytes r,g,b,a color2 (effect color) +// ushort 8.8 fadein time +// ushort 8.8 fadeout time +// ushort 8.8 hold time +// optional ushort 8.8 fxtime (time the highlight lags behing the leading text in effect 2) +// string text message (512 chars max sz string) +#define TE_LINE 30 +// coord, coord, coord startpos +// coord, coord, coord endpos +// short life in 0.1 s +// 3 bytes r, g, b + +#define TE_BOX 31 +// coord, coord, coord boxmins +// coord, coord, coord boxmaxs +// short life in 0.1 s +// 3 bytes r, g, b + +#define TE_KILLBEAM 99 // kill all beams attached to entity +// short (entity) + +#define TE_LARGEFUNNEL 100 +// coord coord coord (funnel position) +// short (sprite index) +// short (flags) + +#define TE_BLOODSTREAM 101 // particle spray +// coord coord coord (start position) +// coord coord coord (spray vector) +// byte (color) +// byte (speed) + +#define TE_SHOWLINE 102 // line of particles every 5 units, dies in 30 seconds +// coord coord coord (start position) +// coord coord coord (end position) + +#define TE_BLOOD 103 // particle spray +// coord coord coord (start position) +// coord coord coord (spray vector) +// byte (color) +// byte (speed) + +#define TE_DECAL 104 // Decal applied to a brush entity (not the world) +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// byte (texture index of precached decal texture name) +// short (entity index) + +#define TE_FIZZ 105 // create alpha sprites inside of entity, float upwards +// short (entity) +// short (sprite index) +// byte (density) + +#define TE_MODEL 106 // create a moving model that bounces and makes a sound when it hits +// coord, coord, coord (position) +// coord, coord, coord (velocity) +// angle (initial yaw) +// short (model index) +// byte (bounce sound type) +// byte (life in 0.1's) + +#define TE_EXPLODEMODEL 107 // spherical shower of models, picks from set +// coord, coord, coord (origin) +// coord (velocity) +// short (model index) +// short (count) +// byte (life in 0.1's) + +#define TE_BREAKMODEL 108 // box of models or sprites +// coord, coord, coord (position) +// coord, coord, coord (size) +// coord, coord, coord (velocity) +// byte (random velocity in 10's) +// short (sprite or model index) +// byte (count) +// byte (life in 0.1 secs) +// byte (flags) + +#define TE_GUNSHOTDECAL 109 // decal and ricochet sound +// coord, coord, coord (position) +// short (entity index???) +// byte (decal???) + +#define TE_SPRITE_SPRAY 110 // spay of alpha sprites +// coord, coord, coord (position) +// coord, coord, coord (velocity) +// short (sprite index) +// byte (count) +// byte (speed) +// byte (noise) + +#define TE_ARMOR_RICOCHET 111 // quick spark sprite, client ricochet sound. +// coord, coord, coord (position) +// byte (scale in 0.1's) + +#define TE_PLAYERDECAL 112 // ??? +// byte (playerindex) +// coord, coord, coord (position) +// short (entity???) +// byte (decal number???) +// [optional] short (model index???) + +#define TE_BUBBLES 113 // create alpha sprites inside of box, float upwards +// coord, coord, coord (min start position) +// coord, coord, coord (max start position) +// coord (float height) +// short (model index) +// byte (count) +// coord (speed) + +#define TE_BUBBLETRAIL 114 // create alpha sprites along a line, float upwards +// coord, coord, coord (min start position) +// coord, coord, coord (max start position) +// coord (float height) +// short (model index) +// byte (count) +// coord (speed) + +#define TE_BLOODSPRITE 115 // spray of opaque sprite1's that fall, single sprite2 for 1..2 secs (this is a high-priority tent) +// coord, coord, coord (position) +// short (sprite1 index) +// short (sprite2 index) +// byte (color) +// byte (scale) + +#define TE_WORLDDECAL 116 // Decal applied to the world brush +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// byte (texture index of precached decal texture name) + +#define TE_WORLDDECALHIGH 117 // Decal (with texture index > 256) applied to world brush +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// byte (texture index of precached decal texture name - 256) + +#define TE_DECALHIGH 118 // Same as TE_DECAL, but the texture index was greater than 256 +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// byte (texture index of precached decal texture name - 256) +// short (entity index) + +#define TE_PROJECTILE 119 // Makes a projectile (like a nail) (this is a high-priority tent) +// coord, coord, coord (position) +// coord, coord, coord (velocity) +// short (modelindex) +// byte (life) +// byte (owner) projectile won't collide with owner (if owner == 0, projectile will hit any client). + +#define TE_SPRAY 120 // Throws a shower of sprites or models +// coord, coord, coord (position) +// coord, coord, coord (direction) +// short (modelindex) +// byte (count) +// byte (speed) +// byte (noise) +// byte (rendermode) + +#define TE_PLAYERSPRITES 121 // sprites emit from a player's bounding box (ONLY use for players!) +// byte (playernum) +// short (sprite modelindex) +// byte (count) +// byte (variance) (0 = no variance in size) (10 = 10% variance in size) + +#define TE_PARTICLEBURST 122 // very similar to lavasplash. +// coord (origin) +// short (radius) +// byte (particle color) +// byte (duration * 10) (will be randomized a bit) + +#define TE_FIREFIELD 123 // makes a field of fire. +// coord (origin) +// short (radius) (fire is made in a square around origin. -radius, -radius to radius, radius) +// short (modelindex) +// byte (count) +// byte (flags) +// byte (duration (in seconds) * 10) (will be randomized a bit) +// +// to keep network traffic low, this message has associated flags that fit into a byte: +#define TEFIRE_FLAG_ALLFLOAT 1 // all sprites will drift upwards as they animate +#define TEFIRE_FLAG_SOMEFLOAT 2 // some of the sprites will drift upwards. (50% chance) +#define TEFIRE_FLAG_LOOP 4 // if set, sprite plays at 15 fps, otherwise plays at whatever rate stretches the animation over the sprite's duration. +#define TEFIRE_FLAG_ALPHA 8 // if set, sprite is rendered alpha blended at 50% else, opaque +#define TEFIRE_FLAG_PLANAR 16 // if set, all fire sprites have same initial Z instead of randomly filling a cube. + +#define TE_PLAYERATTACHMENT 124 // attaches a TENT to a player (this is a high-priority tent) +// byte (entity index of player) +// coord (vertical offset) ( attachment origin.z = player origin.z + vertical offset ) +// short (model index) +// short (life * 10 ); + +#define TE_KILLPLAYERATTACHMENTS 125 // will expire all TENTS attached to a player. +// byte (entity index of player) + +#define TE_MULTIGUNSHOT 126 // much more compact shotgun message +// This message is used to make a client approximate a 'spray' of gunfire. +// Any weapon that fires more than one bullet per frame and fires in a bit of a spread is +// a good candidate for MULTIGUNSHOT use. (shotguns) +// +// NOTE: This effect makes the client do traces for each bullet, these client traces ignore +// entities that have studio models.Traces are 4096 long. +// +// coord (origin) +// coord (origin) +// coord (origin) +// coord (direction) +// coord (direction) +// coord (direction) +// coord (x noise * 100) +// coord (y noise * 100) +// byte (count) +// byte (bullethole decal texture index) + +#define TE_USERTRACER 127 // larger message than the standard tracer, but allows some customization. +// coord (origin) +// coord (origin) +// coord (origin) +// coord (velocity) +// coord (velocity) +// coord (velocity) +// byte ( life * 10 ) +// byte ( color ) this is an index into an array of color vectors in the engine. (0 - ) +// byte ( length * 10 ) + + + +#define MSG_BROADCAST 0 // unreliable to all +#define MSG_ONE 1 // reliable to one (msg_entity) +#define MSG_ALL 2 // reliable to all +#define MSG_INIT 3 // write to the init string +#define MSG_PVS 4 // Ents in PVS of org +#define MSG_PAS 5 // Ents in PAS of org +#define MSG_PVS_R 6 // Reliable to PVS +#define MSG_PAS_R 7 // Reliable to PAS +#define MSG_ONE_UNRELIABLE 8 // Send to one client, but don't put in reliable stream, put in unreliable datagram ( could be dropped ) +#define MSG_SPEC 9 // Sends to all spectator proxies + +// contents of a spot in the world +#define CONTENTS_EMPTY -1 +#define CONTENTS_SOLID -2 +#define CONTENTS_WATER -3 +#define CONTENTS_SLIME -4 +#define CONTENTS_LAVA -5 +#define CONTENTS_SKY -6 +/* These additional contents constants are defined in bspfile.h +#define CONTENTS_ORIGIN -7 // removed at csg time +#define CONTENTS_CLIP -8 // changed to contents_solid +#define CONTENTS_CURRENT_0 -9 +#define CONTENTS_CURRENT_90 -10 +#define CONTENTS_CURRENT_180 -11 +#define CONTENTS_CURRENT_270 -12 +#define CONTENTS_CURRENT_UP -13 +#define CONTENTS_CURRENT_DOWN -14 + +#define CONTENTS_TRANSLUCENT -15 +*/ +#define CONTENTS_LADDER -16 + +#define CONTENT_FLYFIELD -17 +#define CONTENT_GRAVITY_FLYFIELD -18 +#define CONTENT_FOG -19 + +#define CONTENT_EMPTY -1 +#define CONTENT_SOLID -2 +#define CONTENT_WATER -3 +#define CONTENT_SLIME -4 +#define CONTENT_LAVA -5 +#define CONTENT_SKY -6 + +// channels +#define CHAN_AUTO 0 +#define CHAN_WEAPON 1 +#define CHAN_VOICE 2 +#define CHAN_ITEM 3 +#define CHAN_BODY 4 +#define CHAN_STREAM 5 // allocate stream channel from the static or dynamic area +#define CHAN_STATIC 6 // allocate channel from the static area +#define CHAN_NETWORKVOICE_BASE 7 // voice data coming across the network +#define CHAN_NETWORKVOICE_END 500 // network voice data reserves slots (CHAN_NETWORKVOICE_BASE through CHAN_NETWORKVOICE_END). + +// attenuation values +#define ATTN_NONE 0 +#define ATTN_NORM (float)0.8 +#define ATTN_IDLE (float)2 +#define ATTN_STATIC (float)1.25 + +// pitch values +#define PITCH_NORM 100 // non-pitch shifted +#define PITCH_LOW 95 // other values are possible - 0-255, where 255 is very high +#define PITCH_HIGH 120 + +// volume values +#define VOL_NORM 1.0 + +// plats +#define PLAT_LOW_TRIGGER 1 + +// Trains +#define SF_TRAIN_WAIT_RETRIGGER 1 +#define SF_TRAIN_START_ON 4 // Train is initially moving +#define SF_TRAIN_PASSABLE 8 // Train is not solid -- used to make water trains + +// buttons +#ifndef IN_BUTTONS_H +#include "in_buttons.h" +#endif + +// Break Model Defines + +#define BREAK_TYPEMASK 0x4F +#define BREAK_GLASS 0x01 +#define BREAK_METAL 0x02 +#define BREAK_FLESH 0x04 +#define BREAK_WOOD 0x08 + +#define BREAK_SMOKE 0x10 +#define BREAK_TRANS 0x20 +#define BREAK_CONCRETE 0x40 +#define BREAK_2 0x80 + +// Colliding temp entity sounds + +#define BOUNCE_GLASS BREAK_GLASS +#define BOUNCE_METAL BREAK_METAL +#define BOUNCE_FLESH BREAK_FLESH +#define BOUNCE_WOOD BREAK_WOOD +#define BOUNCE_SHRAP 0x10 +#define BOUNCE_SHELL 0x20 +#define BOUNCE_CONCRETE BREAK_CONCRETE +#define BOUNCE_SHOTSHELL 0x80 + +// Temp entity bounce sound types +#define TE_BOUNCE_NULL 0 +#define TE_BOUNCE_SHELL 1 +#define TE_BOUNCE_SHOTSHELL 2 + +// Rendering constants +enum +{ + kRenderNormal, // src + kRenderTransColor, // c*a+dest*(1-a) + kRenderTransTexture, // src*a+dest*(1-a) + kRenderGlow, // src*a+dest -- No Z buffer checks + kRenderTransAlpha, // src*srca+dest*(1-srca) + kRenderTransAdd, // src*a+dest +}; + +enum +{ + kRenderFxNone = 0, + kRenderFxPulseSlow, + kRenderFxPulseFast, + kRenderFxPulseSlowWide, + kRenderFxPulseFastWide, + kRenderFxFadeSlow, + kRenderFxFadeFast, + kRenderFxSolidSlow, + kRenderFxSolidFast, + kRenderFxStrobeSlow, + kRenderFxStrobeFast, + kRenderFxStrobeFaster, + kRenderFxFlickerSlow, + kRenderFxFlickerFast, + kRenderFxNoDissipation, + kRenderFxDistort, // Distort/scale/translate flicker + kRenderFxHologram, // kRenderFxDistort + distance fade + kRenderFxDeadPlayer, // kRenderAmt is the player index + kRenderFxExplode, // Scale up really big! + kRenderFxGlowShell, // Glowing Shell + kRenderFxClampMinScale, // Keep this sprite from getting very small (SPRITES only!) +}; + + +typedef int func_t; +typedef int string_t; + +typedef unsigned char byte; +typedef unsigned short word; +#define _DEF_BYTE_ + +#undef true +#undef false + +#ifndef __cplusplus +typedef enum {false, true} qboolean; +#else +typedef int qboolean; +#endif + +typedef struct +{ + byte r, g, b; +} color24; + +typedef struct +{ + unsigned r, g, b, a; +} colorVec; + +#ifdef _WIN32 +#pragma pack(push,2) +#endif + +typedef struct +{ + unsigned short r, g, b, a; +} PackedColorVec; + +#ifdef _WIN32 +#pragma pack(pop) +#endif +typedef struct link_s +{ + struct link_s *prev, *next; +} link_t; + +typedef struct edict_s edict_t; + +typedef struct +{ + vec3_t normal; + float dist; +} plane_t; + +typedef struct +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + qboolean inopen, inwater; + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + plane_t plane; // surface normal at impact + edict_t *ent; // entity the surface is on + int hitgroup; // 0 == generic, non zero is specific body part +} trace_t; + +#endif + diff --git a/hlsdk/common/crc.h b/hlsdk/common/crc.h new file mode 100644 index 00000000..06df7c59 --- /dev/null +++ b/hlsdk/common/crc.h @@ -0,0 +1,54 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* crc.h */ +#ifndef CRC_H +#define CRC_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +// MD5 Hash +typedef struct +{ + unsigned int buf[4]; + unsigned int bits[2]; + unsigned char in[64]; +} MD5Context_t; + + +typedef unsigned long CRC32_t; +void CRC32_Init(CRC32_t *pulCRC); +CRC32_t CRC32_Final(CRC32_t pulCRC); +void CRC32_ProcessBuffer(CRC32_t *pulCRC, void *p, int len); +void CRC32_ProcessByte(CRC32_t *pulCRC, unsigned char ch); +int CRC_File(CRC32_t *crcvalue, char *pszFileName); + +unsigned char COM_BlockSequenceCRCByte (unsigned char *base, int length, int sequence); + +void MD5Init(MD5Context_t *context); +void MD5Update(MD5Context_t *context, unsigned char const *buf, + unsigned int len); +void MD5Final(unsigned char digest[16], MD5Context_t *context); +void Transform(unsigned int buf[4], unsigned int const in[16]); + +int MD5_Hash_File(unsigned char digest[16], char *pszFileName, int bUsefopen, int bSeed, unsigned int seed[4]); +char *MD5_Print(unsigned char hash[16]); +int MD5_Hash_CachedFile(unsigned char digest[16], unsigned char *pCache, int nFileSize, int bSeed, unsigned int seed[4]); + +int CRC_MapFile(CRC32_t *crcvalue, char *pszFileName); + +#endif diff --git a/hlsdk/common/cvardef.h b/hlsdk/common/cvardef.h new file mode 100644 index 00000000..9e343634 --- /dev/null +++ b/hlsdk/common/cvardef.h @@ -0,0 +1,36 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef CVARDEF_H +#define CVARDEF_H + +#define FCVAR_ARCHIVE (1<<0) // set to cause it to be saved to vars.rc +#define FCVAR_USERINFO (1<<1) // changes the client's info string +#define FCVAR_SERVER (1<<2) // notifies players when changed +#define FCVAR_EXTDLL (1<<3) // defined by external DLL +#define FCVAR_CLIENTDLL (1<<4) // defined by the client dll +#define FCVAR_PROTECTED (1<<5) // It's a server cvar, but we don't send the data since it's a password, etc. Sends 1 if it's not bland/zero, 0 otherwise as value +#define FCVAR_SPONLY (1<<6) // This cvar cannot be changed by clients connected to a multiplayer server. +#define FCVAR_PRINTABLEONLY (1<<7) // This cvar's string cannot contain unprintable characters ( e.g., used for player name etc ). +#define FCVAR_UNLOGGED (1<<8) // If this is a FCVAR_SERVER, don't log changes to the log file / console if we are creating a log + +typedef struct cvar_s +{ + char *name; + char *string; + int flags; + float value; + struct cvar_s *next; +} cvar_t; +#endif diff --git a/hlsdk/common/demo_api.h b/hlsdk/common/demo_api.h new file mode 100644 index 00000000..72d3b7be --- /dev/null +++ b/hlsdk/common/demo_api.h @@ -0,0 +1,33 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined ( DEMO_APIH ) +#define DEMO_APIH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +typedef struct demo_api_s +{ + int ( *IsRecording ) ( void ); + int ( *IsPlayingback ) ( void ); + int ( *IsTimeDemo ) ( void ); + void ( *WriteBuffer ) ( int size, unsigned char *buffer ); +} demo_api_t; + +extern demo_api_t demoapi; + +#endif diff --git a/hlsdk/common/director_cmds.h b/hlsdk/common/director_cmds.h new file mode 100644 index 00000000..da999293 --- /dev/null +++ b/hlsdk/common/director_cmds.h @@ -0,0 +1,38 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// director_cmds.h +// sub commands for svc_director + +#define DRC_ACTIVE 0 // tells client that he's an spectator and will get director command +#define DRC_STATUS 1 // send status infos about proxy +#define DRC_CAMERA 2 // set the actual director camera position +#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_SIDE (1<<4) +#define DRC_FLAG_DRAMATIC (1<<5) + + + +// commands of the director API function CallDirectorProc(...) + +#define DRCAPI_NOP 0 // no operation +#define DRCAPI_ACTIVE 1 // de/acivates director mode in engine +#define DRCAPI_STATUS 2 // request proxy information +#define DRCAPI_SETCAM 3 // set camera n to given position and angle +#define DRCAPI_GETCAM 4 // request camera n position and angle +#define DRCAPI_DIRPLAY 5 // set director time and play with normal speed +#define DRCAPI_DIRFREEZE 6 // freeze directo at this time +#define DRCAPI_SETVIEWMODE 7 // overview or 4 cameras +#define DRCAPI_SETOVERVIEWPARAMS 8 // sets parameter for overview mode +#define DRCAPI_SETFOCUS 9 // set the camera which has the input focus +#define DRCAPI_GETTARGETS 10 // queries engine for player list +#define DRCAPI_SETVIEWPOINTS 11 // gives engine all waypoints + + diff --git a/hlsdk/common/dlight.h b/hlsdk/common/dlight.h new file mode 100644 index 00000000..0de4c4e4 --- /dev/null +++ b/hlsdk/common/dlight.h @@ -0,0 +1,35 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined ( DLIGHTH ) +#define DLIGHTH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +typedef struct dlight_s +{ + vec3_t origin; + float radius; + color24 color; + float die; // stop lighting after this time + float decay; // drop this each second + float minlight; // don't add when contributing less + int key; + qboolean dark; // subtracts light instead of adding +} dlight_t; + +#endif diff --git a/hlsdk/common/dll_state.h b/hlsdk/common/dll_state.h new file mode 100644 index 00000000..a929c796 --- /dev/null +++ b/hlsdk/common/dll_state.h @@ -0,0 +1,23 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +//DLL State Flags + +#define DLL_INACTIVE 0 // no dll +#define DLL_ACTIVE 1 // dll is running +#define DLL_PAUSED 2 // dll is paused +#define DLL_CLOSE 3 // closing down dll +#define DLL_TRANS 4 // Level Transition + +// DLL Pause reasons + +#define DLL_NORMAL 0 // User hit Esc or something. +#define DLL_QUIT 4 // Quit now +#define DLL_RESTART 6 // Switch to launcher for linux, does a quit but returns 1 + +// DLL Substate info ( not relevant ) +#define ENG_NORMAL (1<<0) diff --git a/hlsdk/common/engine_launcher_api.h b/hlsdk/common/engine_launcher_api.h new file mode 100644 index 00000000..53782117 --- /dev/null +++ b/hlsdk/common/engine_launcher_api.h @@ -0,0 +1,112 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// engine/launcher interface +#if !defined( ENGINE_LAUNCHER_APIH ) +#define ENGINE_LAUNCHER_APIH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +//typedef void ( *xcommand_t ) ( void ); + +#define RENDERTYPE_UNDEFINED 0 +#define RENDERTYPE_SOFTWARE 1 +#define RENDERTYPE_HARDWARE 2 + +#define ENGINE_LAUNCHER_API_VERSION 1 + +typedef struct engine_api_s +{ + int version; + int rendertype; + int size; + + // Functions + int ( *GetEngineState ) ( void ); + void ( *Cbuf_AddText ) ( char *text ); // append cmd at end of buf + void ( *Cbuf_InsertText ) ( char *text ); // insert cmd at start of buf + void ( *Cmd_AddCommand ) ( char *cmd_name, void ( *funcname )( void ) ); + int ( *Cmd_Argc ) ( void ); + char *( *Cmd_Args ) ( void ); + char *( *Cmd_Argv ) ( int arg ); + void ( *Con_Printf ) ( char *, ... ); + void ( *Con_SafePrintf ) ( char *, ... ); + void ( *Cvar_Set ) ( char *var_name, char *value ); + void ( *Cvar_SetValue ) ( char *var_name, float value ); + int ( *Cvar_VariableInt ) ( char *var_name ); + char *( *Cvar_VariableString ) ( char *var_name ); + float ( *Cvar_VariableValue ) ( char *var_name ); + void ( *ForceReloadProfile ) ( void ); + int ( *GetGameInfo ) ( struct GameInfo_s *pGI, char *pszChannel ); + void ( *GameSetBackground ) ( int bBack ); + void ( *GameSetState ) ( int iState ); + void ( *GameSetSubState ) ( int iState ); + int ( *GetPauseState ) ( void ); + int ( *Host_Frame ) ( float time, int iState, int *stateInfo ); + void ( *Host_GetHostInfo ) ( float *fps, int *nActive, int *nSpectators, int *nMaxPlayers, char *pszMap ); + void ( *Host_Shutdown ) ( void ); + int ( *Game_Init ) ( char *lpCmdLine, unsigned char *pMem, int iSize, struct exefuncs_s *pef, void *, int ); + void ( *IN_ActivateMouse ) ( void ); + void ( *IN_ClearStates ) ( void ); + void ( *IN_DeactivateMouse ) ( void ); + void ( *IN_MouseEvent ) ( int mstate ); + void ( *Keyboard_ReturnToGame ) ( void ); + void ( *Key_ClearStates ) ( void ); + void ( *Key_Event ) ( int key, int down ); + int ( *LoadGame ) ( const char *pszSlot ); + void ( *S_BlockSound ) ( void ); + void ( *S_ClearBuffer ) ( void ); + void ( *S_GetDSPointer ) ( struct IDirectSound **lpDS, struct IDirectSoundBuffer **lpDSBuf ); + void *( *S_GetWAVPointer ) ( void ); + void ( *S_UnblockSound ) ( void ); + int ( *SaveGame ) ( const char *pszSlot, const char *pszComment ); + void ( *SetAuth ) ( void *pobj ); + void ( *SetMessagePumpDisableMode ) ( int bMode ); + void ( *SetPauseState ) ( int bPause ); + void ( *SetStartupMode ) ( int bMode ); + void ( *SNDDMA_Shutdown ) ( void ); + void ( *Snd_AcquireBuffer ) ( void ); + void ( *Snd_ReleaseBuffer ) ( void ); + void ( *StoreProfile ) ( void ); + double ( *Sys_FloatTime ) ( void ); + void ( *VID_UpdateWindowVars ) ( void *prc, int x, int y ); + void ( *VID_UpdateVID ) ( struct viddef_s *pvid ); + + // VGUI interfaces + void ( *VGui_CallEngineSurfaceProc ) ( void* hwnd, unsigned int msg, unsigned int wparam, long lparam ); + + // notifications that the launcher is taking/giving focus to the engine + void ( *EngineTakingFocus ) ( void ); + void ( *LauncherTakingFocus ) ( void ); + +#ifdef _WIN32 + // Only filled in by rendertype RENDERTYPE_HARDWARE + void ( *GL_Init ) ( void ); + int ( *GL_SetMode ) ( HWND hwndGame, HDC *pmaindc, HGLRC *pbaseRC, int fD3D, const char *p, const char *pszCmdLine ); + void ( *GL_Shutdown ) ( HWND hwnd, HDC hdc, HGLRC hglrc ); + + void ( *QGL_D3DShared ) ( struct tagD3DGlobals *d3dGShared ); + + int ( WINAPI *glSwapBuffers ) ( HDC dc ); + void ( *DirectorProc ) ( unsigned int cmd, void * params ); +#else + // NOT USED IN LINUX!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + void ( *GL_Init ) ( void ); + void ( *GL_SetMode ) ( void ); + void ( *GL_Shutdown ) ( void ); + void ( *QGL_D3DShared ) ( void ); + void ( *glSwapBuffers ) ( void ); + void ( *DirectorProc ) ( void ); + // LINUX +#endif + +} engine_api_t; + +#endif // ENGINE_LAUNCHER_APIH diff --git a/hlsdk/common/entity_state.h b/hlsdk/common/entity_state.h new file mode 100644 index 00000000..b517df75 --- /dev/null +++ b/hlsdk/common/entity_state.h @@ -0,0 +1,195 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( ENTITY_STATEH ) +#define ENTITY_STATEH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +// For entityType below +#define ENTITY_NORMAL (1<<0) +#define ENTITY_BEAM (1<<1) + +// Entity state is used for the baseline and for delta compression of a packet of +// entities that is sent to a client. +typedef struct entity_state_s entity_state_t; + +struct entity_state_s +{ +// Fields which are filled in by routines outside of delta compression + int entityType; + // Index into cl_entities array for this entity. + int number; + float msg_time; + + // Message number last time the player/entity state was updated. + int messagenum; + + // Fields which can be transitted and reconstructed over the network stream + vec3_t origin; + vec3_t angles; + + int modelindex; + int sequence; + float frame; + int colormap; + short skin; + short solid; + int effects; + float scale; + + byte eflags; + + // Render information + int rendermode; + int renderamt; + color24 rendercolor; + int renderfx; + + int movetype; + float animtime; + float framerate; + int body; + byte controller[4]; + byte blending[4]; + vec3_t velocity; + + // Send bbox down to client for use during prediction. + vec3_t mins; + vec3_t maxs; + + int aiment; + // If owned by a player, the index of that player ( for projectiles ). + int owner; + + // Friction, for prediction. + float friction; + // Gravity multiplier + float gravity; + +// PLAYER SPECIFIC + int team; + int playerclass; + int health; + qboolean spectator; + int weaponmodel; + int gaitsequence; + // If standing on conveyor, e.g. + vec3_t basevelocity; + // Use the crouched hull, or the regular player hull. + int usehull; + // Latched buttons last time state updated. + int oldbuttons; + // -1 = in air, else pmove entity number + int onground; + int iStepLeft; + // How fast we are falling + float flFallVelocity; + + float fov; + int weaponanim; + + // Parametric movement overrides + vec3_t startpos; + vec3_t endpos; + float impacttime; + float starttime; + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; +}; + +#include "pm_info.h" + +typedef struct clientdata_s +{ + vec3_t origin; + vec3_t velocity; + + int viewmodel; + vec3_t punchangle; + int flags; + int waterlevel; + int watertype; + vec3_t view_ofs; + float health; + + int bInDuck; + + int weapons; // remove? + + int flTimeStepSound; + int flDuckTime; + int flSwimTime; + int waterjumptime; + + float maxspeed; + + float fov; + int weaponanim; + + int m_iId; + int ammo_shells; + int ammo_nails; + int ammo_cells; + int ammo_rockets; + float m_flNextAttack; + + int tfstate; + + int pushmsec; + + int deadflag; + + char physinfo[ MAX_PHYSINFO_STRING ]; + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; +} clientdata_t; + +#include "weaponinfo.h" + +typedef struct local_state_s +{ + entity_state_t playerstate; + clientdata_t client; + weapon_data_t weapondata[ 32 ]; +} local_state_t; + +#endif // !ENTITY_STATEH diff --git a/hlsdk/common/entity_types.h b/hlsdk/common/entity_types.h new file mode 100644 index 00000000..2ab96e3b --- /dev/null +++ b/hlsdk/common/entity_types.h @@ -0,0 +1,26 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// entity_types.h +#if !defined( ENTITY_TYPESH ) +#define ENTITY_TYPESH + +#define ET_NORMAL 0 +#define ET_PLAYER 1 +#define ET_TEMPENTITY 2 +#define ET_BEAM 3 +// BMODEL or SPRITE that was split across BSP nodes +#define ET_FRAGMENTED 4 + +#endif // !ENTITY_TYPESH diff --git a/hlsdk/common/event_api.h b/hlsdk/common/event_api.h new file mode 100644 index 00000000..a639ec08 --- /dev/null +++ b/hlsdk/common/event_api.h @@ -0,0 +1,53 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined ( EVENT_APIH ) +#define EVENT_APIH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +#define EVENT_API_VERSION 1 + +typedef struct event_api_s +{ + int version; + void ( *EV_PlaySound ) ( int ent, float *origin, int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch ); + void ( *EV_StopSound ) ( int ent, int channel, const char *sample ); + int ( *EV_FindModelIndex )( const char *pmodel ); + int ( *EV_IsLocal ) ( int playernum ); + int ( *EV_LocalPlayerDucking ) ( void ); + void ( *EV_LocalPlayerViewheight ) ( float * ); + void ( *EV_LocalPlayerBounds ) ( int hull, float *mins, float *maxs ); + int ( *EV_IndexFromTrace) ( struct pmtrace_s *pTrace ); + struct physent_s *( *EV_GetPhysent ) ( int idx ); + void ( *EV_SetUpPlayerPrediction ) ( int dopred, int bIncludeLocalClient ); + void ( *EV_PushPMStates ) ( void ); + void ( *EV_PopPMStates ) ( void ); + void ( *EV_SetSolidPlayers ) (int playernum); + void ( *EV_SetTraceHull ) ( int hull ); + void ( *EV_PlayerTrace ) ( float *start, float *end, int traceFlags, int ignore_pe, struct pmtrace_s *tr ); + void ( *EV_WeaponAnimation ) ( int sequence, int body ); + unsigned short ( *EV_PrecacheEvent ) ( int type, const char* psz ); + void ( *EV_PlaybackEvent ) ( int flags, const struct edict_s *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); + const char *( *EV_TraceTexture ) ( int ground, float *vstart, float *vend ); + void ( *EV_StopAllSounds ) ( int entnum, int entchannel ); + void ( *EV_KillEvents ) ( int entnum, const char *eventname ); +} event_api_t; + +extern event_api_t eventapi; + +#endif diff --git a/hlsdk/common/event_args.h b/hlsdk/common/event_args.h new file mode 100644 index 00000000..9ce46aef --- /dev/null +++ b/hlsdk/common/event_args.h @@ -0,0 +1,52 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( EVENT_ARGSH ) +#define EVENT_ARGSH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +// Event was invoked with stated origin +#define FEVENT_ORIGIN ( 1<<0 ) + +// Event was invoked with stated angles +#define FEVENT_ANGLES ( 1<<1 ) + +typedef struct event_args_s +{ + int flags; + + // Transmitted + int entindex; + + float origin[3]; + float angles[3]; + float velocity[3]; + + int ducking; + + float fparam1; + float fparam2; + + int iparam1; + int iparam2; + + int bparam1; + int bparam2; +} event_args_t; + +#endif diff --git a/hlsdk/common/event_flags.h b/hlsdk/common/event_flags.h new file mode 100644 index 00000000..e5d6424b --- /dev/null +++ b/hlsdk/common/event_flags.h @@ -0,0 +1,49 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( EVENT_FLAGSH ) +#define EVENT_FLAGSH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +// Skip local host for event send. +#define FEV_NOTHOST (1<<0) + +// Send the event reliably. You must specify the origin and angles and use +// PLAYBACK_EVENT_FULL for this to work correctly on the server for anything +// that depends on the event origin/angles. I.e., the origin/angles are not +// taken from the invoking edict for reliable events. +#define FEV_RELIABLE (1<<1) + +// Don't restrict to PAS/PVS, send this event to _everybody_ on the server ( useful for stopping CHAN_STATIC +// sounds started by client event when client is not in PVS anymore ( hwguy in TFC e.g. ). +#define FEV_GLOBAL (1<<2) + +// If this client already has one of these events in its queue, just update the event instead of sending it as a duplicate +// +#define FEV_UPDATE (1<<3) + +// Only send to entity specified as the invoker +#define FEV_HOSTONLY (1<<4) + +// Only send if the event was created on the server. +#define FEV_SERVER (1<<5) + +// Only issue event client side ( from shared code ) +#define FEV_CLIENT (1<<6) + +#endif diff --git a/hlsdk/common/exefuncs.h b/hlsdk/common/exefuncs.h new file mode 100644 index 00000000..353c0152 --- /dev/null +++ b/hlsdk/common/exefuncs.h @@ -0,0 +1,50 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// exefuncs.h +#ifndef EXEFUNCS_H +#define EXEFUNCS_H + +// Engine hands this to DLLs for functionality callbacks +typedef struct exefuncs_s +{ + int fMMX; + int iCPUMhz; + void (*unused1)(void); + void (*unused2)(void); + void (*unused3)(void); + void (*unused4)(void); + void (*VID_ForceLockState)(int lk); + int (*VID_ForceUnlockedAndReturnState)(void); + void (*unused5)(void); + void (*unused6)(void); + void (*unused7)(void); + void (*unused8)(void); + void (*unused9)(void); + void (*unused10)(void); + void (*unused11)(void); + void (*unused12)(void); + void (*unused13)(void); + void (*unused14)(void); + void (*unused15)(void); + void (*ErrorMessage)(int nLevel, const char *pszErrorMessage); + void (*unused16)(void); + void (*Sys_Printf)(char *fmt, ...); + void (*unused17)(void); + void (*unused18)(void); + void (*unused19)(void); + void (*unused20)(void); + void (*unused21)(void); + void (*unused22)(void); + void (*unused23)(void); + void (*unused24)(void); + void (*unused25)(void); + void (*unused26)(void); + void (*unused27)(void); +} exefuncs_t; + +#endif diff --git a/hlsdk/common/hltv.h b/hlsdk/common/hltv.h new file mode 100644 index 00000000..ff00d249 --- /dev/null +++ b/hlsdk/common/hltv.h @@ -0,0 +1,57 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// hltv.h +// all shared consts between server, clients and proxy + +#ifndef HLTV_H +#define HLTV_H + +#define TYPE_CLIENT 0 // client is a normal HL client (default) +#define TYPE_PROXY 1 // client is another proxy +#define TYPE_COMMENTATOR 3 // client is a commentator +#define TYPE_DEMO 4 // client is a demo file +// sub commands of svc_hltv: +#define HLTV_ACTIVE 0 // tells client that he's an spectator and will get director commands +#define HLTV_STATUS 1 // send status infos about proxy +#define HLTV_LISTEN 2 // tell client to listen to a multicast stream + +// 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 command +#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 12 + + + +// HLTV_EVENT event flags +#define DRC_FLAG_PRIO_MASK 0x0F // priorities between 0 and 15 (15 most important) +#define DRC_FLAG_SIDE (1<<4) // +#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 + + +#define MAX_DIRECTOR_CMD_PARAMETERS 4 +#define MAX_DIRECTOR_CMD_STRING 128 + + +#endif // HLTV_H diff --git a/hlsdk/common/in_buttons.h b/hlsdk/common/in_buttons.h new file mode 100644 index 00000000..1d724efb --- /dev/null +++ b/hlsdk/common/in_buttons.h @@ -0,0 +1,40 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef IN_BUTTONS_H +#define IN_BUTTONS_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +#define IN_ATTACK (1 << 0) +#define IN_JUMP (1 << 1) +#define IN_DUCK (1 << 2) +#define IN_FORWARD (1 << 3) +#define IN_BACK (1 << 4) +#define IN_USE (1 << 5) +#define IN_CANCEL (1 << 6) +#define IN_LEFT (1 << 7) +#define IN_RIGHT (1 << 8) +#define IN_MOVELEFT (1 << 9) +#define IN_MOVERIGHT (1 << 10) +#define IN_ATTACK2 (1 << 11) +#define IN_RUN (1 << 12) +#define IN_RELOAD (1 << 13) +#define IN_ALT1 (1 << 14) +#define IN_SCORE (1 << 15) // Used by client.dll for when scoreboard is held down + +#endif // IN_BUTTONS_H diff --git a/hlsdk/common/interface.cpp b/hlsdk/common/interface.cpp new file mode 100644 index 00000000..980d7ff6 --- /dev/null +++ b/hlsdk/common/interface.cpp @@ -0,0 +1,150 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include +#include "interface.h" + +#ifndef _WIN32 // LINUX +#include +#include // getcwd +#include // sprintf +#endif + + +// ------------------------------------------------------------------------------------ // +// InterfaceReg. +// ------------------------------------------------------------------------------------ // +InterfaceReg *InterfaceReg::s_pInterfaceRegs = NULL; + + +InterfaceReg::InterfaceReg( InstantiateInterfaceFn fn, const char *pName ) : + m_pName(pName) +{ + m_CreateFn = fn; + m_pNext = s_pInterfaceRegs; + s_pInterfaceRegs = this; +} + + + +// ------------------------------------------------------------------------------------ // +// CreateInterface. +// ------------------------------------------------------------------------------------ // +EXPORT_FUNCTION IBaseInterface *CreateInterface( const char *pName, int *pReturnCode ) +{ + InterfaceReg *pCur; + + for(pCur=InterfaceReg::s_pInterfaceRegs; pCur; pCur=pCur->m_pNext) + { + if(strcmp(pCur->m_pName, pName) == 0) + { + if ( pReturnCode ) + { + *pReturnCode = IFACE_OK; + } + return pCur->m_CreateFn(); + } + } + + if ( pReturnCode ) + { + *pReturnCode = IFACE_FAILED; + } + return NULL; +} + + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include "windows.h" +#endif + + +#ifdef _WIN32 +HINTERFACEMODULE Sys_LoadModule(const char *pModuleName) +{ + return (HINTERFACEMODULE)LoadLibrary(pModuleName); +} + +#else // LINUX +HINTERFACEMODULE Sys_LoadModule(const char *pModuleName) +{ + // Linux dlopen() doesn't look in the current directory for libraries. + // We tell it to, so people don't have to 'install' libraries as root. + + char szCwd[1024]; + char szAbsoluteLibFilename[1024]; + + getcwd( szCwd, sizeof( szCwd ) ); + if ( szCwd[ strlen( szCwd ) - 1 ] == '/' ) + szCwd[ strlen( szCwd ) - 1 ] = 0; + + sprintf( szAbsoluteLibFilename, "%s/%s", szCwd, pModuleName ); + + return (HINTERFACEMODULE)dlopen( szAbsoluteLibFilename, RTLD_NOW ); +} + +#endif + + +#ifdef _WIN32 +void Sys_FreeModule(HINTERFACEMODULE hModule) +{ + if(!hModule) + return; + + FreeLibrary((HMODULE)hModule); +} + +#else // LINUX +void Sys_FreeModule(HINTERFACEMODULE hModule) +{ + if(!hModule) + return; + + dlclose( (void *)hModule ); +} + +#endif + + +//----------------------------------------------------------------------------- +// Purpose: returns the instance of this module +// Output : interface_instance_t +//----------------------------------------------------------------------------- +CreateInterfaceFn Sys_GetFactoryThis( void ) +{ + return CreateInterface; +} + + +//----------------------------------------------------------------------------- +// Purpose: returns the instance of the named module +// Input : *pModuleName - name of the module +// Output : interface_instance_t - instance of that module +//----------------------------------------------------------------------------- + +#ifdef _WIN32 +CreateInterfaceFn Sys_GetFactory( HINTERFACEMODULE hModule ) +{ + if(!hModule) + return NULL; + + return (CreateInterfaceFn)GetProcAddress((HMODULE)hModule, CREATEINTERFACE_PROCNAME); +} + +#else // LINUX +CreateInterfaceFn Sys_GetFactory( HINTERFACEMODULE hModule ) +{ + if(!hModule) + return NULL; + + return (CreateInterfaceFn)dlsym( (void *)hModule, CREATEINTERFACE_PROCNAME ); +} + +#endif diff --git a/hlsdk/common/interface.h b/hlsdk/common/interface.h new file mode 100644 index 00000000..9b8e340e --- /dev/null +++ b/hlsdk/common/interface.h @@ -0,0 +1,129 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// This header defines the interface convention used in the valve engine. +// To make an interface and expose it: +// 1. Derive from IBaseInterface. +// 2. The interface must be ALL pure virtuals, and have no data members. +// 3. Define a name for it. +// 4. In its implementation file, use EXPOSE_INTERFACE or EXPOSE_SINGLE_INTERFACE. + +// Versioning +// There are two versioning cases that are handled by this: +// 1. You add functions to the end of an interface, so it is binary compatible with the previous interface. In this case, +// you need two EXPOSE_INTERFACEs: one to expose your class as the old interface and one to expose it as the new interface. +// 2. You update an interface so it's not compatible anymore (but you still want to be able to expose the old interface +// for legacy code). In this case, you need to make a new version name for your new interface, and make a wrapper interface and +// expose it for the old interface. + +#ifndef INTERFACE_H +#define INTERFACE_H + +#ifdef __cplusplus + +// All interfaces derive from this. +class IBaseInterface +{ +public: + + virtual ~IBaseInterface() {} +}; + + +#define CREATEINTERFACE_PROCNAME "CreateInterface" +typedef IBaseInterface* (*CreateInterfaceFn)(const char *pName, int *pReturnCode); + + +typedef IBaseInterface* (*InstantiateInterfaceFn)(); + + +// Used internally to register classes. +class InterfaceReg +{ +public: + InterfaceReg(InstantiateInterfaceFn fn, const char *pName); + +public: + + InstantiateInterfaceFn m_CreateFn; + const char *m_pName; + + InterfaceReg *m_pNext; // For the global list. + static InterfaceReg *s_pInterfaceRegs; +}; + + +// Use this to expose an interface that can have multiple instances. +// e.g.: +// EXPOSE_INTERFACE( CInterfaceImp, IInterface, "MyInterface001" ) +// This will expose a class called CInterfaceImp that implements IInterface (a pure class) +// clients can receive a pointer to this class by calling CreateInterface( "MyInterface001" ) +// +// In practice, the shared header file defines the interface (IInterface) and version name ("MyInterface001") +// so that each component can use these names/vtables to communicate +// +// A single class can support multiple interfaces through multiple inheritance +// +#define EXPOSE_INTERFACE_FN(functionName, interfaceName, versionName) \ + static InterfaceReg __g_Create##className##_reg(functionName, versionName); + +#define EXPOSE_INTERFACE(className, interfaceName, versionName) \ + static IBaseInterface* __Create##className##_interface() {return (interfaceName *)new className;}\ + static InterfaceReg __g_Create##className##_reg(__Create##className##_interface, versionName ); + +// Use this to expose a singleton interface with a global variable you've created. +#define EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, versionName, globalVarName) \ + static IBaseInterface* __Create##className##interfaceName##_interface() {return (interfaceName *)&globalVarName;}\ + static InterfaceReg __g_Create##className##interfaceName##_reg(__Create##className##interfaceName##_interface, versionName); + +// Use this to expose a singleton interface. This creates the global variable for you automatically. +#define EXPOSE_SINGLE_INTERFACE(className, interfaceName, versionName) \ + static className __g_##className##_singleton;\ + EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, versionName, __g_##className##_singleton) + + +#ifdef WIN32 + #define EXPORT_FUNCTION __declspec(dllexport) +#else + #define EXPORT_FUNCTION +#endif + + +// This function is automatically exported and allows you to access any interfaces exposed with the above macros. +// if pReturnCode is set, it will return one of the following values +// extend this for other error conditions/code +enum +{ + IFACE_OK = 0, + IFACE_FAILED +}; + + +extern "C" +{ + EXPORT_FUNCTION IBaseInterface* CreateInterface(const char *pName, int *pReturnCode); +}; + + +// Handle to an interface (HInterfaceModule_t* is just there for type safety). +typedef struct HInterfaceModule_t* HINTERFACEMODULE; + + +// Use these to load and unload a module. +extern HINTERFACEMODULE Sys_LoadModule(const char *pModuleName); +extern void Sys_FreeModule(HINTERFACEMODULE hModule); + +// Use these to get the factory function from either a loaded module or the current module. +extern CreateInterfaceFn Sys_GetFactory( HINTERFACEMODULE hModule ); +extern CreateInterfaceFn Sys_GetFactoryThis( void ); + +#endif // __cplusplus + +#endif + + + diff --git a/hlsdk/common/ivoicetweak.h b/hlsdk/common/ivoicetweak.h new file mode 100644 index 00000000..21e93f38 --- /dev/null +++ b/hlsdk/common/ivoicetweak.h @@ -0,0 +1,37 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef IVOICETWEAK_H +#define IVOICETWEAK_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +// These provide access to the voice controls. +typedef enum +{ + MicrophoneVolume=0, // values 0-1. + OtherSpeakerScale // values 0-1. Scales how loud other players are. +} VoiceTweakControl; + + +typedef struct IVoiceTweak_s +{ + // These turn voice tweak mode on and off. While in voice tweak mode, the user's voice is echoed back + // without sending to the server. + int (*StartVoiceTweakMode)(); // Returns 0 on error. + void (*EndVoiceTweakMode)(); + + // Get/set control values. + void (*SetControlFloat)(VoiceTweakControl iControl, float value); + float (*GetControlFloat)(VoiceTweakControl iControl); +} IVoiceTweak; + + +#endif // IVOICETWEAK_H diff --git a/hlsdk/common/mathlib.h b/hlsdk/common/mathlib.h new file mode 100644 index 00000000..9fe0bffa --- /dev/null +++ b/hlsdk/common/mathlib.h @@ -0,0 +1,156 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// mathlib.h + +typedef float vec_t; +typedef vec_t vec3_t[3]; +typedef vec_t vec4_t[4]; // x,y,z,w +typedef vec_t vec5_t[5]; + +typedef short vec_s_t; +typedef vec_s_t vec3s_t[3]; +typedef vec_s_t vec4s_t[4]; // x,y,z,w +typedef vec_s_t vec5s_t[5]; + +typedef int fixed4_t; +typedef int fixed8_t; +typedef int fixed16_t; + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +struct mplane_s; + +extern vec3_t vec3_origin; +extern int nanmask; + +#define IS_NAN(x) (((*(int *)&x)&nanmask)==nanmask) + +#ifndef VECTOR_H + #define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]) +#endif + +#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 VectorClear(a) {(a)[0]=0.0;(a)[1]=0.0;(a)[2]=0.0;} + +void VectorMA (const vec3_t veca, float scale, const vec3_t vecb, vec3_t vecc); + +vec_t _DotProduct (vec3_t v1, vec3_t v2); +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out); +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out); +void _VectorCopy (vec3_t in, vec3_t out); + +int VectorCompare (const vec3_t v1, const vec3_t v2); +float Length (const vec3_t v); +void CrossProduct (const vec3_t v1, const vec3_t v2, vec3_t cross); +float VectorNormalize (vec3_t v); // returns vector length +void VectorInverse (vec3_t v); +void VectorScale (const vec3_t in, vec_t scale, vec3_t out); +int Q_log2(int val); + +void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]); +void R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]); + +// Here are some "manual" INLINE routines for doing floating point to integer conversions +extern short new_cw, old_cw; + +typedef union DLONG { + int i[2]; + double d; + float f; + } DLONG; + +extern DLONG dlong; + +#ifdef _WIN32 +void __inline set_fpu_cw(void) +{ +_asm + { wait + fnstcw old_cw + wait + mov ax, word ptr old_cw + or ah, 0xc + mov word ptr new_cw,ax + fldcw new_cw + } +} + +int __inline quick_ftol(float f) +{ + _asm { + // Assumes that we are already in chop mode, and only need a 32-bit int + fld DWORD PTR f + fistp DWORD PTR dlong + } + return dlong.i[0]; +} + +void __inline restore_fpu_cw(void) +{ + _asm fldcw old_cw +} +#else +#define set_fpu_cw() /* */ +#define quick_ftol(f) ftol(f) +#define restore_fpu_cw() /* */ +#endif + +void FloorDivMod (double numer, double denom, int *quotient, + int *rem); +fixed16_t Invert24To16(fixed16_t val); +int GreatestCommonDivisor (int i1, int i2); + +void AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); +void AngleVectorsTranspose (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); +#define AngleIVectors AngleVectorsTranspose + +void AngleMatrix (const vec3_t angles, float (*matrix)[4] ); +void AngleIMatrix (const vec3_t angles, float (*matrix)[4] ); +void VectorTransform (const vec3_t in1, float in2[3][4], vec3_t out); + +void NormalizeAngles( vec3_t angles ); +void InterpolateAngles( vec3_t start, vec3_t end, vec3_t output, float frac ); +float AngleBetweenVectors( const vec3_t v1, const vec3_t v2 ); + + +void VectorMatrix( vec3_t forward, vec3_t right, vec3_t up); +void VectorAngles( const vec3_t forward, vec3_t angles ); + +int InvertMatrix( const float * m, float *out ); + +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct mplane_s *plane); +float anglemod(float a); + + + +#define BOX_ON_PLANE_SIDE(emins, emaxs, p) \ + (((p)->type < 3)? \ + ( \ + ((p)->dist <= (emins)[(p)->type])? \ + 1 \ + : \ + ( \ + ((p)->dist >= (emaxs)[(p)->type])?\ + 2 \ + : \ + 3 \ + ) \ + ) \ + : \ + BoxOnPlaneSide( (emins), (emaxs), (p))) diff --git a/hlsdk/common/net_api.h b/hlsdk/common/net_api.h new file mode 100644 index 00000000..677d381e --- /dev/null +++ b/hlsdk/common/net_api.h @@ -0,0 +1,101 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( NET_APIH ) +#define NET_APIH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +#if !defined ( NETADRH ) +#include "netadr.h" +#endif + +#define NETAPI_REQUEST_SERVERLIST ( 0 ) // Doesn't need a remote address +#define NETAPI_REQUEST_PING ( 1 ) +#define NETAPI_REQUEST_RULES ( 2 ) +#define NETAPI_REQUEST_PLAYERS ( 3 ) +#define NETAPI_REQUEST_DETAILS ( 4 ) + +// Set this flag for things like broadcast requests, etc. where the engine should not +// kill the request hook after receiving the first response +#define FNETAPI_MULTIPLE_RESPONSE ( 1<<0 ) + +typedef void ( *net_api_response_func_t ) ( struct net_response_s *response ); + +#define NET_SUCCESS ( 0 ) +#define NET_ERROR_TIMEOUT ( 1<<0 ) +#define NET_ERROR_PROTO_UNSUPPORTED ( 1<<1 ) +#define NET_ERROR_UNDEFINED ( 1<<2 ) + +typedef struct net_adrlist_s +{ + struct net_adrlist_s *next; + netadr_t remote_address; +} net_adrlist_t; + +typedef struct net_response_s +{ + // NET_SUCCESS or an error code + int error; + + // Context ID + int context; + // Type + int type; + + // Server that is responding to the request + netadr_t remote_address; + + // Response RTT ping time + double ping; + // Key/Value pair string ( separated by backlash \ characters ) + // WARNING: You must copy this buffer in the callback function, because it is freed + // by the engine right after the call!!!! + // ALSO: For NETAPI_REQUEST_SERVERLIST requests, this will be a pointer to a linked list of net_adrlist_t's + void *response; +} net_response_t; + +typedef struct net_status_s +{ + // Connected to remote server? 1 == yes, 0 otherwise + int connected; + // Client's IP address + netadr_t local_address; + // Address of remote server + netadr_t remote_address; + // Packet Loss ( as a percentage ) + int packet_loss; + // Latency, in seconds ( multiply by 1000.0 to get milliseconds ) + double latency; + // Connection time, in seconds + double connection_time; + // Rate setting ( for incoming data ) + double rate; +} net_status_t; + +typedef struct net_api_s +{ + // APIs + void ( *InitNetworking )( void ); + void ( *Status ) ( struct net_status_s *status ); + void ( *SendRequest) ( int context, int request, int flags, double timeout, struct netadr_s *remote_address, net_api_response_func_t response ); + void ( *CancelRequest ) ( int context ); + void ( *CancelAllRequests ) ( void ); + char *( *AdrToString ) ( struct netadr_s *a ); + int ( *CompareAdr ) ( struct netadr_s *a, struct netadr_s *b ); + int ( *StringToAdr ) ( char *s, struct netadr_s *a ); + const char *( *ValueForKey ) ( const char *s, const char *key ); + void ( *RemoveKey ) ( char *s, const char *key ); + void ( *SetValueForKey ) (char *s, const char *key, const char *value, int maxsize ); +} net_api_t; + +extern net_api_t netapi; + +#endif // NET_APIH diff --git a/hlsdk/common/netadr.h b/hlsdk/common/netadr.h new file mode 100644 index 00000000..24b42c6b --- /dev/null +++ b/hlsdk/common/netadr.h @@ -0,0 +1,42 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// netadr.h +#ifndef NETADR_H +#define NETADR_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +typedef enum +{ + NA_UNUSED, + NA_LOOPBACK, + NA_BROADCAST, + NA_IP, + NA_IPX, + NA_BROADCAST_IPX, +} netadrtype_t; + +typedef struct netadr_s +{ + netadrtype_t type; + unsigned char ip[4]; + unsigned char ipx[10]; + unsigned short port; +} netadr_t; + +#endif // NETADR_H diff --git a/hlsdk/common/nowin.h b/hlsdk/common/nowin.h new file mode 100644 index 00000000..ffc9a787 --- /dev/null +++ b/hlsdk/common/nowin.h @@ -0,0 +1,15 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef INC_NOWIN_H +#define INC_NOWIN_H +#ifndef _WIN32 + +#include + +#endif //!_WIN32 +#endif //INC_NOWIN_H diff --git a/hlsdk/common/particledef.h b/hlsdk/common/particledef.h new file mode 100644 index 00000000..0226a101 --- /dev/null +++ b/hlsdk/common/particledef.h @@ -0,0 +1,59 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( PARTICLEDEFH ) +#define PARTICLEDEFH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +typedef enum { + pt_static, + pt_grav, + pt_slowgrav, + pt_fire, + pt_explode, + pt_explode2, + pt_blob, + pt_blob2, + pt_vox_slowgrav, + pt_vox_grav, + pt_clientcustom // Must have callback function specified +} ptype_t; + +// !!! if this is changed, it must be changed in d_ifacea.h too !!! +typedef struct particle_s +{ +// driver-usable fields + vec3_t org; + short color; + short packedColor; +// drivers never touch the following fields + struct particle_s *next; + vec3_t vel; + float ramp; + float die; + ptype_t type; + void (*deathfunc)( struct particle_s *particle ); + + // for pt_clientcusttom, we'll call this function each frame + void (*callback)( struct particle_s *particle, float frametime ); + + // For deathfunc, etc. + unsigned char context; +} particle_t; + +#endif diff --git a/hlsdk/common/pmtrace.h b/hlsdk/common/pmtrace.h new file mode 100644 index 00000000..404ddab5 --- /dev/null +++ b/hlsdk/common/pmtrace.h @@ -0,0 +1,45 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( PMTRACEH ) +#define PMTRACEH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +typedef struct +{ + vec3_t normal; + float dist; +} pmplane_t; + +typedef struct pmtrace_s pmtrace_t; + +struct pmtrace_s +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + qboolean inopen, inwater; // End point is in empty space or in water + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + pmplane_t plane; // surface normal at impact + int ent; // entity at impact + vec3_t deltavelocity; // Change in player's velocity caused by impact. + // Only run on server. + int hitgroup; +}; + +#endif diff --git a/hlsdk/common/qfont.h b/hlsdk/common/qfont.h new file mode 100644 index 00000000..b1d4ec51 --- /dev/null +++ b/hlsdk/common/qfont.h @@ -0,0 +1,42 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( QFONTH ) +#define QFONTH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +// Font stuff + +#define NUM_GLYPHS 256 + +typedef struct +{ + short startoffset; + short charwidth; +} charinfo; + +typedef struct qfont_s +{ + int width, height; + int rowcount; + int rowheight; + charinfo fontinfo[ NUM_GLYPHS ]; + byte data[4]; +} qfont_t; + +#endif // qfont.h diff --git a/hlsdk/common/r_efx.h b/hlsdk/common/r_efx.h new file mode 100644 index 00000000..d6a595e5 --- /dev/null +++ b/hlsdk/common/r_efx.h @@ -0,0 +1,199 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined ( R_EFXH ) +#define R_EFXH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +// particle_t +#if !defined( PARTICLEDEFH ) +#include "particledef.h" +#endif + +// BEAM +#if !defined( BEAMDEFH ) +#include "beamdef.h" +#endif + +// dlight_t +#if !defined ( DLIGHTH ) +#include "dlight.h" +#endif + +// cl_entity_t +#if !defined( CL_ENTITYH ) +#include "cl_entity.h" +#endif + +/* +// FOR REFERENCE, These are the built-in tracer colors. Note, color 4 is the one +// that uses the tracerred/tracergreen/tracerblue and traceralpha cvar settings +color24 gTracerColors[] = +{ + { 255, 255, 255 }, // White + { 255, 0, 0 }, // Red + { 0, 255, 0 }, // Green + { 0, 0, 255 }, // Blue + { 0, 0, 0 }, // Tracer default, filled in from cvars, etc. + { 255, 167, 17 }, // Yellow-orange sparks + { 255, 130, 90 }, // Yellowish streaks (garg) + { 55, 60, 144 }, // Blue egon streak + { 255, 130, 90 }, // More Yellowish streaks (garg) + { 255, 140, 90 }, // More Yellowish streaks (garg) + { 200, 130, 90 }, // More red streaks (garg) + { 255, 120, 70 }, // Darker red streaks (garg) +}; +*/ + +// Temporary entity array +#define TENTPRIORITY_LOW 0 +#define TENTPRIORITY_HIGH 1 + +// TEMPENTITY flags +#define FTENT_NONE 0x00000000 +#define FTENT_SINEWAVE 0x00000001 +#define FTENT_GRAVITY 0x00000002 +#define FTENT_ROTATE 0x00000004 +#define FTENT_SLOWGRAVITY 0x00000008 +#define FTENT_SMOKETRAIL 0x00000010 +#define FTENT_COLLIDEWORLD 0x00000020 +#define FTENT_FLICKER 0x00000040 +#define FTENT_FADEOUT 0x00000080 +#define FTENT_SPRANIMATE 0x00000100 +#define FTENT_HITSOUND 0x00000200 +#define FTENT_SPIRAL 0x00000400 +#define FTENT_SPRCYCLE 0x00000800 +#define FTENT_COLLIDEALL 0x00001000 // will collide with world and slideboxes +#define FTENT_PERSIST 0x00002000 // tent is not removed when unable to draw +#define FTENT_COLLIDEKILL 0x00004000 // tent is removed upon collision with anything +#define FTENT_PLYRATTACHMENT 0x00008000 // tent is attached to a player (owner) +#define FTENT_SPRANIMATELOOP 0x00010000 // animating sprite doesn't die when last frame is displayed +#define FTENT_SPARKSHOWER 0x00020000 +#define FTENT_NOMODEL 0x00040000 // Doesn't have a model, never try to draw ( it just triggers other things ) +#define FTENT_CLIENTCUSTOM 0x00080000 // Must specify callback. Callback function is responsible for killing tempent and updating fields ( unless other flags specify how to do things ) + +typedef struct tempent_s TEMPENTITY; +typedef struct tempent_s +{ + int flags; + float die; + float frameMax; + float x; + float y; + float z; + float fadeSpeed; + float bounceFactor; + int hitSound; + void ( *hitcallback ) ( struct tempent_s *ent, struct pmtrace_s *ptr ); + void ( *callback ) ( struct tempent_s *ent, float frametime, float currenttime ); + TEMPENTITY *next; + int priority; + short clientIndex; // if attached, this is the index of the client to stick to + // if COLLIDEALL, this is the index of the client to ignore + // TENTS with FTENT_PLYRATTACHMENT MUST set the clientindex! + + vec3_t tentOffset; // if attached, client origin + tentOffset = tent origin. + cl_entity_t entity; + + // baseline.origin - velocity + // baseline.renderamt - starting fadeout intensity + // baseline.angles - angle velocity +} TEMPENTITY; + +typedef struct efx_api_s efx_api_t; + +struct efx_api_s +{ + particle_t *( *R_AllocParticle ) ( void ( *callback ) ( struct particle_s *particle, float frametime ) ); + void ( *R_BlobExplosion ) ( float * org ); + void ( *R_Blood ) ( float * org, float * dir, int pcolor, int speed ); + void ( *R_BloodSprite ) ( float * org, int colorindex, int modelIndex, int modelIndex2, float size ); + void ( *R_BloodStream ) ( float * org, float * dir, int pcolor, int speed ); + void ( *R_BreakModel ) ( float *pos, float *size, float *dir, float random, float life, int count, int modelIndex, char flags ); + void ( *R_Bubbles ) ( float * mins, float * maxs, float height, int modelIndex, int count, float speed ); + void ( *R_BubbleTrail ) ( float * start, float * end, float height, int modelIndex, int count, float speed ); + void ( *R_BulletImpactParticles ) ( float * pos ); + void ( *R_EntityParticles ) ( struct cl_entity_s *ent ); + void ( *R_Explosion ) ( float *pos, int model, float scale, float framerate, int flags ); + void ( *R_FizzEffect ) ( struct cl_entity_s *pent, int modelIndex, int density ); + void ( *R_FireField ) ( float * org, int radius, int modelIndex, int count, int flags, float life ); + void ( *R_FlickerParticles ) ( float * org ); + void ( *R_FunnelSprite ) ( float *org, int modelIndex, int reverse ); + void ( *R_Implosion ) ( float * end, float radius, int count, float life ); + void ( *R_LargeFunnel ) ( float * org, int reverse ); + void ( *R_LavaSplash ) ( float * org ); + void ( *R_MultiGunshot ) ( float * org, float * dir, float * noise, int count, int decalCount, int *decalIndices ); + void ( *R_MuzzleFlash ) ( float *pos1, int type ); + void ( *R_ParticleBox ) ( float *mins, float *maxs, unsigned char r, unsigned char g, unsigned char b, float life ); + void ( *R_ParticleBurst ) ( float * pos, int size, int color, float life ); + void ( *R_ParticleExplosion ) ( float * org ); + void ( *R_ParticleExplosion2 ) ( float * org, int colorStart, int colorLength ); + void ( *R_ParticleLine ) ( float * start, float *end, unsigned char r, unsigned char g, unsigned char b, float life ); + void ( *R_PlayerSprites ) ( int client, int modelIndex, int count, int size ); + void ( *R_Projectile ) ( float * origin, float * velocity, int modelIndex, int life, int owner, void (*hitcallback)( struct tempent_s *ent, struct pmtrace_s *ptr ) ); + void ( *R_RicochetSound ) ( float * pos ); + void ( *R_RicochetSprite ) ( float *pos, struct model_s *pmodel, float duration, float scale ); + void ( *R_RocketFlare ) ( float *pos ); + void ( *R_RocketTrail ) ( float * start, float * end, int type ); + void ( *R_RunParticleEffect ) ( float * org, float * dir, int color, int count ); + void ( *R_ShowLine ) ( float * start, float * end ); + void ( *R_SparkEffect ) ( float *pos, int count, int velocityMin, int velocityMax ); + void ( *R_SparkShower ) ( float *pos ); + void ( *R_SparkStreaks ) ( float * pos, int count, int velocityMin, int velocityMax ); + void ( *R_Spray ) ( float * pos, float * dir, int modelIndex, int count, int speed, int spread, int rendermode ); + void ( *R_Sprite_Explode ) ( TEMPENTITY *pTemp, float scale, int flags ); + void ( *R_Sprite_Smoke ) ( TEMPENTITY *pTemp, float scale ); + void ( *R_Sprite_Spray ) ( float * pos, float * dir, int modelIndex, int count, int speed, int iRand ); + void ( *R_Sprite_Trail ) ( int type, float * start, float * end, int modelIndex, int count, float life, float size, float amplitude, int renderamt, float speed ); + void ( *R_Sprite_WallPuff ) ( TEMPENTITY *pTemp, float scale ); + void ( *R_StreakSplash ) ( float * pos, float * dir, int color, int count, float speed, int velocityMin, int velocityMax ); + void ( *R_TracerEffect ) ( float * start, float * end ); + void ( *R_UserTracerParticle ) ( float * org, float * vel, float life, int colorIndex, float length, unsigned char deathcontext, void ( *deathfunc)( struct particle_s *particle ) ); + particle_t *( *R_TracerParticles ) ( float * org, float * vel, float life ); + void ( *R_TeleportSplash ) ( float * org ); + void ( *R_TempSphereModel ) ( float *pos, float speed, float life, int count, int modelIndex ); + TEMPENTITY *( *R_TempModel ) ( float *pos, float *dir, float *angles, float life, int modelIndex, int soundtype ); + TEMPENTITY *( *R_DefaultSprite ) ( float *pos, int spriteIndex, float framerate ); + TEMPENTITY *( *R_TempSprite ) ( float *pos, float *dir, float scale, int modelIndex, int rendermode, int renderfx, float a, float life, int flags ); + int ( *Draw_DecalIndex ) ( int id ); + int ( *Draw_DecalIndexFromName ) ( char *name ); + void ( *R_DecalShoot ) ( int textureIndex, int entity, int modelIndex, float * position, int flags ); + void ( *R_AttachTentToPlayer ) ( int client, int modelIndex, float zoffset, float life ); + void ( *R_KillAttachedTents ) ( int client ); + BEAM *( *R_BeamCirclePoints ) ( int type, float * start, float * end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); + BEAM *( *R_BeamEntPoint ) ( int startEnt, float * end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); + BEAM *( *R_BeamEnts ) ( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); + BEAM *( *R_BeamFollow ) ( int startEnt, int modelIndex, float life, float width, float r, float g, float b, float brightness ); + void ( *R_BeamKill ) ( int deadEntity ); + BEAM *( *R_BeamLightning ) ( float * start, float * end, int modelIndex, float life, float width, float amplitude, float brightness, float speed ); + BEAM *( *R_BeamPoints ) ( float * start, float * end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); + BEAM *( *R_BeamRing ) ( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); + dlight_t *( *CL_AllocDlight ) ( int key ); + dlight_t *( *CL_AllocElight ) ( int key ); + TEMPENTITY *( *CL_TempEntAlloc ) ( float * org, struct model_s *model ); + TEMPENTITY *( *CL_TempEntAllocNoModel ) ( float * org ); + TEMPENTITY *( *CL_TempEntAllocHigh ) ( float * org, struct model_s *model ); + TEMPENTITY *( *CL_TentEntAllocCustom ) ( float *origin, struct model_s *model, int high, void ( *callback ) ( struct tempent_s *ent, float frametime, float currenttime ) ); + void ( *R_GetPackedColor ) ( short *packed, short color ); + short ( *R_LookupColor ) ( unsigned char r, unsigned char g, unsigned char b ); + void ( *R_DecalRemoveAll ) ( int textureIndex ); //textureIndex points to the decal index in the array, not the actual texture index. +}; + +extern efx_api_t efx; + +#endif diff --git a/hlsdk/common/r_studioint.h b/hlsdk/common/r_studioint.h new file mode 100644 index 00000000..30f657c2 --- /dev/null +++ b/hlsdk/common/r_studioint.h @@ -0,0 +1,149 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( R_STUDIOINT_H ) +#define R_STUDIOINT_H +#if defined( _WIN32 ) +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +#define STUDIO_INTERFACE_VERSION 1 + +typedef struct engine_studio_api_s +{ + // Allocate number*size bytes and zero it + void *( *Mem_Calloc ) ( int number, size_t size ); + // Check to see if pointer is in the cache + void *( *Cache_Check ) ( struct cache_user_s *c ); + // Load file into cache ( can be swapped out on demand ) + void ( *LoadCacheFile ) ( char *path, struct cache_user_s *cu ); + // Retrieve model pointer for the named model + struct model_s *( *Mod_ForName ) ( const char *name, int crash_if_missing ); + // Retrieve pointer to studio model data block from a model + void *( *Mod_Extradata ) ( struct model_s *mod ); + // Retrieve indexed model from client side model precache list + struct model_s *( *GetModelByIndex ) ( int index ); + // Get entity that is set for rendering + struct cl_entity_s * ( *GetCurrentEntity ) ( void ); + // Get referenced player_info_t + struct player_info_s *( *PlayerInfo ) ( int index ); + // Get most recently received player state data from network system + struct entity_state_s *( *GetPlayerState ) ( int index ); + // Get viewentity + struct cl_entity_s * ( *GetViewEntity ) ( void ); + // Get current frame count, and last two timestampes on client + void ( *GetTimes ) ( int *framecount, double *current, double *old ); + // Get a pointer to a cvar by name + struct cvar_s *( *GetCvar ) ( const char *name ); + // Get current render origin and view vectors ( up, right and vpn ) + void ( *GetViewInfo ) ( float *origin, float *upv, float *rightv, float *vpnv ); + // Get sprite model used for applying chrome effect + struct model_s *( *GetChromeSprite ) ( void ); + // Get model counters so we can incement instrumentation + void ( *GetModelCounters ) ( int **s, int **a ); + // Get software scaling coefficients + void ( *GetAliasScale ) ( float *x, float *y ); + + // Get bone, light, alias, and rotation matrices + float ****( *StudioGetBoneTransform ) ( void ); + float ****( *StudioGetLightTransform )( void ); + float ***( *StudioGetAliasTransform ) ( void ); + float ***( *StudioGetRotationMatrix ) ( void ); + + // Set up body part, and get submodel pointers + void ( *StudioSetupModel ) ( int bodypart, void **ppbodypart, void **ppsubmodel ); + // Check if entity's bbox is in the view frustum + int ( *StudioCheckBBox ) ( void ); + // Apply lighting effects to model + void ( *StudioDynamicLight ) ( struct cl_entity_s *ent, struct alight_s *plight ); + void ( *StudioEntityLight ) ( struct alight_s *plight ); + void ( *StudioSetupLighting ) ( struct alight_s *plighting ); + + // Draw mesh vertices + void ( *StudioDrawPoints ) ( void ); + + // Draw hulls around bones + void ( *StudioDrawHulls ) ( void ); + // Draw bbox around studio models + void ( *StudioDrawAbsBBox ) ( void ); + // Draws bones + void ( *StudioDrawBones ) ( void ); + // Loads in appropriate texture for model + void ( *StudioSetupSkin ) ( void *ptexturehdr, int index ); + // Sets up for remapped colors + void ( *StudioSetRemapColors ) ( int top, int bottom ); + // Set's player model and returns model pointer + struct model_s *( *SetupPlayerModel ) ( int index ); + // Fires any events embedded in animation + void ( *StudioClientEvents ) ( void ); + // Retrieve/set forced render effects flags + int ( *GetForceFaceFlags ) ( void ); + void ( *SetForceFaceFlags ) ( int flags ); + // Tell engine the value of the studio model header + void ( *StudioSetHeader ) ( void *header ); + // Tell engine which model_t * is being renderered + void ( *SetRenderModel ) ( struct model_s *model ); + + // Final state setup and restore for rendering + void ( *SetupRenderer ) ( int rendermode ); + void ( *RestoreRenderer ) ( void ); + + // Set render origin for applying chrome effect + void ( *SetChromeOrigin ) ( void ); + + // True if using D3D/OpenGL + int ( *IsHardware ) ( void ); + + // Only called by hardware interface + void ( *GL_StudioDrawShadow ) ( void ); + void ( *GL_SetRenderMode ) ( int mode ); +} engine_studio_api_t; + +typedef struct server_studio_api_s +{ + // Allocate number*size bytes and zero it + void *( *Mem_Calloc ) ( int number, size_t size ); + // Check to see if pointer is in the cache + void *( *Cache_Check ) ( struct cache_user_s *c ); + // Load file into cache ( can be swapped out on demand ) + void ( *LoadCacheFile ) ( char *path, struct cache_user_s *cu ); + // Retrieve pointer to studio model data block from a model + void *( *Mod_Extradata ) ( struct model_s *mod ); +} server_studio_api_t; + + +// client blending +typedef struct r_studio_interface_s +{ + int version; + int ( *StudioDrawModel ) ( int flags ); + int ( *StudioDrawPlayer ) ( int flags, struct entity_state_s *pplayer ); +} r_studio_interface_t; + +extern r_studio_interface_t *pStudioAPI; + +// server blending +#define SV_BLENDING_INTERFACE_VERSION 1 + +typedef struct sv_blending_interface_s +{ + int version; + + void ( *SV_StudioSetupBones )( struct model_s *pModel, + float frame, + int sequence, + const vec3_t angles, + const vec3_t origin, + const byte *pcontroller, + const byte *pblending, + int iBone, + const edict_t *pEdict ); +} sv_blending_interface_t; + +#endif // R_STUDIOINT_H diff --git a/hlsdk/common/ref_params.h b/hlsdk/common/ref_params.h new file mode 100644 index 00000000..10e8837b --- /dev/null +++ b/hlsdk/common/ref_params.h @@ -0,0 +1,75 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( REF_PARAMSH ) +#define REF_PARAMSH + +typedef struct ref_params_s +{ + // Output + float vieworg[3]; + float viewangles[3]; + + float forward[3]; + float right[3]; + float up[3]; + + // Client frametime; + float frametime; + // Client time + float time; + + // Misc + int intermission; + int paused; + int spectator; + int onground; + int waterlevel; + + float simvel[3]; + float simorg[3]; + + float viewheight[3]; + float idealpitch; + + float cl_viewangles[3]; + + int health; + float crosshairangle[3]; + float viewsize; + + float punchangle[3]; + int maxclients; + int viewentity; + int playernum; + int max_entities; + int demoplayback; + int hardware; + + int smoothing; + + // Last issued usercmd + struct usercmd_s *cmd; + + // Movevars + struct movevars_s *movevars; + + int viewport[4]; // the viewport coordinates x ,y , width, height + + int nextView; // the renderer calls ClientDLL_CalcRefdef() and Renderview + // so long in cycles until this value is 0 (multiple views) + int onlyClientDraw; // if !=0 nothing is drawn by the engine except clientDraw functions +} ref_params_t; + +#endif // !REF_PARAMSH diff --git a/hlsdk/common/screenfade.h b/hlsdk/common/screenfade.h new file mode 100644 index 00000000..78894acc --- /dev/null +++ b/hlsdk/common/screenfade.h @@ -0,0 +1,26 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( SCREENFADEH ) +#define SCREENFADEH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +typedef struct screenfade_s +{ + float fadeSpeed; // How fast to fade (tics / second) (+ fade in, - fade out) + float fadeEnd; // When the fading hits maximum + float fadeTotalEnd; // Total End Time of the fade (used for FFADE_OUT) + float fadeReset; // When to reset to not fading (for fadeout and hold) + byte fader, fadeg, fadeb, fadealpha; // Fade color + int fadeFlags; // Fading flags +} screenfade_t; + +#endif // !SCREENFADEH diff --git a/hlsdk/common/studio_event.h b/hlsdk/common/studio_event.h new file mode 100644 index 00000000..77007e3a --- /dev/null +++ b/hlsdk/common/studio_event.h @@ -0,0 +1,31 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( STUDIO_EVENTH ) +#define STUDIO_EVENTH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +typedef struct mstudioevent_s +{ + int frame; + int event; + int type; + char options[64]; +} mstudioevent_t; + +#endif // STUDIO_EVENTH diff --git a/hlsdk/common/triangleapi.h b/hlsdk/common/triangleapi.h new file mode 100644 index 00000000..479a846c --- /dev/null +++ b/hlsdk/common/triangleapi.h @@ -0,0 +1,61 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( TRIANGLEAPIH ) +#define TRIANGLEAPIH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +typedef enum +{ + TRI_FRONT = 0, + TRI_NONE = 1, +} TRICULLSTYLE; + +#define TRI_API_VERSION 1 + +#define TRI_TRIANGLES 0 +#define TRI_TRIANGLE_FAN 1 +#define TRI_QUADS 2 +#define TRI_POLYGON 3 +#define TRI_LINES 4 +#define TRI_TRIANGLE_STRIP 5 +#define TRI_QUAD_STRIP 6 + +typedef struct triangleapi_s +{ + int version; + + void ( *RenderMode )( int mode ); + void ( *Begin )( int primitiveCode ); + void ( *End ) ( void ); + + void ( *Color4f ) ( float r, float g, float b, float a ); + void ( *Color4ub ) ( unsigned char r, unsigned char g, unsigned char b, unsigned char a ); + void ( *TexCoord2f ) ( float u, float v ); + void ( *Vertex3fv ) ( float *worldPnt ); + void ( *Vertex3f ) ( float x, float y, float z ); + void ( *Brightness ) ( float brightness ); + void ( *CullFace ) ( TRICULLSTYLE style ); + int ( *SpriteTexture ) ( struct model_s *pSpriteModel, int frame ); + int ( *WorldToScreen ) ( float *world, float *screen ); // Returns 1 if it's z clipped + void ( *Fog ) ( float flFogColor[3], float flStart, float flEnd, int bOn ); //Works just like GL_FOG, flFogColor is r/g/b. + void ( *ScreenToWorld ) ( float *screen, float *world ); + +} triangleapi_t; + +#endif // !TRIANGLEAPIH diff --git a/hlsdk/common/usercmd.h b/hlsdk/common/usercmd.h new file mode 100644 index 00000000..641dd31a --- /dev/null +++ b/hlsdk/common/usercmd.h @@ -0,0 +1,43 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef USERCMD_H +#define USERCMD_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +typedef struct usercmd_s +{ + short lerp_msec; // Interpolation time on client + byte msec; // Duration in ms of command + vec3_t viewangles; // Command view angles. + +// intended velocities + float forwardmove; // Forward velocity. + float sidemove; // Sideways velocity. + float upmove; // Upward velocity. + byte lightlevel; // Light level at spot where we are standing. + unsigned short buttons; // Attack buttons + byte impulse; // Impulse command issued. + byte weaponselect; // Current weapon id + +// Experimental player impact stuff. + int impact_index; + vec3_t impact_position; +} usercmd_t; + +#endif // USERCMD_H diff --git a/hlsdk/common/weaponinfo.h b/hlsdk/common/weaponinfo.h new file mode 100644 index 00000000..d41c861b --- /dev/null +++ b/hlsdk/common/weaponinfo.h @@ -0,0 +1,54 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined ( WEAPONINFOH ) +#define WEAPONINFOH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +// Info about weapons player might have in his/her possession +typedef struct weapon_data_s +{ + int m_iId; + int m_iClip; + + float m_flNextPrimaryAttack; + float m_flNextSecondaryAttack; + float m_flTimeWeaponIdle; + + int m_fInReload; + int m_fInSpecialReload; + float m_flNextReload; + float m_flPumpTime; + float m_fReloadTime; + + float m_fAimedDamage; + float m_fNextAimBonus; + int m_fInZoom; + int m_iWeaponState; + + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; +} weapon_data_t; + +#endif diff --git a/hlsdk/dedicated/Makefile b/hlsdk/dedicated/Makefile new file mode 100644 index 00000000..5b4879e5 --- /dev/null +++ b/hlsdk/dedicated/Makefile @@ -0,0 +1,53 @@ +# +# hlds (front end for hlds_l) Makefile for Linux i386 +# +# May 2000, Leon Hartwig (hartwig@valvesoftware.com) +# + +#make sure this is the correct compiler for your system +CC=g++ + +SRCDIR=. +OBJDIR=$(SRCDIR)/obj + +#safe optimization +CFLAGS=-Wall -Werror -m486 -O1 + +#full optimization +#CFLAGS=-Wall -Werror -m486 -O2 \ + -ffast-math -funroll-loops \ + -fexpensive-optimizations -malign-loops=2 \ + -malign-jumps=2 -malign-functions=2 + +#use these when debugging +#CFLAGS=$(BASE_CFLAGS) -g + +LDFLAGS=-lgcc -ldl + +AR=ar +RANLIB=ranlib + +INCLUDEDIRS=-I. -I../common + +DO_CC=$(CC) $(INCLUDEDIRS) $(CFLAGS) -o $@ -c $< + +############################################################################# +# HLDS FRONT END +############################################################################# + +$(OBJDIR)/%.o: $(SRCDIR)/%.cpp + $(DO_CC) + +OBJ = \ + $(OBJDIR)/sys_ded.o \ + $(OBJDIR)/engine.o + +hlds : neat $(OBJ) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJ) +neat: + -mkdir -p $(OBJDIR) +clean: + -rm -f $(OBJ) + -rm -f hlds +spotless: clean + -rm -r $(OBJDIR) diff --git a/hlsdk/dedicated/NetGame.ico b/hlsdk/dedicated/NetGame.ico new file mode 100644 index 00000000..db6831e6 Binary files /dev/null and b/hlsdk/dedicated/NetGame.ico differ diff --git a/hlsdk/dedicated/conproc.cpp b/hlsdk/dedicated/conproc.cpp new file mode 100644 index 00000000..0a42c9b2 --- /dev/null +++ b/hlsdk/dedicated/conproc.cpp @@ -0,0 +1,424 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// conproc.c -- support for qhost +#include "dedicated.h" +#include "sys_ded.h" +#include +#include +#include +#include "conproc.h" + +static HANDLE heventDone; +static HANDLE hfileBuffer; +static HANDLE heventChildSend; +static HANDLE heventParentSend; +static HANDLE hStdout; +static HANDLE hStdin; + +/* +============== +SetConsoleCXCY + +============== +*/ +BOOL SetConsoleCXCY(HANDLE hStdout, int cx, int cy) +{ + CONSOLE_SCREEN_BUFFER_INFO info; + COORD coordMax; + + coordMax = GetLargestConsoleWindowSize(hStdout); + + if (cy > coordMax.Y) + cy = coordMax.Y; + + if (cx > coordMax.X) + cx = coordMax.X; + + if (!GetConsoleScreenBufferInfo(hStdout, &info)) + return FALSE; + +// height + info.srWindow.Left = 0; + info.srWindow.Right = info.dwSize.X - 1; + info.srWindow.Top = 0; + info.srWindow.Bottom = cy - 1; + + if (cy < info.dwSize.Y) + { + if (!SetConsoleWindowInfo(hStdout, TRUE, &info.srWindow)) + return FALSE; + + info.dwSize.Y = cy; + + if (!SetConsoleScreenBufferSize(hStdout, info.dwSize)) + return FALSE; + } + else if (cy > info.dwSize.Y) + { + info.dwSize.Y = cy; + + if (!SetConsoleScreenBufferSize(hStdout, info.dwSize)) + return FALSE; + + if (!SetConsoleWindowInfo(hStdout, TRUE, &info.srWindow)) + return FALSE; + } + + if (!GetConsoleScreenBufferInfo(hStdout, &info)) + return FALSE; + +// width + info.srWindow.Left = 0; + info.srWindow.Right = cx - 1; + info.srWindow.Top = 0; + info.srWindow.Bottom = info.dwSize.Y - 1; + + if (cx < info.dwSize.X) + { + if (!SetConsoleWindowInfo(hStdout, TRUE, &info.srWindow)) + return FALSE; + + info.dwSize.X = cx; + + if (!SetConsoleScreenBufferSize(hStdout, info.dwSize)) + return FALSE; + } + else if (cx > info.dwSize.X) + { + info.dwSize.X = cx; + + if (!SetConsoleScreenBufferSize(hStdout, info.dwSize)) + return FALSE; + + if (!SetConsoleWindowInfo(hStdout, TRUE, &info.srWindow)) + return FALSE; + } + + return TRUE; +} + +/* +============== +GetMappedBuffer + +============== +*/ +LPVOID GetMappedBuffer (HANDLE hfileBuffer) +{ + LPVOID pBuffer; + + pBuffer = MapViewOfFile (hfileBuffer, + FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0); + + return pBuffer; +} + +/* +============== +ReleaseMappedBuffer + +============== +*/ +void ReleaseMappedBuffer (LPVOID pBuffer) +{ + UnmapViewOfFile (pBuffer); +} + +/* +============== +GetScreenBufferLines + +============== +*/ +BOOL GetScreenBufferLines (int *piLines) +{ + CONSOLE_SCREEN_BUFFER_INFO info; + BOOL bRet; + + bRet = GetConsoleScreenBufferInfo (hStdout, &info); + + if (bRet) + *piLines = info.dwSize.Y; + + return bRet; +} + +/* +============== +SetScreenBufferLines + +============== +*/ +BOOL SetScreenBufferLines (int iLines) +{ + return SetConsoleCXCY (hStdout, 80, iLines); +} + +/* +============== +ReadText + +============== +*/ +BOOL ReadText (LPTSTR pszText, int iBeginLine, int iEndLine) +{ + COORD coord; + DWORD dwRead; + BOOL bRet; + + coord.X = 0; + coord.Y = iBeginLine; + + bRet = ReadConsoleOutputCharacter( + hStdout, + pszText, + 80 * (iEndLine - iBeginLine + 1), + coord, + &dwRead); + + // Make sure it's null terminated. + if (bRet) + pszText[dwRead] = '\0'; + + return bRet; +} + +/* +============== +CharToCode + +============== +*/ +int CharToCode (char c) +{ + char upper; + + upper = toupper(c); + + switch (c) + { + case 13: + return 28; + + default: + break; + } + + if (isalpha(c)) + return (30 + upper - 65); + + if (isdigit(c)) + return (1 + upper - 47); + + return c; +} + +/* +============== +WriteText + +============== +*/ +BOOL WriteText (LPCTSTR szText) +{ + DWORD dwWritten; + INPUT_RECORD rec; + char upper, *sz; + + sz = (LPTSTR) szText; + + while (*sz) + { + // 13 is the code for a carriage return (\n) instead of 10. + if (*sz == 10) + *sz = 13; + + upper = toupper(*sz); + + rec.EventType = KEY_EVENT; + rec.Event.KeyEvent.bKeyDown = TRUE; + rec.Event.KeyEvent.wRepeatCount = 1; + rec.Event.KeyEvent.wVirtualKeyCode = upper; + rec.Event.KeyEvent.wVirtualScanCode = CharToCode (*sz); + rec.Event.KeyEvent.uChar.AsciiChar = *sz; + rec.Event.KeyEvent.uChar.UnicodeChar = *sz; + rec.Event.KeyEvent.dwControlKeyState = isupper(*sz) ? 0x80 : 0x0; + + WriteConsoleInput( + hStdin, + &rec, + 1, + &dwWritten); + + rec.Event.KeyEvent.bKeyDown = FALSE; + + WriteConsoleInput( + hStdin, + &rec, + 1, + &dwWritten); + + sz++; + } + + return TRUE; +} + +/* +============== +RequestProc + +============== +*/ +unsigned _stdcall RequestProc (void *arg) +{ + int *pBuffer; + DWORD dwRet; + HANDLE heventWait[2]; + int iBeginLine, iEndLine; + + heventWait[0] = heventParentSend; + heventWait[1] = heventDone; + + while (1) + { + dwRet = WaitForMultipleObjects (2, heventWait, FALSE, INFINITE); + + // heventDone fired, so we're exiting. + if (dwRet == WAIT_OBJECT_0 + 1) + break; + + pBuffer = (int *) GetMappedBuffer (hfileBuffer); + + // hfileBuffer is invalid. Just leave. + if (!pBuffer) + { + Sys_Printf ("Request Proc: Invalid -HFILE handle\n"); + break; + } + + switch (pBuffer[0]) + { + case CCOM_WRITE_TEXT: + // Param1 : Text + pBuffer[0] = WriteText ((LPCTSTR) (pBuffer + 1)); + break; + + case CCOM_GET_TEXT: + // Param1 : Begin line + // Param2 : End line + iBeginLine = pBuffer[1]; + iEndLine = pBuffer[2]; + pBuffer[0] = ReadText ((LPTSTR) (pBuffer + 1), iBeginLine, + iEndLine); + break; + + case CCOM_GET_SCR_LINES: + // No params + pBuffer[0] = GetScreenBufferLines (&pBuffer[1]); + break; + + case CCOM_SET_SCR_LINES: + // Param1 : Number of lines + pBuffer[0] = SetScreenBufferLines (pBuffer[1]); + break; + } + + ReleaseMappedBuffer (pBuffer); + SetEvent (heventChildSend); + } + + _endthreadex (0); + return 0; +} + +/* +============== +DeinitConProc + +============== +*/ +void DeinitConProc (void) +{ + if ( heventDone ) + { + SetEvent ( heventDone ); + } +} + +/* +============== +InitConProc + +============== +*/ +void InitConProc ( void ) +{ + unsigned threadAddr; + HANDLE hFile = (HANDLE)0; + HANDLE heventParent = (HANDLE)0; + HANDLE heventChild = (HANDLE)0; + int WantHeight = 50; + char *p; + + // give external front ends a chance to hook into the console + if ( CheckParm ( "-HFILE", &p ) && p ) + { + hFile = (HANDLE)atoi ( p ); + } + + if ( CheckParm ( "-HPARENT", &p ) && p ) + { + heventParent = (HANDLE)atoi ( p ); + } + + if ( CheckParm ( "-HCHILD", &p ) && p ) + { + heventChild = (HANDLE)atoi ( p ); + } + + // ignore if we don't have all the events. + if ( !hFile || !heventParent || !heventChild ) + { + //Sys_Printf ("\n\nNo external front end present.\n" ); + return; + } + + Sys_Printf( "\n\nInitConProc: Setting up external control.\n" ); + + hfileBuffer = hFile; + heventParentSend = heventParent; + heventChildSend = heventChild; + + // So we'll know when to go away. + heventDone = CreateEvent (NULL, FALSE, FALSE, NULL); + if (!heventDone) + { + Sys_Printf ("InitConProc: Couldn't create heventDone\n"); + return; + } + + if (!_beginthreadex (NULL, 0, RequestProc, NULL, 0, &threadAddr)) + { + CloseHandle (heventDone); + Sys_Printf ("InitConProc: Couldn't create third party thread\n"); + return; + } + + // save off the input/output handles. + hStdout = GetStdHandle (STD_OUTPUT_HANDLE); + hStdin = GetStdHandle (STD_INPUT_HANDLE); + + if ( CheckParm( "-conheight", &p ) && p ) + { + WantHeight = atoi( p ); + } + + // Force 80 character width, at least 25 character height + SetConsoleCXCY( hStdout, 80, WantHeight ); +} diff --git a/hlsdk/dedicated/conproc.h b/hlsdk/dedicated/conproc.h new file mode 100644 index 00000000..26f85854 --- /dev/null +++ b/hlsdk/dedicated/conproc.h @@ -0,0 +1,30 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// conproc.h -- support for external server monitoring programs +#ifndef INC_CONPROCH +#define INC_CONPROCH + +#define CCOM_WRITE_TEXT 0x2 +// Param1 : Text + +#define CCOM_GET_TEXT 0x3 +// Param1 : Begin line +// Param2 : End line + +#define CCOM_GET_SCR_LINES 0x4 +// No params + +#define CCOM_SET_SCR_LINES 0x5 +// Param1 : Number of lines + +void InitConProc ( void ); +void DeinitConProc ( void ); + +void WriteStatusText( char *psz ); + +#endif // !INC_CONPROCH diff --git a/hlsdk/dedicated/dedicated.h b/hlsdk/dedicated/dedicated.h new file mode 100644 index 00000000..e2175d71 --- /dev/null +++ b/hlsdk/dedicated/dedicated.h @@ -0,0 +1,23 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// dedicated.h +#ifndef INC_DEDICATEDH +#define INC_DEDICATEDH + +int Eng_Frame ( int fForce, double time ); +int Eng_Load ( const char *cmdline, struct exefuncs_s *pef, int memory, void *pmembase, const char *psz, int iSubMode ); +void Eng_Unload ( void); +void Eng_SetState ( int ); +void Eng_SetSubState ( int ); + +char *CheckParm ( const char *psz, char **ppszValue = (char **)0 ); + +extern int gDLLState; +extern int gDLLStateInfo; + +#endif diff --git a/hlsdk/dedicated/dedicated.rc b/hlsdk/dedicated/dedicated.rc new file mode 100644 index 00000000..fb4196e3 --- /dev/null +++ b/hlsdk/dedicated/dedicated.rc @@ -0,0 +1,72 @@ +//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 + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_HALFLIFE ICON DISCARDABLE "NetGame.ico" + +#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 + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/hlsdk/dedicated/engine.cpp b/hlsdk/dedicated/engine.cpp new file mode 100644 index 00000000..ea81e03e --- /dev/null +++ b/hlsdk/dedicated/engine.cpp @@ -0,0 +1,361 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= +#include +#ifdef _WIN32 +#include +#define snprintf _snprintf +#else +#include +#include +#include // exit() +#endif + +#include "dedicated.h" +#include "dll_state.h" +#include "enginecallback.h" +#include "sys_ded.h" + +int iWait = 0; +int fDeferedPause = 0; + +int gDLLState; +int gDLLStateInfo; +long ghMod = 0; + +static engine_api_t nullapi; +engine_api_t engineapi = nullapi; + +extern SleepType Sys_Sleep; // setup by sys_ded.cpp + +typedef int (*engine_api_func)( int version, int size, struct engine_api_s *api ); + +#ifdef _WIN32 +// This is a big chunk of uninitialized memory that we reserve for loading blobs into. +char g_rgchBlob[0x03800000]; +#endif // _WIN32 + +/* +============== +Eng_LoadFunctions + +Load engine->front end interface, if possible +============== +*/ +int Eng_LoadFunctions( long hMod ) +{ + engine_api_func pfnEngineAPI; + + pfnEngineAPI = ( engine_api_func )Sys_GetProcAddress( hMod, "Sys_EngineAPI" ); + if ( !pfnEngineAPI ) + return 0; + + if ( !(*pfnEngineAPI)( ENGINE_LAUNCHER_API_VERSION, sizeof( engine_api_t ), &engineapi ) ) + return 0; + + // All is okay + return 1; +} + +/* +============== +Eng_LoadStubs + +Force NULL interface +============== +*/ +void Eng_LoadStubs( void ) +{ + // No callbacks in dedicated server since engine should always be loaded. + memset( &engineapi, 0, sizeof( engineapi ) ); + + engineapi.version = ENGINE_LAUNCHER_API_VERSION; + engineapi.rendertype = RENDERTYPE_UNDEFINED; + engineapi.size = sizeof( engine_api_t ); +} + +/* +============== +Eng_Unload + +Free engine .dll and reset interfaces +============== +*/ +void Eng_Unload(void) +{ + if ( ghMod ) + { + Sys_FreeLibrary(ghMod); + ghMod = 0; + } + + Eng_LoadStubs(); + + gDLLState = 0; + gDLLStateInfo = 0; +} + +/* +============== +Eng_KillEngine + +Load failure on engine +============== +*/ +void Eng_KillEngine( long *phMod ) +{ + Sys_FreeLibrary( ghMod ); + ghMod = *phMod = 0; + + Eng_LoadStubs(); +} + +/* +============== +Eng_Load + +Try to load the engine with specified command line, etc. etc. and the specified .dll +============== +*/ +int Eng_Load( const char *cmdline, struct exefuncs_s *pef, int memory, void *pmembase, const char *psz, int iSubMode ) +{ + char szLastDLL[ 100 ]; + long hMod = (long)NULL; + +#if defined( _DEBUG ) + char *p; + + if ( psz && !stricmp( psz, "swds.dll" ) && CheckParm( "-force", &p ) && p ) + { + psz = p; + } +#endif + + // Are we loading a different engine? + if ( psz && ghMod && !strcmp( psz, szLastDLL ) ) + { + return 1; + } + + if ( ghMod ) + { + Eng_KillEngine( &hMod ); + } + + if ( !psz ) + { + hMod = 0; + Eng_LoadStubs(); + } + else if ( !ghMod ) + { + hMod = Sys_LoadLibrary( (char *)psz ); + if ( !hMod ) + { + return 0; + } + + // Load function table from engine + if ( !Eng_LoadFunctions( hMod ) ) + { + Sys_FreeLibrary( hMod ); + Eng_LoadStubs(); + return 0; + } + + // Activate engine + Eng_SetState( DLL_ACTIVE ); + } + + Eng_SetSubState( iSubMode ); + + snprintf( szLastDLL, sizeof( szLastDLL ), "%s", psz ); + + ghMod = hMod; + + if ( ghMod ) + { + static char *szEmpty = ""; + + char *p = (char *)cmdline; + if ( !p ) + { + p = szEmpty; + } + + if ( !engineapi.Game_Init( p, (unsigned char *)pmembase, memory, pef, NULL, 1) ) + { + Sys_FreeLibrary(ghMod); + ghMod = hMod = 0; + return 0; + } + + if ( engineapi.SetStartupMode ) + { + engineapi.SetStartupMode( 1 ); + } + + if ( engineapi.Host_Frame ) + { + Eng_Frame( 1, 0.05 ); + } + + if ( engineapi.SetStartupMode ) + { + engineapi.SetStartupMode( 0 ); + } + } + return 1; +} + +/* +============== +Eng_Frame + +Run a frame in the engine, if it's loaded. +============== +*/ +int Eng_Frame( int fForce, double time ) +{ + if ( ( gDLLState != DLL_ACTIVE ) && !fForce ) + return 0; + + if ( gDLLState ) + { + gDLLStateInfo = DLL_NORMAL; + + int iState = engineapi.Host_Frame ( (float)time, gDLLState, &gDLLStateInfo ); + + // Special Signal + if ( gDLLStateInfo != DLL_NORMAL ) + { + switch (gDLLStateInfo) + { + case DLL_QUIT: + Eng_Unload(); +#ifdef _WIN32 + PostQuitMessage(0); +#else + exit( 0 ); +#endif + break; + case DLL_RESTART: + Eng_Unload(); +#ifdef _WIN32 + PostQuitMessage(1); +#else + exit( 1 ); +#endif + break; + default: + break; + } + } + + // Are we in our transistion counter? + if (iWait) + { + iWait--; + + // Defer all pauses until we're ready to bring up the launcher + if (iState == DLL_PAUSED) + { + fDeferedPause = 1; + Eng_SetState(DLL_ACTIVE); + iState = DLL_ACTIVE; + } + + // Are we done waiting, if so, did someone request a pause? + if (!iWait && fDeferedPause) + { + //force a pause + iState = DLL_PAUSED; + gDLLState = DLL_ACTIVE; + fDeferedPause = 0; + } + } + + // Are we now in a transistion? + if (iState == DLL_TRANS) + { + iState = DLL_ACTIVE; + iWait = 5; // Let's wait N frames before we'll allow a pause + Eng_SetState(DLL_ACTIVE); + } + + // Has the state changed? + if (iState != gDLLState) + { + Eng_SetState(iState); + } + } + + if ( gDLLState == DLL_CLOSE || gDLLState == DLL_RESTART) + { + static int bQuitting = 0; + + if ( !bQuitting ) + { + bQuitting = 1; + engineapi.Cbuf_AddText( "killserver\n" ); + Eng_Frame( 1, 0.05 ); + Sys_Sleep( 100 ); + Eng_Frame( 1, 0.05 ); + Sys_Sleep( 100 ); + return gDLLState; + } + + Eng_Unload(); + +#ifdef _WIN32 + PostQuitMessage(0); +#else + if ( gDLLState == DLL_RESTART ) + { + exit( 1 ); + } + else + { + exit( 0 ); + } +#endif + } + + return gDLLState; +} + +/* +============== +Eng_SetSubState + +============== +*/ +void Eng_SetSubState(int iSubState) +{ + if ( !engineapi.GameSetSubState ) + return; + + if ( iSubState != ENG_NORMAL ) + { + engineapi.GameSetSubState( iSubState ); + } +} + +/* +============== +Eng_SetState + +============== +*/ +void Eng_SetState(int iState) +{ + gDLLState = iState; + + if (engineapi.GameSetState) + { + engineapi.GameSetState( iState ); + } +} diff --git a/hlsdk/dedicated/enginecallback.h b/hlsdk/dedicated/enginecallback.h new file mode 100644 index 00000000..cef4c644 --- /dev/null +++ b/hlsdk/dedicated/enginecallback.h @@ -0,0 +1,32 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// enginecallback.h +#ifndef INC_ENGINECALLBACKH +#define INC_ENGINECALLBACKH + +typedef enum +{ + // A dedicated server with no ability to start a client + ca_dedicated, + // Full screen console with no connection + ca_disconnected, + // Challenge requested, waiting for response or to resend connection request. + ca_connecting, + // valid netcon, talking to a server, waiting for server data + ca_connected, + // valid netcon, autodownloading + ca_uninitialized, + // d/l complete, ready game views should be displayed + ca_active +} cactive_t; + +#include "engine_launcher_api.h" + +extern engine_api_t engineapi; + +#endif // !INC_ENGINECALLBACKH diff --git a/hlsdk/dedicated/exports.h b/hlsdk/dedicated/exports.h new file mode 100644 index 00000000..a115175a --- /dev/null +++ b/hlsdk/dedicated/exports.h @@ -0,0 +1,14 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// functions exported from front end to engine +#ifndef INC_EXPORTSH +#define INC_EXPORTSH + +extern void ErrorMessage(int nLevel, const char *pszErrorMessage); + +#endif // !INC_EXPORTSH diff --git a/hlsdk/dedicated/msvc6/Dedicated.dsp b/hlsdk/dedicated/msvc6/Dedicated.dsp new file mode 100644 index 00000000..a411541e --- /dev/null +++ b/hlsdk/dedicated/msvc6/Dedicated.dsp @@ -0,0 +1,144 @@ +# Microsoft Developer Studio Project File - Name="Dedicated" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=Dedicated - 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 "Dedicated.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 "Dedicated.mak" CFG="Dedicated - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "Dedicated - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "Dedicated - 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)" == "Dedicated - 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 ".\Release" +# PROP Intermediate_Dir ".\Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "..\..\engine" /I "..\..\common" /I "..\..\dedicated" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "DEDICATED" /D "LAUNCHERONLY" /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 /machine:I386 +# ADD LINK32 wsock32.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 winmm.lib ..\..\utils\procinfo\lib\win32_vc6\procinfo.lib /nologo /subsystem:windows /machine:I386 /out:".\Release/hlds.exe" +# SUBTRACT LINK32 /map /debug + +!ELSEIF "$(CFG)" == "Dedicated - 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 ".\Debug" +# PROP Intermediate_Dir ".\Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "..\..\engine" /I "..\..\common" /I "..\..\dedicated" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "DEDICATED" /D "LAUNCHERONLY" /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 /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 +# ADD LINK32 wsock32.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 winmm.lib ..\..\utils\procinfo\lib\win32_vc6\procinfo.lib /nologo /subsystem:windows /map /debug /machine:I386 /out:".\Debug/hlds.exe" + +!ENDIF + +# Begin Target + +# Name "Dedicated - Win32 Release" +# Name "Dedicated - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=..\conproc.cpp +# End Source File +# Begin Source File + +SOURCE=..\dedicated.rc +# End Source File +# Begin Source File + +SOURCE=..\engine.cpp +# End Source File +# Begin Source File + +SOURCE=..\sys_ded.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=..\conproc.h +# End Source File +# Begin Source File + +SOURCE=..\..\common\crc.h +# End Source File +# Begin Source File + +SOURCE=..\dedicated.h +# End Source File +# Begin Source File + +SOURCE=..\resource.h +# End Source File +# Begin Source File + +SOURCE=..\sys_ded.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" +# Begin Source File + +SOURCE=..\NetGame.ico +# End Source File +# End Group +# End Target +# End Project diff --git a/hlsdk/dedicated/msvc7/Dedicated.vcproj b/hlsdk/dedicated/msvc7/Dedicated.vcproj new file mode 100644 index 00000000..240099fe --- /dev/null +++ b/hlsdk/dedicated/msvc7/Dedicated.vcproj @@ -0,0 +1,255 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hlsdk/dedicated/msvc8/Dedicated.vcproj b/hlsdk/dedicated/msvc8/Dedicated.vcproj new file mode 100644 index 00000000..c50acb86 --- /dev/null +++ b/hlsdk/dedicated/msvc8/Dedicated.vcproj @@ -0,0 +1,346 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hlsdk/dedicated/resource.h b/hlsdk/dedicated/resource.h new file mode 100644 index 00000000..0eb27652 --- /dev/null +++ b/hlsdk/dedicated/resource.h @@ -0,0 +1,25 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by Dedicated.rc +// +#define IDI_HALFLIFE 101 +#define IDD_CDKEY 102 +#define IDC_KEY 1000 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 104 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/hlsdk/dedicated/sys_ded.cpp b/hlsdk/dedicated/sys_ded.cpp new file mode 100644 index 00000000..2baec0c2 --- /dev/null +++ b/hlsdk/dedicated/sys_ded.cpp @@ -0,0 +1,1083 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifdef _WIN32 +#include +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +//#define FRAMERATE // define me to have hlds print out what it thinks the framerate is + +#include +#include + +#include "sys_ded.h" +#include "conproc.h" +#include "dedicated.h" +#include "exefuncs.h" + +#include "dll_state.h" +#include "enginecallback.h" +#if defined( _WIN32 ) +#include "../utils/procinfo/procinfo.h" +#endif + +#ifdef _WIN32 +static HANDLE hinput; +static HANDLE houtput; + +static const char *g_pszengine = "swds.dll"; +#else +static const char *g_pszengine = "engine_i386.so"; +static char g_szEXEName[ 256 ]; +#endif + +// System Memory & Size +static unsigned char *gpMemBase = NULL; +static int giMemSize = 0x2000000; // 32 Mb default heapsize + +exefuncs_t ef; + +static char console_text[256]; +static int console_textlen; + +static long hDLLThirdParty = 0L; + +char *gpszCmdLine = NULL; + +SleepType Sys_Sleep; + + +void Sys_Sleep_Old( int msec ) +{ +#ifdef _WIN32 + Sleep( msec ); +#else + usleep(msec * 1000); +#endif +} +#if !defined ( _WIN32 ) + +typedef int (*NET_Sleep_t)( void ); +NET_Sleep_t NET_Sleep =NULL; +extern long ghMod; // from engine.cpp + +void Sys_Sleep_Net( int msec ) +{ + if ( NET_Sleep != NULL) + { + NET_Sleep(); + } + else + { + Sys_Sleep_Old(msec); // NET_Sleep isn't hooked yet, fallback to the old method + } +} + +volatile char paused=0; // this checks if pause has run yet, tell the compiler it can change at any time +// for Sys_Sleep_Timer() +void alarmFunc(int num) { + signal( SIGALRM, alarmFunc ); // reset the signal handler + if(!paused) { // paused is 0, the timer has fired before the pause was called... Lets queue it again + struct itimerval itim; + itim.it_interval.tv_sec = 0; + itim.it_interval.tv_usec = 0; + itim.it_value.tv_sec = 0; + itim.it_value.tv_usec = 1000; // get it to run again real soon + setitimer( ITIMER_REAL, &itim, 0 ); + } + +} +#endif + +void Sys_Sleep_Timer( int msec ) +{ +#ifdef _WIN32 + Sleep( msec ); +#else +// linux runs on a 100Hz scheduling clock, so the minimum latency from +// usleep is 10msec. However, people want lower latency than this.. +// +// There are a few solutions, one is to use the realtime scheduler in the +// kernel BUT this needs root privelleges to start. It also can play +// unfriendly with other programs. + +// Another solution is to use software timers, they use the RTC of the +// system and are accurate to microseconds (or so). + +// timers, via setitimer() are used here + + struct itimerval tm; + + tm.it_value.tv_sec=msec/1000; // convert msec to seconds + tm.it_value.tv_usec=static_cast<__suseconds_t>((msec%1000)*1E3); // get the number of msecs and change to micros + tm.it_interval.tv_sec = 0; + tm.it_interval.tv_usec = 0; + + paused=0; + if( setitimer(ITIMER_REAL,&tm,NULL)==0) + { // set the timer to trigger + pause(); // wait for the signal + } + paused=1; + +#endif +} + + +void Sys_Sleep_Select( int msec ) +{ +#ifdef _WIN32 + Sleep( msec ); +#else // _WIN32 + struct timeval tv; + + // Assumes msec < 1000 + tv.tv_sec = 0; + tv.tv_usec = 1000 * msec; + + select( 1, NULL, NULL, NULL, &tv ); +#endif // _WIN32 +} + + +void *Sys_GetProcAddress( long library, const char *name ) +{ +#ifdef _WIN32 + return ( void * )GetProcAddress( (HMODULE)library, name ); +#else // LINUX + return dlsym( (void *)library, name ); +#endif +} + +long Sys_LoadLibrary( char *lib ) +{ + void *hDll = NULL; + +#ifdef _WIN32 + hDll = ::LoadLibrary( lib ); +#else + char cwd[1024]; + char absolute_lib[1024]; + + if (!getcwd(cwd, sizeof(cwd))) + Sys_ErrorMessage(1, "Sys_LoadLibrary: Couldn't determine current directory."); + + if (cwd[strlen(cwd)-1] == '/') + cwd[strlen(cwd)-1] = 0; + + snprintf(absolute_lib, sizeof(absolute_lib), "%s/%s", cwd, lib); + + hDll = dlopen( absolute_lib, RTLD_NOW ); + if ( !hDll ) + { + Sys_ErrorMessage( 1, dlerror() ); + } +#endif + return (long)hDll; +} + +void Sys_FreeLibrary( long library ) +{ + if ( !library ) + return; + +#ifdef _WIN32 + ::FreeLibrary( (HMODULE)library ); +#else + dlclose( (void *)library ); +#endif +} + +int Sys_GetExecutableName( char *out ) +{ +#ifdef _WIN32 + if ( !::GetModuleFileName( ( HINSTANCE )GetModuleHandle( NULL ), out, 256 ) ) + { + return 0; + } +#else + strcpy( out, g_szEXEName ); +#endif + return 1; +} + +/* +============== +Sys_ErrorMessage + +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 ); + PostQuitMessage(0); +#else + printf( "%s\n", msg ); + exit( -1 ); +#endif +} + +#ifdef _WIN32 +/* +============== +UpdateStatus + +Update status line at top of console if engine is running +============== +*/ +void UpdateStatus( int force ) +{ + static double tLast = 0.0; + double tCurrent; + char szPrompt[256]; + int n, spec, nMax; + char szMap[32]; + float fps; + + if ( !engineapi.Host_GetHostInfo ) + return; + + tCurrent = (float)( timeGetTime() / 1000.0f ); + + engineapi.Host_GetHostInfo( &fps, &n, &spec, &nMax, szMap ); + + if ( !force ) + { + if ( ( tCurrent - tLast ) < 0.5f ) + return; + } + + tLast = tCurrent; + + snprintf( szPrompt, sizeof(szPrompt), "%.1f fps %2i(%2i spec)/%2i on %16s", (float)fps, n, spec, nMax, szMap); + + WriteStatusText( szPrompt ); +} +#endif + +/* +================ +Sys_ConsoleOutput + +Print text to the dedicated console +================ +*/ +void Sys_ConsoleOutput (char *string) +{ +#ifdef _WIN32 + unsigned long dummy; + char text[256]; + + if (console_textlen) + { + text[0] = '\r'; + memset(&text[1], ' ', console_textlen); + text[console_textlen+1] = '\r'; + text[console_textlen+2] = 0; + WriteFile(houtput, text, console_textlen+2, &dummy, NULL); + } + + WriteFile(houtput, string, strlen(string), &dummy, NULL); + + if (console_textlen) + { + WriteFile(houtput, console_text, console_textlen, &dummy, NULL); + } + UpdateStatus( 1 /* force */ ); +#else + printf( "%s", string ); + fflush( stdout ); +#endif +} + +/* +============== +Sys_Printf + +Engine is printing to console +============== +*/ +void Sys_Printf(char *fmt, ...) +{ + // Dump text to debugging console. + va_list argptr; + char szText[1024]; + + va_start (argptr, fmt); + vsnprintf (szText, sizeof(szText), fmt, argptr); + va_end (argptr); + + // Get Current text and append it. + Sys_ConsoleOutput( szText ); +} + +/* +============== +Load3rdParty + +Load support for third party .dlls ( gamehost ) +============== +*/ +void Load3rdParty( void ) +{ + // Only do this if the server operator wants the support. + // ( In case of malicious code, too ) + if ( CheckParm( "-usegh" ) ) + { + hDLLThirdParty = Sys_LoadLibrary( "ghostinj.dll" ); + } +} + +/* +============== +EF_VID_ForceUnlockedAndReturnState + +Dummy funcion called by engine +============== +*/ +int EF_VID_ForceUnlockedAndReturnState(void) +{ + return 0; +} + +/* +============== +EF_VID_ForceLockState + +Dummy funcion called by engine +============== +*/ +void EF_VID_ForceLockState(int) +{ +} + +/* +============== +CheckParm + +Search for psz in command line to .exe, if **ppszValue is set, then the pointer is + directed at the NEXT argument in the command line +============== +*/ +char *CheckParm(const char *psz, char **ppszValue) +{ + int i; + static char sz[128]; + char *pret; + + if (!gpszCmdLine) + return NULL; + + pret = strstr( gpszCmdLine, psz ); + + // should we return a pointer to the value? + if (pret && ppszValue) + { + char *p1 = pret; + *ppszValue = NULL; + + while ( *p1 && (*p1 != 32)) + p1++; + + if (p1 != 0) + { + char *p2 = ++p1; + + for ( i = 0; i < 128; i++ ) + { + if ( !*p2 || (*p2 == 32)) + break; + sz[i] = *p2++; + } + + sz[i] = 0; + *ppszValue = &sz[0]; + } + } + + return pret; +} + +/* +============== +InitInstance + +============== +*/ +int InitInstance( void ) +{ + Load3rdParty(); + + Eng_SetState( DLL_INACTIVE ); + + memset( &ef, 0, sizeof( ef ) ); + + // Function pointers used by dedicated server + ef.Sys_Printf = Sys_Printf; + ef.ErrorMessage = Sys_ErrorMessage; + + ef.VID_ForceLockState = EF_VID_ForceLockState; + ef.VID_ForceUnlockedAndReturnState = EF_VID_ForceUnlockedAndReturnState; + +#ifdef _WIN32 + // Data + ef.fMMX = PROC_IsMMX(); + ef.iCPUMhz = PROC_GetSpeed(); // in MHz +#endif + + return 1; +} + +/* +================ +Sys_ConsoleInput + +================ +*/ +#ifdef _WIN32 +char *Sys_ConsoleInput (void) +{ + INPUT_RECORD recs[1024]; + unsigned long dummy; + int ch; + unsigned long numread, numevents; + + while ( 1 ) + { + if (!GetNumberOfConsoleInputEvents (hinput, &numevents)) + { + exit( -1 ); + } + + if (numevents <= 0) + break; + + if ( !ReadConsoleInput(hinput, recs, 1, &numread) ) + { + exit( -1 ); + } + + if (numread != 1) + { + exit( -1 ); + } + + if ( recs[0].EventType == KEY_EVENT ) + { + if ( !recs[0].Event.KeyEvent.bKeyDown ) + { + ch = recs[0].Event.KeyEvent.uChar.AsciiChar; + switch (ch) + { + case '\r': + WriteFile(houtput, "\r\n", 2, &dummy, NULL); + if (console_textlen) + { + console_text[console_textlen] = 0; + console_textlen = 0; + return console_text; + } + break; + + case '\b': + if (console_textlen) + { + console_textlen--; + WriteFile(houtput, "\b \b", 3, &dummy, NULL); + } + break; + + default: + if (ch >= ' ') + { + if (console_textlen < sizeof(console_text)-2) + { + WriteFile(houtput, &ch, 1, &dummy, NULL); + console_text[console_textlen] = ch; + console_textlen++; + } + } + + break; + + } + } + } + } + + return NULL; +} +#else +char *Sys_ConsoleInput(void) +{ + struct timeval tvTimeout; + fd_set fdSet; + unsigned char ch; + + FD_ZERO( &fdSet ); + FD_SET( STDIN_FILENO, &fdSet ); + + tvTimeout.tv_sec = 0; + tvTimeout.tv_usec = 0; + + if ( select( 1, &fdSet, NULL, NULL, &tvTimeout ) == -1 || !FD_ISSET( STDIN_FILENO, &fdSet ) ) + return NULL; + + console_textlen = 0; + + // We're going to shove a newline onto the end of the input later in + // ProcessConsoleInput(), so only accept 254 chars instead of 255. -LH + + while ( read( STDIN_FILENO, &ch, 1 ) ) + { + if ( ( ch == 10 ) || ( console_textlen == 254 ) ) + { + // For commands longer than we can accept, consume the remainder of the input buffer + if ( ( console_textlen == 254 ) && ( ch != 10 ) ) + { + while ( read( STDIN_FILENO, &ch, 1 ) ) + { + if ( ch == 10 ) + break; + } + } + + //Null terminate string and return + console_text[ console_textlen ] = 0; + console_textlen = 0; + return console_text; + } + + console_text[ console_textlen++ ] = ch; + } + + return NULL; +} +#endif + +#ifdef _WIN32 +/* +============== +WriteStatusText + +============== +*/ +void WriteStatusText( char *szText ) +{ + char szFullLine[81]; + COORD coord; + DWORD dwWritten = 0; + WORD wAttrib[80]; + + int i; + + for ( i = 0; i < 80; i++ ) + { + wAttrib[i] = FOREGROUND_RED | FOREGROUND_INTENSITY; + } + + memset( szFullLine, 0, sizeof(szFullLine) ); + snprintf( szFullLine, sizeof( szFullLine ), "%s", szText ); + + coord.X = 0; + coord.Y = 0; + + WriteConsoleOutputAttribute( houtput, wAttrib, 80, coord, &dwWritten ); + WriteConsoleOutputCharacter( houtput, szFullLine, 80, coord, &dwWritten ); +} +#endif + +/* +============== +CreateConsoleWindow + +Create console window ( overridable? ) +============== +*/ +int CreateConsoleWindow( void ) +{ +#ifdef _WIN32 + if ( !AllocConsole () ) + { + return 0; + } + + hinput = GetStdHandle (STD_INPUT_HANDLE); + houtput = GetStdHandle (STD_OUTPUT_HANDLE); + + InitConProc(); +#endif + + return 1; +} + +/* +============== +DestroyConsoleWindow + +============== +*/ +void DestroyConsoleWindow( void ) +{ +#ifdef _WIN32 + FreeConsole (); + + // shut down QHOST hooks if necessary + DeinitConProc (); +#endif +} + +/* +============== +ProcessConsoleInput + +============== +*/ +void ProcessConsoleInput( void ) +{ + char *s; + + if ( !engineapi.Cbuf_AddText ) + return; + + do + { + s = Sys_ConsoleInput (); + if (s) + { + char szBuf[ 256 ]; + snprintf( szBuf, sizeof(szBuf), "%s\n", s ); + engineapi.Cbuf_AddText ( szBuf ); + } + } while (s); +} + +/* +================ +GameInit +================ +*/ +int GameInit(void) +{ + char *p; + + // Command line override + if ( (CheckParm ("-heapsize", &p ) ) && p ) + { + giMemSize = atoi( p ) * 1024; + } + + Sys_Sleep=Sys_Sleep_Old; + +#if !defined ( _WIN32 ) + char *pPingType; + int type; + if ( (CheckParm ("-pingboost", &pPingType)) && pPingType ) + { + type=atoi( pPingType ); + switch( type ) { + case 1: + //printf("Using timer method\n"); + signal(SIGALRM,alarmFunc); + Sys_Sleep=Sys_Sleep_Timer; + break; + case 2: + Sys_Sleep = Sys_Sleep_Select; + break; + case 3: + Sys_Sleep = Sys_Sleep_Net; + // we Sys_GetProcAddress NET_Sleep() from + //engine_i386.so later in this function + break; + default: // just in case + Sys_Sleep=Sys_Sleep_Old; + break; + } + + } +#endif + + + // Try and allocated it +#ifdef _WIN32 + gpMemBase = (unsigned char *)::GlobalAlloc( GMEM_FIXED, giMemSize ); +#else + gpMemBase = (unsigned char *)malloc( giMemSize ); +#endif + if (!gpMemBase) + { + return 0; + } + +#ifdef _WIN32 + // Check that we are running on Win32 + OSVERSIONINFO vinfo; + vinfo.dwOSVersionInfoSize = sizeof(vinfo); + + if ( !GetVersionEx ( &vinfo ) ) + { + return 0; + } + + if ( vinfo.dwPlatformId == VER_PLATFORM_WIN32s ) + { + return 0; + } + +#endif + + if ( !Eng_Load( gpszCmdLine, &ef, giMemSize, gpMemBase, g_pszengine, DLL_NORMAL ) ) + { + return 0; + } + +#if !defined ( _WIN32 ) + if ( type == 3 ) + { + NET_Sleep=(NET_Sleep_t)Sys_GetProcAddress(ghMod,"NET_Sleep_Timeout"); + //printf("Net_Sleep:%p\n",NET_Sleep); + } +#endif + + Eng_SetState( DLL_ACTIVE ); + + return 1; +} + +/* +============== +GameShutdown + +============== +*/ +void GameShutdown( void ) +{ + Eng_Unload(); + + if ( gpMemBase ) + { +#ifdef _WIN32 + ::GlobalFree( gpMemBase ); +#else + free( gpMemBase ); +#endif + gpMemBase = NULL; + } +} + +#ifdef _WIN32 + +BOOL gbAppHasBeenTerminated = FALSE; +BOOL CtrlHandler(DWORD fdwCtrlType) +{ + switch ( fdwCtrlType ) + { + case CTRL_C_EVENT: + case CTRL_CLOSE_EVENT: + case CTRL_BREAK_EVENT: + case CTRL_LOGOFF_EVENT: + case CTRL_SHUTDOWN_EVENT: + gbAppHasBeenTerminated = TRUE; + return TRUE; + + default: + return FALSE; + } +} + +/* +============== +WinMain + +EXE entry point +============== +*/ +int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow ) +{ + int iret = -1; + + // Store off command line for argument searching + gpszCmdLine = strdup( GetCommandLine() ); + + Sys_Sleep=Sys_Sleep_Old; // win32 doesn't have pingbooster options :) + + if ( !InitInstance() ) + { + goto cleanup; + } + + if ( !CreateConsoleWindow() ) + { + goto cleanup; + } + + if ( !GameInit() ) + { + goto cleanup; + } + + if ( engineapi.SetStartupMode ) + { + engineapi.SetStartupMode( 1 ); + } + + while ( 1 ) + { + int bDone = 0; + + static double oldtime = 0.0; + + MSG msg; + double newtime; + double dtime; + + if ( gbAppHasBeenTerminated ) + break; + + // Try to allow other apps to get some CPU + Sys_Sleep( 1 ); + + if ( !engineapi.Sys_FloatTime ) + break; + + while ( 1 ) + { + newtime = engineapi.Sys_FloatTime(); + if ( newtime < oldtime ) + { + oldtime = newtime - 0.05; + } + + dtime = newtime - oldtime; + + if ( gbAppHasBeenTerminated ) + break; + + if ( dtime > 0.001 ) + break; + + // Running really fast, yield some time to other apps + Sys_Sleep( 1 ); + } + + + while ( ::PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) ) + { + if ( gbAppHasBeenTerminated ) + break; + + if (!::GetMessage( &msg, NULL, 0, 0)) + { + bDone = 1; + break; + } + + ::TranslateMessage( &msg ); + ::DispatchMessage( &msg ); + } + + if ( bDone ) + break; + + ProcessConsoleInput(); + + if ( engineapi.Host_Frame ) + { + Eng_Frame( 0, dtime ); + } + + UpdateStatus( 0 /* don't force */ ); + + oldtime = newtime; + } + + GameShutdown(); + + DestroyConsoleWindow(); + + iret = 1; + +cleanup: + + if ( gpszCmdLine ) + { + free( gpszCmdLine ); + } + + return iret; +} + +#else + +#define MAX_LINUX_CMDLINE 512 + +static char cmdline[ MAX_LINUX_CMDLINE ]; + +void BuildCmdLine( int argc, char **argv ) +{ + int len; + int i; + + for (len = 0, i = 1; i < argc; i++) + { + len += strlen(argv[i]) + 1; + } + + if ( len > MAX_LINUX_CMDLINE ) + { + printf( "command line too long, %i max\n", MAX_LINUX_CMDLINE ); + exit(-1); + return; + } + + cmdline[0] = '\0'; + for ( i = 1; i < argc; i++ ) + { + if ( i > 1 ) + { + strcat( cmdline, " " ); + } + strcat( cmdline, argv[ i ] ); + } +} + +char *GetCommandLine( void ) +{ + return cmdline; +} + +int main(int argc, char **argv) +{ + int iret = -1; + +#ifdef _DEBUG + snprintf(g_szEXEName, sizeof( g_szEXEName ), "hlds_run.dbg" ); +#else + snprintf(g_szEXEName, sizeof( g_szEXEName ), "%s", *argv ); +#endif + + // Store off command line for argument searching + BuildCmdLine(argc, argv); + gpszCmdLine = strdup( GetCommandLine() ); + + if ( !InitInstance() ) + { + goto cleanup; + } + + if ( !CreateConsoleWindow() ) + { + goto cleanup; + } + + if ( !GameInit() ) + { + goto cleanup; + } + + if ( engineapi.SetStartupMode ) + { + engineapi.SetStartupMode( 1 ); + } + + while ( 1 ) + { + static double oldtime = 0.0; + + double newtime; + double dtime; + +#ifdef FRAMERATE + static int frameNumber=0,frameRate=0; + static struct timeval beforeTime; + if(frameRate==0) { + gettimeofday(&beforeTime,NULL); + } + + frameRate++; + + if(frameRate%1000==0) { + struct timeval afterTime; + float timediff; + + gettimeofday(&afterTime,NULL); + timediff= (float)(afterTime.tv_sec-beforeTime.tv_sec)+ + ((float)(afterTime.tv_usec-beforeTime.tv_usec))/1E6; + + printf("Frame Rate:%f\n",(float)frameRate/timediff); + frameRate=0; + + } +#endif + + Sys_Sleep( 1 ); + + if ( !engineapi.Sys_FloatTime ) + break; + + while ( 1 ) + { + newtime = engineapi.Sys_FloatTime(); + if ( newtime < oldtime ) + { + oldtime = newtime - 0.05; + } + + dtime = newtime - oldtime; + + if ( dtime > 0.001 ) + break; + + // Running really fast, yield some time to other apps + Sys_Sleep( 1 ); + } + + ProcessConsoleInput(); + + Eng_Frame( 0, dtime ); + + oldtime = newtime; + } + + GameShutdown(); + + DestroyConsoleWindow(); + + iret = 1; + +cleanup: + + if ( gpszCmdLine ) + { + free( gpszCmdLine ); + } + + if ( hDLLThirdParty ) + { + Sys_FreeLibrary( hDLLThirdParty ); + hDLLThirdParty = 0L; + } + + return iret; +} + +#endif diff --git a/hlsdk/dedicated/sys_ded.h b/hlsdk/dedicated/sys_ded.h new file mode 100644 index 00000000..3e2d771e --- /dev/null +++ b/hlsdk/dedicated/sys_ded.h @@ -0,0 +1,31 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( SYS_DEDH ) +#define SYS_DEDH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +#if defined _MSC_VER && _MSC_VER >= 1400 + #ifndef _CRT_SECURE_NO_DEPRECATE + #define _CRT_SECURE_NO_DEPRECATE + #endif + + #pragma warning(disable: 4996) // deprecated functions +#endif + +typedef void (*SleepType)(int); +long Sys_LoadLibrary( char *lib ); +void Sys_FreeLibrary( long library ); +void *Sys_GetProcAddress( long library, const char *name ); +void Sys_Printf(char *fmt, ...); +void Sys_ErrorMessage( int level, const char *msg ); + +#endif // SYS_DEDH diff --git a/hlsdk/dlls/Makefile b/hlsdk/dlls/Makefile new file mode 100644 index 00000000..b3e9479f --- /dev/null +++ b/hlsdk/dlls/Makefile @@ -0,0 +1,149 @@ +# +# Half-Life Standard SDK 2.3 mp_i386.so Makefile for i386 Linux +# +# October 2002 by Leon Hartwig (hartwig@valvesoftware.com) +# + +DLLNAME=mp + +ARCH=i386 + +#make sure this is the correct compiler for your system +CC=gcc + +DLL_SRCDIR=. +ENGINE_SRCDIR=../engine +COMMON_SRCDIR=../common +WPN_SHARED_SRCDIR=./wpn_shared +PM_SHARED_SRCDIR=../pm_shared +GAME_SHARED_SRCDIR=../game_shared + +DLL_OBJDIR=$(DLL_SRCDIR)/obj +WPN_SHARED_OBJDIR=$(WPN_SHARED_SRCDIR)/obj +PM_SHARED_OBJDIR=$(PM_SHARED_SRCDIR)/obj +GAME_SHARED_OBJDIR=$(GAME_SHARED_SRCDIR)/obj + +BASE_CFLAGS= -Dstricmp=strcasecmp -D_strnicmp=strncasecmp -Dstrnicmp=strncasecmp \ + -DCLIENT_WEAPONS + +#safe optimization +CFLAGS=$(BASE_CFLAGS) -Wall -Wno-non-virtual-dtor -Wno-invalid-offsetof -Werror -m486 -O1 +C_FLAGS=$(BASE_CFLAGS) -Wall -Werror -m486 -O1 + +#full optimization +#CFLAGS=$(BASE_CFLAGS) -Wall -Wno-non-virtual-dtor -Wno-invalid-offsetof -Werror -O1 -m486 -ffast-math -funroll-loops \ + -fomit-frame-pointer -fexpensive-optimizations \ + -malign-loops=2 -malign-jumps=2 -malign-functions=2 + +#use these when debugging +#CFLAGS=$(BASE_CFLAGS) -g + +INCLUDEDIRS=-I. -I$(ENGINE_SRCDIR) -I$(COMMON_SRCDIR) -I$(PM_SHARED_SRCDIR) -I$(GAME_SHARED_SRCDIR) + +LDFLAGS= + +SHLIBEXT=so +SHLIBCFLAGS=-fPIC +SHLIBLDFLAGS=-shared + +DO_CC=$(CC) $(CFLAGS) $(SHLIBCFLAGS) $(INCLUDEDIRS) -o $@ -c $< +DO_C=$(CC) $(C_FLAGS) $(SHLIBCFLAGS) $(INCLUDEDIRS) -o $@ -c $< + +############################################################################# +# SETUP AND BUILD +# GAME +############################################################################# + +$(DLL_OBJDIR)/%.o: $(DLL_SRCDIR)/%.cpp + $(DO_CC) + +$(WPN_SHARED_OBJDIR)/%.o: $(WPN_SHARED_SRCDIR)/%.cpp + $(DO_CC) + +$(GAME_SHARED_OBJDIR)/%.o: $(GAME_SHARED_SRCDIR)/%.cpp + $(DO_CC) + +$(PM_SHARED_OBJDIR)/%.o: $(PM_SHARED_SRCDIR)/%.c + $(DO_C) + +OBJ = \ + $(DLL_OBJDIR)/airtank.o \ + $(DLL_OBJDIR)/animating.o \ + $(DLL_OBJDIR)/animation.o \ + $(DLL_OBJDIR)/bmodels.o \ + $(DLL_OBJDIR)/buttons.o \ + $(DLL_OBJDIR)/cbase.o \ + $(DLL_OBJDIR)/client.o \ + $(DLL_OBJDIR)/combat.o \ + $(DLL_OBJDIR)/crossbow.o \ + $(DLL_OBJDIR)/crowbar.o \ + $(DLL_OBJDIR)/doors.o \ + $(DLL_OBJDIR)/effects.o \ + $(DLL_OBJDIR)/egon.o \ + $(DLL_OBJDIR)/explode.o \ + $(DLL_OBJDIR)/func_break.o \ + $(DLL_OBJDIR)/func_tank.o \ + $(DLL_OBJDIR)/game.o \ + $(DLL_OBJDIR)/gamerules.o \ + $(DLL_OBJDIR)/gauss.o \ + $(DLL_OBJDIR)/ggrenade.o \ + $(DLL_OBJDIR)/globals.o \ + $(DLL_OBJDIR)/h_ai.o \ + $(DLL_OBJDIR)/h_battery.o \ + $(DLL_OBJDIR)/h_cycler.o \ + $(DLL_OBJDIR)/h_export.o \ + $(DLL_OBJDIR)/handgrenade.o \ + $(DLL_OBJDIR)/healthkit.o \ + $(DLL_OBJDIR)/hornet.o \ + $(DLL_OBJDIR)/hornetgun.o \ + $(DLL_OBJDIR)/items.o \ + $(DLL_OBJDIR)/lights.o \ + $(DLL_OBJDIR)/maprules.o \ + $(DLL_OBJDIR)/mortar.o \ + $(DLL_OBJDIR)/mp5.o \ + $(DLL_OBJDIR)/mpstubb.o \ + $(DLL_OBJDIR)/multiplay_gamerules.o \ + $(DLL_OBJDIR)/pathcorner.o \ + $(DLL_OBJDIR)/plane.o \ + $(DLL_OBJDIR)/plats.o \ + $(DLL_OBJDIR)/player.o \ + $(DLL_OBJDIR)/python.o \ + $(DLL_OBJDIR)/rpg.o \ + $(DLL_OBJDIR)/satchel.o \ + $(DLL_OBJDIR)/shotgun.o \ + $(DLL_OBJDIR)/singleplay_gamerules.o \ + $(DLL_OBJDIR)/skill.o \ + $(DLL_OBJDIR)/sound.o \ + $(DLL_OBJDIR)/soundent.o \ + $(DLL_OBJDIR)/spectator.o \ + $(DLL_OBJDIR)/squeakgrenade.o \ + $(DLL_OBJDIR)/subs.o \ + $(DLL_OBJDIR)/teamplay_gamerules.o \ + $(DLL_OBJDIR)/triggers.o \ + $(DLL_OBJDIR)/tripmine.o \ + $(DLL_OBJDIR)/util.o \ + $(DLL_OBJDIR)/weapons.o \ + $(DLL_OBJDIR)/world.o \ + $(DLL_OBJDIR)/xen.o \ + $(WPN_SHARED_OBJDIR)/hl_wpn_glock.o \ + $(GAME_SHARED_OBJDIR)/voice_gamemgr.o \ + $(PM_SHARED_OBJDIR)/pm_debug.o \ + $(PM_SHARED_OBJDIR)/pm_math.o \ + $(PM_SHARED_OBJDIR)/pm_shared.o + +$(DLLNAME)_$(ARCH).$(SHLIBEXT) : neat $(OBJ) + $(CC) $(CFLAGS) $(SHLIBLDFLAGS) $(LDFLAGS) -o $@ $(OBJ) + +neat: + -mkdir -p $(DLL_OBJDIR) + -mkdir -p $(WPN_SHARED_OBJDIR) + -mkdir -p $(GAME_SHARED_OBJDIR) + -mkdir -p $(PM_SHARED_OBJDIR) +clean: + -rm -f $(OBJ) + -rm -f $(DLLNAME)_$(ARCH).$(SHLIBEXT) +spotless: clean + -rm -rf $(DLL_OBJDIR) + -rm -rf $(WPN_SHARED_OBJDIR) + -rm -rf $(GAME_SHARED_OBJDIR) + -rm -rf $(PM_SHARED_OBJDIR) diff --git a/hlsdk/dlls/activity.h b/hlsdk/dlls/activity.h new file mode 100644 index 00000000..6fd3a188 --- /dev/null +++ b/hlsdk/dlls/activity.h @@ -0,0 +1,109 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef ACTIVITY_H +#define ACTIVITY_H + + +typedef enum { + ACT_RESET = 0, // Set m_Activity to this invalid value to force a reset to m_IdealActivity + ACT_IDLE = 1, + ACT_GUARD, + ACT_WALK, + ACT_RUN, + ACT_FLY, // Fly (and flap if appropriate) + ACT_SWIM, + ACT_HOP, // vertical jump + ACT_LEAP, // long forward jump + ACT_FALL, + ACT_LAND, + ACT_STRAFE_LEFT, + ACT_STRAFE_RIGHT, + ACT_ROLL_LEFT, // tuck and roll, left + ACT_ROLL_RIGHT, // tuck and roll, right + ACT_TURN_LEFT, // turn quickly left (stationary) + ACT_TURN_RIGHT, // turn quickly right (stationary) + ACT_CROUCH, // the act of crouching down from a standing position + ACT_CROUCHIDLE, // holding body in crouched position (loops) + ACT_STAND, // the act of standing from a crouched position + ACT_USE, + ACT_SIGNAL1, + ACT_SIGNAL2, + ACT_SIGNAL3, + ACT_TWITCH, + ACT_COWER, + ACT_SMALL_FLINCH, + ACT_BIG_FLINCH, + ACT_RANGE_ATTACK1, + ACT_RANGE_ATTACK2, + ACT_MELEE_ATTACK1, + ACT_MELEE_ATTACK2, + ACT_RELOAD, + ACT_ARM, // pull out gun, for instance + ACT_DISARM, // reholster gun + ACT_EAT, // monster chowing on a large food item (loop) + ACT_DIESIMPLE, + ACT_DIEBACKWARD, + ACT_DIEFORWARD, + ACT_DIEVIOLENT, + 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_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 ) + ACT_IDLE_ANGRY, // alternate idle animation in which the monster is clearly agitated. (loop) + ACT_WALK_HURT, // limp (loop) + ACT_RUN_HURT, // limp (loop) + ACT_HOVER, // Idle while in flight + ACT_GLIDE, // Fly (don't flap) + ACT_FLY_LEFT, // Turn left in flight + ACT_FLY_RIGHT, // Turn right in flight + ACT_DETECT_SCENT, // this means the monster smells a scent carried by the air + ACT_SNIFF, // this is the act of actually sniffing an item in front of the monster + ACT_BITE, // some large monsters can eat small things in one bite. This plays one time, EAT loops. + ACT_THREAT_DISPLAY, // without attacking, monster demonstrates that it is angry. (Yell, stick out chest, etc ) + ACT_FEAR_DISPLAY, // monster just saw something that it is afraid of + ACT_EXCITED, // for some reason, monster is excited. Sees something he really likes to eat, or whatever. + ACT_SPECIAL_ATTACK1, // very monster specific special attacks. + ACT_SPECIAL_ATTACK2, + ACT_COMBAT_IDLE, // agitated idle. + ACT_WALK_SCARED, + ACT_RUN_SCARED, + ACT_VICTORY_DANCE, // killed a player, do a victory dance. + ACT_DIE_HEADSHOT, // die, hit in head. + ACT_DIE_CHESTSHOT, // die, hit in chest + ACT_DIE_GUTSHOT, // die, hit in gut + ACT_DIE_BACKSHOT, // die, hit in back + ACT_FLINCH_HEAD, + ACT_FLINCH_CHEST, + ACT_FLINCH_STOMACH, + ACT_FLINCH_LEFTARM, + ACT_FLINCH_RIGHTARM, + ACT_FLINCH_LEFTLEG, + ACT_FLINCH_RIGHTLEG, +} Activity; + + +typedef struct { + int type; + char *name; +} activity_map_t; + +extern activity_map_t activity_map[]; + + +#endif //ACTIVITY_H diff --git a/hlsdk/dlls/activitymap.h b/hlsdk/dlls/activitymap.h new file mode 100644 index 00000000..5f77c55a --- /dev/null +++ b/hlsdk/dlls/activitymap.h @@ -0,0 +1,97 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#define _A( a ) { a, #a } + +activity_map_t activity_map[] = +{ +_A( ACT_IDLE ), +_A( ACT_GUARD ), +_A( ACT_WALK ), +_A( ACT_RUN ), +_A( ACT_FLY ), +_A( ACT_SWIM ), +_A( ACT_HOP ), +_A( ACT_LEAP ), +_A( ACT_FALL ), +_A( ACT_LAND ), +_A( ACT_STRAFE_LEFT ), +_A( ACT_STRAFE_RIGHT ), +_A( ACT_ROLL_LEFT ), +_A( ACT_ROLL_RIGHT ), +_A( ACT_TURN_LEFT ), +_A( ACT_TURN_RIGHT ), +_A( ACT_CROUCH ), +_A( ACT_CROUCHIDLE ), +_A( ACT_STAND ), +_A( ACT_USE ), +_A( ACT_SIGNAL1 ), +_A( ACT_SIGNAL2 ), +_A( ACT_SIGNAL3 ), +_A( ACT_TWITCH ), +_A( ACT_COWER ), +_A( ACT_SMALL_FLINCH ), +_A( ACT_BIG_FLINCH ), +_A( ACT_RANGE_ATTACK1 ), +_A( ACT_RANGE_ATTACK2 ), +_A( ACT_MELEE_ATTACK1 ), +_A( ACT_MELEE_ATTACK2 ), +_A( ACT_RELOAD ), +_A( ACT_ARM ), +_A( ACT_DISARM ), +_A( ACT_EAT ), +_A( ACT_DIESIMPLE ), +_A( ACT_DIEBACKWARD ), +_A( ACT_DIEFORWARD ), +_A( ACT_DIEVIOLENT ), +_A( ACT_BARNACLE_HIT ), +_A( ACT_BARNACLE_PULL ), +_A( ACT_BARNACLE_CHOMP ), +_A( ACT_BARNACLE_CHEW ), +_A( ACT_SLEEP ), +_A( ACT_INSPECT_FLOOR ), +_A( ACT_INSPECT_WALL ), +_A( ACT_IDLE_ANGRY ), +_A( ACT_WALK_HURT ), +_A( ACT_RUN_HURT ), +_A( ACT_HOVER ), +_A( ACT_GLIDE ), +_A( ACT_FLY_LEFT ), +_A( ACT_FLY_RIGHT ), +_A( ACT_DETECT_SCENT ), +_A( ACT_SNIFF ), +_A( ACT_BITE ), +_A( ACT_THREAT_DISPLAY ), +_A( ACT_FEAR_DISPLAY ), +_A( ACT_EXCITED ), +_A( ACT_SPECIAL_ATTACK1 ), +_A( ACT_SPECIAL_ATTACK2 ), +_A( ACT_COMBAT_IDLE ), +_A( ACT_WALK_SCARED ), +_A( ACT_RUN_SCARED ), +_A( ACT_VICTORY_DANCE ), +_A( ACT_DIE_HEADSHOT ), +_A( ACT_DIE_CHESTSHOT ), +_A( ACT_DIE_GUTSHOT ), +_A( ACT_DIE_BACKSHOT ), +_A( ACT_FLINCH_HEAD ), +_A( ACT_FLINCH_CHEST ), +_A( ACT_FLINCH_STOMACH ), +_A( ACT_FLINCH_LEFTARM ), +_A( ACT_FLINCH_RIGHTARM ), +_A( ACT_FLINCH_LEFTLEG ), +_A( ACT_FLINCH_RIGHTLEG ), +{ 0, NULL } +}; diff --git a/hlsdk/dlls/airtank.cpp b/hlsdk/dlls/airtank.cpp new file mode 100644 index 00000000..9a914021 --- /dev/null +++ b/hlsdk/dlls/airtank.cpp @@ -0,0 +1,118 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" + +class CAirtank : public CGrenade +{ + void Spawn( void ); + void Precache( void ); + void EXPORT TankThink( void ); + void EXPORT TankTouch( CBaseEntity *pOther ); + int BloodColor( void ) { return DONT_BLEED; }; + void Killed( entvars_t *pevAttacker, int iGib ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_state; +}; + + +LINK_ENTITY_TO_CLASS( item_airtank, CAirtank ); +TYPEDESCRIPTION CAirtank::m_SaveData[] = +{ + DEFINE_FIELD( CAirtank, m_state, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CAirtank, CGrenade ); + + +void CAirtank :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/w_oxygen.mdl"); + UTIL_SetSize(pev, Vector( -16, -16, 0), Vector(16, 16, 36)); + UTIL_SetOrigin( pev, pev->origin ); + + SetTouch( &CAirtank::TankTouch ); + SetThink( &CAirtank::TankThink ); + + pev->flags |= FL_MONSTER; + pev->takedamage = DAMAGE_YES; + pev->health = 20; + pev->dmg = 50; + m_state = 1; +} + +void CAirtank::Precache( void ) +{ + PRECACHE_MODEL("models/w_oxygen.mdl"); + PRECACHE_SOUND("doors/aliendoor3.wav"); +} + + +void CAirtank :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->owner = ENT( pevAttacker ); + + // UNDONE: this should make a big bubble cloud, not an explosion + + Explode( pev->origin, Vector( 0, 0, -1 ) ); +} + + +void CAirtank::TankThink( void ) +{ + // Fire trigger + m_state = 1; + SUB_UseTargets( this, USE_TOGGLE, 0 ); +} + + +void CAirtank::TankTouch( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + return; + + if (!m_state) + { + // "no oxygen" sound + EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_swim2.wav", 1.0, ATTN_NORM ); + return; + } + + // give player 12 more seconds of air + pOther->pev->air_finished = gpGlobals->time + 12; + + // suit recharge sound + EMIT_SOUND( ENT(pev), CHAN_VOICE, "doors/aliendoor3.wav", 1.0, ATTN_NORM ); + + // recharge airtank in 30 seconds + pev->nextthink = gpGlobals->time + 30; + m_state = 0; + SUB_UseTargets( this, USE_TOGGLE, 1 ); +} diff --git a/hlsdk/dlls/animating.cpp b/hlsdk/dlls/animating.cpp new file mode 100644 index 00000000..808cdd29 --- /dev/null +++ b/hlsdk/dlls/animating.cpp @@ -0,0 +1,318 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== monsters.cpp ======================================================== + + Monster-related utility code + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "animation.h" +#include "saverestore.h" + +TYPEDESCRIPTION CBaseAnimating::m_SaveData[] = +{ + DEFINE_FIELD( CBaseMonster, m_flFrameRate, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flGroundSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flLastEventCheck, FIELD_TIME ), + DEFINE_FIELD( CBaseMonster, m_fSequenceFinished, FIELD_BOOLEAN ), + DEFINE_FIELD( CBaseMonster, m_fSequenceLoops, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CBaseAnimating, CBaseDelay ); + + +//========================================================= +// StudioFrameAdvance - advance the animation frame up to the current time +// if an flInterval is passed in, only advance animation that number of seconds +//========================================================= +float CBaseAnimating :: StudioFrameAdvance ( float flInterval ) +{ + if (flInterval == 0.0) + { + flInterval = (gpGlobals->time - pev->animtime); + if (flInterval <= 0.001) + { + pev->animtime = gpGlobals->time; + return 0.0; + } + } + if (! pev->animtime) + flInterval = 0.0; + + pev->frame += flInterval * m_flFrameRate * pev->framerate; + pev->animtime = gpGlobals->time; + + if (pev->frame < 0.0 || pev->frame >= 256.0) + { + if (m_fSequenceLoops) + pev->frame -= (int)(pev->frame / 256.0) * 256.0; + else + pev->frame = (pev->frame < 0.0) ? 0 : 255; + m_fSequenceFinished = TRUE; // just in case it wasn't caught in GetEvents + } + + return flInterval; +} + +//========================================================= +// LookupActivity +//========================================================= +int CBaseAnimating :: LookupActivity ( int activity ) +{ + ASSERT( activity != 0 ); + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::LookupActivity( pmodel, pev, activity ); +} + +//========================================================= +// LookupActivityHeaviest +// +// Get activity with highest 'weight' +// +//========================================================= +int CBaseAnimating :: LookupActivityHeaviest ( int activity ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::LookupActivityHeaviest( pmodel, pev, activity ); +} + +//========================================================= +//========================================================= +int CBaseAnimating :: LookupSequence ( const char *label ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::LookupSequence( pmodel, label ); +} + + +//========================================================= +//========================================================= +void CBaseAnimating :: ResetSequenceInfo ( ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + GetSequenceInfo( pmodel, pev, &m_flFrameRate, &m_flGroundSpeed ); + m_fSequenceLoops = ((GetSequenceFlags() & STUDIO_LOOPING) != 0); + pev->animtime = gpGlobals->time; + pev->framerate = 1.0; + m_fSequenceFinished = FALSE; + m_flLastEventCheck = gpGlobals->time; +} + + + +//========================================================= +//========================================================= +BOOL CBaseAnimating :: GetSequenceFlags( ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::GetSequenceFlags( pmodel, pev ); +} + +//========================================================= +// DispatchAnimEvents +//========================================================= +void CBaseAnimating :: DispatchAnimEvents ( float flInterval ) +{ + MonsterEvent_t event; + + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + if ( !pmodel ) + { + ALERT( at_aiconsole, "Gibbed monster is thinking!\n" ); + return; + } + + // FIXME: I have to do this or some events get missed, and this is probably causing the problem below + flInterval = 0.1; + + // FIX: this still sometimes hits events twice + float flStart = pev->frame + (m_flLastEventCheck - pev->animtime) * m_flFrameRate * pev->framerate; + float flEnd = pev->frame + flInterval * m_flFrameRate * pev->framerate; + m_flLastEventCheck = pev->animtime + flInterval; + + m_fSequenceFinished = FALSE; + if (flEnd >= 256 || flEnd <= 0.0) + m_fSequenceFinished = TRUE; + + int index = 0; + + while ( (index = GetAnimationEvent( pmodel, pev, &event, flStart, flEnd, index ) ) != 0 ) + { + HandleAnimEvent( &event ); + } +} + + +//========================================================= +//========================================================= +float CBaseAnimating :: SetBoneController ( int iController, float flValue ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return SetController( pmodel, pev, iController, flValue ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: InitBoneControllers ( void ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + SetController( pmodel, pev, 0, 0.0 ); + SetController( pmodel, pev, 1, 0.0 ); + SetController( pmodel, pev, 2, 0.0 ); + SetController( pmodel, pev, 3, 0.0 ); +} + +//========================================================= +//========================================================= +float CBaseAnimating :: SetBlending ( int iBlender, float flValue ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::SetBlending( pmodel, pev, iBlender, flValue ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: GetBonePosition ( int iBone, Vector &origin, Vector &angles ) +{ + GET_BONE_POSITION( ENT(pev), iBone, origin, angles ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: GetAttachment ( int iAttachment, Vector &origin, Vector &angles ) +{ + GET_ATTACHMENT( ENT(pev), iAttachment, origin, angles ); +} + +//========================================================= +//========================================================= +int CBaseAnimating :: FindTransition( int iEndingSequence, int iGoalSequence, int *piDir ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + if (piDir == NULL) + { + int iDir; + int sequence = ::FindTransition( pmodel, iEndingSequence, iGoalSequence, &iDir ); + if (iDir != 1) + return -1; + else + return sequence; + } + + return ::FindTransition( pmodel, iEndingSequence, iGoalSequence, piDir ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: GetAutomovement( Vector &origin, Vector &angles, float flInterval ) +{ + +} + +void CBaseAnimating :: SetBodygroup( int iGroup, int iValue ) +{ + ::SetBodygroup( GET_MODEL_PTR( ENT(pev) ), pev, iGroup, iValue ); +} + +int CBaseAnimating :: GetBodygroup( int iGroup ) +{ + return ::GetBodygroup( GET_MODEL_PTR( ENT(pev) ), pev, iGroup ); +} + + +int CBaseAnimating :: ExtractBbox( int sequence, float *mins, float *maxs ) +{ + return ::ExtractBbox( GET_MODEL_PTR( ENT(pev) ), sequence, mins, maxs ); +} + +//========================================================= +//========================================================= + +void CBaseAnimating :: SetSequenceBox( void ) +{ + Vector mins, maxs; + + // Get sequence bbox + if ( ExtractBbox( pev->sequence, mins, maxs ) ) + { + // expand box for rotation + // find min / max for rotations + float yaw = pev->angles.y * (M_PI / 180.0); + + Vector xvector, yvector; + xvector.x = cos(yaw); + xvector.y = sin(yaw); + yvector.x = -sin(yaw); + yvector.y = cos(yaw); + Vector bounds[2]; + + bounds[0] = mins; + bounds[1] = maxs; + + Vector rmin( 9999, 9999, 9999 ); + Vector rmax( -9999, -9999, -9999 ); + Vector base, transformed; + + for (int i = 0; i <= 1; i++ ) + { + base.x = bounds[i].x; + for ( int j = 0; j <= 1; j++ ) + { + base.y = bounds[j].y; + for ( int k = 0; k <= 1; k++ ) + { + base.z = bounds[k].z; + + // transform the point + transformed.x = xvector.x*base.x + yvector.x*base.y; + transformed.y = xvector.y*base.x + yvector.y*base.y; + transformed.z = base.z; + + if (transformed.x < rmin.x) + rmin.x = transformed.x; + if (transformed.x > rmax.x) + rmax.x = transformed.x; + if (transformed.y < rmin.y) + rmin.y = transformed.y; + if (transformed.y > rmax.y) + rmax.y = transformed.y; + if (transformed.z < rmin.z) + rmin.z = transformed.z; + if (transformed.z > rmax.z) + rmax.z = transformed.z; + } + } + } + rmin.z = 0; + rmax.z = rmin.z + 1; + UTIL_SetSize( pev, rmin, rmax ); + } +} + diff --git a/hlsdk/dlls/animation.cpp b/hlsdk/dlls/animation.cpp new file mode 100644 index 00000000..0283b662 --- /dev/null +++ b/hlsdk/dlls/animation.cpp @@ -0,0 +1,536 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include +#include +#include + +#if defined _MSC_VER && _MSC_VER >= 1400 + #ifndef _CRT_SECURE_NO_DEPRECATE + #define _CRT_SECURE_NO_DEPRECATE + #endif + + #pragma warning(disable: 4996) // deprecated functions +#endif + +#include "../common/nowin.h" + +typedef int BOOL; +#define TRUE 1 +#define FALSE 0 + +// hack into header files that we can ship +typedef int qboolean; +typedef unsigned char byte; +#include "../utils/common/mathlib.h" +#include "const.h" +#include "progdefs.h" +#include "edict.h" +#include "eiface.h" + +#include "studio.h" + +#ifndef ACTIVITY_H +#include "activity.h" +#endif + +#include "activitymap.h" + +#ifndef ANIMATION_H +#include "animation.h" +#endif + +#ifndef SCRIPTEVENT_H +#include "scriptevent.h" +#endif + +#ifndef ENGINECALLBACK_H +#include "enginecallback.h" +#endif + +extern globalvars_t *gpGlobals; + +#ifdef _MSC_VER +#pragma warning( disable : 4244 ) +#endif + + +int ExtractBbox( void *pmodel, int sequence, float *mins, float *maxs ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + mins[0] = pseqdesc[ sequence ].bbmin[0]; + mins[1] = pseqdesc[ sequence ].bbmin[1]; + mins[2] = pseqdesc[ sequence ].bbmin[2]; + + maxs[0] = pseqdesc[ sequence ].bbmax[0]; + maxs[1] = pseqdesc[ sequence ].bbmax[1]; + maxs[2] = pseqdesc[ sequence ].bbmax[2]; + + return 1; +} + + +int LookupActivity( void *pmodel, entvars_t *pev, int activity ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + int weighttotal = 0; + int seq = ACTIVITY_NOT_AVAILABLE; + for (int i = 0; i < pstudiohdr->numseq; i++) + { + if (pseqdesc[i].activity == activity) + { + weighttotal += pseqdesc[i].actweight; + if (!weighttotal || RANDOM_LONG(0,weighttotal-1) < pseqdesc[i].actweight) + seq = i; + } + } + + return seq; +} + + +int LookupActivityHeaviest( void *pmodel, entvars_t *pev, int activity ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr ) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + int weight = 0; + int seq = ACTIVITY_NOT_AVAILABLE; + for (int i = 0; i < pstudiohdr->numseq; i++) + { + if (pseqdesc[i].activity == activity) + { + if ( pseqdesc[i].actweight > weight ) + { + weight = pseqdesc[i].actweight; + seq = i; + } + } + } + + return seq; +} + +void GetEyePosition ( void *pmodel, float *vecEyePosition ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + + if ( !pstudiohdr ) + { + ALERT ( at_console, "GetEyePosition() Can't get pstudiohdr ptr!\n" ); + return; + } + + VectorCopy ( pstudiohdr->eyeposition, vecEyePosition ); +} + +int LookupSequence( void *pmodel, const char *label ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + for (int i = 0; i < pstudiohdr->numseq; i++) + { + if (stricmp( pseqdesc[i].label, label ) == 0) + return i; + } + + return -1; +} + + +int IsSoundEvent( int eventNumber ) +{ + if ( eventNumber == SCRIPT_EVENT_SOUND || eventNumber == SCRIPT_EVENT_SOUND_VOICE ) + return 1; + return 0; +} + + +void SequencePrecache( void *pmodel, const char *pSequenceName ) +{ + int index = LookupSequence( pmodel, pSequenceName ); + if ( index >= 0 ) + { + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || index >= pstudiohdr->numseq ) + return; + + mstudioseqdesc_t *pseqdesc; + mstudioevent_t *pevent; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + index; + pevent = (mstudioevent_t *)((byte *)pstudiohdr + pseqdesc->eventindex); + + for (int i = 0; i < pseqdesc->numevents; i++) + { + // Don't send client-side events to the server AI + if ( pevent[i].event >= EVENT_CLIENT ) + continue; + + // UNDONE: Add a callback to check to see if a sound is precached yet and don't allocate a copy + // of it's name if it is. + if ( IsSoundEvent( pevent[i].event ) ) + { + if ( !strlen(pevent[i].options) ) + { + ALERT( at_error, "Bad sound event %d in sequence %s :: %s (sound is \"%s\")\n", pevent[i].event, pstudiohdr->name, pSequenceName, pevent[i].options ); + } + + PRECACHE_SOUND( (char *)(gpGlobals->pStringBase + ALLOC_STRING(pevent[i].options) ) ); + } + } + } +} + + + +void GetSequenceInfo( void *pmodel, entvars_t *pev, float *pflFrameRate, float *pflGroundSpeed ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return; + + mstudioseqdesc_t *pseqdesc; + + if (pev->sequence >= pstudiohdr->numseq) + { + *pflFrameRate = 0.0; + *pflGroundSpeed = 0.0; + return; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + if (pseqdesc->numframes > 1) + { + *pflFrameRate = 256 * pseqdesc->fps / (pseqdesc->numframes - 1); + *pflGroundSpeed = sqrt( pseqdesc->linearmovement[0]*pseqdesc->linearmovement[0]+ pseqdesc->linearmovement[1]*pseqdesc->linearmovement[1]+ pseqdesc->linearmovement[2]*pseqdesc->linearmovement[2] ); + *pflGroundSpeed = *pflGroundSpeed * pseqdesc->fps / (pseqdesc->numframes - 1); + } + else + { + *pflFrameRate = 256.0; + *pflGroundSpeed = 0.0; + } +} + + +int GetSequenceFlags( void *pmodel, entvars_t *pev ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || pev->sequence >= pstudiohdr->numseq ) + return 0; + + mstudioseqdesc_t *pseqdesc; + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + return pseqdesc->flags; +} + + +int GetAnimationEvent( void *pmodel, entvars_t *pev, MonsterEvent_t *pMonsterEvent, float flStart, float flEnd, int index ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || pev->sequence >= pstudiohdr->numseq || !pMonsterEvent ) + return 0; + + mstudioseqdesc_t *pseqdesc; + mstudioevent_t *pevent; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + pevent = (mstudioevent_t *)((byte *)pstudiohdr + pseqdesc->eventindex); + + if (pseqdesc->numevents == 0 || index > pseqdesc->numevents ) + return 0; + + if (pseqdesc->numframes > 1) + { + flStart *= (pseqdesc->numframes - 1) / 256.0; + flEnd *= (pseqdesc->numframes - 1) / 256.0; + } + else + { + flStart = 0; + flEnd = 1.0; + } + + for (; index < pseqdesc->numevents; index++) + { + // Don't send client-side events to the server AI + if ( pevent[index].event >= EVENT_CLIENT ) + continue; + + if ( (pevent[index].frame >= flStart && pevent[index].frame < flEnd) || + ((pseqdesc->flags & STUDIO_LOOPING) && flEnd >= pseqdesc->numframes - 1 && pevent[index].frame < flEnd - pseqdesc->numframes + 1) ) + { + pMonsterEvent->event = pevent[index].event; + pMonsterEvent->options = pevent[index].options; + return index + 1; + } + } + return 0; +} + +float SetController( void *pmodel, entvars_t *pev, int iController, float flValue ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return flValue; + + mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *)((byte *)pstudiohdr + pstudiohdr->bonecontrollerindex); + + int i = 0; + + // find first controller that matches the index + for (i = 0; i < pstudiohdr->numbonecontrollers; i++, pbonecontroller++) + { + if (pbonecontroller->index == iController) + break; + } + if (i >= pstudiohdr->numbonecontrollers) + return flValue; + + // wrap 0..360 if it's a rotational controller + + if (pbonecontroller->type & (STUDIO_XR | STUDIO_YR | STUDIO_ZR)) + { + // ugly hack, invert value if end < start + if (pbonecontroller->end < pbonecontroller->start) + flValue = -flValue; + + // does the controller not wrap? + if (pbonecontroller->start + 359.0 >= pbonecontroller->end) + { + if (flValue > ((pbonecontroller->start + pbonecontroller->end) / 2.0) + 180) + flValue = flValue - 360; + if (flValue < ((pbonecontroller->start + pbonecontroller->end) / 2.0) - 180) + flValue = flValue + 360; + } + else + { + if (flValue > 360) + flValue = flValue - (int)(flValue / 360.0) * 360.0; + else if (flValue < 0) + flValue = flValue + (int)((flValue / -360.0) + 1) * 360.0; + } + } + + int setting = static_cast(255 * (flValue - pbonecontroller->start) / (pbonecontroller->end - pbonecontroller->start)); + + if (setting < 0) setting = 0; + if (setting > 255) setting = 255; + pev->controller[iController] = setting; + + return setting * (1.0 / 255.0) * (pbonecontroller->end - pbonecontroller->start) + pbonecontroller->start; +} + + +float SetBlending( void *pmodel, entvars_t *pev, int iBlender, float flValue ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return flValue; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + if (pseqdesc->blendtype[iBlender] == 0) + return flValue; + + if (pseqdesc->blendtype[iBlender] & (STUDIO_XR | STUDIO_YR | STUDIO_ZR)) + { + // ugly hack, invert value if end < start + if (pseqdesc->blendend[iBlender] < pseqdesc->blendstart[iBlender]) + flValue = -flValue; + + // does the controller not wrap? + if (pseqdesc->blendstart[iBlender] + 359.0 >= pseqdesc->blendend[iBlender]) + { + if (flValue > ((pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender]) / 2.0) + 180) + flValue = flValue - 360; + if (flValue < ((pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender]) / 2.0) - 180) + flValue = flValue + 360; + } + } + + int setting = static_cast(255 * (flValue - pseqdesc->blendstart[iBlender]) / (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender])); + + if (setting < 0) setting = 0; + if (setting > 255) setting = 255; + + pev->blending[iBlender] = setting; + + return setting * (1.0 / 255.0) * (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender]) + pseqdesc->blendstart[iBlender]; +} + + + + +int FindTransition( void *pmodel, int iEndingAnim, int iGoalAnim, int *piDir ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return iGoalAnim; + + mstudioseqdesc_t *pseqdesc; + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + // bail if we're going to or from a node 0 + if (pseqdesc[iEndingAnim].entrynode == 0 || pseqdesc[iGoalAnim].entrynode == 0) + { + return iGoalAnim; + } + + int iEndNode; + + // ALERT( at_console, "from %d to %d: ", pEndNode->iEndNode, pGoalNode->iStartNode ); + + if (*piDir > 0) + { + iEndNode = pseqdesc[iEndingAnim].exitnode; + } + else + { + iEndNode = pseqdesc[iEndingAnim].entrynode; + } + + if (iEndNode == pseqdesc[iGoalAnim].entrynode) + { + *piDir = 1; + return iGoalAnim; + } + + byte *pTransition = ((byte *)pstudiohdr + pstudiohdr->transitionindex); + + int iInternNode = pTransition[(iEndNode-1)*pstudiohdr->numtransitions + (pseqdesc[iGoalAnim].entrynode-1)]; + + if (iInternNode == 0) + return iGoalAnim; + + int i; + + // look for someone going + for (i = 0; i < pstudiohdr->numseq; i++) + { + if (pseqdesc[i].entrynode == iEndNode && pseqdesc[i].exitnode == iInternNode) + { + *piDir = 1; + return i; + } + if (pseqdesc[i].nodeflags) + { + if (pseqdesc[i].exitnode == iEndNode && pseqdesc[i].entrynode == iInternNode) + { + *piDir = -1; + return i; + } + } + } + + ALERT( at_console, "error in transition graph" ); + return iGoalAnim; +} + +void SetBodygroup( void *pmodel, entvars_t *pev, int iGroup, int iValue ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return; + + if (iGroup > pstudiohdr->numbodyparts) + return; + + mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex) + iGroup; + + if (iValue >= pbodypart->nummodels) + return; + + int iCurrent = (pev->body / pbodypart->base) % pbodypart->nummodels; + + pev->body = (pev->body - (iCurrent * pbodypart->base) + (iValue * pbodypart->base)); +} + + +int GetBodygroup( void *pmodel, entvars_t *pev, int iGroup ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + if (iGroup > pstudiohdr->numbodyparts) + return 0; + + mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex) + iGroup; + + if (pbodypart->nummodels <= 1) + return 0; + + int iCurrent = (pev->body / pbodypart->base) % pbodypart->nummodels; + + return iCurrent; +} diff --git a/hlsdk/dlls/animation.h b/hlsdk/dlls/animation.h new file mode 100644 index 00000000..174bd712 --- /dev/null +++ b/hlsdk/dlls/animation.h @@ -0,0 +1,47 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef ANIMATION_H +#define ANIMATION_H + +#define ACTIVITY_NOT_AVAILABLE -1 + +#ifndef MONSTEREVENT_H +#include "monsterevent.h" +#endif + +extern int IsSoundEvent( int eventNumber ); + +int LookupActivity( void *pmodel, entvars_t *pev, int activity ); +int LookupActivityHeaviest( void *pmodel, entvars_t *pev, int activity ); +int LookupSequence( void *pmodel, const char *label ); +void GetSequenceInfo( void *pmodel, entvars_t *pev, float *pflFrameRate, float *pflGroundSpeed ); +int GetSequenceFlags( void *pmodel, entvars_t *pev ); +int LookupAnimationEvents( void *pmodel, entvars_t *pev, float flStart, float flEnd ); +float SetController( void *pmodel, entvars_t *pev, int iController, float flValue ); +float SetBlending( void *pmodel, entvars_t *pev, int iBlender, float flValue ); +void GetEyePosition( void *pmodel, float *vecEyePosition ); +void SequencePrecache( void *pmodel, const char *pSequenceName ); +int FindTransition( void *pmodel, int iEndingAnim, int iGoalAnim, int *piDir ); +void SetBodygroup( void *pmodel, entvars_t *pev, int iGroup, int iValue ); +int GetBodygroup( void *pmodel, entvars_t *pev, int iGroup ); + +int GetAnimationEvent( void *pmodel, entvars_t *pev, MonsterEvent_t *pMonsterEvent, float flStart, float flEnd, int index ); +int ExtractBbox( void *pmodel, int sequence, float *mins, float *maxs ); + +// From /engine/studio.h +#define STUDIO_LOOPING 0x0001 + + +#endif //ANIMATION_H diff --git a/hlsdk/dlls/basemonster.h b/hlsdk/dlls/basemonster.h new file mode 100644 index 00000000..43a2ed73 --- /dev/null +++ b/hlsdk/dlls/basemonster.h @@ -0,0 +1,94 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef BASEMONSTER_H +#define BASEMONSTER_H + +class CBaseMonster : public CBaseToggle +{ +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 + Vector m_vecEnemyLKP;// last known position of enemy. (enemy's origin) + + + void KeyValue( KeyValueData *pkvd ); + + void MakeIdealYaw( Vector vecTarget ); + 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 ); + virtual Activity GetDeathActivity ( void ); + Activity GetSmallFlinchActivity( void ); + virtual void BecomeDead( 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 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 ); + + 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; } + + 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); } + +}; + + +#endif diff --git a/hlsdk/dlls/bmodels.cpp b/hlsdk/dlls/bmodels.cpp new file mode 100644 index 00000000..39c60a70 --- /dev/null +++ b/hlsdk/dlls/bmodels.cpp @@ -0,0 +1,958 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== bmodels.cpp ======================================================== + + spawn, think, and use functions for entities that use brush models + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "doors.h" + +extern DLL_GLOBAL Vector g_vecAttackDir; + +#define SF_BRUSH_ACCDCC 16// brush should accelerate and decelerate when toggled +#define SF_BRUSH_HURT 32// rotating brush that inflicts pain based on rotation speed +#define SF_ROTATING_NOT_SOLID 64 // some special rotating objects are not solid. + +// covering cheesy noise1, noise2, & noise3 fields so they make more sense (for rotating fans) +#define noiseStart noise1 +#define noiseStop noise2 +#define noiseRunning noise3 + +#define SF_PENDULUM_SWING 2 // spawnflag that makes a pendulum a rope swing. +// +// BModelOrigin - calculates origin of a bmodel from absmin/size because all bmodel origins are 0 0 0 +// +Vector VecBModelOrigin( entvars_t* pevBModel ) +{ + return pevBModel->absmin + ( pevBModel->size * 0.5 ); +} + +// =================== FUNC_WALL ============================================== + +/*QUAKED func_wall (0 .5 .8) ? +This is just a solid wall if not inhibited +*/ +class CFuncWall : public CBaseEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + // Bmodels don't go across transitions + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +LINK_ENTITY_TO_CLASS( func_wall, CFuncWall ); + +void CFuncWall :: Spawn( void ) +{ + pev->angles = g_vecZero; + pev->movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + pev->solid = SOLID_BSP; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + // If it can't move/go away, it's really part of the world + pev->flags |= FL_WORLDBRUSH; +} + + +void CFuncWall :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( ShouldToggle( useType, (int)(pev->frame)) ) + pev->frame = 1 - pev->frame; +} + + +#define SF_WALL_START_OFF 0x0001 + +class CFuncWallToggle : public CFuncWall +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void TurnOff( void ); + void TurnOn( void ); + BOOL IsOn( void ); +}; + +LINK_ENTITY_TO_CLASS( func_wall_toggle, CFuncWallToggle ); + +void CFuncWallToggle :: Spawn( void ) +{ + CFuncWall::Spawn(); + if ( pev->spawnflags & SF_WALL_START_OFF ) + TurnOff(); +} + + +void CFuncWallToggle :: TurnOff( void ) +{ + pev->solid = SOLID_NOT; + pev->effects |= EF_NODRAW; + UTIL_SetOrigin( pev, pev->origin ); +} + + +void CFuncWallToggle :: TurnOn( void ) +{ + pev->solid = SOLID_BSP; + pev->effects &= ~EF_NODRAW; + UTIL_SetOrigin( pev, pev->origin ); +} + + +BOOL CFuncWallToggle :: IsOn( void ) +{ + if ( pev->solid == SOLID_NOT ) + return FALSE; + return TRUE; +} + + +void CFuncWallToggle :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int status = IsOn(); + + if ( ShouldToggle( useType, status ) ) + { + if ( status ) + TurnOff(); + else + TurnOn(); + } +} + + +#define SF_CONVEYOR_VISUAL 0x0001 +#define SF_CONVEYOR_NOTSOLID 0x0002 + +class CFuncConveyor : public CFuncWall +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void UpdateSpeed( float speed ); +}; + +LINK_ENTITY_TO_CLASS( func_conveyor, CFuncConveyor ); +void CFuncConveyor :: Spawn( void ) +{ + SetMovedir( pev ); + CFuncWall::Spawn(); + + if ( !(pev->spawnflags & SF_CONVEYOR_VISUAL) ) + SetBits( pev->flags, FL_CONVEYOR ); + + // HACKHACK - This is to allow for some special effects + if ( pev->spawnflags & SF_CONVEYOR_NOTSOLID ) + { + pev->solid = SOLID_NOT; + pev->skin = 0; // Don't want the engine thinking we've got special contents on this brush + } + + if ( pev->speed == 0 ) + pev->speed = 100; + + UpdateSpeed( pev->speed ); +} + + +// HACKHACK -- This is ugly, but encode the speed in the rendercolor to avoid adding more data to the network stream +void CFuncConveyor :: UpdateSpeed( float speed ) +{ + // Encode it as an integer with 4 fractional bits + int speedCode = (int)(fabs(speed) * 16.0); + + if ( speed < 0 ) + pev->rendercolor.x = 1; + else + pev->rendercolor.x = 0; + + pev->rendercolor.y = (speedCode >> 8); + pev->rendercolor.z = (speedCode & 0xFF); +} + + +void CFuncConveyor :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + pev->speed = -pev->speed; + UpdateSpeed( pev->speed ); +} + + + +// =================== FUNC_ILLUSIONARY ============================================== + + +/*QUAKED func_illusionary (0 .5 .8) ? +A simple entity that looks solid but lets you walk through it. +*/ +class CFuncIllusionary : public CBaseToggle +{ +public: + void Spawn( void ); + void EXPORT SloshTouch( CBaseEntity *pOther ); + void KeyValue( KeyValueData *pkvd ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +LINK_ENTITY_TO_CLASS( func_illusionary, CFuncIllusionary ); + +void CFuncIllusionary :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "skin"))//skin is used for content type + { + pev->skin = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CFuncIllusionary :: Spawn( void ) +{ + pev->angles = g_vecZero; + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT;// always solid_not + SET_MODEL( ENT(pev), STRING(pev->model) ); + + // I'd rather eat the network bandwidth of this than figure out how to save/restore + // these entities after they have been moved to the client, or respawn them ala Quake + // Perhaps we can do this in deathmatch only. + // MAKE_STATIC(ENT(pev)); +} + + +// ------------------------------------------------------------------------------- +// +// Monster only clip brush +// +// This brush will be solid for any entity who has the FL_MONSTERCLIP flag set +// in pev->flags +// +// otherwise it will be invisible and not solid. This can be used to keep +// specific monsters out of certain areas +// +// ------------------------------------------------------------------------------- +class CFuncMonsterClip : public CFuncWall +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) {} // Clear out func_wall's use function +}; + +LINK_ENTITY_TO_CLASS( func_monsterclip, CFuncMonsterClip ); + +void CFuncMonsterClip::Spawn( void ) +{ + CFuncWall::Spawn(); + if ( CVAR_GET_FLOAT("showtriggers") == 0 ) + pev->effects = EF_NODRAW; + pev->flags |= FL_MONSTERCLIP; +} + + +// =================== FUNC_ROTATING ============================================== +class CFuncRotating : public CBaseEntity +{ +public: + // basic functions + void Spawn( void ); + void Precache( void ); + void EXPORT SpinUp ( void ); + void EXPORT SpinDown ( void ); + void KeyValue( KeyValueData* pkvd); + void EXPORT HurtTouch ( CBaseEntity *pOther ); + void EXPORT RotatingUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Rotate( void ); + void RampPitchVol (int fUp ); + void Blocked( CBaseEntity *pOther ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flFanFriction; + float m_flAttenuation; + float m_flVolume; + float m_pitch; + int m_sounds; +}; + +TYPEDESCRIPTION CFuncRotating::m_SaveData[] = +{ + DEFINE_FIELD( CFuncRotating, m_flFanFriction, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_flAttenuation, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_flVolume, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_pitch, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_sounds, FIELD_INTEGER ) +}; + +IMPLEMENT_SAVERESTORE( CFuncRotating, CBaseEntity ); + + +LINK_ENTITY_TO_CLASS( func_rotating, CFuncRotating ); + +void CFuncRotating :: KeyValue( KeyValueData* pkvd) +{ + if (FStrEq(pkvd->szKeyName, "fanfriction")) + { + m_flFanFriction = atof(pkvd->szValue)/100; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "Volume")) + { + m_flVolume = atof(pkvd->szValue)/10.0; + + if (m_flVolume > 1.0) + m_flVolume = 1.0; + if (m_flVolume < 0.0) + m_flVolume = 0.0; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spawnorigin")) + { + Vector tmp; + UTIL_StringToVector( (float *)tmp, pkvd->szValue ); + if ( tmp != g_vecZero ) + pev->origin = tmp; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +/*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS +You need to have an origin brush as part of this entity. The +center of that brush will be +the point around which it is rotated. It will rotate around the Z +axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"speed" determines how fast it moves; default value is 100. +"dmg" damage to inflict when blocked (2 default) + +REVERSE will cause the it to rotate in the opposite direction. +*/ + + +void CFuncRotating :: Spawn( ) +{ + // set final pitch. Must not be PITCH_NORM, since we + // plan on pitch shifting later. + + m_pitch = PITCH_NORM - 1; + + // maintain compatibility with previous maps + if (m_flVolume == 0.0) + m_flVolume = 1.0; + + // if the designer didn't set a sound attenuation, default to one. + m_flAttenuation = ATTN_NORM; + + if ( FBitSet ( pev->spawnflags, SF_BRUSH_ROTATE_SMALLRADIUS) ) + { + m_flAttenuation = ATTN_IDLE; + } + else if ( FBitSet ( pev->spawnflags, SF_BRUSH_ROTATE_MEDIUMRADIUS) ) + { + m_flAttenuation = ATTN_STATIC; + } + else if ( FBitSet ( pev->spawnflags, SF_BRUSH_ROTATE_LARGERADIUS) ) + { + m_flAttenuation = ATTN_NORM; + } + + // prevent divide by zero if level designer forgets friction! + if ( m_flFanFriction == 0 ) + { + m_flFanFriction = 1; + } + + if ( FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_Z_AXIS) ) + pev->movedir = Vector(0,0,1); + else if ( FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_X_AXIS) ) + pev->movedir = Vector(1,0,0); + else + pev->movedir = Vector(0,1,0); // y-axis + + // check for reverse rotation + if ( FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_BACKWARDS) ) + pev->movedir = pev->movedir * -1; + + // some rotating objects like fake volumetric lights will not be solid. + if ( FBitSet(pev->spawnflags, SF_ROTATING_NOT_SOLID) ) + { + pev->solid = SOLID_NOT; + pev->skin = CONTENTS_EMPTY; + pev->movetype = MOVETYPE_PUSH; + } + else + { + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + } + + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + SetUse( &CFuncRotating::RotatingUse ); + // did level designer forget to assign speed? + if (pev->speed <= 0) + pev->speed = 0; + + // Removed this per level designers request. -- JAY + // if (pev->dmg == 0) + // pev->dmg = 2; + + // instant-use brush? + if ( FBitSet( pev->spawnflags, SF_BRUSH_ROTATE_INSTANT) ) + { + SetThink( &CBaseEntity::SUB_CallUseToggle ); + pev->nextthink = pev->ltime + 1.5; // leave a magic delay for client to start up + } + // can this brush inflict pain? + if ( FBitSet (pev->spawnflags, SF_BRUSH_HURT) ) + { + SetTouch( &CFuncRotating::HurtTouch ); + } + + Precache( ); +} + + +void CFuncRotating :: Precache( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + + // set up fan sounds + + if (!FStringNull( pev->message ) && strlen( szSoundFile ) > 0) + { + // if a path is set for a wave, use it + + PRECACHE_SOUND(szSoundFile); + + pev->noiseRunning = ALLOC_STRING(szSoundFile); + } else + { + // otherwise use preset sound + switch (m_sounds) + { + case 1: + PRECACHE_SOUND ("fans/fan1.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan1.wav"); + break; + case 2: + PRECACHE_SOUND ("fans/fan2.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan2.wav"); + break; + case 3: + PRECACHE_SOUND ("fans/fan3.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan3.wav"); + break; + case 4: + PRECACHE_SOUND ("fans/fan4.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan4.wav"); + break; + case 5: + PRECACHE_SOUND ("fans/fan5.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan5.wav"); + break; + + case 0: + default: + if (!FStringNull( pev->message ) && strlen( szSoundFile ) > 0) + { + PRECACHE_SOUND(szSoundFile); + + pev->noiseRunning = ALLOC_STRING(szSoundFile); + break; + } else + { + pev->noiseRunning = ALLOC_STRING("common/null.wav"); + break; + } + } + } + + if (pev->avelocity != g_vecZero ) + { + // if fan was spinning, and we went through transition or save/restore, + // make sure we restart the sound. 1.5 sec delay is magic number. KDB + + SetThink ( &CFuncRotating::SpinUp ); + pev->nextthink = pev->ltime + 1.5; + } +} + + + +// +// Touch - will hurt others based on how fast the brush is spinning +// +void CFuncRotating :: HurtTouch ( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + // we can't hurt this thing, so we're not concerned with it + if ( !pevOther->takedamage ) + return; + + // calculate damage based on rotation speed + pev->dmg = pev->avelocity.Length() / 10; + + pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH); + + pevOther->velocity = (pevOther->origin - VecBModelOrigin(pev) ).Normalize() * pev->dmg; +} + +// +// RampPitchVol - ramp pitch and volume up to final values, based on difference +// between how fast we're going vs how fast we plan to go +// +#define FANPITCHMIN 30 +#define FANPITCHMAX 100 + +void CFuncRotating :: RampPitchVol (int fUp) +{ + + Vector vecAVel = pev->avelocity; + vec_t vecCur; + vec_t vecFinal; + float fpct; + float fvol; + float fpitch; + int pitch; + + // get current angular velocity + + vecCur = abs(vecAVel.x != 0 ? static_cast(vecAVel.x) : static_cast(vecAVel.y != 0 ? vecAVel.y : vecAVel.z)); + + // get target angular velocity + + vecFinal = (pev->movedir.x != 0 ? pev->movedir.x : (pev->movedir.y != 0 ? pev->movedir.y : pev->movedir.z)); + vecFinal *= pev->speed; + vecFinal = abs(static_cast(vecFinal)); + + // calc volume and pitch as % of final vol and pitch + + fpct = vecCur / vecFinal; +// if (fUp) +// fvol = m_flVolume * (0.5 + fpct/2.0); // spinup volume ramps up from 50% max vol +// else + fvol = m_flVolume * fpct; // slowdown volume ramps down to 0 + + fpitch = FANPITCHMIN + (FANPITCHMAX - FANPITCHMIN) * fpct; + + pitch = (int) fpitch; + if (pitch == PITCH_NORM) + pitch = PITCH_NORM-1; + + // change the fan's vol and pitch + + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + fvol, m_flAttenuation, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch); + +} + +// +// SpinUp - accelerates a non-moving func_rotating up to it's speed +// +void CFuncRotating :: SpinUp( void ) +{ + Vector vecAVel;//rotational velocity + + pev->nextthink = pev->ltime + 0.1; + pev->avelocity = pev->avelocity + ( pev->movedir * ( pev->speed * m_flFanFriction ) ); + + vecAVel = pev->avelocity;// cache entity's rotational velocity + + // if we've met or exceeded target speed, set target speed and stop thinking + if ( abs(static_cast(vecAVel.x)) >= abs(static_cast(pev->movedir.x * pev->speed)) && + abs(static_cast(vecAVel.y)) >= abs(static_cast(pev->movedir.y * pev->speed)) && + abs(static_cast(vecAVel.z)) >= abs(static_cast(pev->movedir.z * pev->speed)) ) + { + pev->avelocity = pev->movedir * pev->speed;// set speed in case we overshot + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + m_flVolume, m_flAttenuation, SND_CHANGE_PITCH | SND_CHANGE_VOL, FANPITCHMAX); + + SetThink( &CFuncRotating::Rotate ); + Rotate(); + } + else + { + RampPitchVol(TRUE); + } +} + +// +// SpinDown - decelerates a moving func_rotating to a standstill. +// +void CFuncRotating :: SpinDown( void ) +{ + Vector vecAVel;//rotational velocity + vec_t vecdir; + + pev->nextthink = pev->ltime + 0.1; + + pev->avelocity = pev->avelocity - ( pev->movedir * ( pev->speed * m_flFanFriction ) );//spin down slower than spinup + + vecAVel = pev->avelocity;// cache entity's rotational velocity + + if (pev->movedir.x != 0) + vecdir = pev->movedir.x; + else if (pev->movedir.y != 0) + vecdir = pev->movedir.y; + else + vecdir = pev->movedir.z; + + // if we've met or exceeded target speed, set target speed and stop thinking + // (note: must check for movedir > 0 or < 0) + if (((vecdir > 0) && (vecAVel.x <= 0 && vecAVel.y <= 0 && vecAVel.z <= 0)) || + ((vecdir < 0) && (vecAVel.x >= 0 && vecAVel.y >= 0 && vecAVel.z >= 0))) + { + pev->avelocity = g_vecZero;// set speed in case we overshot + + // stop sound, we're done + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning /* Stop */), + 0, 0, SND_STOP, static_cast(m_pitch)); + + SetThink( &CFuncRotating::Rotate ); + Rotate(); + } + else + { + RampPitchVol(FALSE); + } +} + +void CFuncRotating :: Rotate( void ) +{ + pev->nextthink = pev->ltime + 10; +} + +//========================================================= +// Rotating Use - when a rotating brush is triggered +//========================================================= +void CFuncRotating :: RotatingUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // is this a brush that should accelerate and decelerate when turned on/off (fan)? + if ( FBitSet ( pev->spawnflags, SF_BRUSH_ACCDCC ) ) + { + // fan is spinning, so stop it. + if ( pev->avelocity != g_vecZero ) + { + SetThink ( &CFuncRotating::SpinDown ); + //EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, (char *)STRING(pev->noiseStop), + // m_flVolume, m_flAttenuation, 0, m_pitch); + + pev->nextthink = pev->ltime + 0.1; + } + else// fan is not moving, so start it + { + SetThink ( &CFuncRotating::SpinUp ); + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + 0.01, m_flAttenuation, 0, FANPITCHMIN); + + pev->nextthink = pev->ltime + 0.1; + } + } + else if ( !FBitSet ( pev->spawnflags, SF_BRUSH_ACCDCC ) )//this is a normal start/stop brush. + { + if ( pev->avelocity != g_vecZero ) + { + // play stopping sound here + SetThink ( &CFuncRotating::SpinDown ); + + // EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, (char *)STRING(pev->noiseStop), + // m_flVolume, m_flAttenuation, 0, m_pitch); + + pev->nextthink = pev->ltime + 0.1; + // pev->avelocity = g_vecZero; + } + else + { + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + m_flVolume, m_flAttenuation, 0, FANPITCHMAX); + pev->avelocity = pev->movedir * pev->speed; + + SetThink( &CFuncRotating::Rotate ); + Rotate(); + } + } +} + + +// +// RotatingBlocked - An entity has blocked the brush +// +void CFuncRotating :: Blocked( CBaseEntity *pOther ) + +{ + pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH); +} + + + + + + +//#endif + + +class CPendulum : public CBaseEntity +{ +public: + void Spawn ( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT Swing( void ); + void EXPORT PendulumUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Stop( void ); + void Touch( CBaseEntity *pOther ); + void EXPORT RopeTouch ( CBaseEntity *pOther );// this touch func makes the pendulum a rope + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + void Blocked( CBaseEntity *pOther ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_accel; // Acceleration + float m_distance; // + float m_time; + float m_damp; + float m_maxSpeed; + float m_dampSpeed; + vec3_t m_center; + vec3_t m_start; +}; + +LINK_ENTITY_TO_CLASS( func_pendulum, CPendulum ); + +TYPEDESCRIPTION CPendulum::m_SaveData[] = +{ + DEFINE_FIELD( CPendulum, m_accel, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_distance, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_time, FIELD_TIME ), + DEFINE_FIELD( CPendulum, m_damp, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_maxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_dampSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_center, FIELD_VECTOR ), + DEFINE_FIELD( CPendulum, m_start, FIELD_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CPendulum, CBaseEntity ); + + + +void CPendulum :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "distance")) + { + m_distance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damp")) + { + m_damp = atof(pkvd->szValue) * 0.001; + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +void CPendulum :: Spawn( void ) +{ + // set the axis of rotation + CBaseToggle :: AxisDir( pev ); + + if ( FBitSet (pev->spawnflags, SF_DOOR_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + if ( m_distance == 0 ) + return; + + if (pev->speed == 0) + pev->speed = 100; + + m_accel = (pev->speed * pev->speed) / (2 * fabs(m_distance)); // Calculate constant acceleration from speed and distance + m_maxSpeed = pev->speed; + m_start = pev->angles; + m_center = pev->angles + (m_distance * 0.5) * pev->movedir; + + if ( FBitSet( pev->spawnflags, SF_BRUSH_ROTATE_INSTANT) ) + { + SetThink( &CBaseEntity::SUB_CallUseToggle ); + pev->nextthink = gpGlobals->time + 0.1; + } + pev->speed = 0; + SetUse( &CPendulum::PendulumUse ); + + if ( FBitSet( pev->spawnflags, SF_PENDULUM_SWING ) ) + { + SetTouch ( &CPendulum::RopeTouch ); + } +} + + +void CPendulum :: PendulumUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->speed ) // Pendulum is moving, stop it and auto-return if necessary + { + if ( FBitSet( pev->spawnflags, SF_PENDULUM_AUTO_RETURN ) ) + { + float delta; + + delta = CBaseToggle :: AxisDelta( pev->spawnflags, pev->angles, m_start ); + + pev->avelocity = m_maxSpeed * pev->movedir; + pev->nextthink = pev->ltime + (delta / m_maxSpeed); + SetThink( &CPendulum::Stop ); + } + else + { + pev->speed = 0; // Dead stop + SetThink( NULL ); + pev->avelocity = g_vecZero; + } + } + else + { + pev->nextthink = pev->ltime + 0.1; // Start the pendulum moving + m_time = gpGlobals->time; // Save time to calculate dt + SetThink( &CPendulum::Swing ); + m_dampSpeed = m_maxSpeed; + } +} + + +void CPendulum :: Stop( void ) +{ + pev->angles = m_start; + pev->speed = 0; + SetThink( NULL ); + pev->avelocity = g_vecZero; +} + + +void CPendulum::Blocked( CBaseEntity *pOther ) +{ + m_time = gpGlobals->time; +} + + +void CPendulum :: Swing( void ) +{ + float delta, dt; + + delta = CBaseToggle :: AxisDelta( pev->spawnflags, pev->angles, m_center ); + dt = gpGlobals->time - m_time; // How much time has passed? + m_time = gpGlobals->time; // Remember the last time called + + if ( delta > 0 && m_accel > 0 ) + pev->speed -= m_accel * dt; // Integrate velocity + else + pev->speed += m_accel * dt; + + if ( pev->speed > m_maxSpeed ) + pev->speed = m_maxSpeed; + else if ( pev->speed < -m_maxSpeed ) + pev->speed = -m_maxSpeed; + // scale the destdelta vector by the time spent traveling to get velocity + pev->avelocity = pev->speed * pev->movedir; + + // Call this again + pev->nextthink = pev->ltime + 0.1; + + if ( m_damp ) + { + m_dampSpeed -= m_damp * m_dampSpeed * dt; + if ( m_dampSpeed < 30.0 ) + { + pev->angles = m_center; + pev->speed = 0; + SetThink( NULL ); + pev->avelocity = g_vecZero; + } + else if ( pev->speed > m_dampSpeed ) + pev->speed = m_dampSpeed; + else if ( pev->speed < -m_dampSpeed ) + pev->speed = -m_dampSpeed; + + } +} + + +void CPendulum :: Touch ( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + if ( pev->dmg <= 0 ) + return; + + // we can't hurt this thing, so we're not concerned with it + if ( !pevOther->takedamage ) + return; + + // calculate damage based on rotation speed + float damage = pev->dmg * pev->speed * 0.01; + + if ( damage < 0 ) + damage = -damage; + + pOther->TakeDamage( pev, pev, damage, DMG_CRUSH ); + + pevOther->velocity = (pevOther->origin - VecBModelOrigin(pev) ).Normalize() * damage; +} + +void CPendulum :: RopeTouch ( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + if ( !pOther->IsPlayer() ) + {// not a player! + ALERT ( at_console, "Not a client\n" ); + return; + } + + if ( ENT(pevOther) == pev->enemy ) + {// this player already on the rope. + return; + } + + pev->enemy = pOther->edict(); + pevOther->velocity = g_vecZero; + pevOther->movetype = MOVETYPE_NONE; +} + + diff --git a/hlsdk/dlls/buttons.cpp b/hlsdk/dlls/buttons.cpp new file mode 100644 index 00000000..2ffe311a --- /dev/null +++ b/hlsdk/dlls/buttons.cpp @@ -0,0 +1,1276 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== buttons.cpp ======================================================== + + button-related code + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "doors.h" + + +#define SF_BUTTON_DONTMOVE 1 +#define SF_ROTBUTTON_NOTSOLID 1 +#define SF_BUTTON_TOGGLE 32 // button stays pushed until reactivated +#define SF_BUTTON_SPARK_IF_OFF 64 // button sparks in OFF state +#define SF_BUTTON_TOUCH_ONLY 256 // button only fires as a result of USE key. + +#define SF_GLOBAL_SET 1 // Set global state to initial state on spawn + +class CEnvGlobal : public CPointEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + string_t m_globalstate; + int m_triggermode; + int m_initialstate; +}; + +TYPEDESCRIPTION CEnvGlobal::m_SaveData[] = +{ + DEFINE_FIELD( CEnvGlobal, m_globalstate, FIELD_STRING ), + DEFINE_FIELD( CEnvGlobal, m_triggermode, FIELD_INTEGER ), + DEFINE_FIELD( CEnvGlobal, m_initialstate, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CEnvGlobal, CBaseEntity ); + +LINK_ENTITY_TO_CLASS( env_global, CEnvGlobal ); + +void CEnvGlobal::KeyValue( KeyValueData *pkvd ) +{ + pkvd->fHandled = TRUE; + + if ( FStrEq(pkvd->szKeyName, "globalstate") ) // State name + m_globalstate = ALLOC_STRING( pkvd->szValue ); + else if ( FStrEq(pkvd->szKeyName, "triggermode") ) + m_triggermode = atoi( pkvd->szValue ); + else if ( FStrEq(pkvd->szKeyName, "initialstate") ) + m_initialstate = atoi( pkvd->szValue ); + else + CPointEntity::KeyValue( pkvd ); +} + +void CEnvGlobal::Spawn( void ) +{ + if ( !m_globalstate ) + { + REMOVE_ENTITY( ENT(pev) ); + return; + } + if ( FBitSet( pev->spawnflags, SF_GLOBAL_SET ) ) + { + if ( !gGlobalState.EntityInTable( m_globalstate ) ) + gGlobalState.EntityAdd( m_globalstate, gpGlobals->mapname, (GLOBALESTATE)m_initialstate ); + } +} + + +void CEnvGlobal::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + GLOBALESTATE oldState = gGlobalState.EntityGetState( m_globalstate ); + GLOBALESTATE newState; + + switch( m_triggermode ) + { + case 0: + newState = GLOBAL_OFF; + break; + + case 1: + newState = GLOBAL_ON; + break; + + case 2: + newState = GLOBAL_DEAD; + break; + + default: + case 3: + if ( oldState == GLOBAL_ON ) + newState = GLOBAL_OFF; + else if ( oldState == GLOBAL_OFF ) + newState = GLOBAL_ON; + else + newState = oldState; + } + + if ( gGlobalState.EntityInTable( m_globalstate ) ) + gGlobalState.EntitySetState( m_globalstate, newState ); + else + gGlobalState.EntityAdd( m_globalstate, gpGlobals->mapname, newState ); +} + + + +TYPEDESCRIPTION CMultiSource::m_SaveData[] = +{ + //!!!BUGBUG FIX + DEFINE_ARRAY( CMultiSource, m_rgEntities, FIELD_EHANDLE, MS_MAX_TARGETS ), + DEFINE_ARRAY( CMultiSource, m_rgTriggered, FIELD_INTEGER, MS_MAX_TARGETS ), + DEFINE_FIELD( CMultiSource, m_iTotal, FIELD_INTEGER ), + DEFINE_FIELD( CMultiSource, m_globalstate, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CMultiSource, CBaseEntity ); + +LINK_ENTITY_TO_CLASS( multisource, CMultiSource ); +// +// Cache user-entity-field values until spawn is called. +// + +void CMultiSource::KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "style") || + FStrEq(pkvd->szKeyName, "height") || + FStrEq(pkvd->szKeyName, "killtarget") || + FStrEq(pkvd->szKeyName, "value1") || + FStrEq(pkvd->szKeyName, "value2") || + FStrEq(pkvd->szKeyName, "value3")) + pkvd->fHandled = TRUE; + else if ( FStrEq(pkvd->szKeyName, "globalstate") ) + { + m_globalstate = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +#define SF_MULTI_INIT 1 + +void CMultiSource::Spawn() +{ + // set up think for later registration + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->nextthink = gpGlobals->time + 0.1; + pev->spawnflags |= SF_MULTI_INIT; // Until it's initialized + SetThink(&CMultiSource::Register); +} + +void CMultiSource::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int i = 0; + + // Find the entity in our list + while (i < m_iTotal) + if ( m_rgEntities[i++] == pCaller ) + break; + + // if we didn't find it, report error and leave + if (i > m_iTotal) + { + ALERT(at_console, "MultiSrc:Used by non member %s.\n", STRING(pCaller->pev->classname)); + return; + } + + // CONSIDER: a Use input to the multisource always toggles. Could check useType for ON/OFF/TOGGLE + + m_rgTriggered[i-1] ^= 1; + + // + if ( IsTriggered( pActivator ) ) + { + ALERT( at_aiconsole, "Multisource %s enabled (%d inputs)\n", STRING(pev->targetname), m_iTotal ); + USE_TYPE useType = USE_TOGGLE; + if ( m_globalstate ) + useType = USE_ON; + SUB_UseTargets( NULL, useType, 0 ); + } +} + + +BOOL CMultiSource::IsTriggered( CBaseEntity * ) +{ + // Is everything triggered? + int i = 0; + + // Still initializing? + if ( pev->spawnflags & SF_MULTI_INIT ) + return 0; + + while (i < m_iTotal) + { + if (m_rgTriggered[i] == 0) + break; + i++; + } + + if (i == m_iTotal) + { + if ( !m_globalstate || gGlobalState.EntityGetState( m_globalstate ) == GLOBAL_ON ) + return 1; + } + + return 0; +} + +void CMultiSource::Register(void) +{ + edict_t *pentTarget = NULL; + + m_iTotal = 0; + memset( m_rgEntities, 0, MS_MAX_TARGETS * sizeof(EHANDLE) ); + + SetThink(&CBaseEntity::SUB_DoNothing); + + // search for all entities which target this multisource (pev->targetname) + + pentTarget = FIND_ENTITY_BY_STRING(NULL, "target", STRING(pev->targetname)); + + while (!FNullEnt(pentTarget) && (m_iTotal < MS_MAX_TARGETS)) + { + CBaseEntity *pTarget = CBaseEntity::Instance(pentTarget); + if ( pTarget ) + m_rgEntities[m_iTotal++] = pTarget; + + pentTarget = FIND_ENTITY_BY_STRING( pentTarget, "target", STRING(pev->targetname)); + } + + pentTarget = FIND_ENTITY_BY_STRING(NULL, "classname", "multi_manager"); + while (!FNullEnt(pentTarget) && (m_iTotal < MS_MAX_TARGETS)) + { + CBaseEntity *pTarget = CBaseEntity::Instance(pentTarget); + if ( pTarget && pTarget->HasTarget(pev->targetname) ) + m_rgEntities[m_iTotal++] = pTarget; + + pentTarget = FIND_ENTITY_BY_STRING( pentTarget, "classname", "multi_manager" ); + } + + pev->spawnflags &= ~SF_MULTI_INIT; +} + +// CBaseButton +TYPEDESCRIPTION CBaseButton::m_SaveData[] = +{ + DEFINE_FIELD( CBaseButton, m_fStayPushed, FIELD_BOOLEAN ), + DEFINE_FIELD( CBaseButton, m_fRotating, FIELD_BOOLEAN ), + + DEFINE_FIELD( CBaseButton, m_sounds, FIELD_INTEGER ), + DEFINE_FIELD( CBaseButton, m_bLockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_bLockedSentence, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_bUnlockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_bUnlockedSentence, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_strChangeTarget, FIELD_STRING ), +// DEFINE_FIELD( CBaseButton, m_ls, FIELD_??? ), // This is restored in Precache() +}; + + +IMPLEMENT_SAVERESTORE( CBaseButton, CBaseToggle ); + +void CBaseButton::Precache( void ) +{ + char *pszSound; + + if ( FBitSet ( pev->spawnflags, SF_BUTTON_SPARK_IF_OFF ) )// this button should spark in OFF state + { + PRECACHE_SOUND ("buttons/spark1.wav"); + PRECACHE_SOUND ("buttons/spark2.wav"); + PRECACHE_SOUND ("buttons/spark3.wav"); + PRECACHE_SOUND ("buttons/spark4.wav"); + PRECACHE_SOUND ("buttons/spark5.wav"); + PRECACHE_SOUND ("buttons/spark6.wav"); + } + + // get door button sounds, for doors which require buttons to open + + if (m_bLockedSound) + { + pszSound = ButtonSound( (int)m_bLockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sLockedSound = ALLOC_STRING(pszSound); + } + + if (m_bUnlockedSound) + { + pszSound = ButtonSound( (int)m_bUnlockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sUnlockedSound = ALLOC_STRING(pszSound); + } + + // get sentence group names, for doors which are directly 'touched' to open + + switch (m_bLockedSentence) + { + case 1: m_ls.sLockedSentence = MAKE_STRING("NA"); break; // access denied + case 2: m_ls.sLockedSentence = MAKE_STRING("ND"); break; // security lockout + case 3: m_ls.sLockedSentence = MAKE_STRING("NF"); break; // blast door + case 4: m_ls.sLockedSentence = MAKE_STRING("NFIRE"); break; // fire door + case 5: m_ls.sLockedSentence = MAKE_STRING("NCHEM"); break; // chemical door + case 6: m_ls.sLockedSentence = MAKE_STRING("NRAD"); break; // radiation door + case 7: m_ls.sLockedSentence = MAKE_STRING("NCON"); break; // gen containment + case 8: m_ls.sLockedSentence = MAKE_STRING("NH"); break; // maintenance door + case 9: m_ls.sLockedSentence = MAKE_STRING("NG"); break; // broken door + + default: m_ls.sLockedSentence = 0; break; + } + + switch (m_bUnlockedSentence) + { + case 1: m_ls.sUnlockedSentence = MAKE_STRING("EA"); break; // access granted + case 2: m_ls.sUnlockedSentence = MAKE_STRING("ED"); break; // security door + case 3: m_ls.sUnlockedSentence = MAKE_STRING("EF"); break; // blast door + case 4: m_ls.sUnlockedSentence = MAKE_STRING("EFIRE"); break; // fire door + case 5: m_ls.sUnlockedSentence = MAKE_STRING("ECHEM"); break; // chemical door + case 6: m_ls.sUnlockedSentence = MAKE_STRING("ERAD"); break; // radiation door + case 7: m_ls.sUnlockedSentence = MAKE_STRING("ECON"); break; // gen containment + case 8: m_ls.sUnlockedSentence = MAKE_STRING("EH"); break; // maintenance door + + default: m_ls.sUnlockedSentence = 0; break; + } +} + +// +// Cache user-entity-field values until spawn is called. +// + +void CBaseButton::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "changetarget")) + { + m_strChangeTarget = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sound")) + { + m_bLockedSound = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sentence")) + { + m_bLockedSentence = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sound")) + { + m_bUnlockedSound = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sentence")) + { + m_bUnlockedSentence = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +// +// ButtonShot +// +int CBaseButton::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + BUTTON_CODE code = ButtonResponseToTouch(); + + if ( code == BUTTON_NOTHING ) + return 0; + // Temporarily disable the touch function, until movement is finished. + SetTouch( NULL ); + + m_hActivator = CBaseEntity::Instance( pevAttacker ); + if ( m_hActivator == 0 ) + return 0; + + if ( code == BUTTON_RETURN ) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + // Toggle buttons fire when they get back to their "home" position + if ( !(pev->spawnflags & SF_BUTTON_TOGGLE) ) + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + ButtonReturn(); + } + else // code == BUTTON_ACTIVATE + ButtonActivate( ); + + return 0; +} + +/*QUAKED func_button (0 .5 .8) ? +When a button is touched, it moves some distance in the direction of it's angle, +triggers all of it's targets, waits some time, then returns to it's original position +where it can be triggered again. + +"angle" determines the opening direction +"target" all entities with a matching targetname will be used +"speed" override the default 40 speed +"wait" override the default 1 second wait (-1 = never return) +"lip" override the default 4 pixel lip remaining at end of move +"health" if set, the button must be killed instead of touched +"sounds" +0) steam metal +1) wooden clunk +2) metallic click +3) in-out +*/ +LINK_ENTITY_TO_CLASS( func_button, CBaseButton ); + + +void CBaseButton::Spawn( ) +{ + char *pszSound; + + //---------------------------------------------------- + //determine sounds for buttons + //a sound of 0 should not make a sound + //---------------------------------------------------- + pszSound = ButtonSound( m_sounds ); + PRECACHE_SOUND(pszSound); + pev->noise = ALLOC_STRING(pszSound); + + Precache(); + + if ( FBitSet ( pev->spawnflags, SF_BUTTON_SPARK_IF_OFF ) )// this button should spark in OFF state + { + SetThink ( &CBaseButton::ButtonSpark ); + pev->nextthink = gpGlobals->time + 0.5;// no hurry, make sure everything else spawns + } + + SetMovedir(pev); + + pev->movetype = MOVETYPE_PUSH; + pev->solid = SOLID_BSP; + SET_MODEL(ENT(pev), STRING(pev->model)); + + if (pev->speed == 0) + pev->speed = 40; + + if (pev->health > 0) + { + pev->takedamage = DAMAGE_YES; + } + + if (m_flWait == 0) + m_flWait = 1; + if (m_flLip == 0) + m_flLip = 4; + + m_toggle_state = TS_AT_BOTTOM; + m_vecPosition1 = pev->origin; + // Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big + m_vecPosition2 = m_vecPosition1 + (pev->movedir * (fabs( pev->movedir.x * (pev->size.x-2) ) + fabs( pev->movedir.y * (pev->size.y-2) ) + fabs( pev->movedir.z * (pev->size.z-2) ) - m_flLip)); + + + // Is this a non-moving button? + if ( ((m_vecPosition2 - m_vecPosition1).Length() < 1) || (pev->spawnflags & SF_BUTTON_DONTMOVE) ) + m_vecPosition2 = m_vecPosition1; + + m_fStayPushed = (m_flWait == -1 ? TRUE : FALSE); + m_fRotating = FALSE; + + // if the button is flagged for USE button activation only, take away it's touch function and add a use function + + if ( FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) // touchable button + { + SetTouch( &CBaseButton::ButtonTouch ); + } + else + { + SetTouch ( NULL ); + SetUse ( &CBaseButton::ButtonUse ); + } +} + + +// Button sound table. +// Also used by CBaseDoor to get 'touched' door lock/unlock sounds + +char *ButtonSound( int sound ) +{ + char *pszSound; + + switch ( sound ) + { + case 0: pszSound = "common/null.wav"; break; + case 1: pszSound = "buttons/button1.wav"; break; + case 2: pszSound = "buttons/button2.wav"; break; + case 3: pszSound = "buttons/button3.wav"; break; + case 4: pszSound = "buttons/button4.wav"; break; + case 5: pszSound = "buttons/button5.wav"; break; + case 6: pszSound = "buttons/button6.wav"; break; + case 7: pszSound = "buttons/button7.wav"; break; + case 8: pszSound = "buttons/button8.wav"; break; + case 9: pszSound = "buttons/button9.wav"; break; + case 10: pszSound = "buttons/button10.wav"; break; + case 11: pszSound = "buttons/button11.wav"; break; + case 12: pszSound = "buttons/latchlocked1.wav"; break; + case 13: pszSound = "buttons/latchunlocked1.wav"; break; + case 14: pszSound = "buttons/lightswitch2.wav";break; + +// next 6 slots reserved for any additional sliding button sounds we may add + + case 21: pszSound = "buttons/lever1.wav"; break; + case 22: pszSound = "buttons/lever2.wav"; break; + case 23: pszSound = "buttons/lever3.wav"; break; + case 24: pszSound = "buttons/lever4.wav"; break; + case 25: pszSound = "buttons/lever5.wav"; break; + + default:pszSound = "buttons/button9.wav"; break; + } + + return pszSound; +} + +// +// Makes flagged buttons spark when turned off +// + +void DoSpark(entvars_t *pev, const Vector &location ) +{ + Vector tmp = location + pev->size * 0.5; + UTIL_Sparks( tmp ); + + float flVolume = RANDOM_FLOAT ( 0.25 , 0.75 ) * 0.4;//random volume range + switch ( (int)(RANDOM_FLOAT(0,1) * 6) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark1.wav", flVolume, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark2.wav", flVolume, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark3.wav", flVolume, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark4.wav", flVolume, ATTN_NORM); break; + case 4: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark5.wav", flVolume, ATTN_NORM); break; + case 5: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark6.wav", flVolume, ATTN_NORM); break; + } +} + +void CBaseButton::ButtonSpark ( void ) +{ + SetThink ( &CBaseButton::ButtonSpark ); + pev->nextthink = gpGlobals->time + ( 0.1 + RANDOM_FLOAT ( 0, 1.5 ) );// spark again at random interval + + DoSpark( pev, pev->mins ); +} + + +// +// Button's Use function +// +void CBaseButton::ButtonUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Ignore touches if button is moving, or pushed-in and waiting to auto-come-out. + // UNDONE: Should this use ButtonResponseToTouch() too? + if (m_toggle_state == TS_GOING_UP || m_toggle_state == TS_GOING_DOWN ) + return; + + m_hActivator = pActivator; + if ( m_toggle_state == TS_AT_TOP) + { + if (!m_fStayPushed && FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE)) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + //SUB_UseTargets( m_eoActivator ); + ButtonReturn(); + } + } + else + ButtonActivate( ); +} + + +CBaseButton::BUTTON_CODE CBaseButton::ButtonResponseToTouch( void ) +{ + // Ignore touches if button is moving, or pushed-in and waiting to auto-come-out. + if (m_toggle_state == TS_GOING_UP || + m_toggle_state == TS_GOING_DOWN || + (m_toggle_state == TS_AT_TOP && !m_fStayPushed && !FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE) ) ) + return BUTTON_NOTHING; + + if (m_toggle_state == TS_AT_TOP) + { + if((FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE) ) && !m_fStayPushed) + { + return BUTTON_RETURN; + } + } + else + return BUTTON_ACTIVATE; + + return BUTTON_NOTHING; +} + + +// +// Touching a button simply "activates" it. +// +void CBaseButton:: ButtonTouch( CBaseEntity *pOther ) +{ + // Ignore touches by anything but players + if (!FClassnameIs(pOther->pev, "player")) + return; + + m_hActivator = pOther; + + BUTTON_CODE code = ButtonResponseToTouch(); + + if ( code == BUTTON_NOTHING ) + return; + + if (!UTIL_IsMasterTriggered(m_sMaster, pOther)) + { + // play button locked sound + PlayLockSounds(pev, &m_ls, TRUE, TRUE); + return; + } + + // Temporarily disable the touch function, until movement is finished. + SetTouch( NULL ); + + if ( code == BUTTON_RETURN ) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + ButtonReturn(); + } + else // code == BUTTON_ACTIVATE + ButtonActivate( ); +} + +// +// Starts the button moving "in/up". +// +void CBaseButton::ButtonActivate( ) +{ + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + { + // button is locked, play locked sound + PlayLockSounds(pev, &m_ls, TRUE, TRUE); + return; + } + else + { + // button is unlocked, play unlocked sound + PlayLockSounds(pev, &m_ls, FALSE, TRUE); + } + + ASSERT(m_toggle_state == TS_AT_BOTTOM); + m_toggle_state = TS_GOING_UP; + + SetMoveDone( &CBaseButton::TriggerAndWait ); + if (!m_fRotating) + LinearMove( m_vecPosition2, pev->speed); + else + AngularMove( m_vecAngle2, pev->speed); +} + +// +// Button has reached the "in/up" position. Activate its "targets", and pause before "popping out". +// +void CBaseButton::TriggerAndWait( void ) +{ + ASSERT(m_toggle_state == TS_GOING_UP); + + if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + return; + + m_toggle_state = TS_AT_TOP; + + // If button automatically comes back out, start it moving out. + // Else re-instate touch method + if (m_fStayPushed || FBitSet ( pev->spawnflags, SF_BUTTON_TOGGLE ) ) + { + if ( !FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) // this button only works if USED, not touched! + { + // ALL buttons are now use only + SetTouch ( NULL ); + } + else + SetTouch( &CBaseButton::ButtonTouch ); + } + else + { + pev->nextthink = pev->ltime + m_flWait; + SetThink( &CBaseButton::ButtonReturn ); + } + + pev->frame = 1; // use alternate textures + + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); +} + + +// +// Starts the button moving "out/down". +// +void CBaseButton::ButtonReturn( void ) +{ + ASSERT(m_toggle_state == TS_AT_TOP); + m_toggle_state = TS_GOING_DOWN; + + SetMoveDone( &CBaseButton::ButtonBackHome ); + if (!m_fRotating) + LinearMove( m_vecPosition1, pev->speed); + else + AngularMove( m_vecAngle1, pev->speed); + + pev->frame = 0; // use normal textures +} + + +// +// Button has returned to start state. Quiesce it. +// +void CBaseButton::ButtonBackHome( void ) +{ + ASSERT(m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_AT_BOTTOM; + + if ( FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE) ) + { + //EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + } + + + if (!FStringNull(pev->target)) + { + edict_t* pentTarget = NULL; + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->target)); + + if (FNullEnt(pentTarget)) + break; + + if (!FClassnameIs(pentTarget, "multisource")) + continue; + CBaseEntity *pTarget = CBaseEntity::Instance( pentTarget ); + + if ( pTarget ) + pTarget->Use( m_hActivator, this, USE_TOGGLE, 0 ); + } + } + +// Re-instate touch method, movement cycle is complete. + if ( !FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) // this button only works if USED, not touched! + { + // All buttons are now use only + SetTouch ( NULL ); + } + else + SetTouch( &CBaseButton::ButtonTouch ); + +// reset think for a sparking button + if ( FBitSet ( pev->spawnflags, SF_BUTTON_SPARK_IF_OFF ) ) + { + SetThink ( &CBaseButton::ButtonSpark ); + pev->nextthink = gpGlobals->time + 0.5;// no hurry. + } +} + + + +// +// Rotating button (aka "lever") +// +class CRotButton : public CBaseButton +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( func_rot_button, CRotButton ); + +void CRotButton::Spawn( void ) +{ + char *pszSound; + //---------------------------------------------------- + //determine sounds for buttons + //a sound of 0 should not make a sound + //---------------------------------------------------- + pszSound = ButtonSound( m_sounds ); + PRECACHE_SOUND(pszSound); + pev->noise = ALLOC_STRING(pszSound); + + // set the axis of rotation + CBaseToggle::AxisDir( pev ); + + // check for clockwise rotation + if ( FBitSet (pev->spawnflags, SF_DOOR_ROTATE_BACKWARDS) ) + pev->movedir = pev->movedir * -1; + + pev->movetype = MOVETYPE_PUSH; + + if ( pev->spawnflags & SF_ROTBUTTON_NOTSOLID ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + + SET_MODEL(ENT(pev), STRING(pev->model)); + + if (pev->speed == 0) + pev->speed = 40; + + if (m_flWait == 0) + m_flWait = 1; + + if (pev->health > 0) + { + pev->takedamage = DAMAGE_YES; + } + + m_toggle_state = TS_AT_BOTTOM; + m_vecAngle1 = pev->angles; + m_vecAngle2 = pev->angles + pev->movedir * m_flMoveDistance; + ASSERTSZ(m_vecAngle1 != m_vecAngle2, "rotating button start/end positions are equal"); + + m_fStayPushed = (m_flWait == -1 ? TRUE : FALSE); + m_fRotating = TRUE; + + // if the button is flagged for USE button activation only, take away it's touch function and add a use function + if ( !FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) + { + SetTouch ( NULL ); + SetUse ( &CBaseButton::ButtonUse ); + } + else // touchable button + SetTouch( &CBaseButton::ButtonTouch ); + + //SetTouch( ButtonTouch ); +} + + +// Make this button behave like a door (HACKHACK) +// This will disable use and make the button solid +// rotating buttons were made SOLID_NOT by default since their were some +// collision problems with them... +#define SF_MOMENTARY_DOOR 0x0001 + +class CMomentaryRotButton : public CBaseToggle +{ +public: + void Spawn ( void ); + void KeyValue( KeyValueData *pkvd ); + virtual int ObjectCaps( void ) + { + int flags = CBaseToggle :: ObjectCaps() & (~FCAP_ACROSS_TRANSITION); + if ( pev->spawnflags & SF_MOMENTARY_DOOR ) + return flags; + return flags | FCAP_CONTINUOUS_USE; + } + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Off( void ); + void EXPORT Return( void ); + void UpdateSelf( float value ); + void UpdateSelfReturn( float value ); + void UpdateAllButtons( float value, int start ); + + void PlaySound( void ); + void UpdateTarget( float value ); + + static CMomentaryRotButton *Instance( edict_t *pent ) { return (CMomentaryRotButton *)GET_PRIVATE(pent);}; + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_lastUsed; + int m_direction; + float m_returnSpeed; + vec3_t m_start; + vec3_t m_end; + int m_sounds; +}; +TYPEDESCRIPTION CMomentaryRotButton::m_SaveData[] = +{ + DEFINE_FIELD( CMomentaryRotButton, m_lastUsed, FIELD_INTEGER ), + DEFINE_FIELD( CMomentaryRotButton, m_direction, FIELD_INTEGER ), + DEFINE_FIELD( CMomentaryRotButton, m_returnSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CMomentaryRotButton, m_start, FIELD_VECTOR ), + DEFINE_FIELD( CMomentaryRotButton, m_end, FIELD_VECTOR ), + DEFINE_FIELD( CMomentaryRotButton, m_sounds, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CMomentaryRotButton, CBaseToggle ); + +LINK_ENTITY_TO_CLASS( momentary_rot_button, CMomentaryRotButton ); + +void CMomentaryRotButton::Spawn( void ) +{ + CBaseToggle::AxisDir( pev ); + + if ( pev->speed == 0 ) + pev->speed = 100; + + if ( m_flMoveDistance < 0 ) + { + m_start = pev->angles + pev->movedir * m_flMoveDistance; + m_end = pev->angles; + m_direction = 1; // This will toggle to -1 on the first use() + m_flMoveDistance = -m_flMoveDistance; + } + else + { + m_start = pev->angles; + m_end = pev->angles + pev->movedir * m_flMoveDistance; + m_direction = -1; // This will toggle to +1 on the first use() + } + + if ( pev->spawnflags & SF_MOMENTARY_DOOR ) + pev->solid = SOLID_BSP; + else + pev->solid = SOLID_NOT; + + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + char *pszSound = ButtonSound( m_sounds ); + PRECACHE_SOUND(pszSound); + pev->noise = ALLOC_STRING(pszSound); + m_lastUsed = 0; +} + +void CMomentaryRotButton::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "returnspeed")) + { + m_returnSpeed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CMomentaryRotButton::PlaySound( void ) +{ + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); +} + +// BUGBUG: This design causes a latentcy. When the button is retriggered, the first impulse +// will send the target in the wrong direction because the parameter is calculated based on the +// current, not future position. +void CMomentaryRotButton::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + pev->ideal_yaw = CBaseToggle::AxisDelta( pev->spawnflags, pev->angles, m_start ) / m_flMoveDistance; + + UpdateAllButtons( pev->ideal_yaw, 1 ); + UpdateTarget( pev->ideal_yaw ); +} + +void CMomentaryRotButton::UpdateAllButtons( float value, int start ) +{ + // Update all rot buttons attached to the same target + edict_t *pentTarget = NULL; + for (;;) + { + + pentTarget = FIND_ENTITY_BY_STRING(pentTarget, "target", STRING(pev->target)); + if (FNullEnt(pentTarget)) + break; + + if ( FClassnameIs( VARS(pentTarget), "momentary_rot_button" ) ) + { + CMomentaryRotButton *pEntity = CMomentaryRotButton::Instance(pentTarget); + if ( pEntity ) + { + if ( start ) + pEntity->UpdateSelf( value ); + else + pEntity->UpdateSelfReturn( value ); + } + } + } +} + +void CMomentaryRotButton::UpdateSelf( float value ) +{ + BOOL fplaysound = FALSE; + + if ( !m_lastUsed ) + { + fplaysound = TRUE; + m_direction = -m_direction; + } + m_lastUsed = 1; + + pev->nextthink = pev->ltime + 0.1; + if ( m_direction > 0 && value >= 1.0 ) + { + pev->avelocity = g_vecZero; + pev->angles = m_end; + return; + } + else if ( m_direction < 0 && value <= 0 ) + { + pev->avelocity = g_vecZero; + pev->angles = m_start; + return; + } + + if (fplaysound) + PlaySound(); + + // HACKHACK -- If we're going slow, we'll get multiple player packets per frame, bump nexthink on each one to avoid stalling + if ( pev->nextthink < pev->ltime ) + pev->nextthink = pev->ltime + 0.1; + else + pev->nextthink += 0.1; + + pev->avelocity = (m_direction * pev->speed) * pev->movedir; + SetThink( &CMomentaryRotButton::Off ); +} + +void CMomentaryRotButton::UpdateTarget( float value ) +{ + if (!FStringNull(pev->target)) + { + edict_t* pentTarget = NULL; + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->target)); + if (FNullEnt(pentTarget)) + break; + CBaseEntity *pEntity = CBaseEntity::Instance(pentTarget); + if ( pEntity ) + { + pEntity->Use( this, this, USE_SET, value ); + } + } + } +} + +void CMomentaryRotButton::Off( void ) +{ + pev->avelocity = g_vecZero; + m_lastUsed = 0; + if ( FBitSet( pev->spawnflags, SF_PENDULUM_AUTO_RETURN ) && m_returnSpeed > 0 ) + { + SetThink( &CMomentaryRotButton::Return ); + pev->nextthink = pev->ltime + 0.1; + m_direction = -1; + } + else + SetThink( NULL ); +} + +void CMomentaryRotButton::Return( void ) +{ + float value = CBaseToggle::AxisDelta( pev->spawnflags, pev->angles, m_start ) / m_flMoveDistance; + + UpdateAllButtons( value, 0 ); // This will end up calling UpdateSelfReturn() n times, but it still works right + if ( value > 0 ) + UpdateTarget( value ); +} + + +void CMomentaryRotButton::UpdateSelfReturn( float value ) +{ + if ( value <= 0 ) + { + pev->avelocity = g_vecZero; + pev->angles = m_start; + pev->nextthink = -1; + SetThink( NULL ); + } + else + { + pev->avelocity = -m_returnSpeed * pev->movedir; + pev->nextthink = pev->ltime + 0.1; + } +} + + +//---------------------------------------------------------------- +// Spark +//---------------------------------------------------------------- + +class CEnvSpark : public CBaseEntity +{ +public: + void Spawn(void); + void Precache(void); + void EXPORT SparkThink(void); + void EXPORT SparkStart(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT SparkStop(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue(KeyValueData *pkvd); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flDelay; +}; + + +TYPEDESCRIPTION CEnvSpark::m_SaveData[] = +{ + DEFINE_FIELD( CEnvSpark, m_flDelay, FIELD_FLOAT), +}; + +IMPLEMENT_SAVERESTORE( CEnvSpark, CBaseEntity ); + +LINK_ENTITY_TO_CLASS(env_spark, CEnvSpark); +LINK_ENTITY_TO_CLASS(env_debris, CEnvSpark); + +void CEnvSpark::Spawn(void) +{ + SetThink( NULL ); + SetUse( NULL ); + + if (FBitSet(pev->spawnflags, 32)) // Use for on/off + { + if (FBitSet(pev->spawnflags, 64)) // Start on + { + SetThink(&CEnvSpark::SparkThink); // start sparking + SetUse(&CEnvSpark::SparkStop); // set up +USE to stop sparking + } + else + SetUse(&CEnvSpark::SparkStart); + } + else + SetThink(&CEnvSpark::SparkThink); + + pev->nextthink = gpGlobals->time + ( 0.1 + RANDOM_FLOAT ( 0, 1.5 ) ); + + if (m_flDelay <= 0) + m_flDelay = 1.5; + + Precache( ); +} + + +void CEnvSpark::Precache(void) +{ + PRECACHE_SOUND( "buttons/spark1.wav" ); + PRECACHE_SOUND( "buttons/spark2.wav" ); + PRECACHE_SOUND( "buttons/spark3.wav" ); + PRECACHE_SOUND( "buttons/spark4.wav" ); + PRECACHE_SOUND( "buttons/spark5.wav" ); + PRECACHE_SOUND( "buttons/spark6.wav" ); +} + +void CEnvSpark::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "MaxDelay")) + { + m_flDelay = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "style") || + FStrEq(pkvd->szKeyName, "height") || + FStrEq(pkvd->szKeyName, "killtarget") || + FStrEq(pkvd->szKeyName, "value1") || + FStrEq(pkvd->szKeyName, "value2") || + FStrEq(pkvd->szKeyName, "value3")) + pkvd->fHandled = TRUE; + else + CBaseEntity::KeyValue( pkvd ); +} + +void EXPORT CEnvSpark::SparkThink(void) +{ + pev->nextthink = gpGlobals->time + 0.1 + RANDOM_FLOAT (0, m_flDelay); + DoSpark( pev, pev->origin ); +} + +void EXPORT CEnvSpark::SparkStart(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetUse(&CEnvSpark::SparkStop); + SetThink(&CEnvSpark::SparkThink); + pev->nextthink = gpGlobals->time + (0.1 + RANDOM_FLOAT ( 0, m_flDelay)); +} + +void EXPORT CEnvSpark::SparkStop(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetUse(&CEnvSpark::SparkStart); + SetThink(NULL); +} + +#define SF_BTARGET_USE 0x0001 +#define SF_BTARGET_ON 0x0002 + +class CButtonTarget : public CBaseEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + int ObjectCaps( void ); + +}; + +LINK_ENTITY_TO_CLASS( button_target, CButtonTarget ); + +void CButtonTarget::Spawn( void ) +{ + pev->movetype = MOVETYPE_PUSH; + pev->solid = SOLID_BSP; + SET_MODEL(ENT(pev), STRING(pev->model)); + pev->takedamage = DAMAGE_YES; + + if ( FBitSet( pev->spawnflags, SF_BTARGET_ON ) ) + pev->frame = 1; +} + +void CButtonTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, (int)pev->frame ) ) + return; + pev->frame = 1-pev->frame; + if ( pev->frame ) + SUB_UseTargets( pActivator, USE_ON, 0 ); + else + SUB_UseTargets( pActivator, USE_OFF, 0 ); +} + + +int CButtonTarget :: ObjectCaps( void ) +{ + int caps = CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; + + if ( FBitSet(pev->spawnflags, SF_BTARGET_USE) ) + return caps | FCAP_IMPULSE_USE; + else + return caps; +} + + +int CButtonTarget::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + Use( Instance(pevAttacker), this, USE_TOGGLE, 0 ); + + return 1; +} diff --git a/hlsdk/dlls/cbase.cpp b/hlsdk/dlls/cbase.cpp new file mode 100644 index 00000000..e00ef46f --- /dev/null +++ b/hlsdk/dlls/cbase.cpp @@ -0,0 +1,774 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "client.h" +#include "decals.h" +#include "gamerules.h" +#include "game.h" + +void EntvarsKeyvalue( entvars_t *pev, KeyValueData *pkvd ); + +extern "C" void PM_Move ( struct playermove_s *ppmove, int server ); +extern "C" void PM_Init ( struct playermove_s *ppmove ); +extern "C" char PM_FindTextureType( char *name ); + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); +extern DLL_GLOBAL Vector g_vecAttackDir; +extern DLL_GLOBAL int g_iSkillLevel; + +static DLL_FUNCTIONS gFunctionTable = +{ + GameDLLInit, //pfnGameInit + DispatchSpawn, //pfnSpawn + DispatchThink, //pfnThink + DispatchUse, //pfnUse + DispatchTouch, //pfnTouch + DispatchBlocked, //pfnBlocked + DispatchKeyValue, //pfnKeyValue + DispatchSave, //pfnSave + DispatchRestore, //pfnRestore + DispatchObjectCollsionBox, //pfnAbsBox + + SaveWriteFields, //pfnSaveWriteFields + SaveReadFields, //pfnSaveReadFields + + SaveGlobalState, //pfnSaveGlobalState + RestoreGlobalState, //pfnRestoreGlobalState + ResetGlobalState, //pfnResetGlobalState + + ClientConnect, //pfnClientConnect + ClientDisconnect, //pfnClientDisconnect + ClientKill, //pfnClientKill + ClientPutInServer, //pfnClientPutInServer + ClientCommand, //pfnClientCommand + ClientUserInfoChanged, //pfnClientUserInfoChanged + ServerActivate, //pfnServerActivate + ServerDeactivate, //pfnServerDeactivate + + PlayerPreThink, //pfnPlayerPreThink + PlayerPostThink, //pfnPlayerPostThink + + StartFrame, //pfnStartFrame + ParmsNewLevel, //pfnParmsNewLevel + ParmsChangeLevel, //pfnParmsChangeLevel + + GetGameDescription, //pfnGetGameDescription Returns string describing current .dll game. + PlayerCustomization, //pfnPlayerCustomization Notifies .dll of new customization for player. + + SpectatorConnect, //pfnSpectatorConnect Called when spectator joins server + SpectatorDisconnect, //pfnSpectatorDisconnect Called when spectator leaves the server + SpectatorThink, //pfnSpectatorThink Called when spectator sends a command packet (usercmd_t) + + Sys_Error, //pfnSys_Error Called when engine has encountered an error + + PM_Move, //pfnPM_Move + PM_Init, //pfnPM_Init Server version of player movement initialization + PM_FindTextureType, //pfnPM_FindTextureType + + SetupVisibility, //pfnSetupVisibility Set up PVS and PAS for networking for this client + UpdateClientData, //pfnUpdateClientData Set up data sent only to specific client + AddToFullPack, //pfnAddToFullPack + CreateBaseline, //pfnCreateBaseline Tweak entity baseline for network encoding, allows setup of player baselines, too. + RegisterEncoders, //pfnRegisterEncoders Callbacks for network encoding + GetWeaponData, //pfnGetWeaponData + CmdStart, //pfnCmdStart + CmdEnd, //pfnCmdEnd + ConnectionlessPacket, //pfnConnectionlessPacket + GetHullBounds, //pfnGetHullBounds + CreateInstancedBaselines, //pfnCreateInstancedBaselines + InconsistentFile, //pfnInconsistentFile + AllowLagCompensation, //pfnAllowLagCompensation +}; + +static void SetObjectCollisionBox( entvars_t *pev ); + +#ifndef _WIN32 +extern "C" { +#endif +int GetEntityAPI( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion ) +{ + if ( !pFunctionTable || interfaceVersion != INTERFACE_VERSION ) + { + return FALSE; + } + + memcpy( pFunctionTable, &gFunctionTable, sizeof( DLL_FUNCTIONS ) ); + return TRUE; +} + +int GetEntityAPI2( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ) +{ + if ( !pFunctionTable || *interfaceVersion != INTERFACE_VERSION ) + { + // Tell engine what version we had, so it can figure out who is out of date. + *interfaceVersion = INTERFACE_VERSION; + return FALSE; + } + + memcpy( pFunctionTable, &gFunctionTable, sizeof( DLL_FUNCTIONS ) ); + return TRUE; +} + +#ifndef _WIN32 +} +#endif + + +int DispatchSpawn( edict_t *pent ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if (pEntity) + { + // Initialize these or entities who don't link to the world won't have anything in here + pEntity->pev->absmin = pEntity->pev->origin - Vector(1,1,1); + pEntity->pev->absmax = pEntity->pev->origin + Vector(1,1,1); + + pEntity->Spawn(); + + // Try to get the pointer again, in case the spawn function deleted the entity. + // UNDONE: Spawn() should really return a code to ask that the entity be deleted, but + // that would touch too much code for me to do that right now. + pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if ( pEntity ) + { + if ( g_pGameRules && !g_pGameRules->IsAllowedToSpawn( pEntity ) ) + return -1; // return that this entity should be deleted + if ( pEntity->pev->flags & FL_KILLME ) + return -1; + } + + + // Handle global stuff here + if ( pEntity && pEntity->pev->globalname ) + { + const globalentity_t *pGlobal = gGlobalState.EntityFromTable( pEntity->pev->globalname ); + if ( pGlobal ) + { + // Already dead? delete + if ( pGlobal->state == GLOBAL_DEAD ) + return -1; + else if ( !FStrEq( STRING(gpGlobals->mapname), pGlobal->levelName ) ) + pEntity->MakeDormant(); // Hasn't been moved to this level yet, wait but stay alive + // In this level & not dead, continue on as normal + } + else + { + // Spawned entities default to 'On' + gGlobalState.EntityAdd( pEntity->pev->globalname, gpGlobals->mapname, GLOBAL_ON ); +// ALERT( at_console, "Added global entity %s (%s)\n", STRING(pEntity->pev->classname), STRING(pEntity->pev->globalname) ); + } + } + + } + + return 0; +} + +void DispatchKeyValue( edict_t *pentKeyvalue, KeyValueData *pkvd ) +{ + if ( !pkvd || !pentKeyvalue ) + return; + + EntvarsKeyvalue( VARS(pentKeyvalue), pkvd ); + + // If the key was an entity variable, or there's no class set yet, don't look for the object, it may + // not exist yet. + if ( pkvd->fHandled || pkvd->szClassName == NULL ) + return; + + // Get the actualy entity object + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pentKeyvalue); + + if ( !pEntity ) + return; + + pEntity->KeyValue( pkvd ); +} + + +// HACKHACK -- this is a hack to keep the node graph entity from "touching" things (like triggers) +// while it builds the graph +BOOL gTouchDisabled = FALSE; +void DispatchTouch( edict_t *pentTouched, edict_t *pentOther ) +{ + if ( gTouchDisabled ) + return; + + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pentTouched); + CBaseEntity *pOther = (CBaseEntity *)GET_PRIVATE( pentOther ); + + if ( pEntity && pOther && ! ((pEntity->pev->flags | pOther->pev->flags) & FL_KILLME) ) + pEntity->Touch( pOther ); +} + + +void DispatchUse( edict_t *pentUsed, edict_t *pentOther ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pentUsed); + CBaseEntity *pOther = (CBaseEntity *)GET_PRIVATE(pentOther); + + if (pEntity && !(pEntity->pev->flags & FL_KILLME) ) + pEntity->Use( pOther, pOther, USE_TOGGLE, 0 ); +} + +void DispatchThink( edict_t *pent ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + if (pEntity) + { + if ( FBitSet( pEntity->pev->flags, FL_DORMANT ) ) + ALERT( at_error, "Dormant entity %s is thinking!!\n", STRING(pEntity->pev->classname) ); + + pEntity->Think(); + } +} + +void DispatchBlocked( edict_t *pentBlocked, edict_t *pentOther ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE( pentBlocked ); + CBaseEntity *pOther = (CBaseEntity *)GET_PRIVATE( pentOther ); + + if (pEntity) + pEntity->Blocked( pOther ); +} + +void DispatchSave( edict_t *pent, SAVERESTOREDATA *pSaveData ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if ( pEntity && pSaveData ) + { + ENTITYTABLE *pTable = &pSaveData->pTable[ pSaveData->currentIndex ]; + + if ( pTable->pent != pent ) + ALERT( at_error, "ENTITY TABLE OR INDEX IS WRONG!!!!\n" ); + + if ( pEntity->ObjectCaps() & FCAP_DONT_SAVE ) + return; + + // These don't use ltime & nextthink as times really, but we'll fudge around it. + if ( pEntity->pev->movetype == MOVETYPE_PUSH ) + { + float delta = pEntity->pev->nextthink - pEntity->pev->ltime; + pEntity->pev->ltime = gpGlobals->time; + pEntity->pev->nextthink = pEntity->pev->ltime + delta; + } + + pTable->location = pSaveData->size; // Remember entity position for file I/O + pTable->classname = pEntity->pev->classname; // Remember entity class for respawn + + CSave saveHelper( pSaveData ); + pEntity->Save( saveHelper ); + + pTable->size = pSaveData->size - pTable->location; // Size of entity block is data size written to block + } +} + + +// Find the matching global entity. Spit out an error if the designer made entities of +// different classes with the same global name +CBaseEntity *FindGlobalEntity( string_t classname, string_t globalname ) +{ + edict_t *pent = FIND_ENTITY_BY_STRING( NULL, "globalname", STRING(globalname) ); + CBaseEntity *pReturn = CBaseEntity::Instance( pent ); + if ( pReturn ) + { + if ( !FClassnameIs( pReturn->pev, STRING(classname) ) ) + { + ALERT( at_console, "Global entity found %s, wrong class %s\n", STRING(globalname), STRING(pReturn->pev->classname) ); + pReturn = NULL; + } + } + + return pReturn; +} + + +int DispatchRestore( edict_t *pent, SAVERESTOREDATA *pSaveData, int globalEntity ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if ( pEntity && pSaveData ) + { + entvars_t tmpVars; + Vector oldOffset; + + CRestore restoreHelper( pSaveData ); + if ( globalEntity ) + { + CRestore tmpRestore( pSaveData ); + tmpRestore.PrecacheMode( 0 ); + tmpRestore.ReadEntVars( "ENTVARS", &tmpVars ); + + // HACKHACK - reset the save pointers, we're going to restore for real this time + pSaveData->size = pSaveData->pTable[pSaveData->currentIndex].location; + pSaveData->pCurrentData = pSaveData->pBaseData + pSaveData->size; + // ------------------- + + + const globalentity_t *pGlobal = gGlobalState.EntityFromTable( tmpVars.globalname ); + + // Don't overlay any instance of the global that isn't the latest + // pSaveData->szCurrentMapName is the level this entity is coming from + // pGlobla->levelName is the last level the global entity was active in. + // If they aren't the same, then this global update is out of date. + if ( !FStrEq( pSaveData->szCurrentMapName, pGlobal->levelName ) ) + return 0; + + // Compute the new global offset + oldOffset = pSaveData->vecLandmarkOffset; + CBaseEntity *pNewEntity = FindGlobalEntity( tmpVars.classname, tmpVars.globalname ); + if ( pNewEntity ) + { +// ALERT( at_console, "Overlay %s with %s\n", STRING(pNewEntity->pev->classname), STRING(tmpVars.classname) ); + // Tell the restore code we're overlaying a global entity from another level + restoreHelper.SetGlobalMode( 1 ); // Don't overwrite global fields + pSaveData->vecLandmarkOffset = (pSaveData->vecLandmarkOffset - pNewEntity->pev->mins) + tmpVars.mins; + pEntity = pNewEntity;// we're going to restore this data OVER the old entity + pent = ENT( pEntity->pev ); + // Update the global table to say that the global definition of this entity should come from this level + gGlobalState.EntityUpdate( pEntity->pev->globalname, gpGlobals->mapname ); + } + else + { + // This entity will be freed automatically by the engine. If we don't do a restore on a matching entity (below) + // or call EntityUpdate() to move it to this level, we haven't changed global state at all. + return 0; + } + + } + + if ( pEntity->ObjectCaps() & FCAP_MUST_SPAWN ) + { + pEntity->Restore( restoreHelper ); + pEntity->Spawn(); + } + else + { + pEntity->Restore( restoreHelper ); + pEntity->Precache( ); + } + + // Again, could be deleted, get the pointer again. + pEntity = (CBaseEntity *)GET_PRIVATE(pent); + +#if 0 + if ( pEntity && pEntity->pev->globalname && globalEntity ) + { + ALERT( at_console, "Global %s is %s\n", STRING(pEntity->pev->globalname), STRING(pEntity->pev->model) ); + } +#endif + + // Is this an overriding global entity (coming over the transition), or one restoring in a level + if ( globalEntity ) + { +// ALERT( at_console, "After: %f %f %f %s\n", pEntity->pev->origin.x, pEntity->pev->origin.y, pEntity->pev->origin.z, STRING(pEntity->pev->model) ); + pSaveData->vecLandmarkOffset = oldOffset; + if ( pEntity ) + { + UTIL_SetOrigin( pEntity->pev, pEntity->pev->origin ); + pEntity->OverrideReset(); + } + } + else if ( pEntity && pEntity->pev->globalname ) + { + const globalentity_t *pGlobal = gGlobalState.EntityFromTable( pEntity->pev->globalname ); + if ( pGlobal ) + { + // Already dead? delete + if ( pGlobal->state == GLOBAL_DEAD ) + return -1; + else if ( !FStrEq( STRING(gpGlobals->mapname), pGlobal->levelName ) ) + { + pEntity->MakeDormant(); // Hasn't been moved to this level yet, wait but stay alive + } + // In this level & not dead, continue on as normal + } + else + { + ALERT( at_error, "Global Entity %s (%s) not in table!!!\n", STRING(pEntity->pev->globalname), STRING(pEntity->pev->classname) ); + // Spawned entities default to 'On' + gGlobalState.EntityAdd( pEntity->pev->globalname, gpGlobals->mapname, GLOBAL_ON ); + } + } + } + return 0; +} + + +void DispatchObjectCollsionBox( edict_t *pent ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + if (pEntity) + { + pEntity->SetObjectCollisionBox(); + } + else + SetObjectCollisionBox( &pent->v ); +} + + +void SaveWriteFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + CSave saveHelper( pSaveData ); + saveHelper.WriteFields( pname, pBaseData, pFields, fieldCount ); +} + + +void SaveReadFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + CRestore restoreHelper( pSaveData ); + restoreHelper.ReadFields( pname, pBaseData, pFields, fieldCount ); +} + + +edict_t * EHANDLE::Get( void ) +{ + if (m_pent) + { + if (m_pent->serialnumber == m_serialnumber) + return m_pent; + else + return NULL; + } + return NULL; +}; + +edict_t * EHANDLE::Set( edict_t *pent ) +{ + m_pent = pent; + if (pent) + m_serialnumber = m_pent->serialnumber; + return pent; +}; + + +EHANDLE :: operator CBaseEntity *() +{ + return (CBaseEntity *)GET_PRIVATE( Get( ) ); +}; + + +CBaseEntity * EHANDLE :: operator = (CBaseEntity *pEntity) +{ + if (pEntity) + { + m_pent = ENT( pEntity->pev ); + if (m_pent) + m_serialnumber = m_pent->serialnumber; + } + else + { + m_pent = NULL; + m_serialnumber = 0; + } + return pEntity; +} + +EHANDLE :: operator int () +{ + return Get() != NULL; +} + +CBaseEntity * EHANDLE :: operator -> () +{ + return (CBaseEntity *)GET_PRIVATE( Get( ) ); +} + + +// give health +int CBaseEntity :: TakeHealth( float flHealth, int bitsDamageType ) +{ + if (!pev->takedamage) + return 0; + +// heal + if ( pev->health >= pev->max_health ) + return 0; + + pev->health += flHealth; + + if (pev->health > pev->max_health) + pev->health = pev->max_health; + + return 1; +} + +// inflict damage on this entity. bitsDamageType indicates type of damage inflicted, ie: DMG_CRUSH + +int CBaseEntity :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + Vector vecTemp; + + if (!pev->takedamage) + return 0; + + // UNDONE: some entity types may be immune or resistant to some bitsDamageType + + // if Attacker == Inflictor, the attack was a melee or other instant-hit attack. + // (that is, no actual entity projectile was involved in the attack so use the shooter's origin). + if ( pevAttacker == pevInflictor ) + { + vecTemp = pevInflictor->origin - ( VecBModelOrigin(pev) ); + } + else + // an actual missile was involved. + { + vecTemp = pevInflictor->origin - ( VecBModelOrigin(pev) ); + } + +// this global is still used for glass and other non-monster killables, along with decals. + g_vecAttackDir = vecTemp.Normalize(); + +// save damage based on the target's armor level + +// figure momentum add (don't let hurt brushes or other triggers move player) + if ((!FNullEnt(pevInflictor)) && (pev->movetype == MOVETYPE_WALK || pev->movetype == MOVETYPE_STEP) && (pevAttacker->solid != SOLID_TRIGGER) ) + { + Vector vecDir = pev->origin - (pevInflictor->absmin + pevInflictor->absmax) * 0.5; + vecDir = vecDir.Normalize(); + + float flForce = flDamage * ((32 * 32 * 72.0) / (pev->size.x * pev->size.y * pev->size.z)) * 5; + + if (flForce > 1000.0) + flForce = 1000.0; + pev->velocity = pev->velocity + vecDir * flForce; + } + +// do the damage + pev->health -= flDamage; + if (pev->health <= 0) + { + Killed( pevAttacker, GIB_NORMAL ); + return 0; + } + + return 1; +} + + +void CBaseEntity :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->takedamage = DAMAGE_NO; + pev->deadflag = DEAD_DEAD; + UTIL_Remove( this ); +} + + +CBaseEntity *CBaseEntity::GetNextTarget( void ) +{ + if ( FStringNull( pev->target ) ) + return NULL; + edict_t *pTarget = FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(pev->target) ); + if ( FNullEnt(pTarget) ) + return NULL; + + return Instance( pTarget ); +} + +// Global Savedata for Delay +TYPEDESCRIPTION CBaseEntity::m_SaveData[] = +{ + DEFINE_FIELD( CBaseEntity, m_pGoalEnt, FIELD_CLASSPTR ), + + DEFINE_FIELD( CBaseEntity, m_pfnThink, FIELD_FUNCTION ), // UNDONE: Build table of these!!! + DEFINE_FIELD( CBaseEntity, m_pfnTouch, FIELD_FUNCTION ), + DEFINE_FIELD( CBaseEntity, m_pfnUse, FIELD_FUNCTION ), + DEFINE_FIELD( CBaseEntity, m_pfnBlocked, FIELD_FUNCTION ), +}; + + +int CBaseEntity::Save( CSave &save ) +{ + if ( save.WriteEntVars( "ENTVARS", pev ) ) + return save.WriteFields( "BASE", this, m_SaveData, ARRAYSIZE(m_SaveData) ); + + return 0; +} + +int CBaseEntity::Restore( CRestore &restore ) +{ + int status; + + status = restore.ReadEntVars( "ENTVARS", pev ); + if ( status ) + status = restore.ReadFields( "BASE", this, m_SaveData, ARRAYSIZE(m_SaveData) ); + + if ( pev->modelindex != 0 && !FStringNull(pev->model) ) + { + Vector mins, maxs; + mins = pev->mins; // Set model is about to destroy these + maxs = pev->maxs; + + + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL(ENT(pev), STRING(pev->model)); + UTIL_SetSize(pev, mins, maxs); // Reset them + } + + return status; +} + + +// Initialize absmin & absmax to the appropriate box +void SetObjectCollisionBox( entvars_t *pev ) +{ + if ( (pev->solid == SOLID_BSP) && + (pev->angles.x || pev->angles.y|| pev->angles.z) ) + { // expand for rotation + float max, v; + int i; + + max = 0; + for (i=0 ; i<3 ; i++) + { + v = fabs( ((float *)pev->mins)[i]); + if (v > max) + max = v; + v = fabs( ((float *)pev->maxs)[i]); + if (v > max) + max = v; + } + for (i=0 ; i<3 ; i++) + { + ((float *)pev->absmin)[i] = ((float *)pev->origin)[i] - max; + ((float *)pev->absmax)[i] = ((float *)pev->origin)[i] + max; + } + } + else + { + pev->absmin = pev->origin + pev->mins; + pev->absmax = pev->origin + pev->maxs; + } + + pev->absmin.x -= 1; + pev->absmin.y -= 1; + pev->absmin.z -= 1; + pev->absmax.x += 1; + pev->absmax.y += 1; + pev->absmax.z += 1; +} + + +void CBaseEntity::SetObjectCollisionBox( void ) +{ + ::SetObjectCollisionBox( pev ); +} + + +int CBaseEntity :: Intersects( CBaseEntity *pOther ) +{ + if ( pOther->pev->absmin.x > pev->absmax.x || + pOther->pev->absmin.y > pev->absmax.y || + pOther->pev->absmin.z > pev->absmax.z || + pOther->pev->absmax.x < pev->absmin.x || + pOther->pev->absmax.y < pev->absmin.y || + pOther->pev->absmax.z < pev->absmin.z ) + return 0; + return 1; +} + +void CBaseEntity :: MakeDormant( void ) +{ + SetBits( pev->flags, FL_DORMANT ); + + // Don't touch + pev->solid = SOLID_NOT; + // Don't move + pev->movetype = MOVETYPE_NONE; + // Don't draw + SetBits( pev->effects, EF_NODRAW ); + // Don't think + pev->nextthink = 0; + // Relink + UTIL_SetOrigin( pev, pev->origin ); +} + +int CBaseEntity :: IsDormant( void ) +{ + return FBitSet( pev->flags, FL_DORMANT ); +} + +BOOL CBaseEntity :: IsInWorld( void ) +{ + // position + if (pev->origin.x >= 4096) return FALSE; + if (pev->origin.y >= 4096) return FALSE; + if (pev->origin.z >= 4096) return FALSE; + if (pev->origin.x <= -4096) return FALSE; + if (pev->origin.y <= -4096) return FALSE; + if (pev->origin.z <= -4096) return FALSE; + // speed + if (pev->velocity.x >= 2000) return FALSE; + if (pev->velocity.y >= 2000) return FALSE; + if (pev->velocity.z >= 2000) return FALSE; + if (pev->velocity.x <= -2000) return FALSE; + if (pev->velocity.y <= -2000) return FALSE; + if (pev->velocity.z <= -2000) return FALSE; + + return TRUE; +} + +int CBaseEntity::ShouldToggle( USE_TYPE useType, BOOL currentState ) +{ + if ( useType != USE_TOGGLE && useType != USE_SET ) + { + if ( (currentState && useType == USE_ON) || (!currentState && useType == USE_OFF) ) + return 0; + } + return 1; +} + + +int CBaseEntity :: DamageDecal( int bitsDamageType ) +{ + if ( pev->rendermode == kRenderTransAlpha ) + return -1; + + if ( pev->rendermode != kRenderNormal ) + return DECAL_BPROOF1; + + return DECAL_GUNSHOT1 + RANDOM_LONG(0,4); +} + + + +// NOTE: szName must be a pointer to constant memory, e.g. "monster_class" because the entity +// will keep a pointer to it after this call. +CBaseEntity * CBaseEntity::Create( char *szName, const Vector &vecOrigin, const Vector &vecAngles, edict_t *pentOwner ) +{ + edict_t *pent; + CBaseEntity *pEntity; + + pent = CREATE_NAMED_ENTITY( MAKE_STRING( szName )); + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in Create!\n" ); + return NULL; + } + pEntity = Instance( pent ); + pEntity->pev->owner = pentOwner; + pEntity->pev->origin = vecOrigin; + pEntity->pev->angles = vecAngles; + DispatchSpawn( pEntity->edict() ); + return pEntity; +} + + diff --git a/hlsdk/dlls/cbase.h b/hlsdk/dlls/cbase.h new file mode 100644 index 00000000..0474df8a --- /dev/null +++ b/hlsdk/dlls/cbase.h @@ -0,0 +1,797 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +Class Hierachy + +CBaseEntity + CBaseDelay + CBaseToggle + CBaseItem + CBaseMonster + CBaseCycler + CBasePlayer + CBaseGroup +*/ + +#define MAX_PATH_SIZE 10 // max number of nodes available for a path. + +// These are caps bits to indicate what an object's capabilities (currently used for save/restore and level transitions) +#define FCAP_CUSTOMSAVE 0x00000001 +#define FCAP_ACROSS_TRANSITION 0x00000002 // should transfer between transitions +#define FCAP_MUST_SPAWN 0x00000004 // Spawn after restore +#define FCAP_DONT_SAVE 0x80000000 // Don't save this +#define FCAP_IMPULSE_USE 0x00000008 // can be used by the player +#define FCAP_CONTINUOUS_USE 0x00000010 // can be used by the player +#define FCAP_ONOFF_USE 0x00000020 // can be used by the player +#define FCAP_DIRECTIONAL_USE 0x00000040 // Player sends +/- 1 when using (currently only tracktrains) +#define FCAP_MASTER 0x00000080 // Can be used to "master" other entities (like multisource) + +// UNDONE: This will ignore transition volumes (trigger_transition), but not the PVS!!! +#define FCAP_FORCE_TRANSITION 0x00000080 // ALWAYS goes across transitions + +#include "saverestore.h" +#include "schedule.h" + +#ifndef MONSTEREVENT_H +#include "monsterevent.h" +#endif + +// C functions for external declarations that call the appropriate C++ methods + +#ifdef _WIN32 +#define EXPORT _declspec( dllexport ) +#else +#define EXPORT /* */ +#endif + +extern "C" EXPORT int GetEntityAPI( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion ); +extern "C" EXPORT int GetEntityAPI2( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ); + +extern int DispatchSpawn( edict_t *pent ); +extern void DispatchKeyValue( edict_t *pentKeyvalue, KeyValueData *pkvd ); +extern void DispatchTouch( edict_t *pentTouched, edict_t *pentOther ); +extern void DispatchUse( edict_t *pentUsed, edict_t *pentOther ); +extern void DispatchThink( edict_t *pent ); +extern void DispatchBlocked( edict_t *pentBlocked, edict_t *pentOther ); +extern void DispatchSave( edict_t *pent, SAVERESTOREDATA *pSaveData ); +extern int DispatchRestore( edict_t *pent, SAVERESTOREDATA *pSaveData, int globalEntity ); +extern void DispatchObjectCollsionBox( edict_t *pent ); +extern void SaveWriteFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); +extern void SaveReadFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); +extern void SaveGlobalState( SAVERESTOREDATA *pSaveData ); +extern void RestoreGlobalState( SAVERESTOREDATA *pSaveData ); +extern void ResetGlobalState( void ); + +typedef enum { USE_OFF = 0, USE_ON = 1, USE_SET = 2, USE_TOGGLE = 3 } USE_TYPE; + +extern void FireTargets( const char *targetName, CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +typedef void (CBaseEntity::*BASEPTR)(void); +typedef void (CBaseEntity::*ENTITYFUNCPTR)(CBaseEntity *pOther ); +typedef void (CBaseEntity::*USEPTR)( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +// For CLASSIFY +#define CLASS_NONE 0 +#define CLASS_MACHINE 1 +#define CLASS_PLAYER 2 +#define CLASS_HUMAN_PASSIVE 3 +#define CLASS_HUMAN_MILITARY 4 +#define CLASS_ALIEN_MILITARY 5 +#define CLASS_ALIEN_PASSIVE 6 +#define CLASS_ALIEN_MONSTER 7 +#define CLASS_ALIEN_PREY 8 +#define CLASS_ALIEN_PREDATOR 9 +#define CLASS_INSECT 10 +#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; +class CBasePlayerItem; +class CSquadMonster; + + +#define SF_NORESPAWN ( 1 << 30 )// !!!set this bit on guns and stuff that should never respawn. + +// +// EHANDLE. Safe way to point to CBaseEntities who may die between frames +// +class EHANDLE +{ +private: + edict_t *m_pent; + int m_serialnumber; +public: + edict_t *Get( void ); + edict_t *Set( edict_t *pent ); + + operator int (); + + operator CBaseEntity *(); + + CBaseEntity * operator = (CBaseEntity *pEntity); + CBaseEntity * operator ->(); +}; + + +// +// Base Entity. All entity types derive from this +// +class CBaseEntity +{ +public: + // Constructor. Set engine to use C/C++ callback functions + // pointers to engine data + entvars_t *pev; // Don't need to save/restore this pointer, the engine resets it + + // path corners + CBaseEntity *m_pGoalEnt;// path corner we are heading towards + CBaseEntity *m_pLink;// used for temporary link-list operations. + + // initialization functions + virtual void Spawn( void ) { return; } + virtual void Precache( void ) { return; } + virtual void KeyValue( KeyValueData* pkvd) { pkvd->fHandled = FALSE; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual int ObjectCaps( void ) { return FCAP_ACROSS_TRANSITION; } + virtual void Activate( void ) {} + + // Setup the object->object collision box (pev->mins / pev->maxs is the object->world collision box) + virtual void SetObjectCollisionBox( void ); + +// Classify - returns the type of group (i.e, "houndeye", or "human military" so that monsters with different classnames +// still realize that they are teammates. (overridden for monsters that form groups) + virtual int Classify ( void ) { return CLASS_NONE; }; + virtual void DeathNotice ( entvars_t *pevChild ) {}// monster maker children use this to tell the monster maker that they have died. + + + static TYPEDESCRIPTION m_SaveData[]; + + virtual void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + 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 ); + virtual int BloodColor( void ) { return DONT_BLEED; } + virtual void TraceBleed( float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); + virtual BOOL IsTriggered( CBaseEntity *pActivator ) {return TRUE;} + virtual CBaseMonster *MyMonsterPointer( void ) { return NULL;} + virtual CSquadMonster *MySquadMonsterPointer( void ) { return NULL;} + virtual int GetToggleState( void ) { return TS_AT_TOP; } + virtual void AddPoints( int score, BOOL bAllowNegativeScore ) {} + virtual void AddPointsToTeam( int score, BOOL bAllowNegativeScore ) {} + virtual BOOL AddPlayerItem( CBasePlayerItem *pItem ) { return 0; } + virtual BOOL RemovePlayerItem( CBasePlayerItem *pItem ) { return 0; } + virtual int GiveAmmo( int iAmount, char *szName, int iMax ) { return -1; }; + virtual float GetDelay( void ) { return 0; } + virtual int IsMoving( void ) { return pev->velocity != g_vecZero; } + virtual void OverrideReset( void ) {} + virtual int DamageDecal( int bitsDamageType ); + // This is ONLY used by the node graph to test movement through a door + virtual void SetToggleState( int state ) {} + virtual void StartSneaking( void ) {} + virtual void StopSneaking( void ) {} + virtual BOOL OnControls( entvars_t *onpev ) { return FALSE; } + virtual BOOL IsSneaking( void ) { return FALSE; } + virtual BOOL IsAlive( void ) { return (pev->deadflag == DEAD_NO) && pev->health > 0; } + virtual BOOL IsBSPModel( void ) { return pev->solid == SOLID_BSP || pev->movetype == MOVETYPE_PUSHSTEP; } + virtual BOOL ReflectGauss( void ) { return ( IsBSPModel() && !pev->takedamage ); } + virtual BOOL HasTarget( string_t targetname ) { return FStrEq(STRING(targetname), STRING(pev->targetname) ); } + virtual BOOL IsInWorld( void ); + virtual BOOL IsPlayer( void ) { return FALSE; } + virtual BOOL IsNetClient( void ) { return FALSE; } + virtual const char *TeamID( void ) { return ""; } + + +// virtual void SetActivator( CBaseEntity *pActivator ) {} + virtual CBaseEntity *GetNextTarget( void ); + + // fundamental callbacks + void (CBaseEntity ::*m_pfnThink)(void); + void (CBaseEntity ::*m_pfnTouch)( CBaseEntity *pOther ); + void (CBaseEntity ::*m_pfnUse)( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void (CBaseEntity ::*m_pfnBlocked)( CBaseEntity *pOther ); + + virtual void Think( void ) { if (m_pfnThink) (this->*m_pfnThink)(); }; + virtual void Touch( CBaseEntity *pOther ) { if (m_pfnTouch) (this->*m_pfnTouch)( pOther ); }; + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) + { + if (m_pfnUse) + (this->*m_pfnUse)( pActivator, pCaller, useType, value ); + } + virtual void Blocked( CBaseEntity *pOther ) { if (m_pfnBlocked) (this->*m_pfnBlocked)( pOther ); }; + + // allow engine to allocate instance data + void *operator new( size_t stAllocateBlock, entvars_t *newpev ) + { + return (void *)ALLOC_PRIVATE(ENT(newpev), stAllocateBlock); + }; + + // don't use this. +#if defined(_MSC_VER) && _MSC_VER >= 1200 // only build this code if MSVC++ 6.0 or higher + void operator delete(void *pMem, entvars_t *pev) + { + pev->flags |= FL_KILLME; + }; +#endif + + void UpdateOnRemove( void ); + + // common member functions + void EXPORT SUB_Remove( void ); + void EXPORT SUB_DoNothing( void ); + void EXPORT SUB_StartFadeOut ( void ); + void EXPORT SUB_FadeOut ( void ); + 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 ); + + virtual CBaseEntity *Respawn( void ) { return NULL; } + + void SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ); + // Do the bounding boxes of these two intersect? + int Intersects( CBaseEntity *pOther ); + void MakeDormant( void ); + int IsDormant( void ); + BOOL IsLockedByMaster( void ) { return FALSE; } + + static CBaseEntity *Instance( edict_t *pent ) + { + if ( !pent ) + pent = ENT(0); + CBaseEntity *pEnt = (CBaseEntity *)GET_PRIVATE(pent); + return pEnt; + } + + static CBaseEntity *Instance( entvars_t *instpev ) { return Instance( ENT( instpev ) ); } + static CBaseEntity *Instance( int inst_eoffset) { return Instance( ENT( inst_eoffset) ); } + + CBaseMonster *GetMonsterPointer( entvars_t *pevMonster ) + { + CBaseEntity *pEntity = Instance( pevMonster ); + if ( pEntity ) + return pEntity->MyMonsterPointer(); + return NULL; + } + CBaseMonster *GetMonsterPointer( edict_t *pentMonster ) + { + CBaseEntity *pEntity = Instance( pentMonster ); + if ( pEntity ) + return pEntity->MyMonsterPointer(); + return NULL; + } + + + // Ugly code to lookup all functions to make sure they are exported when set. +#ifdef _DEBUG + void FunctionCheck( void *pFunction, char *name ) + { + if (pFunction && !NAME_FOR_FUNCTION((unsigned long)(pFunction)) ) + ALERT( at_error, "No EXPORT: %s:%s (%08lx)\n", STRING(pev->classname), name, (unsigned long)pFunction ); + } + + BASEPTR ThinkSet( BASEPTR func, char *name ) + { + m_pfnThink = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnThink)))), name ); + return func; + } + ENTITYFUNCPTR TouchSet( ENTITYFUNCPTR func, char *name ) + { + m_pfnTouch = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnTouch)))), name ); + return func; + } + USEPTR UseSet( USEPTR func, char *name ) + { + m_pfnUse = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnUse)))), name ); + return func; + } + ENTITYFUNCPTR BlockedSet( ENTITYFUNCPTR func, char *name ) + { + m_pfnBlocked = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnBlocked)))), name ); + return func; + } + +#endif + + + // virtual functions used by a few classes + + // used by monsters that are created by the MonsterMaker + virtual void UpdateOwner( void ) { return; }; + + + // + static CBaseEntity *Create( char *szName, const Vector &vecOrigin, const Vector &vecAngles, edict_t *pentOwner = NULL ); + + virtual BOOL FBecomeProne( void ) {return FALSE;}; + edict_t *edict() { return ENT( pev ); }; + EOFFSET eoffset( ) { return OFFSET( pev ); }; + int entindex( ) { return ENTINDEX( edict() ); }; + + virtual Vector Center( ) { return (pev->absmax + pev->absmin) * 0.5; }; // center point of entity + virtual Vector EyePosition( ) { return pev->origin + pev->view_ofs; }; // position of eyes + virtual Vector EarPosition( ) { return pev->origin + pev->view_ofs; }; // position of ears + virtual Vector BodyTarget( const Vector &posSrc ) { return Center( ); }; // position to shoot at + + virtual int Illumination( ) { return GETENTITYILLUM( ENT( pev ) ); }; + + virtual BOOL FVisible ( CBaseEntity *pEntity ); + virtual BOOL FVisible ( const Vector &vecOrigin ); + + //We use this variables to store each ammo count. + int ammo_9mm; + int ammo_357; + int ammo_bolts; + int ammo_buckshot; + int ammo_rockets; + int ammo_uranium; + int ammo_hornets; + 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; +}; + + + +// 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. + +#ifdef _DEBUG + +#define SetThink( a ) ThinkSet( static_cast (a), #a ) +#define SetTouch( a ) TouchSet( static_cast (a), #a ) +#define SetUse( a ) UseSet( static_cast (a), #a ) +#define SetBlocked( a ) BlockedSet( static_cast (a), #a ) + +#else + +#define SetThink( a ) m_pfnThink = static_cast (a) +#define SetTouch( a ) m_pfnTouch = static_cast (a) +#define SetUse( a ) m_pfnUse = static_cast (a) +#define SetBlocked( a ) m_pfnBlocked = static_cast (a) + +#endif + + +class CPointEntity : public CBaseEntity +{ +public: + void Spawn( void ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +private: +}; + + +typedef struct locksounds // sounds that doors and buttons make when locked/unlocked +{ + string_t sLockedSound; // sound a door makes when it's locked + string_t sLockedSentence; // sentence group played when door is locked + string_t sUnlockedSound; // sound a door makes when it's unlocked + string_t sUnlockedSentence; // sentence group played when door is unlocked + + int iLockedSentence; // which sentence in sentence group to play next + int iUnlockedSentence; // which sentence in sentence group to play next + + float flwaitSound; // time delay between playing consecutive 'locked/unlocked' sounds + float flwaitSentence; // time delay between playing consecutive sentences + BYTE bEOFLocked; // true if hit end of list of locked sentences + BYTE bEOFUnlocked; // true if hit end of list of unlocked sentences +} locksound_t; + +void PlayLockSounds(entvars_t *pev, locksound_t *pls, int flocked, int fbutton); + +// +// MultiSouce +// + +#define MAX_MULTI_TARGETS 16 // maximum number of targets a single multi_manager entity may be assigned. +#define MS_MAX_TARGETS 32 + +class CMultiSource : public CPointEntity +{ +public: + void Spawn( ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int ObjectCaps( void ) { return (CPointEntity::ObjectCaps() | FCAP_MASTER); } + BOOL IsTriggered( CBaseEntity *pActivator ); + void EXPORT Register( void ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + EHANDLE m_rgEntities[MS_MAX_TARGETS]; + int m_rgTriggered[MS_MAX_TARGETS]; + + int m_iTotal; + string_t m_globalstate; +}; + + +// +// generic Delay entity. +// +class CBaseDelay : public CBaseEntity +{ +public: + float m_flDelay; + int m_iszKillTarget; + + virtual void KeyValue( KeyValueData* pkvd); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + // common member functions + void SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ); + void EXPORT DelayThink( void ); +}; + + +class CBaseAnimating : public CBaseDelay +{ +public: + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + // Basic Monster Animation functions + float StudioFrameAdvance( float flInterval = 0.0 ); // accumulate animation frame time from last time called until now + int GetSequenceFlags( void ); + int LookupActivity ( int activity ); + int LookupActivityHeaviest ( int activity ); + int LookupSequence ( const char *label ); + void ResetSequenceInfo ( ); + void DispatchAnimEvents ( float flFutureInterval = 0.1 ); // Handle events that have happend since last time called up until X seconds into the future + virtual void HandleAnimEvent( MonsterEvent_t *pEvent ) { return; }; + float SetBoneController ( int iController, float flValue ); + void InitBoneControllers ( void ); + float SetBlending ( int iBlender, float flValue ); + void GetBonePosition ( int iBone, Vector &origin, Vector &angles ); + void GetAutomovement( Vector &origin, Vector &angles, float flInterval = 0.1 ); + int FindTransition( int iEndingSequence, int iGoalSequence, int *piDir ); + void GetAttachment ( int iAttachment, Vector &origin, Vector &angles ); + void SetBodygroup( int iGroup, int iValue ); + int GetBodygroup( int iGroup ); + int ExtractBbox( int sequence, float *mins, float *maxs ); + void SetSequenceBox( void ); + + // animation needs + float m_flFrameRate; // computed FPS for current sequence + float m_flGroundSpeed; // computed linear movement rate for current sequence + float m_flLastEventCheck; // last time the event list was checked + BOOL m_fSequenceFinished;// flag set when StudioAdvanceFrame moves across a frame boundry + BOOL m_fSequenceLoops; // true if the sequence loops +}; + + +// +// generic Toggle entity. +// +#define SF_ITEM_USE_ONLY 256 // ITEM_USE_ONLY = BUTTON_USE_ONLY = DOOR_USE_ONLY!!! + +class CBaseToggle : public CBaseAnimating +{ +public: + void KeyValue( KeyValueData *pkvd ); + + TOGGLE_STATE m_toggle_state; + float m_flActivateFinished;//like attack_finished, but for doors + float m_flMoveDistance;// how far a door should slide or rotate + float m_flWait; + float m_flLip; + float m_flTWidth;// for plats + float m_flTLength;// for plats + + Vector m_vecPosition1; + Vector m_vecPosition2; + Vector m_vecAngle1; + Vector m_vecAngle2; + + int m_cTriggersLeft; // trigger_counter only, # of activations remaining + float m_flHeight; + EHANDLE m_hActivator; + void (CBaseToggle::*m_pfnCallWhenMoveDone)(void); + Vector m_vecFinalDest; + Vector m_vecFinalAngle; + + int m_bitsDamageInflict; // DMG_ damage type that the door or tigger does + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + virtual int GetToggleState( void ) { return m_toggle_state; } + virtual float GetDelay( void ) { return m_flWait; } + + // common member functions + void LinearMove( Vector vecDest, float flSpeed ); + void EXPORT LinearMoveDone( void ); + void AngularMove( Vector vecDestAngle, float flSpeed ); + void EXPORT AngularMoveDone( void ); + BOOL IsLockedByMaster( void ); + + static float AxisValue( int flags, const Vector &angles ); + static void AxisDir( entvars_t *pev ); + static float AxisDelta( int flags, const Vector &angle1, const Vector &angle2 ); + + string_t m_sMaster; // If this button has a master switch, this is the targetname. + // A master switch must be of the multisource type. If all + // of the switches in the multisource have been triggered, then + // the button will be allowed to operate. Otherwise, it will be + // deactivated. +}; +#define SetMoveDone( a ) m_pfnCallWhenMoveDone = static_cast (a) + + +// people gib if their health is <= this at the time of death +#define GIB_HEALTH_VALUE -30 + +#define ROUTE_SIZE 8 // how many waypoints a monster can store at one time +#define MAX_OLD_ENEMIES 4 // how many old enemies to remember + +#define bits_CAP_DUCK ( 1 << 0 )// crouch +#define bits_CAP_JUMP ( 1 << 1 )// jump/leap +#define bits_CAP_STRAFE ( 1 << 2 )// strafe ( walk/run sideways) +#define bits_CAP_SQUAD ( 1 << 3 )// can form squads +#define bits_CAP_SWIM ( 1 << 4 )// proficiently navigate in water +#define bits_CAP_CLIMB ( 1 << 5 )// climb ladders/ropes +#define bits_CAP_USE ( 1 << 6 )// open doors/push buttons/pull levers +#define bits_CAP_HEAR ( 1 << 7 )// can hear forced sounds +#define bits_CAP_AUTO_DOORS ( 1 << 8 )// can trigger auto doors +#define bits_CAP_OPEN_DOORS ( 1 << 9 )// can open manual doors +#define bits_CAP_TURN_HEAD ( 1 << 10)// can turn head, always bone controller 0 + +#define bits_CAP_RANGE_ATTACK1 ( 1 << 11)// can do a range attack 1 +#define bits_CAP_RANGE_ATTACK2 ( 1 << 12)// can do a range attack 2 +#define bits_CAP_MELEE_ATTACK1 ( 1 << 13)// can do a melee attack 1 +#define bits_CAP_MELEE_ATTACK2 ( 1 << 14)// can do a melee attack 2 + +#define bits_CAP_FLY ( 1 << 15)// can fly, move all around + +#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 +#define DMG_CRUSH (1 << 0) // crushed by falling or moving object +#define DMG_BULLET (1 << 1) // shot +#define DMG_SLASH (1 << 2) // cut, clawed, stabbed +#define DMG_BURN (1 << 3) // heat burned +#define DMG_FREEZE (1 << 4) // frozen +#define DMG_FALL (1 << 5) // fell too far +#define DMG_BLAST (1 << 6) // explosive blast damage +#define DMG_CLUB (1 << 7) // crowbar, punch, headbutt +#define DMG_SHOCK (1 << 8) // electric shock +#define DMG_SONIC (1 << 9) // sound pulse shockwave +#define DMG_ENERGYBEAM (1 << 10) // laser or other high energy beam +#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. +#define DMG_DROWN (1 << 14) // Drowning +// time-based damage +#define DMG_TIMEBASED (~(0x3fff)) // mask for time-based damage + +#define DMG_PARALYZE (1 << 15) // slows affected creature down +#define DMG_NERVEGAS (1 << 16) // nerve toxins, very bad +#define DMG_POISON (1 << 17) // blood poisioning +#define DMG_RADIATION (1 << 18) // radiation exposure +#define DMG_DROWNRECOVER (1 << 19) // drowning recovery +#define DMG_ACID (1 << 20) // toxic chemicals or acid burns +#define DMG_SLOWBURN (1 << 21) // in an oven +#define DMG_SLOWFREEZE (1 << 22) // in a subzero freezer +#define DMG_MORTAR (1 << 23) // Hit by air raid (done to distinguish grenade from mortar) + +// 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 ) + +// these are the damage types that have client hud art +#define DMG_SHOWNHUD (DMG_POISON | DMG_ACID | DMG_FREEZE | DMG_SLOWFREEZE | DMG_DROWN | DMG_BURN | DMG_SLOWBURN | DMG_NERVEGAS | DMG_RADIATION | DMG_SHOCK) + +// NOTE: tweak these values based on gameplay feedback: + +#define PARALYZE_DURATION 2 // number of 2 second intervals to take damage +#define PARALYZE_DAMAGE 1.0 // damage to take each 2 second interval + +#define NERVEGAS_DURATION 2 +#define NERVEGAS_DAMAGE 5.0 + +#define POISON_DURATION 5 +#define POISON_DAMAGE 2.0 + +#define RADIATION_DURATION 2 +#define RADIATION_DAMAGE 1.0 + +#define ACID_DURATION 2 +#define ACID_DAMAGE 5.0 + +#define SLOWBURN_DURATION 2 +#define SLOWBURN_DAMAGE 1.0 + +#define SLOWFREEZE_DURATION 2 +#define SLOWFREEZE_DAMAGE 1.0 + + +#define itbd_Paralyze 0 +#define itbd_NerveGas 1 +#define itbd_Poison 2 +#define itbd_Radiation 3 +#define itbd_DrownRecover 4 +#define itbd_Acid 5 +#define itbd_SlowBurn 6 +#define itbd_SlowFreeze 7 +#define CDMG_TIMEBASED 8 + +// when calling KILLED(), a value that governs gib behavior is expected to be +// one of these three values +#define GIB_NORMAL 0// gib if entity was overkilled +#define GIB_NEVER 1// never gib, no matter how much death damage is done ( freezing, etc ) +#define GIB_ALWAYS 2// always gib ( Houndeye Shock, Barnacle Bite ) + +class CBaseMonster; +class CCineMonster; +class CSound; + +#include "basemonster.h" + + +char *ButtonSound( int sound ); // get string of button sound number + + +// +// Generic Button +// +class CBaseButton : public CBaseToggle +{ +public: + void Spawn( void ); + virtual void Precache( void ); + void RotSpawn( void ); + virtual void KeyValue( KeyValueData* pkvd); + + void ButtonActivate( ); + void SparkSoundCache( void ); + + void EXPORT ButtonShot( void ); + void EXPORT ButtonTouch( CBaseEntity *pOther ); + void EXPORT ButtonSpark ( void ); + void EXPORT TriggerAndWait( void ); + void EXPORT ButtonReturn( void ); + void EXPORT ButtonBackHome( void ); + void EXPORT ButtonUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + enum BUTTON_CODE { BUTTON_NOTHING, BUTTON_ACTIVATE, BUTTON_RETURN }; + BUTTON_CODE ButtonResponseToTouch( void ); + + static TYPEDESCRIPTION m_SaveData[]; + // Buttons that don't take damage can be IMPULSE used + virtual int ObjectCaps( void ) { return (CBaseToggle:: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | (pev->takedamage?0:FCAP_IMPULSE_USE); } + + BOOL m_fStayPushed; // button stays pushed in until touched again? + BOOL m_fRotating; // a rotating button? default is a sliding button. + + string_t m_strChangeTarget; // if this field is not null, this is an index into the engine string array. + // when this button is touched, it's target entity's TARGET field will be set + // to the button's ChangeTarget. This allows you to make a func_train switch paths, etc. + + locksound_t m_ls; // door lock sounds + + BYTE m_bLockedSound; // ordinals from entity selection + BYTE m_bLockedSentence; + BYTE m_bUnlockedSound; + BYTE m_bUnlockedSentence; + int m_sounds; +}; + +// +// Weapons +// + +#define BAD_WEAPON 0x00007FFF + +// +// Converts a entvars_t * to a class pointer +// It will allocate the class and entity if necessary +// +template T * GetClassPtr( T *a ) +{ + entvars_t *pev = (entvars_t *)a; + + // allocate entity if necessary + if (pev == NULL) + pev = VARS(CREATE_ENTITY()); + + // get the private data + a = (T *)GET_PRIVATE(ENT(pev)); + + if (a == NULL) + { + // allocate private data + a = new(pev) T; + a->pev = pev; + } + return a; +} + + +/* +bit_PUSHBRUSH_DATA | bit_TOGGLE_DATA +bit_MONSTER_DATA +bit_DELAY_DATA +bit_TOGGLE_DATA | bit_DELAY_DATA | bit_MONSTER_DATA +bit_PLAYER_DATA | bit_MONSTER_DATA +bit_MONSTER_DATA | CYCLER_DATA +bit_LIGHT_DATA +path_corner_data +bit_MONSTER_DATA | wildcard_data +bit_MONSTER_DATA | bit_GROUP_DATA +boid_flock_data +boid_data +CYCLER_DATA +bit_ITEM_DATA +bit_ITEM_DATA | func_hud_data +bit_TOGGLE_DATA | bit_ITEM_DATA +EOFFSET +env_sound_data +env_sound_data +push_trigger_data +*/ + +#define TRACER_FREQ 4 // Tracers fire every 4 bullets + +typedef struct _SelAmmo +{ + BYTE Ammo1Type; + BYTE Ammo1; + BYTE Ammo2Type; + BYTE Ammo2; +} SelAmmo; + + +// this moved here from world.cpp, to allow classes to be derived from it +//======================= +// CWorld +// +// This spawns first when each level begins. +//======================= +class CWorld : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); +}; diff --git a/hlsdk/dlls/cdll_dll.h b/hlsdk/dlls/cdll_dll.h new file mode 100644 index 00000000..920fa290 --- /dev/null +++ b/hlsdk/dlls/cdll_dll.h @@ -0,0 +1,46 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cdll_dll.h + +// this file is included by both the game-dll and the client-dll, + +#ifndef CDLL_DLL_H +#define CDLL_DLL_H + +#define MAX_WEAPONS 32 // ??? + +#define MAX_WEAPON_SLOTS 5 // hud item selection slots +#define MAX_ITEM_TYPES 6 // hud item selection slots + +#define MAX_ITEMS 5 // 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 MAX_AMMO_TYPES 32 // ??? +#define MAX_AMMO_SLOTS 32 // not really slots + +#define HUD_PRINTNOTIFY 1 +#define HUD_PRINTCONSOLE 2 +#define HUD_PRINTTALK 3 +#define HUD_PRINTCENTER 4 + + +#define WEAPON_SUIT 31 + +#endif diff --git a/hlsdk/dlls/client.cpp b/hlsdk/dlls/client.cpp new file mode 100644 index 00000000..7b51e261 --- /dev/null +++ b/hlsdk/dlls/client.cpp @@ -0,0 +1,1727 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Robin, 4-22-98: Moved set_suicide_frame() here from player.cpp to allow us to +// have one without a hardcoded player.mdl in tf_client.cpp + +/* + +===== client.cpp ======================================================== + + client/server game specific stuff + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "player.h" +#include "spectator.h" +#include "client.h" +#include "soundent.h" +#include "gamerules.h" +#include "game.h" +#include "customentity.h" +#include "weapons.h" +#include "weaponinfo.h" +#include "usercmd.h" +#include "netadr.h" + +extern DLL_GLOBAL ULONG g_ulModelIndexPlayer; +extern DLL_GLOBAL BOOL g_fGameOver; +extern DLL_GLOBAL int g_iSkillLevel; +extern DLL_GLOBAL ULONG g_ulFrameCount; + +extern void CopyToBodyQue(entvars_t* pev); +extern int giPrecacheGrunt; +extern int gmsgSayText; + +extern int g_teamplay; + +void LinkUserMessages( void ); + +/* + * used by kill command and disconnect command + * ROBIN: Moved here from player.cpp, to allow multiple player models + */ +void set_suicide_frame(entvars_t* pev) +{ + if (!FStrEq(STRING(pev->model), "models/player.mdl")) + return; // allready gibbed + +// pev->frame = $deatha11; + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_TOSS; + pev->deadflag = DEAD_DEAD; + pev->nextthink = -1; +} + + +/* +=========== +ClientConnect + +called when a player connects to a server +============ +*/ +BOOL ClientConnect( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) +{ + return g_pGameRules->ClientConnected( pEntity, pszName, pszAddress, szRejectReason ); + +// a client connecting during an intermission can cause problems +// if (intermission_running) +// ExitIntermission (); + +} + + +/* +=========== +ClientDisconnect + +called when a player disconnects from a server + +GLOBALS ASSUMED SET: g_fGameOver +============ +*/ +void ClientDisconnect( edict_t *pEntity ) +{ + if (g_fGameOver) + return; + + char text[256]; + sprintf( text, "- %s has left the game\n", STRING(pEntity->v.netname) ); + MESSAGE_BEGIN( MSG_ALL, gmsgSayText, NULL ); + WRITE_BYTE( ENTINDEX(pEntity) ); + WRITE_STRING( text ); + MESSAGE_END(); + + CSound *pSound; + pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( pEntity ) ); + { + // since this client isn't around to think anymore, reset their sound. + if ( pSound ) + { + pSound->Reset(); + } + } + +// since the edict doesn't get deleted, fix it so it doesn't interfere. + pEntity->v.takedamage = DAMAGE_NO;// don't attract autoaim + pEntity->v.solid = SOLID_NOT;// nonsolid + UTIL_SetOrigin ( &pEntity->v, pEntity->v.origin ); + + g_pGameRules->ClientDisconnected( pEntity ); +} + + +// called by ClientKill and DeadThink +void respawn(entvars_t* pev, BOOL fCopyCorpse) +{ + if (gpGlobals->coop || gpGlobals->deathmatch) + { + if ( fCopyCorpse ) + { + // make a copy of the dead body for appearances sake + CopyToBodyQue(pev); + } + + // respawn player + GetClassPtr( (CBasePlayer *)pev)->Spawn( ); + } + else + { // restart the entire server + SERVER_COMMAND("reload\n"); + } +} + +/* +============ +ClientKill + +Player entered the suicide command + +GLOBALS ASSUMED SET: g_ulModelIndexPlayer +============ +*/ +void ClientKill( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + + CBasePlayer *pl = (CBasePlayer*) CBasePlayer::Instance( pev ); + + if ( pl->m_fNextSuicideTime > gpGlobals->time ) + return; // prevent suiciding too ofter + + pl->m_fNextSuicideTime = gpGlobals->time + 1; // don't let them suicide for 5 seconds after suiciding + + // have the player kill themself + pev->health = 0; + pl->Killed( pev, GIB_NEVER ); + +// pev->modelindex = g_ulModelIndexPlayer; +// pev->frags -= 2; // extra penalty +// respawn( pev ); +} + +/* +=========== +ClientPutInServer + +called each time a player is spawned +============ +*/ +void ClientPutInServer( edict_t *pEntity ) +{ + CBasePlayer *pPlayer; + + entvars_t *pev = &pEntity->v; + + pPlayer = GetClassPtr((CBasePlayer *)pev); + pPlayer->SetCustomDecalFrames(-1); // Assume none; + + // Allocate a CBasePlayer for pev, and call spawn + pPlayer->Spawn() ; + + // Reset interpolation during first frame + pPlayer->pev->effects |= EF_NOINTERP; +} + +#include "voice_gamemgr.h" +extern CVoiceGameMgr g_VoiceGameMgr; + +//// HOST_SAY +// String comes in as +// say blah blah blah +// or as +// blah blah blah +// +void Host_Say( edict_t *pEntity, int teamonly ) +{ + CBasePlayer *client; + int j; + char *p; + char text[128]; + char szTemp[256]; + const char *cpSay = "say"; + const char *cpSayTeam = "say_team"; + const char *pcmd = CMD_ARGV(0); + + // We can get a raw string now, without the "say " prepended + if ( CMD_ARGC() == 0 ) + return; + + entvars_t *pev = &pEntity->v; + CBasePlayer* player = GetClassPtr((CBasePlayer *)pev); + + //Not yet. + if ( player->m_flNextChatTime > gpGlobals->time ) + return; + + if ( !stricmp( pcmd, cpSay) || !stricmp( pcmd, cpSayTeam ) ) + { + if ( CMD_ARGC() >= 2 ) + { + p = (char *)CMD_ARGS(); + } + else + { + // say with a blank message, nothing to do + return; + } + } + else // Raw text, need to prepend argv[0] + { + if ( CMD_ARGC() >= 2 ) + { + sprintf( szTemp, "%s %s", ( char * )pcmd, (char *)CMD_ARGS() ); + } + else + { + // Just a one word command, use the first word...sigh + sprintf( szTemp, "%s", ( char * )pcmd ); + } + p = szTemp; + } + +// remove quotes if present + if (*p == '"') + { + p++; + p[strlen(p)-1] = 0; + } + +// make sure the text has content + char *pc = NULL; + for ( pc = p; pc != NULL && *pc != 0; pc++ ) + { + if ( isprint( *pc ) && !isspace( *pc ) ) + { + pc = NULL; // we've found an alphanumeric character, so text is valid + break; + } + } + if ( pc != NULL ) + return; // no character found, so say nothing + +// turn on color set 2 (color on, no sound) + if ( teamonly ) + sprintf( text, "%c(TEAM) %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 ) + p[j] = 0; + + strcat( text, p ); + strcat( text, "\n" ); + + + player->m_flNextChatTime = gpGlobals->time + CHAT_INTERVAL; + + // loop through all players + // Start with the first player. + // 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; + + if ( !(client->IsNetClient()) ) // Not a client ? (should never be true) + continue; + + // can the receiver hear the sender? or has he muted him? + if ( g_VoiceGameMgr.PlayerHasBlockedPlayer( client, player ) ) + 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(); + + } + + // print to the sending client + MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, &pEntity->v ); + WRITE_BYTE( ENTINDEX(pEntity) ); + WRITE_STRING( text ); + MESSAGE_END(); + + // echo to server console + g_engfuncs.pfnServerPrint( text ); + + char * temp; + if ( teamonly ) + temp = "say_team"; + else + temp = "say"; + + // team match? + if ( g_teamplay ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" %s \"%s\"\n", + STRING( pEntity->v.netname ), + GETPLAYERUSERID( pEntity ), + GETPLAYERAUTHID( pEntity ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pEntity ), "model" ), + temp, + p ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" %s \"%s\"\n", + STRING( pEntity->v.netname ), + GETPLAYERUSERID( pEntity ), + GETPLAYERAUTHID( pEntity ), + GETPLAYERUSERID( pEntity ), + temp, + p ); + } +} + + +/* +=========== +ClientCommand +called each time a player uses a "cmd" command +============ +*/ +extern float g_flWeaponCheat; + +// Use CMD_ARGV, CMD_ARGV, and CMD_ARGC to get pointers the character string command. +void ClientCommand( edict_t *pEntity ) +{ + const char *pcmd = CMD_ARGV(0); + const char *pstr; + + // Is the client spawned yet? + if ( !pEntity->pvPrivateData ) + return; + + entvars_t *pev = &pEntity->v; + + if ( FStrEq(pcmd, "say" ) ) + { + Host_Say( pEntity, 0 ); + } + else if ( FStrEq(pcmd, "say_team" ) ) + { + Host_Say( pEntity, 1 ); + } + else if ( FStrEq(pcmd, "fullupdate" ) ) + { + GetClassPtr((CBasePlayer *)pev)->ForceClientDllUpdate(); + } + else if ( FStrEq(pcmd, "give" ) ) + { + if ( g_flWeaponCheat != 0.0) + { + int iszItem = ALLOC_STRING( CMD_ARGV(1) ); // Make a copy of the classname + GetClassPtr((CBasePlayer *)pev)->GiveNamedItem( STRING(iszItem) ); + } + } + + else if ( FStrEq(pcmd, "drop" ) ) + { + // player is dropping an item. + GetClassPtr((CBasePlayer *)pev)->DropPlayerItem((char *)CMD_ARGV(1)); + } + else if ( FStrEq(pcmd, "fov" ) ) + { + if ( g_flWeaponCheat && CMD_ARGC() > 1) + { + GetClassPtr((CBasePlayer *)pev)->m_iFOV = atoi( CMD_ARGV(1) ); + } + else + { + CLIENT_PRINTF( pEntity, print_console, UTIL_VarArgs( "\"fov\" is \"%d\"\n", (int)GetClassPtr((CBasePlayer *)pev)->m_iFOV ) ); + } + } + else if ( FStrEq(pcmd, "use" ) ) + { + GetClassPtr((CBasePlayer *)pev)->SelectItem((char *)CMD_ARGV(1)); + } + else if (((pstr = strstr(pcmd, "weapon_")) != NULL) && (pstr == pcmd)) + { + GetClassPtr((CBasePlayer *)pev)->SelectItem(pcmd); + } + else if (FStrEq(pcmd, "lastinv" )) + { + GetClassPtr((CBasePlayer *)pev)->SelectLastItem(); + } + else if ( FStrEq( pcmd, "spectate" ) && (pev->flags & FL_PROXY) ) // added for proxy support + { + CBasePlayer * pPlayer = GetClassPtr((CBasePlayer *)pev); + + edict_t *pentSpawnSpot = g_pGameRules->GetPlayerSpawnSpot( pPlayer ); + pPlayer->StartObserver( pev->origin, VARS(pentSpawnSpot)->angles); + } + else if ( g_pGameRules->ClientCommand( GetClassPtr((CBasePlayer *)pev), pcmd ) ) + { + // MenuSelect returns true only if the command is properly handled, so don't print a warning + } + else + { + // tell the user they entered an unknown command + char command[128]; + + // check the length of the command (prevents crash) + // max total length is 192 ...and we're adding a string below ("Unknown command: %s\n") + strncpy( command, pcmd, 127 ); + command[127] = '\0'; + + // tell the user they entered an unknown command + ClientPrint( &pEntity->v, HUD_PRINTCONSOLE, UTIL_VarArgs( "Unknown command: %s\n", command ) ); + } +} + + +/* +======================== +ClientUserInfoChanged + +called after the player changes +userinfo - gives dll a chance to modify it before +it gets sent into the rest of the engine. +======================== +*/ +void ClientUserInfoChanged( edict_t *pEntity, char *infobuffer ) +{ + // Is the client spawned yet? + if ( !pEntity->pvPrivateData ) + return; + + // msg everyone if someone changes their name, and it isn't the first time (changing no name to current name) + if ( pEntity->v.netname && STRING(pEntity->v.netname)[0] != 0 && !FStrEq( STRING(pEntity->v.netname), g_engfuncs.pfnInfoKeyValue( infobuffer, "name" )) ) + { + char sName[256]; + char *pName = g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ); + strncpy( sName, pName, sizeof(sName) - 1 ); + sName[ sizeof(sName) - 1 ] = '\0'; + + // First parse the name and remove any %'s + for ( char *pApersand = sName; pApersand != NULL && *pApersand != 0; pApersand++ ) + { + // Replace it with a space + if ( *pApersand == '%' ) + *pApersand = ' '; + } + + // Set the name + g_engfuncs.pfnSetClientKeyValue( ENTINDEX(pEntity), infobuffer, "name", sName ); + + char text[256]; + sprintf( text, "* %s changed name to %s\n", STRING(pEntity->v.netname), g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ) ); + MESSAGE_BEGIN( MSG_ALL, gmsgSayText, NULL ); + WRITE_BYTE( ENTINDEX(pEntity) ); + WRITE_STRING( text ); + MESSAGE_END(); + + // team match? + if ( g_teamplay ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" changed name to \"%s\"\n", + STRING( pEntity->v.netname ), + GETPLAYERUSERID( pEntity ), + GETPLAYERAUTHID( pEntity ), + g_engfuncs.pfnInfoKeyValue( infobuffer, "model" ), + g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ) ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" changed name to \"%s\"\n", + STRING( pEntity->v.netname ), + GETPLAYERUSERID( pEntity ), + GETPLAYERAUTHID( pEntity ), + GETPLAYERUSERID( pEntity ), + g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ) ); + } + } + + g_pGameRules->ClientUserInfoChanged( GetClassPtr((CBasePlayer *)&pEntity->v), infobuffer ); +} + +static int g_serveractive = 0; + +void ServerDeactivate( void ) +{ + // It's possible that the engine will call this function more times than is necessary + // Therefore, only run it one time for each call to ServerActivate + if ( g_serveractive != 1 ) + { + return; + } + + g_serveractive = 0; + + // Peform any shutdown operations here... + // +} + +void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) +{ + int i; + CBaseEntity *pClass; + + // Every call to ServerActivate should be matched by a call to ServerDeactivate + g_serveractive = 1; + + // Clients have not been initialized yet + for ( i = 0; i < edictCount; i++ ) + { + if ( pEdictList[i].free ) + continue; + + // Clients aren't necessarily initialized until ClientPutInServer() + if ( i < clientMax || !pEdictList[i].pvPrivateData ) + continue; + + pClass = CBaseEntity::Instance( &pEdictList[i] ); + // Activate this entity if it's got a class & isn't dormant + if ( pClass && !(pClass->pev->flags & FL_DORMANT) ) + { + pClass->Activate(); + } + else + { + ALERT( at_console, "Can't instance %s\n", STRING(pEdictList[i].v.classname) ); + } + } + + // Link user messages here to make sure first client can get them... + LinkUserMessages(); +} + + +/* +================ +PlayerPreThink + +Called every frame before physics are run +================ +*/ +void PlayerPreThink( edict_t *pEntity ) +{ + CBasePlayer *pPlayer = (CBasePlayer *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->PreThink( ); +} + +/* +================ +PlayerPostThink + +Called every frame after physics are run +================ +*/ +void PlayerPostThink( edict_t *pEntity ) +{ + CBasePlayer *pPlayer = (CBasePlayer *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->PostThink( ); +} + + + +void ParmsNewLevel( void ) +{ +} + + +void ParmsChangeLevel( void ) +{ + // retrieve the pointer to the save data + SAVERESTOREDATA *pSaveData = (SAVERESTOREDATA *)gpGlobals->pSaveData; + + if ( pSaveData ) + pSaveData->connectionCount = BuildChangeList( pSaveData->levelList, MAX_LEVEL_CONNECTIONS ); +} + + +// +// GLOBALS ASSUMED SET: g_ulFrameCount +// +void StartFrame( void ) +{ + if ( g_pGameRules ) + g_pGameRules->Think(); + + if ( g_fGameOver ) + return; + + gpGlobals->teamplay = teamplay.value; + g_ulFrameCount++; +} + + +void ClientPrecache( void ) +{ + // setup precaches always needed + PRECACHE_SOUND("player/sprayer.wav"); // spray paint sound for PreAlpha + + // PRECACHE_SOUND("player/pl_jumpland2.wav"); // UNDONE: play 2x step sound + + PRECACHE_SOUND("player/pl_fallpain2.wav"); + PRECACHE_SOUND("player/pl_fallpain3.wav"); + + PRECACHE_SOUND("player/pl_step1.wav"); // walk on concrete + PRECACHE_SOUND("player/pl_step2.wav"); + PRECACHE_SOUND("player/pl_step3.wav"); + PRECACHE_SOUND("player/pl_step4.wav"); + + PRECACHE_SOUND("common/npc_step1.wav"); // NPC walk on concrete + PRECACHE_SOUND("common/npc_step2.wav"); + PRECACHE_SOUND("common/npc_step3.wav"); + PRECACHE_SOUND("common/npc_step4.wav"); + + PRECACHE_SOUND("player/pl_metal1.wav"); // walk on metal + PRECACHE_SOUND("player/pl_metal2.wav"); + PRECACHE_SOUND("player/pl_metal3.wav"); + PRECACHE_SOUND("player/pl_metal4.wav"); + + PRECACHE_SOUND("player/pl_dirt1.wav"); // walk on dirt + PRECACHE_SOUND("player/pl_dirt2.wav"); + PRECACHE_SOUND("player/pl_dirt3.wav"); + PRECACHE_SOUND("player/pl_dirt4.wav"); + + PRECACHE_SOUND("player/pl_duct1.wav"); // walk in duct + PRECACHE_SOUND("player/pl_duct2.wav"); + PRECACHE_SOUND("player/pl_duct3.wav"); + PRECACHE_SOUND("player/pl_duct4.wav"); + + PRECACHE_SOUND("player/pl_grate1.wav"); // walk on grate + PRECACHE_SOUND("player/pl_grate2.wav"); + PRECACHE_SOUND("player/pl_grate3.wav"); + PRECACHE_SOUND("player/pl_grate4.wav"); + + PRECACHE_SOUND("player/pl_slosh1.wav"); // walk in shallow water + PRECACHE_SOUND("player/pl_slosh2.wav"); + PRECACHE_SOUND("player/pl_slosh3.wav"); + PRECACHE_SOUND("player/pl_slosh4.wav"); + + PRECACHE_SOUND("player/pl_tile1.wav"); // walk on tile + PRECACHE_SOUND("player/pl_tile2.wav"); + PRECACHE_SOUND("player/pl_tile3.wav"); + PRECACHE_SOUND("player/pl_tile4.wav"); + PRECACHE_SOUND("player/pl_tile5.wav"); + + PRECACHE_SOUND("player/pl_swim1.wav"); // breathe bubbles + PRECACHE_SOUND("player/pl_swim2.wav"); + PRECACHE_SOUND("player/pl_swim3.wav"); + PRECACHE_SOUND("player/pl_swim4.wav"); + + PRECACHE_SOUND("player/pl_ladder1.wav"); // climb ladder rung + PRECACHE_SOUND("player/pl_ladder2.wav"); + PRECACHE_SOUND("player/pl_ladder3.wav"); + PRECACHE_SOUND("player/pl_ladder4.wav"); + + PRECACHE_SOUND("player/pl_wade1.wav"); // wade in water + PRECACHE_SOUND("player/pl_wade2.wav"); + PRECACHE_SOUND("player/pl_wade3.wav"); + PRECACHE_SOUND("player/pl_wade4.wav"); + + PRECACHE_SOUND("debris/wood1.wav"); // hit wood texture + PRECACHE_SOUND("debris/wood2.wav"); + PRECACHE_SOUND("debris/wood3.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( SOUND_FLASHLIGHT_ON ); + PRECACHE_SOUND( SOUND_FLASHLIGHT_OFF ); + +// player gib sounds + PRECACHE_SOUND("common/bodysplat.wav"); + +// player pain sounds + PRECACHE_SOUND("player/pl_pain2.wav"); + PRECACHE_SOUND("player/pl_pain4.wav"); + PRECACHE_SOUND("player/pl_pain5.wav"); + PRECACHE_SOUND("player/pl_pain6.wav"); + PRECACHE_SOUND("player/pl_pain7.wav"); + + PRECACHE_MODEL("models/player.mdl"); + + // hud sounds + + 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"); + + + // geiger sounds + + PRECACHE_SOUND("player/geiger6.wav"); + PRECACHE_SOUND("player/geiger5.wav"); + PRECACHE_SOUND("player/geiger4.wav"); + PRECACHE_SOUND("player/geiger3.wav"); + PRECACHE_SOUND("player/geiger2.wav"); + PRECACHE_SOUND("player/geiger1.wav"); + + if (giPrecacheGrunt) + UTIL_PrecacheOther("monster_human_grunt"); +} + +/* +=============== +GetGameDescription + +Returns the descriptive name of this .dll. E.g., Half-Life, or Team Fortress 2 +=============== +*/ +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"; +} + +/* +================ +Sys_Error + +Engine is going to shut down, allows setting a breakpoint in game .dll to catch that occasion +================ +*/ +void Sys_Error( const char *error_string ) +{ + // Default case, do nothing. MOD AUTHORS: Add code ( e.g., _asm { int 3 }; here to cause a breakpoint for debugging your game .dlls +} + +/* +================ +PlayerCustomization + +A new player customization has been registered on the server +UNDONE: This only sets the # of frames of the spray can logo +animation right now. +================ +*/ +void PlayerCustomization( edict_t *pEntity, customization_t *pCust ) +{ + CBasePlayer *pPlayer = (CBasePlayer *)GET_PRIVATE(pEntity); + + if (!pPlayer) + { + ALERT(at_console, "PlayerCustomization: Couldn't get player!\n"); + return; + } + + if (!pCust) + { + ALERT(at_console, "PlayerCustomization: NULL customization!\n"); + return; + } + + switch (pCust->resource.type) + { + case t_decal: + pPlayer->SetCustomDecalFrames(pCust->nUserData2); // Second int is max # of frames. + break; + case t_sound: + case t_skin: + case t_model: + // Ignore for now. + break; + default: + ALERT(at_console, "PlayerCustomization: Unknown customization type!\n"); + break; + } +} + +/* +================ +SpectatorConnect + +A spectator has joined the game +================ +*/ +void SpectatorConnect( edict_t *pEntity ) +{ + CBaseSpectator *pPlayer = (CBaseSpectator *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->SpectatorConnect( ); +} + +/* +================ +SpectatorConnect + +A spectator has left the game +================ +*/ +void SpectatorDisconnect( edict_t *pEntity ) +{ + CBaseSpectator *pPlayer = (CBaseSpectator *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->SpectatorDisconnect( ); +} + +/* +================ +SpectatorConnect + +A spectator has sent a usercmd +================ +*/ +void SpectatorThink( edict_t *pEntity ) +{ + CBaseSpectator *pPlayer = (CBaseSpectator *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->SpectatorThink( ); +} + +//////////////////////////////////////////////////////// +// PAS and PVS routines for client messaging +// + +/* +================ +SetupVisibility + +A client can have a separate "view entity" indicating that his/her view should depend on the origin of that +view entity. If that's the case, then pViewEntity will be non-NULL and will be used. Otherwise, the current +entity's origin is used. Either is offset by the view_ofs to get the eye position. + +From the eye position, we set up the PAS and PVS to use for filtering network messages to the client. At this point, we could + override the actual PAS or PVS values, or use a different origin. + +NOTE: Do not cache the values of pas and pvs, as they depend on reusable memory in the engine, they are only good for this one frame +================ +*/ +void SetupVisibility( edict_t *pViewEntity, edict_t *pClient, unsigned char **pvs, unsigned char **pas ) +{ + Vector org; + edict_t *pView = pClient; + + // Find the client's PVS + if ( pViewEntity ) + { + pView = pViewEntity; + } + + if ( pClient->v.flags & FL_PROXY ) + { + *pvs = NULL; // the spectator proxy sees + *pas = NULL; // and hears everything + return; + } + + org = pView->v.origin + pView->v.view_ofs; + if ( pView->v.flags & FL_DUCKING ) + { + org = org + ( VEC_HULL_MIN - VEC_DUCK_HULL_MIN ); + } + + *pvs = ENGINE_SET_PVS ( (float *)&org ); + *pas = ENGINE_SET_PAS ( (float *)&org ); +} + +#include "entity_state.h" + +/* +AddToFullPack + +Return 1 if the entity state has been filled in for the ent and the entity will be propagated to the client, 0 otherwise + +state is the server maintained copy of the state info that is transmitted to the client +a MOD could alter values copied into state to send the "host" a different look for a particular entity update, etc. +e and ent are the entity that is being added to the update, if 1 is returned +host is the player's edict of the player whom we are sending the update to +player is 1 if the ent/e is a player and 0 otherwise +pSet is either the PAS or PVS that we previous set up. We can use it to ask the engine to filter the entity against the PAS or PVS. +we could also use the pas/ pvs that we set in SetupVisibility, if we wanted to. Caching the value is valid in that case, but still only for the current frame +*/ +int AddToFullPack( struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet ) +{ + int i; + + // don't send if flagged for NODRAW and it's not the host getting the message + if ( ( ent->v.effects == EF_NODRAW ) && + ( ent != host ) ) + return 0; + + // Ignore ents without valid / visible models + if ( !ent->v.modelindex || !STRING( ent->v.model ) ) + return 0; + + // Don't send spectators to other players + if ( ( ent->v.flags & FL_SPECTATOR ) && ( ent != host ) ) + { + return 0; + } + + // Ignore if not the host and not touching a PVS/PAS leaf + // If pSet is NULL, then the test will always succeed and the entity will be added to the update + if ( ent != host ) + { + if ( !ENGINE_CHECK_VISIBILITY( (const struct edict_s *)ent, pSet ) ) + { + return 0; + } + } + + + // Don't send entity to local client if the client says it's predicting the entity itself. + if ( ent->v.flags & FL_SKIPLOCALHOST ) + { + if ( ( hostflags & 1 ) && ( ent->v.owner == host ) ) + return 0; + } + + if ( host->v.groupinfo ) + { + UTIL_SetGroupTrace( host->v.groupinfo, GROUP_OP_AND ); + + // Should always be set, of course + if ( ent->v.groupinfo ) + { + if ( g_groupop == GROUP_OP_AND ) + { + if ( !(ent->v.groupinfo & host->v.groupinfo ) ) + return 0; + } + else if ( g_groupop == GROUP_OP_NAND ) + { + if ( ent->v.groupinfo & host->v.groupinfo ) + return 0; + } + } + + UTIL_UnsetGroupTrace(); + } + + memset( state, 0, sizeof( *state ) ); + + // Assign index so we can track this entity from frame to frame and + // delta from it. + state->number = e; + state->entityType = ENTITY_NORMAL; + + // Flag custom entities. + if ( ent->v.flags & FL_CUSTOMENTITY ) + { + state->entityType = ENTITY_BEAM; + } + + // + // Copy state data + // + + // Round animtime to nearest millisecond + state->animtime = (int)(1000.0 * ent->v.animtime ) / 1000.0; + + memcpy( state->origin, ent->v.origin, 3 * sizeof( float ) ); + memcpy( state->angles, ent->v.angles, 3 * sizeof( float ) ); + memcpy( state->mins, ent->v.mins, 3 * sizeof( float ) ); + memcpy( state->maxs, ent->v.maxs, 3 * sizeof( float ) ); + + memcpy( state->startpos, ent->v.startpos, 3 * sizeof( float ) ); + memcpy( state->endpos, ent->v.endpos, 3 * sizeof( float ) ); + + state->impacttime = ent->v.impacttime; + state->starttime = ent->v.starttime; + + state->modelindex = ent->v.modelindex; + + state->frame = ent->v.frame; + + state->skin = ent->v.skin; + state->effects = ent->v.effects; + + // This non-player entity is being moved by the game .dll and not the physics simulation system + // make sure that we interpolate it's position on the client if it moves + if ( !player && + ent->v.animtime && + ent->v.velocity[ 0 ] == 0 && + ent->v.velocity[ 1 ] == 0 && + ent->v.velocity[ 2 ] == 0 ) + { + state->eflags |= EFLAG_SLERP; + } + + state->scale = ent->v.scale; + state->solid = ent->v.solid; + state->colormap = ent->v.colormap; + + state->movetype = ent->v.movetype; + state->sequence = ent->v.sequence; + state->framerate = ent->v.framerate; + state->body = ent->v.body; + + for (i = 0; i < 4; i++) + { + state->controller[i] = ent->v.controller[i]; + } + + for (i = 0; i < 2; i++) + { + state->blending[i] = ent->v.blending[i]; + } + + state->rendermode = ent->v.rendermode; + state->renderamt = static_cast(ent->v.renderamt); + state->renderfx = ent->v.renderfx; + state->rendercolor.r = static_cast(ent->v.rendercolor.x); + state->rendercolor.g = static_cast(ent->v.rendercolor.y); + state->rendercolor.b = static_cast(ent->v.rendercolor.z); + + state->aiment = 0; + if ( ent->v.aiment ) + { + state->aiment = ENTINDEX( ent->v.aiment ); + } + + state->owner = 0; + if ( ent->v.owner ) + { + int owner = ENTINDEX( ent->v.owner ); + + // Only care if owned by a player + if ( owner >= 1 && owner <= gpGlobals->maxClients ) + { + state->owner = owner; + } + } + + // HACK: Somewhat... + // Class is overridden for non-players to signify a breakable glass object ( sort of a class? ) + if ( !player ) + { + state->playerclass = ent->v.playerclass; + } + + // Special stuff for players only + if ( player ) + { + memcpy( state->basevelocity, ent->v.basevelocity, 3 * sizeof( float ) ); + + state->weaponmodel = MODEL_INDEX( STRING( ent->v.weaponmodel ) ); + state->gaitsequence = ent->v.gaitsequence; + state->spectator = ent->v.flags & FL_SPECTATOR; + state->friction = ent->v.friction; + + state->gravity = ent->v.gravity; +// state->team = ent->v.team; +// + state->usehull = ( ent->v.flags & FL_DUCKING ) ? 1 : 0; + state->health = static_cast(ent->v.health); + } + + return 1; +} + +// defaults for clientinfo messages +#define DEFAULT_VIEWHEIGHT 28 + +/* +=================== +CreateBaseline + +Creates baselines used for network encoding, especially for player data since players are not spawned until connect time. +=================== +*/ +void CreateBaseline( int player, int eindex, struct entity_state_s *baseline, struct edict_s *entity, int playermodelindex, vec3_t player_mins, vec3_t player_maxs ) +{ + baseline->origin = entity->v.origin; + baseline->angles = entity->v.angles; + baseline->frame = entity->v.frame; + baseline->skin = (short)entity->v.skin; + + // render information + baseline->rendermode = (byte)entity->v.rendermode; + baseline->renderamt = (byte)entity->v.renderamt; + baseline->rendercolor.r = (byte)entity->v.rendercolor.x; + baseline->rendercolor.g = (byte)entity->v.rendercolor.y; + baseline->rendercolor.b = (byte)entity->v.rendercolor.z; + baseline->renderfx = (byte)entity->v.renderfx; + + if ( player ) + { + baseline->mins = player_mins; + baseline->maxs = player_maxs; + + baseline->colormap = eindex; + baseline->modelindex = playermodelindex; + baseline->friction = 1.0; + baseline->movetype = MOVETYPE_WALK; + + baseline->scale = entity->v.scale; + baseline->solid = SOLID_SLIDEBOX; + baseline->framerate = 1.0; + baseline->gravity = 1.0; + + } + else + { + baseline->mins = entity->v.mins; + baseline->maxs = entity->v.maxs; + + baseline->colormap = 0; + baseline->modelindex = entity->v.modelindex;//SV_ModelIndex(pr_strings + entity->v.model); + baseline->movetype = entity->v.movetype; + + baseline->scale = entity->v.scale; + baseline->solid = entity->v.solid; + baseline->framerate = entity->v.framerate; + baseline->gravity = entity->v.gravity; + } +} + +typedef struct +{ + char name[32]; + int field; +} entity_field_alias_t; + +#define FIELD_ORIGIN0 0 +#define FIELD_ORIGIN1 1 +#define FIELD_ORIGIN2 2 +#define FIELD_ANGLES0 3 +#define FIELD_ANGLES1 4 +#define FIELD_ANGLES2 5 + +static entity_field_alias_t entity_field_alias[]= +{ + { "origin[0]", 0 }, + { "origin[1]", 0 }, + { "origin[2]", 0 }, + { "angles[0]", 0 }, + { "angles[1]", 0 }, + { "angles[2]", 0 }, +}; + +void Entity_FieldInit( struct delta_s *pFields ) +{ + entity_field_alias[ FIELD_ORIGIN0 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ORIGIN0 ].name ); + entity_field_alias[ FIELD_ORIGIN1 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ORIGIN1 ].name ); + entity_field_alias[ FIELD_ORIGIN2 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ORIGIN2 ].name ); + entity_field_alias[ FIELD_ANGLES0 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ANGLES0 ].name ); + entity_field_alias[ FIELD_ANGLES1 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ANGLES1 ].name ); + entity_field_alias[ FIELD_ANGLES2 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ANGLES2 ].name ); +} + +/* +================== +Entity_Encode + +Callback for sending entity_state_t info over network. +FIXME: Move to script +================== +*/ +void Entity_Encode( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) +{ + entity_state_t *f, *t; + int localplayer = 0; + static int initialized = 0; + + if ( !initialized ) + { + Entity_FieldInit( pFields ); + initialized = 1; + } + + f = (entity_state_t *)from; + t = (entity_state_t *)to; + + // Never send origin to local player, it's sent with more resolution in clientdata_t structure + localplayer = ( t->number - 1 ) == ENGINE_CURRENT_PLAYER(); + if ( localplayer ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + + if ( ( t->impacttime != 0 ) && ( t->starttime != 0 ) ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ANGLES0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ANGLES1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ANGLES2 ].field ); + } + + if ( ( t->movetype == MOVETYPE_FOLLOW ) && + ( t->aiment != 0 ) ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + else if ( t->aiment != f->aiment ) + { + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } +} + +static entity_field_alias_t player_field_alias[]= +{ + { "origin[0]", 0 }, + { "origin[1]", 0 }, + { "origin[2]", 0 }, +}; + +void Player_FieldInit( struct delta_s *pFields ) +{ + player_field_alias[ FIELD_ORIGIN0 ].field = DELTA_FINDFIELD( pFields, player_field_alias[ FIELD_ORIGIN0 ].name ); + player_field_alias[ FIELD_ORIGIN1 ].field = DELTA_FINDFIELD( pFields, player_field_alias[ FIELD_ORIGIN1 ].name ); + player_field_alias[ FIELD_ORIGIN2 ].field = DELTA_FINDFIELD( pFields, player_field_alias[ FIELD_ORIGIN2 ].name ); +} + +/* +================== +Player_Encode + +Callback for sending entity_state_t for players info over network. +================== +*/ +void Player_Encode( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) +{ + entity_state_t *f, *t; + int localplayer = 0; + static int initialized = 0; + + if ( !initialized ) + { + Player_FieldInit( pFields ); + initialized = 1; + } + + f = (entity_state_t *)from; + t = (entity_state_t *)to; + + // Never send origin to local player, it's sent with more resolution in clientdata_t structure + localplayer = ( t->number - 1 ) == ENGINE_CURRENT_PLAYER(); + if ( localplayer ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + + if ( ( t->movetype == MOVETYPE_FOLLOW ) && + ( t->aiment != 0 ) ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + else if ( t->aiment != f->aiment ) + { + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } +} + +#define CUSTOMFIELD_ORIGIN0 0 +#define CUSTOMFIELD_ORIGIN1 1 +#define CUSTOMFIELD_ORIGIN2 2 +#define CUSTOMFIELD_ANGLES0 3 +#define CUSTOMFIELD_ANGLES1 4 +#define CUSTOMFIELD_ANGLES2 5 +#define CUSTOMFIELD_SKIN 6 +#define CUSTOMFIELD_SEQUENCE 7 +#define CUSTOMFIELD_ANIMTIME 8 + +entity_field_alias_t custom_entity_field_alias[]= +{ + { "origin[0]", 0 }, + { "origin[1]", 0 }, + { "origin[2]", 0 }, + { "angles[0]", 0 }, + { "angles[1]", 0 }, + { "angles[2]", 0 }, + { "skin", 0 }, + { "sequence", 0 }, + { "animtime", 0 }, +}; + +void Custom_Entity_FieldInit( struct delta_s *pFields ) +{ + custom_entity_field_alias[ CUSTOMFIELD_ORIGIN0 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN0 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ORIGIN1 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN1 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ORIGIN2 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN2 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANGLES0 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES0 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANGLES1 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES1 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANGLES2 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES2 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_SKIN ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_SKIN ].name ); + custom_entity_field_alias[ CUSTOMFIELD_SEQUENCE ].field= DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_SEQUENCE ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANIMTIME ].field= DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANIMTIME ].name ); +} + +/* +================== +Custom_Encode + +Callback for sending entity_state_t info ( for custom entities ) over network. +FIXME: Move to script +================== +*/ +void Custom_Encode( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) +{ + entity_state_t *f, *t; + int beamType; + static int initialized = 0; + + if ( !initialized ) + { + Custom_Entity_FieldInit( pFields ); + initialized = 1; + } + + f = (entity_state_t *)from; + t = (entity_state_t *)to; + + beamType = t->rendermode & 0x0f; + + if ( beamType != BEAM_POINTS && beamType != BEAM_ENTPOINT ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN2 ].field ); + } + + if ( beamType != BEAM_POINTS ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES0 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES1 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES2 ].field ); + } + + if ( beamType != BEAM_ENTS && beamType != BEAM_ENTPOINT ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_SKIN ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_SEQUENCE ].field ); + } + + // animtime is compared by rounding first + // see if we really shouldn't actually send it + if ( (int)f->animtime == (int)t->animtime ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANIMTIME ].field ); + } +} + +/* +================= +RegisterEncoders + +Allows game .dll to override network encoding of certain types of entities and tweak values, etc. +================= +*/ +void RegisterEncoders( void ) +{ + DELTA_ADDENCODER( "Entity_Encode", Entity_Encode ); + DELTA_ADDENCODER( "Custom_Encode", Custom_Encode ); + DELTA_ADDENCODER( "Player_Encode", Player_Encode ); +} + +int GetWeaponData( struct edict_s *player, struct weapon_data_s *info ) +{ +#if defined( CLIENT_WEAPONS ) + int i; + weapon_data_t *item; + entvars_t *pev = &player->v; + CBasePlayer *pl = ( CBasePlayer *) CBasePlayer::Instance( pev ); + CBasePlayerWeapon *gun; + + ItemInfo II; + + memset( info, 0, 32 * sizeof( weapon_data_t ) ); + + if ( !pl ) + return 1; + + // go through all of the weapons and make a list of the ones to pack + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( pl->m_rgpPlayerItems[ i ] ) + { + // there's a weapon here. Should I pack it? + CBasePlayerItem *pPlayerItem = pl->m_rgpPlayerItems[ i ]; + + while ( pPlayerItem ) + { + gun = (CBasePlayerWeapon *)pPlayerItem->GetWeaponPtr(); + if ( gun && gun->UseDecrement() ) + { + // Get The ID. + memset( &II, 0, sizeof( II ) ); + gun->GetItemInfo( &II ); + + if ( II.iId >= 0 && II.iId < 32 ) + { + item = &info[ II.iId ]; + + item->m_iId = II.iId; + item->m_iClip = gun->m_iClip; + + item->m_flTimeWeaponIdle = max( gun->m_flTimeWeaponIdle, -0.001 ); + item->m_flNextPrimaryAttack = max( gun->m_flNextPrimaryAttack, -0.001 ); + item->m_flNextSecondaryAttack = max( gun->m_flNextSecondaryAttack, -0.001 ); + item->m_fInReload = gun->m_fInReload; + item->m_fInSpecialReload = gun->m_fInSpecialReload; + 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 ); + } + } + pPlayerItem = pPlayerItem->m_pNext; + } + } + } +#else + memset( info, 0, 32 * sizeof( weapon_data_t ) ); +#endif + return 1; +} + +/* +================= +UpdateClientData + +Data sent to current client only +engine sets cd to 0 before calling. +================= +*/ +void UpdateClientData ( const struct edict_s *ent, int sendweapons, struct clientdata_s *cd ) +{ + cd->flags = ent->v.flags; + cd->health = ent->v.health; + + cd->viewmodel = MODEL_INDEX( STRING( ent->v.viewmodel ) ); + + cd->waterlevel = ent->v.waterlevel; + cd->watertype = ent->v.watertype; + cd->weapons = ent->v.weapons; + + // Vectors + cd->origin = ent->v.origin; + cd->velocity = ent->v.velocity; + cd->view_ofs = ent->v.view_ofs; + cd->punchangle = ent->v.punchangle; + + cd->bInDuck = ent->v.bInDuck; + cd->flTimeStepSound = ent->v.flTimeStepSound; + cd->flDuckTime = ent->v.flDuckTime; + cd->flSwimTime = ent->v.flSwimTime; + cd->waterjumptime = static_cast(ent->v.teleport_time); + + strcpy( cd->physinfo, ENGINE_GETPHYSINFO( ent ) ); + + cd->maxspeed = ent->v.maxspeed; + cd->fov = ent->v.fov; + cd->weaponanim = ent->v.weaponanim; + + cd->pushmsec = ent->v.pushmsec; + +#if defined( CLIENT_WEAPONS ) + if ( sendweapons ) + { + entvars_t *pev = (entvars_t *)&ent->v; + CBasePlayer *pl = ( CBasePlayer *) CBasePlayer::Instance( pev ); + + 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; + cd->ammo_nails = pl->ammo_bolts; + cd->ammo_shells = pl->ammo_buckshot; + cd->ammo_rockets = pl->ammo_rockets; + cd->ammo_cells = pl->ammo_uranium; + cd->vuser2.x = pl->ammo_hornets; + + + if ( pl->m_pActiveItem ) + { + CBasePlayerWeapon *gun; + gun = (CBasePlayerWeapon *)pl->m_pActiveItem->GetWeaponPtr(); + if ( gun && gun->UseDecrement() ) + { + ItemInfo II; + memset( &II, 0, sizeof( II ) ); + gun->GetItemInfo( &II ); + + cd->m_iId = II.iId; + + cd->vuser3.z = gun->m_iSecondaryAmmoType; + 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; + } + } + } + } + } +#endif +} + +/* +================= +CmdStart + +We're about to run this usercmd for the specified player. We can set up groupinfo and masking here, etc. +This is the time to examine the usercmd for anything extra. This call happens even if think does not. +================= +*/ +void CmdStart( const edict_t *player, const struct usercmd_s *cmd, unsigned int random_seed ) +{ + entvars_t *pev = (entvars_t *)&player->v; + CBasePlayer *pl = ( CBasePlayer *) CBasePlayer::Instance( pev ); + + if( !pl ) + return; + + if ( pl->pev->groupinfo != 0 ) + { + UTIL_SetGroupTrace( pl->pev->groupinfo, GROUP_OP_AND ); + } + + pl->random_seed = random_seed; +} + +/* +================= +CmdEnd + +Each cmdstart is exactly matched with a cmd end, clean up any group trace flags, etc. here +================= +*/ +void CmdEnd ( const edict_t *player ) +{ + entvars_t *pev = (entvars_t *)&player->v; + CBasePlayer *pl = ( CBasePlayer *) CBasePlayer::Instance( pev ); + + if( !pl ) + return; + if ( pl->pev->groupinfo != 0 ) + { + UTIL_UnsetGroupTrace(); + } +} + +/* +================================ +ConnectionlessPacket + + Return 1 if the packet is valid. Set response_buffer_size if you want to send a response packet. Incoming, it holds the max + size of the response_buffer, so you must zero it out if you choose not to respond. +================================ +*/ +int ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ) +{ + // Zero it out since we aren't going to respond. + // If we wanted to response, we'd write data into response_buffer + *response_buffer_size = 0; + + // Since we don't listen for anything here, just respond that it's a bogus message + // If we didn't reject the message, we'd return 1 for success instead. + return 0; +} + +/* +================================ +GetHullBounds + + Engine calls this to enumerate player collision hulls, for prediction. Return 0 if the hullnumber doesn't exist. +================================ +*/ +int GetHullBounds( int hullnumber, float *mins, float *maxs ) +{ + int iret = 0; + + switch ( hullnumber ) + { + case 0: // Normal player + mins = VEC_HULL_MIN; + maxs = VEC_HULL_MAX; + iret = 1; + break; + case 1: // Crouched player + mins = VEC_DUCK_HULL_MIN; + maxs = VEC_DUCK_HULL_MAX; + iret = 1; + break; + case 2: // Point based hull + mins = Vector( 0, 0, 0 ); + maxs = Vector( 0, 0, 0 ); + iret = 1; + break; + } + + return iret; +} + +/* +================================ +CreateInstancedBaselines + +Create pseudo-baselines for items that aren't placed in the map at spawn time, but which are likely +to be created during play ( e.g., grenades, ammo packs, projectiles, corpses, etc. ) +================================ +*/ +void CreateInstancedBaselines ( void ) +{ + entity_state_t state; + + memset( &state, 0, sizeof( state ) ); + + // Create any additional baselines here for things like grendates, etc. + // iret = ENGINE_INSTANCE_BASELINE( pc->pev->classname, &state ); + + // Destroy objects. + //UTIL_Remove( pc ); +} + +/* +================================ +InconsistentFile + +One of the ENGINE_FORCE_UNMODIFIED files failed the consistency check for the specified player + Return 0 to allow the client to continue, 1 to force immediate disconnection ( with an optional disconnect message of up to 256 characters ) +================================ +*/ +int InconsistentFile( const edict_t *player, const char *filename, char *disconnect_message ) +{ + // Server doesn't care? + if ( CVAR_GET_FLOAT( "mp_consistency" ) != 1 ) + return 0; + + // Default behavior is to kick the player + sprintf( disconnect_message, "Server is enforcing file consistency for %s\n", filename ); + + // Kick now with specified disconnect message. + return 1; +} + +/* +================================ +AllowLagCompensation + + The game .dll should return 1 if lag compensation should be allowed ( could also just set + the sv_unlag cvar. + Most games right now should return 0, until client-side weapon prediction code is written + and tested for them ( note you can predict weapons, but not do lag compensation, too, + if you want. +================================ +*/ +int AllowLagCompensation( void ) +{ + return 1; +} diff --git a/hlsdk/dlls/client.h b/hlsdk/dlls/client.h new file mode 100644 index 00000000..1e66cc89 --- /dev/null +++ b/hlsdk/dlls/client.h @@ -0,0 +1,65 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef CLIENT_H +#define CLIENT_H + +extern void respawn( entvars_t* pev, BOOL fCopyCorpse ); +extern BOOL ClientConnect( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); +extern void ClientDisconnect( edict_t *pEntity ); +extern void ClientKill( edict_t *pEntity ); +extern void ClientPutInServer( edict_t *pEntity ); +extern void ClientCommand( edict_t *pEntity ); +extern void ClientUserInfoChanged( edict_t *pEntity, char *infobuffer ); +extern void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ); +extern void ServerDeactivate( void ); +extern void StartFrame( void ); +extern void PlayerPostThink( edict_t *pEntity ); +extern void PlayerPreThink( edict_t *pEntity ); +extern void ParmsNewLevel( void ); +extern void ParmsChangeLevel( void ); + +extern void ClientPrecache( void ); + +extern const char *GetGameDescription( void ); +extern void PlayerCustomization( edict_t *pEntity, customization_t *pCust ); + +extern void SpectatorConnect ( edict_t *pEntity ); +extern void SpectatorDisconnect ( edict_t *pEntity ); +extern void SpectatorThink ( edict_t *pEntity ); + +extern void Sys_Error( const char *error_string ); + +extern void SetupVisibility( edict_t *pViewEntity, edict_t *pClient, unsigned char **pvs, unsigned char **pas ); +extern void UpdateClientData ( const struct edict_s *ent, int sendweapons, struct clientdata_s *cd ); +extern int AddToFullPack( struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet ); +extern void CreateBaseline( int player, int eindex, struct entity_state_s *baseline, struct edict_s *entity, int playermodelindex, vec3_t player_mins, vec3_t player_maxs ); +extern void RegisterEncoders( void ); + +extern int GetWeaponData( struct edict_s *player, struct weapon_data_s *info ); + +extern void CmdStart( const edict_t *player, const struct usercmd_s *cmd, unsigned int random_seed ); +extern void CmdEnd ( const edict_t *player ); + +extern int ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); + +extern int GetHullBounds( int hullnumber, float *mins, float *maxs ); + +extern void CreateInstancedBaselines ( void ); + +extern int InconsistentFile( const edict_t *player, const char *filename, char *disconnect_message ); + +extern int AllowLagCompensation( void ); + +#endif // CLIENT_H diff --git a/hlsdk/dlls/combat.cpp b/hlsdk/dlls/combat.cpp new file mode 100644 index 00000000..e2d1efff --- /dev/null +++ b/hlsdk/dlls/combat.cpp @@ -0,0 +1,1702 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== combat.cpp ======================================================== + + functions dealing with damage infliction & death + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "soundent.h" +#include "decals.h" +#include "animation.h" +#include "weapons.h" +#include "func_break.h" + +extern DLL_GLOBAL Vector g_vecAttackDir; +extern DLL_GLOBAL int g_iSkillLevel; + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); +extern entvars_t *g_pevLastInflictor; + +#define GERMAN_GIB_COUNT 4 +#define HUMAN_GIB_COUNT 6 +#define ALIEN_GIB_COUNT 4 + + +// HACKHACK -- The gib velocity equations don't work +void CGib :: LimitVelocity( void ) +{ + float length = pev->velocity.Length(); + + // ceiling at 1500. The gib velocity equation is not bounded properly. Rather than tune it + // in 3 separate places again, I'll just limit it here. + if ( length > 1500.0 ) + pev->velocity = pev->velocity.Normalize() * 1500; // This should really be sv_maxvelocity * 0.75 or something +} + + +void CGib :: SpawnStickyGibs( entvars_t *pevVictim, Vector vecOrigin, int cGibs ) +{ + int i; + + if ( g_Language == LANGUAGE_GERMAN ) + { + // no sticky gibs in germany right now! + return; + } + + for ( i = 0 ; i < cGibs ; i++ ) + { + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + pGib->Spawn( "models/stickygib.mdl" ); + pGib->pev->body = RANDOM_LONG(0,2); + + if ( pevVictim ) + { + pGib->pev->origin.x = vecOrigin.x + RANDOM_FLOAT( -3, 3 ); + pGib->pev->origin.y = vecOrigin.y + RANDOM_FLOAT( -3, 3 ); + pGib->pev->origin.z = vecOrigin.z + RANDOM_FLOAT( -3, 3 ); + + /* + pGib->pev->origin.x = pevVictim->absmin.x + pevVictim->size.x * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.y = pevVictim->absmin.y + pevVictim->size.y * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.z = pevVictim->absmin.z + pevVictim->size.z * (RANDOM_FLOAT ( 0 , 1 ) ); + */ + + // make the gib fly away from the attack vector + pGib->pev->velocity = g_vecAttackDir * -1; + + // mix in some noise + pGib->pev->velocity.x += RANDOM_FLOAT ( -0.15, 0.15 ); + pGib->pev->velocity.y += RANDOM_FLOAT ( -0.15, 0.15 ); + pGib->pev->velocity.z += RANDOM_FLOAT ( -0.15, 0.15 ); + + pGib->pev->velocity = pGib->pev->velocity * 900; + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 250, 400 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 250, 400 ); + + // copy owner's blood color + pGib->m_bloodColor = (CBaseEntity::Instance(pevVictim))->BloodColor(); + + if ( pevVictim->health > -50) + { + pGib->pev->velocity = pGib->pev->velocity * 0.7; + } + else if ( pevVictim->health > -200) + { + pGib->pev->velocity = pGib->pev->velocity * 2; + } + else + { + pGib->pev->velocity = pGib->pev->velocity * 4; + } + + + pGib->pev->movetype = MOVETYPE_TOSS; + pGib->pev->solid = SOLID_BBOX; + UTIL_SetSize ( pGib->pev, Vector ( 0, 0 ,0 ), Vector ( 0, 0, 0 ) ); + pGib->SetTouch ( &CGib::StickyGibTouch ); + pGib->SetThink (NULL); + } + pGib->LimitVelocity(); + } +} + +void CGib :: SpawnHeadGib( entvars_t *pevVictim ) +{ + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + if ( g_Language == LANGUAGE_GERMAN ) + { + pGib->Spawn( "models/germangibs.mdl" );// throw one head + pGib->pev->body = 0; + } + else + { + pGib->Spawn( "models/hgibs.mdl" );// throw one head + pGib->pev->body = 0; + } + + if ( pevVictim ) + { + pGib->pev->origin = pevVictim->origin + pevVictim->view_ofs; + + edict_t *pentPlayer = FIND_CLIENT_IN_PVS( pGib->edict() ); + + if ( RANDOM_LONG ( 0, 100 ) <= 5 && pentPlayer ) + { + // 5% chance head will be thrown at player's face. + entvars_t *pevPlayer; + + pevPlayer = VARS( pentPlayer ); + pGib->pev->velocity = ( ( pevPlayer->origin + pevPlayer->view_ofs ) - pGib->pev->origin ).Normalize() * 300; + pGib->pev->velocity.z += 100; + } + else + { + pGib->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + } + + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + + // copy owner's blood color + pGib->m_bloodColor = (CBaseEntity::Instance(pevVictim))->BloodColor(); + + if ( pevVictim->health > -50) + { + pGib->pev->velocity = pGib->pev->velocity * 0.7; + } + else if ( pevVictim->health > -200) + { + pGib->pev->velocity = pGib->pev->velocity * 2; + } + else + { + pGib->pev->velocity = pGib->pev->velocity * 4; + } + } + pGib->LimitVelocity(); +} + +void CGib :: SpawnRandomGibs( entvars_t *pevVictim, int cGibs, int human ) +{ + int cSplat; + + for ( cSplat = 0 ; cSplat < cGibs ; cSplat++ ) + { + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + if ( g_Language == LANGUAGE_GERMAN ) + { + pGib->Spawn( "models/germangibs.mdl" ); + pGib->pev->body = RANDOM_LONG(0,GERMAN_GIB_COUNT-1); + } + else + { + if ( human ) + { + // human pieces + pGib->Spawn( "models/hgibs.mdl" ); + pGib->pev->body = RANDOM_LONG(1,HUMAN_GIB_COUNT-1);// start at one to avoid throwing random amounts of skulls (0th gib) + } + else + { + // aliens + pGib->Spawn( "models/agibs.mdl" ); + pGib->pev->body = RANDOM_LONG(0,ALIEN_GIB_COUNT-1); + } + } + + if ( pevVictim ) + { + // spawn the gib somewhere in the monster's bounding volume + pGib->pev->origin.x = pevVictim->absmin.x + pevVictim->size.x * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.y = pevVictim->absmin.y + pevVictim->size.y * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.z = pevVictim->absmin.z + pevVictim->size.z * (RANDOM_FLOAT ( 0 , 1 ) ) + 1; // absmin.z is in the floor because the engine subtracts 1 to enlarge the box + + // make the gib fly away from the attack vector + pGib->pev->velocity = g_vecAttackDir * -1; + + // mix in some noise + pGib->pev->velocity.x += RANDOM_FLOAT ( -0.25, 0.25 ); + pGib->pev->velocity.y += RANDOM_FLOAT ( -0.25, 0.25 ); + pGib->pev->velocity.z += RANDOM_FLOAT ( -0.25, 0.25 ); + + pGib->pev->velocity = pGib->pev->velocity * RANDOM_FLOAT ( 300, 400 ); + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + + // copy owner's blood color + pGib->m_bloodColor = (CBaseEntity::Instance(pevVictim))->BloodColor(); + + if ( pevVictim->health > -50) + { + pGib->pev->velocity = pGib->pev->velocity * 0.7; + } + else if ( pevVictim->health > -200) + { + pGib->pev->velocity = pGib->pev->velocity * 2; + } + else + { + pGib->pev->velocity = pGib->pev->velocity * 4; + } + + pGib->pev->solid = SOLID_BBOX; + UTIL_SetSize ( pGib->pev, Vector( 0 , 0 , 0 ), Vector ( 0, 0, 0 ) ); + } + pGib->LimitVelocity(); + } +} + + +BOOL CBaseMonster :: HasHumanGibs( void ) +{ + int myClass = Classify(); + + if ( myClass == CLASS_HUMAN_MILITARY || + myClass == CLASS_PLAYER_ALLY || + myClass == CLASS_HUMAN_PASSIVE || + myClass == CLASS_PLAYER ) + + return TRUE; + + return FALSE; +} + + +BOOL CBaseMonster :: HasAlienGibs( void ) +{ + int myClass = Classify(); + + if ( myClass == CLASS_ALIEN_MILITARY || + myClass == CLASS_ALIEN_MONSTER || + myClass == CLASS_ALIEN_PASSIVE || + myClass == CLASS_INSECT || + myClass == CLASS_ALIEN_PREDATOR || + myClass == CLASS_ALIEN_PREY ) + + return TRUE; + + return FALSE; +} + + +void CBaseMonster::FadeMonster( void ) +{ + StopAnimation(); + pev->velocity = g_vecZero; + pev->movetype = MOVETYPE_NONE; + pev->avelocity = g_vecZero; + pev->animtime = gpGlobals->time; + pev->effects |= EF_NOINTERP; + SUB_StartFadeOut(); +} + +//========================================================= +// GibMonster - create some gore and get rid of a monster's +// model. +//========================================================= +void CBaseMonster :: GibMonster( void ) +{ + TraceResult tr; + BOOL gibbed = FALSE; + + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "common/bodysplat.wav", 1, ATTN_NORM); + + // only humans throw skulls !!!UNDONE - eventually monsters will have their own sets of gibs + if ( HasHumanGibs() ) + { + if ( CVAR_GET_FLOAT("violence_hgibs") != 0 ) // Only the player will ever get here + { + CGib::SpawnHeadGib( pev ); + CGib::SpawnRandomGibs( pev, 4, 1 ); // throw some human gibs. + } + gibbed = TRUE; + } + else if ( HasAlienGibs() ) + { + if ( CVAR_GET_FLOAT("violence_agibs") != 0 ) // Should never get here, but someone might call it directly + { + CGib::SpawnRandomGibs( pev, 4, 0 ); // Throw alien gibs + } + gibbed = TRUE; + } + + if ( !IsPlayer() ) + { + if ( gibbed ) + { + // don't remove players! + SetThink ( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time; + } + else + { + FadeMonster(); + } + } +} + +//========================================================= +// GetDeathActivity - determines the best type of death +// anim to play. +//========================================================= +Activity CBaseMonster :: GetDeathActivity ( void ) +{ + Activity deathActivity; + BOOL fTriedDirection; + float flDot; + TraceResult tr; + Vector vecSrc; + + if ( pev->deadflag != DEAD_NO ) + { + // don't run this while dying. + return m_IdealActivity; + } + + vecSrc = Center(); + + fTriedDirection = FALSE; + deathActivity = ACT_DIESIMPLE;// in case we can't find any special deaths to do. + + UTIL_MakeVectors ( pev->angles ); + flDot = DotProduct ( gpGlobals->v_forward, g_vecAttackDir * -1 ); + + switch ( m_LastHitGroup ) + { + // try to pick a region-specific death. + case HITGROUP_HEAD: + deathActivity = ACT_DIE_HEADSHOT; + break; + + case HITGROUP_STOMACH: + deathActivity = ACT_DIE_GUTSHOT; + break; + + case HITGROUP_GENERIC: + // try to pick a death based on attack direction + fTriedDirection = TRUE; + + if ( flDot > 0.3 ) + { + deathActivity = ACT_DIEFORWARD; + } + else if ( flDot <= -0.3 ) + { + deathActivity = ACT_DIEBACKWARD; + } + break; + + default: + // try to pick a death based on attack direction + fTriedDirection = TRUE; + + if ( flDot > 0.3 ) + { + deathActivity = ACT_DIEFORWARD; + } + else if ( flDot <= -0.3 ) + { + deathActivity = ACT_DIEBACKWARD; + } + break; + } + + + // can we perform the prescribed death? + if ( LookupActivity ( deathActivity ) == ACTIVITY_NOT_AVAILABLE ) + { + // no! did we fail to perform a directional death? + if ( fTriedDirection ) + { + // if yes, we're out of options. Go simple. + deathActivity = ACT_DIESIMPLE; + } + else + { + // cannot perform the ideal region-specific death, so try a direction. + if ( flDot > 0.3 ) + { + deathActivity = ACT_DIEFORWARD; + } + else if ( flDot <= -0.3 ) + { + deathActivity = ACT_DIEBACKWARD; + } + } + } + + if ( LookupActivity ( deathActivity ) == ACTIVITY_NOT_AVAILABLE ) + { + // if we're still invalid, simple is our only option. + deathActivity = ACT_DIESIMPLE; + } + + if ( deathActivity == ACT_DIEFORWARD ) + { + // make sure there's room to fall forward + UTIL_TraceHull ( vecSrc, vecSrc + gpGlobals->v_forward * 64, dont_ignore_monsters, head_hull, edict(), &tr ); + + if ( tr.flFraction != 1.0 ) + { + deathActivity = ACT_DIESIMPLE; + } + } + + if ( deathActivity == ACT_DIEBACKWARD ) + { + // make sure there's room to fall backward + UTIL_TraceHull ( vecSrc, vecSrc - gpGlobals->v_forward * 64, dont_ignore_monsters, head_hull, edict(), &tr ); + + if ( tr.flFraction != 1.0 ) + { + deathActivity = ACT_DIESIMPLE; + } + } + + return deathActivity; +} + +//========================================================= +// GetSmallFlinchActivity - determines the best type of flinch +// anim to play. +//========================================================= +Activity CBaseMonster :: GetSmallFlinchActivity ( void ) +{ + Activity flinchActivity; + BOOL fTriedDirection; + float flDot; + + fTriedDirection = FALSE; + UTIL_MakeVectors ( pev->angles ); + flDot = DotProduct ( gpGlobals->v_forward, g_vecAttackDir * -1 ); + + switch ( m_LastHitGroup ) + { + // pick a region-specific flinch + case HITGROUP_HEAD: + flinchActivity = ACT_FLINCH_HEAD; + break; + case HITGROUP_STOMACH: + flinchActivity = ACT_FLINCH_STOMACH; + break; + case HITGROUP_LEFTARM: + flinchActivity = ACT_FLINCH_LEFTARM; + break; + case HITGROUP_RIGHTARM: + flinchActivity = ACT_FLINCH_RIGHTARM; + break; + case HITGROUP_LEFTLEG: + flinchActivity = ACT_FLINCH_LEFTLEG; + break; + case HITGROUP_RIGHTLEG: + flinchActivity = ACT_FLINCH_RIGHTLEG; + break; + case HITGROUP_GENERIC: + default: + // just get a generic flinch. + flinchActivity = ACT_SMALL_FLINCH; + break; + } + + + // do we have a sequence for the ideal activity? + if ( LookupActivity ( flinchActivity ) == ACTIVITY_NOT_AVAILABLE ) + { + flinchActivity = ACT_SMALL_FLINCH; + } + + return flinchActivity; +} + + +void CBaseMonster::BecomeDead( void ) +{ + pev->takedamage = DAMAGE_YES;// don't let autoaim aim at corpses. + + // give the corpse half of the monster's original maximum health. + pev->health = pev->max_health / 2; + pev->max_health = 5; // max_health now becomes a counter for how many blood decals the corpse can place. + + // make the corpse fly away from the attack vector + pev->movetype = MOVETYPE_TOSS; + //pev->flags &= ~FL_ONGROUND; + //pev->origin.z += 2; + //pev->velocity = g_vecAttackDir * -1; + //pev->velocity = pev->velocity * RANDOM_FLOAT( 300, 400 ); +} + + +BOOL CBaseMonster::ShouldGibMonster( int iGib ) +{ + if ( ( iGib == GIB_NORMAL && pev->health < GIB_HEALTH_VALUE ) || ( iGib == GIB_ALWAYS ) ) + return TRUE; + + return FALSE; +} + + +void CBaseMonster::CallGibMonster( void ) +{ + BOOL fade = FALSE; + + if ( HasHumanGibs() ) + { + if ( CVAR_GET_FLOAT("violence_hgibs") == 0 ) + fade = TRUE; + } + else if ( HasAlienGibs() ) + { + if ( CVAR_GET_FLOAT("violence_agibs") == 0 ) + fade = TRUE; + } + + pev->takedamage = DAMAGE_NO; + pev->solid = SOLID_NOT;// do something with the body. while monster blows up + + if ( fade ) + { + FadeMonster(); + } + else + { + pev->effects = EF_NODRAW; // make the model invisible. + GibMonster(); + } + + pev->deadflag = DEAD_DEAD; + FCheckAITrigger(); + + // don't let the status bar glitch for players.with <0 health. + if (pev->health < -99) + { + pev->health = 0; + } + + if ( ShouldFadeOnDeath() && !fade ) + UTIL_Remove(this); +} + + +/* +============ +Killed +============ +*/ +void CBaseMonster :: Killed( entvars_t *pevAttacker, int iGib ) +{ + if ( HasMemory( bits_MEMORY_KILLED ) ) + { + if ( ShouldGibMonster( iGib ) ) + CallGibMonster(); + return; + } + + Remember( bits_MEMORY_KILLED ); + + // clear the deceased's sound channels.(may have been firing or reloading when killed) + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "common/null.wav", 1, ATTN_NORM); + m_IdealMonsterState = MONSTERSTATE_DEAD; + // Make sure this condition is fired too (TakeDamage breaks out before this happens on death) + SetConditions( bits_COND_LIGHT_DAMAGE ); + + // tell owner ( if any ) that we're dead.This is mostly for MonsterMaker functionality. + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + if ( pOwner ) + { + pOwner->DeathNotice( pev ); + } + + if ( ShouldGibMonster( iGib ) ) + { + CallGibMonster(); + return; + } + else if ( pev->flags & FL_MONSTER ) + { + SetTouch( NULL ); + BecomeDead(); + } + + // don't let the status bar glitch for players.with <0 health. + if (pev->health < -99) + { + pev->health = 0; + } + + //pev->enemy = ENT( pevAttacker );//why? (sjb) + + m_IdealMonsterState = MONSTERSTATE_DEAD; +} + +// +// fade out - slowly fades a entity out, then removes it. +// +// DON'T USE ME FOR GIBS AND STUFF IN MULTIPLAYER! +// SET A FUTURE THINK AND A RENDERMODE!! +void CBaseEntity :: SUB_StartFadeOut ( void ) +{ + if (pev->rendermode == kRenderNormal) + { + pev->renderamt = 255; + pev->rendermode = kRenderTransTexture; + } + + pev->solid = SOLID_NOT; + pev->avelocity = g_vecZero; + + pev->nextthink = gpGlobals->time + 0.1; + SetThink ( &CBaseEntity::SUB_FadeOut ); +} + +void CBaseEntity :: SUB_FadeOut ( void ) +{ + if ( pev->renderamt > 7 ) + { + pev->renderamt -= 7; + pev->nextthink = gpGlobals->time + 0.1; + } + else + { + pev->renderamt = 0; + pev->nextthink = gpGlobals->time + 0.2; + SetThink ( &CBaseEntity::SUB_Remove ); + } +} + +//========================================================= +// WaitTillLand - in order to emit their meaty scent from +// the proper location, gibs should wait until they stop +// bouncing to emit their scent. That's what this function +// does. +//========================================================= +void CGib :: WaitTillLand ( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + if ( pev->velocity == g_vecZero ) + { + SetThink (&CBaseEntity::SUB_StartFadeOut); + pev->nextthink = gpGlobals->time + m_lifeTime; + + // If you bleed, you stink! + if ( m_bloodColor != DONT_BLEED ) + { + // ok, start stinkin! + CSoundEnt::InsertSound ( bits_SOUND_MEAT, pev->origin, 384, 25 ); + } + } + else + { + // wait and check again in another half second. + pev->nextthink = gpGlobals->time + 0.5; + } +} + +// +// Gib bounces on the ground or wall, sponges some blood down, too! +// +void CGib :: BounceGibTouch ( CBaseEntity *pOther ) +{ + Vector vecSpot; + TraceResult tr; + + //if ( RANDOM_LONG(0,1) ) + // return;// don't bleed everytime + + if (pev->flags & FL_ONGROUND) + { + pev->velocity = pev->velocity * 0.9; + pev->angles.x = 0; + pev->angles.z = 0; + pev->avelocity.x = 0; + pev->avelocity.z = 0; + } + else + { + if ( g_Language != LANGUAGE_GERMAN && m_cBloodDecals > 0 && m_bloodColor != DONT_BLEED ) + { + vecSpot = pev->origin + Vector ( 0 , 0 , 8 );//move up a bit, and trace down. + UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -24 ), ignore_monsters, ENT(pev), & tr); + + UTIL_BloodDecalTrace( &tr, m_bloodColor ); + + m_cBloodDecals--; + } + + if ( m_material != matNone && RANDOM_LONG(0,2) == 0 ) + { + float volume; + float zvel = fabs(pev->velocity.z); + + volume = 0.8 * min(1.0, ((float)zvel) / 450.0); + + CBreakable::MaterialSoundRandom( edict(), (Materials)m_material, volume ); + } + } +} + +// +// Sticky gib puts blood on the wall and stays put. +// +void CGib :: StickyGibTouch ( CBaseEntity *pOther ) +{ + Vector vecSpot; + TraceResult tr; + + SetThink ( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time + 10; + + if ( !FClassnameIs( pOther->pev, "worldspawn" ) ) + { + pev->nextthink = gpGlobals->time; + return; + } + + UTIL_TraceLine ( pev->origin, pev->origin + pev->velocity * 32, ignore_monsters, ENT(pev), & tr); + + UTIL_BloodDecalTrace( &tr, m_bloodColor ); + + pev->velocity = tr.vecPlaneNormal * -1; + pev->angles = UTIL_VecToAngles ( pev->velocity ); + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + pev->movetype = MOVETYPE_NONE; +} + +// +// Throw a chunk +// +void CGib :: Spawn( const char *szGibModel ) +{ + pev->movetype = MOVETYPE_BOUNCE; + pev->friction = 0.55; // deading the bounce a bit + + // sometimes an entity inherits the edict from a former piece of glass, + // and will spawn using the same render FX or rendermode! bad! + pev->renderamt = 255; + pev->rendermode = kRenderNormal; + pev->renderfx = kRenderFxNone; + pev->solid = SOLID_SLIDEBOX;/// hopefully this will fix the VELOCITY TOO LOW crap + pev->classname = MAKE_STRING("gib"); + + SET_MODEL(ENT(pev), szGibModel); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + + pev->nextthink = gpGlobals->time + 4; + m_lifeTime = 25; + SetThink ( &CGib::WaitTillLand ); + SetTouch ( &CGib::BounceGibTouch ); + + m_material = matNone; + m_cBloodDecals = 5;// how many blood decals this gib can place (1 per bounce until none remain). +} + +// take health +int CBaseMonster :: TakeHealth (float flHealth, int bitsDamageType) +{ + if (!pev->takedamage) + return 0; + + // clear out any damage types we healed. + // UNDONE: generic health should not heal any + // UNDONE: time-based damage + + m_bitsDamageType &= ~(bitsDamageType & ~DMG_TIMEBASED); + + return CBaseEntity::TakeHealth(flHealth, bitsDamageType); +} + +/* +============ +TakeDamage + +The damage is coming from inflictor, but get mad at attacker +This should be the only function that ever reduces health. +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 +============ +*/ +int CBaseMonster :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + float flTake; + Vector vecDir; + + if (!pev->takedamage) + return 0; + + if ( !IsAlive() ) + { + return DeadTakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); + } + + if ( pev->deadflag == DEAD_NO ) + { + // no pain sound during death animation. + PainSound();// "Ouch!" + } + + //!!!LATER - make armor consideration here! + flTake = flDamage; + + // set damage type sustained + m_bitsDamageType |= bitsDamageType; + + // 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 )) + { + CBaseEntity *pInflictor = CBaseEntity :: Instance( pevInflictor ); + if (pInflictor) + { + vecDir = ( pInflictor->Center() - Vector ( 0, 0, 10 ) - Center() ).Normalize(); + vecDir = g_vecAttackDir = vecDir.Normalize(); + } + } + + // add to the damage total for clients, which will be sent as a single + // message at the end of the frame + // todo: remove after combining shotgun blasts? + if ( IsPlayer() ) + { + if ( pevInflictor ) + pev->dmg_inflictor = ENT(pevInflictor); + + pev->dmg_take += flTake; + + // check for godmode or invincibility + if ( pev->flags & FL_GODMODE ) + { + return 0; + } + } + + // if this is a player, move him around! + if ( ( !FNullEnt( pevInflictor ) ) && (pev->movetype == MOVETYPE_WALK) && (!pevAttacker || pevAttacker->solid != SOLID_TRIGGER) ) + { + pev->velocity = pev->velocity + vecDir * -DamageForce( flDamage ); + } + + // 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 ) + { + SetConditions( bits_COND_LIGHT_DAMAGE ); + return 0; + } + + if ( pev->health <= 0 ) + { + g_pevLastInflictor = pevInflictor; + + if ( bitsDamageType & DMG_ALWAYSGIB ) + { + Killed( pevAttacker, GIB_ALWAYS ); + } + else if ( bitsDamageType & DMG_NEVERGIB ) + { + Killed( pevAttacker, GIB_NEVER ); + } + else + { + Killed( pevAttacker, GIB_NORMAL ); + } + + g_pevLastInflictor = NULL; + + return 0; + } + + // react to the damage (get mad) + if ( (pev->flags & FL_MONSTER) && !FNullEnt(pevAttacker) ) + { + if ( pevAttacker->flags & (FL_MONSTER | FL_CLIENT) ) + {// only if the attack was a monster or client! + + // enemy's last known position is somewhere down the vector that the attack came from. + if (pevInflictor) + { + if (m_hEnemy == 0 || pevInflictor == m_hEnemy->pev || !HasConditions(bits_COND_SEE_ENEMY)) + { + m_vecEnemyLKP = pevInflictor->origin; + } + } + else + { + m_vecEnemyLKP = pev->origin + ( g_vecAttackDir * 64 ); + } + + MakeIdealYaw( m_vecEnemyLKP ); + + // add pain to the conditions + // !!!HACKHACK - fudged for now. Do we want to have a virtual function to determine what is light and + // heavy damage per monster class? + if ( flDamage > 0 ) + { + SetConditions(bits_COND_LIGHT_DAMAGE); + } + + if ( flDamage >= 20 ) + { + SetConditions(bits_COND_HEAVY_DAMAGE); + } + } + } + + return 1; +} + +//========================================================= +// DeadTakeDamage - takedamage function called when a monster's +// corpse is damaged. +//========================================================= +int CBaseMonster :: DeadTakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + Vector vecDir; + + // 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 )) + { + CBaseEntity *pInflictor = CBaseEntity :: Instance( pevInflictor ); + if (pInflictor) + { + vecDir = ( pInflictor->Center() - Vector ( 0, 0, 10 ) - Center() ).Normalize(); + vecDir = g_vecAttackDir = vecDir.Normalize(); + } + } + +#if 0// turn this back on when the bounding box issues are resolved. + + pev->flags &= ~FL_ONGROUND; + pev->origin.z += 1; + + // let the damage scoot the corpse around a bit. + if ( !FNullEnt(pevInflictor) && (pevAttacker->solid != SOLID_TRIGGER) ) + { + pev->velocity = pev->velocity + vecDir * -DamageForce( flDamage ); + } + +#endif + + // kill the corpse if enough damage was done to destroy the corpse and the damage is of a type that is allowed to destroy the corpse. + if ( bitsDamageType & DMG_GIB_CORPSE ) + { + if ( pev->health <= flDamage ) + { + pev->health = -50; + Killed( pevAttacker, GIB_ALWAYS ); + return 0; + } + // Accumulate corpse gibbing damage, so you can gib with multiple hits + pev->health -= flDamage * 0.1; + } + + return 1; +} + + +float CBaseMonster :: DamageForce( float damage ) +{ + float force = damage * ((32 * 32 * 72.0) / (pev->size.x * pev->size.y * pev->size.z)) * 5; + + if ( force > 1000.0) + { + force = 1000.0; + } + + return force; +} + +// +// RadiusDamage - this entity is exploding, or otherwise needs to inflict damage upon entities within a certain range. +// +// only damage ents that can clearly be seen by the explosion! + + +void RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, float flRadius, int iClassIgnore, int bitsDamageType ) +{ + CBaseEntity *pEntity = NULL; + TraceResult tr; + float flAdjustedDamage, falloff; + Vector vecSpot; + + if ( flRadius ) + falloff = flDamage / flRadius; + else + falloff = 1.0; + + int bInWater = (UTIL_PointContents ( vecSrc ) == CONTENTS_WATER); + + vecSrc.z += 1;// in case grenade is lying on the ground + + if ( !pevAttacker ) + pevAttacker = pevInflictor; + + // iterate on all entities in the vicinity. + while ((pEntity = UTIL_FindEntityInSphere( pEntity, vecSrc, flRadius )) != NULL) + { + if ( pEntity->pev->takedamage != DAMAGE_NO ) + { + // UNDONE: this should check a damage mask, not an ignore + if ( iClassIgnore != CLASS_NONE && pEntity->Classify() == iClassIgnore ) + {// houndeyes don't hurt other houndeyes with their attack + continue; + } + + // blast's don't tavel into or out of water + if (bInWater && pEntity->pev->waterlevel == 0) + continue; + if (!bInWater && pEntity->pev->waterlevel == 3) + continue; + + vecSpot = pEntity->BodyTarget( vecSrc ); + + UTIL_TraceLine ( vecSrc, vecSpot, dont_ignore_monsters, ENT(pevInflictor), &tr ); + + if ( tr.flFraction == 1.0 || tr.pHit == pEntity->edict() ) + {// the explosion can 'see' this entity, so hurt them! + if (tr.fStartSolid) + { + // if we're stuck inside them, fixup the position and distance + tr.vecEndPos = vecSrc; + tr.flFraction = 0.0; + } + + // decrease damage for an ent that's farther from the bomb. + flAdjustedDamage = ( vecSrc - tr.vecEndPos ).Length() * falloff; + flAdjustedDamage = flDamage - flAdjustedDamage; + + if ( flAdjustedDamage < 0 ) + { + flAdjustedDamage = 0; + } + + // ALERT( at_console, "hit %s\n", STRING( pEntity->pev->classname ) ); + if (tr.flFraction != 1.0) + { + ClearMultiDamage( ); + pEntity->TraceAttack( pevInflictor, flAdjustedDamage, (tr.vecEndPos - vecSrc).Normalize( ), &tr, bitsDamageType ); + ApplyMultiDamage( pevInflictor, pevAttacker ); + } + else + { + pEntity->TakeDamage ( pevInflictor, pevAttacker, flAdjustedDamage, bitsDamageType ); + } + } + } + } +} + + +void CBaseMonster :: RadiusDamage(entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) +{ + ::RadiusDamage( pev->origin, pevInflictor, pevAttacker, flDamage, flDamage * 2.5, iClassIgnore, bitsDamageType ); +} + + +void CBaseMonster :: RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) +{ + ::RadiusDamage( vecSrc, pevInflictor, pevAttacker, flDamage, flDamage * 2.5, iClassIgnore, bitsDamageType ); +} + + +//========================================================= +// CheckTraceHullAttack - expects a length to trace, amount +// of damage to do, and damage type. Returns a pointer to +// the damaged entity in case the monster wishes to do +// other stuff to the victim (punchangle, etc) +// +// Used for many contact-range melee attacks. Bites, claws, etc. +//========================================================= +CBaseEntity* CBaseMonster :: CheckTraceHullAttack( float flDist, int iDamage, int iDmgType ) +{ + TraceResult tr; + + if (IsPlayer()) + UTIL_MakeVectors( pev->angles ); + else + UTIL_MakeAimVectors( pev->angles ); + + Vector vecStart = pev->origin; + vecStart.z += pev->size.z * 0.5; + Vector vecEnd = vecStart + (gpGlobals->v_forward * flDist ); + + UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT(pev), &tr ); + + if ( tr.pHit ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + + if ( iDamage > 0 ) + { + pEntity->TakeDamage( pev, pev, iDamage, iDmgType ); + } + + return pEntity; + } + + return NULL; +} + + +//========================================================= +// FInViewCone - returns true is the passed ent is in +// the caller's forward view cone. The dot product is performed +// in 2d, making the view cone infinitely tall. +//========================================================= +BOOL CBaseMonster :: FInViewCone ( CBaseEntity *pEntity ) +{ + Vector2D vec2LOS; + float flDot; + + UTIL_MakeVectors ( pev->angles ); + + vec2LOS = ( pEntity->pev->origin - pev->origin ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + flDot = DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() ); + + if ( flDot > m_flFieldOfView ) + { + return TRUE; + } + else + { + return FALSE; + } +} + +//========================================================= +// FInViewCone - returns true is the passed vector is in +// the caller's forward view cone. The dot product is performed +// in 2d, making the view cone infinitely tall. +//========================================================= +BOOL CBaseMonster :: FInViewCone ( Vector *pOrigin ) +{ + Vector2D vec2LOS; + float flDot; + + UTIL_MakeVectors ( pev->angles ); + + vec2LOS = ( *pOrigin - pev->origin ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + flDot = DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() ); + + if ( flDot > m_flFieldOfView ) + { + return TRUE; + } + else + { + return FALSE; + } +} + +//========================================================= +// FVisible - returns true if a line can be traced from +// the caller's eyes to the target +//========================================================= +BOOL CBaseEntity :: FVisible ( CBaseEntity *pEntity ) +{ + TraceResult tr; + Vector vecLookerOrigin; + Vector vecTargetOrigin; + + if (FBitSet( pEntity->pev->flags, FL_NOTARGET )) + return FALSE; + + // don't look through water + if ((pev->waterlevel != 3 && pEntity->pev->waterlevel == 3) + || (pev->waterlevel == 3 && pEntity->pev->waterlevel == 0)) + return FALSE; + + vecLookerOrigin = pev->origin + pev->view_ofs;//look through the caller's 'eyes' + vecTargetOrigin = pEntity->EyePosition(); + + UTIL_TraceLine(vecLookerOrigin, vecTargetOrigin, ignore_monsters, ignore_glass, ENT(pev)/*pentIgnore*/, &tr); + + if (tr.flFraction != 1.0) + { + return FALSE;// Line of sight is not established + } + else + { + return TRUE;// line of sight is valid. + } +} + +//========================================================= +// FVisible - returns true if a line can be traced from +// the caller's eyes to the target vector +//========================================================= +BOOL CBaseEntity :: FVisible ( const Vector &vecOrigin ) +{ + TraceResult tr; + Vector vecLookerOrigin; + + vecLookerOrigin = EyePosition();//look through the caller's 'eyes' + + UTIL_TraceLine(vecLookerOrigin, vecOrigin, ignore_monsters, ignore_glass, ENT(pev)/*pentIgnore*/, &tr); + + if (tr.flFraction != 1.0) + { + return FALSE;// Line of sight is not established + } + else + { + return TRUE;// line of sight is valid. + } +} + +/* +================ +TraceAttack +================ +*/ +void CBaseEntity::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + Vector vecOrigin = ptr->vecEndPos - vecDir * 4; + + if ( pev->takedamage ) + { + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + + int blood = BloodColor(); + + if ( blood != DONT_BLEED ) + { + SpawnBlood(vecOrigin, blood, flDamage);// a little surface blood. + TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); + } + } +} + + +/* +//========================================================= +// TraceAttack +//========================================================= +void CBaseMonster::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + Vector vecOrigin = ptr->vecEndPos - vecDir * 4; + + ALERT ( at_console, "%d\n", ptr->iHitgroup ); + + + if ( pev->takedamage ) + { + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + + int blood = BloodColor(); + + if ( blood != DONT_BLEED ) + { + SpawnBlood(vecOrigin, blood, flDamage);// a little surface blood. + } + } +} +*/ + +//========================================================= +// TraceAttack +//========================================================= +void CBaseMonster :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if ( pev->takedamage ) + { + m_LastHitGroup = ptr->iHitgroup; + + switch ( ptr->iHitgroup ) + { + 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; + } + + SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage);// a little surface blood. + TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + } +} + +/* +================ +FireBullets + +Go to the trouble of combining multiple pellets into a single damage call. + +This version is used by Monsters. +================ +*/ +void CBaseEntity::FireBullets(ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq, int iDamage, entvars_t *pevAttacker ) +{ + static int tracerCount; + int tracer; + TraceResult tr; + Vector vecRight = gpGlobals->v_right; + Vector vecUp = gpGlobals->v_up; + + if ( pevAttacker == NULL ) + pevAttacker = pev; // the default attacker is ourselves + + ClearMultiDamage(); + gMultiDamage.type = DMG_BULLET | DMG_NEVERGIB; + + for (ULONG iShot = 1; iShot <= cShots; iShot++) + { + // get circular gaussian spread + float x, y, z; + 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); + + 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); + + tracer = 0; + if (iTracerFreq != 0 && (tracerCount++ % iTracerFreq) == 0) + { + Vector vecTracerSrc; + + if ( IsPlayer() ) + {// adjust tracer position for player + vecTracerSrc = vecSrc + Vector ( 0 , 0 , -4 ) + gpGlobals->v_right * 2 + gpGlobals->v_forward * 16; + } + else + { + vecTracerSrc = vecSrc; + } + + 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) + { + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + + if ( iDamage ) + { + 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_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); + 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; + } + } + // make bullet trails + UTIL_BubbleTrail( vecSrc, tr.vecEndPos, static_cast((flDistance * tr.flFraction) / 64.0) ); + } + ApplyMultiDamage(pev, pevAttacker); +} + + +/* +================ +FireBullets + +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 ) +{ + TraceResult tr; + Vector vecRight = gpGlobals->v_right; + Vector vecUp = gpGlobals->v_up; + float x = 0.0f, y = 0.0f, z = 0.0f; + + if ( pevAttacker == NULL ) + pevAttacker = pev; // the default attacker is ourselves + + ClearMultiDamage(); + gMultiDamage.type = DMG_BULLET | DMG_NEVERGIB; + + for ( ULONG iShot = 1; iShot <= cShots; iShot++ ) + { + //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; + + 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) + { + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + + if ( iDamage ) + { + 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; + + 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) + { + UTIL_DecalTrace( &tr, DECAL_GLASSBREAK1 + RANDOM_LONG(0,2) ); + } + + break; + } + } + // make bullet trails + UTIL_BubbleTrail( vecSrc, tr.vecEndPos, static_cast((flDistance * tr.flFraction) / 64.0) ); + } + ApplyMultiDamage(pev, pevAttacker); + + return Vector( x * vecSpread.x, y * vecSpread.y, 0.0 ); +} + +void CBaseEntity :: TraceBleed( float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + if (BloodColor() == DONT_BLEED) + return; + + if (flDamage == 0) + return; + + if (! (bitsDamageType & (DMG_CRUSH | DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB | DMG_MORTAR))) + return; + + // make blood decal on the wall! + TraceResult Bloodtr; + Vector vecTraceDir; + float flNoise; + int cCount; + int i; + +/* + if ( !IsAlive() ) + { + // dealing with a dead monster. + if ( pev->max_health <= 0 ) + { + // no blood decal for a monster that has already decalled its limit. + return; + } + else + { + pev->max_health--; + } + } +*/ + + if (flDamage < 10) + { + flNoise = 0.1; + cCount = 1; + } + else if (flDamage < 25) + { + flNoise = 0.2; + cCount = 2; + } + else + { + flNoise = 0.3; + cCount = 4; + } + + for ( i = 0 ; i < cCount ; i++ ) + { + vecTraceDir = vecDir * -1;// trace in the opposite direction the shot came from (the direction the shot is going) + + vecTraceDir.x += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.y += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.z += RANDOM_FLOAT( -flNoise, flNoise ); + + UTIL_TraceLine( ptr->vecEndPos, ptr->vecEndPos + vecTraceDir * -172, ignore_monsters, ENT(pev), &Bloodtr); + + if ( Bloodtr.flFraction != 1.0 ) + { + UTIL_BloodDecalTrace( &Bloodtr, BloodColor() ); + } + } +} + +//========================================================= +//========================================================= +void CBaseMonster :: MakeDamageBloodDecal ( int cCount, float flNoise, TraceResult *ptr, const Vector &vecDir ) +{ + // make blood decal on the wall! + TraceResult Bloodtr; + Vector vecTraceDir; + int i; + + if ( !IsAlive() ) + { + // dealing with a dead monster. + if ( pev->max_health <= 0 ) + { + // no blood decal for a monster that has already decalled its limit. + return; + } + else + { + pev->max_health--; + } + } + + for ( i = 0 ; i < cCount ; i++ ) + { + vecTraceDir = vecDir; + + vecTraceDir.x += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.y += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.z += RANDOM_FLOAT( -flNoise, flNoise ); + + UTIL_TraceLine( ptr->vecEndPos, ptr->vecEndPos + vecTraceDir * 172, ignore_monsters, ENT(pev), &Bloodtr); + +/* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + WRITE_COORD( ptr->vecEndPos.x ); + WRITE_COORD( ptr->vecEndPos.y ); + WRITE_COORD( ptr->vecEndPos.z ); + + WRITE_COORD( Bloodtr.vecEndPos.x ); + WRITE_COORD( Bloodtr.vecEndPos.y ); + WRITE_COORD( Bloodtr.vecEndPos.z ); + MESSAGE_END(); +*/ + + if ( Bloodtr.flFraction != 1.0 ) + { + UTIL_BloodDecalTrace( &Bloodtr, BloodColor() ); + } + } +} diff --git a/hlsdk/dlls/crossbow.cpp b/hlsdk/dlls/crossbow.cpp new file mode 100644 index 00000000..91510ede --- /dev/null +++ b/hlsdk/dlls/crossbow.cpp @@ -0,0 +1,548 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "gamerules.h" + +#ifndef CLIENT_DLL +#define BOLT_AIR_VELOCITY 2000 +#define BOLT_WATER_VELOCITY 1000 + +// UNDONE: Save/restore this? Don't forget to set classname and LINK_ENTITY_TO_CLASS() +// +// OVERLOADS SOME ENTVARS: +// +// speed - the ideal magnitude of my velocity +class CCrossbowBolt : public CBaseEntity +{ + void Spawn( void ); + void Precache( void ); + int Classify ( void ); + void EXPORT BubbleThink( void ); + void EXPORT BoltTouch( CBaseEntity *pOther ); + void EXPORT ExplodeThink( void ); + + int m_iTrail; + +public: + static CCrossbowBolt *BoltCreate( void ); +}; +LINK_ENTITY_TO_CLASS( crossbow_bolt, CCrossbowBolt ); + +CCrossbowBolt *CCrossbowBolt::BoltCreate( void ) +{ + // Create a new entity with CCrossbowBolt private data + CCrossbowBolt *pBolt = GetClassPtr( (CCrossbowBolt *)NULL ); + pBolt->pev->classname = MAKE_STRING("bolt"); + pBolt->Spawn(); + + return pBolt; +} + +void CCrossbowBolt::Spawn( ) +{ + Precache( ); + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + pev->gravity = 0.5; + + SET_MODEL(ENT(pev), "models/crossbow_bolt.mdl"); + + UTIL_SetOrigin( pev, pev->origin ); + UTIL_SetSize(pev, Vector(0, 0, 0), Vector(0, 0, 0)); + + SetTouch( &CCrossbowBolt::BoltTouch ); + SetThink( &CCrossbowBolt::BubbleThink ); + pev->nextthink = gpGlobals->time + 0.2; +} + + +void CCrossbowBolt::Precache( ) +{ + PRECACHE_MODEL ("models/crossbow_bolt.mdl"); + PRECACHE_SOUND("weapons/xbow_hitbod1.wav"); + PRECACHE_SOUND("weapons/xbow_hitbod2.wav"); + PRECACHE_SOUND("weapons/xbow_fly1.wav"); + PRECACHE_SOUND("weapons/xbow_hit1.wav"); + PRECACHE_SOUND("fvox/beep.wav"); + m_iTrail = PRECACHE_MODEL("sprites/streak.spr"); +} + + +int CCrossbowBolt :: Classify ( void ) +{ + return CLASS_NONE; +} + +void CCrossbowBolt::BoltTouch( CBaseEntity *pOther ) +{ + SetTouch( NULL ); + SetThink( NULL ); + + if (pOther->pev->takedamage) + { + TraceResult tr = UTIL_GetGlobalTrace( ); + entvars_t *pevOwner; + + pevOwner = VARS( pev->owner ); + + // UNDONE: this needs to call TraceAttack instead + ClearMultiDamage( ); + + if ( pOther->IsPlayer() ) + { + pOther->TraceAttack(pevOwner, gSkillData.plrDmgCrossbowClient, pev->velocity.Normalize(), &tr, DMG_NEVERGIB ); + } + else + { + pOther->TraceAttack(pevOwner, gSkillData.plrDmgCrossbowMonster, pev->velocity.Normalize(), &tr, DMG_BULLET | DMG_NEVERGIB ); + } + + ApplyMultiDamage( pev, pevOwner ); + + pev->velocity = Vector( 0, 0, 0 ); + // play body "thwack" sound + switch( RANDOM_LONG(0,1) ) + { + case 0: + EMIT_SOUND(ENT(pev), CHAN_BODY, "weapons/xbow_hitbod1.wav", 1, ATTN_NORM); break; + case 1: + EMIT_SOUND(ENT(pev), CHAN_BODY, "weapons/xbow_hitbod2.wav", 1, ATTN_NORM); break; + } + + if ( !g_pGameRules->IsMultiplayer() ) + { + Killed( pev, GIB_NEVER ); + } + } + else + { + EMIT_SOUND_DYN(ENT(pev), CHAN_BODY, "weapons/xbow_hit1.wav", RANDOM_FLOAT(0.95, 1.0), ATTN_NORM, 0, 98 + RANDOM_LONG(0,7)); + + SetThink( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time;// this will get changed below if the bolt is allowed to stick in what it hit. + + if ( FClassnameIs( pOther->pev, "worldspawn" ) ) + { + // if what we hit is static architecture, can stay around for a while. + Vector vecDir = pev->velocity.Normalize( ); + UTIL_SetOrigin( pev, pev->origin - vecDir * 12 ); + pev->angles = UTIL_VecToAngles( vecDir ); + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_FLY; + pev->velocity = Vector( 0, 0, 0 ); + pev->avelocity.z = 0; + pev->angles.z = RANDOM_LONG(0,360); + pev->nextthink = gpGlobals->time + 10.0; + } + + if (UTIL_PointContents(pev->origin) != CONTENTS_WATER) + { + UTIL_Sparks( pev->origin ); + } + } + + if ( g_pGameRules->IsMultiplayer() ) + { + SetThink( &CCrossbowBolt::ExplodeThink ); + pev->nextthink = gpGlobals->time + 0.1; + } +} + +void CCrossbowBolt::BubbleThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + if (pev->waterlevel == 0) + return; + + UTIL_BubbleTrail( pev->origin - pev->velocity * 0.1, pev->origin, 1 ); +} + +void CCrossbowBolt::ExplodeThink( void ) +{ + int iContents = UTIL_PointContents ( pev->origin ); + int iScale; + + pev->dmg = 40; + iScale = 10; + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + if (iContents != CONTENTS_WATER) + { + WRITE_SHORT( g_sModelIndexFireball ); + } + else + { + WRITE_SHORT( g_sModelIndexWExplosion ); + } + WRITE_BYTE( iScale ); // scale * 10 + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + + 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->origin, pev, pevOwner, pev->dmg, 128, CLASS_NONE, DMG_BLAST | DMG_ALWAYSGIB ); + + UTIL_Remove(this); +} +#endif + +enum crossbow_e { + CROSSBOW_IDLE1 = 0, // full + CROSSBOW_IDLE2, // empty + CROSSBOW_FIDGET1, // full + CROSSBOW_FIDGET2, // empty + CROSSBOW_FIRE1, // full + CROSSBOW_FIRE2, // reload + CROSSBOW_FIRE3, // empty + CROSSBOW_RELOAD, // from empty + CROSSBOW_DRAW1, // full + CROSSBOW_DRAW2, // empty + CROSSBOW_HOLSTER1, // full + CROSSBOW_HOLSTER2, // empty +}; + +LINK_ENTITY_TO_CLASS( weapon_crossbow, CCrossbow ); + +void CCrossbow::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_CROSSBOW; + SET_MODEL(ENT(pev), "models/w_crossbow.mdl"); + + m_iDefaultAmmo = CROSSBOW_DEFAULT_GIVE; + + FallInit();// get ready to fall down. +} + +int CCrossbow::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + +void CCrossbow::Precache( void ) +{ + PRECACHE_MODEL("models/w_crossbow.mdl"); + PRECACHE_MODEL("models/v_crossbow.mdl"); + PRECACHE_MODEL("models/p_crossbow.mdl"); + + PRECACHE_SOUND("weapons/xbow_fire1.wav"); + PRECACHE_SOUND("weapons/xbow_reload1.wav"); + + UTIL_PrecacheOther( "crossbow_bolt" ); + + m_usCrossbow = PRECACHE_EVENT( 1, "events/crossbow1.sc" ); + m_usCrossbow2 = PRECACHE_EVENT( 1, "events/crossbow2.sc" ); +} + + +int CCrossbow::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "bolts"; + p->iMaxAmmo1 = BOLT_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = CROSSBOW_MAX_CLIP; + p->iSlot = 2; + p->iPosition = 2; + p->iId = WEAPON_CROSSBOW; + p->iFlags = 0; + p->iWeight = CROSSBOW_WEIGHT; + return 1; +} + + +BOOL CCrossbow::Deploy( ) +{ + if (m_iClip) + return DefaultDeploy( "models/v_crossbow.mdl", "models/p_crossbow.mdl", CROSSBOW_DRAW1, "bow" ); + return DefaultDeploy( "models/v_crossbow.mdl", "models/p_crossbow.mdl", CROSSBOW_DRAW2, "bow" ); +} + +void CCrossbow::Holster( int skiplocal /* = 0 */ ) +{ + m_fInReload = FALSE;// cancel any reload in progress. + + if ( m_fInZoom ) + { + SecondaryAttack( ); + } + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + if (m_iClip) + SendWeaponAnim( CROSSBOW_HOLSTER1 ); + else + SendWeaponAnim( CROSSBOW_HOLSTER2 ); +} + +void CCrossbow::PrimaryAttack( void ) +{ + +#ifdef CLIENT_DLL + if ( m_fInZoom && bIsMultiplayer() ) +#else + if ( m_fInZoom && g_pGameRules->IsMultiplayer() ) +#endif + { + FireSniperBolt(); + return; + } + + FireBolt(); +} + +// this function only gets called in multiplayer +void CCrossbow::FireSniperBolt() +{ + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.75; + + if (m_iClip == 0) + { + PlayEmptySound( ); + return; + } + + TraceResult tr; + + m_pPlayer->m_iWeaponVolume = QUIET_GUN_VOLUME; + m_iClip--; + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usCrossbow2, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0, 0, m_iClip, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType], 0, 0 ); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + Vector anglesAim = m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle; + UTIL_MakeVectors( anglesAim ); + Vector vecSrc = m_pPlayer->GetGunPosition( ) - gpGlobals->v_up * 2; + Vector vecDir = gpGlobals->v_forward; + + UTIL_TraceLine(vecSrc, vecSrc + vecDir * 8192, dont_ignore_monsters, m_pPlayer->edict(), &tr); + +#ifndef CLIENT_DLL + if ( tr.pHit->v.takedamage ) + { + ClearMultiDamage( ); + CBaseEntity::Instance(tr.pHit)->TraceAttack(m_pPlayer->pev, 120, vecDir, &tr, DMG_BULLET | DMG_NEVERGIB ); + ApplyMultiDamage( pev, m_pPlayer->pev ); + } +#endif +} + +void CCrossbow::FireBolt() +{ + TraceResult tr; + + if (m_iClip == 0) + { + PlayEmptySound( ); + return; + } + + m_pPlayer->m_iWeaponVolume = QUIET_GUN_VOLUME; + + m_iClip--; + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usCrossbow, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0, 0, m_iClip, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType], 0, 0 ); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + Vector anglesAim = m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle; + UTIL_MakeVectors( anglesAim ); + + anglesAim.x = -anglesAim.x; + Vector vecSrc = m_pPlayer->GetGunPosition( ) - gpGlobals->v_up * 2; + Vector vecDir = gpGlobals->v_forward; + +#ifndef CLIENT_DLL + CCrossbowBolt *pBolt = CCrossbowBolt::BoltCreate(); + pBolt->pev->origin = vecSrc; + pBolt->pev->angles = anglesAim; + pBolt->pev->owner = m_pPlayer->edict(); + + if (m_pPlayer->pev->waterlevel == 3) + { + pBolt->pev->velocity = vecDir * BOLT_WATER_VELOCITY; + pBolt->pev->speed = BOLT_WATER_VELOCITY; + } + else + { + pBolt->pev->velocity = vecDir * BOLT_AIR_VELOCITY; + pBolt->pev->speed = BOLT_AIR_VELOCITY; + } + pBolt->pev->avelocity.z = 10; +#endif + + if (!m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + // HEV suit - indicate out of ammo condition + m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.75; + + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.75; + + if (m_iClip != 0) + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 5.0; + else + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.75; +} + + +void CCrossbow::SecondaryAttack() +{ + if ( m_pPlayer->pev->fov != 0 ) + { + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = 0; // 0 means reset to default fov + m_fInZoom = 0; + } + else if ( m_pPlayer->pev->fov != 20 ) + { + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = 20; + m_fInZoom = 1; + } + + pev->nextthink = UTIL_WeaponTimeBase() + 0.1; + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 1.0; +} + + +void CCrossbow::Reload( void ) +{ + if ( m_pPlayer->ammo_bolts <= 0 ) + return; + + if ( m_pPlayer->pev->fov != 0 ) + { + SecondaryAttack(); + } + + if ( DefaultReload( 5, CROSSBOW_RELOAD, 4.5 ) ) + { + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/xbow_reload1.wav", RANDOM_FLOAT(0.95, 1.0), ATTN_NORM, 0, 93 + RANDOM_LONG(0,0xF)); + } +} + + +void CCrossbow::WeaponIdle( void ) +{ + m_pPlayer->GetAutoaimVector( AUTOAIM_2DEGREES ); // get the autoaim vector but ignore it; used for autoaim crosshair in DM + + ResetEmptySound( ); + + if ( m_flTimeWeaponIdle < UTIL_WeaponTimeBase() ) + { + float flRand = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 0, 1 ); + if (flRand <= 0.75) + { + if (m_iClip) + { + SendWeaponAnim( CROSSBOW_IDLE1 ); + } + else + { + SendWeaponAnim( CROSSBOW_IDLE2 ); + } + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + } + else + { + if (m_iClip) + { + SendWeaponAnim( CROSSBOW_FIDGET1 ); + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 90.0 / 30.0; + } + else + { + SendWeaponAnim( CROSSBOW_FIDGET2 ); + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 80.0 / 30.0; + } + } + } +} + + + +class CCrossbowAmmo : public CBasePlayerAmmo +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_crossbow_clip.mdl"); + CBasePlayerAmmo::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_crossbow_clip.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + } + BOOL AddAmmo( CBaseEntity *pOther ) + { + if (pOther->GiveAmmo( AMMO_CROSSBOWCLIP_GIVE, "bolts", BOLT_MAX_CARRY ) != -1) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + return TRUE; + } + return FALSE; + } +}; +LINK_ENTITY_TO_CLASS( ammo_crossbow, CCrossbowAmmo ); + + + +#endif diff --git a/hlsdk/dlls/crowbar.cpp b/hlsdk/dlls/crowbar.cpp new file mode 100644 index 00000000..6ec87766 --- /dev/null +++ b/hlsdk/dlls/crowbar.cpp @@ -0,0 +1,318 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "gamerules.h" + + +#define CROWBAR_BODYHIT_VOLUME 128 +#define CROWBAR_WALLHIT_VOLUME 512 + +LINK_ENTITY_TO_CLASS( weapon_crowbar, CCrowbar ); + + + +enum gauss_e { + CROWBAR_IDLE = 0, + CROWBAR_DRAW, + CROWBAR_HOLSTER, + CROWBAR_ATTACK1HIT, + CROWBAR_ATTACK1MISS, + CROWBAR_ATTACK2MISS, + CROWBAR_ATTACK2HIT, + CROWBAR_ATTACK3MISS, + CROWBAR_ATTACK3HIT +}; + + +void CCrowbar::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_CROWBAR; + SET_MODEL(ENT(pev), "models/w_crowbar.mdl"); + m_iClip = -1; + + FallInit();// get ready to fall down. +} + + +void CCrowbar::Precache( void ) +{ + PRECACHE_MODEL("models/v_crowbar.mdl"); + PRECACHE_MODEL("models/w_crowbar.mdl"); + PRECACHE_MODEL("models/p_crowbar.mdl"); + PRECACHE_SOUND("weapons/cbar_hit1.wav"); + PRECACHE_SOUND("weapons/cbar_hit2.wav"); + PRECACHE_SOUND("weapons/cbar_hitbod1.wav"); + PRECACHE_SOUND("weapons/cbar_hitbod2.wav"); + PRECACHE_SOUND("weapons/cbar_hitbod3.wav"); + PRECACHE_SOUND("weapons/cbar_miss1.wav"); + + m_usCrowbar = PRECACHE_EVENT ( 1, "events/crowbar.sc" ); +} + +int CCrowbar::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = NULL; + p->iMaxAmmo1 = -1; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 0; + p->iPosition = 0; + p->iId = WEAPON_CROWBAR; + p->iWeight = CROWBAR_WEIGHT; + return 1; +} + + + +BOOL CCrowbar::Deploy( ) +{ + return DefaultDeploy( "models/v_crowbar.mdl", "models/p_crowbar.mdl", CROWBAR_DRAW, "crowbar" ); +} + +void CCrowbar::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + SendWeaponAnim( CROWBAR_HOLSTER ); +} + + +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; + } + } + } + } + } +} + + +void CCrowbar::PrimaryAttack() +{ + if (! Swing( 1 )) + { + SetThink( &CCrowbar::SwingAgain ); + pev->nextthink = gpGlobals->time + 0.1; + } +} + + +void CCrowbar::Smack( ) +{ + DecalGunshot( &m_trHit, BULLET_PLAYER_CROWBAR ); +} + + +void CCrowbar::SwingAgain( void ) +{ + Swing( 0 ); +} + + +int CCrowbar::Swing( int fFirst ) +{ + int fDidHit = FALSE; + + TraceResult tr; + + UTIL_MakeVectors (m_pPlayer->pev->v_angle); + Vector vecSrc = m_pPlayer->GetGunPosition( ); + 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) + } + } +#endif + + PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), m_usCrowbar, + 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0, 0, 0, + 0, 0, 0 ); + + + if ( tr.flFraction >= 1.0 ) + { + if (fFirst) + { + // miss + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.5; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + } + } + else + { + switch( ((m_iSwing++) % 2) + 1 ) + { + case 0: + SendWeaponAnim( CROWBAR_ATTACK1HIT ); break; + case 1: + SendWeaponAnim( CROWBAR_ATTACK2HIT ); break; + case 2: + SendWeaponAnim( CROWBAR_ATTACK3HIT ); break; + } + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + +#ifndef CLIENT_DLL + + // hit + fDidHit = TRUE; + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + + ClearMultiDamage( ); + + if ( (m_flNextPrimaryAttack + 1 < UTIL_WeaponTimeBase() ) || g_pGameRules->IsMultiplayer() ) + { + // first swing does full damage + pEntity->TraceAttack(m_pPlayer->pev, gSkillData.plrDmgCrowbar, gpGlobals->v_forward, &tr, DMG_CLUB ); + } + else + { + // subsequent swings do half + pEntity->TraceAttack(m_pPlayer->pev, gSkillData.plrDmgCrowbar / 2, gpGlobals->v_forward, &tr, DMG_CLUB ); + } + ApplyMultiDamage( m_pPlayer->pev, m_pPlayer->pev ); + + // play thwack, smack, or dong sound + float flVol = 1.0; + int fHitWorld = 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; + } + m_pPlayer->m_iWeaponVolume = CROWBAR_BODYHIT_VOLUME; + if ( !pEntity->IsAlive() ) + return TRUE; + else + flVol = 0.1; + + fHitWorld = FALSE; + } + } + + // play texture hit sound + // UNDONE: Calculate the correct point of intersection when we hit with the hull instead of the line + + if (fHitWorld) + { + float fvolbar = TEXTURETYPE_PlaySound(&tr, vecSrc, vecSrc + (vecEnd-vecSrc)*2, BULLET_PLAYER_CROWBAR); + + if ( g_pGameRules->IsMultiplayer() ) + { + // override the volume here, cause we don't play texture sounds in multiplayer, + // and fvolbar is going to be 0 from the above call. + + fvolbar = 1; + } + + // also play crowbar strike + switch( RANDOM_LONG(0,1) ) + { + case 0: + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hit1.wav", fvolbar, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + break; + case 1: + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hit2.wav", fvolbar, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + break; + } + + // delay the decal a bit + m_trHit = tr; + } + + m_pPlayer->m_iWeaponVolume = static_cast(flVol * CROWBAR_WALLHIT_VOLUME); +#endif + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.25; + + SetThink( &CCrowbar::Smack ); + pev->nextthink = UTIL_WeaponTimeBase() + 0.2; + + + } + return fDidHit; +} + + + diff --git a/hlsdk/dlls/decals.h b/hlsdk/dlls/decals.h new file mode 100644 index 00000000..95fa44f5 --- /dev/null +++ b/hlsdk/dlls/decals.h @@ -0,0 +1,75 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef DECALS_H +#define DECALS_H + +// +// Dynamic Decals +// +enum decal_e +{ + DECAL_GUNSHOT1 = 0, + DECAL_GUNSHOT2, + DECAL_GUNSHOT3, + DECAL_GUNSHOT4, + DECAL_GUNSHOT5, + DECAL_LAMBDA1, + DECAL_LAMBDA2, + DECAL_LAMBDA3, + DECAL_LAMBDA4, + DECAL_LAMBDA5, + DECAL_LAMBDA6, + DECAL_SCORCH1, + DECAL_SCORCH2, + DECAL_BLOOD1, + DECAL_BLOOD2, + DECAL_BLOOD3, + DECAL_BLOOD4, + DECAL_BLOOD5, + DECAL_BLOOD6, + DECAL_YBLOOD1, + DECAL_YBLOOD2, + DECAL_YBLOOD3, + DECAL_YBLOOD4, + DECAL_YBLOOD5, + DECAL_YBLOOD6, + DECAL_GLASSBREAK1, + DECAL_GLASSBREAK2, + DECAL_GLASSBREAK3, + DECAL_BIGSHOT1, + DECAL_BIGSHOT2, + DECAL_BIGSHOT3, + DECAL_BIGSHOT4, + DECAL_BIGSHOT5, + DECAL_SPIT1, + DECAL_SPIT2, + DECAL_BPROOF1, // Bulletproof glass decal + DECAL_GARGSTOMP1, // Gargantua stomp crack + DECAL_SMALLSCORCH1, // Small scorch mark + DECAL_SMALLSCORCH2, // Small scorch mark + DECAL_SMALLSCORCH3, // Small scorch mark + DECAL_MOMMABIRTH, // Big momma birth splatter + DECAL_MOMMASPLAT, +}; + +typedef struct +{ + char *name; + int index; +} DLL_DECALLIST; + +extern DLL_DECALLIST gDecals[]; + +#endif // DECALS_H diff --git a/hlsdk/dlls/doors.cpp b/hlsdk/dlls/doors.cpp new file mode 100644 index 00000000..21a1600e --- /dev/null +++ b/hlsdk/dlls/doors.cpp @@ -0,0 +1,1026 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== doors.cpp ======================================================== + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "doors.h" + + +extern void SetMovedir(entvars_t* ev); + +#define noiseMoving noise1 +#define noiseArrived noise2 + +class CBaseDoor : public CBaseToggle +{ +public: + void Spawn( void ); + void Precache( void ); + virtual void KeyValue( KeyValueData *pkvd ); + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual void Blocked( CBaseEntity *pOther ); + + + virtual int ObjectCaps( void ) + { + if (pev->spawnflags & SF_ITEM_USE_ONLY) + return (CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_IMPULSE_USE; + else + return (CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION); + }; + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + virtual void SetToggleState( int state ); + + // used to selectivly override defaults + void EXPORT DoorTouch( CBaseEntity *pOther ); + + // local functions + int DoorActivate( ); + void EXPORT DoorGoUp( void ); + void EXPORT DoorGoDown( void ); + void EXPORT DoorHitTop( void ); + void EXPORT DoorHitBottom( void ); + + BYTE m_bHealthValue;// some doors are medi-kit doors, they give players health + + BYTE m_bMoveSnd; // sound a door makes while moving + BYTE m_bStopSnd; // sound a door makes when it stops + + locksound_t m_ls; // door lock sounds + + BYTE m_bLockedSound; // ordinals from entity selection + BYTE m_bLockedSentence; + BYTE m_bUnlockedSound; + BYTE m_bUnlockedSentence; +}; + + +TYPEDESCRIPTION CBaseDoor::m_SaveData[] = +{ + DEFINE_FIELD( CBaseDoor, m_bHealthValue, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bMoveSnd, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bStopSnd, FIELD_CHARACTER ), + + DEFINE_FIELD( CBaseDoor, m_bLockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bLockedSentence, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bUnlockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bUnlockedSentence, FIELD_CHARACTER ), + +}; + +IMPLEMENT_SAVERESTORE( CBaseDoor, CBaseToggle ); + + +#define DOOR_SENTENCEWAIT 6 +#define DOOR_SOUNDWAIT 3 +#define BUTTON_SOUNDWAIT 0.5 + +// play door or button locked or unlocked sounds. +// pass in pointer to valid locksound struct. +// if flocked is true, play 'door is locked' sound, +// otherwise play 'door is unlocked' sound +// NOTE: this routine is shared by doors and buttons + +void PlayLockSounds(entvars_t *pev, locksound_t *pls, int flocked, int fbutton) +{ + // LOCKED SOUND + + // CONSIDER: consolidate the locksound_t struct (all entries are duplicates for lock/unlock) + // CONSIDER: and condense this code. + float flsoundwait; + + if (fbutton) + flsoundwait = BUTTON_SOUNDWAIT; + else + flsoundwait = DOOR_SOUNDWAIT; + + if (flocked) + { + int fplaysound = (pls->sLockedSound && gpGlobals->time > pls->flwaitSound); + int fplaysentence = (pls->sLockedSentence && !pls->bEOFLocked && gpGlobals->time > pls->flwaitSentence); + float fvol; + + if (fplaysound && fplaysentence) + fvol = 0.25; + else + fvol = 1.0; + + // if there is a locked sound, and we've debounced, play sound + if (fplaysound) + { + // play 'door locked' sound + EMIT_SOUND(ENT(pev), CHAN_ITEM, (char*)STRING(pls->sLockedSound), fvol, ATTN_NORM); + pls->flwaitSound = gpGlobals->time + flsoundwait; + } + + // if there is a sentence, we've not played all in list, and we've debounced, play sound + if (fplaysentence) + { + // play next 'door locked' sentence in group + int iprev = pls->iLockedSentence; + + pls->iLockedSentence = SENTENCEG_PlaySequentialSz(ENT(pev), STRING(pls->sLockedSentence), + 0.85, ATTN_NORM, 0, 100, pls->iLockedSentence, FALSE); + pls->iUnlockedSentence = 0; + + // make sure we don't keep calling last sentence in list + pls->bEOFLocked = (iprev == pls->iLockedSentence); + + pls->flwaitSentence = gpGlobals->time + DOOR_SENTENCEWAIT; + } + } + else + { + // UNLOCKED SOUND + + int fplaysound = (pls->sUnlockedSound && gpGlobals->time > pls->flwaitSound); + int fplaysentence = (pls->sUnlockedSentence && !pls->bEOFUnlocked && gpGlobals->time > pls->flwaitSentence); + float fvol; + + // if playing both sentence and sound, lower sound volume so we hear sentence + if (fplaysound && fplaysentence) + fvol = 0.25; + else + fvol = 1.0; + + // play 'door unlocked' sound if set + if (fplaysound) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, (char*)STRING(pls->sUnlockedSound), fvol, ATTN_NORM); + pls->flwaitSound = gpGlobals->time + flsoundwait; + } + + // play next 'door unlocked' sentence in group + if (fplaysentence) + { + int iprev = pls->iUnlockedSentence; + + pls->iUnlockedSentence = SENTENCEG_PlaySequentialSz(ENT(pev), STRING(pls->sUnlockedSentence), + 0.85, ATTN_NORM, 0, 100, pls->iUnlockedSentence, FALSE); + pls->iLockedSentence = 0; + + // make sure we don't keep calling last sentence in list + pls->bEOFUnlocked = (iprev == pls->iUnlockedSentence); + pls->flwaitSentence = gpGlobals->time + DOOR_SENTENCEWAIT; + } + } +} + +// +// Cache user-entity-field values until spawn is called. +// + +void CBaseDoor::KeyValue( KeyValueData *pkvd ) +{ + + if (FStrEq(pkvd->szKeyName, "skin"))//skin is used for content type + { + pev->skin = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "movesnd")) + { + m_bMoveSnd = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "stopsnd")) + { + m_bStopSnd = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "healthvalue")) + { + m_bHealthValue = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sound")) + { + m_bLockedSound = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sentence")) + { + m_bLockedSentence = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sound")) + { + m_bUnlockedSound = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sentence")) + { + m_bUnlockedSentence = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "WaveHeight")) + { + pev->scale = atof(pkvd->szValue) * (1.0/8.0); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +/*QUAKED func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK TOGGLE +if two doors touch, they are assumed to be connected and operate as a unit. + +TOGGLE causes the door to wait in both the start and end states for a trigger event. + +START_OPEN causes the door to move to its destination when spawned, and operate in reverse. +It is used to temporarily or permanently close off an area when triggered (not usefull for +touch or takedamage doors). + +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger + field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"lip" lip remaining at end of move (8 default) +"dmg" damage to inflict when blocked (2 default) +"sounds" +0) no sound +1) stone +2) base +3) stone chain +4) screechy metal +*/ + +LINK_ENTITY_TO_CLASS( func_door, CBaseDoor ); +// +// func_water - same as a door. +// +LINK_ENTITY_TO_CLASS( func_water, CBaseDoor ); + + +void CBaseDoor::Spawn( ) +{ + Precache(); + SetMovedir (pev); + + if ( pev->skin == 0 ) + {//normal door + if ( FBitSet (pev->spawnflags, SF_DOOR_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + } + else + {// special contents + pev->solid = SOLID_NOT; + SetBits( pev->spawnflags, SF_DOOR_SILENT ); // water is silent for now + } + + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + if (pev->speed == 0) + pev->speed = 100; + + m_vecPosition1 = pev->origin; + // Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big + m_vecPosition2 = m_vecPosition1 + (pev->movedir * (fabs( pev->movedir.x * (pev->size.x-2) ) + fabs( pev->movedir.y * (pev->size.y-2) ) + fabs( pev->movedir.z * (pev->size.z-2) ) - m_flLip)); + ASSERTSZ(m_vecPosition1 != m_vecPosition2, "door start/end positions are equal"); + if ( FBitSet (pev->spawnflags, SF_DOOR_START_OPEN) ) + { // swap pos1 and pos2, put door at pos2 + UTIL_SetOrigin(pev, m_vecPosition2); + m_vecPosition2 = m_vecPosition1; + m_vecPosition1 = pev->origin; + } + + m_toggle_state = TS_AT_BOTTOM; + + // if the door is flagged for USE button activation only, use NULL touch function + if ( FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) ) + { + SetTouch ( NULL ); + } + else // touchable button + SetTouch( &CBaseDoor::DoorTouch ); +} + + +void CBaseDoor :: SetToggleState( int state ) +{ + if ( state == TS_AT_TOP ) + UTIL_SetOrigin( pev, m_vecPosition2 ); + else + UTIL_SetOrigin( pev, m_vecPosition1 ); +} + + +void CBaseDoor::Precache( void ) +{ + char *pszSound; + +// set the door's "in-motion" sound + switch (m_bMoveSnd) + { + case 0: + pev->noiseMoving = ALLOC_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("doors/doormove1.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove1.wav"); + break; + case 2: + PRECACHE_SOUND ("doors/doormove2.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove2.wav"); + break; + case 3: + PRECACHE_SOUND ("doors/doormove3.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove3.wav"); + break; + case 4: + PRECACHE_SOUND ("doors/doormove4.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove4.wav"); + break; + case 5: + PRECACHE_SOUND ("doors/doormove5.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove5.wav"); + break; + case 6: + PRECACHE_SOUND ("doors/doormove6.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove6.wav"); + break; + case 7: + PRECACHE_SOUND ("doors/doormove7.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove7.wav"); + break; + case 8: + PRECACHE_SOUND ("doors/doormove8.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove8.wav"); + break; + case 9: + PRECACHE_SOUND ("doors/doormove9.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove9.wav"); + break; + case 10: + PRECACHE_SOUND ("doors/doormove10.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove10.wav"); + break; + default: + pev->noiseMoving = ALLOC_STRING("common/null.wav"); + break; + } + +// set the door's 'reached destination' stop sound + switch (m_bStopSnd) + { + case 0: + pev->noiseArrived = ALLOC_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("doors/doorstop1.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop1.wav"); + break; + case 2: + PRECACHE_SOUND ("doors/doorstop2.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop2.wav"); + break; + case 3: + PRECACHE_SOUND ("doors/doorstop3.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop3.wav"); + break; + case 4: + PRECACHE_SOUND ("doors/doorstop4.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop4.wav"); + break; + case 5: + PRECACHE_SOUND ("doors/doorstop5.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop5.wav"); + break; + case 6: + PRECACHE_SOUND ("doors/doorstop6.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop6.wav"); + break; + case 7: + PRECACHE_SOUND ("doors/doorstop7.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop7.wav"); + break; + case 8: + PRECACHE_SOUND ("doors/doorstop8.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop8.wav"); + break; + default: + pev->noiseArrived = ALLOC_STRING("common/null.wav"); + break; + } + + // get door button sounds, for doors which are directly 'touched' to open + + if (m_bLockedSound) + { + pszSound = ButtonSound( (int)m_bLockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sLockedSound = ALLOC_STRING(pszSound); + } + + if (m_bUnlockedSound) + { + pszSound = ButtonSound( (int)m_bUnlockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sUnlockedSound = ALLOC_STRING(pszSound); + } + + // get sentence group names, for doors which are directly 'touched' to open + + switch (m_bLockedSentence) + { + case 1: m_ls.sLockedSentence = ALLOC_STRING("NA"); break; // access denied + case 2: m_ls.sLockedSentence = ALLOC_STRING("ND"); break; // security lockout + case 3: m_ls.sLockedSentence = ALLOC_STRING("NF"); break; // blast door + case 4: m_ls.sLockedSentence = ALLOC_STRING("NFIRE"); break; // fire door + case 5: m_ls.sLockedSentence = ALLOC_STRING("NCHEM"); break; // chemical door + case 6: m_ls.sLockedSentence = ALLOC_STRING("NRAD"); break; // radiation door + case 7: m_ls.sLockedSentence = ALLOC_STRING("NCON"); break; // gen containment + case 8: m_ls.sLockedSentence = ALLOC_STRING("NH"); break; // maintenance door + case 9: m_ls.sLockedSentence = ALLOC_STRING("NG"); break; // broken door + + default: m_ls.sLockedSentence = 0; break; + } + + switch (m_bUnlockedSentence) + { + case 1: m_ls.sUnlockedSentence = ALLOC_STRING("EA"); break; // access granted + case 2: m_ls.sUnlockedSentence = ALLOC_STRING("ED"); break; // security door + case 3: m_ls.sUnlockedSentence = ALLOC_STRING("EF"); break; // blast door + case 4: m_ls.sUnlockedSentence = ALLOC_STRING("EFIRE"); break; // fire door + case 5: m_ls.sUnlockedSentence = ALLOC_STRING("ECHEM"); break; // chemical door + case 6: m_ls.sUnlockedSentence = ALLOC_STRING("ERAD"); break; // radiation door + case 7: m_ls.sUnlockedSentence = ALLOC_STRING("ECON"); break; // gen containment + case 8: m_ls.sUnlockedSentence = ALLOC_STRING("EH"); break; // maintenance door + + default: m_ls.sUnlockedSentence = 0; break; + } +} + +// +// Doors not tied to anything (e.g. button, another door) can be touched, to make them activate. +// +void CBaseDoor::DoorTouch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + + // Ignore touches by anything but players + if (!FClassnameIs(pevToucher, "player")) + return; + + // If door has master, and it's not ready to trigger, + // play 'locked' sound + + if (m_sMaster && !UTIL_IsMasterTriggered(m_sMaster, pOther)) + PlayLockSounds(pev, &m_ls, TRUE, FALSE); + + // If door is somebody's target, then touching does nothing. + // You have to activate the owner (e.g. button). + + if (!FStringNull(pev->targetname)) + { + // play locked sound + PlayLockSounds(pev, &m_ls, TRUE, FALSE); + return; + } + + m_hActivator = pOther;// remember who activated the door + + if (DoorActivate( )) + SetTouch( NULL ); // Temporarily disable the touch function, until movement is finished. +} + + +// +// Used by SUB_UseTargets, when a door is the target of a button. +// +void CBaseDoor::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_hActivator = pActivator; + // if not ready to be used, ignore "use" command. + if (m_toggle_state == TS_AT_BOTTOM || FBitSet(pev->spawnflags, SF_DOOR_NO_AUTO_RETURN) && m_toggle_state == TS_AT_TOP) + DoorActivate(); +} + +// +// Causes the door to "do its thing", i.e. start moving, and cascade activation. +// +int CBaseDoor::DoorActivate( ) +{ + if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + return 0; + + if (FBitSet(pev->spawnflags, SF_DOOR_NO_AUTO_RETURN) && m_toggle_state == TS_AT_TOP) + {// door should close + DoorGoDown(); + } + else + {// door should open + + if ( m_hActivator != 0 && m_hActivator->IsPlayer() ) + {// give health if player opened the door (medikit) + // VARS( m_eoActivator )->health += m_bHealthValue; + + m_hActivator->TakeHealth( m_bHealthValue, DMG_GENERIC ); + + } + + // play door unlock sounds + PlayLockSounds(pev, &m_ls, FALSE, FALSE); + + DoorGoUp(); + } + + return 1; +} + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); + +// +// Starts the door going to its "up" position (simply ToggleData->vecPosition2). +// +void CBaseDoor::DoorGoUp( void ) +{ + entvars_t *pevActivator; + + // It could be going-down, if blocked. + ASSERT(m_toggle_state == TS_AT_BOTTOM || m_toggle_state == TS_GOING_DOWN); + + // emit door moving and stop sounds on CHAN_STATIC so that the multicast doesn't + // filter them out and leave a client stuck with looping door sounds! + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving), 1, ATTN_NORM); + + m_toggle_state = TS_GOING_UP; + + SetMoveDone( &CBaseDoor::DoorHitTop ); + if ( FClassnameIs(pev, "func_door_rotating")) // !!! BUGBUG Triggered doors don't work with this yet + { + float sign = 1.0; + + if ( m_hActivator != 0 ) + { + pevActivator = m_hActivator->pev; + + if ( !FBitSet( pev->spawnflags, SF_DOOR_ONEWAY ) && pev->movedir.y ) // Y axis rotation, move away from the player + { + Vector vec = pevActivator->origin - pev->origin; + Vector angles = pevActivator->angles; + angles.x = 0; + angles.z = 0; + UTIL_MakeVectors (angles); + // Vector vnext = (pevToucher->origin + (pevToucher->velocity * 10)) - pev->origin; + UTIL_MakeVectors ( pevActivator->angles ); + Vector vnext = (pevActivator->origin + (gpGlobals->v_forward * 10)) - pev->origin; + if ( (vec.x*vnext.y - vec.y*vnext.x) < 0 ) + sign = -1.0; + } + } + AngularMove(m_vecAngle2*sign, pev->speed); + } + else + LinearMove(m_vecPosition2, pev->speed); +} + + +// +// The door has reached the "up" position. Either go back down, or wait for another activation. +// +void CBaseDoor::DoorHitTop( void ) +{ + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + { + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving) ); + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseArrived), 1, ATTN_NORM); + } + + ASSERT(m_toggle_state == TS_GOING_UP); + m_toggle_state = TS_AT_TOP; + + // toggle-doors don't come down automatically, they wait for refire. + if (FBitSet(pev->spawnflags, SF_DOOR_NO_AUTO_RETURN)) + { + // Re-instate touch method, movement is complete + if ( !FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) ) + SetTouch( &CBaseDoor::DoorTouch ); + } + else + { + // In flWait seconds, DoorGoDown will fire, unless wait is -1, then door stays open + pev->nextthink = pev->ltime + m_flWait; + SetThink( &CBaseDoor::DoorGoDown ); + + if ( m_flWait == -1 ) + { + pev->nextthink = -1; + } + } + + // Fire the close target (if startopen is set, then "top" is closed) - netname is the close target + if ( pev->netname && (pev->spawnflags & SF_DOOR_START_OPEN) ) + FireTargets( STRING(pev->netname), m_hActivator, this, USE_TOGGLE, 0 ); + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); // this isn't finished +} + + +// +// Starts the door going to its "down" position (simply ToggleData->vecPosition1). +// +void CBaseDoor::DoorGoDown( void ) +{ + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving), 1, ATTN_NORM); + +#ifdef DOOR_ASSERT + ASSERT(m_toggle_state == TS_AT_TOP); +#endif // DOOR_ASSERT + m_toggle_state = TS_GOING_DOWN; + + SetMoveDone( &CBaseDoor::DoorHitBottom ); + if ( FClassnameIs(pev, "func_door_rotating"))//rotating door + AngularMove( m_vecAngle1, pev->speed); + else + LinearMove( m_vecPosition1, pev->speed); +} + +// +// The door has reached the "down" position. Back to quiescence. +// +void CBaseDoor::DoorHitBottom( void ) +{ + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + { + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving) ); + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseArrived), 1, ATTN_NORM); + } + + ASSERT(m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_AT_BOTTOM; + + // Re-instate touch method, cycle is complete + if ( FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) ) + {// use only door + SetTouch ( NULL ); + } + else // touchable door + SetTouch( &CBaseDoor::DoorTouch ); + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); // this isn't finished + + // Fire the close target (if startopen is set, then "top" is closed) - netname is the close target + if ( pev->netname && !(pev->spawnflags & SF_DOOR_START_OPEN) ) + FireTargets( STRING(pev->netname), m_hActivator, this, USE_TOGGLE, 0 ); +} + +void CBaseDoor::Blocked( CBaseEntity *pOther ) +{ + edict_t *pentTarget = NULL; + CBaseDoor *pDoor = NULL; + + + // Hurt the blocker a little. + if ( pev->dmg ) + pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH ); + + // if a door has a negative wait, it would never come back if blocked, + // so let it just squash the object to death real fast + + if (m_flWait >= 0) + { + if (m_toggle_state == TS_GOING_DOWN) + { + DoorGoUp(); + } + else + { + DoorGoDown(); + } + } + + // Block all door pieces with the same targetname here. + if ( !FStringNull ( pev->targetname ) ) + { + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->targetname)); + + if ( VARS( pentTarget ) != pev ) + { + if (FNullEnt(pentTarget)) + break; + + if ( FClassnameIs ( pentTarget, "func_door" ) || FClassnameIs ( pentTarget, "func_door_rotating" ) ) + { + + pDoor = GetClassPtr( (CBaseDoor *) VARS(pentTarget) ); + + if ( pDoor->m_flWait >= 0) + { + if (pDoor->pev->velocity == pev->velocity && pDoor->pev->avelocity == pev->velocity) + { + // this is the most hacked, evil, bastardized thing I've ever seen. kjb + if ( FClassnameIs ( pentTarget, "func_door" ) ) + {// set origin to realign normal doors + pDoor->pev->origin = pev->origin; + pDoor->pev->velocity = g_vecZero;// stop! + } + else + {// set angles to realign rotating doors + pDoor->pev->angles = pev->angles; + pDoor->pev->avelocity = g_vecZero; + } + } + + if ( pDoor->m_toggle_state == TS_GOING_DOWN) + pDoor->DoorGoUp(); + else + pDoor->DoorGoDown(); + } + } + } + } + } +} + + +/*QUAKED FuncRotDoorSpawn (0 .5 .8) ? START_OPEN REVERSE +DOOR_DONT_LINK TOGGLE X_AXIS Y_AXIS +if two doors touch, they are assumed to be connected and operate as +a unit. + +TOGGLE causes the door to wait in both the start and end states for +a trigger event. + +START_OPEN causes the door to move to its destination when spawned, +and operate in reverse. It is used to temporarily or permanently +close off an area when triggered (not usefull for touch or +takedamage doors). + +You need to have an origin brush as part of this entity. The +center of that brush will be +the point around which it is rotated. It will rotate around the Z +axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"distance" is how many degrees the door will be rotated. +"speed" determines how fast the door moves; default value is 100. + +REVERSE will cause the door to rotate in the opposite direction. + +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote +button or trigger field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"dmg" damage to inflict when blocked (2 default) +"sounds" +0) no sound +1) stone +2) base +3) stone chain +4) screechy metal +*/ +class CRotDoor : public CBaseDoor +{ +public: + void Spawn( void ); + virtual void SetToggleState( int state ); +}; + +LINK_ENTITY_TO_CLASS( func_door_rotating, CRotDoor ); + + +void CRotDoor::Spawn( void ) +{ + Precache(); + // set the axis of rotation + CBaseToggle::AxisDir( pev ); + + // check for clockwise rotation + if ( FBitSet (pev->spawnflags, SF_DOOR_ROTATE_BACKWARDS) ) + pev->movedir = pev->movedir * -1; + + //m_flWait = 2; who the hell did this? (sjb) + m_vecAngle1 = pev->angles; + m_vecAngle2 = pev->angles + pev->movedir * m_flMoveDistance; + + ASSERTSZ(m_vecAngle1 != m_vecAngle2, "rotating door start/end positions are equal"); + + if ( FBitSet (pev->spawnflags, SF_DOOR_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + if (pev->speed == 0) + pev->speed = 100; + +// DOOR_START_OPEN is to allow an entity to be lighted in the closed position +// but spawn in the open position + if ( FBitSet (pev->spawnflags, SF_DOOR_START_OPEN) ) + { // swap pos1 and pos2, put door at pos2, invert movement direction + pev->angles = m_vecAngle2; + Vector vecSav = m_vecAngle1; + m_vecAngle2 = m_vecAngle1; + m_vecAngle1 = vecSav; + pev->movedir = pev->movedir * -1; + } + + m_toggle_state = TS_AT_BOTTOM; + + if ( FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) ) + { + SetTouch ( NULL ); + } + else // touchable button + SetTouch( &CBaseDoor::DoorTouch ); +} + + +void CRotDoor :: SetToggleState( int state ) +{ + if ( state == TS_AT_TOP ) + pev->angles = m_vecAngle2; + else + pev->angles = m_vecAngle1; + + UTIL_SetOrigin( pev, pev->origin ); +} + + +class CMomentaryDoor : public CBaseToggle +{ +public: + void Spawn( void ); + void Precache( void ); + + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + BYTE m_bMoveSnd; // sound a door makes while moving +}; + +LINK_ENTITY_TO_CLASS( momentary_door, CMomentaryDoor ); + +TYPEDESCRIPTION CMomentaryDoor::m_SaveData[] = +{ + DEFINE_FIELD( CMomentaryDoor, m_bMoveSnd, FIELD_CHARACTER ), +}; + +IMPLEMENT_SAVERESTORE( CMomentaryDoor, CBaseToggle ); + +void CMomentaryDoor::Spawn( void ) +{ + SetMovedir (pev); + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + if (pev->speed == 0) + pev->speed = 100; + if (pev->dmg == 0) + pev->dmg = 2; + + m_vecPosition1 = pev->origin; + // Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big + m_vecPosition2 = m_vecPosition1 + (pev->movedir * (fabs( pev->movedir.x * (pev->size.x-2) ) + fabs( pev->movedir.y * (pev->size.y-2) ) + fabs( pev->movedir.z * (pev->size.z-2) ) - m_flLip)); + ASSERTSZ(m_vecPosition1 != m_vecPosition2, "door start/end positions are equal"); + + if ( FBitSet (pev->spawnflags, SF_DOOR_START_OPEN) ) + { // swap pos1 and pos2, put door at pos2 + UTIL_SetOrigin(pev, m_vecPosition2); + m_vecPosition2 = m_vecPosition1; + m_vecPosition1 = pev->origin; + } + SetTouch( NULL ); + + Precache(); +} + +void CMomentaryDoor::Precache( void ) +{ + +// set the door's "in-motion" sound + switch (m_bMoveSnd) + { + case 0: + pev->noiseMoving = ALLOC_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("doors/doormove1.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove1.wav"); + break; + case 2: + PRECACHE_SOUND ("doors/doormove2.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove2.wav"); + break; + case 3: + PRECACHE_SOUND ("doors/doormove3.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove3.wav"); + break; + case 4: + PRECACHE_SOUND ("doors/doormove4.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove4.wav"); + break; + case 5: + PRECACHE_SOUND ("doors/doormove5.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove5.wav"); + break; + case 6: + PRECACHE_SOUND ("doors/doormove6.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove6.wav"); + break; + case 7: + PRECACHE_SOUND ("doors/doormove7.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove7.wav"); + break; + case 8: + PRECACHE_SOUND ("doors/doormove8.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove8.wav"); + break; + default: + pev->noiseMoving = ALLOC_STRING("common/null.wav"); + break; + } +} + +void CMomentaryDoor::KeyValue( KeyValueData *pkvd ) +{ + + if (FStrEq(pkvd->szKeyName, "movesnd")) + { + m_bMoveSnd = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "stopsnd")) + { +// m_bStopSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "healthvalue")) + { +// m_bHealthValue = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CMomentaryDoor::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( useType != USE_SET ) // Momentary buttons will pass down a float in here + return; + + if ( value > 1.0 ) + value = 1.0; + Vector move = m_vecPosition1 + (value * (m_vecPosition2 - m_vecPosition1)); + + Vector delta = move - pev->origin; + float speed = delta.Length() * 10; + + if ( speed != 0 ) + { + // This entity only thinks when it moves, so if it's thinking, it's in the process of moving + // play the sound when it starts moving + if ( pev->nextthink < pev->ltime || pev->nextthink == 0 ) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving), 1, ATTN_NORM); + + LinearMove( move, speed ); + } + +} diff --git a/hlsdk/dlls/doors.h b/hlsdk/dlls/doors.h new file mode 100644 index 00000000..80088615 --- /dev/null +++ b/hlsdk/dlls/doors.h @@ -0,0 +1,33 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef DOORS_H +#define DOORS_H + +// doors +#define SF_DOOR_ROTATE_Y 0 +#define SF_DOOR_START_OPEN 1 +#define SF_DOOR_ROTATE_BACKWARDS 2 +#define SF_DOOR_PASSABLE 8 +#define SF_DOOR_ONEWAY 16 +#define SF_DOOR_NO_AUTO_RETURN 32 +#define SF_DOOR_ROTATE_Z 64 +#define SF_DOOR_ROTATE_X 128 +#define SF_DOOR_USE_ONLY 256 // door must be opened by player's use button. +#define SF_DOOR_NOMONSTERS 512 // Monster can't open +#define SF_DOOR_SILENT 0x80000000 + + + +#endif //DOORS_H diff --git a/hlsdk/dlls/effects.cpp b/hlsdk/dlls/effects.cpp new file mode 100644 index 00000000..b46df857 --- /dev/null +++ b/hlsdk/dlls/effects.cpp @@ -0,0 +1,2268 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "customentity.h" +#include "effects.h" +#include "weapons.h" +#include "decals.h" +#include "func_break.h" +#include "shake.h" + +#define SF_GIBSHOOTER_REPEATABLE 1 // allows a gibshooter to be refired + +#define SF_FUNNEL_REVERSE 1 // funnel effect repels particles instead of attracting them. + + +// Lightning target, just alias landmark +LINK_ENTITY_TO_CLASS( info_target, CPointEntity ); + + +class CBubbling : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + void EXPORT FizzThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + static TYPEDESCRIPTION m_SaveData[]; + + int m_density; + int m_frequency; + int m_bubbleModel; + int m_state; +}; + +LINK_ENTITY_TO_CLASS( env_bubbles, CBubbling ); + +TYPEDESCRIPTION CBubbling::m_SaveData[] = +{ + DEFINE_FIELD( CBubbling, m_density, FIELD_INTEGER ), + DEFINE_FIELD( CBubbling, m_frequency, FIELD_INTEGER ), + DEFINE_FIELD( CBubbling, m_state, FIELD_INTEGER ), + // Let spawn restore this! + // DEFINE_FIELD( CBubbling, m_bubbleModel, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CBubbling, CBaseEntity ); + + +#define SF_BUBBLES_STARTOFF 0x0001 + +void CBubbling::Spawn( void ) +{ + Precache( ); + SET_MODEL( ENT(pev), STRING(pev->model) ); // Set size + + pev->solid = SOLID_NOT; // Remove model & collisions + pev->renderamt = 0; // The engine won't draw this model if this is set to 0 and blending is on + pev->rendermode = kRenderTransTexture; + int speed = pev->speed > 0 ? static_cast(pev->speed) : static_cast(-pev->speed); + + // HACKHACK!!! - Speed in rendercolor + pev->rendercolor.x = speed >> 8; + pev->rendercolor.y = speed & 255; + pev->rendercolor.z = (pev->speed < 0) ? 1 : 0; + + + if ( !(pev->spawnflags & SF_BUBBLES_STARTOFF) ) + { + SetThink( &CBubbling::FizzThink ); + pev->nextthink = gpGlobals->time + 2.0; + m_state = 1; + } + else + m_state = 0; +} + +void CBubbling::Precache( void ) +{ + m_bubbleModel = PRECACHE_MODEL("sprites/bubble.spr"); // Precache bubble sprite +} + + +void CBubbling::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( ShouldToggle( useType, m_state ) ) + m_state = !m_state; + + if ( m_state ) + { + SetThink( &CBubbling::FizzThink ); + pev->nextthink = gpGlobals->time + 0.1; + } + else + { + SetThink( NULL ); + pev->nextthink = 0; + } +} + + +void CBubbling::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "density")) + { + m_density = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "frequency")) + { + m_frequency = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "current")) + { + pev->speed = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +void CBubbling::FizzThink( void ) +{ + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, VecBModelOrigin(pev) ); + WRITE_BYTE( TE_FIZZ ); + WRITE_SHORT( (short)ENTINDEX( edict() ) ); + WRITE_SHORT( (short)m_bubbleModel ); + WRITE_BYTE( m_density ); + MESSAGE_END(); + + if ( m_frequency > 19 ) + pev->nextthink = gpGlobals->time + 0.5; + else + pev->nextthink = gpGlobals->time + 2.5 - (0.1 * m_frequency); +} + +// -------------------------------------------------- +// +// Beams +// +// -------------------------------------------------- + +LINK_ENTITY_TO_CLASS( beam, CBeam ); + +void CBeam::Spawn( void ) +{ + pev->solid = SOLID_NOT; // Remove model & collisions + Precache( ); +} + +void CBeam::Precache( void ) +{ + if ( pev->owner ) + SetStartEntity( ENTINDEX( pev->owner ) ); + if ( pev->aiment ) + SetEndEntity( ENTINDEX( pev->aiment ) ); +} + +void CBeam::SetStartEntity( int entityIndex ) +{ + pev->sequence = (entityIndex & 0x0FFF) | ((pev->sequence&0xF000)<<12); + pev->owner = g_engfuncs.pfnPEntityOfEntIndex( entityIndex ); +} + +void CBeam::SetEndEntity( int entityIndex ) +{ + pev->skin = (entityIndex & 0x0FFF) | ((pev->skin&0xF000)<<12); + pev->aiment = g_engfuncs.pfnPEntityOfEntIndex( entityIndex ); +} + + +// These don't take attachments into account +const Vector &CBeam::GetStartPos( void ) +{ + if ( GetType() == BEAM_ENTS ) + { + edict_t *pent = g_engfuncs.pfnPEntityOfEntIndex( GetStartEntity() ); + return pent->v.origin; + } + return pev->origin; +} + + +const Vector &CBeam::GetEndPos( void ) +{ + int type = GetType(); + if ( type == BEAM_POINTS || type == BEAM_HOSE ) + { + return pev->angles; + } + + edict_t *pent = g_engfuncs.pfnPEntityOfEntIndex( GetEndEntity() ); + if ( pent ) + return pent->v.origin; + return pev->angles; +} + + +CBeam *CBeam::BeamCreate( const char *pSpriteName, int width ) +{ + // Create a new entity with CBeam private data + CBeam *pBeam = GetClassPtr( (CBeam *)NULL ); + pBeam->pev->classname = MAKE_STRING("beam"); + + pBeam->BeamInit( pSpriteName, width ); + + return pBeam; +} + + +void CBeam::BeamInit( const char *pSpriteName, int width ) +{ + pev->flags |= FL_CUSTOMENTITY; + SetColor( 255, 255, 255 ); + SetBrightness( 255 ); + SetNoise( 0 ); + SetFrame( 0 ); + SetScrollRate( 0 ); + pev->model = MAKE_STRING( pSpriteName ); + SetTexture( PRECACHE_MODEL( (char *)pSpriteName ) ); + SetWidth( width ); + pev->skin = 0; + pev->sequence = 0; + pev->rendermode = 0; +} + + +void CBeam::PointsInit( const Vector &start, const Vector &end ) +{ + SetType( BEAM_POINTS ); + SetStartPos( start ); + SetEndPos( end ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + + +void CBeam::HoseInit( const Vector &start, const Vector &direction ) +{ + SetType( BEAM_HOSE ); + SetStartPos( start ); + SetEndPos( direction ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + + +void CBeam::PointEntInit( const Vector &start, int endIndex ) +{ + SetType( BEAM_ENTPOINT ); + SetStartPos( start ); + SetEndEntity( endIndex ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + +void CBeam::EntsInit( int startIndex, int endIndex ) +{ + SetType( BEAM_ENTS ); + SetStartEntity( startIndex ); + SetEndEntity( endIndex ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + + +void CBeam::RelinkBeam( void ) +{ + const Vector &startPos = GetStartPos(), &endPos = GetEndPos(); + + pev->mins.x = min( startPos.x, endPos.x ); + pev->mins.y = min( startPos.y, endPos.y ); + pev->mins.z = min( startPos.z, endPos.z ); + pev->maxs.x = max( startPos.x, endPos.x ); + pev->maxs.y = max( startPos.y, endPos.y ); + pev->maxs.z = max( startPos.z, endPos.z ); + pev->mins = pev->mins - pev->origin; + pev->maxs = pev->maxs - pev->origin; + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( pev, pev->origin ); +} + +#if 0 +void CBeam::SetObjectCollisionBox( void ) +{ + const Vector &startPos = GetStartPos(), &endPos = GetEndPos(); + + pev->absmin.x = min( startPos.x, endPos.x ); + pev->absmin.y = min( startPos.y, endPos.y ); + pev->absmin.z = min( startPos.z, endPos.z ); + pev->absmax.x = max( startPos.x, endPos.x ); + pev->absmax.y = max( startPos.y, endPos.y ); + pev->absmax.z = max( startPos.z, endPos.z ); +} +#endif + + +void CBeam::TriggerTouch( CBaseEntity *pOther ) +{ + if ( pOther->pev->flags & (FL_CLIENT | FL_MONSTER) ) + { + if ( pev->owner ) + { + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + pOwner->Use( pOther, this, USE_TOGGLE, 0 ); + } + ALERT( at_console, "Firing targets!!!\n" ); + } +} + + +CBaseEntity *CBeam::RandomTargetname( const char *szName ) +{ + int total = 0; + + CBaseEntity *pEntity = NULL; + CBaseEntity *pNewEntity = NULL; + while ((pNewEntity = UTIL_FindEntityByTargetname( pNewEntity, szName )) != NULL) + { + total++; + if (RANDOM_LONG(0,total-1) < 1) + pEntity = pNewEntity; + } + return pEntity; +} + + +void CBeam::DoSparks( const Vector &start, const Vector &end ) +{ + if ( pev->spawnflags & (SF_BEAM_SPARKSTART|SF_BEAM_SPARKEND) ) + { + if ( pev->spawnflags & SF_BEAM_SPARKSTART ) + { + UTIL_Sparks( start ); + } + if ( pev->spawnflags & SF_BEAM_SPARKEND ) + { + UTIL_Sparks( end ); + } + } +} + + +class CLightning : public CBeam +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void Activate( void ); + + void EXPORT StrikeThink( void ); + void EXPORT DamageThink( void ); + void RandomArea( void ); + void RandomPoint( Vector &vecSrc ); + void Zap( const Vector &vecSrc, const Vector &vecDest ); + void EXPORT StrikeUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + inline BOOL ServerSide( void ) + { + if ( m_life == 0 && !(pev->spawnflags & SF_BEAM_RING) ) + return TRUE; + return FALSE; + } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void BeamUpdateVars( void ); + + int m_active; + int m_iszStartEntity; + int m_iszEndEntity; + float m_life; + int m_boltWidth; + int m_noiseAmplitude; + int m_brightness; + int m_speed; + float m_restrike; + int m_spriteTexture; + int m_iszSpriteName; + int m_frameStart; + + float m_radius; +}; + +LINK_ENTITY_TO_CLASS( env_lightning, CLightning ); +LINK_ENTITY_TO_CLASS( env_beam, CLightning ); + +// UNDONE: Jay -- This is only a test +#if _DEBUG +class CTripBeam : public CLightning +{ + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS( trip_beam, CTripBeam ); + +void CTripBeam::Spawn( void ) +{ + CLightning::Spawn(); + SetTouch( &CBeam::TriggerTouch ); + pev->solid = SOLID_TRIGGER; + RelinkBeam(); +} +#endif + + + +TYPEDESCRIPTION CLightning::m_SaveData[] = +{ + DEFINE_FIELD( CLightning, m_active, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_iszStartEntity, FIELD_STRING ), + DEFINE_FIELD( CLightning, m_iszEndEntity, FIELD_STRING ), + DEFINE_FIELD( CLightning, m_life, FIELD_FLOAT ), + DEFINE_FIELD( CLightning, m_boltWidth, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_noiseAmplitude, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_brightness, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_speed, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_restrike, FIELD_FLOAT ), + DEFINE_FIELD( CLightning, m_spriteTexture, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_iszSpriteName, FIELD_STRING ), + DEFINE_FIELD( CLightning, m_frameStart, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_radius, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CLightning, CBeam ); + + +void CLightning::Spawn( void ) +{ + if ( FStringNull( m_iszSpriteName ) ) + { + SetThink( &CBaseEntity::SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; // Remove model & collisions + Precache( ); + + pev->dmgtime = gpGlobals->time; + + if ( ServerSide() ) + { + SetThink( NULL ); + if ( pev->dmg > 0 ) + { + SetThink( &CLightning::DamageThink ); + pev->nextthink = gpGlobals->time + 0.1; + } + if ( pev->targetname ) + { + if ( !(pev->spawnflags & SF_BEAM_STARTON) ) + { + pev->effects = EF_NODRAW; + m_active = 0; + pev->nextthink = 0; + } + else + m_active = 1; + + SetUse( &CLightning::ToggleUse ); + } + } + else + { + m_active = 0; + if ( !FStringNull(pev->targetname) ) + { + SetUse( &CLightning::StrikeUse ); + } + if ( FStringNull(pev->targetname) || FBitSet(pev->spawnflags, SF_BEAM_STARTON) ) + { + SetThink( &CLightning::StrikeThink ); + pev->nextthink = gpGlobals->time + 1.0; + } + } +} + +void CLightning::Precache( void ) +{ + m_spriteTexture = PRECACHE_MODEL( (char *)STRING(m_iszSpriteName) ); + CBeam::Precache(); +} + + +void CLightning::Activate( void ) +{ + if ( ServerSide() ) + BeamUpdateVars(); +} + + +void CLightning::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "LightningStart")) + { + m_iszStartEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "LightningEnd")) + { + m_iszEndEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "life")) + { + m_life = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "BoltWidth")) + { + m_boltWidth = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "NoiseAmplitude")) + { + m_noiseAmplitude = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "TextureScroll")) + { + m_speed = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "StrikeTime")) + { + m_restrike = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "texture")) + { + m_iszSpriteName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "framestart")) + { + m_frameStart = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "Radius")) + { + m_radius = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damage")) + { + pev->dmg = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBeam::KeyValue( pkvd ); +} + + +void CLightning::ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_active ) ) + return; + if ( m_active ) + { + m_active = 0; + pev->effects |= EF_NODRAW; + pev->nextthink = 0; + } + else + { + m_active = 1; + pev->effects &= ~EF_NODRAW; + DoSparks( GetStartPos(), GetEndPos() ); + if ( pev->dmg > 0 ) + { + pev->nextthink = gpGlobals->time; + pev->dmgtime = gpGlobals->time; + } + } +} + + +void CLightning::StrikeUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_active ) ) + return; + + if ( m_active ) + { + m_active = 0; + SetThink( NULL ); + } + else + { + SetThink( &CLightning::StrikeThink ); + pev->nextthink = gpGlobals->time + 0.1; + } + + if ( !FBitSet( pev->spawnflags, SF_BEAM_TOGGLE ) ) + SetUse( NULL ); +} + + +int IsPointEntity( CBaseEntity *pEnt ) +{ + if ( !pEnt->pev->modelindex ) + return 1; + if ( FClassnameIs( pEnt->pev, "info_target" ) || FClassnameIs( pEnt->pev, "info_landmark" ) || FClassnameIs( pEnt->pev, "path_corner" ) ) + return 1; + + return 0; +} + + +void CLightning::StrikeThink( void ) +{ + if ( m_life != 0 ) + { + if ( pev->spawnflags & SF_BEAM_RANDOM ) + pev->nextthink = gpGlobals->time + m_life + RANDOM_FLOAT( 0, m_restrike ); + else + pev->nextthink = gpGlobals->time + m_life + m_restrike; + } + m_active = 1; + + if (FStringNull(m_iszEndEntity)) + { + if (FStringNull(m_iszStartEntity)) + { + RandomArea( ); + } + else + { + CBaseEntity *pStart = RandomTargetname( STRING(m_iszStartEntity) ); + if (pStart != NULL) + RandomPoint( pStart->pev->origin ); + else + ALERT( at_console, "env_beam: unknown entity \"%s\"\n", STRING(m_iszStartEntity) ); + } + return; + } + + CBaseEntity *pStart = RandomTargetname( STRING(m_iszStartEntity) ); + CBaseEntity *pEnd = RandomTargetname( STRING(m_iszEndEntity) ); + + if ( pStart != NULL && pEnd != NULL ) + { + if ( IsPointEntity( pStart ) || IsPointEntity( pEnd ) ) + { + if ( pev->spawnflags & SF_BEAM_RING) + { + // don't work + return; + } + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + if ( IsPointEntity( pStart ) || IsPointEntity( pEnd ) ) + { + if ( !IsPointEntity( pEnd ) ) // One point entity must be in pEnd + { + CBaseEntity *pTemp; + pTemp = pStart; + pStart = pEnd; + pEnd = pTemp; + } + if ( !IsPointEntity( pStart ) ) // One sided + { + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( pStart->entindex() ); + WRITE_COORD( pEnd->pev->origin.x); + WRITE_COORD( pEnd->pev->origin.y); + WRITE_COORD( pEnd->pev->origin.z); + } + else + { + WRITE_BYTE( TE_BEAMPOINTS); + WRITE_COORD( pStart->pev->origin.x); + WRITE_COORD( pStart->pev->origin.y); + WRITE_COORD( pStart->pev->origin.z); + WRITE_COORD( pEnd->pev->origin.x); + WRITE_COORD( pEnd->pev->origin.y); + WRITE_COORD( pEnd->pev->origin.z); + } + + + } + else + { + if ( pev->spawnflags & SF_BEAM_RING) + WRITE_BYTE( TE_BEAMRING ); + else + WRITE_BYTE( TE_BEAMENTS ); + WRITE_SHORT( pStart->entindex() ); + WRITE_SHORT( pEnd->entindex() ); + } + + WRITE_SHORT( m_spriteTexture ); + WRITE_BYTE( m_frameStart ); // framestart + WRITE_BYTE( (int)pev->framerate); // framerate + WRITE_BYTE( (int)(m_life*10.0) ); // life + WRITE_BYTE( m_boltWidth ); // width + WRITE_BYTE( m_noiseAmplitude ); // noise + WRITE_BYTE( (int)pev->rendercolor.x ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.y ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.z ); // r, g, b + WRITE_BYTE( (int)pev->renderamt ); // brightness + WRITE_BYTE( m_speed ); // speed + MESSAGE_END(); + DoSparks( pStart->pev->origin, pEnd->pev->origin ); + if ( pev->dmg > 0 ) + { + TraceResult tr; + UTIL_TraceLine( pStart->pev->origin, pEnd->pev->origin, dont_ignore_monsters, NULL, &tr ); + BeamDamageInstant( &tr, pev->dmg ); + } + } +} + + +void CBeam::BeamDamage( TraceResult *ptr ) +{ + RelinkBeam(); + if ( ptr->flFraction != 1.0 && ptr->pHit != NULL ) + { + CBaseEntity *pHit = CBaseEntity::Instance(ptr->pHit); + if ( pHit ) + { + ClearMultiDamage(); + pHit->TraceAttack( pev, pev->dmg * (gpGlobals->time - pev->dmgtime), (ptr->vecEndPos - pev->origin).Normalize(), ptr, DMG_ENERGYBEAM ); + ApplyMultiDamage( pev, pev ); + if ( pev->spawnflags & SF_BEAM_DECALS ) + { + if ( pHit->IsBSPModel() ) + UTIL_DecalTrace( ptr, DECAL_BIGSHOT1 + RANDOM_LONG(0,4) ); + } + } + } + pev->dmgtime = gpGlobals->time; +} + + +void CLightning::DamageThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + TraceResult tr; + UTIL_TraceLine( GetStartPos(), GetEndPos(), dont_ignore_monsters, NULL, &tr ); + BeamDamage( &tr ); +} + + + +void CLightning::Zap( const Vector &vecSrc, const Vector &vecDest ) +{ +#if 1 + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS); + WRITE_COORD(vecSrc.x); + WRITE_COORD(vecSrc.y); + WRITE_COORD(vecSrc.z); + WRITE_COORD(vecDest.x); + WRITE_COORD(vecDest.y); + WRITE_COORD(vecDest.z); + WRITE_SHORT( m_spriteTexture ); + WRITE_BYTE( m_frameStart ); // framestart + WRITE_BYTE( (int)pev->framerate); // framerate + WRITE_BYTE( (int)(m_life*10.0) ); // life + WRITE_BYTE( m_boltWidth ); // width + WRITE_BYTE( m_noiseAmplitude ); // noise + WRITE_BYTE( (int)pev->rendercolor.x ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.y ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.z ); // r, g, b + WRITE_BYTE( (int)pev->renderamt ); // brightness + WRITE_BYTE( m_speed ); // speed + MESSAGE_END(); +#else + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE(TE_LIGHTNING); + WRITE_COORD(vecSrc.x); + WRITE_COORD(vecSrc.y); + WRITE_COORD(vecSrc.z); + WRITE_COORD(vecDest.x); + WRITE_COORD(vecDest.y); + WRITE_COORD(vecDest.z); + WRITE_BYTE(10); + WRITE_BYTE(50); + WRITE_BYTE(40); + WRITE_SHORT(m_spriteTexture); + MESSAGE_END(); +#endif + DoSparks( vecSrc, vecDest ); +} + +void CLightning::RandomArea( void ) +{ + int iLoops = 0; + + for (iLoops = 0; iLoops < 10; iLoops++) + { + Vector vecSrc = pev->origin; + + Vector vecDir1 = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + vecDir1 = vecDir1.Normalize(); + TraceResult tr1; + UTIL_TraceLine( vecSrc, vecSrc + vecDir1 * m_radius, ignore_monsters, ENT(pev), &tr1 ); + + if (tr1.flFraction == 1.0) + continue; + + Vector vecDir2; + do { + vecDir2 = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + } while (DotProduct(vecDir1, vecDir2 ) > 0); + vecDir2 = vecDir2.Normalize(); + TraceResult tr2; + UTIL_TraceLine( vecSrc, vecSrc + vecDir2 * m_radius, ignore_monsters, ENT(pev), &tr2 ); + + if (tr2.flFraction == 1.0) + continue; + + if ((tr1.vecEndPos - tr2.vecEndPos).Length() < m_radius * 0.1) + continue; + + UTIL_TraceLine( tr1.vecEndPos, tr2.vecEndPos, ignore_monsters, ENT(pev), &tr2 ); + + if (tr2.flFraction != 1.0) + continue; + + Zap( tr1.vecEndPos, tr2.vecEndPos ); + + break; + } +} + + +void CLightning::RandomPoint( Vector &vecSrc ) +{ + int iLoops = 0; + + for (iLoops = 0; iLoops < 10; iLoops++) + { + Vector vecDir1 = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + vecDir1 = vecDir1.Normalize(); + TraceResult tr1; + UTIL_TraceLine( vecSrc, vecSrc + vecDir1 * m_radius, ignore_monsters, ENT(pev), &tr1 ); + + if ((tr1.vecEndPos - vecSrc).Length() < m_radius * 0.1) + continue; + + if (tr1.flFraction == 1.0) + continue; + + Zap( vecSrc, tr1.vecEndPos ); + break; + } +} + + + +void CLightning::BeamUpdateVars( void ) +{ + int beamType; + int pointStart, pointEnd; + + edict_t *pStart = FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(m_iszStartEntity) ); + edict_t *pEnd = FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(m_iszEndEntity) ); + pointStart = IsPointEntity( CBaseEntity::Instance(pStart) ); + pointEnd = IsPointEntity( CBaseEntity::Instance(pEnd) ); + + pev->skin = 0; + pev->sequence = 0; + pev->rendermode = 0; + pev->flags |= FL_CUSTOMENTITY; + pev->model = m_iszSpriteName; + SetTexture( m_spriteTexture ); + + beamType = BEAM_ENTS; + if ( pointStart || pointEnd ) + { + if ( !pointStart ) // One point entity must be in pStart + { + edict_t *pTemp; + // Swap start & end + pTemp = pStart; + pStart = pEnd; + pEnd = pTemp; + int swap = pointStart; + pointStart = pointEnd; + pointEnd = swap; + } + if ( !pointEnd ) + beamType = BEAM_ENTPOINT; + else + beamType = BEAM_POINTS; + } + + SetType( beamType ); + if ( beamType == BEAM_POINTS || beamType == BEAM_ENTPOINT || beamType == BEAM_HOSE ) + { + SetStartPos( pStart->v.origin ); + if ( beamType == BEAM_POINTS || beamType == BEAM_HOSE ) + SetEndPos( pEnd->v.origin ); + else + SetEndEntity( ENTINDEX(pEnd) ); + } + else + { + SetStartEntity( ENTINDEX(pStart) ); + SetEndEntity( ENTINDEX(pEnd) ); + } + + RelinkBeam(); + + SetWidth( m_boltWidth ); + SetNoise( m_noiseAmplitude ); + SetFrame( m_frameStart ); + SetScrollRate( m_speed ); + if ( pev->spawnflags & SF_BEAM_SHADEIN ) + SetFlags( BEAM_FSHADEIN ); + else if ( pev->spawnflags & SF_BEAM_SHADEOUT ) + SetFlags( BEAM_FSHADEOUT ); +} + + +LINK_ENTITY_TO_CLASS( env_laser, CLaser ); + +TYPEDESCRIPTION CLaser::m_SaveData[] = +{ + DEFINE_FIELD( CLaser, m_pSprite, FIELD_CLASSPTR ), + DEFINE_FIELD( CLaser, m_iszSpriteName, FIELD_STRING ), + DEFINE_FIELD( CLaser, m_firePosition, FIELD_POSITION_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CLaser, CBeam ); + +void CLaser::Spawn( void ) +{ + if ( FStringNull( pev->model ) ) + { + SetThink( &CBaseEntity::SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; // Remove model & collisions + Precache( ); + + SetThink( &CLaser::StrikeThink ); + pev->flags |= FL_CUSTOMENTITY; + + PointsInit( pev->origin, pev->origin ); + + if ( !m_pSprite && m_iszSpriteName ) + m_pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteName), pev->origin, TRUE ); + else + m_pSprite = NULL; + + if ( m_pSprite ) + m_pSprite->SetTransparency( kRenderGlow, static_cast(pev->rendercolor.x), static_cast(pev->rendercolor.y), static_cast(pev->rendercolor.z), static_cast(pev->renderamt), static_cast(pev->renderfx) ); + + if ( pev->targetname && !(pev->spawnflags & SF_BEAM_STARTON) ) + TurnOff(); + else + TurnOn(); +} + +void CLaser::Precache( void ) +{ + pev->modelindex = PRECACHE_MODEL( (char *)STRING(pev->model) ); + if ( m_iszSpriteName ) + PRECACHE_MODEL( (char *)STRING(m_iszSpriteName) ); +} + + +void CLaser::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "LaserTarget")) + { + pev->message = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "width")) + { + SetWidth( atoi(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "NoiseAmplitude")) + { + SetNoise( atoi(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "TextureScroll")) + { + SetScrollRate( atoi(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "texture")) + { + pev->model = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "EndSprite")) + { + m_iszSpriteName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "framestart")) + { + pev->frame = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damage")) + { + pev->dmg = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBeam::KeyValue( pkvd ); +} + + +int CLaser::IsOn( void ) +{ + if (pev->effects & EF_NODRAW) + return 0; + return 1; +} + + +void CLaser::TurnOff( void ) +{ + pev->effects |= EF_NODRAW; + pev->nextthink = 0; + if ( m_pSprite ) + m_pSprite->TurnOff(); +} + + +void CLaser::TurnOn( void ) +{ + pev->effects &= ~EF_NODRAW; + if ( m_pSprite ) + m_pSprite->TurnOn(); + pev->dmgtime = gpGlobals->time; + pev->nextthink = gpGlobals->time; +} + + +void CLaser::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int active = IsOn(); + + if ( !ShouldToggle( useType, active ) ) + return; + if ( active ) + { + TurnOff(); + } + else + { + TurnOn(); + } +} + + +void CLaser::FireAtPoint( TraceResult &tr ) +{ + SetEndPos( tr.vecEndPos ); + if ( m_pSprite ) + UTIL_SetOrigin( m_pSprite->pev, tr.vecEndPos ); + + BeamDamage( &tr ); + DoSparks( GetStartPos(), tr.vecEndPos ); +} + +void CLaser::StrikeThink( void ) +{ + CBaseEntity *pEnd = RandomTargetname( STRING(pev->message) ); + + if ( pEnd ) + m_firePosition = pEnd->pev->origin; + + TraceResult tr; + + UTIL_TraceLine( pev->origin, m_firePosition, dont_ignore_monsters, NULL, &tr ); + FireAtPoint( tr ); + pev->nextthink = gpGlobals->time + 0.1; +} + + + +class CGlow : public CPointEntity +{ +public: + void Spawn( void ); + void Think( void ); + void Animate( float frames ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + float m_lastTime; + float m_maxFrame; +}; + +LINK_ENTITY_TO_CLASS( env_glow, CGlow ); + +TYPEDESCRIPTION CGlow::m_SaveData[] = +{ + DEFINE_FIELD( CGlow, m_lastTime, FIELD_TIME ), + DEFINE_FIELD( CGlow, m_maxFrame, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CGlow, CPointEntity ); + +void CGlow::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; + + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; + if ( m_maxFrame > 1.0 && pev->framerate != 0 ) + pev->nextthink = gpGlobals->time + 0.1; + + m_lastTime = gpGlobals->time; +} + + +void CGlow::Think( void ) +{ + Animate( pev->framerate * (gpGlobals->time - m_lastTime) ); + + pev->nextthink = gpGlobals->time + 0.1; + m_lastTime = gpGlobals->time; +} + + +void CGlow::Animate( float frames ) +{ + if ( m_maxFrame > 0 ) + pev->frame = fmod( pev->frame + frames, m_maxFrame ); +} + + +LINK_ENTITY_TO_CLASS( env_sprite, CSprite ); + +TYPEDESCRIPTION CSprite::m_SaveData[] = +{ + DEFINE_FIELD( CSprite, m_lastTime, FIELD_TIME ), + DEFINE_FIELD( CSprite, m_maxFrame, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CSprite, CPointEntity ); + +void CSprite::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; + + Precache(); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; + if ( pev->targetname && !(pev->spawnflags & SF_SPRITE_STARTON) ) + TurnOff(); + else + TurnOn(); + + // Worldcraft only sets y rotation, copy to Z + if ( pev->angles.y != 0 && pev->angles.z == 0 ) + { + pev->angles.z = pev->angles.y; + pev->angles.y = 0; + } +} + + +void CSprite::Precache( void ) +{ + PRECACHE_MODEL( (char *)STRING(pev->model) ); + + // Reset attachment after save/restore + if ( pev->aiment ) + SetAttachment( pev->aiment, pev->body ); + else + { + // Clear attachment + pev->skin = 0; + pev->body = 0; + } +} + + +void CSprite::SpriteInit( const char *pSpriteName, const Vector &origin ) +{ + pev->model = MAKE_STRING(pSpriteName); + pev->origin = origin; + Spawn(); +} + +CSprite *CSprite::SpriteCreate( const char *pSpriteName, const Vector &origin, BOOL animate ) +{ + CSprite *pSprite = GetClassPtr( (CSprite *)NULL ); + pSprite->SpriteInit( pSpriteName, origin ); + pSprite->pev->classname = MAKE_STRING("env_sprite"); + pSprite->pev->solid = SOLID_NOT; + pSprite->pev->movetype = MOVETYPE_NOCLIP; + if ( animate ) + pSprite->TurnOn(); + + return pSprite; +} + + +void CSprite::AnimateThink( void ) +{ + Animate( pev->framerate * (gpGlobals->time - m_lastTime) ); + + pev->nextthink = gpGlobals->time + 0.1; + m_lastTime = gpGlobals->time; +} + +void CSprite::AnimateUntilDead( void ) +{ + if ( gpGlobals->time > pev->dmgtime ) + UTIL_Remove(this); + else + { + AnimateThink(); + pev->nextthink = gpGlobals->time; + } +} + +void CSprite::Expand( float scaleSpeed, float fadeSpeed ) +{ + pev->speed = scaleSpeed; + pev->health = fadeSpeed; + SetThink( &CSprite::ExpandThink ); + + pev->nextthink = gpGlobals->time; + m_lastTime = gpGlobals->time; +} + + +void CSprite::ExpandThink( void ) +{ + float frametime = gpGlobals->time - m_lastTime; + pev->scale += pev->speed * frametime; + pev->renderamt -= pev->health * frametime; + if ( pev->renderamt <= 0 ) + { + pev->renderamt = 0; + UTIL_Remove( this ); + } + else + { + pev->nextthink = gpGlobals->time + 0.1; + m_lastTime = gpGlobals->time; + } +} + + +void CSprite::Animate( float frames ) +{ + pev->frame += frames; + if ( pev->frame > m_maxFrame ) + { + if ( pev->spawnflags & SF_SPRITE_ONCE ) + { + TurnOff(); + } + else + { + if ( m_maxFrame > 0 ) + pev->frame = fmod( pev->frame, m_maxFrame ); + } + } +} + + +void CSprite::TurnOff( void ) +{ + pev->effects = EF_NODRAW; + pev->nextthink = 0; +} + + +void CSprite::TurnOn( void ) +{ + pev->effects = 0; + if ( (pev->framerate && m_maxFrame > 1.0) || (pev->spawnflags & SF_SPRITE_ONCE) ) + { + SetThink( &CSprite::AnimateThink ); + pev->nextthink = gpGlobals->time; + m_lastTime = gpGlobals->time; + } + pev->frame = 0; +} + + +void CSprite::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int on = pev->effects != EF_NODRAW; + if ( ShouldToggle( useType, on ) ) + { + if ( on ) + { + TurnOff(); + } + else + { + TurnOn(); + } + } +} + + +class CGibShooter : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT ShootThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual CGib *CreateGib( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_iGibs; + int m_iGibCapacity; + int m_iGibMaterial; + int m_iGibModelIndex; + float m_flGibVelocity; + float m_flVariance; + float m_flGibLife; +}; + +TYPEDESCRIPTION CGibShooter::m_SaveData[] = +{ + DEFINE_FIELD( CGibShooter, m_iGibs, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_iGibCapacity, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_iGibMaterial, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_iGibModelIndex, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_flGibVelocity, FIELD_FLOAT ), + DEFINE_FIELD( CGibShooter, m_flVariance, FIELD_FLOAT ), + DEFINE_FIELD( CGibShooter, m_flGibLife, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CGibShooter, CBaseDelay ); +LINK_ENTITY_TO_CLASS( gibshooter, CGibShooter ); + + +void CGibShooter :: Precache ( void ) +{ + if ( g_Language == LANGUAGE_GERMAN ) + { + m_iGibModelIndex = PRECACHE_MODEL ("models/germanygibs.mdl"); + } + else + { + m_iGibModelIndex = PRECACHE_MODEL ("models/hgibs.mdl"); + } +} + + +void CGibShooter::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iGibs")) + { + m_iGibs = m_iGibCapacity = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flVelocity")) + { + m_flGibVelocity = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flVariance")) + { + m_flVariance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flGibLife")) + { + m_flGibLife = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + { + CBaseDelay::KeyValue( pkvd ); + } +} + +void CGibShooter::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink( &CGibShooter::ShootThink ); + pev->nextthink = gpGlobals->time; +} + +void CGibShooter::Spawn( void ) +{ + Precache(); + + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; + + if ( m_flDelay == 0 ) + { + m_flDelay = 0.1; + } + + if ( m_flGibLife == 0 ) + { + m_flGibLife = 25; + } + + SetMovedir ( pev ); + pev->body = MODEL_FRAMES( m_iGibModelIndex ); +} + + +CGib *CGibShooter :: CreateGib ( void ) +{ + if ( CVAR_GET_FLOAT("violence_hgibs") == 0 ) + return NULL; + + CGib *pGib = GetClassPtr( (CGib *)NULL ); + pGib->Spawn( "models/hgibs.mdl" ); + pGib->m_bloodColor = BLOOD_COLOR_RED; + + if ( pev->body <= 1 ) + { + ALERT ( at_aiconsole, "GibShooter Body is <= 1!\n" ); + } + + pGib->pev->body = RANDOM_LONG ( 1, pev->body - 1 );// avoid throwing random amounts of the 0th gib. (skull). + + return pGib; +} + + +void CGibShooter :: ShootThink ( void ) +{ + pev->nextthink = gpGlobals->time + m_flDelay; + + Vector vecShootDir; + + vecShootDir = pev->movedir; + + vecShootDir = vecShootDir + gpGlobals->v_right * RANDOM_FLOAT( -1, 1) * m_flVariance;; + vecShootDir = vecShootDir + gpGlobals->v_forward * RANDOM_FLOAT( -1, 1) * m_flVariance;; + vecShootDir = vecShootDir + gpGlobals->v_up * RANDOM_FLOAT( -1, 1) * m_flVariance;; + + vecShootDir = vecShootDir.Normalize(); + CGib *pGib = CreateGib(); + + if ( pGib ) + { + pGib->pev->origin = pev->origin; + pGib->pev->velocity = vecShootDir * m_flGibVelocity; + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + + float thinkTime = pGib->pev->nextthink - gpGlobals->time; + + pGib->m_lifeTime = (m_flGibLife * RANDOM_FLOAT( 0.95, 1.05 )); // +/- 5% + if ( pGib->m_lifeTime < thinkTime ) + { + pGib->pev->nextthink = gpGlobals->time + pGib->m_lifeTime; + pGib->m_lifeTime = 0; + } + + } + + if ( --m_iGibs <= 0 ) + { + if ( pev->spawnflags & SF_GIBSHOOTER_REPEATABLE ) + { + m_iGibs = m_iGibCapacity; + SetThink ( NULL ); + pev->nextthink = gpGlobals->time; + } + else + { + SetThink ( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time; + } + } +} + + +class CEnvShooter : public CGibShooter +{ + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + CGib *CreateGib( void ); +}; + +LINK_ENTITY_TO_CLASS( env_shooter, CEnvShooter ); + +void CEnvShooter :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "shootmodel")) + { + pev->model = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "shootsounds")) + { + int iNoise = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + switch( iNoise ) + { + case 0: + m_iGibMaterial = matGlass; + break; + case 1: + m_iGibMaterial = matWood; + break; + case 2: + m_iGibMaterial = matMetal; + break; + case 3: + m_iGibMaterial = matFlesh; + break; + case 4: + m_iGibMaterial = matRocks; + break; + + default: + case -1: + m_iGibMaterial = matNone; + break; + } + } + else + { + CGibShooter::KeyValue( pkvd ); + } +} + + +void CEnvShooter :: Precache ( void ) +{ + m_iGibModelIndex = PRECACHE_MODEL( (char *)STRING(pev->model) ); + CBreakable::MaterialSoundPrecache( (Materials)m_iGibMaterial ); +} + + +CGib *CEnvShooter :: CreateGib ( void ) +{ + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + pGib->Spawn( STRING(pev->model) ); + + int bodyPart = 0; + + if ( pev->body > 1 ) + bodyPart = RANDOM_LONG( 0, pev->body-1 ); + + pGib->pev->body = bodyPart; + pGib->m_bloodColor = DONT_BLEED; + pGib->m_material = m_iGibMaterial; + + pGib->pev->rendermode = pev->rendermode; + pGib->pev->renderamt = pev->renderamt; + pGib->pev->rendercolor = pev->rendercolor; + pGib->pev->renderfx = pev->renderfx; + pGib->pev->scale = pev->scale; + pGib->pev->skin = pev->skin; + + return pGib; +} + + + + +class CTestEffect : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + // void KeyValue( KeyValueData *pkvd ); + void EXPORT TestThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int m_iLoop; + int m_iBeam; + CBeam *m_pBeam[24]; + float m_flBeamTime[24]; + float m_flStartTime; +}; + + +LINK_ENTITY_TO_CLASS( test_effect, CTestEffect ); + +void CTestEffect::Spawn( void ) +{ + Precache( ); +} + +void CTestEffect::Precache( void ) +{ + PRECACHE_MODEL( "sprites/lgtning.spr" ); +} + +void CTestEffect::TestThink( void ) +{ + int i; + float t = (gpGlobals->time - m_flStartTime); + + if (m_iBeam < 24) + { + CBeam *pbeam = CBeam::BeamCreate( "sprites/lgtning.spr", 100 ); + + TraceResult tr; + + Vector vecSrc = pev->origin; + Vector vecDir = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + vecDir = vecDir.Normalize(); + UTIL_TraceLine( vecSrc, vecSrc + vecDir * 128, ignore_monsters, ENT(pev), &tr); + + pbeam->PointsInit( vecSrc, tr.vecEndPos ); + // pbeam->SetColor( 80, 100, 255 ); + pbeam->SetColor( 255, 180, 100 ); + pbeam->SetWidth( 100 ); + pbeam->SetScrollRate( 12 ); + + m_flBeamTime[m_iBeam] = gpGlobals->time; + m_pBeam[m_iBeam] = pbeam; + m_iBeam++; + +#if 0 + Vector vecMid = (vecSrc + tr.vecEndPos) * 0.5; + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE(TE_DLIGHT); + WRITE_COORD(vecMid.x); // X + WRITE_COORD(vecMid.y); // Y + WRITE_COORD(vecMid.z); // Z + WRITE_BYTE( 20 ); // radius * 0.1 + WRITE_BYTE( 255 ); // r + WRITE_BYTE( 180 ); // g + WRITE_BYTE( 100 ); // b + WRITE_BYTE( 20 ); // time * 10 + WRITE_BYTE( 0 ); // decay * 0.1 + MESSAGE_END( ); +#endif + } + + if (t < 3.0) + { + for (i = 0; i < m_iBeam; i++) + { + t = (gpGlobals->time - m_flBeamTime[i]) / ( 3 + m_flStartTime - m_flBeamTime[i]); + m_pBeam[i]->SetBrightness( static_cast(255 * t) ); + // m_pBeam[i]->SetScrollRate( 20 * t ); + } + pev->nextthink = gpGlobals->time + 0.1; + } + else + { + for (i = 0; i < m_iBeam; i++) + { + UTIL_Remove( m_pBeam[i] ); + } + m_flStartTime = gpGlobals->time; + m_iBeam = 0; + // pev->nextthink = gpGlobals->time; + SetThink( NULL ); + } +} + + +void CTestEffect::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink( &CTestEffect::TestThink ); + pev->nextthink = gpGlobals->time + 0.1; + m_flStartTime = gpGlobals->time; +} + + + +// Blood effects +class CBlood : public CPointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline int Color( void ) { return pev->impulse; } + inline float BloodAmount( void ) { return pev->dmg; } + + inline void SetColor( int color ) { pev->impulse = color; } + inline void SetBloodAmount( float amount ) { pev->dmg = amount; } + + Vector Direction( void ); + Vector BloodPosition( CBaseEntity *pActivator ); + +private: +}; + +LINK_ENTITY_TO_CLASS( env_blood, CBlood ); + + + +#define SF_BLOOD_RANDOM 0x0001 +#define SF_BLOOD_STREAM 0x0002 +#define SF_BLOOD_PLAYER 0x0004 +#define SF_BLOOD_DECAL 0x0008 + +void CBlood::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; + SetMovedir( pev ); +} + + +void CBlood::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "color")) + { + int color = atoi(pkvd->szValue); + switch( color ) + { + case 1: + SetColor( BLOOD_COLOR_YELLOW ); + break; + default: + SetColor( BLOOD_COLOR_RED ); + break; + } + + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "amount")) + { + SetBloodAmount( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +Vector CBlood::Direction( void ) +{ + if ( pev->spawnflags & SF_BLOOD_RANDOM ) + return UTIL_RandomBloodVector(); + + return pev->movedir; +} + + +Vector CBlood::BloodPosition( CBaseEntity *pActivator ) +{ + if ( pev->spawnflags & SF_BLOOD_PLAYER ) + { + edict_t *pPlayer; + + if ( pActivator && pActivator->IsPlayer() ) + { + pPlayer = pActivator->edict(); + } + else + pPlayer = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + if ( pPlayer ) + return (pPlayer->v.origin + pPlayer->v.view_ofs) + Vector( RANDOM_FLOAT(-10,10), RANDOM_FLOAT(-10,10), RANDOM_FLOAT(-10,10) ); + } + + return pev->origin; +} + + +void CBlood::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->spawnflags & SF_BLOOD_STREAM ) + UTIL_BloodStream( BloodPosition(pActivator), Direction(), (Color() == BLOOD_COLOR_RED) ? 70 : Color(), static_cast(BloodAmount()) ); + else + UTIL_BloodDrips( BloodPosition(pActivator), Direction(), Color(), static_cast(BloodAmount()) ); + + if ( pev->spawnflags & SF_BLOOD_DECAL ) + { + Vector forward = Direction(); + Vector start = BloodPosition( pActivator ); + TraceResult tr; + + UTIL_TraceLine( start, start + forward * BloodAmount() * 2, ignore_monsters, NULL, &tr ); + if ( tr.flFraction != 1.0 ) + UTIL_BloodDecalTrace( &tr, Color() ); + } +} + + + +// Screen shake +class CShake : public CPointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline float Amplitude( void ) { return pev->scale; } + inline float Frequency( void ) { return pev->dmg_save; } + inline float Duration( void ) { return pev->dmg_take; } + inline float Radius( void ) { return pev->dmg; } + + inline void SetAmplitude( float amplitude ) { pev->scale = amplitude; } + inline void SetFrequency( float frequency ) { pev->dmg_save = frequency; } + inline void SetDuration( float duration ) { pev->dmg_take = duration; } + inline void SetRadius( float radius ) { pev->dmg = radius; } +private: +}; + +LINK_ENTITY_TO_CLASS( env_shake, CShake ); + +// pev->scale is amplitude +// pev->dmg_save is frequency +// pev->dmg_take is duration +// pev->dmg is radius +// radius of 0 means all players +// NOTE: UTIL_ScreenShake() will only shake players who are on the ground + +#define SF_SHAKE_EVERYONE 0x0001 // Don't check radius +// UNDONE: These don't work yet +#define SF_SHAKE_DISRUPT 0x0002 // Disrupt controls +#define SF_SHAKE_INAIR 0x0004 // Shake players in air + +void CShake::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; + + if ( pev->spawnflags & SF_SHAKE_EVERYONE ) + pev->dmg = 0; +} + + +void CShake::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "amplitude")) + { + SetAmplitude( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "frequency")) + { + SetFrequency( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "duration")) + { + SetDuration( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "radius")) + { + SetRadius( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CShake::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + UTIL_ScreenShake( pev->origin, Amplitude(), Frequency(), Duration(), Radius() ); +} + + +class CFade : public CPointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline float Duration( void ) { return pev->dmg_take; } + inline float HoldTime( void ) { return pev->dmg_save; } + + inline void SetDuration( float duration ) { pev->dmg_take = duration; } + inline void SetHoldTime( float hold ) { pev->dmg_save = hold; } +private: +}; + +LINK_ENTITY_TO_CLASS( env_fade, CFade ); + +// pev->dmg_take is duration +// pev->dmg_save is hold duration +#define SF_FADE_IN 0x0001 // Fade in, not out +#define SF_FADE_MODULATE 0x0002 // Modulate, don't blend +#define SF_FADE_ONLYONE 0x0004 + +void CFade::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; +} + + +void CFade::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "duration")) + { + SetDuration( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "holdtime")) + { + SetHoldTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CFade::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int fadeFlags = 0; + + if ( !(pev->spawnflags & SF_FADE_IN) ) + fadeFlags |= FFADE_OUT; + + if ( pev->spawnflags & SF_FADE_MODULATE ) + fadeFlags |= FFADE_MODULATE; + + if ( pev->spawnflags & SF_FADE_ONLYONE ) + { + if ( pActivator->IsNetClient() ) + { + UTIL_ScreenFade( pActivator, pev->rendercolor, Duration(), HoldTime(), static_cast(pev->renderamt), fadeFlags ); + } + } + else + { + UTIL_ScreenFadeAll( pev->rendercolor, Duration(), HoldTime(), static_cast(pev->renderamt), fadeFlags ); + } + SUB_UseTargets( this, USE_TOGGLE, 0 ); +} + + +class CMessage : public CPointEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); +private: +}; + +LINK_ENTITY_TO_CLASS( env_message, CMessage ); + + +void CMessage::Spawn( void ) +{ + Precache(); + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + switch( pev->impulse ) + { + case 1: // Medium radius + pev->speed = ATTN_STATIC; + break; + + case 2: // Large radius + pev->speed = ATTN_NORM; + break; + + case 3: //EVERYWHERE + pev->speed = ATTN_NONE; + break; + + default: + case 0: // Small radius + pev->speed = ATTN_IDLE; + break; + } + pev->impulse = 0; + + // No volume, use normal + if ( pev->scale <= 0 ) + pev->scale = 1.0; +} + + +void CMessage::Precache( void ) +{ + if ( pev->noise ) + PRECACHE_SOUND( (char *)STRING(pev->noise) ); +} + +void CMessage::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "messagesound")) + { + pev->noise = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "messagevolume")) + { + pev->scale = atof(pkvd->szValue) * 0.1; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "messageattenuation")) + { + pev->impulse = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CMessage::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBaseEntity *pPlayer = NULL; + + if ( pev->spawnflags & SF_MESSAGE_ALL ) + UTIL_ShowMessageAll( STRING(pev->message) ); + else + { + if ( pActivator && pActivator->IsPlayer() ) + pPlayer = pActivator; + else + { + pPlayer = CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) ); + } + if ( pPlayer ) + UTIL_ShowMessage( STRING(pev->message), pPlayer ); + } + if ( pev->noise ) + { + EMIT_SOUND( edict(), CHAN_BODY, STRING(pev->noise), pev->scale, pev->speed ); + } + if ( pev->spawnflags & SF_MESSAGE_ONCE ) + UTIL_Remove( this ); + + SUB_UseTargets( this, USE_TOGGLE, 0 ); +} + + + +//========================================================= +// FunnelEffect +//========================================================= +class CEnvFunnel : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int m_iSprite; // Don't save, precache +}; + +void CEnvFunnel :: Precache ( void ) +{ + m_iSprite = PRECACHE_MODEL ( "sprites/flare6.spr" ); +} + +LINK_ENTITY_TO_CLASS( env_funnel, CEnvFunnel ); + +void CEnvFunnel::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_LARGEFUNNEL ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( m_iSprite ); + + if ( pev->spawnflags & SF_FUNNEL_REVERSE )// funnel flows in reverse? + { + WRITE_SHORT( 1 ); + } + else + { + WRITE_SHORT( 0 ); + } + + + MESSAGE_END(); + + SetThink( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time; +} + +void CEnvFunnel::Spawn( void ) +{ + Precache(); + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; +} + +//========================================================= +// Beverage Dispenser +// overloaded pev->frags, is now a flag for whether or not a can is stuck in the dispenser. +// overloaded pev->health, is now how many cans remain in the machine. +//========================================================= +class CEnvBeverage : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; + +void CEnvBeverage :: Precache ( void ) +{ + PRECACHE_MODEL( "models/can.mdl" ); + PRECACHE_SOUND( "weapons/g_bounce3.wav" ); +} + +LINK_ENTITY_TO_CLASS( env_beverage, CEnvBeverage ); + +void CEnvBeverage::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->frags != 0 || pev->health <= 0 ) + { + // no more cans while one is waiting in the dispenser, or if I'm out of cans. + return; + } + + CBaseEntity *pCan = CBaseEntity::Create( "item_sodacan", pev->origin, pev->angles, edict() ); + + if ( pev->skin == 6 ) + { + // random + pCan->pev->skin = RANDOM_LONG( 0, 5 ); + } + else + { + pCan->pev->skin = pev->skin; + } + + pev->frags = 1; + pev->health--; + + //SetThink (SUB_Remove); + //pev->nextthink = gpGlobals->time; +} + +void CEnvBeverage::Spawn( void ) +{ + Precache(); + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; + pev->frags = 0; + + if ( pev->health == 0 ) + { + pev->health = 10; + } +} + +//========================================================= +// Soda can +//========================================================= +class CItemSoda : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT CanThink ( void ); + void EXPORT CanTouch ( CBaseEntity *pOther ); +}; + +void CItemSoda :: Precache ( void ) +{ +} + +LINK_ENTITY_TO_CLASS( item_sodacan, CItemSoda ); + +void CItemSoda::Spawn( void ) +{ + Precache(); + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_TOSS; + + SET_MODEL ( ENT(pev), "models/can.mdl" ); + UTIL_SetSize ( pev, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ) ); + + SetThink (&CItemSoda::CanThink); + pev->nextthink = gpGlobals->time + 0.5; +} + +void CItemSoda::CanThink ( void ) +{ + EMIT_SOUND (ENT(pev), CHAN_WEAPON, "weapons/g_bounce3.wav", 1, ATTN_NORM ); + + pev->solid = SOLID_TRIGGER; + UTIL_SetSize ( pev, Vector ( -8, -8, 0 ), Vector ( 8, 8, 8 ) ); + SetThink ( NULL ); + SetTouch ( &CItemSoda::CanTouch ); +} + +void CItemSoda::CanTouch ( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + { + return; + } + + // spoit sound here + + pOther->TakeHealth( 1, DMG_GENERIC );// a bit of health. + + if ( !FNullEnt( pev->owner ) ) + { + // tell the machine the can was taken + pev->owner->v.frags = 0; + } + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = EF_NODRAW; + SetTouch ( NULL ); + SetThink ( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time; +} diff --git a/hlsdk/dlls/effects.h b/hlsdk/dlls/effects.h new file mode 100644 index 00000000..bcfc78ae --- /dev/null +++ b/hlsdk/dlls/effects.h @@ -0,0 +1,209 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef EFFECTS_H +#define EFFECTS_H + +#define SF_BEAM_STARTON 0x0001 +#define SF_BEAM_TOGGLE 0x0002 +#define SF_BEAM_RANDOM 0x0004 +#define SF_BEAM_RING 0x0008 +#define SF_BEAM_SPARKSTART 0x0010 +#define SF_BEAM_SPARKEND 0x0020 +#define SF_BEAM_DECALS 0x0040 +#define SF_BEAM_SHADEIN 0x0080 +#define SF_BEAM_SHADEOUT 0x0100 +#define SF_BEAM_TEMPORARY 0x8000 + +#define SF_SPRITE_STARTON 0x0001 +#define SF_SPRITE_ONCE 0x0002 +#define SF_SPRITE_TEMPORARY 0x8000 + +class CSprite : public CPointEntity +{ +public: + void Spawn( void ); + void Precache( void ); + + int ObjectCaps( void ) + { + int flags = 0; + if ( pev->spawnflags & SF_SPRITE_TEMPORARY ) + flags = FCAP_DONT_SAVE; + return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | flags; + } + void EXPORT AnimateThink( void ); + void EXPORT ExpandThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Animate( float frames ); + void Expand( float scaleSpeed, float fadeSpeed ); + void SpriteInit( const char *pSpriteName, const Vector &origin ); + + inline void SetAttachment( edict_t *pEntity, int attachment ) + { + if ( pEntity ) + { + pev->skin = ENTINDEX(pEntity); + pev->body = attachment; + pev->aiment = pEntity; + pev->movetype = MOVETYPE_FOLLOW; + } + } + void TurnOff( void ); + void TurnOn( void ); + inline float Frames( void ) { return m_maxFrame; } + inline void SetTransparency( int rendermode, int r, int g, int b, int a, int fx ) + { + pev->rendermode = rendermode; + pev->rendercolor.x = r; + pev->rendercolor.y = g; + pev->rendercolor.z = b; + pev->renderamt = a; + pev->renderfx = fx; + } + inline void SetTexture( int spriteIndex ) { pev->modelindex = spriteIndex; } + inline void SetScale( float scale ) { pev->scale = scale; } + inline void SetColor( int r, int g, int b ) { pev->rendercolor.x = r; pev->rendercolor.y = g; pev->rendercolor.z = b; } + inline void SetBrightness( int brightness ) { pev->renderamt = brightness; } + + inline void AnimateAndDie( float framerate ) + { + SetThink(&CSprite::AnimateUntilDead); + pev->framerate = framerate; + pev->dmgtime = gpGlobals->time + (m_maxFrame / framerate); + pev->nextthink = gpGlobals->time; + } + + void EXPORT AnimateUntilDead( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + static CSprite *SpriteCreate( const char *pSpriteName, const Vector &origin, BOOL animate ); + +private: + + float m_lastTime; + float m_maxFrame; +}; + + +class CBeam : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + int ObjectCaps( void ) + { + int flags = 0; + if ( pev->spawnflags & SF_BEAM_TEMPORARY ) + flags = FCAP_DONT_SAVE; + return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | flags; + } + + void EXPORT TriggerTouch( CBaseEntity *pOther ); + + // These functions are here to show the way beams are encoded as entities. + // Encoding beams as entities simplifies their management in the client/server architecture + inline void SetType( int type ) { pev->rendermode = (pev->rendermode & 0xF0) | (type&0x0F); } + inline void SetFlags( int flags ) { pev->rendermode = (pev->rendermode & 0x0F) | (flags&0xF0); } + inline void SetStartPos( const Vector& pos ) { pev->origin = pos; } + inline void SetEndPos( const Vector& pos ) { pev->angles = pos; } + void SetStartEntity( int entityIndex ); + void SetEndEntity( int entityIndex ); + + inline void SetStartAttachment( int attachment ) { pev->sequence = (pev->sequence & 0x0FFF) | ((attachment&0xF)<<12); } + inline void SetEndAttachment( int attachment ) { pev->skin = (pev->skin & 0x0FFF) | ((attachment&0xF)<<12); } + + inline void SetTexture( int spriteIndex ) { pev->modelindex = spriteIndex; } + inline void SetWidth( int width ) { pev->scale = width; } + inline void SetNoise( int amplitude ) { pev->body = amplitude; } + inline void SetColor( int r, int g, int b ) { pev->rendercolor.x = r; pev->rendercolor.y = g; pev->rendercolor.z = b; } + inline void SetBrightness( int brightness ) { pev->renderamt = brightness; } + inline void SetFrame( float frame ) { pev->frame = frame; } + inline void SetScrollRate( int speed ) { pev->animtime = speed; } + + inline int GetType( void ) { return pev->rendermode & 0x0F; } + inline int GetFlags( void ) { return pev->rendermode & 0xF0; } + inline int GetStartEntity( void ) { return pev->sequence & 0xFFF; } + inline int GetEndEntity( void ) { return pev->skin & 0xFFF; } + + const Vector &GetStartPos( void ); + const Vector &GetEndPos( void ); + + Vector Center( void ) { return (GetStartPos() + GetEndPos()) * 0.5; }; // center point of beam + + inline int GetTexture( void ) { return pev->modelindex; } + inline int GetWidth( void ) { return static_cast(pev->scale); } + inline int GetNoise( void ) { return pev->body; } + // inline void GetColor( int r, int g, int b ) { pev->rendercolor.x = r; pev->rendercolor.y = g; pev->rendercolor.z = b; } + inline int GetBrightness( void ) { return static_cast(pev->renderamt); } + inline int GetFrame( void ) { return static_cast(pev->frame); } + inline int GetScrollRate( void ) { return static_cast(pev->animtime); } + + // Call after you change start/end positions + void RelinkBeam( void ); +// void SetObjectCollisionBox( void ); + + void DoSparks( const Vector &start, const Vector &end ); + CBaseEntity *RandomTargetname( const char *szName ); + void BeamDamage( TraceResult *ptr ); + // Init after BeamCreate() + void BeamInit( const char *pSpriteName, int width ); + void PointsInit( const Vector &start, const Vector &end ); + void PointEntInit( const Vector &start, int endIndex ); + void EntsInit( int startIndex, int endIndex ); + void HoseInit( const Vector &start, const Vector &direction ); + + static CBeam *BeamCreate( const char *pSpriteName, int width ); + + inline void LiveForTime( float time ) { SetThink(&CBaseEntity::SUB_Remove); pev->nextthink = gpGlobals->time + time; } + inline void BeamDamageInstant( TraceResult *ptr, float damage ) + { + pev->dmg = damage; + pev->dmgtime = gpGlobals->time - 1; + BeamDamage(ptr); + } +}; + + +#define SF_MESSAGE_ONCE 0x0001 // Fade in, not out +#define SF_MESSAGE_ALL 0x0002 // Send to all clients + + +class CLaser : public CBeam +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + void TurnOn( void ); + void TurnOff( void ); + int IsOn( void ); + + void FireAtPoint( TraceResult &point ); + + void EXPORT StrikeThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + CSprite *m_pSprite; + int m_iszSpriteName; + Vector m_firePosition; +}; + +#endif //EFFECTS_H diff --git a/hlsdk/dlls/egon.cpp b/hlsdk/dlls/egon.cpp new file mode 100644 index 00000000..71d3609e --- /dev/null +++ b/hlsdk/dlls/egon.cpp @@ -0,0 +1,568 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "effects.h" +#include "customentity.h" +#include "gamerules.h" + +#define EGON_PRIMARY_VOLUME 450 +#define EGON_BEAM_SPRITE "sprites/xbeam1.spr" +#define EGON_FLARE_SPRITE "sprites/XSpark1.spr" +#define EGON_SOUND_OFF "weapons/egon_off1.wav" +#define EGON_SOUND_RUN "weapons/egon_run3.wav" +#define EGON_SOUND_STARTUP "weapons/egon_windup2.wav" + +#define EGON_SWITCH_NARROW_TIME 0.75 // Time it takes to switch fire modes +#define EGON_SWITCH_WIDE_TIME 1.5 + +enum egon_e { + EGON_IDLE1 = 0, + EGON_FIDGET1, + EGON_ALTFIREON, + EGON_ALTFIRECYCLE, + EGON_ALTFIREOFF, + EGON_FIRE1, + EGON_FIRE2, + EGON_FIRE3, + EGON_FIRE4, + EGON_DRAW, + EGON_HOLSTER +}; + +LINK_ENTITY_TO_CLASS( weapon_egon, CEgon ); + +void CEgon::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_EGON; + SET_MODEL(ENT(pev), "models/w_egon.mdl"); + + m_iDefaultAmmo = EGON_DEFAULT_GIVE; + + FallInit();// get ready to fall down. +} + + +void CEgon::Precache( void ) +{ + PRECACHE_MODEL("models/w_egon.mdl"); + PRECACHE_MODEL("models/v_egon.mdl"); + PRECACHE_MODEL("models/p_egon.mdl"); + + PRECACHE_MODEL("models/w_9mmclip.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + + PRECACHE_SOUND( EGON_SOUND_OFF ); + PRECACHE_SOUND( EGON_SOUND_RUN ); + PRECACHE_SOUND( EGON_SOUND_STARTUP ); + + PRECACHE_MODEL( EGON_BEAM_SPRITE ); + PRECACHE_MODEL( EGON_FLARE_SPRITE ); + + PRECACHE_SOUND ("weapons/357_cock1.wav"); + + m_usEgonFire = PRECACHE_EVENT ( 1, "events/egon_fire.sc" ); + m_usEgonStop = PRECACHE_EVENT ( 1, "events/egon_stop.sc" ); +} + + +BOOL CEgon::Deploy( void ) +{ + m_deployed = FALSE; + m_fireState = FIRE_OFF; + return DefaultDeploy( "models/v_egon.mdl", "models/p_egon.mdl", EGON_DRAW, "egon" ); +} + +int CEgon::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + + + +void CEgon::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + SendWeaponAnim( EGON_HOLSTER ); + + EndAttack(); +} + +int CEgon::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "uranium"; + p->iMaxAmmo1 = URANIUM_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 3; + p->iPosition = 2; + p->iId = m_iId = WEAPON_EGON; + p->iFlags = 0; + p->iWeight = EGON_WEIGHT; + + return 1; +} + +#define EGON_PULSE_INTERVAL 0.1 +#define EGON_DISCHARGE_INTERVAL 0.1 + +float CEgon::GetPulseInterval( void ) +{ + return EGON_PULSE_INTERVAL; +} + +float CEgon::GetDischargeInterval( void ) +{ + return EGON_DISCHARGE_INTERVAL; +} + +BOOL CEgon::HasAmmo( void ) +{ + if ( m_pPlayer->ammo_uranium <= 0 ) + return FALSE; + + return TRUE; +} + +void CEgon::UseAmmo( int count ) +{ + if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] >= count ) + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= count; + else + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] = 0; +} + +void CEgon::Attack( void ) +{ + // don't fire underwater + if ( m_pPlayer->pev->waterlevel == 3 ) + { + + if ( m_fireState != FIRE_OFF || m_pBeam ) + { + EndAttack(); + } + else + { + PlayEmptySound( ); + } + return; + } + + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + Vector vecAiming = gpGlobals->v_forward; + Vector vecSrc = m_pPlayer->GetGunPosition( ); + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + switch( m_fireState ) + { + case FIRE_OFF: + { + if ( !HasAmmo() ) + { + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.25; + PlayEmptySound( ); + return; + } + + m_flAmmoUseTime = gpGlobals->time;// start using ammo ASAP. + + PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usEgonFire, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, m_fireState, m_fireMode, 1, 0 ); + + m_shakeTime = 0; + + m_pPlayer->m_iWeaponVolume = EGON_PRIMARY_VOLUME; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.1; + pev->fuser1 = UTIL_WeaponTimeBase() + 2; + + pev->dmgtime = gpGlobals->time + GetPulseInterval(); + m_fireState = FIRE_CHARGE; + } + break; + + case FIRE_CHARGE: + { + Fire( vecSrc, vecAiming ); + m_pPlayer->m_iWeaponVolume = EGON_PRIMARY_VOLUME; + + if ( pev->fuser1 <= UTIL_WeaponTimeBase() ) + { + PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usEgonFire, 0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, m_fireState, m_fireMode, 0, 0 ); + pev->fuser1 = 1000; + } + + if ( !HasAmmo() ) + { + EndAttack(); + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 1.0; + } + + } + break; + } +} + +void CEgon::PrimaryAttack( void ) +{ + m_fireMode = FIRE_WIDE; + Attack(); + +} + +void CEgon::Fire( const Vector &vecOrigSrc, const Vector &vecDir ) +{ + Vector vecDest = vecOrigSrc + vecDir * 2048; + edict_t *pentIgnore; + TraceResult tr; + + pentIgnore = m_pPlayer->edict(); + Vector tmpSrc = vecOrigSrc + gpGlobals->v_up * -8 + gpGlobals->v_right * 3; + + // ALERT( at_console, "." ); + + UTIL_TraceLine( vecOrigSrc, vecDest, dont_ignore_monsters, pentIgnore, &tr ); + + if (tr.fAllSolid) + return; + +#ifndef CLIENT_DLL + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + + if (pEntity == NULL) + return; + + if ( g_pGameRules->IsMultiplayer() ) + { + if ( m_pSprite && pEntity->pev->takedamage ) + { + m_pSprite->pev->effects &= ~EF_NODRAW; + } + else if ( m_pSprite ) + { + m_pSprite->pev->effects |= EF_NODRAW; + } + } + + +#endif + + float timedist = 0.0f; + + switch ( m_fireMode ) + { + case FIRE_NARROW: +#ifndef CLIENT_DLL + if ( pev->dmgtime < gpGlobals->time ) + { + // Narrow mode only does damage to the entity it hits + ClearMultiDamage(); + if (pEntity->pev->takedamage) + { + pEntity->TraceAttack( m_pPlayer->pev, gSkillData.plrDmgEgonNarrow, vecDir, &tr, DMG_ENERGYBEAM ); + } + ApplyMultiDamage(m_pPlayer->pev, m_pPlayer->pev); + + if ( g_pGameRules->IsMultiplayer() ) + { + // multiplayer uses 1 ammo every 1/10th second + if ( gpGlobals->time >= m_flAmmoUseTime ) + { + UseAmmo( 1 ); + m_flAmmoUseTime = gpGlobals->time + 0.1; + } + } + else + { + // single player, use 3 ammo/second + if ( gpGlobals->time >= m_flAmmoUseTime ) + { + UseAmmo( 1 ); + m_flAmmoUseTime = gpGlobals->time + 0.166; + } + } + + pev->dmgtime = gpGlobals->time + GetPulseInterval(); + } +#endif + timedist = ( pev->dmgtime - gpGlobals->time ) / GetPulseInterval(); + break; + + case FIRE_WIDE: +#ifndef CLIENT_DLL + if ( pev->dmgtime < gpGlobals->time ) + { + // wide mode does damage to the ent, and radius damage + ClearMultiDamage(); + if (pEntity->pev->takedamage) + { + pEntity->TraceAttack( m_pPlayer->pev, gSkillData.plrDmgEgonWide, vecDir, &tr, DMG_ENERGYBEAM | DMG_ALWAYSGIB); + } + ApplyMultiDamage(m_pPlayer->pev, m_pPlayer->pev); + + if ( g_pGameRules->IsMultiplayer() ) + { + // radius damage a little more potent in multiplayer. + ::RadiusDamage( tr.vecEndPos, pev, m_pPlayer->pev, gSkillData.plrDmgEgonWide/4, 128, CLASS_NONE, DMG_ENERGYBEAM | DMG_BLAST | DMG_ALWAYSGIB ); + } + + if ( !m_pPlayer->IsAlive() ) + return; + + if ( g_pGameRules->IsMultiplayer() ) + { + //multiplayer uses 5 ammo/second + if ( gpGlobals->time >= m_flAmmoUseTime ) + { + UseAmmo( 1 ); + m_flAmmoUseTime = gpGlobals->time + 0.2; + } + } + else + { + // Wide mode uses 10 charges per second in single player + if ( gpGlobals->time >= m_flAmmoUseTime ) + { + UseAmmo( 1 ); + m_flAmmoUseTime = gpGlobals->time + 0.1; + } + } + + pev->dmgtime = gpGlobals->time + GetDischargeInterval(); + if ( m_shakeTime < gpGlobals->time ) + { + UTIL_ScreenShake( tr.vecEndPos, 5.0, 150.0, 0.75, 250.0 ); + m_shakeTime = gpGlobals->time + 1.5; + } + } +#endif + timedist = ( pev->dmgtime - gpGlobals->time ) / GetDischargeInterval(); + break; + } + + if ( timedist < 0 ) + timedist = 0; + else if ( timedist > 1 ) + timedist = 1; + timedist = 1-timedist; + + UpdateEffect( tmpSrc, tr.vecEndPos, timedist ); +} + + +void CEgon::UpdateEffect( const Vector &startPoint, const Vector &endPoint, float timeBlend ) +{ +#ifndef CLIENT_DLL + if ( !m_pBeam ) + { + CreateEffect(); + } + + m_pBeam->SetStartPos( endPoint ); + m_pBeam->SetBrightness( static_cast(255 - (timeBlend*180)) ); + m_pBeam->SetWidth( static_cast(40 - (timeBlend*20)) ); + + if ( m_fireMode == FIRE_WIDE ) + m_pBeam->SetColor( static_cast(30 + (25*timeBlend)), static_cast(30 + (30*timeBlend)), static_cast(64 + 80*fabs(sin(gpGlobals->time*10))) ); + else + m_pBeam->SetColor( static_cast(60 + (25*timeBlend)), static_cast(120 + (30*timeBlend)), static_cast(64 + 80*fabs(sin(gpGlobals->time*10))) ); + + + UTIL_SetOrigin( m_pSprite->pev, endPoint ); + m_pSprite->pev->frame += 8 * gpGlobals->frametime; + if ( m_pSprite->pev->frame > m_pSprite->Frames() ) + m_pSprite->pev->frame = 0; + + m_pNoise->SetStartPos( endPoint ); + +#endif + +} + +void CEgon::CreateEffect( void ) +{ + +#ifndef CLIENT_DLL + DestroyEffect(); + + m_pBeam = CBeam::BeamCreate( EGON_BEAM_SPRITE, 40 ); + m_pBeam->PointEntInit( pev->origin, m_pPlayer->entindex() ); + m_pBeam->SetFlags( BEAM_FSINE ); + m_pBeam->SetEndAttachment( 1 ); + m_pBeam->pev->spawnflags |= SF_BEAM_TEMPORARY; // Flag these to be destroyed on save/restore or level transition + m_pBeam->pev->flags |= FL_SKIPLOCALHOST; + m_pBeam->pev->owner = m_pPlayer->edict(); + + m_pNoise = CBeam::BeamCreate( EGON_BEAM_SPRITE, 55 ); + m_pNoise->PointEntInit( pev->origin, m_pPlayer->entindex() ); + m_pNoise->SetScrollRate( 25 ); + m_pNoise->SetBrightness( 100 ); + m_pNoise->SetEndAttachment( 1 ); + m_pNoise->pev->spawnflags |= SF_BEAM_TEMPORARY; + m_pNoise->pev->flags |= FL_SKIPLOCALHOST; + m_pNoise->pev->owner = m_pPlayer->edict(); + + m_pSprite = CSprite::SpriteCreate( EGON_FLARE_SPRITE, pev->origin, FALSE ); + m_pSprite->pev->scale = 1.0; + m_pSprite->SetTransparency( kRenderGlow, 255, 255, 255, 255, kRenderFxNoDissipation ); + m_pSprite->pev->spawnflags |= SF_SPRITE_TEMPORARY; + m_pSprite->pev->flags |= FL_SKIPLOCALHOST; + m_pSprite->pev->owner = m_pPlayer->edict(); + + if ( m_fireMode == FIRE_WIDE ) + { + m_pBeam->SetScrollRate( 50 ); + m_pBeam->SetNoise( 20 ); + m_pNoise->SetColor( 50, 50, 255 ); + m_pNoise->SetNoise( 8 ); + } + else + { + m_pBeam->SetScrollRate( 110 ); + m_pBeam->SetNoise( 5 ); + m_pNoise->SetColor( 80, 120, 255 ); + m_pNoise->SetNoise( 2 ); + } +#endif + +} + + +void CEgon::DestroyEffect( void ) +{ + +#ifndef CLIENT_DLL + if ( m_pBeam ) + { + UTIL_Remove( m_pBeam ); + m_pBeam = NULL; + } + if ( m_pNoise ) + { + UTIL_Remove( m_pNoise ); + m_pNoise = NULL; + } + if ( m_pSprite ) + { + if ( m_fireMode == FIRE_WIDE ) + m_pSprite->Expand( 10, 500 ); + else + UTIL_Remove( m_pSprite ); + m_pSprite = NULL; + } +#endif + +} + + + +void CEgon::WeaponIdle( void ) +{ + ResetEmptySound( ); + + if ( m_flTimeWeaponIdle > gpGlobals->time ) + return; + + if ( m_fireState != FIRE_OFF ) + EndAttack(); + + int iAnim; + + float flRand = RANDOM_FLOAT(0,1); + + if ( flRand <= 0.5 ) + { + iAnim = EGON_IDLE1; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + } + else + { + iAnim = EGON_FIDGET1; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 3; + } + + SendWeaponAnim( iAnim ); + m_deployed = TRUE; +} + + + +void CEgon::EndAttack( void ) +{ + bool bMakeNoise = false; + + if ( m_fireState != FIRE_OFF ) //Checking the button just in case!. + bMakeNoise = true; + + PLAYBACK_EVENT_FULL( FEV_GLOBAL | FEV_RELIABLE, m_pPlayer->edict(), m_usEgonStop, 0, (float *)&m_pPlayer->pev->origin, (float *)&m_pPlayer->pev->angles, 0.0, 0.0, bMakeNoise, 0, 0, 0 ); + + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 2.0; + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.5; + + m_fireState = FIRE_OFF; + + DestroyEffect(); +} + + + +class CEgonAmmo : public CBasePlayerAmmo +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_chainammo.mdl"); + CBasePlayerAmmo::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_chainammo.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + } + BOOL AddAmmo( CBaseEntity *pOther ) + { + if (pOther->GiveAmmo( AMMO_URANIUMBOX_GIVE, "uranium", URANIUM_MAX_CARRY ) != -1) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + return TRUE; + } + return FALSE; + } +}; +LINK_ENTITY_TO_CLASS( ammo_egonclip, CEgonAmmo ); + +#endif diff --git a/hlsdk/dlls/enginecallback.h b/hlsdk/dlls/enginecallback.h new file mode 100644 index 00000000..d74ec5b5 --- /dev/null +++ b/hlsdk/dlls/enginecallback.h @@ -0,0 +1,165 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef ENGINECALLBACK_H +#define ENGINECALLBACK_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +#include "event_flags.h" + +// Fix warning in MSVC8 +#undef SERVER_EXECUTE + +// Must be provided by user of this code +extern enginefuncs_t g_engfuncs; + +// The actual engine callbacks +#define GETPLAYERUSERID (*g_engfuncs.pfnGetPlayerUserId) +#define PRECACHE_MODEL (*g_engfuncs.pfnPrecacheModel) +#define PRECACHE_SOUND (*g_engfuncs.pfnPrecacheSound) +#define PRECACHE_GENERIC (*g_engfuncs.pfnPrecacheGeneric) +#define SET_MODEL (*g_engfuncs.pfnSetModel) +#define MODEL_INDEX (*g_engfuncs.pfnModelIndex) +#define MODEL_FRAMES (*g_engfuncs.pfnModelFrames) +#define SET_SIZE (*g_engfuncs.pfnSetSize) +#define CHANGE_LEVEL (*g_engfuncs.pfnChangeLevel) +#define GET_SPAWN_PARMS (*g_engfuncs.pfnGetSpawnParms) +#define SAVE_SPAWN_PARMS (*g_engfuncs.pfnSaveSpawnParms) +#define VEC_TO_YAW (*g_engfuncs.pfnVecToYaw) +#define VEC_TO_ANGLES (*g_engfuncs.pfnVecToAngles) +#define MOVE_TO_ORIGIN (*g_engfuncs.pfnMoveToOrigin) +#define oldCHANGE_YAW (*g_engfuncs.pfnChangeYaw) +#define CHANGE_PITCH (*g_engfuncs.pfnChangePitch) +#define MAKE_VECTORS (*g_engfuncs.pfnMakeVectors) +#define CREATE_ENTITY (*g_engfuncs.pfnCreateEntity) +#define REMOVE_ENTITY (*g_engfuncs.pfnRemoveEntity) +#define CREATE_NAMED_ENTITY (*g_engfuncs.pfnCreateNamedEntity) +#define MAKE_STATIC (*g_engfuncs.pfnMakeStatic) +#define ENT_IS_ON_FLOOR (*g_engfuncs.pfnEntIsOnFloor) +#define DROP_TO_FLOOR (*g_engfuncs.pfnDropToFloor) +#define WALK_MOVE (*g_engfuncs.pfnWalkMove) +#define SET_ORIGIN (*g_engfuncs.pfnSetOrigin) +#define EMIT_SOUND_DYN2 (*g_engfuncs.pfnEmitSound) +#define BUILD_SOUND_MSG (*g_engfuncs.pfnBuildSoundMsg) +#define TRACE_LINE (*g_engfuncs.pfnTraceLine) +#define TRACE_TOSS (*g_engfuncs.pfnTraceToss) +#define TRACE_MONSTER_HULL (*g_engfuncs.pfnTraceMonsterHull) +#define TRACE_HULL (*g_engfuncs.pfnTraceHull) +#define GET_AIM_VECTOR (*g_engfuncs.pfnGetAimVector) +#define SERVER_COMMAND (*g_engfuncs.pfnServerCommand) +#define SERVER_EXECUTE (*g_engfuncs.pfnServerExecute) +#define CLIENT_COMMAND (*g_engfuncs.pfnClientCommand) +#define PARTICLE_EFFECT (*g_engfuncs.pfnParticleEffect) +#define LIGHT_STYLE (*g_engfuncs.pfnLightStyle) +#define DECAL_INDEX (*g_engfuncs.pfnDecalIndex) +#define POINT_CONTENTS (*g_engfuncs.pfnPointContents) +#define CRC32_INIT (*g_engfuncs.pfnCRC32_Init) +#define CRC32_PROCESS_BUFFER (*g_engfuncs.pfnCRC32_ProcessBuffer) +#define CRC32_PROCESS_BYTE (*g_engfuncs.pfnCRC32_ProcessByte) +#define CRC32_FINAL (*g_engfuncs.pfnCRC32_Final) +#define RANDOM_LONG (*g_engfuncs.pfnRandomLong) +#define RANDOM_FLOAT (*g_engfuncs.pfnRandomFloat) +#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); +} +#define MESSAGE_END (*g_engfuncs.pfnMessageEnd) +#define WRITE_BYTE (*g_engfuncs.pfnWriteByte) +#define WRITE_CHAR (*g_engfuncs.pfnWriteChar) +#define WRITE_SHORT (*g_engfuncs.pfnWriteShort) +#define WRITE_LONG (*g_engfuncs.pfnWriteLong) +#define WRITE_ANGLE (*g_engfuncs.pfnWriteAngle) +#define WRITE_COORD (*g_engfuncs.pfnWriteCoord) +#define WRITE_STRING (*g_engfuncs.pfnWriteString) +#define WRITE_ENTITY (*g_engfuncs.pfnWriteEntity) +#define CVAR_REGISTER (*g_engfuncs.pfnCVarRegister) +#define CVAR_GET_FLOAT (*g_engfuncs.pfnCVarGetFloat) +#define CVAR_GET_STRING (*g_engfuncs.pfnCVarGetString) +#define CVAR_SET_FLOAT (*g_engfuncs.pfnCVarSetFloat) +#define CVAR_SET_STRING (*g_engfuncs.pfnCVarSetString) +#define CVAR_GET_POINTER (*g_engfuncs.pfnCVarGetPointer) +#define ALERT (*g_engfuncs.pfnAlertMessage) +#define ENGINE_FPRINTF (*g_engfuncs.pfnEngineFprintf) +#define ALLOC_PRIVATE (*g_engfuncs.pfnPvAllocEntPrivateData) +inline void *GET_PRIVATE( edict_t *pent ) +{ + if ( pent ) + return pent->pvPrivateData; + return NULL; +} + +#define FREE_PRIVATE (*g_engfuncs.pfnFreeEntPrivateData) +//#define STRING (*g_engfuncs.pfnSzFromIndex) +#define ALLOC_STRING (*g_engfuncs.pfnAllocString) +#define FIND_ENTITY_BY_STRING (*g_engfuncs.pfnFindEntityByString) +#define GETENTITYILLUM (*g_engfuncs.pfnGetEntityIllum) +#define FIND_ENTITY_IN_SPHERE (*g_engfuncs.pfnFindEntityInSphere) +#define FIND_CLIENT_IN_PVS (*g_engfuncs.pfnFindClientInPVS) +#define EMIT_AMBIENT_SOUND (*g_engfuncs.pfnEmitAmbientSound) +#define GET_MODEL_PTR (*g_engfuncs.pfnGetModelPtr) +#define REG_USER_MSG (*g_engfuncs.pfnRegUserMsg) +#define GET_BONE_POSITION (*g_engfuncs.pfnGetBonePosition) +#define FUNCTION_FROM_NAME (*g_engfuncs.pfnFunctionFromName) +#define NAME_FOR_FUNCTION (*g_engfuncs.pfnNameForFunction) +#define TRACE_TEXTURE (*g_engfuncs.pfnTraceTexture) +#define CLIENT_PRINTF (*g_engfuncs.pfnClientPrintf) +#define CMD_ARGS (*g_engfuncs.pfnCmd_Args) +#define CMD_ARGC (*g_engfuncs.pfnCmd_Argc) +#define CMD_ARGV (*g_engfuncs.pfnCmd_Argv) +#define GET_ATTACHMENT (*g_engfuncs.pfnGetAttachment) +#define SET_VIEW (*g_engfuncs.pfnSetView) +#define SET_CROSSHAIRANGLE (*g_engfuncs.pfnCrosshairAngle) +#define LOAD_FILE_FOR_ME (*g_engfuncs.pfnLoadFileForMe) +#define FREE_FILE (*g_engfuncs.pfnFreeFile) +#define COMPARE_FILE_TIME (*g_engfuncs.pfnCompareFileTime) +#define GET_GAME_DIR (*g_engfuncs.pfnGetGameDir) +#define IS_MAP_VALID (*g_engfuncs.pfnIsMapValid) +#define NUMBER_OF_ENTITIES (*g_engfuncs.pfnNumberOfEntities) +#define IS_DEDICATED_SERVER (*g_engfuncs.pfnIsDedicatedServer) + +#define PRECACHE_EVENT (*g_engfuncs.pfnPrecacheEvent) +#define PLAYBACK_EVENT_FULL (*g_engfuncs.pfnPlaybackEvent) + +#define ENGINE_SET_PVS (*g_engfuncs.pfnSetFatPVS) +#define ENGINE_SET_PAS (*g_engfuncs.pfnSetFatPAS) + +#define ENGINE_CHECK_VISIBILITY (*g_engfuncs.pfnCheckVisibility) + +#define DELTA_SET ( *g_engfuncs.pfnDeltaSetField ) +#define DELTA_UNSET ( *g_engfuncs.pfnDeltaUnsetField ) +#define DELTA_ADDENCODER ( *g_engfuncs.pfnDeltaAddEncoder ) +#define ENGINE_CURRENT_PLAYER ( *g_engfuncs.pfnGetCurrentPlayer ) + +#define ENGINE_CANSKIP ( *g_engfuncs.pfnCanSkipPlayer ) + +#define DELTA_FINDFIELD ( *g_engfuncs.pfnDeltaFindField ) +#define DELTA_SETBYINDEX ( *g_engfuncs.pfnDeltaSetFieldByIndex ) +#define DELTA_UNSETBYINDEX ( *g_engfuncs.pfnDeltaUnsetFieldByIndex ) + +#define ENGINE_GETPHYSINFO ( *g_engfuncs.pfnGetPhysicsInfoString ) + +#define ENGINE_SETGROUPMASK ( *g_engfuncs.pfnSetGroupMask ) + +#define ENGINE_INSTANCE_BASELINE ( *g_engfuncs.pfnCreateInstancedBaseline ) + +#define ENGINE_FORCE_UNMODIFIED ( *g_engfuncs.pfnForceUnmodified ) + +#define PLAYER_CNX_STATS ( *g_engfuncs.pfnGetPlayerStats ) + +#endif //ENGINECALLBACK_H diff --git a/hlsdk/dlls/explode.cpp b/hlsdk/dlls/explode.cpp new file mode 100644 index 00000000..4e5f1c15 --- /dev/null +++ b/hlsdk/dlls/explode.cpp @@ -0,0 +1,273 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== explode.cpp ======================================================== + + Explosion-related code + +*/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "decals.h" +#include "explode.h" + +// Spark Shower +class CShower : public CBaseEntity +{ + void Spawn( void ); + void Think( void ); + void Touch( CBaseEntity *pOther ); + int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +}; + +LINK_ENTITY_TO_CLASS( spark_shower, CShower ); + +void CShower::Spawn( void ) +{ + pev->velocity = RANDOM_FLOAT( 200, 300 ) * pev->angles; + pev->velocity.x += RANDOM_FLOAT(-100.f,100.f); + pev->velocity.y += RANDOM_FLOAT(-100.f,100.f); + if ( pev->velocity.z >= 0 ) + pev->velocity.z += 200; + else + pev->velocity.z -= 200; + pev->movetype = MOVETYPE_BOUNCE; + pev->gravity = 0.5; + pev->nextthink = gpGlobals->time + 0.1; + pev->solid = SOLID_NOT; + SET_MODEL( edict(), "models/grenade.mdl"); // Need a model, just use the grenade, we don't draw it anyway + UTIL_SetSize(pev, g_vecZero, g_vecZero ); + pev->effects |= EF_NODRAW; + pev->speed = RANDOM_FLOAT( 0.5, 1.5 ); + + pev->angles = g_vecZero; +} + + +void CShower::Think( void ) +{ + UTIL_Sparks( pev->origin ); + + pev->speed -= 0.1; + if ( pev->speed > 0 ) + pev->nextthink = gpGlobals->time + 0.1; + else + UTIL_Remove( this ); + pev->flags &= ~FL_ONGROUND; +} + +void CShower::Touch( CBaseEntity *pOther ) +{ + if ( pev->flags & FL_ONGROUND ) + pev->velocity = pev->velocity * 0.1; + else + pev->velocity = pev->velocity * 0.6; + + if ( (pev->velocity.x*pev->velocity.x+pev->velocity.y*pev->velocity.y) < 10.0 ) + pev->speed = 0; +} + +class CEnvExplosion : public CBaseMonster +{ +public: + void Spawn( ); + void EXPORT Smoke ( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_iMagnitude;// how large is the fireball? how much damage? + int m_spriteScale; // what's the exact fireball sprite scale? +}; + +TYPEDESCRIPTION CEnvExplosion::m_SaveData[] = +{ + DEFINE_FIELD( CEnvExplosion, m_iMagnitude, FIELD_INTEGER ), + DEFINE_FIELD( CEnvExplosion, m_spriteScale, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CEnvExplosion, CBaseMonster ); +LINK_ENTITY_TO_CLASS( env_explosion, CEnvExplosion ); + +void CEnvExplosion::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "iMagnitude")) + { + m_iMagnitude = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CEnvExplosion::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; + + pev->movetype = MOVETYPE_NONE; + /* + if ( m_iMagnitude > 250 ) + { + m_iMagnitude = 250; + } + */ + + float flSpriteScale; + flSpriteScale = ( m_iMagnitude - 50) * 0.6; + + /* + if ( flSpriteScale > 50 ) + { + flSpriteScale = 50; + } + */ + if ( flSpriteScale < 10 ) + { + flSpriteScale = 10; + } + + m_spriteScale = (int)flSpriteScale; +} + +void CEnvExplosion::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + TraceResult tr; + + pev->model = iStringNull;//invisible + pev->solid = SOLID_NOT;// intangible + + 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); + + // Pull out of the wall a bit + if ( tr.flFraction != 1.0 ) + { + pev->origin = tr.vecEndPos + (tr.vecPlaneNormal * (m_iMagnitude - 24) * 0.6); + } + else + { + pev->origin = pev->origin; + } + + // draw decal + if (! ( pev->spawnflags & SF_ENVEXPLOSION_NODECAL)) + { + if ( RANDOM_FLOAT( 0 , 1 ) < 0.5 ) + { + UTIL_DecalTrace( &tr, DECAL_SCORCH1 ); + } + else + { + UTIL_DecalTrace( &tr, DECAL_SCORCH2 ); + } + } + + // draw fireball + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NOFIREBALL ) ) + { + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( (BYTE)m_spriteScale ); // scale * 10 + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + } + else + { + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( 0 ); // no sprite + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + } + + // do damage + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NODAMAGE ) ) + { + RadiusDamage ( pev, pev, m_iMagnitude, CLASS_NONE, DMG_BLAST ); + } + + SetThink( &CEnvExplosion::Smoke ); + pev->nextthink = gpGlobals->time + 0.3; + + // draw sparks + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NOSPARKS ) ) + { + int sparkCount = RANDOM_LONG(0,3); + + for ( int i = 0; i < sparkCount; i++ ) + { + Create( "spark_shower", pev->origin, tr.vecPlaneNormal, NULL ); + } + } +} + +void CEnvExplosion::Smoke( void ) +{ + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NOSMOKE ) ) + { + MESSAGE_BEGIN( MSG_PAS, 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)m_spriteScale ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + } + + if ( !(pev->spawnflags & SF_ENVEXPLOSION_REPEATABLE) ) + { + UTIL_Remove( this ); + } +} + + +// HACKHACK -- create one of these and fake a keyvalue to get the right explosion setup +void ExplosionCreate( const Vector ¢er, const Vector &angles, edict_t *pOwner, int magnitude, BOOL doDamage ) +{ + KeyValueData kvd; + char buf[128]; + + CBaseEntity *pExplosion = CBaseEntity::Create( "env_explosion", center, angles, pOwner ); + sprintf( buf, "%3d", magnitude ); + kvd.szKeyName = "iMagnitude"; + kvd.szValue = buf; + pExplosion->KeyValue( &kvd ); + if ( !doDamage ) + pExplosion->pev->spawnflags |= SF_ENVEXPLOSION_NODAMAGE; + + pExplosion->Spawn(); + pExplosion->Use( NULL, NULL, USE_TOGGLE, 0 ); +} diff --git a/hlsdk/dlls/explode.h b/hlsdk/dlls/explode.h new file mode 100644 index 00000000..3feb011c --- /dev/null +++ b/hlsdk/dlls/explode.h @@ -0,0 +1,32 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef EXPLODE_H +#define EXPLODE_H + + +#define SF_ENVEXPLOSION_NODAMAGE ( 1 << 0 ) // when set, ENV_EXPLOSION will not actually inflict damage +#define SF_ENVEXPLOSION_REPEATABLE ( 1 << 1 ) // can this entity be refired? +#define SF_ENVEXPLOSION_NOFIREBALL ( 1 << 2 ) // don't draw the fireball +#define SF_ENVEXPLOSION_NOSMOKE ( 1 << 3 ) // don't draw the smoke +#define SF_ENVEXPLOSION_NODECAL ( 1 << 4 ) // don't make a scorch mark +#define SF_ENVEXPLOSION_NOSPARKS ( 1 << 5 ) // don't make a scorch mark + +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/hlsdk/dlls/extdll.h b/hlsdk/dlls/extdll.h new file mode 100644 index 00000000..c2e417c4 --- /dev/null +++ b/hlsdk/dlls/extdll.h @@ -0,0 +1,99 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef EXTDLL_H +#define EXTDLL_H + + +// +// Global header file for extension DLLs +// + +// Allow "DEBUG" in addition to default "_DEBUG" +#ifdef _DEBUG + #define DEBUG 1 +#endif + +// Silence certain warnings +#ifdef _MSC_VER + #pragma warning(disable : 4244) // int or float down-conversion + #pragma warning(disable : 4305) // int or float data truncation + #pragma warning(disable : 4201) // nameless struct/union + #pragma warning(disable : 4514) // unreferenced inline function removed + #pragma warning(disable : 4100) // unreferenced formal parameter + + #if _MSC_VER >= 1400 + #ifndef _CRT_SECURE_NO_DEPRECATE + #define _CRT_SECURE_NO_DEPRECATE + #endif + + #pragma warning(disable: 4996) // deprecated functions + #endif +#endif + +// Prevent tons of unused windows definitions +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOWINRES +#define NOSERVICE +#define NOMCX +#define NOIME +#include "windows.h" +#else // _WIN32 +#define FALSE 0 +#define TRUE (!FALSE) +typedef unsigned long ULONG; +typedef unsigned char BYTE; +typedef int BOOL; +#define MAX_PATH PATH_MAX +#include +#include +#include +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#define _vsnprintf(a,b,c,d) vsnprintf(a,b,c,d) +#endif +#endif //_WIN32 + +// Misc C-runtime library headers +#include "stdio.h" +#include "stdlib.h" +#include "math.h" + +// Header file containing definition of globalvars_t and entvars_t +typedef int func_t; // +typedef int string_t; // from engine's pr_comp.h; +typedef float vec_t; // needed before including progdefs.h + +// Vector class +#include "vector.h" + +// Defining it as a (bogus) struct helps enforce type-checking +#define vec3_t Vector + +// Shared engine/DLL constants +#include "const.h" +#include "progdefs.h" +#include "edict.h" + +// Shared header describing protocol between engine and DLLs +#include "eiface.h" + +// Shared header between the client DLL and the game DLLs +#include "cdll_dll.h" + +#endif //EXTDLL_H diff --git a/hlsdk/dlls/func_break.cpp b/hlsdk/dlls/func_break.cpp new file mode 100644 index 00000000..e94e8f56 --- /dev/null +++ b/hlsdk/dlls/func_break.cpp @@ -0,0 +1,1011 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== bmodels.cpp ======================================================== + + spawn, think, and use functions for entities that use brush models + +*/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "func_break.h" +#include "decals.h" +#include "explode.h" + +extern DLL_GLOBAL Vector g_vecAttackDir; + +// =================== FUNC_Breakable ============================================== + +// Just add more items to the bottom of this array and they will automagically be supported +// This is done instead of just a classname in the FGD so we can control which entities can +// be spawned, and still remain fairly flexible +const char *CBreakable::pSpawnObjects[] = +{ + NULL, // 0 + "item_battery", // 1 + "item_healthkit", // 2 + "weapon_9mmhandgun",// 3 + "ammo_9mmclip", // 4 + "weapon_9mmAR", // 5 + "ammo_9mmAR", // 6 + "ammo_ARgrenades", // 7 + "weapon_shotgun", // 8 + "ammo_buckshot", // 9 + "weapon_crossbow", // 10 + "ammo_crossbow", // 11 + "weapon_357", // 12 + "ammo_357", // 13 + "weapon_rpg", // 14 + "ammo_rpgclip", // 15 + "ammo_gaussclip", // 16 + "weapon_handgrenade",// 17 + "weapon_tripmine", // 18 + "weapon_satchel", // 19 + "weapon_snark", // 20 + "weapon_hornetgun", // 21 +}; + +void CBreakable::KeyValue( KeyValueData* pkvd ) +{ + // UNDONE_WC: explicitly ignoring these fields, but they shouldn't be in the map file! + if (FStrEq(pkvd->szKeyName, "explosion")) + { + if (!stricmp(pkvd->szValue, "directed")) + m_Explosion = expDirected; + else if (!stricmp(pkvd->szValue, "random")) + m_Explosion = expRandom; + else + m_Explosion = expRandom; + + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "material")) + { + int i = atoi( pkvd->szValue); + + // 0:glass, 1:metal, 2:flesh, 3:wood + + if ((i < 0) || (i >= matLastMaterial)) + m_Material = matWood; + else + m_Material = (Materials)i; + + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "deadmodel")) + { + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "shards")) + { +// m_iShards = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "gibmodel") ) + { + m_iszGibModel = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spawnobject") ) + { + int object = atoi( pkvd->szValue ); + if ( object > 0 && object < static_cast(ARRAYSIZE(pSpawnObjects)) ) + m_iszSpawnObject = MAKE_STRING( pSpawnObjects[object] ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "explodemagnitude") ) + { + ExplosionSetMagnitude( atoi( pkvd->szValue ) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "lip") ) + pkvd->fHandled = TRUE; + else + CBaseDelay::KeyValue( pkvd ); +} + + +// +// func_breakable - bmodel that breaks into pieces after taking damage +// +LINK_ENTITY_TO_CLASS( func_breakable, CBreakable ); +TYPEDESCRIPTION CBreakable::m_SaveData[] = +{ + DEFINE_FIELD( CBreakable, m_Material, FIELD_INTEGER ), + DEFINE_FIELD( CBreakable, m_Explosion, FIELD_INTEGER ), + +// Don't need to save/restore these because we precache after restore +// DEFINE_FIELD( CBreakable, m_idShard, FIELD_INTEGER ), + + DEFINE_FIELD( CBreakable, m_angle, FIELD_FLOAT ), + DEFINE_FIELD( CBreakable, m_iszGibModel, FIELD_STRING ), + DEFINE_FIELD( CBreakable, m_iszSpawnObject, FIELD_STRING ), + + // Explosion magnitude is stored in pev->impulse +}; + +IMPLEMENT_SAVERESTORE( CBreakable, CBaseEntity ); + +void CBreakable::Spawn( void ) +{ + Precache( ); + + if ( FBitSet( pev->spawnflags, SF_BREAK_TRIGGER_ONLY ) ) + pev->takedamage = DAMAGE_NO; + else + pev->takedamage = DAMAGE_YES; + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + m_angle = pev->angles.y; + pev->angles.y = 0; + + // HACK: matGlass can receive decals, we need the client to know about this + // so use class to store the material flag + if ( m_Material == matGlass ) + { + pev->playerclass = 1; + } + + SET_MODEL(ENT(pev), STRING(pev->model) );//set size and link into world. + + SetTouch( &CBreakable::BreakTouch ); + if ( FBitSet( pev->spawnflags, SF_BREAK_TRIGGER_ONLY ) ) // Only break on trigger + SetTouch( NULL ); + + // Flag unbreakable glass as "worldbrush" so it will block ALL tracelines + if ( !IsBreakable() && pev->rendermode != kRenderNormal ) + pev->flags |= FL_WORLDBRUSH; +} + + +const char *CBreakable::pSoundsWood[] = +{ + "debris/wood1.wav", + "debris/wood2.wav", + "debris/wood3.wav", +}; + +const char *CBreakable::pSoundsFlesh[] = +{ + "debris/flesh1.wav", + "debris/flesh2.wav", + "debris/flesh3.wav", + "debris/flesh5.wav", + "debris/flesh6.wav", + "debris/flesh7.wav", +}; + +const char *CBreakable::pSoundsMetal[] = +{ + "debris/metal1.wav", + "debris/metal2.wav", + "debris/metal3.wav", +}; + +const char *CBreakable::pSoundsConcrete[] = +{ + "debris/concrete1.wav", + "debris/concrete2.wav", + "debris/concrete3.wav", +}; + + +const char *CBreakable::pSoundsGlass[] = +{ + "debris/glass1.wav", + "debris/glass2.wav", + "debris/glass3.wav", +}; + +const char **CBreakable::MaterialSoundList( Materials precacheMaterial, int &soundCount ) +{ + const char **pSoundList = NULL; + + switch ( precacheMaterial ) + { + case matWood: + pSoundList = pSoundsWood; + soundCount = ARRAYSIZE(pSoundsWood); + break; + case matFlesh: + pSoundList = pSoundsFlesh; + soundCount = ARRAYSIZE(pSoundsFlesh); + break; + case matComputer: + case matUnbreakableGlass: + case matGlass: + pSoundList = pSoundsGlass; + soundCount = ARRAYSIZE(pSoundsGlass); + break; + + case matMetal: + pSoundList = pSoundsMetal; + soundCount = ARRAYSIZE(pSoundsMetal); + break; + + case matCinderBlock: + case matRocks: + pSoundList = pSoundsConcrete; + soundCount = ARRAYSIZE(pSoundsConcrete); + break; + + + case matCeilingTile: + case matNone: + default: + soundCount = 0; + break; + } + + return pSoundList; +} + +void CBreakable::MaterialSoundPrecache( Materials precacheMaterial ) +{ + const char **pSoundList; + int i, soundCount = 0; + + pSoundList = MaterialSoundList( precacheMaterial, soundCount ); + + for ( i = 0; i < soundCount; i++ ) + { + PRECACHE_SOUND( (char *)pSoundList[i] ); + } +} + +void CBreakable::MaterialSoundRandom( edict_t *pEdict, Materials soundMaterial, float volume ) +{ + const char **pSoundList; + int soundCount = 0; + + pSoundList = MaterialSoundList( soundMaterial, soundCount ); + + if ( soundCount ) + EMIT_SOUND( pEdict, CHAN_BODY, pSoundList[ RANDOM_LONG(0,soundCount-1) ], volume, 1.0 ); +} + + +void CBreakable::Precache( void ) +{ + const char *pGibName = NULL; + + switch (m_Material) + { + case matWood: + pGibName = "models/woodgibs.mdl"; + + PRECACHE_SOUND("debris/bustcrate1.wav"); + PRECACHE_SOUND("debris/bustcrate2.wav"); + break; + case matFlesh: + pGibName = "models/fleshgibs.mdl"; + + PRECACHE_SOUND("debris/bustflesh1.wav"); + PRECACHE_SOUND("debris/bustflesh2.wav"); + break; + case matComputer: + PRECACHE_SOUND("buttons/spark5.wav"); + PRECACHE_SOUND("buttons/spark6.wav"); + pGibName = "models/computergibs.mdl"; + + PRECACHE_SOUND("debris/bustmetal1.wav"); + PRECACHE_SOUND("debris/bustmetal2.wav"); + break; + + case matUnbreakableGlass: + case matGlass: + pGibName = "models/glassgibs.mdl"; + + PRECACHE_SOUND("debris/bustglass1.wav"); + PRECACHE_SOUND("debris/bustglass2.wav"); + break; + case matMetal: + pGibName = "models/metalplategibs.mdl"; + + PRECACHE_SOUND("debris/bustmetal1.wav"); + PRECACHE_SOUND("debris/bustmetal2.wav"); + break; + case matCinderBlock: + pGibName = "models/cindergibs.mdl"; + + PRECACHE_SOUND("debris/bustconcrete1.wav"); + PRECACHE_SOUND("debris/bustconcrete2.wav"); + break; + case matRocks: + pGibName = "models/rockgibs.mdl"; + + PRECACHE_SOUND("debris/bustconcrete1.wav"); + PRECACHE_SOUND("debris/bustconcrete2.wav"); + break; + case matCeilingTile: + pGibName = "models/ceilinggibs.mdl"; + + PRECACHE_SOUND ("debris/bustceiling.wav"); + break; + default: + break; + } + MaterialSoundPrecache( m_Material ); + if ( m_iszGibModel ) + pGibName = STRING(m_iszGibModel); + + m_idShard = PRECACHE_MODEL( (char *)pGibName ); + + // Precache the spawn item's data + if ( m_iszSpawnObject ) + UTIL_PrecacheOther( (char *)STRING( m_iszSpawnObject ) ); +} + +// play shard sound when func_breakable takes damage. +// the more damage, the louder the shard sound. + + +void CBreakable::DamageSound( void ) +{ + int pitch; + float fvol; + char *rgpsz[6]; + int i = 0; + int material = m_Material; + +// if (RANDOM_LONG(0,1)) +// return; + + if (RANDOM_LONG(0,2)) + pitch = PITCH_NORM; + else + pitch = 95 + RANDOM_LONG(0,34); + + fvol = RANDOM_FLOAT(0.75, 1.0); + + if (material == matComputer && RANDOM_LONG(0,1)) + material = matMetal; + + switch (material) + { + case matComputer: + case matGlass: + case matUnbreakableGlass: + rgpsz[0] = "debris/glass1.wav"; + rgpsz[1] = "debris/glass2.wav"; + rgpsz[2] = "debris/glass3.wav"; + i = 3; + break; + + case matWood: + rgpsz[0] = "debris/wood1.wav"; + rgpsz[1] = "debris/wood2.wav"; + rgpsz[2] = "debris/wood3.wav"; + i = 3; + break; + + case matMetal: + rgpsz[0] = "debris/metal1.wav"; + rgpsz[1] = "debris/metal3.wav"; + rgpsz[2] = "debris/metal2.wav"; + i = 2; + break; + + case matFlesh: + rgpsz[0] = "debris/flesh1.wav"; + rgpsz[1] = "debris/flesh2.wav"; + rgpsz[2] = "debris/flesh3.wav"; + rgpsz[3] = "debris/flesh5.wav"; + rgpsz[4] = "debris/flesh6.wav"; + rgpsz[5] = "debris/flesh7.wav"; + i = 6; + break; + + case matRocks: + case matCinderBlock: + rgpsz[0] = "debris/concrete1.wav"; + rgpsz[1] = "debris/concrete2.wav"; + rgpsz[2] = "debris/concrete3.wav"; + i = 3; + break; + + case matCeilingTile: + // UNDONE: no ceiling tile shard sound yet + i = 0; + break; + } + + if (i) + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, rgpsz[RANDOM_LONG(0,i-1)], fvol, ATTN_NORM, 0, pitch); +} + +void CBreakable::BreakTouch( CBaseEntity *pOther ) +{ + float flDamage; + entvars_t* pevToucher = pOther->pev; + + // only players can break these right now + if ( !pOther->IsPlayer() || !IsBreakable() ) + { + return; + } + + if ( FBitSet ( pev->spawnflags, SF_BREAK_TOUCH ) ) + {// can be broken when run into + flDamage = pevToucher->velocity.Length() * 0.01; + + if (flDamage >= pev->health) + { + SetTouch( NULL ); + TakeDamage(pevToucher, pevToucher, flDamage, DMG_CRUSH); + + // do a little damage to player if we broke glass or computer + pOther->TakeDamage( pev, pev, flDamage/4, DMG_SLASH ); + } + } + + if ( FBitSet ( pev->spawnflags, SF_BREAK_PRESSURE ) && pevToucher->absmin.z >= pev->maxs.z - 2 ) + {// can be broken when stood upon + + // play creaking sound here. + DamageSound(); + + SetThink ( &CBreakable::Die ); + SetTouch( NULL ); + + if ( m_flDelay == 0 ) + {// !!!BUGBUG - why doesn't zero delay work? + m_flDelay = 0.1; + } + + pev->nextthink = pev->ltime + m_flDelay; + + } + +} + + +// +// Smash the our breakable object +// + +// Break when triggered +void CBreakable::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( IsBreakable() ) + { + pev->angles.y = m_angle; + UTIL_MakeVectors(pev->angles); + g_vecAttackDir = gpGlobals->v_forward; + + Die(); + } +} + + +void CBreakable::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + // random spark if this is a 'computer' object + if (RANDOM_LONG(0,1) ) + { + switch( m_Material ) + { + case matComputer: + { + UTIL_Sparks( ptr->vecEndPos ); + + float flVolume = RANDOM_FLOAT ( 0.7 , 1.0 );//random volume range + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark5.wav", flVolume, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark6.wav", flVolume, ATTN_NORM); break; + } + } + break; + + case matUnbreakableGlass: + UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT(0.5,1.5) ); + break; + + default: + break; + } + } + + CBaseDelay::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + +//========================================================= +// Special takedamage for func_breakable. Allows us to make +// exceptions that are breakable-specific +// bitsDamageType indicates the type of damage sustained ie: DMG_CRUSH +//========================================================= +int CBreakable :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + Vector vecTemp; + + // if Attacker == Inflictor, the attack was a melee or other instant-hit attack. + // (that is, no actual entity projectile was involved in the attack so use the shooter's origin). + if ( pevAttacker == pevInflictor ) + { + vecTemp = pevInflictor->origin - ( pev->absmin + ( pev->size * 0.5 ) ); + + // if a client hit the breakable with a crowbar, and breakable is crowbar-sensitive, break it now. + if ( FBitSet ( pevAttacker->flags, FL_CLIENT ) && + FBitSet ( pev->spawnflags, SF_BREAK_CROWBAR ) && (bitsDamageType & DMG_CLUB)) + flDamage = pev->health; + } + else + // an actual missile was involved. + { + vecTemp = pevInflictor->origin - ( pev->absmin + ( pev->size * 0.5 ) ); + } + + if (!IsBreakable()) + return 0; + + // Breakables take double damage from the crowbar + if ( bitsDamageType & DMG_CLUB ) + flDamage *= 2; + + // Boxes / glass / etc. don't take much poison damage, just the impact of the dart - consider that 10% + if ( bitsDamageType & DMG_POISON ) + flDamage *= 0.1; + +// this global is still used for glass and other non-monster killables, along with decals. + g_vecAttackDir = vecTemp.Normalize(); + +// do the damage + pev->health -= flDamage; + if (pev->health <= 0) + { + Killed( pevAttacker, GIB_NORMAL ); + Die(); + return 0; + } + + // Make a shard noise each time func breakable is hit. + // Don't play shard noise if cbreakable actually died. + + DamageSound(); + + return 1; +} + + +void CBreakable::Die( void ) +{ + Vector vecSpot;// shard origin + Vector vecVelocity;// shard velocity + char cFlag = 0; + int pitch; + float fvol; + + pitch = 95 + RANDOM_LONG(0,29); + + if (pitch > 97 && pitch < 103) + pitch = 100; + + // The more negative pev->health, the louder + // the sound should be. + + fvol = RANDOM_FLOAT(0.85, 1.0) + (abs(static_cast(pev->health)) / 100.0); + + if (fvol > 1.0) + fvol = 1.0; + + + switch (m_Material) + { + case matGlass: + switch ( RANDOM_LONG(0,1) ) + { + 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); + break; + } + cFlag = BREAK_GLASS; + break; + + case matWood: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustcrate1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustcrate2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_WOOD; + break; + + case matComputer: + case matMetal: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustmetal1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustmetal2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_METAL; + break; + + case matFlesh: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustflesh1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustflesh2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_FLESH; + break; + + case matRocks: + case matCinderBlock: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustconcrete1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustconcrete2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_CONCRETE; + break; + + case matCeilingTile: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustceiling.wav", fvol, ATTN_NORM, 0, pitch); + break; + + default: + break; + } + + + if (m_Explosion == expDirected) + vecVelocity = g_vecAttackDir * 200; + else + { + vecVelocity.x = 0; + vecVelocity.y = 0; + vecVelocity.z = 0; + } + + vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z ); + + // size + WRITE_COORD( pev->size.x); + WRITE_COORD( pev->size.y); + WRITE_COORD( pev->size.z); + + // velocity + WRITE_COORD( vecVelocity.x ); + WRITE_COORD( vecVelocity.y ); + WRITE_COORD( vecVelocity.z ); + + // randomization + WRITE_BYTE( 10 ); + + // Model + WRITE_SHORT( m_idShard ); //model id# + + // # of shards + WRITE_BYTE( 0 ); // let client decide + + // duration + WRITE_BYTE( 25 );// 2.5 seconds + + // flags + WRITE_BYTE( cFlag ); + MESSAGE_END(); + + float size = pev->size.x; + if ( size < pev->size.y ) + size = pev->size.y; + if ( size < pev->size.z ) + size = pev->size.z; + + // !!! HACK This should work! + // Build a box above the entity that looks like an 8 pixel high sheet + Vector mins = pev->absmin; + Vector maxs = pev->absmax; + mins.z = pev->absmax.z; + maxs.z += 8; + + // BUGBUG -- can only find 256 entities on a breakable -- should be enough + CBaseEntity *pList[256]; + int count = UTIL_EntitiesInBox( pList, 256, mins, maxs, FL_ONGROUND ); + if ( count ) + { + for ( int i = 0; i < count; i++ ) + { + ClearBits( pList[i]->pev->flags, FL_ONGROUND ); + pList[i]->pev->groundentity = NULL; + } + } + + // Don't fire something that could fire myself + pev->targetname = 0; + + pev->solid = SOLID_NOT; + // Fire targets on break + SUB_UseTargets( NULL, USE_TOGGLE, 0 ); + + SetThink( &CBaseEntity::SUB_Remove ); + pev->nextthink = pev->ltime + 0.1; + if ( m_iszSpawnObject ) + CBaseEntity::Create( (char *)STRING(m_iszSpawnObject), VecBModelOrigin(pev), pev->angles, edict() ); + + + if ( Explodable() ) + { + ExplosionCreate( Center(), pev->angles, edict(), ExplosionMagnitude(), TRUE ); + } +} + + + +BOOL CBreakable :: IsBreakable( void ) +{ + return m_Material != matUnbreakableGlass; +} + + +int CBreakable :: DamageDecal( int bitsDamageType ) +{ + if ( m_Material == matGlass ) + return DECAL_GLASSBREAK1 + RANDOM_LONG(0,2); + + if ( m_Material == matUnbreakableGlass ) + return DECAL_BPROOF1; + + return CBaseEntity::DamageDecal( bitsDamageType ); +} + + +class CPushable : public CBreakable +{ +public: + void Spawn ( void ); + void Precache( void ); + void Touch ( CBaseEntity *pOther ); + void Move( CBaseEntity *pMover, int push ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT StopSound( void ); +// virtual void SetActivator( CBaseEntity *pActivator ) { m_pPusher = pActivator; } + + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_CONTINUOUS_USE; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + inline float MaxSpeed( void ) { return m_maxSpeed; } + + // breakables use an overridden takedamage + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + + static TYPEDESCRIPTION m_SaveData[]; + + static char *m_soundNames[3]; + int m_lastSound; // no need to save/restore, just keeps the same sound from playing twice in a row + float m_maxSpeed; + float m_soundTime; +}; + +TYPEDESCRIPTION CPushable::m_SaveData[] = +{ + DEFINE_FIELD( CPushable, m_maxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CPushable, m_soundTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CPushable, CBreakable ); + +LINK_ENTITY_TO_CLASS( func_pushable, CPushable ); + +char *CPushable :: m_soundNames[3] = { "debris/pushbox1.wav", "debris/pushbox2.wav", "debris/pushbox3.wav" }; + + +void CPushable :: Spawn( void ) +{ + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + CBreakable::Spawn(); + else + Precache( ); + + pev->movetype = MOVETYPE_PUSHSTEP; + pev->solid = SOLID_BBOX; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + if ( pev->friction > 399 ) + pev->friction = 399; + + m_maxSpeed = 400 - pev->friction; + SetBits( pev->flags, FL_FLOAT ); + pev->friction = 0; + + pev->origin.z += 1; // Pick up off of the floor + UTIL_SetOrigin( pev, pev->origin ); + + // Multiply by area of the box's cross-section (assume 1000 units^3 standard volume) + pev->skin = static_cast(( pev->skin * (pev->maxs.x - pev->mins.x) * (pev->maxs.y - pev->mins.y) ) * 0.0005); + m_soundTime = 0; +} + + +void CPushable :: Precache( void ) +{ + for ( int i = 0; i < 3; i++ ) + PRECACHE_SOUND( m_soundNames[i] ); + + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + CBreakable::Precache( ); +} + + +void CPushable :: KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "size") ) + { + int bbox = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + switch( bbox ) + { + case 0: // Point + UTIL_SetSize(pev, Vector(-8, -8, -8), Vector(8, 8, 8)); + break; + + case 2: // Big Hull!?!? !!!BUGBUG Figure out what this hull really is + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN*2, VEC_DUCK_HULL_MAX*2); + break; + + case 3: // Player duck + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + break; + + default: + case 1: // Player + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + break; + } + + } + else if ( FStrEq(pkvd->szKeyName, "buoyancy") ) + { + pev->skin = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBreakable::KeyValue( pkvd ); +} + + +// Pull the func_pushable +void CPushable :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !pActivator || !pActivator->IsPlayer() ) + { + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + this->CBreakable::Use( pActivator, pCaller, useType, value ); + return; + } + + if ( pActivator->pev->velocity != g_vecZero ) + Move( pActivator, 0 ); +} + + +void CPushable :: Touch( CBaseEntity *pOther ) +{ + if ( FClassnameIs( pOther->pev, "worldspawn" ) ) + return; + + Move( pOther, 1 ); +} + + +void CPushable :: Move( CBaseEntity *pOther, int push ) +{ + entvars_t* pevToucher = pOther->pev; + int playerTouch = 0; + + // Is entity standing on this pushable ? + if ( FBitSet(pevToucher->flags,FL_ONGROUND) && pevToucher->groundentity && VARS(pevToucher->groundentity) == pev ) + { + // Only push if floating + if ( pev->waterlevel > 0 ) + pev->velocity.z += pevToucher->velocity.z * 0.1; + + return; + } + + + if ( pOther->IsPlayer() ) + { + if ( push && !(pevToucher->button & (IN_FORWARD|IN_USE)) ) // Don't push unless the player is pushing forward and NOT use (pull) + return; + playerTouch = 1; + } + + float factor; + + if ( playerTouch ) + { + if ( !(pevToucher->flags & FL_ONGROUND) ) // Don't push away from jumping/falling players unless in water + { + if ( pev->waterlevel < 1 ) + return; + else + factor = 0.1; + } + else + factor = 1; + } + else + factor = 0.25; + + pev->velocity.x += pevToucher->velocity.x * factor; + pev->velocity.y += pevToucher->velocity.y * factor; + + float length = sqrt( pev->velocity.x * pev->velocity.x + pev->velocity.y * pev->velocity.y ); + if ( push && (length > MaxSpeed()) ) + { + pev->velocity.x = (pev->velocity.x * MaxSpeed() / length ); + pev->velocity.y = (pev->velocity.y * MaxSpeed() / length ); + } + if ( playerTouch ) + { + pevToucher->velocity.x = pev->velocity.x; + pevToucher->velocity.y = pev->velocity.y; + if ( (gpGlobals->time - m_soundTime) > 0.7 ) + { + m_soundTime = gpGlobals->time; + if ( length > 0 && FBitSet(pev->flags,FL_ONGROUND) ) + { + m_lastSound = RANDOM_LONG(0,2); + EMIT_SOUND(ENT(pev), CHAN_WEAPON, m_soundNames[m_lastSound], 0.5, ATTN_NORM); + // SetThink( StopSound ); + // pev->nextthink = pev->ltime + 0.1; + } + else + STOP_SOUND( ENT(pev), CHAN_WEAPON, m_soundNames[m_lastSound] ); + } + } +} + +#if 0 +void CPushable::StopSound( void ) +{ + Vector dist = pev->oldorigin - pev->origin; + if ( dist.Length() <= 0 ) + STOP_SOUND( ENT(pev), CHAN_WEAPON, m_soundNames[m_lastSound] ); +} +#endif + +int CPushable::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + return CBreakable::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); + + return 1; +} + diff --git a/hlsdk/dlls/func_break.h b/hlsdk/dlls/func_break.h new file mode 100644 index 00000000..9bb281d6 --- /dev/null +++ b/hlsdk/dlls/func_break.h @@ -0,0 +1,74 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef FUNC_BREAK_H +#define FUNC_BREAK_H + +typedef enum { expRandom, expDirected} Explosions; +typedef enum { matGlass = 0, matWood, matMetal, matFlesh, matCinderBlock, matCeilingTile, matComputer, matUnbreakableGlass, matRocks, matNone, matLastMaterial } Materials; + +#define NUM_SHARDS 6 // this many shards spawned when breakable objects break; + +class CBreakable : public CBaseDelay +{ +public: + // basic functions + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData* pkvd); + void EXPORT BreakTouch( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void DamageSound( void ); + + // 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 ); + + BOOL IsBreakable( void ); + BOOL SparkWhenHit( void ); + + int DamageDecal( int bitsDamageType ); + + void EXPORT Die( void ); + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + inline BOOL Explodable( void ) { return ExplosionMagnitude() > 0; } + inline int ExplosionMagnitude( void ) { return pev->impulse; } + inline void ExplosionSetMagnitude( int magnitude ) { pev->impulse = magnitude; } + + static void MaterialSoundPrecache( Materials precacheMaterial ); + static void MaterialSoundRandom( edict_t *pEdict, Materials soundMaterial, float volume ); + static const char **MaterialSoundList( Materials precacheMaterial, int &soundCount ); + + static const char *pSoundsWood[]; + static const char *pSoundsFlesh[]; + static const char *pSoundsGlass[]; + static const char *pSoundsMetal[]; + static const char *pSoundsConcrete[]; + static const char *pSpawnObjects[]; + + static TYPEDESCRIPTION m_SaveData[]; + + Materials m_Material; + Explosions m_Explosion; + int m_idShard; + float m_angle; + int m_iszGibModel; + int m_iszSpawnObject; +}; + +#endif // FUNC_BREAK_H diff --git a/hlsdk/dlls/func_tank.cpp b/hlsdk/dlls/func_tank.cpp new file mode 100644 index 00000000..c5472dd4 --- /dev/null +++ b/hlsdk/dlls/func_tank.cpp @@ -0,0 +1,1039 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "effects.h" +#include "weapons.h" +#include "explode.h" + +#include "player.h" + + +#define SF_TANK_ACTIVE 0x0001 +#define SF_TANK_PLAYER 0x0002 +#define SF_TANK_HUMANS 0x0004 +#define SF_TANK_ALIENS 0x0008 +#define SF_TANK_LINEOFSIGHT 0x0010 +#define SF_TANK_CANCONTROL 0x0020 +#define SF_TANK_SOUNDON 0x8000 + +enum TANKBULLET +{ + TANK_BULLET_NONE = 0, + TANK_BULLET_9MM = 1, + TANK_BULLET_MP5 = 2, + TANK_BULLET_12MM = 3, +}; + +// Custom damage +// env_laser (duration is 0.5 rate of fire) +// rockets +// explosion? + +class CFuncTank : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Think( void ); + void TrackTarget( void ); + + virtual void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); + virtual Vector UpdateTargetPosition( CBaseEntity *pTarget ) + { + return pTarget->BodyTarget( pev->origin ); + } + + void StartRotSound( void ); + void StopRotSound( void ); + + // Bmodels don't go across transitions + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + inline BOOL IsActive( void ) { return (pev->spawnflags & SF_TANK_ACTIVE)?TRUE:FALSE; } + inline void TankActivate( void ) { pev->spawnflags |= SF_TANK_ACTIVE; pev->nextthink = pev->ltime + 0.1; m_fireLast = 0; } + inline void TankDeactivate( void ) { pev->spawnflags &= ~SF_TANK_ACTIVE; m_fireLast = 0; StopRotSound(); } + inline BOOL CanFire( void ) { return (gpGlobals->time - m_lastSightTime) < m_persist; } + BOOL InRange( float range ); + + // Acquire a target. pPlayer is a player in the PVS + edict_t *FindTarget( edict_t *pPlayer ); + + void TankTrace( const Vector &vecStart, const Vector &vecForward, const Vector &vecSpread, TraceResult &tr ); + + Vector BarrelPosition( void ) + { + Vector forward, right, up; + UTIL_MakeVectorsPrivate( pev->angles, forward, right, up ); + return pev->origin + (forward * m_barrelPos.x) + (right * m_barrelPos.y) + (up * m_barrelPos.z); + } + + void AdjustAnglesForBarrel( Vector &angles, float distance ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + BOOL OnControls( entvars_t *pevTest ); + BOOL StartControl( CBasePlayer* pController ); + void StopControl( void ); + void ControllerPostFrame( void ); + + +protected: + CBasePlayer* m_pController; + float m_flNextAttack; + Vector m_vecControllerUsePos; + + float m_yawCenter; // "Center" yaw + float m_yawRate; // Max turn rate to track targets + float m_yawRange; // Range of turning motion (one-sided: 30 is +/- 30 degress from center) + // Zero is full rotation + float m_yawTolerance; // Tolerance angle + + float m_pitchCenter; // "Center" pitch + float m_pitchRate; // Max turn rate on pitch + float m_pitchRange; // Range of pitch motion as above + float m_pitchTolerance; // Tolerance angle + + float m_fireLast; // Last time I fired + float m_fireRate; // How many rounds/second + float m_lastSightTime;// Last time I saw target + float m_persist; // Persistence of firing (how long do I shoot when I can't see) + float m_minRange; // Minimum range to aim/track + float m_maxRange; // Max range to aim/track + + Vector m_barrelPos; // Length of the freakin barrel + float m_spriteScale; // Scale of any sprites we shoot + int m_iszSpriteSmoke; + int m_iszSpriteFlash; + TANKBULLET m_bulletType; // Bullet type + int m_iBulletDamage; // 0 means use Bullet type's default damage + + Vector m_sightOrigin; // Last sight of target + int m_spread; // firing spread + int m_iszMaster; // Master entity (game_team_master or multisource) +}; + + +TYPEDESCRIPTION CFuncTank::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTank, m_yawCenter, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_yawRate, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_yawRange, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_yawTolerance, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_pitchCenter, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_pitchRate, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_pitchRange, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_pitchTolerance, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_fireLast, FIELD_TIME ), + DEFINE_FIELD( CFuncTank, m_fireRate, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_lastSightTime, FIELD_TIME ), + DEFINE_FIELD( CFuncTank, m_persist, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_minRange, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_maxRange, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_barrelPos, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTank, m_spriteScale, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_iszSpriteSmoke, FIELD_STRING ), + DEFINE_FIELD( CFuncTank, m_iszSpriteFlash, FIELD_STRING ), + DEFINE_FIELD( CFuncTank, m_bulletType, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTank, m_sightOrigin, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTank, m_spread, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTank, m_pController, FIELD_CLASSPTR ), + DEFINE_FIELD( CFuncTank, m_vecControllerUsePos, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTank, m_flNextAttack, FIELD_TIME ), + DEFINE_FIELD( CFuncTank, m_iBulletDamage, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTank, m_iszMaster, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTank, CBaseEntity ); + +static Vector gTankSpread[] = +{ + Vector( 0, 0, 0 ), // perfect + Vector( 0.025, 0.025, 0.025 ), // small cone + Vector( 0.05, 0.05, 0.05 ), // medium cone + Vector( 0.1, 0.1, 0.1 ), // large cone + Vector( 0.25, 0.25, 0.25 ), // extra-large cone +}; +#define MAX_FIRING_SPREADS ARRAYSIZE(gTankSpread) + + +void CFuncTank :: Spawn( void ) +{ + Precache(); + + pev->movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + pev->solid = SOLID_BSP; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + m_yawCenter = pev->angles.y; + m_pitchCenter = pev->angles.x; + + if ( IsActive() ) + pev->nextthink = pev->ltime + 1.0; + + m_sightOrigin = BarrelPosition(); // Point at the end of the barrel + + if ( m_fireRate <= 0 ) + m_fireRate = 1; + if ( m_spread > static_cast(MAX_FIRING_SPREADS) ) + m_spread = 0; + + pev->oldorigin = pev->origin; +} + + +void CFuncTank :: Precache( void ) +{ + if ( m_iszSpriteSmoke ) + PRECACHE_MODEL( (char *)STRING(m_iszSpriteSmoke) ); + if ( m_iszSpriteFlash ) + PRECACHE_MODEL( (char *)STRING(m_iszSpriteFlash) ); + + if ( pev->noise ) + PRECACHE_SOUND( (char *)STRING(pev->noise) ); +} + + +void CFuncTank :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "yawrate")) + { + m_yawRate = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "yawrange")) + { + m_yawRange = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "yawtolerance")) + { + m_yawTolerance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitchrange")) + { + m_pitchRange = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitchrate")) + { + m_pitchRate = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitchtolerance")) + { + m_pitchTolerance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "firerate")) + { + m_fireRate = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "barrel")) + { + m_barrelPos.x = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "barrely")) + { + m_barrelPos.y = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "barrelz")) + { + m_barrelPos.z = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spritescale")) + { + m_spriteScale = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spritesmoke")) + { + m_iszSpriteSmoke = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spriteflash")) + { + m_iszSpriteFlash = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "rotatesound")) + { + pev->noise = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "persistence")) + { + m_persist = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "bullet")) + { + m_bulletType = (TANKBULLET)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "bullet_damage" )) + { + m_iBulletDamage = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "firespread")) + { + m_spread = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "minRange")) + { + m_minRange = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "maxRange")) + { + m_maxRange = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "master")) + { + m_iszMaster = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +////////////// START NEW STUFF ////////////// + +//================================================================================== +// TANK CONTROLLING +BOOL CFuncTank :: OnControls( entvars_t *pevTest ) +{ + if ( !(pev->spawnflags & SF_TANK_CANCONTROL) ) + return FALSE; + + Vector offset = pevTest->origin - pev->origin; + + if ( (m_vecControllerUsePos - pevTest->origin).Length() < 30 ) + return TRUE; + + return FALSE; +} + +BOOL CFuncTank :: StartControl( CBasePlayer *pController ) +{ + if ( m_pController != NULL ) + return FALSE; + + // Team only or disabled? + if ( m_iszMaster ) + { + if ( !UTIL_IsMasterTriggered( m_iszMaster, pController ) ) + return FALSE; + } + + ALERT( at_console, "using TANK!\n"); + + m_pController = pController; + if ( m_pController->m_pActiveItem ) + { + m_pController->m_pActiveItem->Holster(); + m_pController->pev->weaponmodel = 0; + m_pController->pev->viewmodel = 0; + + } + + m_pController->m_iHideHUD |= HIDEHUD_WEAPONS; + m_vecControllerUsePos = m_pController->pev->origin; + + pev->nextthink = pev->ltime + 0.1; + + return TRUE; +} + +void CFuncTank :: StopControl() +{ + // TODO: bring back the controllers current weapon + if ( !m_pController ) + return; + + if ( m_pController->m_pActiveItem ) + m_pController->m_pActiveItem->Deploy(); + + ALERT( at_console, "stopped using TANK\n"); + + m_pController->m_iHideHUD &= ~HIDEHUD_WEAPONS; + + pev->nextthink = 0; + m_pController = NULL; + + if ( IsActive() ) + pev->nextthink = pev->ltime + 1.0; +} + +// Called each frame by the player's ItemPostFrame +void CFuncTank :: ControllerPostFrame( void ) +{ + ASSERT(m_pController != NULL); + + if ( gpGlobals->time < m_flNextAttack ) + return; + + if ( m_pController->pev->button & IN_ATTACK ) + { + Vector vecForward; + UTIL_MakeVectorsPrivate( pev->angles, vecForward, NULL, NULL ); + + m_fireLast = gpGlobals->time - (1/m_fireRate) - 0.01; // to make sure the gun doesn't fire too many bullets + + Fire( BarrelPosition(), vecForward, m_pController->pev ); + + // HACKHACK -- make some noise (that the AI can hear) + if ( m_pController && m_pController->IsPlayer() ) + ((CBasePlayer *)m_pController)->m_iWeaponVolume = LOUD_GUN_VOLUME; + + m_flNextAttack = gpGlobals->time + (1/m_fireRate); + } +} +////////////// END NEW STUFF ////////////// + + +void CFuncTank :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->spawnflags & SF_TANK_CANCONTROL ) + { // player controlled turret + + if ( pActivator->Classify() != CLASS_PLAYER ) + return; + + if ( value == 2 && useType == USE_SET ) + { + ControllerPostFrame(); + } + else if ( !m_pController && useType != USE_OFF ) + { + ((CBasePlayer*)pActivator)->m_pTank = this; + StartControl( (CBasePlayer*)pActivator ); + } + else + { + StopControl(); + } + } + else + { + if ( !ShouldToggle( useType, IsActive() ) ) + return; + + if ( IsActive() ) + TankDeactivate(); + else + TankActivate(); + } +} + + +edict_t *CFuncTank :: FindTarget( edict_t *pPlayer ) +{ + return pPlayer; +} + + + +BOOL CFuncTank :: InRange( float range ) +{ + if ( range < m_minRange ) + return FALSE; + if ( m_maxRange > 0 && range > m_maxRange ) + return FALSE; + + return TRUE; +} + + +void CFuncTank :: Think( void ) +{ + pev->avelocity = g_vecZero; + TrackTarget(); + + if ( fabs(pev->avelocity.x) > 1 || fabs(pev->avelocity.y) > 1 ) + StartRotSound(); + else + StopRotSound(); +} + +void CFuncTank::TrackTarget( void ) +{ + TraceResult tr; + edict_t *pPlayer = FIND_CLIENT_IN_PVS( edict() ); + BOOL updateTime = FALSE, lineOfSight; + Vector angles, direction, targetPosition, barrelEnd; + edict_t *pTarget = NULL; + + // Get a position to aim for + if (m_pController) + { + // Tanks attempt to mirror the player's angles + angles = m_pController->pev->v_angle; + angles[0] = 0 - angles[0]; + pev->nextthink = pev->ltime + 0.05; + } + else + { + if ( IsActive() ) + pev->nextthink = pev->ltime + 0.1; + else + return; + + if ( FNullEnt( pPlayer ) ) + { + if ( IsActive() ) + pev->nextthink = pev->ltime + 2; // Wait 2 secs + return; + } + pTarget = FindTarget( pPlayer ); + if ( !pTarget ) + return; + + // Calculate angle needed to aim at target + barrelEnd = BarrelPosition(); + targetPosition = pTarget->v.origin + pTarget->v.view_ofs; + float range = (targetPosition - barrelEnd).Length(); + + if ( !InRange( range ) ) + return; + + UTIL_TraceLine( barrelEnd, targetPosition, dont_ignore_monsters, edict(), &tr ); + + lineOfSight = FALSE; + // No line of sight, don't track + if ( tr.flFraction == 1.0 || tr.pHit == pTarget ) + { + lineOfSight = TRUE; + + CBaseEntity *pInstance = CBaseEntity::Instance(pTarget); + if ( InRange( range ) && pInstance && pInstance->IsAlive() ) + { + updateTime = TRUE; + m_sightOrigin = UpdateTargetPosition( pInstance ); + } + } + + // Track sight origin + +// !!! I'm not sure what i changed + direction = m_sightOrigin - pev->origin; +// direction = m_sightOrigin - barrelEnd; + angles = UTIL_VecToAngles( direction ); + + // Calculate the additional rotation to point the end of the barrel at the target (not the gun's center) + AdjustAnglesForBarrel( angles, direction.Length() ); + } + + angles.x = -angles.x; + + // Force the angles to be relative to the center position + angles.y = m_yawCenter + UTIL_AngleDistance( angles.y, m_yawCenter ); + angles.x = m_pitchCenter + UTIL_AngleDistance( angles.x, m_pitchCenter ); + + // Limit against range in y + if ( angles.y > m_yawCenter + m_yawRange ) + { + angles.y = m_yawCenter + m_yawRange; + updateTime = FALSE; // Don't update if you saw the player, but out of range + } + else if ( angles.y < (m_yawCenter - m_yawRange) ) + { + angles.y = (m_yawCenter - m_yawRange); + updateTime = FALSE; // Don't update if you saw the player, but out of range + } + + if ( updateTime ) + m_lastSightTime = gpGlobals->time; + + // Move toward target at rate or less + float distY = UTIL_AngleDistance( angles.y, pev->angles.y ); + pev->avelocity.y = distY * 10; + if ( pev->avelocity.y > m_yawRate ) + pev->avelocity.y = m_yawRate; + else if ( pev->avelocity.y < -m_yawRate ) + pev->avelocity.y = -m_yawRate; + + // Limit against range in x + if ( angles.x > m_pitchCenter + m_pitchRange ) + angles.x = m_pitchCenter + m_pitchRange; + else if ( angles.x < m_pitchCenter - m_pitchRange ) + angles.x = m_pitchCenter - m_pitchRange; + + // Move toward target at rate or less + float distX = UTIL_AngleDistance( angles.x, pev->angles.x ); + pev->avelocity.x = distX * 10; + + if ( pev->avelocity.x > m_pitchRate ) + pev->avelocity.x = m_pitchRate; + else if ( pev->avelocity.x < -m_pitchRate ) + pev->avelocity.x = -m_pitchRate; + + if ( m_pController ) + return; + + if ( CanFire() && ( (fabs(distX) < m_pitchTolerance && fabs(distY) < m_yawTolerance) || (pev->spawnflags & SF_TANK_LINEOFSIGHT) ) ) + { + BOOL fire = FALSE; + Vector forward; + UTIL_MakeVectorsPrivate( pev->angles, forward, NULL, NULL ); + + if ( pev->spawnflags & SF_TANK_LINEOFSIGHT ) + { + float length = direction.Length(); + UTIL_TraceLine( barrelEnd, barrelEnd + forward * length, dont_ignore_monsters, edict(), &tr ); + if ( tr.pHit == pTarget ) + fire = TRUE; + } + else + fire = TRUE; + + if ( fire ) + { + Fire( BarrelPosition(), forward, pev ); + } + else + m_fireLast = 0; + } + else + m_fireLast = 0; +} + + +// If barrel is offset, add in additional rotation +void CFuncTank::AdjustAnglesForBarrel( Vector &angles, float distance ) +{ + float r2, d2; + + + if ( m_barrelPos.y != 0 || m_barrelPos.z != 0 ) + { + distance -= m_barrelPos.z; + d2 = distance * distance; + if ( m_barrelPos.y ) + { + r2 = m_barrelPos.y * m_barrelPos.y; + angles.y += (180.0 / M_PI) * atan2( m_barrelPos.y, sqrt( d2 - r2 ) ); + } + if ( m_barrelPos.z ) + { + r2 = m_barrelPos.z * m_barrelPos.z; + angles.x += (180.0 / M_PI) * atan2( -m_barrelPos.z, sqrt( d2 - r2 ) ); + } + } +} + + +// Fire targets and spawn sprites +void CFuncTank::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ + if ( m_fireLast != 0 ) + { + if ( m_iszSpriteSmoke ) + { + CSprite *pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteSmoke), barrelEnd, TRUE ); + pSprite->AnimateAndDie( RANDOM_FLOAT( 15.0, 20.0 ) ); + pSprite->SetTransparency( kRenderTransAlpha, static_cast(pev->rendercolor.x), static_cast(pev->rendercolor.y), static_cast(pev->rendercolor.z), 255, kRenderFxNone ); + pSprite->pev->velocity.z = RANDOM_FLOAT(40, 80); + pSprite->SetScale( m_spriteScale ); + } + if ( m_iszSpriteFlash ) + { + CSprite *pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteFlash), barrelEnd, TRUE ); + pSprite->AnimateAndDie( 60 ); + pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); + pSprite->SetScale( m_spriteScale ); + + // Hack Hack, make it stick around for at least 100 ms. + pSprite->pev->nextthink += 0.1; + } + SUB_UseTargets( this, USE_TOGGLE, 0 ); + } + m_fireLast = gpGlobals->time; +} + + +void CFuncTank::TankTrace( const Vector &vecStart, const Vector &vecForward, const Vector &vecSpread, TraceResult &tr ) +{ + // get circular gaussian spread + float x, y, z; + 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); + Vector vecDir = vecForward + + x * vecSpread.x * gpGlobals->v_right + + y * vecSpread.y * gpGlobals->v_up; + Vector vecEnd; + + vecEnd = vecStart + vecDir * 4096; + UTIL_TraceLine( vecStart, vecEnd, dont_ignore_monsters, edict(), &tr ); +} + + +void CFuncTank::StartRotSound( void ) +{ + if ( !pev->noise || (pev->spawnflags & SF_TANK_SOUNDON) ) + return; + pev->spawnflags |= SF_TANK_SOUNDON; + EMIT_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noise), 0.85, ATTN_NORM); +} + + +void CFuncTank::StopRotSound( void ) +{ + if ( pev->spawnflags & SF_TANK_SOUNDON ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noise) ); + pev->spawnflags &= ~SF_TANK_SOUNDON; +} + +class CFuncTankGun : public CFuncTank +{ +public: + void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); +}; +LINK_ENTITY_TO_CLASS( func_tank, CFuncTankGun ); + +void CFuncTankGun::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ + int i; + + if ( m_fireLast != 0 ) + { + // FireBullets needs gpGlobals->v_up, etc. + UTIL_MakeAimVectors(pev->angles); + + int bulletCount = static_cast((gpGlobals->time - m_fireLast) * m_fireRate); + if ( bulletCount > 0 ) + { + for ( i = 0; i < bulletCount; i++ ) + { + switch( m_bulletType ) + { + case TANK_BULLET_9MM: + FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], 4096, BULLET_MONSTER_9MM, 1, m_iBulletDamage, pevAttacker ); + break; + + case TANK_BULLET_MP5: + FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], 4096, BULLET_MONSTER_MP5, 1, m_iBulletDamage, pevAttacker ); + break; + + case TANK_BULLET_12MM: + FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], 4096, BULLET_MONSTER_12MM, 1, m_iBulletDamage, pevAttacker ); + break; + + default: + case TANK_BULLET_NONE: + break; + } + } + CFuncTank::Fire( barrelEnd, forward, pevAttacker ); + } + } + else + CFuncTank::Fire( barrelEnd, forward, pevAttacker ); +} + + + +class CFuncTankLaser : public CFuncTank +{ +public: + void Activate( void ); + void KeyValue( KeyValueData *pkvd ); + void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); + void Think( void ); + CLaser *GetLaser( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + CLaser *m_pLaser; + float m_laserTime; +}; +LINK_ENTITY_TO_CLASS( func_tanklaser, CFuncTankLaser ); + +TYPEDESCRIPTION CFuncTankLaser::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTankLaser, m_pLaser, FIELD_CLASSPTR ), + DEFINE_FIELD( CFuncTankLaser, m_laserTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTankLaser, CFuncTank ); + +void CFuncTankLaser::Activate( void ) +{ + if ( !GetLaser() ) + { + UTIL_Remove(this); + ALERT( at_error, "Laser tank with no env_laser!\n" ); + } + else + { + m_pLaser->TurnOff(); + } +} + + +void CFuncTankLaser::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "laserentity")) + { + pev->message = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CFuncTank::KeyValue( pkvd ); +} + + +CLaser *CFuncTankLaser::GetLaser( void ) +{ + if ( m_pLaser ) + return m_pLaser; + + edict_t *pentLaser; + + pentLaser = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->message) ); + while ( !FNullEnt( pentLaser ) ) + { + // Found the landmark + if ( FClassnameIs( pentLaser, "env_laser" ) ) + { + m_pLaser = (CLaser *)CBaseEntity::Instance(pentLaser); + break; + } + else + pentLaser = FIND_ENTITY_BY_TARGETNAME( pentLaser, STRING(pev->message) ); + } + + return m_pLaser; +} + + +void CFuncTankLaser::Think( void ) +{ + if ( m_pLaser && (gpGlobals->time > m_laserTime) ) + m_pLaser->TurnOff(); + + CFuncTank::Think(); +} + + +void CFuncTankLaser::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ + int i; + TraceResult tr; + + if ( m_fireLast != 0 && GetLaser() ) + { + // TankTrace needs gpGlobals->v_up, etc. + UTIL_MakeAimVectors(pev->angles); + + int bulletCount = static_cast((gpGlobals->time - m_fireLast) * m_fireRate); + if ( bulletCount ) + { + for ( i = 0; i < bulletCount; i++ ) + { + m_pLaser->pev->origin = barrelEnd; + TankTrace( barrelEnd, forward, gTankSpread[m_spread], tr ); + + m_laserTime = gpGlobals->time; + m_pLaser->TurnOn(); + m_pLaser->pev->dmgtime = gpGlobals->time - 1.0; + m_pLaser->FireAtPoint( tr ); + m_pLaser->pev->nextthink = 0; + } + CFuncTank::Fire( barrelEnd, forward, pev ); + } + } + else + { + CFuncTank::Fire( barrelEnd, forward, pev ); + } +} + +class CFuncTankRocket : public CFuncTank +{ +public: + void Precache( void ); + void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); +}; +LINK_ENTITY_TO_CLASS( func_tankrocket, CFuncTankRocket ); + +void CFuncTankRocket::Precache( void ) +{ + UTIL_PrecacheOther( "rpg_rocket" ); + CFuncTank::Precache(); +} + + + +void CFuncTankRocket::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ + int i; + + if ( m_fireLast != 0 ) + { + int bulletCount = static_cast((gpGlobals->time - m_fireLast) * m_fireRate); + if ( bulletCount > 0 ) + { + for ( i = 0; i < bulletCount; i++ ) + { + CBaseEntity::Create( "rpg_rocket", barrelEnd, pev->angles, edict() ); + } + CFuncTank::Fire( barrelEnd, forward, pev ); + } + } + else + CFuncTank::Fire( barrelEnd, forward, pev ); +} + + +class CFuncTankMortar : public CFuncTank +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); +}; +LINK_ENTITY_TO_CLASS( func_tankmortar, CFuncTankMortar ); + + +void CFuncTankMortar::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "iMagnitude")) + { + pev->impulse = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CFuncTank::KeyValue( pkvd ); +} + + +void CFuncTankMortar::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ + if ( m_fireLast != 0 ) + { + int bulletCount = static_cast((gpGlobals->time - m_fireLast) * m_fireRate); + // Only create 1 explosion + if ( bulletCount > 0 ) + { + TraceResult tr; + + // TankTrace needs gpGlobals->v_up, etc. + UTIL_MakeAimVectors(pev->angles); + + TankTrace( barrelEnd, forward, gTankSpread[m_spread], tr ); + + ExplosionCreate( tr.vecEndPos, pev->angles, edict(), pev->impulse, TRUE ); + + CFuncTank::Fire( barrelEnd, forward, pev ); + } + } + else + CFuncTank::Fire( barrelEnd, forward, pev ); +} + + + +//============================================================================ +// FUNC TANK CONTROLS +//============================================================================ +class CFuncTankControls : public CBaseEntity +{ +public: + virtual int ObjectCaps( void ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Think( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + CFuncTank *m_pTank; +}; +LINK_ENTITY_TO_CLASS( func_tankcontrols, CFuncTankControls ); + +TYPEDESCRIPTION CFuncTankControls::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTankControls, m_pTank, FIELD_CLASSPTR ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTankControls, CBaseEntity ); + +int CFuncTankControls :: ObjectCaps( void ) +{ + return (CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_IMPULSE_USE; +} + + +void CFuncTankControls :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ // pass the Use command onto the controls + if ( m_pTank ) + m_pTank->Use( pActivator, pCaller, useType, value ); + + ASSERT( m_pTank != NULL ); // if this fails, most likely means save/restore hasn't worked properly +} + + +void CFuncTankControls :: Think( void ) +{ + edict_t *pTarget = NULL; + + do + { + pTarget = FIND_ENTITY_BY_TARGETNAME( pTarget, STRING(pev->target) ); + } while ( !FNullEnt(pTarget) && strncmp( STRING(pTarget->v.classname), "func_tank", 9 ) ); + + if ( FNullEnt( pTarget ) ) + { + ALERT( at_console, "No tank %s\n", STRING(pev->target) ); + return; + } + + m_pTank = (CFuncTank*)Instance(pTarget); +} + +void CFuncTankControls::Spawn( void ) +{ + pev->solid = SOLID_TRIGGER; + pev->movetype = MOVETYPE_NONE; + pev->effects |= EF_NODRAW; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( pev, pev->origin ); + + pev->nextthink = gpGlobals->time + 0.3; // After all the func_tank's have spawned + + CBaseEntity::Spawn(); +} diff --git a/hlsdk/dlls/game.cpp b/hlsdk/dlls/game.cpp new file mode 100644 index 00000000..1017da1b --- /dev/null +++ b/hlsdk/dlls/game.cpp @@ -0,0 +1,887 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "eiface.h" +#include "util.h" +#include "game.h" + +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 }; // " " + +// multiplayer server rules +cvar_t teamplay = {"mp_teamplay","0", FCVAR_SERVER }; +cvar_t fraglimit = {"mp_fraglimit","0", 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 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 + +// Register your console variables here +// This gets called one time when the game is initialied +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 (&fraglimit); + 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 (&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/hlsdk/dlls/game.h b/hlsdk/dlls/game.h new file mode 100644 index 00000000..58e7e759 --- /dev/null +++ b/hlsdk/dlls/game.h @@ -0,0 +1,45 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef GAME_H +#define GAME_H + +extern void GameDLLInit( void ); + + +extern cvar_t displaysoundlist; + +// multiplayer server rules +extern cvar_t teamplay; +extern cvar_t fraglimit; +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; + +// 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/hlsdk/dlls/gamerules.cpp b/hlsdk/dlls/gamerules.cpp new file mode 100644 index 00000000..829cae0f --- /dev/null +++ b/hlsdk/dlls/gamerules.cpp @@ -0,0 +1,347 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// 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 "skill.h" +#include "game.h" + +extern edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ); + +DLL_GLOBAL CGameRules* g_pGameRules = NULL; +extern DLL_GLOBAL BOOL g_fGameOver; +extern int gmsgDeathMsg; // client dll messages +extern int gmsgMOTD; + +int g_teamplay = 0; + +//========================================================= +//========================================================= +BOOL CGameRules::CanHaveAmmo( CBasePlayer *pPlayer, const char *pszAmmoName, int iMaxCarry ) +{ + int iAmmoIndex; + + if ( pszAmmoName ) + { + iAmmoIndex = pPlayer->GetAmmoIndex( pszAmmoName ); + + if ( iAmmoIndex > -1 ) + { + if ( pPlayer->AmmoInventory( iAmmoIndex ) < iMaxCarry ) + { + // player has room for more of this type of ammo + return TRUE; + } + } + } + + return FALSE; +} + +//========================================================= +//========================================================= +edict_t *CGameRules :: GetPlayerSpawnSpot( CBasePlayer *pPlayer ) +{ + edict_t *pentSpawnSpot = EntSelectSpawnPoint( pPlayer ); + + pPlayer->pev->origin = VARS(pentSpawnSpot)->origin + Vector(0,0,1); + pPlayer->pev->v_angle = g_vecZero; + pPlayer->pev->velocity = g_vecZero; + pPlayer->pev->angles = VARS(pentSpawnSpot)->angles; + pPlayer->pev->punchangle = g_vecZero; + pPlayer->pev->fixangle = TRUE; + + return pentSpawnSpot; +} + +//========================================================= +//========================================================= +BOOL CGameRules::CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ + // only living players can have items + if ( pPlayer->pev->deadflag != DEAD_NO ) + return FALSE; + + if ( pWeapon->pszAmmo1() ) + { + if ( !CanHaveAmmo( pPlayer, pWeapon->pszAmmo1(), pWeapon->iMaxAmmo1() ) ) + { + // we can't carry anymore ammo for this gun. We can only + // have the gun if we aren't already carrying one of this type + if ( pPlayer->HasPlayerItem( pWeapon ) ) + { + return FALSE; + } + } + } + else + { + // weapon doesn't use ammo, don't take another if you already have it. + if ( pPlayer->HasPlayerItem( pWeapon ) ) + { + return FALSE; + } + } + + // note: will fall through to here if GetItemInfo doesn't fill the struct! + return TRUE; +} + +//========================================================= +// load the SkillData struct with the proper values based on the skill level. +//========================================================= +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" ); +} + +//========================================================= +// instantiate the proper game rules object +//========================================================= + +CGameRules *InstallGameRules( void ) +{ + SERVER_COMMAND( "exec game.cfg\n" ); + SERVER_EXECUTE( ); + + if ( !gpGlobals->deathmatch ) + { + // generic half-life + g_teamplay = 0; + return new CHalfLifeRules; + } + else + { + if ( teamplay.value > 0 ) + { + // 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; + return new CHalfLifeMultiplay; + } + } +} + + + diff --git a/hlsdk/dlls/gamerules.h b/hlsdk/dlls/gamerules.h new file mode 100644 index 00000000..76b35155 --- /dev/null +++ b/hlsdk/dlls/gamerules.h @@ -0,0 +1,360 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// GameRules +//========================================================= + +//#include "weapons.h" +//#include "items.h" +class CBasePlayerItem; +class CBasePlayer; +class CItem; +class CBasePlayerAmmo; + +// weapon respawning return codes +enum +{ + GR_NONE = 0, + + GR_WEAPON_RESPAWN_YES, + GR_WEAPON_RESPAWN_NO, + + GR_AMMO_RESPAWN_YES, + GR_AMMO_RESPAWN_NO, + + GR_ITEM_RESPAWN_YES, + GR_ITEM_RESPAWN_NO, + + GR_PLR_DROP_GUN_ALL, + GR_PLR_DROP_GUN_ACTIVE, + GR_PLR_DROP_GUN_NO, + + GR_PLR_DROP_AMMO_ALL, + GR_PLR_DROP_AMMO_ACTIVE, + GR_PLR_DROP_AMMO_NO, +}; + +// Player relationship return codes +enum +{ + GR_NOTTEAMMATE = 0, + GR_TEAMMATE, + GR_ENEMY, + GR_ALLY, + GR_NEUTRAL, +}; + +class CGameRules +{ +public: + virtual void RefreshSkillData( void );// fill skill data struct with proper values + virtual void Think( void ) = 0;// GR_Think - runs every server frame, should handle any timer tasks, periodic events, etc. + virtual BOOL IsAllowedToSpawn( CBaseEntity *pEntity ) = 0; // Can this item spawn (eg monsters don't spawn in deathmatch). + + virtual BOOL FAllowFlashlight( void ) = 0;// Are players allowed to switch on their flashlight? + virtual BOOL FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) = 0;// should the player switch to this weapon? + virtual BOOL GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) = 0;// I can't use this weapon anymore, get me the next best one. + +// Functions to verify the single/multiplayer status of a game + virtual BOOL IsMultiplayer( void ) = 0;// is this a multiplayer game? (either coop or deathmatch) + 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 + +// 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) + virtual void InitHUD( CBasePlayer *pl ) = 0; // the client dll is ready for updating + virtual void ClientDisconnected( edict_t *pClient ) = 0;// a client just disconnected from the server + virtual void UpdateGameMode( CBasePlayer *pPlayer ) {} // the client needs to be informed of the current game mode + +// Client damage rules + 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; } + +// Client spawn/respawn control + virtual void PlayerSpawn( CBasePlayer *pPlayer ) = 0;// called by CBasePlayer::Spawn just before releasing player into the game + virtual void PlayerThink( CBasePlayer *pPlayer ) = 0; // called by CBasePlayer::PreThink every frame, before physics are run and after keys are accepted + virtual BOOL FPlayerCanRespawn( CBasePlayer *pPlayer ) = 0;// is this player allowed to respawn now? + virtual float FlPlayerSpawnTime( CBasePlayer *pPlayer ) = 0;// When in the future will this player be able to spawn? + virtual edict_t *GetPlayerSpawnSpot( CBasePlayer *pPlayer );// Place this player on their spawnspot and face them the proper direction. + + virtual BOOL AllowAutoTargetCrosshair( void ) { return TRUE; }; + virtual BOOL ClientCommand( CBasePlayer *pPlayer, const char *pcmd ) { return FALSE; }; // handles the user commands; returns TRUE if command handled properly + virtual void ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ) {} // the player has changed userinfo; can change it now + +// 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 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? + virtual void PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) = 0;// Called each time a player picks up a weapon from the ground + +// Weapon spawn/respawn control + virtual int WeaponShouldRespawn( CBasePlayerItem *pWeapon ) = 0;// should this weapon respawn? + virtual float FlWeaponRespawnTime( CBasePlayerItem *pWeapon ) = 0;// when may this weapon respawn? + virtual float FlWeaponTryRespawn( CBasePlayerItem *pWeapon ) = 0; // can i respawn now, and if not, when should i try again? + virtual Vector VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ) = 0;// where in the world should this weapon respawn? + +// 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) + +// Item spawn/respawn control + virtual int ItemShouldRespawn( CItem *pItem ) = 0;// Should this item respawn? + virtual float FlItemRespawnTime( CItem *pItem ) = 0;// when may this item respawn? + virtual Vector VecItemRespawnSpot( CItem *pItem ) = 0;// where in the world should this item respawn? + +// Ammo retrieval + virtual BOOL CanHaveAmmo( CBasePlayer *pPlayer, const char *pszAmmoName, int iMaxCarry );// can this player take more of this ammo? + virtual void PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ) = 0;// called each time a player picks up some ammo in the world + +// Ammo spawn/respawn control + virtual int AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ) = 0;// should this ammo item respawn? + virtual float FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ) = 0;// when should this ammo item respawn? + virtual Vector VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ) = 0;// where in the world should this ammo item respawn? + // by default, everything spawns + +// Healthcharger respawn control + virtual float FlHealthChargerRechargeTime( void ) = 0;// how long until a depleted HealthCharger recharges itself? + virtual float FlHEVChargerRechargeTime( void ) { return 0; }// how long until a depleted HealthCharger recharges itself? + +// What happens to a dead player's weapons + virtual int DeadPlayerWeapons( CBasePlayer *pPlayer ) = 0;// what do I do with a player's weapons when he's killed? + +// What happens to a dead player's ammo + virtual int DeadPlayerAmmo( CBasePlayer *pPlayer ) = 0;// Do I drop ammo when the player dies? How much? + +// Teamplay stuff + virtual const char *GetTeamID( CBaseEntity *pEntity ) = 0;// what team is this entity on? + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) = 0;// What is the player's relationship with this entity? + virtual int GetTeamIndex( const char *pTeamName ) { return -1; } + virtual const char *GetIndexedTeamName( int teamIndex ) { return ""; } + virtual BOOL IsValidTeam( const char *pTeamName ) { return TRUE; } + virtual void ChangePlayerTeam( CBasePlayer *pPlayer, const char *pTeamName, BOOL bKill, BOOL bGib ) {} + virtual const char *SetDefaultPlayerTeam( CBasePlayer *pPlayer ) { return ""; } + +// Sounds + virtual BOOL PlayTextureSounds( void ) { return TRUE; } + virtual BOOL PlayFootstepSounds( CBasePlayer *pl, float fvol ) { return TRUE; } + +// Monsters + virtual BOOL FAllowMonsters( void ) = 0;//are monsters allowed + + // Immediately end a multiplayer game + virtual void EndMultiplayerGame( void ) {} +}; + +extern CGameRules *InstallGameRules( void ); + + +//========================================================= +// CHalfLifeRules - rules for the single player Half-Life +// game. +//========================================================= +class CHalfLifeRules : public CGameRules +{ +public: + CHalfLifeRules ( void ); + +// GR_Think + virtual void Think( void ); + virtual BOOL IsAllowedToSpawn( CBaseEntity *pEntity ); + virtual BOOL FAllowFlashlight( void ) { return TRUE; }; + + virtual BOOL FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + virtual BOOL GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ); + +// Functions to verify the single/multiplayer status of a game + virtual BOOL IsMultiplayer( void ); + virtual BOOL IsDeathmatch( void ); + virtual BOOL IsCoOp( void ); + +// Client connection/disconnection + virtual BOOL ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); + virtual void InitHUD( CBasePlayer *pl ); // the client dll is ready for updating + virtual void ClientDisconnected( edict_t *pClient ); + +// Client damage rules + virtual float FlPlayerFallDamage( CBasePlayer *pPlayer ); + +// Client spawn/respawn control + virtual void PlayerSpawn( CBasePlayer *pPlayer ); + virtual void PlayerThink( CBasePlayer *pPlayer ); + virtual BOOL FPlayerCanRespawn( CBasePlayer *pPlayer ); + virtual float FlPlayerSpawnTime( CBasePlayer *pPlayer ); + + virtual BOOL AllowAutoTargetCrosshair( void ); + +// 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 ); + +// Weapon retrieval + virtual void PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + +// Weapon spawn/respawn control + virtual int WeaponShouldRespawn( CBasePlayerItem *pWeapon ); + virtual float FlWeaponRespawnTime( CBasePlayerItem *pWeapon ); + virtual float FlWeaponTryRespawn( CBasePlayerItem *pWeapon ); + virtual Vector VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ); + +// Item retrieval + virtual BOOL CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ); + virtual void PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ); + +// Item spawn/respawn control + virtual int ItemShouldRespawn( CItem *pItem ); + virtual float FlItemRespawnTime( CItem *pItem ); + virtual Vector VecItemRespawnSpot( CItem *pItem ); + +// Ammo retrieval + virtual void PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ); + +// Ammo spawn/respawn control + virtual int AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ); + virtual float FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ); + virtual Vector VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ); + +// Healthcharger respawn control + virtual float FlHealthChargerRechargeTime( void ); + +// What happens to a dead player's weapons + virtual int DeadPlayerWeapons( CBasePlayer *pPlayer ); + +// What happens to a dead player's ammo + virtual int DeadPlayerAmmo( CBasePlayer *pPlayer ); + +// Monsters + virtual BOOL FAllowMonsters( void ); + +// Teamplay stuff + virtual const char *GetTeamID( CBaseEntity *pEntity ) {return "";}; + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); +}; + +//========================================================= +// CHalfLifeMultiplay - rules for the basic half life multiplayer +// competition +//========================================================= +class CHalfLifeMultiplay : public CGameRules +{ +public: + CHalfLifeMultiplay(); + +// GR_Think + virtual void Think( void ); + virtual void RefreshSkillData( void ); + virtual BOOL IsAllowedToSpawn( CBaseEntity *pEntity ); + virtual BOOL FAllowFlashlight( void ); + + virtual BOOL FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + virtual BOOL GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ); + +// Functions to verify the single/multiplayer status of a game + virtual BOOL IsMultiplayer( void ); + virtual BOOL IsDeathmatch( void ); + virtual BOOL IsCoOp( void ); + +// Client connection/disconnection + // If ClientConnected returns FALSE, the connection is rejected and the user is provided the reason specified in + // svRejectReason + // Only the client's name and remote address are provided to the dll for verification. + virtual BOOL ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); + virtual void InitHUD( CBasePlayer *pl ); // the client dll is ready for updating + virtual void ClientDisconnected( edict_t *pClient ); + virtual void UpdateGameMode( CBasePlayer *pPlayer ); // the client needs to be informed of the current game mode + +// Client damage rules + virtual float FlPlayerFallDamage( CBasePlayer *pPlayer ); + virtual BOOL FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ); + +// Client spawn/respawn control + virtual void PlayerSpawn( CBasePlayer *pPlayer ); + virtual void PlayerThink( CBasePlayer *pPlayer ); + virtual BOOL FPlayerCanRespawn( CBasePlayer *pPlayer ); + virtual float FlPlayerSpawnTime( CBasePlayer *pPlayer ); + virtual edict_t *GetPlayerSpawnSpot( CBasePlayer *pPlayer ); + + virtual BOOL AllowAutoTargetCrosshair( void ); + 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 ); + +// Weapon retrieval + virtual void PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + virtual BOOL CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon );// The player is touching an CBasePlayerItem, do I give it to him? + +// Weapon spawn/respawn control + virtual int WeaponShouldRespawn( CBasePlayerItem *pWeapon ); + virtual float FlWeaponRespawnTime( CBasePlayerItem *pWeapon ); + virtual float FlWeaponTryRespawn( CBasePlayerItem *pWeapon ); + virtual Vector VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ); + +// Item retrieval + virtual BOOL CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ); + virtual void PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ); + +// Item spawn/respawn control + virtual int ItemShouldRespawn( CItem *pItem ); + virtual float FlItemRespawnTime( CItem *pItem ); + virtual Vector VecItemRespawnSpot( CItem *pItem ); + +// Ammo retrieval + virtual void PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ); + +// Ammo spawn/respawn control + virtual int AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ); + virtual float FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ); + virtual Vector VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ); + +// Healthcharger respawn control + virtual float FlHealthChargerRechargeTime( void ); + virtual float FlHEVChargerRechargeTime( void ); + +// What happens to a dead player's weapons + virtual int DeadPlayerWeapons( CBasePlayer *pPlayer ); + +// What happens to a dead player's ammo + virtual int DeadPlayerAmmo( CBasePlayer *pPlayer ); + +// Teamplay stuff + virtual const char *GetTeamID( CBaseEntity *pEntity ) {return "";} + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); + + virtual BOOL PlayTextureSounds( void ) { return FALSE; } + virtual BOOL PlayFootstepSounds( CBasePlayer *pl, float fvol ); + +// Monsters + virtual BOOL FAllowMonsters( void ); + + // Immediately end a multiplayer game + virtual void EndMultiplayerGame( void ) { GoToIntermission(); } + +protected: + virtual void ChangeLevel( void ); + virtual void GoToIntermission( void ); + float m_flIntermissionEndTime; + BOOL m_iEndIntermissionButtonHit; + void SendMOTDToClient( edict_t *client ); +}; + +extern DLL_GLOBAL CGameRules* g_pGameRules; diff --git a/hlsdk/dlls/gauss.cpp b/hlsdk/dlls/gauss.cpp new file mode 100644 index 00000000..ffed320e --- /dev/null +++ b/hlsdk/dlls/gauss.cpp @@ -0,0 +1,623 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "soundent.h" +#include "shake.h" +#include "gamerules.h" + + +#define GAUSS_PRIMARY_CHARGE_VOLUME 256// how loud gauss is while charging +#define GAUSS_PRIMARY_FIRE_VOLUME 450// how loud gauss is when discharged + +enum gauss_e { + GAUSS_IDLE = 0, + GAUSS_IDLE2, + GAUSS_FIDGET, + GAUSS_SPINUP, + GAUSS_SPIN, + GAUSS_FIRE, + GAUSS_FIRE2, + GAUSS_HOLSTER, + GAUSS_DRAW +}; + +LINK_ENTITY_TO_CLASS( weapon_gauss, CGauss ); + +float CGauss::GetFullChargeTime( void ) +{ +#ifdef CLIENT_DLL + if ( bIsMultiplayer() ) +#else + if ( g_pGameRules->IsMultiplayer() ) +#endif + { + return 1.5; + } + + return 4; +} + +#ifdef CLIENT_DLL +extern int g_irunninggausspred; +#endif + +void CGauss::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_GAUSS; + SET_MODEL(ENT(pev), "models/w_gauss.mdl"); + + m_iDefaultAmmo = GAUSS_DEFAULT_GIVE; + + FallInit();// get ready to fall down. +} + + +void CGauss::Precache( void ) +{ + PRECACHE_MODEL("models/w_gauss.mdl"); + PRECACHE_MODEL("models/v_gauss.mdl"); + PRECACHE_MODEL("models/p_gauss.mdl"); + + PRECACHE_SOUND("items/9mmclip1.wav"); + + PRECACHE_SOUND("weapons/gauss2.wav"); + PRECACHE_SOUND("weapons/electro4.wav"); + PRECACHE_SOUND("weapons/electro5.wav"); + PRECACHE_SOUND("weapons/electro6.wav"); + PRECACHE_SOUND("ambience/pulsemachine.wav"); + + m_iGlow = PRECACHE_MODEL( "sprites/hotglow.spr" ); + m_iBalls = PRECACHE_MODEL( "sprites/hotglow.spr" ); + m_iBeam = PRECACHE_MODEL( "sprites/smoke.spr" ); + + m_usGaussFire = PRECACHE_EVENT( 1, "events/gauss.sc" ); + m_usGaussSpin = PRECACHE_EVENT( 1, "events/gaussspin.sc" ); +} + +int CGauss::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + +int CGauss::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "uranium"; + p->iMaxAmmo1 = URANIUM_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 3; + p->iPosition = 1; + p->iId = m_iId = WEAPON_GAUSS; + p->iFlags = 0; + p->iWeight = GAUSS_WEIGHT; + + return 1; +} + +BOOL CGauss::Deploy( ) +{ + m_pPlayer->m_flPlayAftershock = 0.0; + return DefaultDeploy( "models/v_gauss.mdl", "models/p_gauss.mdl", GAUSS_DRAW, "gauss" ); +} + +void CGauss::Holster( int skiplocal /* = 0 */ ) +{ + PLAYBACK_EVENT_FULL( FEV_RELIABLE | FEV_GLOBAL, m_pPlayer->edict(), m_usGaussFire, 0.01, (float *)&m_pPlayer->pev->origin, (float *)&m_pPlayer->pev->angles, 0.0, 0.0, 0, 0, 0, 1 ); + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + + SendWeaponAnim( GAUSS_HOLSTER ); + m_fInAttack = 0; +} + + +void CGauss::PrimaryAttack() +{ + // don't fire underwater + if ( m_pPlayer->pev->waterlevel == 3 ) + { + PlayEmptySound( ); + m_flNextSecondaryAttack = m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.15; + return; + } + + if ( m_pPlayer->m_rgAmmo[ m_iPrimaryAmmoType ] < 2 ) + { + PlayEmptySound( ); + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + return; + } + + m_pPlayer->m_iWeaponVolume = GAUSS_PRIMARY_FIRE_VOLUME; + m_fPrimaryFire = TRUE; + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= 2; + + StartFire(); + m_fInAttack = 0; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.0; + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.2; +} + +void CGauss::SecondaryAttack() +{ + // don't fire underwater + if ( m_pPlayer->pev->waterlevel == 3 ) + { + if ( m_fInAttack != 0 ) + { + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/electro4.wav", 1.0, ATTN_NORM, 0, 80 + RANDOM_LONG(0,0x3f)); + SendWeaponAnim( GAUSS_IDLE ); + m_fInAttack = 0; + } + else + { + PlayEmptySound( ); + } + + m_flNextSecondaryAttack = m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.5; + return; + } + + if ( m_fInAttack == 0 ) + { + if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0 ) + { + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/357_cock1.wav", 0.8, ATTN_NORM); + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + return; + } + + m_fPrimaryFire = FALSE; + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--;// take one ammo just to start the spin + m_pPlayer->m_flNextAmmoBurn = UTIL_WeaponTimeBase(); + + // spin up + m_pPlayer->m_iWeaponVolume = GAUSS_PRIMARY_CHARGE_VOLUME; + + SendWeaponAnim( GAUSS_SPINUP ); + m_fInAttack = 1; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.5; + m_pPlayer->m_flStartCharge = gpGlobals->time; + m_pPlayer->m_flAmmoStartCharge = UTIL_WeaponTimeBase() + GetFullChargeTime(); + + PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), m_usGaussSpin, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 110, 0, 0, 0 ); + + m_iSoundState = SND_CHANGE_PITCH; + } + else if (m_fInAttack == 1) + { + if (m_flTimeWeaponIdle < UTIL_WeaponTimeBase()) + { + SendWeaponAnim( GAUSS_SPIN ); + m_fInAttack = 2; + } + } + else + { + // during the charging process, eat one bit of ammo every once in a while + if ( UTIL_WeaponTimeBase() >= m_pPlayer->m_flNextAmmoBurn && m_pPlayer->m_flNextAmmoBurn != 1000 ) + { +#ifdef CLIENT_DLL + if ( bIsMultiplayer() ) +#else + if ( g_pGameRules->IsMultiplayer() ) +#endif + { + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + m_pPlayer->m_flNextAmmoBurn = UTIL_WeaponTimeBase() + 0.1; + } + else + { + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + m_pPlayer->m_flNextAmmoBurn = UTIL_WeaponTimeBase() + 0.3; + } + } + + if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0 ) + { + // out of ammo! force the gun to fire + StartFire(); + m_fInAttack = 0; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.0; + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1; + return; + } + + if ( UTIL_WeaponTimeBase() >= m_pPlayer->m_flAmmoStartCharge ) + { + // don't eat any more ammo after gun is fully charged. + m_pPlayer->m_flNextAmmoBurn = 1000; + } + + int pitch = static_cast(( gpGlobals->time - m_pPlayer->m_flStartCharge ) * ( 150 / GetFullChargeTime() ) + 100); + if ( pitch > 250 ) + pitch = 250; + + // ALERT( at_console, "%d %d %d\n", m_fInAttack, m_iSoundState, pitch ); + + if ( m_iSoundState == 0 ) + ALERT( at_console, "sound state %d\n", m_iSoundState ); + + PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), m_usGaussSpin, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, pitch, 0, ( m_iSoundState == SND_CHANGE_PITCH ) ? 1 : 0, 0 ); + + m_iSoundState = SND_CHANGE_PITCH; // hack for going through level transitions + + m_pPlayer->m_iWeaponVolume = GAUSS_PRIMARY_CHARGE_VOLUME; + + // m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.1; + if ( m_pPlayer->m_flStartCharge < gpGlobals->time - 10 ) + { + // Player charged up too long. Zap him. + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/electro4.wav", 1.0, ATTN_NORM, 0, 80 + RANDOM_LONG(0,0x3f)); + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/electro6.wav", 1.0, ATTN_NORM, 0, 75 + RANDOM_LONG(0,0x3f)); + + m_fInAttack = 0; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.0; + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.0; + +#ifndef CLIENT_DLL + m_pPlayer->TakeDamage( VARS(eoNullEntity), VARS(eoNullEntity), 50, DMG_SHOCK ); + UTIL_ScreenFade( m_pPlayer, Vector(255,128,0), 2, 0.5, 128, FFADE_IN ); +#endif + SendWeaponAnim( GAUSS_IDLE ); + + // Player may have been killed and this weapon dropped, don't execute any more code after this! + return; + } + } +} + +//========================================================= +// StartFire- since all of this code has to run and then +// call Fire(), it was easier at this point to rip it out +// of weaponidle() and make its own function then to try to +// merge this into Fire(), which has some identical variable names +//========================================================= +void CGauss::StartFire( void ) +{ + float flDamage; + + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + Vector vecAiming = gpGlobals->v_forward; + Vector vecSrc = m_pPlayer->GetGunPosition( ); // + gpGlobals->v_up * -8 + gpGlobals->v_right * 8; + + if ( gpGlobals->time - m_pPlayer->m_flStartCharge > GetFullChargeTime() ) + { + flDamage = 200; + } + else + { + flDamage = 200 * (( gpGlobals->time - m_pPlayer->m_flStartCharge) / GetFullChargeTime() ); + } + + if ( m_fPrimaryFire ) + { + // fixed damage on primary attack +#ifdef CLIENT_DLL + flDamage = 20; +#else + flDamage = gSkillData.plrDmgGauss; +#endif + } + + if (m_fInAttack != 3) + { + //ALERT ( at_console, "Time:%f Damage:%f\n", gpGlobals->time - m_pPlayer->m_flStartCharge, flDamage ); + +#ifndef CLIENT_DLL + float flZVel = m_pPlayer->pev->velocity.z; + + if ( !m_fPrimaryFire ) + { + m_pPlayer->pev->velocity = m_pPlayer->pev->velocity - gpGlobals->v_forward * flDamage * 5; + } + + if ( !g_pGameRules->IsMultiplayer() ) + + { + // in deathmatch, gauss can pop you up into the air. Not in single play. + m_pPlayer->pev->velocity.z = flZVel; + } +#endif + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + } + + // time until aftershock 'static discharge' sound + m_pPlayer->m_flPlayAftershock = gpGlobals->time + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 0.3, 0.8 ); + + Fire( vecSrc, vecAiming, flDamage ); +} + +void CGauss::Fire( Vector vecOrigSrc, Vector vecDir, float flDamage ) +{ + m_pPlayer->m_iWeaponVolume = GAUSS_PRIMARY_FIRE_VOLUME; + + Vector vecSrc = vecOrigSrc; + Vector vecDest = vecSrc + vecDir * 8192; + edict_t *pentIgnore; + TraceResult tr, beam_tr; + float flMaxFrac = 1.0; + int nTotal = 0; + int fHasPunched = 0; + int fFirstBeam = 1; + int nMaxHits = 10; + + pentIgnore = ENT( m_pPlayer->pev ); + +#ifdef CLIENT_DLL + if ( m_fPrimaryFire == false ) + g_irunninggausspred = true; +#endif + + // The main firing event is sent unreliably so it won't be delayed. + PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), m_usGaussFire, 0.0, (float *)&m_pPlayer->pev->origin, (float *)&m_pPlayer->pev->angles, flDamage, 0.0, 0, 0, m_fPrimaryFire ? 1 : 0, 0 ); + + // This reliable event is used to stop the spinning sound + // It's delayed by a fraction of second to make sure it is delayed by 1 frame on the client + // It's sent reliably anyway, which could lead to other delays + + PLAYBACK_EVENT_FULL( FEV_NOTHOST | FEV_RELIABLE, m_pPlayer->edict(), m_usGaussFire, 0.01, (float *)&m_pPlayer->pev->origin, (float *)&m_pPlayer->pev->angles, 0.0, 0.0, 0, 0, 0, 1 ); + + + /*ALERT( at_console, "%f %f %f\n%f %f %f\n", + vecSrc.x, vecSrc.y, vecSrc.z, + vecDest.x, vecDest.y, vecDest.z );*/ + + +// ALERT( at_console, "%f %f\n", tr.flFraction, flMaxFrac ); + +#ifndef CLIENT_DLL + while (flDamage > 10 && nMaxHits > 0) + { + nMaxHits--; + + // ALERT( at_console, "." ); + UTIL_TraceLine(vecSrc, vecDest, dont_ignore_monsters, pentIgnore, &tr); + + if (tr.fAllSolid) + break; + + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + + if (pEntity == NULL) + break; + + if ( fFirstBeam ) + { + m_pPlayer->pev->effects |= EF_MUZZLEFLASH; + fFirstBeam = 0; + + nTotal += 26; + } + + if (pEntity->pev->takedamage) + { + ClearMultiDamage(); + pEntity->TraceAttack( m_pPlayer->pev, flDamage, vecDir, &tr, DMG_BULLET ); + ApplyMultiDamage(m_pPlayer->pev, m_pPlayer->pev); + } + + if ( pEntity->ReflectGauss() ) + { + float n; + + pentIgnore = NULL; + + n = -DotProduct(tr.vecPlaneNormal, vecDir); + + if (n < 0.5) // 60 degrees + { + // ALERT( at_console, "reflect %f\n", n ); + // reflect + Vector r; + + r = 2.0 * tr.vecPlaneNormal * n + vecDir; + flMaxFrac = flMaxFrac - tr.flFraction; + vecDir = r; + vecSrc = tr.vecEndPos + vecDir * 8; + vecDest = vecSrc + vecDir * 8192; + + // explode a bit + m_pPlayer->RadiusDamage( tr.vecEndPos, pev, m_pPlayer->pev, flDamage * n, CLASS_NONE, DMG_BLAST ); + + nTotal += 34; + + // lose energy + if (n == 0) n = 0.1; + flDamage = flDamage * (1 - n); + } + else + { + nTotal += 13; + + // limit it to one hole punch + if (fHasPunched) + break; + fHasPunched = 1; + + // try punching through wall if secondary attack (primary is incapable of breaking through) + if ( !m_fPrimaryFire ) + { + UTIL_TraceLine( tr.vecEndPos + vecDir * 8, vecDest, dont_ignore_monsters, pentIgnore, &beam_tr); + if (!beam_tr.fAllSolid) + { + // trace backwards to find exit point + UTIL_TraceLine( beam_tr.vecEndPos, tr.vecEndPos, dont_ignore_monsters, pentIgnore, &beam_tr); + + float n = (beam_tr.vecEndPos - tr.vecEndPos).Length( ); + + if (n < flDamage) + { + if (n == 0) n = 1; + flDamage -= n; + + // ALERT( at_console, "punch %f\n", n ); + nTotal += 21; + + // exit blast damage + //m_pPlayer->RadiusDamage( beam_tr.vecEndPos + vecDir * 8, pev, m_pPlayer->pev, flDamage, CLASS_NONE, DMG_BLAST ); + float damage_radius; + + + if ( g_pGameRules->IsMultiplayer() ) + { + damage_radius = flDamage * 1.75; // Old code == 2.5 + } + else + { + damage_radius = flDamage * 2.5; + } + + ::RadiusDamage( beam_tr.vecEndPos + vecDir * 8, pev, m_pPlayer->pev, flDamage, damage_radius, CLASS_NONE, DMG_BLAST ); + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, NORMAL_EXPLOSION_VOLUME, 3.0 ); + + nTotal += 53; + + vecSrc = beam_tr.vecEndPos + vecDir; + } + } + else + { + //ALERT( at_console, "blocked %f\n", n ); + flDamage = 0; + } + } + else + { + //ALERT( at_console, "blocked solid\n" ); + + flDamage = 0; + } + + } + } + else + { + vecSrc = tr.vecEndPos + vecDir; + pentIgnore = ENT( pEntity->pev ); + } + } +#endif + // ALERT( at_console, "%d bytes\n", nTotal ); +} + + + + +void CGauss::WeaponIdle( void ) +{ + ResetEmptySound( ); + + // play aftershock static discharge + if ( m_pPlayer->m_flPlayAftershock && m_pPlayer->m_flPlayAftershock < gpGlobals->time ) + { + switch (RANDOM_LONG(0,3)) + { + case 0: EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/electro4.wav", RANDOM_FLOAT(0.7, 0.8), ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/electro5.wav", RANDOM_FLOAT(0.7, 0.8), ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/electro6.wav", RANDOM_FLOAT(0.7, 0.8), ATTN_NORM); break; + case 3: break; // no sound + } + m_pPlayer->m_flPlayAftershock = 0.0; + } + + if (m_flTimeWeaponIdle > UTIL_WeaponTimeBase()) + return; + + if (m_fInAttack != 0) + { + StartFire(); + m_fInAttack = 0; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 2.0; + } + else + { + int iAnim; + float flRand = RANDOM_FLOAT(0, 1); + if (flRand <= 0.5) + { + iAnim = GAUSS_IDLE; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + } + else if (flRand <= 0.75) + { + iAnim = GAUSS_IDLE2; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + } + else + { + iAnim = GAUSS_FIDGET; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 3; + } + + return; + SendWeaponAnim( iAnim ); + + } +} + + + + + + +class CGaussAmmo : public CBasePlayerAmmo +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_gaussammo.mdl"); + CBasePlayerAmmo::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_gaussammo.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + } + BOOL AddAmmo( CBaseEntity *pOther ) + { + if (pOther->GiveAmmo( AMMO_URANIUMBOX_GIVE, "uranium", URANIUM_MAX_CARRY ) != -1) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + return TRUE; + } + return FALSE; + } +}; +LINK_ENTITY_TO_CLASS( ammo_gaussclip, CGaussAmmo ); + +#endif diff --git a/hlsdk/dlls/ggrenade.cpp b/hlsdk/dlls/ggrenade.cpp new file mode 100644 index 00000000..1805eb0d --- /dev/null +++ b/hlsdk/dlls/ggrenade.cpp @@ -0,0 +1,488 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== generic grenade.cpp ======================================================== + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "soundent.h" +#include "decals.h" + + +//===================grenade + + +LINK_ENTITY_TO_CLASS( grenade, CGrenade ); + +// Grenades flagged with this will be triggered when the owner calls detonateSatchelCharges +#define SF_DETONATE 0x0001 + +// +// Grenade Explode +// +void CGrenade::Explode( Vector vecSrc, Vector vecAim ) +{ + TraceResult tr; + UTIL_TraceLine ( pev->origin, pev->origin + Vector ( 0, 0, -32 ), ignore_monsters, ENT(pev), & tr); + + Explode( &tr, DMG_BLAST ); +} + +// UNDONE: temporary scorching for PreAlpha - find a less sleazy permenant solution. +void CGrenade::Explode( TraceResult *pTrace, int bitsDamageType ) +{ + float flRndSound;// sound randomizer + + pev->model = iStringNull;//invisible + pev->solid = SOLID_NOT;// intangible + + pev->takedamage = DAMAGE_NO; + + // Pull out of the wall a bit + if ( pTrace->flFraction != 1.0 ) + { + pev->origin = pTrace->vecEndPos + (pTrace->vecPlaneNormal * (pev->dmg - 24) * 0.6); + } + + int iContents = UTIL_PointContents ( pev->origin ); + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION ); // This makes a dynamic light and the explosion sprites/sound + WRITE_COORD( pev->origin.x ); // Send to PAS because of the sound + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + if (iContents != CONTENTS_WATER) + { + WRITE_SHORT( g_sModelIndexFireball ); + } + else + { + WRITE_SHORT( g_sModelIndexWExplosion ); + } + WRITE_BYTE( static_cast((pev->dmg - 50) * .60) ); // scale * 10 + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + + 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, bitsDamageType ); + + if ( RANDOM_FLOAT( 0 , 1 ) < 0.5 ) + { + UTIL_DecalTrace( pTrace, DECAL_SCORCH1 ); + } + else + { + UTIL_DecalTrace( pTrace, DECAL_SCORCH2 ); + } + + flRndSound = RANDOM_FLOAT( 0 , 1 ); + + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris1.wav", 0.55, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris2.wav", 0.55, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris3.wav", 0.55, ATTN_NORM); break; + } + + pev->effects |= EF_NODRAW; + SetThink( &CGrenade::Smoke ); + pev->velocity = g_vecZero; + pev->nextthink = gpGlobals->time + 0.3; + + if (iContents != CONTENTS_WATER) + { + int sparkCount = RANDOM_LONG(0,3); + for ( int i = 0; i < sparkCount; i++ ) + Create( "spark_shower", pev->origin, pTrace->vecPlaneNormal, NULL ); + } +} + + +void CGrenade::Smoke( void ) +{ + if (UTIL_PointContents ( pev->origin ) == CONTENTS_WATER) + { + UTIL_Bubbles( pev->origin - Vector( 64, 64, 64 ), pev->origin + Vector( 64, 64, 64 ), 100 ); + } + else + { + 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( static_cast((pev->dmg - 50) * 0.80) ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + } + UTIL_Remove( this ); +} + +void CGrenade::Killed( entvars_t *pevAttacker, int iGib ) +{ + Detonate( ); +} + + +// Timed grenade, this think is called when time runs out. +void CGrenade::DetonateUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink( &CGrenade::Detonate ); + pev->nextthink = gpGlobals->time; +} + +void CGrenade::PreDetonate( void ) +{ + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin, 400, 0.3 ); + + SetThink( &CGrenade::Detonate ); + pev->nextthink = gpGlobals->time + 1; +} + + +void CGrenade::Detonate( void ) +{ + 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); + + Explode( &tr, DMG_BLAST ); +} + + +// +// Contact grenade, explode when it touches something +// +void CGrenade::ExplodeTouch( 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 ); + + Explode( &tr, DMG_BLAST ); +} + + +void CGrenade::DangerSoundThink( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin + pev->velocity * 0.5, static_cast(pev->velocity.Length( )), 0.2 ); + pev->nextthink = gpGlobals->time + 0.2; + + if (pev->waterlevel != 0) + { + pev->velocity = pev->velocity * 0.5; + } +} + + +void CGrenade::BounceTouch( CBaseEntity *pOther ) +{ + // don't hit the guy that launched this grenade + if ( pOther->edict() == pev->owner ) + return; + + // only do damage if we're moving fairly fast + if (m_flNextAttack < gpGlobals->time && pev->velocity.Length() > 100) + { + entvars_t *pevOwner = VARS( pev->owner ); + if (pevOwner) + { + TraceResult tr = UTIL_GetGlobalTrace( ); + ClearMultiDamage( ); + pOther->TraceAttack(pevOwner, 1, gpGlobals->v_forward, &tr, DMG_CLUB ); + ApplyMultiDamage( pev, pevOwner); + } + m_flNextAttack = gpGlobals->time + 1.0; // debounce + } + + Vector vecTestVelocity; + // pev->avelocity = Vector (300, 300, 300); + + // this is my heuristic for modulating the grenade velocity because grenades dropped purely vertical + // or thrown very far tend to slow down too quickly for me to always catch just by testing velocity. + // trimming the Z velocity a bit seems to help quite a bit. + vecTestVelocity = pev->velocity; + vecTestVelocity.z *= 0.45; + + if ( !m_fRegisteredSound && vecTestVelocity.Length() <= 60 ) + { + //ALERT( at_console, "Grenade Registered!: %f\n", vecTestVelocity.Length() ); + + // grenade is moving really slow. It's probably very close to where it will ultimately stop moving. + // go ahead and emit the danger sound. + + // register a radius louder than the explosion, so we make sure everyone gets out of the way + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin, static_cast(pev->dmg / 0.4), 0.3 ); + m_fRegisteredSound = TRUE; + } + + if (pev->flags & FL_ONGROUND) + { + // add a bit of static friction + pev->velocity = pev->velocity * 0.8; + + pev->sequence = RANDOM_LONG( 1, 1 ); + } + else + { + // play bounce sound + BounceSound(); + } + pev->framerate = pev->velocity.Length() / 200.0; + if (pev->framerate > 1.0) + pev->framerate = 1; + else if (pev->framerate < 0.5) + pev->framerate = 0; + +} + + + +void CGrenade::SlideTouch( CBaseEntity *pOther ) +{ + // don't hit the guy that launched this grenade + if ( pOther->edict() == pev->owner ) + return; + + // pev->avelocity = Vector (300, 300, 300); + + if (pev->flags & FL_ONGROUND) + { + // add a bit of static friction + pev->velocity = pev->velocity * 0.95; + + if (pev->velocity.x != 0 || pev->velocity.y != 0) + { + // maintain sliding sound + } + } + else + { + BounceSound(); + } +} + +void CGrenade :: BounceSound( void ) +{ + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/grenade_hit1.wav", 0.25, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/grenade_hit2.wav", 0.25, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/grenade_hit3.wav", 0.25, ATTN_NORM); break; + } +} + +void CGrenade :: TumbleThink( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + StudioFrameAdvance( ); + 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( &CGrenade::Detonate ); + } + if (pev->waterlevel != 0) + { + pev->velocity = pev->velocity * 0.5; + pev->framerate = 0.2; + } +} + + +void CGrenade:: Spawn( void ) +{ + pev->movetype = MOVETYPE_BOUNCE; + pev->classname = MAKE_STRING( "grenade" ); + + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/grenade.mdl"); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + + pev->dmg = 100; + m_fRegisteredSound = FALSE; +} + + +CGrenade *CGrenade::ShootContact( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ) +{ + CGrenade *pGrenade = GetClassPtr( (CGrenade *)NULL ); + pGrenade->Spawn(); + // contact grenades arc lower + pGrenade->pev->gravity = 0.5;// lower gravity since grenade is aerodynamic and engine doesn't know it. + UTIL_SetOrigin( pGrenade->pev, vecStart ); + pGrenade->pev->velocity = vecVelocity; + pGrenade->pev->angles = UTIL_VecToAngles (pGrenade->pev->velocity); + pGrenade->pev->owner = ENT(pevOwner); + + // make monsters afaid of it while in the air + pGrenade->SetThink( &CGrenade::DangerSoundThink ); + pGrenade->pev->nextthink = gpGlobals->time; + + // Tumble in air + pGrenade->pev->avelocity.x = RANDOM_FLOAT ( -100, -500 ); + + // Explode on contact + pGrenade->SetTouch( &CGrenade::ExplodeTouch ); + + pGrenade->pev->dmg = gSkillData.plrDmgM203Grenade; + + return pGrenade; +} + + +CGrenade * CGrenade:: ShootTimed( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time ) +{ + CGrenade *pGrenade = GetClassPtr( (CGrenade *)NULL ); + pGrenade->Spawn(); + UTIL_SetOrigin( pGrenade->pev, vecStart ); + pGrenade->pev->velocity = vecVelocity; + pGrenade->pev->angles = UTIL_VecToAngles(pGrenade->pev->velocity); + pGrenade->pev->owner = ENT(pevOwner); + + pGrenade->SetTouch( &CGrenade::BounceTouch ); // Bounce if touched + + // 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(). + + pGrenade->pev->dmgtime = gpGlobals->time + time; + pGrenade->SetThink( &CGrenade::TumbleThink ); + pGrenade->pev->nextthink = gpGlobals->time + 0.1; + if (time < 0.1) + { + pGrenade->pev->nextthink = gpGlobals->time; + pGrenade->pev->velocity = Vector( 0, 0, 0 ); + } + + pGrenade->pev->sequence = RANDOM_LONG( 3, 6 ); + pGrenade->pev->framerate = 1.0; + + // Tumble through the air + // pGrenade->pev->avelocity.x = -400; + + pGrenade->pev->gravity = 0.5; + pGrenade->pev->friction = 0.8; + + SET_MODEL(ENT(pGrenade->pev), "models/w_grenade.mdl"); + pGrenade->pev->dmg = 100; + + return pGrenade; +} + + +CGrenade * CGrenade :: ShootSatchelCharge( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ) +{ + CGrenade *pGrenade = GetClassPtr( (CGrenade *)NULL ); + pGrenade->pev->movetype = MOVETYPE_BOUNCE; + pGrenade->pev->classname = MAKE_STRING( "grenade" ); + + pGrenade->pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pGrenade->pev), "models/grenade.mdl"); // Change this to satchel charge model + + UTIL_SetSize(pGrenade->pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + + pGrenade->pev->dmg = 200; + UTIL_SetOrigin( pGrenade->pev, vecStart ); + pGrenade->pev->velocity = vecVelocity; + pGrenade->pev->angles = g_vecZero; + pGrenade->pev->owner = ENT(pevOwner); + + // Detonate in "time" seconds + pGrenade->SetThink( &CBaseEntity::SUB_DoNothing ); + pGrenade->SetUse( &CGrenade::DetonateUse ); + pGrenade->SetTouch( &CGrenade::SlideTouch ); + pGrenade->pev->spawnflags = SF_DETONATE; + + pGrenade->pev->friction = 0.9; + + return pGrenade; +} + + + +void CGrenade :: UseSatchelCharges( entvars_t *pevOwner, SATCHELCODE code ) +{ + edict_t *pentFind; + edict_t *pentOwner; + + if ( !pevOwner ) + return; + + CBaseEntity *pOwner = CBaseEntity::Instance( pevOwner ); + + pentOwner = pOwner->edict(); + + pentFind = FIND_ENTITY_BY_CLASSNAME( NULL, "grenade" ); + while ( !FNullEnt( pentFind ) ) + { + CBaseEntity *pEnt = Instance( pentFind ); + if ( pEnt ) + { + if ( FBitSet( pEnt->pev->spawnflags, SF_DETONATE ) && pEnt->pev->owner == pentOwner ) + { + if ( code == SATCHEL_DETONATE ) + pEnt->Use( pOwner, pOwner, USE_ON, 0 ); + else // SATCHEL_RELEASE + pEnt->pev->owner = NULL; + } + } + pentFind = FIND_ENTITY_BY_CLASSNAME( pentFind, "grenade" ); + } +} + +//======================end grenade + diff --git a/hlsdk/dlls/globals.cpp b/hlsdk/dlls/globals.cpp new file mode 100644 index 00000000..ef657c2d --- /dev/null +++ b/hlsdk/dlls/globals.cpp @@ -0,0 +1,39 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== globals.cpp ======================================================== + + DLL-wide global variable definitions. + They're all defined here, for convenient centralization. + Source files that need them should "extern ..." declare each + variable, to better document what globals they care about. + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "soundent.h" + +DLL_GLOBAL ULONG g_ulFrameCount; +DLL_GLOBAL ULONG g_ulModelIndexEyes; +DLL_GLOBAL ULONG g_ulModelIndexPlayer; +DLL_GLOBAL Vector g_vecAttackDir; +DLL_GLOBAL int g_iSkillLevel; +DLL_GLOBAL int gDisplayTitle; +DLL_GLOBAL BOOL g_fGameOver; +DLL_GLOBAL const Vector g_vecZero = Vector(0,0,0); +DLL_GLOBAL int g_Language; diff --git a/hlsdk/dlls/h_ai.cpp b/hlsdk/dlls/h_ai.cpp new file mode 100644 index 00000000..73a5064a --- /dev/null +++ b/hlsdk/dlls/h_ai.cpp @@ -0,0 +1,199 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + + h_ai.cpp - halflife specific ai code + +*/ + + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "game.h" + +#define NUM_LATERAL_CHECKS 13 // how many checks are made on each side of a monster looking for lateral cover +#define NUM_LATERAL_LOS_CHECKS 6 // how many checks are made on each side of a monster looking for lateral cover + +//float flRandom = RANDOM_FLOAT(0,1); + +DLL_GLOBAL BOOL g_fDrawLines = FALSE; + +//========================================================= +// +// AI UTILITY FUNCTIONS +// +// !!!UNDONE - move CBaseMonster functions to monsters.cpp +//========================================================= + +//========================================================= +// FBoxVisible - a more accurate ( and slower ) version +// of FVisible. +// +// !!!UNDONE - make this CBaseMonster? +//========================================================= +BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget, Vector &vecTargetOrigin, float flSize ) +{ + // don't look through water + if ((pevLooker->waterlevel != 3 && pevTarget->waterlevel == 3) + || (pevLooker->waterlevel == 3 && pevTarget->waterlevel == 0)) + return FALSE; + + TraceResult tr; + Vector vecLookerOrigin = pevLooker->origin + pevLooker->view_ofs;//look through the monster's 'eyes' + for (int i = 0; i < 5; i++) + { + Vector vecTarget = pevTarget->origin; + vecTarget.x += RANDOM_FLOAT( pevTarget->mins.x + flSize, pevTarget->maxs.x - flSize); + vecTarget.y += RANDOM_FLOAT( pevTarget->mins.y + flSize, pevTarget->maxs.y - flSize); + vecTarget.z += RANDOM_FLOAT( pevTarget->mins.z + flSize, pevTarget->maxs.z - flSize); + + UTIL_TraceLine(vecLookerOrigin, vecTarget, ignore_monsters, ignore_glass, ENT(pevLooker)/*pentIgnore*/, &tr); + + if (tr.flFraction == 1.0) + { + vecTargetOrigin = vecTarget; + return TRUE;// line of sight is valid. + } + } + return FALSE;// Line of sight is not established +} + +// +// VecCheckToss - returns the velocity at which an object should be lobbed from vecspot1 to land near vecspot2. +// returns g_vecZero if toss is not feasible. +// +Vector VecCheckToss ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flGravityAdj ) +{ + TraceResult tr; + Vector vecMidPoint;// halfway point between Spot1 and Spot2 + Vector vecApex;// highest point + Vector vecScale; + Vector vecGrenadeVel; + Vector vecTemp; + float flGravity = g_psv_gravity->value * flGravityAdj; + + if (vecSpot2.z - vecSpot1.z > 500) + { + // to high, fail + return g_vecZero; + } + + UTIL_MakeVectors (pev->angles); + + // toss a little bit to the left or right, not right down on the enemy's bean (head). + vecSpot2 = vecSpot2 + gpGlobals->v_right * ( RANDOM_FLOAT(-8,8) + RANDOM_FLOAT(-16,16) ); + vecSpot2 = vecSpot2 + gpGlobals->v_forward * ( RANDOM_FLOAT(-8,8) + RANDOM_FLOAT(-16,16) ); + + // calculate the midpoint and apex of the 'triangle' + // UNDONE: normalize any Z position differences between spot1 and spot2 so that triangle is always RIGHT + + // How much time does it take to get there? + + // get a rough idea of how high it can be thrown + vecMidPoint = vecSpot1 + (vecSpot2 - vecSpot1) * 0.5; + UTIL_TraceLine(vecMidPoint, vecMidPoint + Vector(0,0,500), ignore_monsters, ENT(pev), &tr); + vecMidPoint = tr.vecEndPos; + // (subtract 15 so the grenade doesn't hit the ceiling) + vecMidPoint.z -= 15; + + if (vecMidPoint.z < vecSpot1.z || vecMidPoint.z < vecSpot2.z) + { + // to not enough space, fail + return g_vecZero; + } + + // How high should the grenade travel to reach the apex + float distance1 = (vecMidPoint.z - vecSpot1.z); + float distance2 = (vecMidPoint.z - vecSpot2.z); + + // How long will it take for the grenade to travel this distance + float time1 = sqrt( distance1 / (0.5 * flGravity) ); + float time2 = sqrt( distance2 / (0.5 * flGravity) ); + + if (time1 < 0.1) + { + // too close + return g_vecZero; + } + + // how hard to throw sideways to get there in time. + vecGrenadeVel = (vecSpot2 - vecSpot1) / (time1 + time2); + // how hard upwards to reach the apex at the right time. + vecGrenadeVel.z = flGravity * time1; + + // find the apex + vecApex = vecSpot1 + vecGrenadeVel * time1; + vecApex.z = vecMidPoint.z; + + UTIL_TraceLine(vecSpot1, vecApex, dont_ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + // UNDONE: either ignore monsters or change it to not care if we hit our enemy + UTIL_TraceLine(vecSpot2, vecApex, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + return vecGrenadeVel; +} + + +// +// VecCheckThrow - returns the velocity vector at which an object should be thrown from vecspot1 to hit vecspot2. +// returns g_vecZero if throw is not feasible. +// +Vector VecCheckThrow ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flSpeed, float flGravityAdj ) +{ + float flGravity = g_psv_gravity->value * flGravityAdj; + + Vector vecGrenadeVel = (vecSpot2 - vecSpot1); + + // throw at a constant time + float time = vecGrenadeVel.Length( ) / flSpeed; + vecGrenadeVel = vecGrenadeVel * (1.0 / time); + + // adjust upward toss to compensate for gravity loss + vecGrenadeVel.z += flGravity * time * 0.5; + + Vector vecApex = vecSpot1 + (vecSpot2 - vecSpot1) * 0.5; + vecApex.z += 0.5 * flGravity * (time * 0.5) * (time * 0.5); + + TraceResult tr; + UTIL_TraceLine(vecSpot1, vecApex, dont_ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + UTIL_TraceLine(vecSpot2, vecApex, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + return vecGrenadeVel; +} + + diff --git a/hlsdk/dlls/h_battery.cpp b/hlsdk/dlls/h_battery.cpp new file mode 100644 index 00000000..6f2e07ad --- /dev/null +++ b/hlsdk/dlls/h_battery.cpp @@ -0,0 +1,200 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== h_battery.cpp ======================================================== + + battery-related code + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "skill.h" +#include "gamerules.h" + +class CRecharge : public CBaseToggle +{ +public: + void Spawn( ); + void Precache( void ); + void EXPORT Off(void); + void EXPORT Recharge(void); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return (CBaseToggle :: ObjectCaps() | FCAP_CONTINUOUS_USE) & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flNextCharge; + int m_iReactivate ; // DeathMatch Delay until reactvated + int m_iJuice; + int m_iOn; // 0 = off, 1 = startup, 2 = going + float m_flSoundTime; +}; + +TYPEDESCRIPTION CRecharge::m_SaveData[] = +{ + DEFINE_FIELD( CRecharge, m_flNextCharge, FIELD_TIME ), + DEFINE_FIELD( CRecharge, m_iReactivate, FIELD_INTEGER), + DEFINE_FIELD( CRecharge, m_iJuice, FIELD_INTEGER), + DEFINE_FIELD( CRecharge, m_iOn, FIELD_INTEGER), + DEFINE_FIELD( CRecharge, m_flSoundTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CRecharge, CBaseEntity ); + +LINK_ENTITY_TO_CLASS(func_recharge, CRecharge); + + +void CRecharge::KeyValue( KeyValueData *pkvd ) +{ + 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 if (FStrEq(pkvd->szKeyName, "dmdelay")) + { + m_iReactivate = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CRecharge::Spawn() +{ + Precache( ); + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); // set size and link into world + UTIL_SetSize(pev, pev->mins, pev->maxs); + SET_MODEL(ENT(pev), STRING(pev->model) ); + m_iJuice = static_cast(gSkillData.suitchargerCapacity); + pev->frame = 0; +} + +void CRecharge::Precache() +{ + PRECACHE_SOUND("items/suitcharge1.wav"); + PRECACHE_SOUND("items/suitchargeno1.wav"); + PRECACHE_SOUND("items/suitchargeok1.wav"); +} + + +void CRecharge::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // if it's not a player, ignore + if (!FClassnameIs(pActivator->pev, "player")) + return; + + // if there is no juice left, turn it off + if (m_iJuice <= 0) + { + pev->frame = 1; + Off(); + } + + // if the player doesn't have the suit, or there is no juice left, make the deny noise + if ((m_iJuice <= 0) || (!(pActivator->pev->weapons & (1<time) + { + m_flSoundTime = gpGlobals->time + 0.62; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/suitchargeno1.wav", 0.85, ATTN_NORM ); + } + return; + } + + pev->nextthink = pev->ltime + 0.25; + SetThink(&CRecharge::Off); + + // Time to recharge yet? + + if (m_flNextCharge >= gpGlobals->time) + return; + + // Make sure that we have a caller + if (!pActivator) + return; + + m_hActivator = pActivator; + + //only recharge the player + + if (!m_hActivator->IsPlayer() ) + return; + + // Play the on sound or the looping charging sound + if (!m_iOn) + { + m_iOn++; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/suitchargeok1.wav", 0.85, ATTN_NORM ); + m_flSoundTime = 0.56 + gpGlobals->time; + } + if ((m_iOn == 1) && (m_flSoundTime <= gpGlobals->time)) + { + m_iOn++; + EMIT_SOUND(ENT(pev), CHAN_STATIC, "items/suitcharge1.wav", 0.85, ATTN_NORM ); + } + + + // charge the player + if (m_hActivator->pev->armorvalue < 100) + { + m_iJuice--; + m_hActivator->pev->armorvalue += 1; + + if (m_hActivator->pev->armorvalue > 100) + m_hActivator->pev->armorvalue = 100; + } + + // govern the rate of charge + m_flNextCharge = gpGlobals->time + 0.1; +} + +void CRecharge::Recharge(void) +{ + m_iJuice = static_cast(gSkillData.suitchargerCapacity); + pev->frame = 0; + SetThink( &CBaseEntity::SUB_DoNothing ); +} + +void CRecharge::Off(void) +{ + // Stop looping sound. + if (m_iOn > 1) + STOP_SOUND( ENT(pev), CHAN_STATIC, "items/suitcharge1.wav" ); + + m_iOn = 0; + + if ((!m_iJuice) && ( ( m_iReactivate = static_cast(g_pGameRules->FlHEVChargerRechargeTime()) ) > 0) ) + { + pev->nextthink = pev->ltime + m_iReactivate; + SetThink(&CRecharge::Recharge); + } + else + SetThink( &CBaseEntity::SUB_DoNothing ); +} diff --git a/hlsdk/dlls/h_cycler.cpp b/hlsdk/dlls/h_cycler.cpp new file mode 100644 index 00000000..47a5e54c --- /dev/null +++ b/hlsdk/dlls/h_cycler.cpp @@ -0,0 +1,471 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== h_cycler.cpp ======================================================== + + The Halflife Cycler Monsters + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "animation.h" +#include "weapons.h" +#include "player.h" + + +#define TEMP_FOR_SCREEN_SHOTS +#ifdef TEMP_FOR_SCREEN_SHOTS //=================================================== + +class CCycler : public CBaseMonster +{ +public: + void GenericCyclerSpawn(char *szModel, Vector vecMin, Vector vecMax); + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() | FCAP_IMPULSE_USE); } + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void Spawn( void ); + void Think( void ); + //void Pain( float flDamage ); + void Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + // Don't treat as a live target + virtual BOOL IsAlive( void ) { return FALSE; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_animate; +}; + +TYPEDESCRIPTION CCycler::m_SaveData[] = +{ + DEFINE_FIELD( CCycler, m_animate, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CCycler, CBaseMonster ); + + +// +// we should get rid of all the other cyclers and replace them with this. +// +class CGenericCycler : public CCycler +{ +public: + void Spawn( void ) { GenericCyclerSpawn( (char *)STRING(pev->model), Vector(-16, -16, 0), Vector(16, 16, 72) ); } +}; +LINK_ENTITY_TO_CLASS( cycler, CGenericCycler ); + + + +// Probe droid imported for tech demo compatibility +// +// PROBE DROID +// +class CCyclerProbe : public CCycler +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS( cycler_prdroid, CCyclerProbe ); +void CCyclerProbe :: Spawn( void ) +{ + pev->origin = pev->origin + Vector ( 0, 0, 16 ); + GenericCyclerSpawn( "models/prdroid.mdl", Vector(-16,-16,-16), Vector(16,16,16)); +} + + + +// Cycler member functions + +void CCycler :: GenericCyclerSpawn(char *szModel, Vector vecMin, Vector vecMax) +{ + if (!szModel || !*szModel) + { + ALERT(at_error, "cycler at %.0f %.0f %0.f missing modelname", pev->origin.x, pev->origin.y, pev->origin.z ); + REMOVE_ENTITY(ENT(pev)); + return; + } + + pev->classname = MAKE_STRING("cycler"); + PRECACHE_MODEL( szModel ); + SET_MODEL(ENT(pev), szModel); + + CCycler::Spawn( ); + + UTIL_SetSize(pev, vecMin, vecMax); +} + + +void CCycler :: Spawn( ) +{ + InitBoneControllers(); + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_NONE; + pev->takedamage = DAMAGE_YES; + pev->effects = 0; + pev->health = 80000;// no cycler should die + pev->yaw_speed = 5; + pev->ideal_yaw = pev->angles.y; + ChangeYaw( 360 ); + + m_flFrameRate = 75; + m_flGroundSpeed = 0; + + pev->nextthink += 1.0; + + ResetSequenceInfo( ); + + if (pev->sequence != 0 || pev->frame != 0) + { + m_animate = 0; + pev->framerate = 0; + } + else + { + m_animate = 1; + } +} + + + + +// +// cycler think +// +void CCycler :: Think( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + if (m_animate) + { + StudioFrameAdvance ( ); + } + if (m_fSequenceFinished && !m_fSequenceLoops) + { + // ResetSequenceInfo(); + // hack to avoid reloading model every frame + pev->animtime = gpGlobals->time; + pev->framerate = 1.0; + m_fSequenceFinished = FALSE; + m_flLastEventCheck = gpGlobals->time; + pev->frame = 0; + if (!m_animate) + pev->framerate = 0.0; // FIX: don't reset framerate + } +} + +// +// CyclerUse - starts a rotation trend +// +void CCycler :: Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_animate = !m_animate; + if (m_animate) + pev->framerate = 1.0; + else + pev->framerate = 0.0; +} + +// +// CyclerPain , changes sequences when shot +// +//void CCycler :: Pain( float flDamage ) +int CCycler :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if (m_animate) + { + pev->sequence++; + + ResetSequenceInfo( ); + + if (m_flFrameRate == 0.0) + { + pev->sequence = 0; + ResetSequenceInfo( ); + } + pev->frame = 0; + } + else + { + pev->framerate = 1.0; + StudioFrameAdvance ( 0.1 ); + pev->framerate = 0; + ALERT( at_console, "sequence: %d, frame %.0f\n", pev->sequence, pev->frame ); + } + + return 0; +} + +#endif + + +class CCyclerSprite : public CBaseEntity +{ +public: + void Spawn( void ); + void Think( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() | FCAP_DONT_SAVE | FCAP_IMPULSE_USE); } + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void Animate( float frames ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + inline int ShouldAnimate( void ) { return m_animate && m_maxFrame > 1.0; } + int m_animate; + float m_lastTime; + float m_maxFrame; +}; + +LINK_ENTITY_TO_CLASS( cycler_sprite, CCyclerSprite ); + +TYPEDESCRIPTION CCyclerSprite::m_SaveData[] = +{ + DEFINE_FIELD( CCyclerSprite, m_animate, FIELD_INTEGER ), + DEFINE_FIELD( CCyclerSprite, m_lastTime, FIELD_TIME ), + DEFINE_FIELD( CCyclerSprite, m_maxFrame, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CCyclerSprite, CBaseEntity ); + + +void CCyclerSprite::Spawn( void ) +{ + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_NONE; + pev->takedamage = DAMAGE_YES; + pev->effects = 0; + + pev->frame = 0; + pev->nextthink = gpGlobals->time + 0.1; + m_animate = 1; + m_lastTime = gpGlobals->time; + + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; +} + + +void CCyclerSprite::Think( void ) +{ + if ( ShouldAnimate() ) + Animate( pev->framerate * (gpGlobals->time - m_lastTime) ); + + pev->nextthink = gpGlobals->time + 0.1; + m_lastTime = gpGlobals->time; +} + + +void CCyclerSprite::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_animate = !m_animate; + ALERT( at_console, "Sprite: %s\n", STRING(pev->model) ); +} + + +int CCyclerSprite::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( m_maxFrame > 1.0 ) + { + Animate( 1.0 ); + } + return 1; +} + +void CCyclerSprite::Animate( float frames ) +{ + pev->frame += frames; + if ( m_maxFrame > 0 ) + pev->frame = fmod( pev->frame, m_maxFrame ); +} + + + + + + + +class CWeaponCycler : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + int iItemSlot( void ) { return 1; } + int GetItemInfo(ItemInfo *p) {return 0; } + + void PrimaryAttack( void ); + void SecondaryAttack( void ); + BOOL Deploy( void ); + void Holster( int skiplocal = 0 ); + int m_iszModel; + int m_iModel; +}; +LINK_ENTITY_TO_CLASS( cycler_weapon, CWeaponCycler ); + + +void CWeaponCycler::Spawn( ) +{ + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_NONE; + + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL( ENT(pev), STRING(pev->model) ); + m_iszModel = pev->model; + m_iModel = pev->modelindex; + + UTIL_SetOrigin( pev, pev->origin ); + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 16)); + SetTouch( &CBasePlayerItem::DefaultTouch ); +} + + + +BOOL CWeaponCycler::Deploy( ) +{ + m_pPlayer->pev->viewmodel = m_iszModel; + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.0; + SendWeaponAnim( 0 ); + m_iClip = 0; + return TRUE; +} + + +void CWeaponCycler::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; +} + + +void CWeaponCycler::PrimaryAttack() +{ + + SendWeaponAnim( pev->sequence ); + + m_flNextPrimaryAttack = gpGlobals->time + 0.3; +} + + +void CWeaponCycler::SecondaryAttack( void ) +{ + float flFrameRate, flGroundSpeed; + + pev->sequence = (pev->sequence + 1) % 8; + + pev->modelindex = m_iModel; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + GetSequenceInfo( pmodel, pev, &flFrameRate, &flGroundSpeed ); + pev->modelindex = 0; + + if (flFrameRate == 0.0) + { + pev->sequence = 0; + } + + SendWeaponAnim( pev->sequence ); + + m_flNextSecondaryAttack = gpGlobals->time + 0.3; +} + + + +// Flaming Wreakage +class CWreckage : public CBaseMonster +{ + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + void Think( void ); + + int m_flStartTime; +}; +TYPEDESCRIPTION CWreckage::m_SaveData[] = +{ + DEFINE_FIELD( CWreckage, m_flStartTime, FIELD_TIME ), +}; +IMPLEMENT_SAVERESTORE( CWreckage, CBaseMonster ); + + +LINK_ENTITY_TO_CLASS( cycler_wreckage, CWreckage ); + +void CWreckage::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->takedamage = 0; + pev->effects = 0; + + pev->frame = 0; + pev->nextthink = gpGlobals->time + 0.1; + + if (pev->model) + { + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL( ENT(pev), STRING(pev->model) ); + } + // pev->scale = 5.0; + + m_flStartTime = static_cast(gpGlobals->time); +} + +void CWreckage::Precache( ) +{ + if ( pev->model ) + PRECACHE_MODEL( (char *)STRING(pev->model) ); +} + +void CWreckage::Think( void ) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.2; + + if (pev->dmgtime) + { + if (pev->dmgtime < gpGlobals->time) + { + UTIL_Remove( this ); + return; + } + else if (RANDOM_FLOAT( 0, pev->dmgtime - m_flStartTime ) > pev->dmgtime - gpGlobals->time) + { + return; + } + } + + Vector VecSrc; + + VecSrc.x = RANDOM_FLOAT( pev->absmin.x, pev->absmax.x ); + VecSrc.y = RANDOM_FLOAT( pev->absmin.y, pev->absmax.y ); + VecSrc.z = RANDOM_FLOAT( pev->absmin.z, pev->absmax.z ); + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, VecSrc ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( VecSrc.x ); + WRITE_COORD( VecSrc.y ); + WRITE_COORD( VecSrc.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( RANDOM_LONG(0,49) + 50 ); // scale * 10 + WRITE_BYTE( RANDOM_LONG(0, 3) + 8 ); // framerate + MESSAGE_END(); +} diff --git a/hlsdk/dlls/h_export.cpp b/hlsdk/dlls/h_export.cpp new file mode 100644 index 00000000..e7cdc7c1 --- /dev/null +++ b/hlsdk/dlls/h_export.cpp @@ -0,0 +1,69 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== h_export.cpp ======================================================== + + Entity classes exported by Halflife. + +*/ + +#include "extdll.h" +#include "util.h" + +#include "cbase.h" + +// Holds engine functionality callbacks +enginefuncs_t g_engfuncs; +globalvars_t *gpGlobals; + + +#ifdef _WIN32 + +// Required DLL entry point +BOOL WINAPI DllMain( + HINSTANCE hinstDLL, + DWORD fdwReason, + LPVOID lpvReserved) +{ + if (fdwReason == DLL_PROCESS_ATTACH) + { + } + else if (fdwReason == DLL_PROCESS_DETACH) + { + } + return TRUE; +} + +void DLLEXPORT GiveFnptrsToDll( enginefuncs_t* pengfuncsFromEngine, globalvars_t *pGlobals ) +{ + memcpy(&g_engfuncs, pengfuncsFromEngine, sizeof(enginefuncs_t)); + gpGlobals = pGlobals; +} + + +#else + +extern "C" { + +void GiveFnptrsToDll( enginefuncs_t* pengfuncsFromEngine, globalvars_t *pGlobals ) +{ + memcpy(&g_engfuncs, pengfuncsFromEngine, sizeof(enginefuncs_t)); + gpGlobals = pGlobals; +} + +} + +#endif diff --git a/hlsdk/dlls/handgrenade.cpp b/hlsdk/dlls/handgrenade.cpp new file mode 100644 index 00000000..61285526 --- /dev/null +++ b/hlsdk/dlls/handgrenade.cpp @@ -0,0 +1,233 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" + + +#define HANDGRENADE_PRIMARY_VOLUME 450 + +enum handgrenade_e { + HANDGRENADE_IDLE = 0, + HANDGRENADE_FIDGET, + HANDGRENADE_PINPULL, + HANDGRENADE_THROW1, // toss + HANDGRENADE_THROW2, // medium + HANDGRENADE_THROW3, // hard + HANDGRENADE_HOLSTER, + HANDGRENADE_DRAW +}; + + +LINK_ENTITY_TO_CLASS( weapon_handgrenade, CHandGrenade ); + + +void CHandGrenade::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_HANDGRENADE; + SET_MODEL(ENT(pev), "models/w_grenade.mdl"); + +#ifndef CLIENT_DLL + pev->dmg = gSkillData.plrDmgHandGrenade; +#endif + + m_iDefaultAmmo = HANDGRENADE_DEFAULT_GIVE; + + FallInit();// get ready to fall down. +} + + +void CHandGrenade::Precache( void ) +{ + PRECACHE_MODEL("models/w_grenade.mdl"); + PRECACHE_MODEL("models/v_grenade.mdl"); + PRECACHE_MODEL("models/p_grenade.mdl"); +} + +int CHandGrenade::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "Hand Grenade"; + p->iMaxAmmo1 = HANDGRENADE_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 4; + p->iPosition = 0; + p->iId = m_iId = WEAPON_HANDGRENADE; + p->iWeight = HANDGRENADE_WEIGHT; + p->iFlags = ITEM_FLAG_LIMITINWORLD | ITEM_FLAG_EXHAUSTIBLE; + + return 1; +} + + +BOOL CHandGrenade::Deploy( ) +{ + m_flReleaseThrow = -1; + return DefaultDeploy( "models/v_grenade.mdl", "models/p_grenade.mdl", HANDGRENADE_DRAW, "crowbar" ); +} + +BOOL CHandGrenade::CanHolster( void ) +{ + // can only holster hand grenades when not primed! + return ( m_flStartThrow == 0 ); +} + +void CHandGrenade::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + + if ( m_pPlayer->m_rgAmmo[ m_iPrimaryAmmoType ] ) + { + SendWeaponAnim( HANDGRENADE_HOLSTER ); + } + else + { + // no more grenades! + m_pPlayer->pev->weapons &= ~(1<nextthink = gpGlobals->time + 0.1; + } + + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "common/null.wav", 1.0, ATTN_NORM); +} + +void CHandGrenade::PrimaryAttack() +{ + if ( !m_flStartThrow && m_pPlayer->m_rgAmmo[ m_iPrimaryAmmoType ] > 0 ) + { + m_flStartThrow = gpGlobals->time; + m_flReleaseThrow = 0; + + SendWeaponAnim( HANDGRENADE_PINPULL ); + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.5; + } +} + + +void CHandGrenade::WeaponIdle( void ) +{ + if ( m_flReleaseThrow == 0 && m_flStartThrow ) + m_flReleaseThrow = gpGlobals->time; + + if ( m_flTimeWeaponIdle > UTIL_WeaponTimeBase() ) + return; + + if ( m_flStartThrow ) + { + Vector angThrow = m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle; + + if ( angThrow.x < 0 ) + angThrow.x = -10 + angThrow.x * ( ( 90 - 10 ) / 90.0 ); + else + angThrow.x = -10 + angThrow.x * ( ( 90 + 10 ) / 90.0 ); + + float flVel = ( 90 - angThrow.x ) * 4; + if ( flVel > 500 ) + flVel = 500; + + UTIL_MakeVectors( angThrow ); + + Vector vecSrc = m_pPlayer->pev->origin + m_pPlayer->pev->view_ofs + gpGlobals->v_forward * 16; + + Vector vecThrow = gpGlobals->v_forward * flVel + m_pPlayer->pev->velocity; + + // alway explode 3 seconds after the pin was pulled + float time = m_flStartThrow - gpGlobals->time + 3.0; + if (time < 0) + time = 0; + + CGrenade::ShootTimed( m_pPlayer->pev, vecSrc, vecThrow, time ); + + if ( flVel < 500 ) + { + SendWeaponAnim( HANDGRENADE_THROW1 ); + } + else if ( flVel < 1000 ) + { + SendWeaponAnim( HANDGRENADE_THROW2 ); + } + else + { + SendWeaponAnim( HANDGRENADE_THROW3 ); + } + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_flReleaseThrow = 0; + m_flStartThrow = 0; + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.5; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.5; + + m_pPlayer->m_rgAmmo[ m_iPrimaryAmmoType ]--; + + if ( !m_pPlayer->m_rgAmmo[ m_iPrimaryAmmoType ] ) + { + // just threw last grenade + // set attack times in the future, and weapon idle in the future so we can see the whole throw + // animation, weapon idle will automatically retire the weapon for us. + m_flTimeWeaponIdle = m_flNextSecondaryAttack = m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.5;// ensure that the animation can finish playing + } + return; + } + else if ( m_flReleaseThrow > 0 ) + { + // we've finished the throw, restart. + m_flStartThrow = 0; + + if ( m_pPlayer->m_rgAmmo[ m_iPrimaryAmmoType ] ) + { + SendWeaponAnim( HANDGRENADE_DRAW ); + } + else + { + RetireWeapon(); + return; + } + + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + m_flReleaseThrow = -1; + return; + } + + if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] ) + { + int iAnim; + float flRand = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 0, 1 ); + if (flRand <= 0.75) + { + iAnim = HANDGRENADE_IDLE; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 );// how long till we do this again. + } + else + { + iAnim = HANDGRENADE_FIDGET; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 75.0 / 30.0; + } + + SendWeaponAnim( iAnim ); + } +} + + + + diff --git a/hlsdk/dlls/healthkit.cpp b/hlsdk/dlls/healthkit.cpp new file mode 100644 index 00000000..aa50083b --- /dev/null +++ b/hlsdk/dlls/healthkit.cpp @@ -0,0 +1,264 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "items.h" +#include "gamerules.h" + +extern int gmsgItemPickup; + +class CHealthKit : public CItem +{ + void Spawn( void ); + void Precache( void ); + BOOL MyTouch( CBasePlayer *pPlayer ); + +/* + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; +*/ + +}; + + +LINK_ENTITY_TO_CLASS( item_healthkit, CHealthKit ); + +/* +TYPEDESCRIPTION CHealthKit::m_SaveData[] = +{ + +}; + + +IMPLEMENT_SAVERESTORE( CHealthKit, CItem); +*/ + +void CHealthKit :: Spawn( void ) +{ + Precache( ); + SET_MODEL(ENT(pev), "models/w_medkit.mdl"); + + CItem::Spawn(); +} + +void CHealthKit::Precache( void ) +{ + PRECACHE_MODEL("models/w_medkit.mdl"); + PRECACHE_SOUND("items/smallmedkit1.wav"); +} + +BOOL CHealthKit::MyTouch( CBasePlayer *pPlayer ) +{ + if ( pPlayer->pev->deadflag != DEAD_NO ) + { + return FALSE; + } + + if ( pPlayer->TakeHealth( gSkillData.healthkitCapacity, DMG_GENERIC ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgItemPickup, NULL, pPlayer->pev ); + WRITE_STRING( STRING(pev->classname) ); + MESSAGE_END(); + + EMIT_SOUND(ENT(pPlayer->pev), CHAN_ITEM, "items/smallmedkit1.wav", 1, ATTN_NORM); + + if ( g_pGameRules->ItemShouldRespawn( this ) ) + { + Respawn(); + } + else + { + UTIL_Remove(this); + } + + return TRUE; + } + + return FALSE; +} + + + +//------------------------------------------------------------- +// Wall mounted health kit +//------------------------------------------------------------- +class CWallHealth : public CBaseToggle +{ +public: + void Spawn( ); + void Precache( void ); + void EXPORT Off(void); + void EXPORT Recharge(void); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return (CBaseToggle :: ObjectCaps() | FCAP_CONTINUOUS_USE) & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flNextCharge; + int m_iReactivate ; // DeathMatch Delay until reactvated + int m_iJuice; + int m_iOn; // 0 = off, 1 = startup, 2 = going + float m_flSoundTime; +}; + +TYPEDESCRIPTION CWallHealth::m_SaveData[] = +{ + DEFINE_FIELD( CWallHealth, m_flNextCharge, FIELD_TIME), + DEFINE_FIELD( CWallHealth, m_iReactivate, FIELD_INTEGER), + DEFINE_FIELD( CWallHealth, m_iJuice, FIELD_INTEGER), + DEFINE_FIELD( CWallHealth, m_iOn, FIELD_INTEGER), + DEFINE_FIELD( CWallHealth, m_flSoundTime, FIELD_TIME), +}; + +IMPLEMENT_SAVERESTORE( CWallHealth, CBaseEntity ); + +LINK_ENTITY_TO_CLASS(func_healthcharger, CWallHealth); + + +void CWallHealth::KeyValue( KeyValueData *pkvd ) +{ + 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 if (FStrEq(pkvd->szKeyName, "dmdelay")) + { + m_iReactivate = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CWallHealth::Spawn() +{ + Precache( ); + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); // set size and link into world + UTIL_SetSize(pev, pev->mins, pev->maxs); + SET_MODEL(ENT(pev), STRING(pev->model) ); + m_iJuice = static_cast(gSkillData.healthchargerCapacity); + pev->frame = 0; + +} + +void CWallHealth::Precache() +{ + PRECACHE_SOUND("items/medshot4.wav"); + PRECACHE_SOUND("items/medshotno1.wav"); + PRECACHE_SOUND("items/medcharge4.wav"); +} + + +void CWallHealth::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Make sure that we have a caller + if (!pActivator) + return; + // if it's not a player, ignore + if ( !pActivator->IsPlayer() ) + return; + + // if there is no juice left, turn it off + if (m_iJuice <= 0) + { + pev->frame = 1; + Off(); + } + + // if the player doesn't have the suit, or there is no juice left, make the deny noise + if ((m_iJuice <= 0) || (!(pActivator->pev->weapons & (1<time) + { + m_flSoundTime = gpGlobals->time + 0.62; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/medshotno1.wav", 1.0, ATTN_NORM ); + } + return; + } + + pev->nextthink = pev->ltime + 0.25; + SetThink(&CWallHealth::Off); + + // Time to recharge yet? + + if (m_flNextCharge >= gpGlobals->time) + return; + + // Play the on sound or the looping charging sound + if (!m_iOn) + { + m_iOn++; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/medshot4.wav", 1.0, ATTN_NORM ); + m_flSoundTime = 0.56 + gpGlobals->time; + } + if ((m_iOn == 1) && (m_flSoundTime <= gpGlobals->time)) + { + m_iOn++; + EMIT_SOUND(ENT(pev), CHAN_STATIC, "items/medcharge4.wav", 1.0, ATTN_NORM ); + } + + + // charge the player + if ( pActivator->TakeHealth( 1, DMG_GENERIC ) ) + { + m_iJuice--; + } + + // govern the rate of charge + m_flNextCharge = gpGlobals->time + 0.1; +} + +void CWallHealth::Recharge(void) +{ + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/medshot4.wav", 1.0, ATTN_NORM ); + m_iJuice = static_cast(gSkillData.healthchargerCapacity); + pev->frame = 0; + SetThink( &CBaseEntity::SUB_DoNothing ); +} + +void CWallHealth::Off(void) +{ + // Stop looping sound. + if (m_iOn > 1) + STOP_SOUND( ENT(pev), CHAN_STATIC, "items/medcharge4.wav" ); + + m_iOn = 0; + + if ((!m_iJuice) && ( ( m_iReactivate = static_cast(g_pGameRules->FlHealthChargerRechargeTime()) ) > 0) ) + { + pev->nextthink = pev->ltime + m_iReactivate; + SetThink(&CWallHealth::Recharge); + } + else + SetThink( &CBaseEntity::SUB_DoNothing ); +} diff --git a/hlsdk/dlls/hornet.cpp b/hlsdk/dlls/hornet.cpp new file mode 100644 index 00000000..edd6b530 --- /dev/null +++ b/hlsdk/dlls/hornet.cpp @@ -0,0 +1,419 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// Hornets +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "soundent.h" +#include "hornet.h" +#include "gamerules.h" + + +int iHornetTrail; +int iHornetPuff; + +LINK_ENTITY_TO_CLASS( hornet, CHornet ); + +//========================================================= +// Save/Restore +//========================================================= +TYPEDESCRIPTION CHornet::m_SaveData[] = +{ + DEFINE_FIELD( CHornet, m_flStopAttack, FIELD_TIME ), + DEFINE_FIELD( CHornet, m_iHornetType, FIELD_INTEGER ), + DEFINE_FIELD( CHornet, m_flFlySpeed, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CHornet, CBaseMonster ); + +//========================================================= +// don't let hornets gib, ever. +//========================================================= +int CHornet :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // filter these bits a little. + bitsDamageType &= ~ ( DMG_ALWAYSGIB ); + bitsDamageType |= DMG_NEVERGIB; + + return CBaseMonster :: TakeDamage ( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +//========================================================= +//========================================================= +void CHornet :: Spawn( void ) +{ + Precache(); + + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + pev->takedamage = DAMAGE_YES; + pev->flags |= FL_MONSTER; + pev->health = 1;// weak! + + if ( g_pGameRules->IsMultiplayer() ) + { + // hornets don't live as long in multiplayer + m_flStopAttack = gpGlobals->time + 3.5; + } + else + { + m_flStopAttack = gpGlobals->time + 5.0; + } + + m_flFieldOfView = 0.9; // +- 25 degrees + + if ( RANDOM_LONG ( 1, 5 ) <= 2 ) + { + m_iHornetType = HORNET_TYPE_RED; + m_flFlySpeed = HORNET_RED_SPEED; + } + else + { + m_iHornetType = HORNET_TYPE_ORANGE; + m_flFlySpeed = HORNET_ORANGE_SPEED; + } + + SET_MODEL(ENT( pev ), "models/hornet.mdl"); + UTIL_SetSize( pev, Vector( -4, -4, -4 ), Vector( 4, 4, 4 ) ); + + SetTouch( &CHornet::DieTouch ); + SetThink( &CHornet::StartTrack ); + + edict_t *pSoundEnt = pev->owner; + if ( !pSoundEnt ) + pSoundEnt = edict(); + + if ( !FNullEnt(pev->owner) && (pev->owner->v.flags & FL_CLIENT) ) + { + pev->dmg = gSkillData.plrDmgHornet; + } + else + { + // no real owner, or owner isn't a client. + pev->dmg = gSkillData.monDmgHornet; + } + + pev->nextthink = gpGlobals->time + 0.1; + ResetSequenceInfo( ); +} + + +void CHornet :: Precache() +{ + PRECACHE_MODEL("models/hornet.mdl"); + + PRECACHE_SOUND( "agrunt/ag_fire1.wav" ); + PRECACHE_SOUND( "agrunt/ag_fire2.wav" ); + PRECACHE_SOUND( "agrunt/ag_fire3.wav" ); + + PRECACHE_SOUND( "hornet/ag_buzz1.wav" ); + PRECACHE_SOUND( "hornet/ag_buzz2.wav" ); + PRECACHE_SOUND( "hornet/ag_buzz3.wav" ); + + PRECACHE_SOUND( "hornet/ag_hornethit1.wav" ); + PRECACHE_SOUND( "hornet/ag_hornethit2.wav" ); + PRECACHE_SOUND( "hornet/ag_hornethit3.wav" ); + + iHornetPuff = PRECACHE_MODEL( "sprites/muz1.spr" ); + iHornetTrail = PRECACHE_MODEL("sprites/laserbeam.spr"); +} + +//========================================================= +// hornets will never get mad at each other, no matter who the owner is. +//========================================================= +int CHornet::IRelationship ( CBaseEntity *pTarget ) +{ + if ( pTarget->pev->modelindex == pev->modelindex ) + { + return R_NO; + } + + return CBaseMonster :: IRelationship( pTarget ); +} + +//========================================================= +// ID's Hornet as their owner +//========================================================= +int CHornet::Classify ( void ) +{ + + if ( pev->owner && pev->owner->v.flags & FL_CLIENT) + { + return CLASS_PLAYER_BIOWEAPON; + } + + return CLASS_ALIEN_BIOWEAPON; +} + +//========================================================= +// StartTrack - starts a hornet out tracking its target +//========================================================= +void CHornet :: StartTrack ( void ) +{ + IgniteTrail(); + + SetTouch( &CHornet::TrackTouch ); + SetThink( &CHornet::TrackTarget ); + + pev->nextthink = gpGlobals->time + 0.1; +} + +//========================================================= +// StartDart - starts a hornet out just flying straight. +//========================================================= +void CHornet :: StartDart ( void ) +{ + IgniteTrail(); + + SetTouch( &CHornet::DartTouch ); + + SetThink( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time + 4; +} + +void CHornet::IgniteTrail( void ) +{ +/* + + ted's suggested trail colors: + +r161 +g25 +b97 + +r173 +g39 +b14 + +old colors + case HORNET_TYPE_RED: + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 128 ); // r, g, b + WRITE_BYTE( 0 ); // r, g, b + break; + case HORNET_TYPE_ORANGE: + WRITE_BYTE( 0 ); // r, g, b + WRITE_BYTE( 100 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + break; + +*/ + + // trail + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMFOLLOW ); + WRITE_SHORT( entindex() ); // entity + WRITE_SHORT( iHornetTrail ); // model + WRITE_BYTE( 10 ); // life + WRITE_BYTE( 2 ); // width + + switch ( m_iHornetType ) + { + case HORNET_TYPE_RED: + WRITE_BYTE( 179 ); // r, g, b + WRITE_BYTE( 39 ); // r, g, b + WRITE_BYTE( 14 ); // r, g, b + break; + case HORNET_TYPE_ORANGE: + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 128 ); // r, g, b + WRITE_BYTE( 0 ); // r, g, b + break; + } + + WRITE_BYTE( 128 ); // brightness + + MESSAGE_END(); +} + +//========================================================= +// Hornet is flying, gently tracking target +//========================================================= +void CHornet :: TrackTarget ( void ) +{ + Vector vecFlightDir; + Vector vecDirToEnemy; + float flDelta; + + StudioFrameAdvance( ); + + if (gpGlobals->time > m_flStopAttack) + { + SetTouch( NULL ); + SetThink( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; + return; + } + + // UNDONE: The player pointer should come back after returning from another level + if ( m_hEnemy == 0 ) + {// enemy is dead. + Look( 512 ); + m_hEnemy = BestVisibleEnemy( ); + } + + if ( m_hEnemy != 0 && FVisible( m_hEnemy )) + { + m_vecEnemyLKP = m_hEnemy->BodyTarget( pev->origin ); + } + else + { + m_vecEnemyLKP = m_vecEnemyLKP + pev->velocity * m_flFlySpeed * 0.1; + } + + vecDirToEnemy = ( m_vecEnemyLKP - pev->origin ).Normalize(); + + if (pev->velocity.Length() < 0.1) + vecFlightDir = vecDirToEnemy; + else + vecFlightDir = pev->velocity.Normalize(); + + // measure how far the turn is, the wider the turn, the slow we'll go this time. + flDelta = DotProduct ( vecFlightDir, vecDirToEnemy ); + + if ( flDelta < 0.5 ) + {// hafta turn wide again. play sound + switch (RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_buzz1.wav", HORNET_BUZZ_VOLUME, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_buzz2.wav", HORNET_BUZZ_VOLUME, ATTN_NORM); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_buzz3.wav", HORNET_BUZZ_VOLUME, ATTN_NORM); break; + } + } + + if ( flDelta <= 0 && m_iHornetType == HORNET_TYPE_RED ) + {// no flying backwards, but we don't want to invert this, cause we'd go fast when we have to turn REAL far. + flDelta = 0.25; + } + + pev->velocity = ( vecFlightDir + vecDirToEnemy).Normalize(); + + if ( pev->owner && (pev->owner->v.flags & FL_MONSTER) ) + { + // random pattern only applies to hornets fired by monsters, not players. + + pev->velocity.x += RANDOM_FLOAT ( -0.10, 0.10 );// scramble the flight dir a bit. + pev->velocity.y += RANDOM_FLOAT ( -0.10, 0.10 ); + pev->velocity.z += RANDOM_FLOAT ( -0.10, 0.10 ); + } + + switch ( m_iHornetType ) + { + case HORNET_TYPE_RED: + pev->velocity = pev->velocity * ( m_flFlySpeed * flDelta );// scale the dir by the ( speed * width of turn ) + pev->nextthink = gpGlobals->time + RANDOM_FLOAT( 0.1, 0.3 ); + break; + case HORNET_TYPE_ORANGE: + pev->velocity = pev->velocity * m_flFlySpeed;// do not have to slow down to turn. + pev->nextthink = gpGlobals->time + 0.1;// fixed think time + break; + } + + pev->angles = UTIL_VecToAngles (pev->velocity); + + pev->solid = SOLID_BBOX; + + // if hornet is close to the enemy, jet in a straight line for a half second. + // (only in the single player game) + if ( m_hEnemy != 0 && !g_pGameRules->IsMultiplayer() ) + { + if ( flDelta >= 0.4 && ( pev->origin - m_vecEnemyLKP ).Length() <= 300 ) + { + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SPRITE ); + WRITE_COORD( pev->origin.x); // pos + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z); + WRITE_SHORT( iHornetPuff ); // model + // WRITE_BYTE( 0 ); // life * 10 + WRITE_BYTE( 2 ); // size * 10 + WRITE_BYTE( 128 ); // brightness + MESSAGE_END(); + + switch (RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_buzz1.wav", HORNET_BUZZ_VOLUME, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_buzz2.wav", HORNET_BUZZ_VOLUME, ATTN_NORM); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_buzz3.wav", HORNET_BUZZ_VOLUME, ATTN_NORM); break; + } + pev->velocity = pev->velocity * 2; + pev->nextthink = gpGlobals->time + 1.0; + // don't attack again + m_flStopAttack = gpGlobals->time; + } + } +} + +//========================================================= +// Tracking Hornet hit something +//========================================================= +void CHornet :: TrackTouch ( CBaseEntity *pOther ) +{ + if ( pOther->edict() == pev->owner || pOther->pev->modelindex == pev->modelindex ) + {// bumped into the guy that shot it. + pev->solid = SOLID_NOT; + return; + } + + if ( IRelationship( pOther ) <= R_NO ) + { + // hit something we don't want to hurt, so turn around. + + pev->velocity = pev->velocity.Normalize(); + + pev->velocity.x *= -1; + pev->velocity.y *= -1; + + pev->origin = pev->origin + pev->velocity * 4; // bounce the hornet off a bit. + pev->velocity = pev->velocity * m_flFlySpeed; + + return; + } + + DieTouch( pOther ); +} + +void CHornet::DartTouch( CBaseEntity *pOther ) +{ + DieTouch( pOther ); +} + +void CHornet::DieTouch ( CBaseEntity *pOther ) +{ + if ( pOther && pOther->pev->takedamage ) + {// do the damage + + switch (RANDOM_LONG(0,2)) + {// buzz when you plug someone + case 0: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_hornethit1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_hornethit2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_hornethit3.wav", 1, ATTN_NORM); break; + } + + pOther->TakeDamage( pev, VARS( pev->owner ), pev->dmg, DMG_BULLET ); + } + + pev->modelindex = 0;// so will disappear for the 0.1 secs we wait until NEXTTHINK gets rid + pev->solid = SOLID_NOT; + + SetThink ( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time + 1;// stick around long enough for the sound to finish! +} + diff --git a/hlsdk/dlls/hornet.h b/hlsdk/dlls/hornet.h new file mode 100644 index 00000000..98d1710f --- /dev/null +++ b/hlsdk/dlls/hornet.h @@ -0,0 +1,58 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// Hornets +//========================================================= + +//========================================================= +// Hornet Defines +//========================================================= +#define HORNET_TYPE_RED 0 +#define HORNET_TYPE_ORANGE 1 +#define HORNET_RED_SPEED (float)600 +#define HORNET_ORANGE_SPEED (float)800 +#define HORNET_BUZZ_VOLUME (float)0.8 + +extern int iHornetPuff; + +//========================================================= +// Hornet - this is the projectile that the Alien Grunt fires. +//========================================================= +class CHornet : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + int Classify ( void ); + int IRelationship ( CBaseEntity *pTarget ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void IgniteTrail( void ); + void EXPORT StartTrack ( void ); + void EXPORT StartDart ( void ); + void EXPORT TrackTarget ( void ); + void EXPORT TrackTouch ( CBaseEntity *pOther ); + void EXPORT DartTouch( CBaseEntity *pOther ); + void EXPORT DieTouch ( CBaseEntity *pOther ); + + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + float m_flStopAttack; + int m_iHornetType; + float m_flFlySpeed; +}; + diff --git a/hlsdk/dlls/hornetgun.cpp b/hlsdk/dlls/hornetgun.cpp new file mode 100644 index 00000000..60eed502 --- /dev/null +++ b/hlsdk/dlls/hornetgun.cpp @@ -0,0 +1,305 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "hornet.h" +#include "gamerules.h" + + +enum hgun_e { + HGUN_IDLE1 = 0, + HGUN_FIDGETSWAY, + HGUN_FIDGETSHAKE, + HGUN_DOWN, + HGUN_UP, + HGUN_SHOOT +}; + +enum firemode_e +{ + FIREMODE_TRACK = 0, + FIREMODE_FAST +}; + + +LINK_ENTITY_TO_CLASS( weapon_hornetgun, CHgun ); + +BOOL CHgun::IsUseable( void ) +{ + return TRUE; +} + +void CHgun::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_HORNETGUN; + SET_MODEL(ENT(pev), "models/w_hgun.mdl"); + + m_iDefaultAmmo = HIVEHAND_DEFAULT_GIVE; + m_iFirePhase = 0; + + FallInit();// get ready to fall down. +} + + +void CHgun::Precache( void ) +{ + PRECACHE_MODEL("models/v_hgun.mdl"); + PRECACHE_MODEL("models/w_hgun.mdl"); + PRECACHE_MODEL("models/p_hgun.mdl"); + + m_usHornetFire = PRECACHE_EVENT ( 1, "events/firehornet.sc" ); + + UTIL_PrecacheOther("hornet"); +} + +int CHgun::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + +#ifndef CLIENT_DLL + if ( g_pGameRules->IsMultiplayer() ) + { + // in multiplayer, all hivehands come full. + pPlayer->m_rgAmmo[ PrimaryAmmoIndex() ] = HORNET_MAX_CARRY; + } +#endif + + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + +int CHgun::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "Hornets"; + p->iMaxAmmo1 = HORNET_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 3; + p->iPosition = 3; + p->iId = m_iId = WEAPON_HORNETGUN; + p->iFlags = ITEM_FLAG_NOAUTOSWITCHEMPTY | ITEM_FLAG_NOAUTORELOAD; + p->iWeight = HORNETGUN_WEIGHT; + + return 1; +} + + +BOOL CHgun::Deploy( ) +{ + return DefaultDeploy( "models/v_hgun.mdl", "models/p_hgun.mdl", HGUN_UP, "hive" ); +} + +void CHgun::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + SendWeaponAnim( HGUN_DOWN ); + + //!!!HACKHACK - can't select hornetgun if it's empty! no way to get ammo for it, either. + if ( !m_pPlayer->m_rgAmmo[ PrimaryAmmoIndex() ] ) + { + m_pPlayer->m_rgAmmo[ PrimaryAmmoIndex() ] = 1; + } +} + + +void CHgun::PrimaryAttack() +{ + Reload( ); + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + { + return; + } + +#ifndef CLIENT_DLL + UTIL_MakeVectors( m_pPlayer->pev->v_angle ); + + CBaseEntity *pHornet = CBaseEntity::Create( "hornet", m_pPlayer->GetGunPosition( ) + gpGlobals->v_forward * 16 + gpGlobals->v_right * 8 + gpGlobals->v_up * -12, m_pPlayer->pev->v_angle, m_pPlayer->edict() ); + pHornet->pev->velocity = gpGlobals->v_forward * 300; + + m_flRechargeTime = gpGlobals->time + 0.5; +#endif + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + + m_pPlayer->m_iWeaponVolume = QUIET_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = DIM_GUN_FLASH; + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usHornetFire, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, FIREMODE_TRACK, 0, 0, 0 ); + + + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_flNextPrimaryAttack = m_flNextPrimaryAttack + 0.25; + + if (m_flNextPrimaryAttack < UTIL_WeaponTimeBase() ) + { + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.25; + } + + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); +} + + + +void CHgun::SecondaryAttack( void ) +{ + Reload(); + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + { + return; + } + + //Wouldn't be a bad idea to completely predict these, since they fly so fast... +#ifndef CLIENT_DLL + CBaseEntity *pHornet; + Vector vecSrc; + + UTIL_MakeVectors( m_pPlayer->pev->v_angle ); + + vecSrc = m_pPlayer->GetGunPosition( ) + gpGlobals->v_forward * 16 + gpGlobals->v_right * 8 + gpGlobals->v_up * -12; + + m_iFirePhase++; + switch ( m_iFirePhase ) + { + case 1: + vecSrc = vecSrc + gpGlobals->v_up * 8; + break; + case 2: + vecSrc = vecSrc + gpGlobals->v_up * 8; + vecSrc = vecSrc + gpGlobals->v_right * 8; + break; + case 3: + vecSrc = vecSrc + gpGlobals->v_right * 8; + break; + case 4: + vecSrc = vecSrc + gpGlobals->v_up * -8; + vecSrc = vecSrc + gpGlobals->v_right * 8; + break; + case 5: + vecSrc = vecSrc + gpGlobals->v_up * -8; + break; + case 6: + vecSrc = vecSrc + gpGlobals->v_up * -8; + vecSrc = vecSrc + gpGlobals->v_right * -8; + break; + case 7: + vecSrc = vecSrc + gpGlobals->v_right * -8; + break; + case 8: + vecSrc = vecSrc + gpGlobals->v_up * 8; + vecSrc = vecSrc + gpGlobals->v_right * -8; + m_iFirePhase = 0; + break; + } + + pHornet = CBaseEntity::Create( "hornet", vecSrc, m_pPlayer->pev->v_angle, m_pPlayer->edict() ); + pHornet->pev->velocity = gpGlobals->v_forward * 1200; + pHornet->pev->angles = UTIL_VecToAngles( pHornet->pev->velocity ); + + pHornet->SetThink( &CHornet::StartDart ); + + m_flRechargeTime = gpGlobals->time + 0.5; +#endif + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usHornetFire, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, FIREMODE_FAST, 0, 0, 0 ); + + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + m_pPlayer->m_iWeaponVolume = NORMAL_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = DIM_GUN_FLASH; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.1; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); +} + + +void CHgun::Reload( void ) +{ + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] >= HORNET_MAX_CARRY) + return; + + while (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] < HORNET_MAX_CARRY && m_flRechargeTime < gpGlobals->time) + { + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]++; + m_flRechargeTime += 0.5; + } +} + + +void CHgun::WeaponIdle( void ) +{ + Reload( ); + + if (m_flTimeWeaponIdle > UTIL_WeaponTimeBase()) + return; + + int iAnim; + float flRand = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 0, 1 ); + if (flRand <= 0.75) + { + iAnim = HGUN_IDLE1; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 30.0 / 16 * (2); + } + else if (flRand <= 0.875) + { + iAnim = HGUN_FIDGETSWAY; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 40.0 / 16.0; + } + else + { + iAnim = HGUN_FIDGETSHAKE; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 35.0 / 16.0; + } + SendWeaponAnim( iAnim ); +} + +#endif diff --git a/hlsdk/dlls/items.cpp b/hlsdk/dlls/items.cpp new file mode 100644 index 00000000..5a4a7e07 --- /dev/null +++ b/hlsdk/dlls/items.cpp @@ -0,0 +1,342 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== items.cpp ======================================================== + + functions governing the selection/use of weapons for players + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "player.h" +#include "skill.h" +#include "items.h" +#include "gamerules.h" + +extern int gmsgItemPickup; + +class CWorldItem : public CBaseEntity +{ +public: + void KeyValue(KeyValueData *pkvd ); + void Spawn( void ); + int m_iType; +}; + +LINK_ENTITY_TO_CLASS(world_items, CWorldItem); + +void CWorldItem::KeyValue(KeyValueData *pkvd) +{ + if (FStrEq(pkvd->szKeyName, "type")) + { + m_iType = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CWorldItem::Spawn( void ) +{ + CBaseEntity *pEntity = NULL; + + 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) + { + ALERT( at_console, "unable to create world_item %d\n", m_iType ); + } + else + { + pEntity->pev->target = pev->target; + pEntity->pev->targetname = pev->targetname; + pEntity->pev->spawnflags = pev->spawnflags; + } + + REMOVE_ENTITY(edict()); +} + + +void CItem::Spawn( void ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + UTIL_SetOrigin( pev, pev->origin ); + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 16)); + SetTouch(&CItem::ItemTouch); + + if (DROP_TO_FLOOR(ENT(pev)) == 0) + { + ALERT(at_error, "Item %s fell out of level at %f,%f,%f", STRING( pev->classname ), pev->origin.x, pev->origin.y, pev->origin.z); + UTIL_Remove( this ); + return; + } +} + +extern int gEvilImpulse101; + +void CItem::ItemTouch( CBaseEntity *pOther ) +{ + // if it's not a player, ignore + if ( !pOther->IsPlayer() ) + { + return; + } + + CBasePlayer *pPlayer = (CBasePlayer *)pOther; + + // ok, a player is touching this item, but can he have it? + if ( !g_pGameRules->CanHaveItem( pPlayer, this ) ) + { + // no? Ignore the touch. + return; + } + + if (MyTouch( pPlayer )) + { + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); + SetTouch( NULL ); + + // player grabbed the item. + g_pGameRules->PlayerGotItem( pPlayer, this ); + if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_YES ) + { + Respawn(); + } + else + { + UTIL_Remove( this ); + } + } + else if (gEvilImpulse101) + { + UTIL_Remove( this ); + } +} + +CBaseEntity* CItem::Respawn( void ) +{ + SetTouch( NULL ); + pev->effects |= EF_NODRAW; + + UTIL_SetOrigin( pev, g_pGameRules->VecItemRespawnSpot( this ) );// blip to whereever you should respawn. + + SetThink ( &CItem::Materialize ); + pev->nextthink = g_pGameRules->FlItemRespawnTime( this ); + return this; +} + +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; + } + + SetTouch( &CItem::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/hlsdk/dlls/items.h b/hlsdk/dlls/items.h new file mode 100644 index 00000000..e9852966 --- /dev/null +++ b/hlsdk/dlls/items.h @@ -0,0 +1,29 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef ITEMS_H +#define ITEMS_H + + +class CItem : public CBaseEntity +{ +public: + void Spawn( void ); + CBaseEntity* Respawn( void ); + void EXPORT ItemTouch( CBaseEntity *pOther ); + void EXPORT Materialize( void ); + virtual BOOL MyTouch( CBasePlayer *pPlayer ) { return FALSE; }; +}; + +#endif // ITEMS_H diff --git a/hlsdk/dlls/lights.cpp b/hlsdk/dlls/lights.cpp new file mode 100644 index 00000000..b9488b5e --- /dev/null +++ b/hlsdk/dlls/lights.cpp @@ -0,0 +1,199 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== lights.cpp ======================================================== + + spawn and think functions for editor-placed lights + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" + + + +class CLight : public CPointEntity +{ +public: + virtual void KeyValue( KeyValueData* pkvd ); + virtual void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_iStyle; + int m_iszPattern; +}; +LINK_ENTITY_TO_CLASS( light, CLight ); + +TYPEDESCRIPTION CLight::m_SaveData[] = +{ + DEFINE_FIELD( CLight, m_iStyle, FIELD_INTEGER ), + DEFINE_FIELD( CLight, m_iszPattern, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CLight, CPointEntity ); + + +// +// Cache user-entity-field values until spawn is called. +// +void CLight :: KeyValue( KeyValueData* pkvd) +{ + if (FStrEq(pkvd->szKeyName, "style")) + { + m_iStyle = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitch")) + { + pev->angles.x = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pattern")) + { + m_iszPattern = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + { + CPointEntity::KeyValue( pkvd ); + } +} + +/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) LIGHT_START_OFF +Non-displayed light. +Default light value is 300 +Default style is 0 +If targeted, it will toggle between on or off. +*/ + +void CLight :: Spawn( void ) +{ + if (FStringNull(pev->targetname)) + { // inert light + REMOVE_ENTITY(ENT(pev)); + return; + } + + if (m_iStyle >= 32) + { +// CHANGE_METHOD(ENT(pev), em_use, light_use); + if (FBitSet(pev->spawnflags, SF_LIGHT_START_OFF)) + LIGHT_STYLE(m_iStyle, "a"); + else if (m_iszPattern) + LIGHT_STYLE(m_iStyle, (char *)STRING( m_iszPattern )); + else + LIGHT_STYLE(m_iStyle, "m"); + } +} + + +void CLight :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (m_iStyle >= 32) + { + if ( !ShouldToggle( useType, !FBitSet(pev->spawnflags, SF_LIGHT_START_OFF) ) ) + return; + + if (FBitSet(pev->spawnflags, SF_LIGHT_START_OFF)) + { + if (m_iszPattern) + LIGHT_STYLE(m_iStyle, (char *)STRING( m_iszPattern )); + else + LIGHT_STYLE(m_iStyle, "m"); + ClearBits(pev->spawnflags, SF_LIGHT_START_OFF); + } + else + { + LIGHT_STYLE(m_iStyle, "a"); + SetBits(pev->spawnflags, SF_LIGHT_START_OFF); + } + } +} + +// +// shut up spawn functions for new spotlights +// +LINK_ENTITY_TO_CLASS( light_spot, CLight ); + + +class CEnvLight : public CLight +{ +public: + void KeyValue( KeyValueData* pkvd ); + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( light_environment, CEnvLight ); + +void CEnvLight::KeyValue( KeyValueData* pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "_light")) + { + int r, g, b, v, j; + char szColor[64]; + j = sscanf( pkvd->szValue, "%d %d %d %d\n", &r, &g, &b, &v ); + if (j == 1) + { + g = b = r; + } + else if (j == 4) + { + r = r * (v / 255); + g = g * (v / 255); + b = b * (v / 255); + } + + // simulate qrad direct, ambient,and gamma adjustments, as well as engine scaling + r = static_cast(powf( r / 114.0f, 0.6f ) * 264); + g = static_cast(powf( g / 114.0f, 0.6f ) * 264); + b = static_cast(powf( b / 114.0f, 0.6f ) * 264); + + pkvd->fHandled = TRUE; + sprintf( szColor, "%d", r ); + CVAR_SET_STRING( "sv_skycolor_r", szColor ); + sprintf( szColor, "%d", g ); + CVAR_SET_STRING( "sv_skycolor_g", szColor ); + sprintf( szColor, "%d", b ); + CVAR_SET_STRING( "sv_skycolor_b", szColor ); + } + else + { + CLight::KeyValue( pkvd ); + } +} + + +void CEnvLight :: Spawn( void ) +{ + char szVector[64]; + UTIL_MakeAimVectors( pev->angles ); + + sprintf( szVector, "%f", gpGlobals->v_forward.x ); + CVAR_SET_STRING( "sv_skyvec_x", szVector ); + sprintf( szVector, "%f", gpGlobals->v_forward.y ); + CVAR_SET_STRING( "sv_skyvec_y", szVector ); + sprintf( szVector, "%f", gpGlobals->v_forward.z ); + CVAR_SET_STRING( "sv_skyvec_z", szVector ); + + CLight::Spawn( ); +} diff --git a/hlsdk/dlls/maprules.cpp b/hlsdk/dlls/maprules.cpp new file mode 100644 index 00000000..a07562df --- /dev/null +++ b/hlsdk/dlls/maprules.cpp @@ -0,0 +1,918 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +// ------------------------------------------- +// +// maprules.cpp +// +// This module contains entities for implementing/changing game +// rules dynamically within each map (.BSP) +// +// ------------------------------------------- + +#include "extdll.h" +#include "eiface.h" +#include "util.h" +#include "gamerules.h" +#include "maprules.h" +#include "cbase.h" +#include "player.h" + +class CRuleEntity : public CBaseEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void SetMaster( int iszMaster ) { m_iszMaster = iszMaster; } + +protected: + BOOL CanFireForActivator( CBaseEntity *pActivator ); + +private: + string_t m_iszMaster; +}; + +TYPEDESCRIPTION CRuleEntity::m_SaveData[] = +{ + DEFINE_FIELD( CRuleEntity, m_iszMaster, FIELD_STRING), +}; + +IMPLEMENT_SAVERESTORE( CRuleEntity, CBaseEntity ); + + +void CRuleEntity::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = EF_NODRAW; +} + + +void CRuleEntity::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "master")) + { + SetMaster( ALLOC_STRING(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +BOOL CRuleEntity::CanFireForActivator( CBaseEntity *pActivator ) +{ + if ( m_iszMaster ) + { + if ( UTIL_IsMasterTriggered( m_iszMaster, pActivator ) ) + return TRUE; + else + return FALSE; + } + + return TRUE; +} + +// +// CRulePointEntity -- base class for all rule "point" entities (not brushes) +// +class CRulePointEntity : public CRuleEntity +{ +public: + void Spawn( void ); +}; + +void CRulePointEntity::Spawn( void ) +{ + CRuleEntity::Spawn(); + pev->frame = 0; + pev->model = 0; +} + +// +// CRuleBrushEntity -- base class for all rule "brush" entities (not brushes) +// Default behavior is to set up like a trigger, invisible, but keep the model for volume testing +// +class CRuleBrushEntity : public CRuleEntity +{ +public: + void Spawn( void ); + +private: +}; + +void CRuleBrushEntity::Spawn( void ) +{ + SET_MODEL( edict(), STRING(pev->model) ); + CRuleEntity::Spawn(); +} + + +// CGameScore / game_score -- award points to player / team +// Points +/- total +// Flag: Allow negative scores SF_SCORE_NEGATIVE +// Flag: Award points to team in teamplay SF_SCORE_TEAM + +#define SF_SCORE_NEGATIVE 0x0001 +#define SF_SCORE_TEAM 0x0002 + +class CGameScore : public CRulePointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline int Points( void ) { return static_cast(pev->frags); } + inline BOOL AllowNegativeScore( void ) { return pev->spawnflags & SF_SCORE_NEGATIVE; } + inline BOOL AwardToTeam( void ) { return pev->spawnflags & SF_SCORE_TEAM; } + + inline void SetPoints( int points ) { pev->frags = points; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_score, CGameScore ); + + +void CGameScore::Spawn( void ) +{ + CRulePointEntity::Spawn(); +} + + +void CGameScore::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "points")) + { + SetPoints( atoi(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CRulePointEntity::KeyValue( pkvd ); +} + + + +void CGameScore::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + // Only players can use this + if ( pActivator->IsPlayer() ) + { + if ( AwardToTeam() ) + { + pActivator->AddPointsToTeam( Points(), AllowNegativeScore() ); + } + else + { + pActivator->AddPoints( Points(), AllowNegativeScore() ); + } + } +} + + +// CGameEnd / game_end -- Ends the game in MP + +class CGameEnd : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +private: +}; + +LINK_ENTITY_TO_CLASS( game_end, CGameEnd ); + + +void CGameEnd::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + g_pGameRules->EndMultiplayerGame(); +} + + +// +// CGameText / game_text -- NON-Localized HUD Message (use env_message to display a titles.txt message) +// Flag: All players SF_ENVTEXT_ALLPLAYERS +// + + +#define SF_ENVTEXT_ALLPLAYERS 0x0001 + + +class CGameText : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + inline BOOL MessageToAll( void ) { return (pev->spawnflags & SF_ENVTEXT_ALLPLAYERS); } + inline void MessageSet( const char *pMessage ) { pev->message = ALLOC_STRING(pMessage); } + inline const char *MessageGet( void ) { return STRING(pev->message); } + +private: + + hudtextparms_t m_textParms; +}; + +LINK_ENTITY_TO_CLASS( game_text, CGameText ); + +// Save parms as a block. Will break save/restore if the structure changes, but this entity didn't ship with Half-Life, so +// it can't impact saved Half-Life games. +TYPEDESCRIPTION CGameText::m_SaveData[] = +{ + DEFINE_ARRAY( CGameText, m_textParms, FIELD_CHARACTER, sizeof(hudtextparms_t) ), +}; + +IMPLEMENT_SAVERESTORE( CGameText, CRulePointEntity ); + + +void CGameText::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "channel")) + { + m_textParms.channel = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "x")) + { + m_textParms.x = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "y")) + { + m_textParms.y = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "effect")) + { + m_textParms.effect = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "color")) + { + int color[4]; + UTIL_StringToIntArray( color, 4, pkvd->szValue ); + m_textParms.r1 = color[0]; + m_textParms.g1 = color[1]; + m_textParms.b1 = color[2]; + m_textParms.a1 = color[3]; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "color2")) + { + int color[4]; + UTIL_StringToIntArray( color, 4, pkvd->szValue ); + m_textParms.r2 = color[0]; + m_textParms.g2 = color[1]; + m_textParms.b2 = color[2]; + m_textParms.a2 = color[3]; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "fadein")) + { + m_textParms.fadeinTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "fadeout")) + { + m_textParms.fadeoutTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "holdtime")) + { + m_textParms.holdTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "fxtime")) + { + m_textParms.fxTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CRulePointEntity::KeyValue( pkvd ); +} + + +void CGameText::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( MessageToAll() ) + { + UTIL_HudMessageAll( m_textParms, MessageGet() ); + } + else + { + if ( pActivator->IsNetClient() ) + { + UTIL_HudMessage( pActivator, m_textParms, MessageGet() ); + } + } +} + + +// +// 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 +// +// team index (pulled from server team list "mp_teamlist" +// Flag: Remove on Fire +// Flag: Any team until set? -- Any team can use this until the team is set (otherwise no teams can use it) +// + +#define SF_TEAMMASTER_FIREONCE 0x0001 +#define SF_TEAMMASTER_ANYTEAM 0x0002 + +class CGameTeamMaster : public CRulePointEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int ObjectCaps( void ) { return CRulePointEntity:: ObjectCaps() | FCAP_MASTER; } + + BOOL IsTriggered( CBaseEntity *pActivator ); + const char *TeamID( void ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_TEAMMASTER_FIREONCE) ? TRUE : FALSE; } + inline BOOL AnyTeam( void ) { return (pev->spawnflags & SF_TEAMMASTER_ANYTEAM) ? TRUE : FALSE; } + +private: + BOOL TeamMatch( CBaseEntity *pActivator ); + + int m_teamIndex; + USE_TYPE triggerType; +}; + +LINK_ENTITY_TO_CLASS( game_team_master, CGameTeamMaster ); + +void CGameTeamMaster::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "teamindex")) + { + m_teamIndex = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "triggerstate")) + { + int type = atoi( pkvd->szValue ); + switch( type ) + { + case 0: + triggerType = USE_OFF; + break; + case 2: + triggerType = USE_TOGGLE; + break; + default: + triggerType = USE_ON; + break; + } + pkvd->fHandled = TRUE; + } + else + CRulePointEntity::KeyValue( pkvd ); +} + + +void CGameTeamMaster::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( useType == USE_SET ) + { + if ( value < 0 ) + { + m_teamIndex = -1; + } + else + { + m_teamIndex = g_pGameRules->GetTeamIndex( pActivator->TeamID() ); + } + return; + } + + if ( TeamMatch( pActivator ) ) + { + SUB_UseTargets( pActivator, triggerType, value ); + if ( RemoveOnFire() ) + UTIL_Remove( this ); + } +} + + +BOOL CGameTeamMaster::IsTriggered( CBaseEntity *pActivator ) +{ + return TeamMatch( pActivator ); +} + + +const char *CGameTeamMaster::TeamID( void ) +{ + if ( m_teamIndex < 0 ) // Currently set to "no team" + return ""; + + return g_pGameRules->GetIndexedTeamName( m_teamIndex ); // UNDONE: Fill this in with the team from the "teamlist" +} + + +BOOL CGameTeamMaster::TeamMatch( CBaseEntity *pActivator ) +{ + if ( m_teamIndex < 0 && AnyTeam() ) + return TRUE; + + if ( !pActivator ) + return FALSE; + + return UTIL_TeamsMatch( pActivator->TeamID(), TeamID() ); +} + + +// +// CGameTeamSet / game_team_set -- Changes the team of the entity it targets to the activator's team +// Flag: Fire once +// Flag: Clear team -- Sets the team to "NONE" instead of activator + +#define SF_TEAMSET_FIREONCE 0x0001 +#define SF_TEAMSET_CLEARTEAM 0x0002 + +class CGameTeamSet : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_TEAMSET_FIREONCE) ? TRUE : FALSE; } + inline BOOL ShouldClearTeam( void ) { return (pev->spawnflags & SF_TEAMSET_CLEARTEAM) ? TRUE : FALSE; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_team_set, CGameTeamSet ); + + +void CGameTeamSet::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( ShouldClearTeam() ) + { + SUB_UseTargets( pActivator, USE_SET, -1 ); + } + else + { + SUB_UseTargets( pActivator, USE_SET, 0 ); + } + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + +// +// CGamePlayerZone / game_player_zone -- players in the zone fire my target when I'm fired +// +// Needs master? +class CGamePlayerZone : public CRuleBrushEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + string_t m_iszInTarget; + string_t m_iszOutTarget; + string_t m_iszInCount; + string_t m_iszOutCount; +}; + +LINK_ENTITY_TO_CLASS( game_zone_player, CGamePlayerZone ); +TYPEDESCRIPTION CGamePlayerZone::m_SaveData[] = +{ + DEFINE_FIELD( CGamePlayerZone, m_iszInTarget, FIELD_STRING ), + DEFINE_FIELD( CGamePlayerZone, m_iszOutTarget, FIELD_STRING ), + DEFINE_FIELD( CGamePlayerZone, m_iszInCount, FIELD_STRING ), + DEFINE_FIELD( CGamePlayerZone, m_iszOutCount, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CGamePlayerZone, CRuleBrushEntity ); + +void CGamePlayerZone::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "intarget")) + { + m_iszInTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "outtarget")) + { + m_iszOutTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "incount")) + { + m_iszInCount = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "outcount")) + { + m_iszOutCount = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CRuleBrushEntity::KeyValue( pkvd ); +} + +void CGamePlayerZone::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int playersInCount = 0; + int playersOutCount = 0; + + if ( !CanFireForActivator( pActivator ) ) + return; + + CBaseEntity *pPlayer = NULL; + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + { + TraceResult trace; + int hullNumber; + + hullNumber = human_hull; + if ( pPlayer->pev->flags & FL_DUCKING ) + { + hullNumber = head_hull; + } + + UTIL_TraceModel( pPlayer->pev->origin, pPlayer->pev->origin, hullNumber, edict(), &trace ); + + if ( trace.fStartSolid ) + { + playersInCount++; + if ( m_iszInTarget ) + { + FireTargets( STRING(m_iszInTarget), pPlayer, pActivator, useType, value ); + } + } + else + { + playersOutCount++; + if ( m_iszOutTarget ) + { + FireTargets( STRING(m_iszOutTarget), pPlayer, pActivator, useType, value ); + } + } + } + } + + if ( m_iszInCount ) + { + FireTargets( STRING(m_iszInCount), pActivator, this, USE_SET, playersInCount ); + } + + if ( m_iszOutCount ) + { + FireTargets( STRING(m_iszOutCount), pActivator, this, USE_SET, playersOutCount ); + } +} + + + +// +// CGamePlayerHurt / game_player_hurt -- Damages the player who fires it +// Flag: Fire once + +#define SF_PKILL_FIREONCE 0x0001 +class CGamePlayerHurt : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_PKILL_FIREONCE) ? TRUE : FALSE; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_player_hurt, CGamePlayerHurt ); + + +void CGamePlayerHurt::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( pActivator->IsPlayer() ) + { + if ( pev->dmg < 0 ) + pActivator->TakeHealth( -pev->dmg, DMG_GENERIC ); + else + pActivator->TakeDamage( pev, pev, pev->dmg, DMG_GENERIC ); + } + + SUB_UseTargets( pActivator, useType, value ); + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + + +// +// CGameCounter / game_counter -- Counts events and fires target +// Flag: Fire once +// Flag: Reset on Fire + +#define SF_GAMECOUNT_FIREONCE 0x0001 +#define SF_GAMECOUNT_RESET 0x0002 + +class CGameCounter : public CRulePointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_GAMECOUNT_FIREONCE) ? TRUE : FALSE; } + inline BOOL ResetOnFire( void ) { return (pev->spawnflags & SF_GAMECOUNT_RESET) ? TRUE : FALSE; } + + inline void CountUp( void ) { pev->frags++; } + inline void CountDown( void ) { pev->frags--; } + inline void ResetCount( void ) { pev->frags = pev->dmg; } + inline int CountValue( void ) { return static_cast(pev->frags); } + inline int LimitValue( void ) { return static_cast(pev->health); } + + inline BOOL HitLimit( void ) { return CountValue() == LimitValue(); } + +private: + + inline void SetCountValue( int value ) { pev->frags = value; } + inline void SetInitialValue( int value ) { pev->dmg = value; } +}; + +LINK_ENTITY_TO_CLASS( game_counter, CGameCounter ); + +void CGameCounter::Spawn( void ) +{ + // Save off the initial count + SetInitialValue( CountValue() ); + CRulePointEntity::Spawn(); +} + + +void CGameCounter::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + switch( useType ) + { + case USE_ON: + case USE_TOGGLE: + CountUp(); + break; + + case USE_OFF: + CountDown(); + break; + + case USE_SET: + SetCountValue( (int)value ); + break; + } + + if ( HitLimit() ) + { + SUB_UseTargets( pActivator, USE_TOGGLE, 0 ); + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } + + if ( ResetOnFire() ) + { + ResetCount(); + } + } +} + + + +// +// CGameCounterSet / game_counter_set -- Sets the counter's value +// Flag: Fire once + +#define SF_GAMECOUNTSET_FIREONCE 0x0001 + +class CGameCounterSet : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_GAMECOUNTSET_FIREONCE) ? TRUE : FALSE; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_counter_set, CGameCounterSet ); + + +void CGameCounterSet::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + SUB_UseTargets( pActivator, USE_SET, pev->frags ); + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + +// +// CGamePlayerEquip / game_playerequip -- Sets the default player equipment +// Flag: USE Only + +#define SF_PLAYEREQUIP_USEONLY 0x0001 +#define MAX_EQUIP 32 + +class CGamePlayerEquip : public CRulePointEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Touch( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + inline BOOL UseOnly( void ) { return (pev->spawnflags & SF_PLAYEREQUIP_USEONLY) ? TRUE : FALSE; } + +private: + + void EquipPlayer( CBaseEntity *pPlayer ); + + string_t m_weaponNames[MAX_EQUIP]; + int m_weaponCount[MAX_EQUIP]; +}; + +LINK_ENTITY_TO_CLASS( game_player_equip, CGamePlayerEquip ); + + +void CGamePlayerEquip::KeyValue( KeyValueData *pkvd ) +{ + CRulePointEntity::KeyValue( pkvd ); + + if ( !pkvd->fHandled ) + { + for ( int i = 0; i < MAX_EQUIP; i++ ) + { + if ( !m_weaponNames[i] ) + { + char tmp[128]; + + UTIL_StripToken( pkvd->szKeyName, tmp ); + + m_weaponNames[i] = ALLOC_STRING(tmp); + m_weaponCount[i] = atoi(pkvd->szValue); + m_weaponCount[i] = max(1,m_weaponCount[i]); + pkvd->fHandled = TRUE; + break; + } + } + } +} + + +void CGamePlayerEquip::Touch( CBaseEntity *pOther ) +{ + if ( !CanFireForActivator( pOther ) ) + return; + + if ( UseOnly() ) + return; + + EquipPlayer( pOther ); +} + +void CGamePlayerEquip::EquipPlayer( CBaseEntity *pEntity ) +{ + CBasePlayer *pPlayer = NULL; + + if ( pEntity->IsPlayer() ) + { + pPlayer = (CBasePlayer *)pEntity; + } + + if ( !pPlayer ) + return; + + for ( int i = 0; i < MAX_EQUIP; i++ ) + { + if ( !m_weaponNames[i] ) + break; + for ( int j = 0; j < m_weaponCount[i]; j++ ) + { + pPlayer->GiveNamedItem( STRING(m_weaponNames[i]) ); + } + } +} + + +void CGamePlayerEquip::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + EquipPlayer( pActivator ); +} + + +// +// CGamePlayerTeam / game_player_team -- Changes the team of the player who fired it +// Flag: Fire once +// Flag: Kill Player +// Flag: Gib Player + +#define SF_PTEAM_FIREONCE 0x0001 +#define SF_PTEAM_KILL 0x0002 +#define SF_PTEAM_GIB 0x0004 + +class CGamePlayerTeam : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +private: + + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_PTEAM_FIREONCE) ? TRUE : FALSE; } + inline BOOL ShouldKillPlayer( void ) { return (pev->spawnflags & SF_PTEAM_KILL) ? TRUE : FALSE; } + inline BOOL ShouldGibPlayer( void ) { return (pev->spawnflags & SF_PTEAM_GIB) ? TRUE : FALSE; } + + const char *TargetTeamName( const char *pszTargetName ); +}; + +LINK_ENTITY_TO_CLASS( game_player_team, CGamePlayerTeam ); + + +const char *CGamePlayerTeam::TargetTeamName( const char *pszTargetName ) +{ + CBaseEntity *pTeamEntity = NULL; + + while ((pTeamEntity = UTIL_FindEntityByTargetname( pTeamEntity, pszTargetName )) != NULL) + { + if ( FClassnameIs( pTeamEntity->pev, "game_team_master" ) ) + return pTeamEntity->TeamID(); + } + + return NULL; +} + + +void CGamePlayerTeam::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( pActivator->IsPlayer() ) + { + const char *pszTargetTeam = TargetTeamName( STRING(pev->target) ); + if ( pszTargetTeam ) + { + CBasePlayer *pPlayer = (CBasePlayer *)pActivator; + g_pGameRules->ChangePlayerTeam( pPlayer, pszTargetTeam, ShouldKillPlayer(), ShouldGibPlayer() ); + } + } + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + diff --git a/hlsdk/dlls/maprules.h b/hlsdk/dlls/maprules.h new file mode 100644 index 00000000..57f9939b --- /dev/null +++ b/hlsdk/dlls/maprules.h @@ -0,0 +1,22 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef MAPRULES_H +#define MAPRULES_H + + + +#endif // MAPRULES_H + diff --git a/hlsdk/dlls/monsterevent.h b/hlsdk/dlls/monsterevent.h new file mode 100644 index 00000000..46c5624a --- /dev/null +++ b/hlsdk/dlls/monsterevent.h @@ -0,0 +1,34 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef MONSTEREVENT_H +#define MONSTEREVENT_H + +typedef struct +{ + int event; + char *options; +} MonsterEvent_t; + +#define EVENT_SPECIFIC 0 +#define EVENT_SCRIPTED 1000 +#define EVENT_SHARED 2000 +#define EVENT_CLIENT 5000 + +#define MONSTER_EVENT_BODYDROP_LIGHT 2001 +#define MONSTER_EVENT_BODYDROP_HEAVY 2002 + +#define MONSTER_EVENT_SWISHSOUND 2010 + +#endif // MONSTEREVENT_H diff --git a/hlsdk/dlls/monsters.h b/hlsdk/dlls/monsters.h new file mode 100644 index 00000000..a0a8da63 --- /dev/null +++ b/hlsdk/dlls/monsters.h @@ -0,0 +1,88 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef MONSTERS_H +#include "skill.h" +#define MONSTERS_H + +/* + +===== monsters.h ======================================================== + + Header file for monster-related utility code + +*/ + +// Hit Group standards +#define HITGROUP_GENERIC 0 +#define HITGROUP_HEAD 1 +#define HITGROUP_CHEST 2 +#define HITGROUP_STOMACH 3 +#define HITGROUP_LEFTARM 4 +#define HITGROUP_RIGHTARM 5 +#define HITGROUP_LEFTLEG 6 +#define HITGROUP_RIGHTLEG 7 + + +// 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 ); + +Vector VecCheckToss ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flGravityAdj = 1.0 ); +Vector VecCheckThrow ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flSpeed, float flGravityAdj = 1.0 ); +extern DLL_GLOBAL Vector g_vecAttackDir; +extern DLL_GLOBAL CONSTANT float g_flMeleeRange; +extern DLL_GLOBAL CONSTANT float g_flMediumRange; +extern DLL_GLOBAL CONSTANT float g_flLongRange; +extern void EjectBrass (const Vector &vecOrigin, const Vector &vecVelocity, float rotation, int model, int soundtype ); +extern void ExplodeModel( const Vector &vecOrigin, float speed, int model, int count ); + +BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget ); +BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget, Vector &vecTargetOrigin, float flSize = 0.0 ); + +// monster to monster relationship types +#define R_AL -2 // (ALLY) pals. Good alternative to R_NO when applicable. +#define R_FR -1// (FEAR)will run +#define R_NO 0// (NO RELATIONSHIP) disregard +#define R_DL 1// (DISLIKE) will attack +#define R_HT 2// (HATE)will attack this character instead of any visible DISLIKEd characters +#define R_NM 3// (NEMESIS) A monster Will ALWAYS attack its nemsis, no matter what + + +#define bits_MEMORY_KILLED ( 1 << 7 )// HACKHACK -- remember that I've already called my Killed() + +// +// A gib is a chunk of a body, or a piece of wood/metal/rocks/etc. +// +class CGib : public CBaseEntity +{ +public: + void Spawn( const char *szGibModel ); + void EXPORT BounceGibTouch ( CBaseEntity *pOther ); + void EXPORT StickyGibTouch ( CBaseEntity *pOther ); + void EXPORT WaitTillLand( void ); + void LimitVelocity( void ); + + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DONT_SAVE; } + static void SpawnHeadGib( entvars_t *pevVictim ); + static void SpawnRandomGibs( entvars_t *pevVictim, int cGibs, int human ); + static void SpawnStickyGibs( entvars_t *pevVictim, Vector vecOrigin, int cGibs ); + + int m_bloodColor; + int m_cBloodDecals; + int m_material; + float m_lifeTime; +}; + + +#endif //MONSTERS_H diff --git a/hlsdk/dlls/mortar.cpp b/hlsdk/dlls/mortar.cpp new file mode 100644 index 00000000..18b221bd --- /dev/null +++ b/hlsdk/dlls/mortar.cpp @@ -0,0 +1,323 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== mortar.cpp ======================================================== + + the "LaBuznik" mortar device + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "weapons.h" +#include "decals.h" +#include "soundent.h" + +class CFuncMortarField : public CBaseToggle +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + // Bmodels don't go across transitions + virtual int ObjectCaps( void ) { return CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + void EXPORT FieldUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int m_iszXController; + int m_iszYController; + float m_flSpread; + float m_flDelay; + int m_iCount; + int m_fControl; +}; + +LINK_ENTITY_TO_CLASS( func_mortar_field, CFuncMortarField ); + +TYPEDESCRIPTION CFuncMortarField::m_SaveData[] = +{ + DEFINE_FIELD( CFuncMortarField, m_iszXController, FIELD_STRING ), + DEFINE_FIELD( CFuncMortarField, m_iszYController, FIELD_STRING ), + DEFINE_FIELD( CFuncMortarField, m_flSpread, FIELD_FLOAT ), + DEFINE_FIELD( CFuncMortarField, m_flDelay, FIELD_FLOAT ), + DEFINE_FIELD( CFuncMortarField, m_iCount, FIELD_INTEGER ), + DEFINE_FIELD( CFuncMortarField, m_fControl, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CFuncMortarField, CBaseToggle ); + + +void CFuncMortarField :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszXController")) + { + m_iszXController = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszYController")) + { + m_iszYController = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flSpread")) + { + m_flSpread = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fControl")) + { + m_fControl = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iCount")) + { + m_iCount = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } +} + + +// Drop bombs from above +void CFuncMortarField :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->movetype = MOVETYPE_NONE; + SetBits( pev->effects, EF_NODRAW ); + SetUse( &CFuncMortarField::FieldUse ); + Precache(); +} + + +void CFuncMortarField :: Precache( void ) +{ + PRECACHE_SOUND ("weapons/mortar.wav"); + PRECACHE_SOUND ("weapons/mortarhit.wav"); + PRECACHE_MODEL( "sprites/lgtning.spr" ); +} + + +// If connected to a table, then use the table controllers, else hit where the trigger is. +void CFuncMortarField :: FieldUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + Vector vecStart; + + vecStart.x = RANDOM_FLOAT( pev->mins.x, pev->maxs.x ); + vecStart.y = RANDOM_FLOAT( pev->mins.y, pev->maxs.y ); + vecStart.z = pev->maxs.z; + + switch( m_fControl ) + { + case 0: // random + break; + case 1: // Trigger Activator + if (pActivator != NULL) + { + vecStart.x = pActivator->pev->origin.x; + vecStart.y = pActivator->pev->origin.y; + } + break; + case 2: // table + { + CBaseEntity *pController; + + if (!FStringNull(m_iszXController)) + { + pController = UTIL_FindEntityByTargetname( NULL, STRING(m_iszXController)); + if (pController != NULL) + { + vecStart.x = pev->mins.x + pController->pev->ideal_yaw * (pev->size.x); + } + } + if (!FStringNull(m_iszYController)) + { + pController = UTIL_FindEntityByTargetname( NULL, STRING(m_iszYController)); + if (pController != NULL) + { + vecStart.y = pev->mins.y + pController->pev->ideal_yaw * (pev->size.y); + } + } + } + break; + } + + int pitch = RANDOM_LONG(95,124); + + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "weapons/mortar.wav", 1.0, ATTN_NONE, 0, pitch); + + float t = 2.5; + for (int i = 0; i < m_iCount; i++) + { + Vector vecSpot = vecStart; + vecSpot.x += RANDOM_FLOAT( -m_flSpread, m_flSpread ); + vecSpot.y += RANDOM_FLOAT( -m_flSpread, m_flSpread ); + + TraceResult tr; + UTIL_TraceLine( vecSpot, vecSpot + Vector( 0, 0, -1 ) * 4096, ignore_monsters, ENT(pev), &tr ); + + edict_t *pentOwner = NULL; + if (pActivator) pentOwner = pActivator->edict(); + + CBaseEntity *pMortar = Create("monster_mortar", tr.vecEndPos, Vector( 0, 0, 0 ), pentOwner ); + pMortar->pev->nextthink = gpGlobals->time + t; + t += RANDOM_FLOAT( 0.2, 0.5 ); + + if (i == 0) + CSoundEnt::InsertSound ( bits_SOUND_DANGER, tr.vecEndPos, 400, 0.3 ); + } +} + + +class CMortar : public CGrenade +{ +public: + void Spawn( void ); + void Precache( void ); + + void EXPORT MortarExplode( void ); + + int m_spriteTexture; +}; + +LINK_ENTITY_TO_CLASS( monster_mortar, CMortar ); + +void CMortar::Spawn( ) +{ + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT; + + pev->dmg = 200; + + SetThink( &CMortar::MortarExplode ); + pev->nextthink = 0; + + Precache( ); + + +} + + +void CMortar::Precache( ) +{ + m_spriteTexture = PRECACHE_MODEL( "sprites/lgtning.spr" ); +} + +void CMortar::MortarExplode( void ) +{ +#if 1 + // mortar beam + 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(pev->origin.x); + WRITE_COORD(pev->origin.y); + WRITE_COORD(pev->origin.z + 1024); + WRITE_SHORT(m_spriteTexture ); + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 1 ); // life + WRITE_BYTE( 40 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 160 ); // r, g, b + WRITE_BYTE( 100 ); // r, g, b + WRITE_BYTE( 128 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); +#endif + +#if 0 + // blast circle + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMTORUS); + WRITE_COORD(pev->origin.x); + WRITE_COORD(pev->origin.y); + WRITE_COORD(pev->origin.z + 32); + WRITE_COORD(pev->origin.x); + WRITE_COORD(pev->origin.y); + WRITE_COORD(pev->origin.z + 32 + pev->dmg * 2 / .2); // reach damage radius over .3 seconds + WRITE_SHORT(m_spriteTexture ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 2 ); // life + WRITE_BYTE( 12 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 160 ); // r, g, b + WRITE_BYTE( 100 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); +#endif + + TraceResult tr; + UTIL_TraceLine( pev->origin + Vector( 0, 0, 1024 ), pev->origin - Vector( 0, 0, 1024 ), dont_ignore_monsters, ENT(pev), &tr ); + + Explode( &tr, DMG_BLAST | DMG_MORTAR ); + UTIL_ScreenShake( tr.vecEndPos, 25.0, 150.0, 1.0, 750 ); + +#if 0 + int pitch = RANDOM_LONG(95,124); + 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 ); + + /* + if ( RANDOM_FLOAT ( 0 , 1 ) < 0.5 ) + { + UTIL_DecalTrace( pTrace, DECAL_SCORCH1 ); + } + else + { + UTIL_DecalTrace( pTrace, DECAL_SCORCH2 ); + } + */ + + SetThink( SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; +#endif + +} + + +#if 0 +void CMortar::ShootTimed( EVARS *pevOwner, Vector vecStart, float time ) +{ + CMortar *pMortar = GetClassPtr( (CMortar *)NULL ); + pMortar->Spawn(); + + TraceResult tr; + UTIL_TraceLine( vecStart, vecStart + Vector( 0, 0, -1 ) * 4096, ignore_monsters, ENT(pMortar->pev), &tr ); + + pMortar->pev->nextthink = gpGlobals->time + time; + + UTIL_SetOrigin( pMortar->pev, tr.vecEndPos ); +} +#endif diff --git a/hlsdk/dlls/mp.def b/hlsdk/dlls/mp.def new file mode 100644 index 00000000..5ae6f18f --- /dev/null +++ b/hlsdk/dlls/mp.def @@ -0,0 +1,5 @@ +LIBRARY mp +EXPORTS + GiveFnptrsToDll @1 +SECTIONS + .data READ WRITE diff --git a/hlsdk/dlls/mp5.cpp b/hlsdk/dlls/mp5.cpp new file mode 100644 index 00000000..789c264e --- /dev/null +++ b/hlsdk/dlls/mp5.cpp @@ -0,0 +1,385 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "soundent.h" +#include "gamerules.h" + +enum mp5_e +{ + MP5_LONGIDLE = 0, + MP5_IDLE1, + MP5_LAUNCH, + MP5_RELOAD, + MP5_DEPLOY, + MP5_FIRE1, + MP5_FIRE2, + MP5_FIRE3, +}; + + + +LINK_ENTITY_TO_CLASS( weapon_mp5, CMP5 ); +LINK_ENTITY_TO_CLASS( weapon_9mmAR, CMP5 ); + + +//========================================================= +//========================================================= +int CMP5::SecondaryAmmoIndex( void ) +{ + return m_iSecondaryAmmoType; +} + +void CMP5::Spawn( ) +{ + pev->classname = MAKE_STRING("weapon_9mmAR"); // hack to allow for old names + Precache( ); + SET_MODEL(ENT(pev), "models/w_9mmAR.mdl"); + m_iId = WEAPON_MP5; + + m_iDefaultAmmo = MP5_DEFAULT_GIVE; + + FallInit();// get ready to fall down. +} + + +void CMP5::Precache( void ) +{ + PRECACHE_MODEL("models/v_9mmAR.mdl"); + PRECACHE_MODEL("models/w_9mmAR.mdl"); + PRECACHE_MODEL("models/p_9mmAR.mdl"); + + m_iShell = PRECACHE_MODEL ("models/shell.mdl");// brass shellTE_MODEL + + PRECACHE_MODEL("models/grenade.mdl"); // grenade + + PRECACHE_MODEL("models/w_9mmARclip.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + + PRECACHE_SOUND("items/clipinsert1.wav"); + PRECACHE_SOUND("items/cliprelease1.wav"); + + PRECACHE_SOUND ("weapons/hks1.wav");// H to the K + PRECACHE_SOUND ("weapons/hks2.wav");// H to the K + PRECACHE_SOUND ("weapons/hks3.wav");// H to the K + + PRECACHE_SOUND( "weapons/glauncher.wav" ); + PRECACHE_SOUND( "weapons/glauncher2.wav" ); + + PRECACHE_SOUND ("weapons/357_cock1.wav"); + + m_usMP5 = PRECACHE_EVENT( 1, "events/mp5.sc" ); + m_usMP52 = PRECACHE_EVENT( 1, "events/mp52.sc" ); +} + +int CMP5::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "9mm"; + p->iMaxAmmo1 = _9MM_MAX_CARRY; + p->pszAmmo2 = "ARgrenades"; + p->iMaxAmmo2 = M203_GRENADE_MAX_CARRY; + p->iMaxClip = MP5_MAX_CLIP; + p->iSlot = 2; + p->iPosition = 0; + p->iFlags = 0; + p->iId = m_iId = WEAPON_MP5; + p->iWeight = MP5_WEIGHT; + + return 1; +} + +int CMP5::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + +BOOL CMP5::Deploy( ) +{ + return DefaultDeploy( "models/v_9mmAR.mdl", "models/p_9mmAR.mdl", MP5_DEPLOY, "mp5" ); +} + + +void CMP5::PrimaryAttack() +{ + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = 0.15; + return; + } + + if (m_iClip <= 0) + { + PlayEmptySound(); + m_flNextPrimaryAttack = 0.15; + return; + } + + m_pPlayer->m_iWeaponVolume = NORMAL_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; + + m_iClip--; + + + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = m_pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); + Vector vecDir; + +#ifdef CLIENT_DLL + if ( !bIsMultiplayer() ) +#else + if ( !g_pGameRules->IsMultiplayer() ) +#endif + { + // optimized multiplayer. Widened to make it easier to hit a moving player + vecDir = m_pPlayer->FireBulletsPlayer( 1, vecSrc, vecAiming, VECTOR_CONE_6DEGREES, 8192, BULLET_PLAYER_MP5, 2, 0, m_pPlayer->pev, m_pPlayer->random_seed ); + } + else + { + // single player spread + vecDir = m_pPlayer->FireBulletsPlayer( 1, vecSrc, vecAiming, VECTOR_CONE_3DEGREES, 8192, BULLET_PLAYER_MP5, 2, 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_usMP5, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, vecDir.x, vecDir.y, 0, 0, 0, 0 ); + + if (!m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + // HEV suit - indicate out of ammo condition + m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.1; + + if ( m_flNextPrimaryAttack < UTIL_WeaponTimeBase() ) + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.1; + + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); +} + + + +void CMP5::SecondaryAttack( void ) +{ + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = 0.15; + return; + } + + if (m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] == 0) + { + PlayEmptySound( ); + return; + } + + m_pPlayer->m_iWeaponVolume = NORMAL_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = BRIGHT_GUN_FLASH; + + m_pPlayer->m_iExtraSoundTypes = bits_SOUND_DANGER; + m_pPlayer->m_flStopExtraSoundTime = UTIL_WeaponTimeBase() + 0.2; + + m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType]--; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + + // we don't add in player velocity anymore. + CGrenade::ShootContact( m_pPlayer->pev, + m_pPlayer->pev->origin + m_pPlayer->pev->view_ofs + gpGlobals->v_forward * 16, + gpGlobals->v_forward * 800 ); + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT( flags, m_pPlayer->edict(), m_usMP52 ); + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 1; + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 1; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 5;// idle pretty soon after shooting. + + if (!m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType]) + // HEV suit - indicate out of ammo condition + m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); +} + +void CMP5::Reload( void ) +{ + if ( m_pPlayer->ammo_9mm <= 0 ) + return; + + DefaultReload( MP5_MAX_CLIP, MP5_RELOAD, 1.5 ); +} + + +void CMP5::WeaponIdle( void ) +{ + ResetEmptySound( ); + + m_pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); + + if ( m_flTimeWeaponIdle > UTIL_WeaponTimeBase() ) + return; + + int iAnim; + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: + iAnim = MP5_LONGIDLE; + break; + + default: + case 1: + iAnim = MP5_IDLE1; + break; + } + + SendWeaponAnim( iAnim ); + + m_flTimeWeaponIdle = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); // how long till we do this again. +} + + + +class CMP5AmmoClip : public CBasePlayerAmmo +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_9mmARclip.mdl"); + CBasePlayerAmmo::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_9mmARclip.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + } + BOOL AddAmmo( CBaseEntity *pOther ) + { + int bResult = (pOther->GiveAmmo( AMMO_MP5CLIP_GIVE, "9mm", _9MM_MAX_CARRY) != -1); + if (bResult) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + } + return bResult; + } +}; +LINK_ENTITY_TO_CLASS( ammo_mp5clip, CMP5AmmoClip ); +LINK_ENTITY_TO_CLASS( ammo_9mmAR, CMP5AmmoClip ); + + + +class CMP5Chainammo : public CBasePlayerAmmo +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_chainammo.mdl"); + CBasePlayerAmmo::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_chainammo.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + } + BOOL AddAmmo( CBaseEntity *pOther ) + { + int bResult = (pOther->GiveAmmo( AMMO_CHAINBOX_GIVE, "9mm", _9MM_MAX_CARRY) != -1); + if (bResult) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + } + return bResult; + } +}; +LINK_ENTITY_TO_CLASS( ammo_9mmbox, CMP5Chainammo ); + + +class CMP5AmmoGrenade : public CBasePlayerAmmo +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_ARgrenade.mdl"); + CBasePlayerAmmo::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_ARgrenade.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + } + BOOL AddAmmo( CBaseEntity *pOther ) + { + int bResult = (pOther->GiveAmmo( AMMO_M203BOX_GIVE, "ARgrenades", M203_GRENADE_MAX_CARRY ) != -1); + + if (bResult) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + } + return bResult; + } +}; +LINK_ENTITY_TO_CLASS( ammo_mp5grenades, CMP5AmmoGrenade ); +LINK_ENTITY_TO_CLASS( ammo_ARgrenades, CMP5AmmoGrenade ); + + + + + + + + + + + + + + + + + + diff --git a/hlsdk/dlls/mpstubb.cpp b/hlsdk/dlls/mpstubb.cpp new file mode 100644 index 00000000..6c7a34aa --- /dev/null +++ b/hlsdk/dlls/mpstubb.cpp @@ -0,0 +1,264 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "soundent.h" +#include "nodes.h" +#include "talkmonster.h" + + +float CTalkMonster::g_talkWaitTime = 0; // time delay until it's ok to speak: used so that two NPCs don't talk at once + +/*********************************************************/ + + +CGraph WorldGraph; +void CGraph :: InitGraph( void ) { } +int CGraph :: FLoadGraph ( char *szMapName ) { return FALSE; } +int CGraph :: AllocNodes ( void ) { return FALSE; } +int CGraph :: CheckNODFile ( char *szMapName ) { return FALSE; } +int CGraph :: FSetGraphPointers ( void ) { return 0; } +void CGraph :: ShowNodeConnections ( int iNode ) { } +int CGraph :: FindNearestNode ( const Vector &vecOrigin, int afNodeTypes ) { return 0; } + + +/*********************************************************/ + + +void CBaseMonster :: ReportAIState( void ) { } +float CBaseMonster :: ChangeYaw ( int speed ) { return 0; } +void CBaseMonster :: MakeIdealYaw( Vector vecTarget ) { } + + +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( &CBaseMonster::CorpseFallThink ); + pev->nextthink = gpGlobals->time + 0.5; +} + + +BOOL CBaseMonster :: ShouldFadeOnDeath( void ) +{ + return FALSE; +} + +BOOL CBaseMonster :: FCheckAITrigger ( void ) +{ + return FALSE; +} + +void CBaseMonster :: KeyValue( KeyValueData *pkvd ) +{ + CBaseToggle::KeyValue( pkvd ); +} + +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() ]; +} + + +//========================================================= +// 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 + + 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]; + if ( pSightEnt != this && 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 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 ); +} + + +//========================================================= +// 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 = static_cast(( 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 = static_cast(( pNextEnt->pev->origin - pev->origin ).Length()); + + if ( iDist <= iNearest ) + { + iNearest = iDist; + iBestRelationship = IRelationship ( pNextEnt ); + pReturn = pNextEnt; + } + } + } + + pNextEnt = pNextEnt->m_pLink; + } + + return pReturn; +} diff --git a/hlsdk/dlls/msvc6/mp.dsp b/hlsdk/dlls/msvc6/mp.dsp new file mode 100644 index 00000000..b63e1ab7 --- /dev/null +++ b/hlsdk/dlls/msvc6/mp.dsp @@ -0,0 +1,559 @@ +# Microsoft Developer Studio Project File - Name="mp" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=mp - 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 "mp.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 "mp.mak" CFG="mp - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mp - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mp - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mp - Win32 Profile" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "mp - 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 ".\Releasemp" +# PROP Intermediate_Dir ".\Releasemp" +# PROP Ignore_Export_Lib 0 +# 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 /GX /Zi /O2 /I "..\..\dlls" /I "..\..\engine" /I "..\..\common" /I "..\..\pm_shared" /I "..\..\game_shared" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /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 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:"..\mp.def" +# SUBTRACT LINK32 /profile + +!ELSEIF "$(CFG)" == "mp - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\mp___Win" +# PROP BASE Intermediate_Dir ".\mp___Win" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir ".\debugmp" +# PROP Intermediate_Dir ".\debugmp" +# 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 /GX /ZI /Od /I "..\..\dlls" /I "..\..\engine" /I "..\..\common" /I "..\..\pm_shared" /I "..\..\game_shared" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /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:"..\mp.def" /implib:".\Debug\mp.lib" +# SUBTRACT LINK32 /profile + +!ELSEIF "$(CFG)" == "mp - Win32 Profile" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\mp___Win" +# PROP BASE Intermediate_Dir ".\mp___Win" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir ".\Profilemp" +# PROP Intermediate_Dir ".\Profilemp" +# 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 /GX /Zi /O2 /I "..\..\dlls" /I "..\..\engine" /I "..\..\common" /I "..\..\pm_shared" /I "..\..\game_shared" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /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:"..\mp.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:"..\mp.def" + +!ENDIF + +# Begin Target + +# Name "mp - Win32 Release" +# Name "mp - Win32 Debug" +# Name "mp - Win32 Profile" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=..\airtank.cpp +# End Source File +# 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=..\crossbow.cpp +# End Source File +# Begin Source File + +SOURCE=..\crowbar.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=..\egon.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=..\gamerules.cpp +# End Source File +# Begin Source File + +SOURCE=..\gauss.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_battery.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=..\handgrenade.cpp +# End Source File +# Begin Source File + +SOURCE=..\healthkit.cpp +# End Source File +# Begin Source File + +SOURCE=..\wpn_shared\hl_wpn_glock.cpp +# End Source File +# Begin Source File + +SOURCE=..\hornet.cpp +# End Source File +# Begin Source File + +SOURCE=..\hornetgun.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\common\interface.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=..\mortar.cpp +# End Source File +# Begin Source File + +SOURCE=..\mp5.cpp +# End Source File +# Begin Source File + +SOURCE=..\mpstubb.cpp +# End Source File +# Begin Source File + +SOURCE=..\multiplay_gamerules.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=..\python.cpp +# End Source File +# Begin Source File + +SOURCE=..\rpg.cpp +# End Source File +# Begin Source File + +SOURCE=..\satchel.cpp +# End Source File +# Begin Source File + +SOURCE=..\shotgun.cpp +# End Source File +# Begin Source File + +SOURCE=..\singleplay_gamerules.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=..\squeakgrenade.cpp +# End Source File +# Begin Source File + +SOURCE=..\subs.cpp +# End Source File +# Begin Source File + +SOURCE=..\teamplay_gamerules.cpp +# End Source File +# Begin Source File + +SOURCE=..\triggers.cpp +# End Source File +# Begin Source File + +SOURCE=..\tripmine.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 +# Begin Source File + +SOURCE=..\xen.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=..\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=..\hornet.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=..\saverestore.h +# End Source File +# Begin Source File + +SOURCE=..\schedule.h +# End Source File +# Begin Source File + +SOURCE=..\scriptevent.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=..\talkmonster.h +# End Source File +# Begin Source File + +SOURCE=..\teamplay_gamerules.h +# End Source File +# Begin Source File + +SOURCE=..\trains.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/hlsdk/dlls/msvc7/mp.vcproj b/hlsdk/dlls/msvc7/mp.vcproj new file mode 100644 index 00000000..706b8c05 --- /dev/null +++ b/hlsdk/dlls/msvc7/mp.vcproj @@ -0,0 +1,2155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hlsdk/dlls/msvc8/mp.vcproj b/hlsdk/dlls/msvc8/mp.vcproj new file mode 100644 index 00000000..3cea7d8f --- /dev/null +++ b/hlsdk/dlls/msvc8/mp.vcproj @@ -0,0 +1,2741 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hlsdk/dlls/multiplay_gamerules.cpp b/hlsdk/dlls/multiplay_gamerules.cpp new file mode 100644 index 00000000..7b3e47a0 --- /dev/null +++ b/hlsdk/dlls/multiplay_gamerules.cpp @@ -0,0 +1,1699 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// teamplay_gamerules.cpp +// + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" + +#include "skill.h" +#include "game.h" +#include "items.h" +#include "voice_gamemgr.h" +#include "hltv.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 gmsgServerName; + +extern int g_teamplay; + +#define ITEM_RESPAWN_TIME 30 +#define WEAPON_RESPAWN_TIME 20 +#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_teamplay ) + { + 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; + + // 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; + + return CGameRules::ClientCommand(pPlayer, pcmd); +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay::RefreshSkillData( void ) +{ +// load all default values + CGameRules::RefreshSkillData(); + +// override some values for multiplay. + + // suitcharger + gSkillData.suitchargerCapacity = 30; + + // Crowbar whack + gSkillData.plrDmgCrowbar = 25; + + // Glock Round + gSkillData.plrDmg9MM = 12; + + // 357 Round + gSkillData.plrDmg357 = 40; + + // MP5 Round + gSkillData.plrDmgMP5 = 12; + + // M203 grenade + gSkillData.plrDmgM203Grenade = 100; + + // Shotgun buckshot + gSkillData.plrDmgBuckshot = 20;// fewer pellets in deathmatch + + // Crossbow + gSkillData.plrDmgCrossbowClient = 20; + + // RPG + gSkillData.plrDmgRPG = 120; + + // Egon + gSkillData.plrDmgEgonWide = 20; + gSkillData.plrDmgEgonNarrow = 10; + + // Hand Grendade + gSkillData.plrDmgHandGrenade = 100; + + // Satchel Charge + gSkillData.plrDmgSatchel = 120; + + // Tripmine + gSkillData.plrDmgTripmine = 150; + + // hornet + gSkillData.plrDmgHornet = 10; +} + +// 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 < 1 ) + CVAR_SET_STRING( "mp_chattime", "1" ); + 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 = static_cast(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; +} + + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsMultiplayer( void ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsDeathmatch( void ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsCoOp( void ) +{ + return static_cast(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; + } + + if ( pWeapon->iWeight() > pPlayer->m_pActiveItem->iWeight() ) + { + 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 ) + { + 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_teamplay ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" entered the game\n", + STRING( pl->pev->netname ), + GETPLAYERUSERID( pl->edict() ), + GETPLAYERAUTHID( pl->edict() ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pl->edict() ), "model" ) ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" entered the game\n", + STRING( pl->pev->netname ), + GETPLAYERUSERID( pl->edict() ), + GETPLAYERAUTHID( 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(); + + 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( static_cast(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_teamplay ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" disconnected\n", + STRING( pPlayer->pev->netname ), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model" ) ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" disconnected\n", + STRING( pPlayer->pev->netname ), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + GETPLAYERUSERID( pPlayer->edict() ) ); + } + + pPlayer->RemoveAllItems( TRUE );// destroy all of the players weapons and items + } + } +} + +//========================================================= +//========================================================= +float CHalfLifeMultiplay :: FlPlayerFallDamage( CBasePlayer *pPlayer ) +{ + int iFallDamage = (int)falldamage.value; + + switch ( iFallDamage ) + { + case 1://progressive + pPlayer->m_flFallVelocity -= PLAYER_MAX_SAFE_FALL_SPEED; + return pPlayer->m_flFallVelocity * DAMAGE_FOR_FALL_SPEED; + break; + default: + case 0:// fixed + return 10; + break; + } +} + +//========================================================= +//========================================================= +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; + + pPlayer->pev->weapons |= (1<Touch( pPlayer ); + addDefault = FALSE; + } + + if ( addDefault ) + { + pPlayer->GiveNamedItem( "weapon_crowbar" ); + pPlayer->GiveNamedItem( "weapon_9mmhandgun" ); + pPlayer->GiveAmmo( 68, "9mm", _9MM_MAX_CARRY );// 4 full reloads + } +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay :: FPlayerCanRespawn( CBasePlayer *pPlayer ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +float CHalfLifeMultiplay :: FlPlayerSpawnTime( CBasePlayer *pPlayer ) +{ + return gpGlobals->time;//now! +} + +BOOL CHalfLifeMultiplay :: AllowAutoTargetCrosshair( void ) +{ + return ( aimcrosshair.value != 0 ); +} + +//========================================================= +// 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() ) + { + // if a player dies in a deathmatch game and the killer is a client, award the killer some points + pKiller->frags += IPointsForKill( peKiller, pVictim ); + + 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( static_cast(pVictim->pev->frags) ); + WRITE_SHORT( pVictim->m_iDeaths ); + WRITE_SHORT( 0 ); + 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( static_cast(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; + } +#ifndef HLDEMO_BUILD + if ( pVictim->HasNamedPlayerItem("weapon_satchel") ) + { + DeactivateSatchels( pVictim ); + } +#endif +} + +//========================================================= +// 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::Instance( pKiller ); + + const char *killer_weapon_name = "world"; // by default, the player is killed by the world + int killer_index = 0; + + // Hack to fix name change + char *tau = "tau_cannon"; + char *gluon = "gluon gun"; + + 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; + + MESSAGE_BEGIN( MSG_ALL, gmsgDeathMsg ); + WRITE_BYTE( killer_index ); // the killer + WRITE_BYTE( ENTINDEX(pVictim->edict()) ); // the victim + WRITE_STRING( killer_weapon_name ); // what they were killed by (should this be a string?) + MESSAGE_END(); + + // replace the code names with the 'real' names + if ( !strcmp( killer_weapon_name, "egon" ) ) + killer_weapon_name = gluon; + else if ( !strcmp( killer_weapon_name, "gauss" ) ) + killer_weapon_name = tau; + + if ( pVictim->pev == pKiller ) + { + // killed self + + // team match? + if ( g_teamplay ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" committed suicide with \"%s\"\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pVictim->edict() ), "model" ), + killer_weapon_name ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" committed suicide with \"%s\"\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); + } + } + else if ( pKiller->flags & FL_CLIENT ) + { + // team match? + if ( g_teamplay ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" killed \"%s<%i><%s><%s>\" with \"%s\"\n", + STRING( pKiller->netname ), + GETPLAYERUSERID( ENT(pKiller) ), + GETPLAYERAUTHID( ENT(pKiller) ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( ENT(pKiller) ), "model" ), + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pVictim->edict() ), "model" ), + killer_weapon_name ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" killed \"%s<%i><%s><%i>\" with \"%s\"\n", + STRING( pKiller->netname ), + GETPLAYERUSERID( ENT(pKiller) ), + GETPLAYERAUTHID( ENT(pKiller) ), + GETPLAYERUSERID( ENT(pKiller) ), + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); + } + } + else + { + // killed by the world + + // team match? + if ( g_teamplay ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" committed suicide with \"%s\" (world)\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pVictim->edict() ), "model" ), + killer_weapon_name ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" committed suicide with \"%s\" (world)\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); + } + } + + MESSAGE_BEGIN( MSG_SPEC, SVC_DIRECTOR ); + WRITE_BYTE ( 9 ); // command length in bytes + WRITE_BYTE ( DRC_CMD_EVENT ); // player killed + WRITE_SHORT( ENTINDEX(pVictim->edict()) ); // index number of primary entity + 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); // eventflags (priority and flags) + 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 + } + } + + 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; + + 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_ACTIVE; +} + +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 < 1 ) + CVAR_SET_STRING( "mp_chattime", "1" ); + 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 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]; + + 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(); + } + + FREE_FILE( aFileList ); +} + + diff --git a/hlsdk/dlls/nodes.h b/hlsdk/dlls/nodes.h new file mode 100644 index 00000000..f610bbc4 --- /dev/null +++ b/hlsdk/dlls/nodes.h @@ -0,0 +1,54 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// nodes.h +//========================================================= + +#ifndef NODES_H +#define NODES_H + +#define bits_NODE_GROUP_REALM 1 + +class CLink +{ +public: + entvars_t *m_pLinkEnt;// the entity that blocks this connection (doors, etc) +}; + + +class CGraph +{ +public: + BOOL m_fGraphPresent;// is the graph in memory? + BOOL m_fGraphPointersSet;// are the entity pointers for the graph all set? + + int m_cLinks;// total number of links + CLink *m_pLinkPool;// big list of all node connections + + void InitGraph( void ); + int AllocNodes ( void ); + + int CheckNODFile(char *szMapName); + int FLoadGraph(char *szMapName); + int FSetGraphPointers(void); + void ShowNodeConnections ( int iNode ); + int FindNearestNode ( const Vector &vecOrigin, CBaseEntity *pEntity ); + int FindNearestNode ( const Vector &vecOrigin, int afNodeTypes ); + +}; + +extern CGraph WorldGraph; + +#endif // NODES_H diff --git a/hlsdk/dlls/pathcorner.cpp b/hlsdk/dlls/pathcorner.cpp new file mode 100644 index 00000000..b2c1e3f5 --- /dev/null +++ b/hlsdk/dlls/pathcorner.cpp @@ -0,0 +1,428 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// ========================== PATH_CORNER =========================== +// + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "trains.h" +#include "saverestore.h" + +class CPathCorner : public CPointEntity +{ +public: + void Spawn( ); + void KeyValue( KeyValueData* pkvd ); + float GetDelay( void ) { return m_flWait; } +// void Touch( CBaseEntity *pOther ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + float m_flWait; +}; + +LINK_ENTITY_TO_CLASS( path_corner, CPathCorner ); + +// Global Savedata for Delay +TYPEDESCRIPTION CPathCorner::m_SaveData[] = +{ + DEFINE_FIELD( CPathCorner, m_flWait, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CPathCorner, CPointEntity ); + +// +// Cache user-entity-field values until spawn is called. +// +void CPathCorner :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CPathCorner :: Spawn( ) +{ + ASSERTSZ(!FStringNull(pev->targetname), "path_corner without a targetname"); +} + +#if 0 +void CPathCorner :: Touch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + + if ( FBitSet ( pevToucher->flags, FL_MONSTER ) ) + {// monsters don't navigate path corners based on touch anymore + return; + } + + // If OTHER isn't explicitly looking for this path_corner, bail out + if ( pOther->m_pGoalEnt != this ) + { + return; + } + + // If OTHER has an enemy, this touch is incidental, ignore + if ( !FNullEnt(pevToucher->enemy) ) + { + return; // fighting, not following a path + } + + // UNDONE: support non-zero flWait + /* + if (m_flWait != 0) + ALERT(at_warning, "Non-zero path-cornder waits NYI"); + */ + + // Find the next "stop" on the path, make it the goal of the "toucher". + if (FStringNull(pev->target)) + { + ALERT(at_warning, "PathCornerTouch: no next stop specified"); + } + + pOther->m_pGoalEnt = CBaseEntity::Instance( FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(pev->target) ) ); + + // If "next spot" was not found (does not exist - level design error) + if ( !pOther->m_pGoalEnt ) + { + ALERT(at_console, "PathCornerTouch--%s couldn't find next stop in path: %s", STRING(pev->classname), STRING(pev->target)); + return; + } + + // Turn towards the next stop in the path. + pevToucher->ideal_yaw = UTIL_VecToYaw ( pOther->m_pGoalEnt->pev->origin - pevToucher->origin ); +} +#endif + + + +TYPEDESCRIPTION CPathTrack::m_SaveData[] = +{ + DEFINE_FIELD( CPathTrack, m_length, FIELD_FLOAT ), + DEFINE_FIELD( CPathTrack, m_pnext, FIELD_CLASSPTR ), + DEFINE_FIELD( CPathTrack, m_paltpath, FIELD_CLASSPTR ), + DEFINE_FIELD( CPathTrack, m_pprevious, FIELD_CLASSPTR ), + DEFINE_FIELD( CPathTrack, m_altName, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CPathTrack, CBaseEntity ); +LINK_ENTITY_TO_CLASS( path_track, CPathTrack ); + +// +// Cache user-entity-field values until spawn is called. +// +void CPathTrack :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "altpath")) + { + m_altName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CPathTrack :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int on; + + // Use toggles between two paths + if ( m_paltpath ) + { + on = !FBitSet( pev->spawnflags, SF_PATH_ALTERNATE ); + if ( ShouldToggle( useType, on ) ) + { + if ( on ) + SetBits( pev->spawnflags, SF_PATH_ALTERNATE ); + else + ClearBits( pev->spawnflags, SF_PATH_ALTERNATE ); + } + } + else // Use toggles between enabled/disabled + { + on = !FBitSet( pev->spawnflags, SF_PATH_DISABLED ); + + if ( ShouldToggle( useType, on ) ) + { + if ( on ) + SetBits( pev->spawnflags, SF_PATH_DISABLED ); + else + ClearBits( pev->spawnflags, SF_PATH_DISABLED ); + } + } +} + + +void CPathTrack :: Link( void ) +{ + edict_t *pentTarget; + + if ( !FStringNull(pev->target) ) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) ); + if ( !FNullEnt(pentTarget) ) + { + m_pnext = CPathTrack::Instance( pentTarget ); + + if ( m_pnext ) // If no next pointer, this is the end of a path + { + m_pnext->SetPrevious( this ); + } + } + else + ALERT( at_console, "Dead end link %s\n", STRING(pev->target) ); + } + + // Find "alternate" path + if ( m_altName ) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_altName) ); + if ( !FNullEnt(pentTarget) ) + { + m_paltpath = CPathTrack::Instance( pentTarget ); + + if ( m_paltpath ) // If no next pointer, this is the end of a path + { + m_paltpath->SetPrevious( this ); + } + } + } +} + + +void CPathTrack :: Spawn( void ) +{ + pev->solid = SOLID_TRIGGER; + UTIL_SetSize(pev, Vector(-8, -8, -8), Vector(8, 8, 8)); + + m_pnext = NULL; + m_pprevious = NULL; +// DEBUGGING CODE +#if PATH_SPARKLE_DEBUG + SetThink( Sparkle ); + pev->nextthink = gpGlobals->time + 0.5; +#endif +} + + +void CPathTrack::Activate( void ) +{ + if ( !FStringNull( pev->targetname ) ) // Link to next, and back-link + Link(); +} + +CPathTrack *CPathTrack :: ValidPath( CPathTrack *ppath, int testFlag ) +{ + if ( !ppath ) + return NULL; + + if ( testFlag && FBitSet( ppath->pev->spawnflags, SF_PATH_DISABLED ) ) + return NULL; + + return ppath; +} + + +void CPathTrack :: Project( CPathTrack *pstart, CPathTrack *pend, Vector *origin, float dist ) +{ + if ( pstart && pend ) + { + Vector dir = (pend->pev->origin - pstart->pev->origin); + dir = dir.Normalize(); + *origin = pend->pev->origin + dir * dist; + } +} + +CPathTrack *CPathTrack::GetNext( void ) +{ + if ( m_paltpath && FBitSet( pev->spawnflags, SF_PATH_ALTERNATE ) && !FBitSet( pev->spawnflags, SF_PATH_ALTREVERSE ) ) + return m_paltpath; + + return m_pnext; +} + + + +CPathTrack *CPathTrack::GetPrevious( void ) +{ + if ( m_paltpath && FBitSet( pev->spawnflags, SF_PATH_ALTERNATE ) && FBitSet( pev->spawnflags, SF_PATH_ALTREVERSE ) ) + return m_paltpath; + + return m_pprevious; +} + + + +void CPathTrack::SetPrevious( CPathTrack *pprev ) +{ + // Only set previous if this isn't my alternate path + if ( pprev && !FStrEq( STRING(pprev->pev->targetname), STRING(m_altName) ) ) + m_pprevious = pprev; +} + + +// Assumes this is ALWAYS enabled +CPathTrack *CPathTrack :: LookAhead( Vector *origin, float dist, int move ) +{ + CPathTrack *pcurrent; + float originalDist = dist; + + pcurrent = this; + Vector currentPos = *origin; + + if ( dist < 0 ) // Travelling backwards through path + { + dist = -dist; + while ( dist > 0 ) + { + Vector dir = pcurrent->pev->origin - currentPos; + float length = dir.Length(); + if ( !length ) + { + if ( !ValidPath(pcurrent->GetPrevious(), move) ) // If there is no previous node, or it's disabled, return now. + { + if ( !move ) + Project( pcurrent->GetNext(), pcurrent, origin, dist ); + return NULL; + } + pcurrent = pcurrent->GetPrevious(); + } + else if ( length > dist ) // enough left in this path to move + { + *origin = currentPos + (dir * (dist / length)); + return pcurrent; + } + else + { + dist -= length; + currentPos = pcurrent->pev->origin; + *origin = currentPos; + if ( !ValidPath(pcurrent->GetPrevious(), move) ) // If there is no previous node, or it's disabled, return now. + return NULL; + + pcurrent = pcurrent->GetPrevious(); + } + } + *origin = currentPos; + return pcurrent; + } + else + { + while ( dist > 0 ) + { + if ( !ValidPath(pcurrent->GetNext(), move) ) // If there is no next node, or it's disabled, return now. + { + if ( !move ) + Project( pcurrent->GetPrevious(), pcurrent, origin, dist ); + return NULL; + } + Vector dir = pcurrent->GetNext()->pev->origin - currentPos; + float length = dir.Length(); + if ( !length && !ValidPath( pcurrent->GetNext()->GetNext(), move ) ) + { + if ( dist == originalDist ) // HACK -- up against a dead end + return NULL; + return pcurrent; + } + if ( length > dist ) // enough left in this path to move + { + *origin = currentPos + (dir * (dist / length)); + return pcurrent; + } + else + { + dist -= length; + currentPos = pcurrent->GetNext()->pev->origin; + pcurrent = pcurrent->GetNext(); + *origin = currentPos; + } + } + *origin = currentPos; + } + + return pcurrent; +} + + +// Assumes this is ALWAYS enabled +CPathTrack *CPathTrack :: Nearest( Vector origin ) +{ + int deadCount; + float minDist, dist; + Vector delta; + CPathTrack *ppath, *pnearest; + + + delta = origin - pev->origin; + delta.z = 0; + minDist = delta.Length(); + pnearest = this; + ppath = GetNext(); + + // Hey, I could use the old 2 racing pointers solution to this, but I'm lazy :) + deadCount = 0; + while ( ppath && ppath != this ) + { + deadCount++; + if ( deadCount > 9999 ) + { + ALERT( at_error, "Bad sequence of path_tracks from %s", STRING(pev->targetname) ); + return NULL; + } + delta = origin - ppath->pev->origin; + delta.z = 0; + dist = delta.Length(); + if ( dist < minDist ) + { + minDist = dist; + pnearest = ppath; + } + ppath = ppath->GetNext(); + } + return pnearest; +} + + +CPathTrack *CPathTrack::Instance( edict_t *pent ) +{ + if ( FClassnameIs( pent, "path_track" ) ) + return (CPathTrack *)GET_PRIVATE(pent); + return NULL; +} + + + // DEBUGGING CODE +#if PATH_SPARKLE_DEBUG +void CPathTrack :: Sparkle( void ) +{ + + pev->nextthink = gpGlobals->time + 0.2; + if ( FBitSet( pev->spawnflags, SF_PATH_DISABLED ) ) + UTIL_ParticleEffect(pev->origin, Vector(0,0,100), 210, 10); + else + UTIL_ParticleEffect(pev->origin, Vector(0,0,100), 84, 10); +} +#endif + diff --git a/hlsdk/dlls/plane.cpp b/hlsdk/dlls/plane.cpp new file mode 100644 index 00000000..39a91c32 --- /dev/null +++ b/hlsdk/dlls/plane.cpp @@ -0,0 +1,60 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "plane.h" + +//========================================================= +// Plane +//========================================================= +CPlane :: CPlane ( void ) +{ + m_fInitialized = FALSE; +} + +//========================================================= +// InitializePlane - Takes a normal for the plane and a +// point on the plane and +//========================================================= +void CPlane :: InitializePlane ( const Vector &vecNormal, const Vector &vecPoint ) +{ + m_vecNormal = vecNormal; + m_flDist = DotProduct ( m_vecNormal, vecPoint ); + m_fInitialized = TRUE; +} + + +//========================================================= +// PointInFront - determines whether the given vector is +// in front of the plane. +//========================================================= +BOOL CPlane :: PointInFront ( const Vector &vecPoint ) +{ + float flFace; + + if ( !m_fInitialized ) + { + return FALSE; + } + + flFace = DotProduct ( m_vecNormal, vecPoint ) - m_flDist; + + if ( flFace >= 0 ) + { + return TRUE; + } + + return FALSE; +} + diff --git a/hlsdk/dlls/plane.h b/hlsdk/dlls/plane.h new file mode 100644 index 00000000..af70f1cc --- /dev/null +++ b/hlsdk/dlls/plane.h @@ -0,0 +1,43 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PLANE_H +#define PLANE_H + +//========================================================= +// Plane +//========================================================= +class CPlane +{ +public: + CPlane ( void ); + + //========================================================= + // InitializePlane - Takes a normal for the plane and a + // point on the plane and + //========================================================= + void InitializePlane ( const Vector &vecNormal, const Vector &vecPoint ); + + //========================================================= + // PointInFront - determines whether the given vector is + // in front of the plane. + //========================================================= + BOOL PointInFront ( const Vector &vecPoint ); + + Vector m_vecNormal; + float m_flDist; + BOOL m_fInitialized; +}; + +#endif // PLANE_H diff --git a/hlsdk/dlls/plats.cpp b/hlsdk/dlls/plats.cpp new file mode 100644 index 00000000..5b727f79 --- /dev/null +++ b/hlsdk/dlls/plats.cpp @@ -0,0 +1,2285 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== plats.cpp ======================================================== + + spawn, think, and touch functions for trains, etc + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "trains.h" +#include "saverestore.h" + +static void PlatSpawnInsideTrigger(entvars_t* pevPlatform); + +#define SF_PLAT_TOGGLE 0x0001 + +class CBasePlatTrain : public CBaseToggle +{ +public: + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + void KeyValue( KeyValueData* pkvd); + void Precache( void ); + + // This is done to fix spawn flag collisions between this class and a derived class + virtual BOOL IsTogglePlat( void ) { return (pev->spawnflags & SF_PLAT_TOGGLE) ? TRUE : FALSE; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + BYTE m_bMoveSnd; // sound a plat makes while moving + BYTE m_bStopSnd; // sound a plat makes when it stops + float m_volume; // Sound volume +}; + +TYPEDESCRIPTION CBasePlatTrain::m_SaveData[] = +{ + DEFINE_FIELD( CBasePlatTrain, m_bMoveSnd, FIELD_CHARACTER ), + DEFINE_FIELD( CBasePlatTrain, m_bStopSnd, FIELD_CHARACTER ), + DEFINE_FIELD( CBasePlatTrain, m_volume, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CBasePlatTrain, CBaseToggle ); + +void CBasePlatTrain :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "lip")) + { + m_flLip = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "height")) + { + m_flHeight = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "rotation")) + { + m_vecFinalAngle.x = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "movesnd")) + { + m_bMoveSnd = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "stopsnd")) + { + m_bStopSnd = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "volume")) + { + m_volume = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +#define noiseMoving noise +#define noiseArrived noise1 + +void CBasePlatTrain::Precache( void ) +{ +// set the plat's "in-motion" sound + switch (m_bMoveSnd) + { + case 0: + pev->noiseMoving = MAKE_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("plats/bigmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/bigmove1.wav"); + break; + case 2: + PRECACHE_SOUND ("plats/bigmove2.wav"); + pev->noiseMoving = MAKE_STRING("plats/bigmove2.wav"); + break; + case 3: + PRECACHE_SOUND ("plats/elevmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/elevmove1.wav"); + break; + case 4: + PRECACHE_SOUND ("plats/elevmove2.wav"); + pev->noiseMoving = MAKE_STRING("plats/elevmove2.wav"); + break; + case 5: + PRECACHE_SOUND ("plats/elevmove3.wav"); + pev->noiseMoving = MAKE_STRING("plats/elevmove3.wav"); + break; + case 6: + PRECACHE_SOUND ("plats/freightmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/freightmove1.wav"); + break; + case 7: + PRECACHE_SOUND ("plats/freightmove2.wav"); + pev->noiseMoving = MAKE_STRING("plats/freightmove2.wav"); + break; + case 8: + PRECACHE_SOUND ("plats/heavymove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/heavymove1.wav"); + break; + case 9: + PRECACHE_SOUND ("plats/rackmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/rackmove1.wav"); + break; + case 10: + PRECACHE_SOUND ("plats/railmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/railmove1.wav"); + break; + case 11: + PRECACHE_SOUND ("plats/squeekmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/squeekmove1.wav"); + break; + case 12: + PRECACHE_SOUND ("plats/talkmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/talkmove1.wav"); + break; + case 13: + PRECACHE_SOUND ("plats/talkmove2.wav"); + pev->noiseMoving = MAKE_STRING("plats/talkmove2.wav"); + break; + default: + pev->noiseMoving = MAKE_STRING("common/null.wav"); + break; + } + +// set the plat's 'reached destination' stop sound + switch (m_bStopSnd) + { + case 0: + pev->noiseArrived = MAKE_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("plats/bigstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/bigstop1.wav"); + break; + case 2: + PRECACHE_SOUND ("plats/bigstop2.wav"); + pev->noiseArrived = MAKE_STRING("plats/bigstop2.wav"); + break; + case 3: + PRECACHE_SOUND ("plats/freightstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/freightstop1.wav"); + break; + case 4: + PRECACHE_SOUND ("plats/heavystop2.wav"); + pev->noiseArrived = MAKE_STRING("plats/heavystop2.wav"); + break; + case 5: + PRECACHE_SOUND ("plats/rackstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/rackstop1.wav"); + break; + case 6: + PRECACHE_SOUND ("plats/railstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/railstop1.wav"); + break; + case 7: + PRECACHE_SOUND ("plats/squeekstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/squeekstop1.wav"); + break; + case 8: + PRECACHE_SOUND ("plats/talkstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/talkstop1.wav"); + break; + + default: + pev->noiseArrived = MAKE_STRING("common/null.wav"); + break; + } +} + +// +//====================== PLAT code ==================================================== +// + + +#define noiseMovement noise +#define noiseStopMoving noise1 + +class CFuncPlat : public CBasePlatTrain +{ +public: + void Spawn( void ); + void Precache( void ); + void Setup( void ); + + virtual void Blocked( CBaseEntity *pOther ); + + + void EXPORT PlatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + void EXPORT CallGoDown( void ) { GoDown(); } + void EXPORT CallHitTop( void ) { HitTop(); } + void EXPORT CallHitBottom( void ) { HitBottom(); } + + virtual void GoUp( void ); + virtual void GoDown( void ); + virtual void HitTop( void ); + virtual void HitBottom( void ); +}; +LINK_ENTITY_TO_CLASS( func_plat, CFuncPlat ); + + +// UNDONE: Need to save this!!! It needs class & linkage +class CPlatTrigger : public CBaseEntity +{ +public: + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DONT_SAVE; } + void SpawnInsideTrigger( CFuncPlat *pPlatform ); + void Touch( CBaseEntity *pOther ); + CFuncPlat *m_pPlatform; +}; + + + +/*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER +speed default 150 + +Plats are always drawn in the extended position, so they will light correctly. + +If the plat is the target of another trigger or button, it will start out disabled in +the extended position until it is trigger, when it will lower and become a normal plat. + +If the "height" key is set, that will determine the amount the plat moves, instead of +being implicitly determined by the model's height. + +Set "sounds" to one of the following: +1) base fast +2) chain slow +*/ + +void CFuncPlat :: Setup( void ) +{ + //pev->noiseMovement = MAKE_STRING("plats/platmove1.wav"); + //pev->noiseStopMoving = MAKE_STRING("plats/platstop1.wav"); + + if (m_flTLength == 0) + m_flTLength = 80; + if (m_flTWidth == 0) + m_flTWidth = 10; + + pev->angles = g_vecZero; + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); // set size and link into world + UTIL_SetSize(pev, pev->mins, pev->maxs); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + // vecPosition1 is the top position, vecPosition2 is the bottom + m_vecPosition1 = pev->origin; + m_vecPosition2 = pev->origin; + if (m_flHeight != 0) + m_vecPosition2.z = pev->origin.z - m_flHeight; + else + m_vecPosition2.z = pev->origin.z - pev->size.z + 8; + if (pev->speed == 0) + pev->speed = 150; + + if ( m_volume == 0 ) + m_volume = 0.85; +} + + +void CFuncPlat :: Precache( ) +{ + CBasePlatTrain::Precache(); + //PRECACHE_SOUND("plats/platmove1.wav"); + //PRECACHE_SOUND("plats/platstop1.wav"); + if ( !IsTogglePlat() ) + PlatSpawnInsideTrigger( pev ); // the "start moving" trigger +} + + +void CFuncPlat :: Spawn( ) +{ + Setup(); + + Precache(); + + // If this platform is the target of some button, it starts at the TOP position, + // and is brought down by that button. Otherwise, it starts at BOTTOM. + if ( !FStringNull(pev->targetname) ) + { + UTIL_SetOrigin (pev, m_vecPosition1); + m_toggle_state = TS_AT_TOP; + SetUse( &CFuncPlat::PlatUse ); + } + else + { + UTIL_SetOrigin (pev, m_vecPosition2); + m_toggle_state = TS_AT_BOTTOM; + } +} + + + +static void PlatSpawnInsideTrigger(entvars_t* pevPlatform) +{ + GetClassPtr( (CPlatTrigger *)NULL)->SpawnInsideTrigger( GetClassPtr( (CFuncPlat *)pevPlatform ) ); +} + + +// +// Create a trigger entity for a platform. +// +void CPlatTrigger :: SpawnInsideTrigger( CFuncPlat *pPlatform ) +{ + m_pPlatform = pPlatform; + // Create trigger entity, "point" it at the owning platform, give it a touch method + pev->solid = SOLID_TRIGGER; + pev->movetype = MOVETYPE_NONE; + pev->origin = pPlatform->pev->origin; + + // Establish the trigger field's size + Vector vecTMin = m_pPlatform->pev->mins + Vector ( 25 , 25 , 0 ); + Vector vecTMax = m_pPlatform->pev->maxs + Vector ( 25 , 25 , 8 ); + vecTMin.z = vecTMax.z - ( m_pPlatform->m_vecPosition1.z - m_pPlatform->m_vecPosition2.z + 8 ); + if (m_pPlatform->pev->size.x <= 50) + { + vecTMin.x = (m_pPlatform->pev->mins.x + m_pPlatform->pev->maxs.x) / 2; + vecTMax.x = vecTMin.x + 1; + } + if (m_pPlatform->pev->size.y <= 50) + { + vecTMin.y = (m_pPlatform->pev->mins.y + m_pPlatform->pev->maxs.y) / 2; + vecTMax.y = vecTMin.y + 1; + } + UTIL_SetSize ( pev, vecTMin, vecTMax ); +} + + +// +// When the platform's trigger field is touched, the platform ??? +// +void CPlatTrigger :: Touch( CBaseEntity *pOther ) +{ + // Ignore touches by non-players + entvars_t* pevToucher = pOther->pev; + if ( !FClassnameIs (pevToucher, "player") ) + return; + + // Ignore touches by corpses + if (!pOther->IsAlive()) + return; + + // Make linked platform go up/down. + if (m_pPlatform->m_toggle_state == TS_AT_BOTTOM) + m_pPlatform->GoUp(); + else if (m_pPlatform->m_toggle_state == TS_AT_TOP) + m_pPlatform->pev->nextthink = m_pPlatform->pev->ltime + 1;// delay going down +} + + +// +// Used by SUB_UseTargets, when a platform is the target of a button. +// Start bringing platform down. +// +void CFuncPlat :: PlatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( IsTogglePlat() ) + { + // Top is off, bottom is on + BOOL on = (m_toggle_state == TS_AT_BOTTOM) ? TRUE : FALSE; + + if ( !ShouldToggle( useType, on ) ) + return; + + if (m_toggle_state == TS_AT_TOP) + GoDown(); + else if ( m_toggle_state == TS_AT_BOTTOM ) + GoUp(); + } + else + { + SetUse( NULL ); + + if (m_toggle_state == TS_AT_TOP) + GoDown(); + } +} + + +// +// Platform is at top, now starts moving down. +// +void CFuncPlat :: GoDown( void ) +{ + if(pev->noiseMovement) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_AT_TOP || m_toggle_state == TS_GOING_UP); + m_toggle_state = TS_GOING_DOWN; + SetMoveDone(&CFuncPlat::CallHitBottom); + LinearMove(m_vecPosition2, pev->speed); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncPlat :: HitBottom( void ) +{ + if(pev->noiseMovement) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement)); + + if (pev->noiseStopMoving) + EMIT_SOUND(ENT(pev), CHAN_WEAPON, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_AT_BOTTOM; +} + + +// +// Platform is at bottom, now starts moving up +// +void CFuncPlat :: GoUp( void ) +{ + if (pev->noiseMovement) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_AT_BOTTOM || m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_GOING_UP; + SetMoveDone(&CFuncPlat::CallHitTop); + LinearMove(m_vecPosition1, pev->speed); +} + + +// +// Platform has hit top. Pauses, then starts back down again. +// +void CFuncPlat :: HitTop( void ) +{ + if(pev->noiseMovement) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement)); + + if (pev->noiseStopMoving) + EMIT_SOUND(ENT(pev), CHAN_WEAPON, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_GOING_UP); + m_toggle_state = TS_AT_TOP; + + if ( !IsTogglePlat() ) + { + // After a delay, the platform will automatically start going down again. + SetThink( &CFuncPlat::CallGoDown ); + pev->nextthink = pev->ltime + 3; + } +} + + +void CFuncPlat :: Blocked( CBaseEntity *pOther ) +{ + ALERT( at_aiconsole, "%s Blocked by %s\n", STRING(pev->classname), STRING(pOther->pev->classname) ); + // Hurt the blocker a little + pOther->TakeDamage(pev, pev, 1, DMG_CRUSH); + + if(pev->noiseMovement) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement)); + + // Send the platform back where it came from + ASSERT(m_toggle_state == TS_GOING_UP || m_toggle_state == TS_GOING_DOWN); + if (m_toggle_state == TS_GOING_UP) + GoDown(); + else if (m_toggle_state == TS_GOING_DOWN) + GoUp (); +} + + +class CFuncPlatRot : public CFuncPlat +{ +public: + void Spawn( void ); + void SetupRotation( void ); + + virtual void GoUp( void ); + virtual void GoDown( void ); + virtual void HitTop( void ); + virtual void HitBottom( void ); + + void RotMove( Vector &destAngle, float time ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + Vector m_end, m_start; +}; +LINK_ENTITY_TO_CLASS( func_platrot, CFuncPlatRot ); +TYPEDESCRIPTION CFuncPlatRot::m_SaveData[] = +{ + DEFINE_FIELD( CFuncPlatRot, m_end, FIELD_VECTOR ), + DEFINE_FIELD( CFuncPlatRot, m_start, FIELD_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CFuncPlatRot, CFuncPlat ); + + +void CFuncPlatRot :: SetupRotation( void ) +{ + if ( m_vecFinalAngle.x != 0 ) // This plat rotates too! + { + CBaseToggle :: AxisDir( pev ); + m_start = pev->angles; + m_end = pev->angles + pev->movedir * m_vecFinalAngle.x; + } + else + { + m_start = g_vecZero; + m_end = g_vecZero; + } + if ( !FStringNull(pev->targetname) ) // Start at top + { + pev->angles = m_end; + } +} + + +void CFuncPlatRot :: Spawn( void ) +{ + CFuncPlat :: Spawn(); + SetupRotation(); +} + +void CFuncPlatRot :: GoDown( void ) +{ + CFuncPlat :: GoDown(); + RotMove( m_start, pev->nextthink - pev->ltime ); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncPlatRot :: HitBottom( void ) +{ + CFuncPlat :: HitBottom(); + pev->avelocity = g_vecZero; + pev->angles = m_start; +} + + +// +// Platform is at bottom, now starts moving up +// +void CFuncPlatRot :: GoUp( void ) +{ + CFuncPlat :: GoUp(); + RotMove( m_end, pev->nextthink - pev->ltime ); +} + + +// +// Platform has hit top. Pauses, then starts back down again. +// +void CFuncPlatRot :: HitTop( void ) +{ + CFuncPlat :: HitTop(); + pev->avelocity = g_vecZero; + pev->angles = m_end; +} + + +void CFuncPlatRot :: RotMove( Vector &destAngle, float time ) +{ + // set destdelta to the vector needed to move + Vector vecDestDelta = destAngle - pev->angles; + + // Travel time is so short, we're practically there already; so make it so. + if ( time >= 0.1) + pev->avelocity = vecDestDelta / time; + else + { + pev->avelocity = vecDestDelta; + pev->nextthink = pev->ltime + 1; + } +} + + +// +//====================== TRAIN code ================================================== +// + +class CFuncTrain : public CBasePlatTrain +{ +public: + void Spawn( void ); + void Precache( void ); + void Activate( void ); + void OverrideReset( void ); + + void Blocked( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + + void EXPORT Wait( void ); + void EXPORT Next( void ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + entvars_t *m_pevCurrentTarget; + int m_sounds; + BOOL m_activated; +}; + +LINK_ENTITY_TO_CLASS( func_train, CFuncTrain ); +TYPEDESCRIPTION CFuncTrain::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTrain, m_sounds, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrain, m_pevCurrentTarget, FIELD_EVARS ), + DEFINE_FIELD( CFuncTrain, m_activated, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTrain, CBasePlatTrain ); + + +void CFuncTrain :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBasePlatTrain::KeyValue( pkvd ); +} + + +void CFuncTrain :: Blocked( CBaseEntity *pOther ) + +{ + if ( gpGlobals->time < m_flActivateFinished) + return; + + m_flActivateFinished = gpGlobals->time + 0.5; + + pOther->TakeDamage(pev, pev, pev->dmg, DMG_CRUSH); +} + + +void CFuncTrain :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->spawnflags & SF_TRAIN_WAIT_RETRIGGER ) + { + // Move toward my target + pev->spawnflags &= ~SF_TRAIN_WAIT_RETRIGGER; + Next(); + } + else + { + pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER; + // Pop back to last target if it's available + if ( pev->enemy ) + pev->target = pev->enemy->v.targetname; + pev->nextthink = 0; + pev->velocity = g_vecZero; + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + } +} + + +void CFuncTrain :: Wait( void ) +{ + // Fire the pass target if there is one + if ( m_pevCurrentTarget->message ) + { + FireTargets( STRING(m_pevCurrentTarget->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( m_pevCurrentTarget->spawnflags, SF_CORNER_FIREONCE ) ) + m_pevCurrentTarget->message = 0; + } + + // need pointer to LAST target. + if ( FBitSet (m_pevCurrentTarget->spawnflags , SF_TRAIN_WAIT_RETRIGGER ) || ( pev->spawnflags & SF_TRAIN_WAIT_RETRIGGER ) ) + { + pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER; + // clear the sound channel. + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + pev->nextthink = 0; + return; + } + + // ALERT ( at_console, "%f\n", m_flWait ); + + if (m_flWait != 0) + {// -1 wait will wait forever! + pev->nextthink = pev->ltime + m_flWait; + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + SetThink( &CFuncTrain::Next ); + } + else + { + Next();// do it RIGHT now! + } +} + + +// +// Train next - path corner needs to change to next target +// +void CFuncTrain :: Next( void ) +{ + CBaseEntity *pTarg; + + + // now find our next target + pTarg = GetNextTarget(); + + if ( !pTarg ) + { + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + // Play stop sound + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + return; + } + + // Save last target in case we need to find it again + pev->message = pev->target; + + pev->target = pTarg->pev->target; + m_flWait = pTarg->GetDelay(); + + if ( m_pevCurrentTarget && m_pevCurrentTarget->speed != 0 ) + {// don't copy speed from target if it is 0 (uninitialized) + pev->speed = m_pevCurrentTarget->speed; + ALERT( at_aiconsole, "Train %s speed to %4.2f\n", STRING(pev->targetname), pev->speed ); + } + m_pevCurrentTarget = pTarg->pev;// keep track of this since path corners change our target for us. + + pev->enemy = pTarg->edict();//hack + + if(FBitSet(m_pevCurrentTarget->spawnflags, SF_CORNER_TELEPORT)) + { + // Path corner has indicated a teleport to the next corner. + SetBits(pev->effects, EF_NOINTERP); + UTIL_SetOrigin(pev, pTarg->pev->origin - (pev->mins + pev->maxs)* 0.5); + Wait(); // Get on with doing the next path corner. + } + else + { + // Normal linear move. + + // CHANGED this from CHAN_VOICE to CHAN_STATIC around OEM beta time because trains should + // use CHAN_STATIC for their movement sounds to prevent sound field problems. + // this is not a hack or temporary fix, this is how things should be. (sjb). + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + if ( pev->noiseMovement ) + EMIT_SOUND (ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement), m_volume, ATTN_NORM); + ClearBits(pev->effects, EF_NOINTERP); + SetMoveDone( &CFuncTrain::Wait ); + LinearMove (pTarg->pev->origin - (pev->mins + pev->maxs)* 0.5, pev->speed); + } +} + + +void CFuncTrain :: Activate( void ) +{ + // Not yet active, so teleport to first target + if ( !m_activated ) + { + m_activated = TRUE; + entvars_t *pevTarg = VARS( FIND_ENTITY_BY_TARGETNAME (NULL, STRING(pev->target) ) ); + + pev->target = pevTarg->target; + m_pevCurrentTarget = pevTarg;// keep track of this since path corners change our target for us. + + UTIL_SetOrigin (pev, pevTarg->origin - (pev->mins + pev->maxs) * 0.5 ); + + if ( FStringNull(pev->targetname) ) + { // not triggered, so start immediately + pev->nextthink = pev->ltime + 0.1; + SetThink( &CFuncTrain::Next ); + } + else + pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER; + } +} + +/*QUAKED func_train (0 .5 .8) ? +Trains are moving platforms that players can ride. +The targets origin specifies the min point of the train at each corner. +The train spawns at the first target it is pointing at. +If the train is the target of a button or trigger, it will not begin moving until activated. +speed default 100 +dmg default 2 +sounds +1) ratchet metal +*/ + +void CFuncTrain :: Spawn( void ) +{ + Precache(); + if (pev->speed == 0) + pev->speed = 100; + + if ( FStringNull(pev->target) ) + ALERT(at_console, "FuncTrain with no target"); + + if (pev->dmg == 0) + pev->dmg = 2; + + pev->movetype = MOVETYPE_PUSH; + + if ( FBitSet (pev->spawnflags, SF_TRACKTRAIN_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + + SET_MODEL( ENT(pev), STRING(pev->model) ); + UTIL_SetSize (pev, pev->mins, pev->maxs); + UTIL_SetOrigin(pev, pev->origin); + + m_activated = FALSE; + + if ( m_volume == 0 ) + m_volume = 0.85; +} + + +void CFuncTrain::Precache( void ) +{ + CBasePlatTrain::Precache(); + +#if 0 // obsolete + // otherwise use preset sound + switch (m_sounds) + { + case 0: + pev->noise = 0; + pev->noise1 = 0; + break; + + case 1: + PRECACHE_SOUND ("plats/train2.wav"); + PRECACHE_SOUND ("plats/train1.wav"); + pev->noise = MAKE_STRING("plats/train2.wav"); + pev->noise1 = MAKE_STRING("plats/train1.wav"); + break; + + case 2: + PRECACHE_SOUND ("plats/platmove1.wav"); + PRECACHE_SOUND ("plats/platstop1.wav"); + pev->noise = MAKE_STRING("plats/platstop1.wav"); + pev->noise1 = MAKE_STRING("plats/platmove1.wav"); + break; + } +#endif +} + + +void CFuncTrain::OverrideReset( void ) +{ + CBaseEntity *pTarg; + + // Are we moving? + if ( pev->velocity != g_vecZero && pev->nextthink != 0 ) + { + pev->target = pev->message; + // now find our next target + pTarg = GetNextTarget(); + if ( !pTarg ) + { + pev->nextthink = 0; + pev->velocity = g_vecZero; + } + else // Keep moving for 0.1 secs, then find path_corner again and restart + { + SetThink( &CFuncTrain::Next ); + pev->nextthink = pev->ltime + 0.1; + } + } +} + + + + +// --------------------------------------------------------------------- +// +// Track Train +// +// --------------------------------------------------------------------- + +TYPEDESCRIPTION CFuncTrackTrain::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTrackTrain, m_ppath, FIELD_CLASSPTR ), + DEFINE_FIELD( CFuncTrackTrain, m_length, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_height, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_speed, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_dir, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_startSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_controlMins, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTrackTrain, m_controlMaxs, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTrackTrain, m_sounds, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrackTrain, m_flVolume, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_flBank, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_oldSpeed, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTrackTrain, CBaseEntity ); +LINK_ENTITY_TO_CLASS( func_tracktrain, CFuncTrackTrain ); + +void CFuncTrackTrain :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "wheels")) + { + m_length = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "height")) + { + m_height = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "startspeed")) + { + m_startSpeed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "volume")) + { + m_flVolume = (float) (atoi(pkvd->szValue)); + m_flVolume *= 0.1; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "bank")) + { + m_flBank = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +void CFuncTrackTrain :: NextThink( float thinkTime, BOOL alwaysThink ) +{ + if ( alwaysThink ) + pev->flags |= FL_ALWAYSTHINK; + else + pev->flags &= ~FL_ALWAYSTHINK; + + pev->nextthink = thinkTime; +} + + +void CFuncTrackTrain :: Blocked( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + // Blocker is on-ground on the train + if ( FBitSet( pevOther->flags, FL_ONGROUND ) && VARS(pevOther->groundentity) == pev ) + { + float deltaSpeed = fabs(pev->speed); + if ( deltaSpeed > 50 ) + deltaSpeed = 50; + if ( !pevOther->velocity.z ) + pevOther->velocity.z += deltaSpeed; + return; + } + else + pevOther->velocity = (pevOther->origin - pev->origin ).Normalize() * pev->dmg; + + ALERT( at_aiconsole, "TRAIN(%s): Blocked by %s (dmg:%.2f)\n", STRING(pev->targetname), STRING(pOther->pev->classname), pev->dmg ); + if ( pev->dmg <= 0 ) + return; + // we can't hurt this thing, so we're not concerned with it + pOther->TakeDamage(pev, pev, pev->dmg, DMG_CRUSH); +} + + +void CFuncTrackTrain :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( useType != USE_SET ) + { + if ( !ShouldToggle( useType, (pev->speed != 0) ) ) + return; + + if ( pev->speed == 0 ) + { + pev->speed = m_speed * m_dir; + + Next(); + } + else + { + pev->speed = 0; + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + StopSound(); + SetThink( NULL ); + } + } + else + { + float delta = value; + + delta = ((int)(pev->speed * 4) / (int)m_speed)*0.25 + 0.25 * delta; + if ( delta > 1 ) + delta = 1; + else if ( delta < -1 ) + delta = -1; + if ( pev->spawnflags & SF_TRACKTRAIN_FORWARDONLY ) + { + if ( delta < 0 ) + delta = 0; + } + pev->speed = m_speed * delta; + Next(); + ALERT( at_aiconsole, "TRAIN(%s), speed to %.2f\n", STRING(pev->targetname), pev->speed ); + } +} + + +static float Fix( float angle ) +{ + while ( angle < 0 ) + angle += 360; + while ( angle > 360 ) + angle -= 360; + + return angle; +} + + +static void FixupAngles( Vector &v ) +{ + v.x = Fix( v.x ); + v.y = Fix( v.y ); + v.z = Fix( v.z ); +} + +#define TRAIN_STARTPITCH 60 +#define TRAIN_MAXPITCH 200 +#define TRAIN_MAXSPEED 1000 // approx max speed for sound pitch calculation + +void CFuncTrackTrain :: StopSound( void ) +{ + // if sound playing, stop it + if (m_soundPlaying && pev->noise) + { + unsigned short us_encode; + unsigned short us_sound = ( ( unsigned short )( m_sounds ) & 0x0007 ) << 12; + + us_encode = us_sound; + + PLAYBACK_EVENT_FULL( FEV_RELIABLE | FEV_UPDATE, edict(), m_usAdjustPitch, 0.0, + (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, us_encode, 0, 1, 0 ); + + /* + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noise)); + */ + EMIT_SOUND_DYN(ENT(pev), CHAN_ITEM, "plats/ttrain_brake1.wav", m_flVolume, ATTN_NORM, 0, 100); + } + + m_soundPlaying = 0; +} + +// update pitch based on speed, start sound if not playing +// NOTE: when train goes through transition, m_soundPlaying should go to 0, +// which will cause the looped sound to restart. + +void CFuncTrackTrain :: UpdateSound( void ) +{ + float flpitch; + + if (!pev->noise) + return; + + flpitch = TRAIN_STARTPITCH + (abs(static_cast(pev->speed)) * (TRAIN_MAXPITCH - TRAIN_STARTPITCH) / TRAIN_MAXSPEED); + + if (!m_soundPlaying) + { + // play startup sound for train + EMIT_SOUND_DYN(ENT(pev), CHAN_ITEM, "plats/ttrain_start1.wav", m_flVolume, ATTN_NORM, 0, 100); + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noise), m_flVolume, ATTN_NORM, 0, (int) flpitch); + m_soundPlaying = 1; + } + else + { +/* + // update pitch + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noise), m_flVolume, ATTN_NORM, SND_CHANGE_PITCH, (int) flpitch); +*/ + // volume 0.0 - 1.0 - 6 bits + // m_sounds 3 bits + // flpitch = 6 bits + // 15 bits total + + unsigned short us_encode; + unsigned short us_sound = ( ( unsigned short )( m_sounds ) & 0x0007 ) << 12; + unsigned short us_pitch = ( ( unsigned short )( flpitch / 10.0 ) & 0x003f ) << 6; + unsigned short us_volume = ( ( unsigned short )( m_flVolume * 40.0 ) & 0x003f ); + + us_encode = us_sound | us_pitch | us_volume; + + PLAYBACK_EVENT_FULL( FEV_RELIABLE | FEV_UPDATE, edict(), m_usAdjustPitch, 0.0, + (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, us_encode, 0, 0, 0 ); + } +} + + +void CFuncTrackTrain :: Next( void ) +{ + float time = 0.5; + + if ( !pev->speed ) + { + ALERT( at_aiconsole, "TRAIN(%s): Speed is 0\n", STRING(pev->targetname) ); + StopSound(); + return; + } + +// if ( !m_ppath ) +// m_ppath = CPathTrack::Instance(FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) )); + if ( !m_ppath ) + { + ALERT( at_aiconsole, "TRAIN(%s): Lost path\n", STRING(pev->targetname) ); + StopSound(); + return; + } + + UpdateSound(); + + Vector nextPos = pev->origin; + + nextPos.z -= m_height; + CPathTrack *pnext = m_ppath->LookAhead( &nextPos, pev->speed * 0.1, 1 ); + nextPos.z += m_height; + + pev->velocity = (nextPos - pev->origin) * 10; + Vector nextFront = pev->origin; + + nextFront.z -= m_height; + if ( m_length > 0 ) + m_ppath->LookAhead( &nextFront, m_length, 0 ); + else + m_ppath->LookAhead( &nextFront, 100, 0 ); + nextFront.z += m_height; + + Vector delta = nextFront - pev->origin; + Vector angles = UTIL_VecToAngles( delta ); + // The train actually points west + angles.y += 180; + + // !!! All of this crap has to be done to make the angles not wrap around, revisit this. + FixupAngles( angles ); + FixupAngles( pev->angles ); + + if ( !pnext || (delta.x == 0 && delta.y == 0) ) + angles = pev->angles; + + float vy, vx; + if ( !(pev->spawnflags & SF_TRACKTRAIN_NOPITCH) ) + vx = UTIL_AngleDistance( angles.x, pev->angles.x ); + else + vx = 0; + vy = UTIL_AngleDistance( angles.y, pev->angles.y ); + + pev->avelocity.y = vy * 10; + pev->avelocity.x = vx * 10; + + if ( m_flBank != 0 ) + { + if ( pev->avelocity.y < -5 ) + pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( -m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z); + else if ( pev->avelocity.y > 5 ) + pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z); + else + pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( 0, pev->angles.z, m_flBank*4 ), pev->angles.z) * 4; + } + + if ( pnext ) + { + if ( pnext != m_ppath ) + { + CPathTrack *pFire; + if ( pev->speed >= 0 ) + pFire = pnext; + else + pFire = m_ppath; + + m_ppath = pnext; + // Fire the pass target if there is one + if ( pFire->pev->message ) + { + FireTargets( STRING(pFire->pev->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( pFire->pev->spawnflags, SF_PATH_FIREONCE ) ) + pFire->pev->message = 0; + } + + if ( pFire->pev->spawnflags & SF_PATH_DISABLE_TRAIN ) + pev->spawnflags |= SF_TRACKTRAIN_NOCONTROL; + + // Don't override speed if under user control + if ( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL ) + { + if ( pFire->pev->speed != 0 ) + {// don't copy speed from target if it is 0 (uninitialized) + pev->speed = pFire->pev->speed; + ALERT( at_aiconsole, "TrackTrain %s speed to %4.2f\n", STRING(pev->targetname), pev->speed ); + } + } + + } + SetThink( &CFuncTrackTrain::Next ); + NextThink( pev->ltime + time, TRUE ); + } + else // end of path, stop + { + StopSound(); + pev->velocity = (nextPos - pev->origin); + pev->avelocity = g_vecZero; + float distance = pev->velocity.Length(); + m_oldSpeed = pev->speed; + + + pev->speed = 0; + + // Move to the dead end + + // Are we there yet? + if ( distance > 0 ) + { + // no, how long to get there? + time = distance / m_oldSpeed; + pev->velocity = pev->velocity * (m_oldSpeed / distance); + SetThink( &CFuncTrackTrain::DeadEnd ); + NextThink( pev->ltime + time, FALSE ); + } + else + { + DeadEnd(); + } + } +} + + +void CFuncTrackTrain::DeadEnd( void ) +{ + // Fire the dead-end target if there is one + CPathTrack *pTrack, *pNext; + + pTrack = m_ppath; + + ALERT( at_aiconsole, "TRAIN(%s): Dead end ", STRING(pev->targetname) ); + // Find the dead end path node + // HACKHACK -- This is bugly, but the train can actually stop moving at a different node depending on it's speed + // so we have to traverse the list to it's end. + if ( pTrack ) + { + if ( m_oldSpeed < 0 ) + { + do + { + pNext = pTrack->ValidPath( pTrack->GetPrevious(), TRUE ); + if ( pNext ) + pTrack = pNext; + } while ( pNext ); + } + else + { + do + { + pNext = pTrack->ValidPath( pTrack->GetNext(), TRUE ); + if ( pNext ) + pTrack = pNext; + } while ( pNext ); + } + } + + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + if ( pTrack ) + { + ALERT( at_aiconsole, "at %s\n", STRING(pTrack->pev->targetname) ); + if ( pTrack->pev->netname ) + FireTargets( STRING(pTrack->pev->netname), this, this, USE_TOGGLE, 0 ); + } + else + ALERT( at_aiconsole, "\n" ); +} + + +void CFuncTrackTrain :: SetControls( entvars_t *pevControls ) +{ + Vector offset = pevControls->origin - pev->oldorigin; + + m_controlMins = pevControls->mins + offset; + m_controlMaxs = pevControls->maxs + offset; +} + + +BOOL CFuncTrackTrain :: OnControls( entvars_t *pevTest ) +{ + Vector offset = pevTest->origin - pev->origin; + + if ( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL ) + return FALSE; + + // Transform offset into local coordinates + UTIL_MakeVectors( pev->angles ); + Vector local; + local.x = DotProduct( offset, gpGlobals->v_forward ); + local.y = -DotProduct( offset, gpGlobals->v_right ); + local.z = DotProduct( offset, gpGlobals->v_up ); + + if ( local.x >= m_controlMins.x && local.y >= m_controlMins.y && local.z >= m_controlMins.z && + local.x <= m_controlMaxs.x && local.y <= m_controlMaxs.y && local.z <= m_controlMaxs.z ) + return TRUE; + + return FALSE; +} + + +void CFuncTrackTrain :: Find( void ) +{ + m_ppath = CPathTrack::Instance(FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) )); + if ( !m_ppath ) + return; + + entvars_t *pevTarget = m_ppath->pev; + if ( !FClassnameIs( pevTarget, "path_track" ) ) + { + ALERT( at_error, "func_track_train must be on a path of path_track\n" ); + m_ppath = NULL; + return; + } + + Vector nextPos = pevTarget->origin; + nextPos.z += m_height; + + Vector look = nextPos; + look.z -= m_height; + m_ppath->LookAhead( &look, m_length, 0 ); + look.z += m_height; + + pev->angles = UTIL_VecToAngles( look - nextPos ); + // The train actually points west + pev->angles.y += 180; + + if ( pev->spawnflags & SF_TRACKTRAIN_NOPITCH ) + pev->angles.x = 0; + UTIL_SetOrigin( pev, nextPos ); + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncTrackTrain::Next ); + pev->speed = m_startSpeed; + + UpdateSound(); +} + + +void CFuncTrackTrain :: NearestPath( void ) +{ + CBaseEntity *pTrack = NULL; + CBaseEntity *pNearest = NULL; + float dist, closest; + + closest = 1024; + + while ((pTrack = UTIL_FindEntityInSphere( pTrack, pev->origin, 1024 )) != NULL) + { + // filter out non-tracks + if ( !(pTrack->pev->flags & (FL_CLIENT|FL_MONSTER)) && FClassnameIs( pTrack->pev, "path_track" ) ) + { + dist = (pev->origin - pTrack->pev->origin).Length(); + if ( dist < closest ) + { + closest = dist; + pNearest = pTrack; + } + } + } + + if ( !pNearest ) + { + ALERT( at_console, "Can't find a nearby track !!!\n" ); + SetThink(NULL); + return; + } + + ALERT( at_aiconsole, "TRAIN: %s, Nearest track is %s\n", STRING(pev->targetname), STRING(pNearest->pev->targetname) ); + // If I'm closer to the next path_track on this path, then it's my real path + pTrack = ((CPathTrack *)pNearest)->GetNext(); + if ( pTrack ) + { + if ( (pev->origin - pTrack->pev->origin).Length() < (pev->origin - pNearest->pev->origin).Length() ) + pNearest = pTrack; + } + + m_ppath = (CPathTrack *)pNearest; + + if ( pev->speed != 0 ) + { + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncTrackTrain::Next ); + } +} + + +void CFuncTrackTrain::OverrideReset( void ) +{ + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncTrackTrain::NearestPath ); +} + + +CFuncTrackTrain *CFuncTrackTrain::Instance( edict_t *pent ) +{ + if ( FClassnameIs( pent, "func_tracktrain" ) ) + return (CFuncTrackTrain *)GET_PRIVATE(pent); + return NULL; +} + +/*QUAKED func_train (0 .5 .8) ? +Trains are moving platforms that players can ride. +The targets origin specifies the min point of the train at each corner. +The train spawns at the first target it is pointing at. +If the train is the target of a button or trigger, it will not begin moving until activated. +speed default 100 +dmg default 2 +sounds +1) ratchet metal +*/ + +void CFuncTrackTrain :: Spawn( void ) +{ + if ( pev->speed == 0 ) + m_speed = 100; + else + m_speed = pev->speed; + + pev->speed = 0; + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + pev->impulse = static_cast(m_speed); + + m_dir = 1; + + if ( FStringNull(pev->target) ) + ALERT( at_console, "FuncTrain with no target" ); + + if ( pev->spawnflags & SF_TRACKTRAIN_PASSABLE ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + SET_MODEL( ENT(pev), STRING(pev->model) ); + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( pev, pev->origin ); + + // Cache off placed origin for train controls + pev->oldorigin = pev->origin; + + m_controlMins = pev->mins; + m_controlMaxs = pev->maxs; + m_controlMaxs.z += 72; +// start trains on the next frame, to make sure their targets have had +// a chance to spawn/activate + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncTrackTrain::Find ); + Precache(); +} + +void CFuncTrackTrain :: Precache( void ) +{ + if (m_flVolume == 0.0) + m_flVolume = 1.0; + + switch (m_sounds) + { + default: + // no sound + pev->noise = 0; + break; + case 1: PRECACHE_SOUND("plats/ttrain1.wav"); pev->noise = MAKE_STRING("plats/ttrain1.wav");break; + case 2: PRECACHE_SOUND("plats/ttrain2.wav"); pev->noise = MAKE_STRING("plats/ttrain2.wav");break; + case 3: PRECACHE_SOUND("plats/ttrain3.wav"); pev->noise = MAKE_STRING("plats/ttrain3.wav");break; + case 4: PRECACHE_SOUND("plats/ttrain4.wav"); pev->noise = MAKE_STRING("plats/ttrain4.wav");break; + case 5: PRECACHE_SOUND("plats/ttrain6.wav"); pev->noise = MAKE_STRING("plats/ttrain6.wav");break; + case 6: PRECACHE_SOUND("plats/ttrain7.wav"); pev->noise = MAKE_STRING("plats/ttrain7.wav");break; + } + + PRECACHE_SOUND("plats/ttrain_brake1.wav"); + PRECACHE_SOUND("plats/ttrain_start1.wav"); + + m_usAdjustPitch = PRECACHE_EVENT( 1, "events/train.sc" ); +} + +// This class defines the volume of space that the player must stand in to control the train +class CFuncTrainControls : public CBaseEntity +{ +public: + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + void Spawn( void ); + void EXPORT Find( void ); +}; +LINK_ENTITY_TO_CLASS( func_traincontrols, CFuncTrainControls ); + + +void CFuncTrainControls :: Find( void ) +{ + edict_t *pTarget = NULL; + + do + { + pTarget = FIND_ENTITY_BY_TARGETNAME( pTarget, STRING(pev->target) ); + } while ( !FNullEnt(pTarget) && !FClassnameIs(pTarget, "func_tracktrain") ); + + if ( FNullEnt( pTarget ) ) + { + ALERT( at_console, "No train %s\n", STRING(pev->target) ); + return; + } + + CFuncTrackTrain *ptrain = CFuncTrackTrain::Instance(pTarget); + ptrain->SetControls( pev ); + UTIL_Remove( this ); +} + + +void CFuncTrainControls :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( pev, pev->origin ); + + SetThink( &CFuncTrainControls::Find ); + pev->nextthink = gpGlobals->time; +} + + + +// ---------------------------------------------------------------------------- +// +// Track changer / Train elevator +// +// ---------------------------------------------------------------------------- + +#define SF_TRACK_ACTIVATETRAIN 0x00000001 +#define SF_TRACK_RELINK 0x00000002 +#define SF_TRACK_ROTMOVE 0x00000004 +#define SF_TRACK_STARTBOTTOM 0x00000008 +#define SF_TRACK_DONT_MOVE 0x00000010 + +// +// This entity is a rotating/moving platform that will carry a train to a new track. +// It must be larger in X-Y planar area than the train, since it must contain the +// train within these dimensions in order to operate when the train is near it. +// + +typedef enum { TRAIN_SAFE, TRAIN_BLOCKING, TRAIN_FOLLOWING } TRAIN_CODE; + +class CFuncTrackChange : public CFuncPlatRot +{ +public: + void Spawn( void ); + void Precache( void ); + +// virtual void Blocked( void ); + virtual void EXPORT GoUp( void ); + virtual void EXPORT GoDown( void ); + + void KeyValue( KeyValueData* pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Find( void ); + TRAIN_CODE EvaluateTrain( CPathTrack *pcurrent ); + void UpdateTrain( Vector &dest ); + virtual void HitBottom( void ); + virtual void HitTop( void ); + void Touch( CBaseEntity *pOther ); + virtual void UpdateAutoTargets( int toggleState ); + virtual BOOL IsTogglePlat( void ) { return TRUE; } + + void DisableUse( void ) { m_use = 0; } + void EnableUse( void ) { m_use = 1; } + int UseEnabled( void ) { return m_use; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + virtual void OverrideReset( void ); + + + CPathTrack *m_trackTop; + CPathTrack *m_trackBottom; + + CFuncTrackTrain *m_train; + + int m_trackTopName; + int m_trackBottomName; + int m_trainName; + TRAIN_CODE m_code; + int m_targetState; + int m_use; +}; +LINK_ENTITY_TO_CLASS( func_trackchange, CFuncTrackChange ); + +TYPEDESCRIPTION CFuncTrackChange::m_SaveData[] = +{ + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackTop, FIELD_CLASSPTR ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackBottom, FIELD_CLASSPTR ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_train, FIELD_CLASSPTR ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackTopName, FIELD_STRING ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackBottomName, FIELD_STRING ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trainName, FIELD_STRING ), + DEFINE_FIELD( CFuncTrackChange, m_code, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrackChange, m_targetState, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrackChange, m_use, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTrackChange, CFuncPlatRot ); + +void CFuncTrackChange :: Spawn( void ) +{ + Setup(); + if ( FBitSet( pev->spawnflags, SF_TRACK_DONT_MOVE ) ) + m_vecPosition2.z = pev->origin.z; + + SetupRotation(); + + if ( FBitSet( pev->spawnflags, SF_TRACK_STARTBOTTOM ) ) + { + UTIL_SetOrigin (pev, m_vecPosition2); + m_toggle_state = TS_AT_BOTTOM; + pev->angles = m_start; + m_targetState = TS_AT_TOP; + } + else + { + UTIL_SetOrigin (pev, m_vecPosition1); + m_toggle_state = TS_AT_TOP; + pev->angles = m_end; + m_targetState = TS_AT_BOTTOM; + } + + EnableUse(); + pev->nextthink = pev->ltime + 2.0; + SetThink( &CFuncTrackChange::Find ); + Precache(); +} + +void CFuncTrackChange :: Precache( void ) +{ + // Can't trigger sound + PRECACHE_SOUND( "buttons/button11.wav" ); + + CFuncPlatRot::Precache(); +} + + +// UNDONE: Filter touches before re-evaluating the train. +void CFuncTrackChange :: Touch( CBaseEntity *pOther ) +{ +#if 0 + TRAIN_CODE code; + entvars_t *pevToucher = pOther->pev; +#endif +} + + + +void CFuncTrackChange :: KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "train") ) + { + m_trainName = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "toptrack") ) + { + m_trackTopName = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "bottomtrack") ) + { + m_trackBottomName = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + { + CFuncPlatRot::KeyValue( pkvd ); // Pass up to base class + } +} + + +void CFuncTrackChange::OverrideReset( void ) +{ + pev->nextthink = pev->ltime + 1.0; + SetThink( &CFuncTrackChange::Find ); +} + +void CFuncTrackChange :: Find( void ) +{ + // Find track entities + edict_t *target; + + target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trackTopName) ); + if ( !FNullEnt(target) ) + { + m_trackTop = CPathTrack::Instance( target ); + target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trackBottomName) ); + if ( !FNullEnt(target) ) + { + m_trackBottom = CPathTrack::Instance( target ); + target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trainName) ); + if ( !FNullEnt(target) ) + { + m_train = CFuncTrackTrain::Instance( FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trainName) ) ); + if ( !m_train ) + { + ALERT( at_error, "Can't find train for track change! %s\n", STRING(m_trainName) ); + return; + } + Vector center = (pev->absmin + pev->absmax) * 0.5; + m_trackBottom = m_trackBottom->Nearest( center ); + m_trackTop = m_trackTop->Nearest( center ); + UpdateAutoTargets( m_toggle_state ); + SetThink( NULL ); + return; + } + else + { + ALERT( at_error, "Can't find train for track change! %s\n", STRING(m_trainName) ); + target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trainName) ); + } + } + else + ALERT( at_error, "Can't find bottom track for track change! %s\n", STRING(m_trackBottomName) ); + } + else + ALERT( at_error, "Can't find top track for track change! %s\n", STRING(m_trackTopName) ); +} + + + +TRAIN_CODE CFuncTrackChange :: EvaluateTrain( CPathTrack *pcurrent ) +{ + // Go ahead and work, we don't have anything to switch, so just be an elevator + if ( !pcurrent || !m_train ) + return TRAIN_SAFE; + + if ( m_train->m_ppath == pcurrent || (pcurrent->m_pprevious && m_train->m_ppath == pcurrent->m_pprevious) || + (pcurrent->m_pnext && m_train->m_ppath == pcurrent->m_pnext) ) + { + if ( m_train->pev->speed != 0 ) + return TRAIN_BLOCKING; + + Vector dist = pev->origin - m_train->pev->origin; + float length = dist.Length2D(); + if ( length < m_train->m_length ) // Empirically determined close distance + return TRAIN_FOLLOWING; + else if ( length > (150 + m_train->m_length) ) + return TRAIN_SAFE; + + return TRAIN_BLOCKING; + } + + return TRAIN_SAFE; +} + + +void CFuncTrackChange :: UpdateTrain( Vector &dest ) +{ + float time = (pev->nextthink - pev->ltime); + + m_train->pev->velocity = pev->velocity; + m_train->pev->avelocity = pev->avelocity; + m_train->NextThink( m_train->pev->ltime + time, FALSE ); + + // Attempt at getting the train to rotate properly around the origin of the trackchange + if ( time <= 0 ) + return; + + Vector offset = m_train->pev->origin - pev->origin; + Vector delta = dest - pev->angles; + // Transform offset into local coordinates + UTIL_MakeInvVectors( delta, gpGlobals ); + Vector local; + local.x = DotProduct( offset, gpGlobals->v_forward ); + local.y = DotProduct( offset, gpGlobals->v_right ); + local.z = DotProduct( offset, gpGlobals->v_up ); + + local = local - offset; + m_train->pev->velocity = pev->velocity + (local * (1.0/time)); +} + +void CFuncTrackChange :: GoDown( void ) +{ + if ( m_code == TRAIN_BLOCKING ) + return; + + // HitBottom may get called during CFuncPlat::GoDown(), so set up for that + // before you call GoDown() + + UpdateAutoTargets( TS_GOING_DOWN ); + // If ROTMOVE, move & rotate + if ( FBitSet( pev->spawnflags, SF_TRACK_DONT_MOVE ) ) + { + SetMoveDone( &CFuncPlat::CallHitBottom ); + m_toggle_state = TS_GOING_DOWN; + AngularMove( m_start, pev->speed ); + } + else + { + CFuncPlat :: GoDown(); + SetMoveDone( &CFuncPlat::CallHitBottom ); + RotMove( m_start, pev->nextthink - pev->ltime ); + } + // Otherwise, rotate first, move second + + // If the train is moving with the platform, update it + if ( m_code == TRAIN_FOLLOWING ) + { + UpdateTrain( m_start ); + m_train->m_ppath = NULL; + } +} + + +// +// Platform is at bottom, now starts moving up +// +void CFuncTrackChange :: GoUp( void ) +{ + if ( m_code == TRAIN_BLOCKING ) + return; + + // HitTop may get called during CFuncPlat::GoUp(), so set up for that + // before you call GoUp(); + + UpdateAutoTargets( TS_GOING_UP ); + if ( FBitSet( pev->spawnflags, SF_TRACK_DONT_MOVE ) ) + { + m_toggle_state = TS_GOING_UP; + SetMoveDone( &CFuncPlat::CallHitTop ); + AngularMove( m_end, pev->speed ); + } + else + { + // If ROTMOVE, move & rotate + CFuncPlat :: GoUp(); + SetMoveDone( &CFuncPlat::CallHitTop ); + RotMove( m_end, pev->nextthink - pev->ltime ); + } + + // Otherwise, move first, rotate second + + // If the train is moving with the platform, update it + if ( m_code == TRAIN_FOLLOWING ) + { + UpdateTrain( m_end ); + m_train->m_ppath = NULL; + } +} + + +// Normal track change +void CFuncTrackChange :: UpdateAutoTargets( int toggleState ) +{ + if ( !m_trackTop || !m_trackBottom ) + return; + + if ( toggleState == TS_AT_TOP ) + ClearBits( m_trackTop->pev->spawnflags, SF_PATH_DISABLED ); + else + SetBits( m_trackTop->pev->spawnflags, SF_PATH_DISABLED ); + + if ( toggleState == TS_AT_BOTTOM ) + ClearBits( m_trackBottom->pev->spawnflags, SF_PATH_DISABLED ); + else + SetBits( m_trackBottom->pev->spawnflags, SF_PATH_DISABLED ); +} + + +void CFuncTrackChange :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( m_toggle_state != TS_AT_TOP && m_toggle_state != TS_AT_BOTTOM ) + return; + + // If train is in "safe" area, but not on the elevator, play alarm sound + if ( m_toggle_state == TS_AT_TOP ) + m_code = EvaluateTrain( m_trackTop ); + else if ( m_toggle_state == TS_AT_BOTTOM ) + m_code = EvaluateTrain( m_trackBottom ); + else + m_code = TRAIN_BLOCKING; + if ( m_code == TRAIN_BLOCKING ) + { + // Play alarm and return + EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/button11.wav", 1, ATTN_NORM); + return; + } + + // Otherwise, it's safe to move + // If at top, go down + // at bottom, go up + + DisableUse(); + if (m_toggle_state == TS_AT_TOP) + GoDown(); + else + GoUp(); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncTrackChange :: HitBottom( void ) +{ + CFuncPlatRot :: HitBottom(); + if ( m_code == TRAIN_FOLLOWING ) + { +// UpdateTrain(); + m_train->SetTrack( m_trackBottom ); + } + SetThink( NULL ); + pev->nextthink = -1; + + UpdateAutoTargets( m_toggle_state ); + + EnableUse(); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncTrackChange :: HitTop( void ) +{ + CFuncPlatRot :: HitTop(); + if ( m_code == TRAIN_FOLLOWING ) + { +// UpdateTrain(); + m_train->SetTrack( m_trackTop ); + } + + // Don't let the plat go back down + SetThink( NULL ); + pev->nextthink = -1; + UpdateAutoTargets( m_toggle_state ); + EnableUse(); +} + + + +class CFuncTrackAuto : public CFuncTrackChange +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual void UpdateAutoTargets( int toggleState ); +}; + +LINK_ENTITY_TO_CLASS( func_trackautochange, CFuncTrackAuto ); + +// Auto track change +void CFuncTrackAuto :: UpdateAutoTargets( int toggleState ) +{ + CPathTrack *pTarget, *pNextTarget; + + if ( !m_trackTop || !m_trackBottom ) + return; + + if ( m_targetState == TS_AT_TOP ) + { + pTarget = m_trackTop->GetNext(); + pNextTarget = m_trackBottom->GetNext(); + } + else + { + pTarget = m_trackBottom->GetNext(); + pNextTarget = m_trackTop->GetNext(); + } + if ( pTarget ) + { + ClearBits( pTarget->pev->spawnflags, SF_PATH_DISABLED ); + if ( m_code == TRAIN_FOLLOWING && m_train && m_train->pev->speed == 0 ) + m_train->Use( this, this, USE_ON, 0 ); + } + + if ( pNextTarget ) + SetBits( pNextTarget->pev->spawnflags, SF_PATH_DISABLED ); + +} + + +void CFuncTrackAuto :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CPathTrack *pTarget; + + if ( !UseEnabled() ) + return; + + if ( m_toggle_state == TS_AT_TOP ) + pTarget = m_trackTop; + else if ( m_toggle_state == TS_AT_BOTTOM ) + pTarget = m_trackBottom; + else + pTarget = NULL; + + if ( FClassnameIs( pActivator->pev, "func_tracktrain" ) ) + { + m_code = EvaluateTrain( pTarget ); + // Safe to fire? + if ( m_code == TRAIN_FOLLOWING && m_toggle_state != m_targetState ) + { + DisableUse(); + if (m_toggle_state == TS_AT_TOP) + GoDown(); + else + GoUp(); + } + } + else + { + if ( pTarget ) + pTarget = pTarget->GetNext(); + if ( pTarget && m_train->m_ppath != pTarget && ShouldToggle( useType, m_targetState ) ) + { + if ( m_targetState == TS_AT_TOP ) + m_targetState = TS_AT_BOTTOM; + else + m_targetState = TS_AT_TOP; + } + + UpdateAutoTargets( m_targetState ); + } +} + + +// ---------------------------------------------------------- +// +// +// pev->speed is the travel speed +// pev->health is current health +// pev->max_health is the amount to reset to each time it starts + +#define FGUNTARGET_START_ON 0x0001 + +class CGunTarget : public CBaseMonster +{ +public: + void Spawn( void ); + void Activate( void ); + void EXPORT Next( void ); + void EXPORT Start( void ); + void EXPORT Wait( void ); + void Stop( void ); + + int BloodColor( void ) { return DONT_BLEED; } + int Classify( void ) { return CLASS_MACHINE; } + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + Vector BodyTarget( const Vector &posSrc ) { return pev->origin; } + + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + BOOL m_on; +}; + + +LINK_ENTITY_TO_CLASS( func_guntarget, CGunTarget ); + +TYPEDESCRIPTION CGunTarget::m_SaveData[] = +{ + DEFINE_FIELD( CGunTarget, m_on, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CGunTarget, CBaseMonster ); + + +void CGunTarget::Spawn( void ) +{ + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + if ( pev->speed == 0 ) + pev->speed = 100; + + // Don't take damage until "on" + pev->takedamage = DAMAGE_NO; + pev->flags |= FL_MONSTER; + + m_on = FALSE; + pev->max_health = pev->health; + + if ( pev->spawnflags & FGUNTARGET_START_ON ) + { + SetThink( &CGunTarget::Start ); + pev->nextthink = pev->ltime + 0.3; + } +} + + +void CGunTarget::Activate( void ) +{ + CBaseEntity *pTarg; + + // now find our next target + pTarg = GetNextTarget(); + if ( pTarg ) + { + m_hTargetEnt = pTarg; + UTIL_SetOrigin( pev, pTarg->pev->origin - (pev->mins + pev->maxs) * 0.5 ); + } +} + + +void CGunTarget::Start( void ) +{ + Use( this, this, USE_ON, 0 ); +} + + +void CGunTarget::Next( void ) +{ + SetThink( NULL ); + + m_hTargetEnt = GetNextTarget(); + CBaseEntity *pTarget = m_hTargetEnt; + + if ( !pTarget ) + { + Stop(); + return; + } + SetMoveDone( &CGunTarget::Wait ); + LinearMove( pTarget->pev->origin - (pev->mins + pev->maxs) * 0.5, pev->speed ); +} + + +void CGunTarget::Wait( void ) +{ + CBaseEntity *pTarget = m_hTargetEnt; + + if ( !pTarget ) + { + Stop(); + return; + } + + // Fire the pass target if there is one + if ( pTarget->pev->message ) + { + FireTargets( STRING(pTarget->pev->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( pTarget->pev->spawnflags, SF_CORNER_FIREONCE ) ) + pTarget->pev->message = 0; + } + + m_flWait = pTarget->GetDelay(); + + pev->target = pTarget->pev->target; + SetThink( &CGunTarget::Next ); + if (m_flWait != 0) + {// -1 wait will wait forever! + pev->nextthink = pev->ltime + m_flWait; + } + else + { + Next();// do it RIGHT now! + } +} + + +void CGunTarget::Stop( void ) +{ + pev->velocity = g_vecZero; + pev->nextthink = 0; + pev->takedamage = DAMAGE_NO; +} + + +int CGunTarget::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( pev->health > 0 ) + { + pev->health -= flDamage; + if ( pev->health <= 0 ) + { + pev->health = 0; + Stop(); + if ( pev->message ) + FireTargets( STRING(pev->message), this, this, USE_TOGGLE, 0 ); + } + } + return 0; +} + + +void CGunTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_on ) ) + return; + + if ( m_on ) + { + Stop(); + } + else + { + pev->takedamage = DAMAGE_AIM; + m_hTargetEnt = GetNextTarget(); + if ( m_hTargetEnt == 0 ) + return; + pev->health = pev->max_health; + Next(); + } +} + + + diff --git a/hlsdk/dlls/player.cpp b/hlsdk/dlls/player.cpp new file mode 100644 index 00000000..0e77e67e --- /dev/null +++ b/hlsdk/dlls/player.cpp @@ -0,0 +1,4785 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== player.cpp ======================================================== + + functions dealing with the player + +*/ + +#include "extdll.h" +#include "util.h" + +#include "cbase.h" +#include "player.h" +#include "trains.h" +#include "nodes.h" +#include "weapons.h" +#include "soundent.h" +#include "monsters.h" +#include "shake.h" +#include "decals.h" +#include "gamerules.h" +#include "game.h" +#include "hltv.h" + +// #define DUCKFIX + +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); +extern void respawn(entvars_t *pev, BOOL fCopyCorpse); +extern Vector VecBModelOrigin(entvars_t *pevBModel ); +extern edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ); + +// the world node graph +extern CGraph WorldGraph; + +#define TRAIN_ACTIVE 0x80 +#define TRAIN_NEW 0xc0 +#define TRAIN_OFF 0x00 +#define TRAIN_NEUTRAL 0x01 +#define TRAIN_SLOW 0x02 +#define TRAIN_MEDIUM 0x03 +#define TRAIN_FAST 0x04 +#define TRAIN_BACK 0x05 + +#define FLASH_DRAIN_TIME 1.2 //100 units/3 minutes +#define FLASH_CHARGE_TIME 0.2 // 100 units/20 seconds (seconds per unit) + +// Global Savedata for player +TYPEDESCRIPTION CBasePlayer::m_playerSaveData[] = +{ + DEFINE_FIELD( CBasePlayer, m_flFlashLightTime, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_iFlashBattery, FIELD_INTEGER ), + + DEFINE_FIELD( CBasePlayer, m_afButtonLast, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_afButtonPressed, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_afButtonReleased, FIELD_INTEGER ), + + DEFINE_ARRAY( CBasePlayer, m_rgItems, FIELD_INTEGER, MAX_ITEMS ), + DEFINE_FIELD( CBasePlayer, m_afPhysicsFlags, FIELD_INTEGER ), + + DEFINE_FIELD( CBasePlayer, m_flTimeStepSound, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flTimeWeaponIdle, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flSwimTime, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flDuckTime, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flWallJumpTime, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_flSuitUpdate, FIELD_TIME ), + DEFINE_ARRAY( CBasePlayer, m_rgSuitPlayList, FIELD_INTEGER, CSUITPLAYLIST ), + DEFINE_FIELD( CBasePlayer, m_iSuitPlayNext, FIELD_INTEGER ), + DEFINE_ARRAY( CBasePlayer, m_rgiSuitNoRepeat, FIELD_INTEGER, CSUITNOREPEAT ), + DEFINE_ARRAY( CBasePlayer, m_rgflSuitNoRepeatTime, FIELD_TIME, CSUITNOREPEAT ), + DEFINE_FIELD( CBasePlayer, m_lastDamageAmount, FIELD_INTEGER ), + + DEFINE_ARRAY( CBasePlayer, m_rgpPlayerItems, FIELD_CLASSPTR, MAX_ITEM_TYPES ), + DEFINE_FIELD( CBasePlayer, m_pActiveItem, FIELD_CLASSPTR ), + DEFINE_FIELD( CBasePlayer, m_pLastItem, FIELD_CLASSPTR ), + + DEFINE_ARRAY( CBasePlayer, m_rgAmmo, FIELD_INTEGER, MAX_AMMO_SLOTS ), + DEFINE_FIELD( CBasePlayer, m_idrowndmg, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_idrownrestored, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_tSneaking, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_iTrain, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_bitsHUDDamage, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_flFallVelocity, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayer, m_iTargetVolume, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iWeaponVolume, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iExtraSoundTypes, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iWeaponFlash, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_fLongJump, FIELD_BOOLEAN ), + DEFINE_FIELD( CBasePlayer, m_fInitHUD, FIELD_BOOLEAN ), + DEFINE_FIELD( CBasePlayer, m_tbdPrev, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_pTank, FIELD_EHANDLE ), + DEFINE_FIELD( CBasePlayer, m_iHideHUD, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iFOV, FIELD_INTEGER ), + + //DEFINE_FIELD( CBasePlayer, m_fDeadTime, FIELD_FLOAT ), // only used in multiplayer games + //DEFINE_FIELD( CBasePlayer, m_fGameHUDInitialized, FIELD_INTEGER ), // only used in multiplayer games + //DEFINE_FIELD( CBasePlayer, m_flStopExtraSoundTime, FIELD_TIME ), + //DEFINE_FIELD( CBasePlayer, m_fKnownItem, FIELD_INTEGER ), // reset to zero on load + //DEFINE_FIELD( CBasePlayer, m_iPlayerSound, FIELD_INTEGER ), // Don't restore, set in Precache() + //DEFINE_FIELD( CBasePlayer, m_pentSndLast, FIELD_EDICT ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_flSndRoomtype, FIELD_FLOAT ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_flSndRange, FIELD_FLOAT ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_fNewAmmo, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_flgeigerRange, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( CBasePlayer, m_flgeigerDelay, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( CBasePlayer, m_igeigerRangePrev, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( CBasePlayer, m_iStepLeft, FIELD_INTEGER ), // Don't need to restore + //DEFINE_ARRAY( CBasePlayer, m_szTextureName, FIELD_CHARACTER, CBTEXTURENAMEMAX ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_chTextureType, FIELD_CHARACTER ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_fNoPlayerSound, FIELD_BOOLEAN ), // Don't need to restore, debug + //DEFINE_FIELD( CBasePlayer, m_iUpdateTime, FIELD_INTEGER ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_iClientHealth, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_iClientBattery, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_iClientHideHUD, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_fWeapon, FIELD_BOOLEAN ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_nCustomSprayFrames, FIELD_INTEGER ), // Don't restore, depends on server message after spawning and only matters in multiplayer + //DEFINE_FIELD( CBasePlayer, m_vecAutoAim, FIELD_VECTOR ), // Don't save/restore - this is recomputed + //DEFINE_ARRAY( CBasePlayer, m_rgAmmoLast, FIELD_INTEGER, MAX_AMMO_SLOTS ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_fOnTarget, FIELD_BOOLEAN ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_nCustomSprayFrames, FIELD_INTEGER ), // Don't need to restore + +}; + + +int giPrecacheGrunt = 0; +int gmsgShake = 0; +int gmsgFade = 0; +int gmsgSelAmmo = 0; +int gmsgFlashlight = 0; +int gmsgFlashBattery = 0; +int gmsgResetHUD = 0; +int gmsgInitHUD = 0; +int gmsgShowGameTitle = 0; +int gmsgCurWeapon = 0; +int gmsgHealth = 0; +int gmsgDamage = 0; +int gmsgBattery = 0; +int gmsgTrain = 0; +int gmsgLogo = 0; +int gmsgWeaponList = 0; +int gmsgAmmoX = 0; +int gmsgHudText = 0; +int gmsgDeathMsg = 0; +int gmsgScoreInfo = 0; +int gmsgTeamInfo = 0; +int gmsgTeamScore = 0; +int gmsgGameMode = 0; +int gmsgMOTD = 0; +int gmsgServerName = 0; +int gmsgAmmoPickup = 0; +int gmsgWeapPickup = 0; +int gmsgItemPickup = 0; +int gmsgHideWeapon = 0; +int gmsgSetCurWeap = 0; +int gmsgSayText = 0; +int gmsgTextMsg = 0; +int gmsgSetFOV = 0; +int gmsgShowMenu = 0; +int gmsgGeigerRange = 0; +int gmsgTeamNames = 0; + +int gmsgStatusText = 0; +int gmsgStatusValue = 0; + + + +void LinkUserMessages( void ) +{ + // Already taken care of? + if ( gmsgSelAmmo ) + { + return; + } + + gmsgSelAmmo = REG_USER_MSG("SelAmmo", sizeof(SelAmmo)); + gmsgCurWeapon = REG_USER_MSG("CurWeapon", 3); + gmsgGeigerRange = REG_USER_MSG("Geiger", 1); + gmsgFlashlight = REG_USER_MSG("Flashlight", 2); + gmsgFlashBattery = REG_USER_MSG("FlashBat", 1); + gmsgHealth = REG_USER_MSG( "Health", 1 ); + gmsgDamage = REG_USER_MSG( "Damage", 12 ); + gmsgBattery = REG_USER_MSG( "Battery", 2); + gmsgTrain = REG_USER_MSG( "Train", 1); + gmsgHudText = REG_USER_MSG( "HudText", -1 ); + gmsgSayText = REG_USER_MSG( "SayText", -1 ); + gmsgTextMsg = REG_USER_MSG( "TextMsg", -1 ); + gmsgWeaponList = REG_USER_MSG("WeaponList", -1); + gmsgResetHUD = REG_USER_MSG("ResetHUD", 1); // called every respawn + gmsgInitHUD = REG_USER_MSG("InitHUD", 0 ); // called every time a new player joins the server + gmsgShowGameTitle = REG_USER_MSG("GameTitle", 1); + gmsgDeathMsg = REG_USER_MSG( "DeathMsg", -1 ); + gmsgScoreInfo = REG_USER_MSG( "ScoreInfo", 9 ); + gmsgTeamInfo = REG_USER_MSG( "TeamInfo", -1 ); // sets the name of a player's team + gmsgTeamScore = REG_USER_MSG( "TeamScore", -1 ); // sets the score of a team on the scoreboard + gmsgGameMode = REG_USER_MSG( "GameMode", 1 ); + gmsgMOTD = REG_USER_MSG( "MOTD", -1 ); + gmsgServerName = REG_USER_MSG( "ServerName", -1 ); + gmsgAmmoPickup = REG_USER_MSG( "AmmoPickup", 2 ); + gmsgWeapPickup = REG_USER_MSG( "WeapPickup", 1 ); + gmsgItemPickup = REG_USER_MSG( "ItemPickup", -1 ); + gmsgHideWeapon = REG_USER_MSG( "HideWeapon", 1 ); + gmsgSetFOV = REG_USER_MSG( "SetFOV", 1 ); + gmsgShowMenu = REG_USER_MSG( "ShowMenu", -1 ); + gmsgShake = REG_USER_MSG("ScreenShake", sizeof(ScreenShake)); + gmsgFade = REG_USER_MSG("ScreenFade", sizeof(ScreenFade)); + gmsgAmmoX = REG_USER_MSG("AmmoX", 2); + gmsgTeamNames = REG_USER_MSG( "TeamNames", -1 ); + + gmsgStatusText = REG_USER_MSG("StatusText", -1); + gmsgStatusValue = REG_USER_MSG("StatusValue", 3); + +} + +LINK_ENTITY_TO_CLASS( player, CBasePlayer ); + + + +void CBasePlayer :: Pain( void ) +{ + float flRndSound;//sound randomizer + + flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.33 ) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain5.wav", 1, ATTN_NORM); + else if ( flRndSound <= 0.66 ) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain6.wav", 1, ATTN_NORM); + else + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain7.wav", 1, ATTN_NORM); +} + +/* + * + */ +Vector VecVelocityForDamage(float flDamage) +{ + Vector vec(RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + + if (flDamage > -50) + vec = vec * 0.7; + else if (flDamage > -200) + vec = vec * 2; + else + vec = vec * 10; + + return vec; +} + +#if 0 /* +static void ThrowGib(entvars_t *pev, char *szGibModel, float flDamage) +{ + edict_t *pentNew = CREATE_ENTITY(); + entvars_t *pevNew = VARS(pentNew); + + pevNew->origin = pev->origin; + SET_MODEL(ENT(pevNew), szGibModel); + UTIL_SetSize(pevNew, g_vecZero, g_vecZero); + + pevNew->velocity = VecVelocityForDamage(flDamage); + pevNew->movetype = MOVETYPE_BOUNCE; + pevNew->solid = SOLID_NOT; + pevNew->avelocity.x = RANDOM_FLOAT(0,600); + pevNew->avelocity.y = RANDOM_FLOAT(0,600); + pevNew->avelocity.z = RANDOM_FLOAT(0,600); + CHANGE_METHOD(ENT(pevNew), em_think, SUB_Remove); + pevNew->ltime = gpGlobals->time; + pevNew->nextthink = gpGlobals->time + RANDOM_FLOAT(10,20); + pevNew->frame = 0; + pevNew->flags = 0; +} + + +static void ThrowHead(entvars_t *pev, char *szGibModel, floatflDamage) +{ + SET_MODEL(ENT(pev), szGibModel); + pev->frame = 0; + pev->nextthink = -1; + pev->movetype = MOVETYPE_BOUNCE; + pev->takedamage = DAMAGE_NO; + pev->solid = SOLID_NOT; + pev->view_ofs = Vector(0,0,8); + UTIL_SetSize(pev, Vector(-16,-16,0), Vector(16,16,56)); + pev->velocity = VecVelocityForDamage(flDamage); + pev->avelocity = RANDOM_FLOAT(-1,1) * Vector(0,600,0); + pev->origin.z -= 24; + ClearBits(pev->flags, FL_ONGROUND); +} + + +*/ +#endif + +int TrainSpeed(int iSpeed, int iMax) +{ + float fSpeed, fMax; + int iRet = 0; + + fMax = (float)iMax; + fSpeed = iSpeed; + + fSpeed = fSpeed/fMax; + + if (iSpeed < 0) + iRet = TRAIN_BACK; + else if (iSpeed == 0) + iRet = TRAIN_NEUTRAL; + else if (fSpeed < 0.33) + iRet = TRAIN_SLOW; + else if (fSpeed < 0.66) + iRet = TRAIN_MEDIUM; + else + iRet = TRAIN_FAST; + + return iRet; +} + +void CBasePlayer :: DeathSound( void ) +{ + // water death sounds + /* + if (pev->waterlevel == 3) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/h2odeath.wav", 1, ATTN_NONE); + return; + } + */ + + // temporarily using pain sounds for death sounds + switch (RANDOM_LONG(1,5)) + { + case 1: + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain5.wav", 1, ATTN_NORM); + break; + case 2: + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain6.wav", 1, ATTN_NORM); + break; + case 3: + 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 +// bitsDamageType indicates type of damage healed. + +int CBasePlayer :: TakeHealth( float flHealth, int bitsDamageType ) +{ + return CBaseMonster :: TakeHealth (flHealth, bitsDamageType); + +} + +Vector CBasePlayer :: GetGunPosition( ) +{ +// UTIL_MakeVectors(pev->v_angle); +// m_HackedGunPos = pev->view_ofs; + Vector origin; + + origin = pev->origin + pev->view_ofs; + + return origin; +} + +//========================================================= +// TraceAttack +//========================================================= +void CBasePlayer :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if ( pev->takedamage ) + { + m_LastHitGroup = ptr->iHitgroup; + + switch ( ptr->iHitgroup ) + { + case HITGROUP_GENERIC: + break; + case HITGROUP_HEAD: + flDamage *= gSkillData.plrHead; + break; + case HITGROUP_CHEST: + flDamage *= gSkillData.plrChest; + break; + case HITGROUP_STOMACH: + flDamage *= gSkillData.plrStomach; + break; + case HITGROUP_LEFTARM: + case HITGROUP_RIGHTARM: + flDamage *= gSkillData.plrArm; + break; + case HITGROUP_LEFTLEG: + case HITGROUP_RIGHTLEG: + flDamage *= gSkillData.plrLeg; + break; + default: + break; + } + + SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage);// a little surface blood. + TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + } +} + +/* + Take some damage. + NOTE: each call to TakeDamage with bitsDamageType set to a time-based damage + type will cause the damage time countdown to be reset. Thus the ongoing effects of poison, radiation + etc are implemented with subsequent calls to TakeDamage using DMG_GENERIC. +*/ + +#define ARMOR_RATIO 0.2 // Armor Takes 80% of the damage +#define ARMOR_BONUS 0.5 // Each Point of Armor is work 1/x points of health + +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; + int fcritical; + int fTookDamage; + int ftrivial; + float flRatio; + float flBonus; + float flHealthPrev = pev->health; + + flBonus = ARMOR_BONUS; + flRatio = ARMOR_RATIO; + + if ( ( bitsDamageType & DMG_BLAST ) && g_pGameRules->IsMultiplayer() ) + { + // blasts damage armor more. + flBonus *= 2; + } + + // Already dead + if ( !IsAlive() ) + return 0; + // go take the damage first + + + CBaseEntity *pAttacker = CBaseEntity::Instance(pevAttacker); + + if ( !g_pGameRules->FPlayerCanTakeDamage( this, pAttacker ) ) + { + // Refuse the damage + return 0; + } + + // keep track of amount of damage last sustained + m_lastDamageAmount = static_cast(flDamage); + + // Armor. + if (pev->armorvalue && !(bitsDamageType & (DMG_FALL | DMG_DROWN)) )// armor doesn't protect against fall or drown damage! + { + float flNew = flDamage * flRatio; + + float flArmor; + + flArmor = (flDamage - flNew) * flBonus; + + // Does this use more armor than we have? + if (flArmor > pev->armorvalue) + { + flArmor = pev->armorvalue; + flArmor *= (1/flBonus); + flNew = flDamage - flArmor; + pev->armorvalue = 0; + } + else + pev->armorvalue -= flArmor; + + flDamage = flNew; + } + + // this cast to INT is critical!!! If a player ends up with 0.5 health, the engine will get that + // as an int (zero) and think the player is dead! (this will incite a clientside screentilt, etc) + fTookDamage = CBaseMonster::TakeDamage(pevInflictor, pevAttacker, (int)flDamage, bitsDamageType); + + // reset damage time countdown for each type of time based damage player just sustained + + { + for (int i = 0; i < CDMG_TIMEBASED; i++) + if (bitsDamageType & (DMG_PARALYZE << i)) + m_rgbTimeBasedDamage[i] = 0; + } + + // tell director about it + MESSAGE_BEGIN( MSG_SPEC, SVC_DIRECTOR ); + WRITE_BYTE ( 9 ); // command length in bytes + WRITE_BYTE ( DRC_CMD_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(); + + + // how bad is it, doc? + + ftrivial = (pev->health > 75 || m_lastDamageAmount < 5); + fmajor = (m_lastDamageAmount > 25); + 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 + + m_bitsDamageType |= bitsDamage; // Save this so we can report it to the client + m_bitsHUDDamage = -1; // make sure the damage bits get resent + + while (fTookDamage && (!ftrivial || (bitsDamage & DMG_TIMEBASED)) && ffound && bitsDamage) + { + ffound = FALSE; + + 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; + } + if (bitsDamage & DMG_SHOCK) + { + bitsDamage &= ~DMG_SHOCK; + ffound = TRUE; + } + } + + 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 + } + + return fTookDamage; +} + +//========================================================= +// PackDeadPlayerItems - call this when a player dies to +// pack up the appropriate weapons and ammo items, and to +// destroy anything that shouldn't be packed. +// +// This is pretty brute force :( +//========================================================= +void CBasePlayer::PackDeadPlayerItems( void ) +{ + int iWeaponRules; + int iAmmoRules; + int i; + CBasePlayerWeapon *rgpPackWeapons[ 20 ];// 20 hardcoded for now. How to determine exactly how many weapons we have? + int iPackAmmo[ MAX_AMMO_SLOTS + 1]; + int iPW = 0;// index into packweapons array + int iPA = 0;// index into packammo array + + memset(rgpPackWeapons, 0, sizeof(rgpPackWeapons) ); + memset(iPackAmmo, -1, sizeof(iPackAmmo) ); + + // get the game rules + iWeaponRules = g_pGameRules->DeadPlayerWeapons( this ); + iAmmoRules = g_pGameRules->DeadPlayerAmmo( this ); + + 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 ); + return; + } + +// go through all of the weapons and make a list of the ones to pack + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + // there's a weapon here. Should I pack it? + CBasePlayerItem *pPlayerItem = m_rgpPlayerItems[ i ]; + + while ( pPlayerItem ) + { + 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; + + case GR_PLR_DROP_GUN_ALL: + rgpPackWeapons[ iPW++ ] = (CBasePlayerWeapon *)pPlayerItem; + break; + + default: + break; + } + + pPlayerItem = pPlayerItem->m_pNext; + } + } + } + +// now go through ammo and make a list of which types to pack. + if ( iAmmoRules != GR_PLR_DROP_AMMO_NO ) + { + for ( i = 0 ; i < MAX_AMMO_SLOTS ; i++ ) + { + if ( m_rgAmmo[ i ] > 0 ) + { + // player has some ammo of this type. + switch ( iAmmoRules ) + { + case GR_PLR_DROP_AMMO_ALL: + iPackAmmo[ iPA++ ] = i; + break; + + case GR_PLR_DROP_AMMO_ACTIVE: + if ( m_pActiveItem && i == m_pActiveItem->PrimaryAmmoIndex() ) + { + // this is the primary ammo type for the active weapon + iPackAmmo[ iPA++ ] = i; + } + else if ( m_pActiveItem && i == m_pActiveItem->SecondaryAmmoIndex() ) + { + // this is the secondary ammo type for the active weapon + iPackAmmo[ iPA++ ] = i; + } + break; + + default: + break; + } + } + } + } + +// create a box to pack the stuff into. + 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; + +// back these two lists up to their first elements + iPA = 0; + iPW = 0; + +// pack the ammo + while ( iPackAmmo[ iPA ] != -1 ) + { + pWeaponBox->PackAmmo( MAKE_STRING( CBasePlayerItem::AmmoInfoArray[ iPackAmmo[ iPA ] ].pszName ), m_rgAmmo[ iPackAmmo[ iPA ] ] ); + iPA++; + } + +// now pack all of the items in the lists + while ( rgpPackWeapons[ iPW ] ) + { + // weapon unhooked from the player. Pack it into der box. + pWeaponBox->PackWeapon( rgpPackWeapons[ iPW ] ); + + iPW++; + } + + pWeaponBox->pev->velocity = pev->velocity * 1.2;// weaponbox has player's velocity, then some. + + RemoveAllItems( TRUE );// now strip off everything that wasn't handled by the code above. +} + +void CBasePlayer::RemoveAllItems( BOOL removeSuit ) +{ + if (m_pActiveItem) + { + ResetAutoaim( ); + m_pActiveItem->Holster( ); + m_pActiveItem = NULL; + } + + m_pLastItem = NULL; + + int i; + CBasePlayerItem *pPendingItem; + for (i = 0; i < MAX_ITEM_TYPES; i++) + { + m_pActiveItem = m_rgpPlayerItems[i]; + while (m_pActiveItem) + { + pPendingItem = m_pActiveItem->m_pNext; + m_pActiveItem->Drop( ); + m_pActiveItem = pPendingItem; + } + m_rgpPlayerItems[i] = NULL; + } + m_pActiveItem = NULL; + + pev->viewmodel = 0; + pev->weaponmodel = 0; + + if ( removeSuit ) + pev->weapons = 0; + else + pev->weapons &= ~WEAPON_ALLWEAPONS; + + for ( i = 0; i < MAX_AMMO_SLOTS;i++) + m_rgAmmo[i] = 0; + + UpdateClientData(); + // send Selected Weapon Message to our client + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pev ); + WRITE_BYTE(0); + WRITE_BYTE(0); + WRITE_BYTE(0); + MESSAGE_END(); +} + +/* + * GLOBALS ASSUMED SET: g_ulModelIndexPlayer + * + * ENTITY_METHOD(PlayerDie) + */ +entvars_t *g_pevLastInflictor; // Set in combat.cpp. Used to pass the damage inflictor for death messages. + // Better solution: Add as parameter to all Killed() functions. + +void CBasePlayer::Killed( entvars_t *pevAttacker, int iGib ) +{ + CSound *pSound; + + // Holster weapon immediately, to allow it to cleanup + if ( m_pActiveItem ) + m_pActiveItem->Holster( ); + + g_pGameRules->PlayerKilled( this, pevAttacker, g_pevLastInflictor ); + + if ( m_pTank != 0 ) + { + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + } + + // this client isn't going to be thinking for a while, so reset the sound until they respawn + pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( edict() ) ); + { + if ( pSound ) + { + pSound->Reset(); + } + } + + SetAnimation( PLAYER_DIE ); + + m_iRespawnFrames = 0; + + pev->modelindex = g_ulModelIndexPlayer; // don't use eyes + + pev->deadflag = DEAD_DYING; + pev->movetype = MOVETYPE_TOSS; + ClearBits( pev->flags, FL_ONGROUND ); + if (pev->velocity.z < 10) + pev->velocity.z += RANDOM_FLOAT(0,300); + + // clear out the suit message cache so we don't keep chattering + SetSuitUpdate(NULL, FALSE, 0); + + // send "health" update message to zero + m_iClientHealth = 0; + MESSAGE_BEGIN( MSG_ONE, gmsgHealth, NULL, pev ); + WRITE_BYTE( m_iClientHealth ); + MESSAGE_END(); + + // Tell Ammo Hud that the player is dead + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pev ); + WRITE_BYTE(0); + WRITE_BYTE(0XFF); + WRITE_BYTE(0xFF); + MESSAGE_END(); + + // reset FOV + pev->fov = m_iFOV = m_iClientFOV = 0; + + MESSAGE_BEGIN( MSG_ONE, gmsgSetFOV, NULL, pev ); + 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; + GibMonster(); // This clears pev->model + pev->effects |= EF_NODRAW; + return; + } + + DeathSound(); + + pev->angles.x = 0; + pev->angles.z = 0; + + SetThink(&CBasePlayer::PlayerDeathThink); + pev->nextthink = gpGlobals->time + 0.1; +} + + +// Set the activity based on an event or current state +void CBasePlayer::SetAnimation( PLAYER_ANIM playerAnim ) +{ + int animDesired; + float speed; + char szAnim[64]; + + speed = pev->velocity.Length2D(); + + if (pev->flags & FL_FROZEN) + { + speed = 0; + playerAnim = PLAYER_IDLE; + } + + switch (playerAnim) + { + case PLAYER_JUMP: + m_IdealActivity = ACT_HOP; + break; + + case PLAYER_SUPERJUMP: + m_IdealActivity = ACT_LEAP; + break; + + case PLAYER_DIE: + m_IdealActivity = ACT_DIESIMPLE; + m_IdealActivity = GetDeathActivity( ); + break; + + case PLAYER_ATTACK1: + switch( m_Activity ) + { + case ACT_HOVER: + case ACT_SWIM: + case ACT_HOP: + case ACT_LEAP: + case ACT_DIESIMPLE: + m_IdealActivity = m_Activity; + break; + default: + m_IdealActivity = ACT_RANGE_ATTACK1; + break; + } + break; + case PLAYER_IDLE: + case PLAYER_WALK: + if ( !FBitSet( pev->flags, FL_ONGROUND ) && (m_Activity == ACT_HOP || m_Activity == ACT_LEAP) ) // Still jumping + { + m_IdealActivity = m_Activity; + } + else if ( pev->waterlevel > 1 ) + { + if ( speed == 0 ) + m_IdealActivity = ACT_HOVER; + else + m_IdealActivity = ACT_SWIM; + } + else + { + m_IdealActivity = ACT_WALK; + } + break; + } + + switch (m_IdealActivity) + { + case ACT_HOVER: + case ACT_LEAP: + case ACT_SWIM: + case ACT_HOP: + case ACT_DIESIMPLE: + default: + if ( m_Activity == m_IdealActivity) + return; + m_Activity = m_IdealActivity; + + animDesired = LookupActivity( m_Activity ); + // Already using the desired animation? + if (pev->sequence == animDesired) + return; + + pev->gaitsequence = 0; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + + case ACT_RANGE_ATTACK1: + if ( FBitSet( pev->flags, FL_DUCKING ) ) // crouching + strcpy( szAnim, "crouch_shoot_" ); + else + strcpy( szAnim, "ref_shoot_" ); + strcat( szAnim, m_szAnimExtention ); + animDesired = LookupSequence( szAnim ); + if (animDesired == -1) + animDesired = 0; + + if ( pev->sequence != animDesired || !m_fSequenceLoops ) + { + pev->frame = 0; + } + + if (!m_fSequenceLoops) + { + pev->effects |= EF_NOINTERP; + } + + m_Activity = m_IdealActivity; + + pev->sequence = animDesired; + ResetSequenceInfo( ); + break; + + case ACT_WALK: + if (m_Activity != ACT_RANGE_ATTACK1 || m_fSequenceFinished) + { + if ( FBitSet( pev->flags, FL_DUCKING ) ) // crouching + strcpy( szAnim, "crouch_aim_" ); + else + strcpy( szAnim, "ref_aim_" ); + strcat( szAnim, m_szAnimExtention ); + animDesired = LookupSequence( szAnim ); + if (animDesired == -1) + animDesired = 0; + m_Activity = ACT_WALK; + } + else + { + animDesired = pev->sequence; + } + } + + if ( FBitSet( pev->flags, FL_DUCKING ) ) + { + if ( speed == 0) + { + pev->gaitsequence = LookupActivity( ACT_CROUCHIDLE ); + // pev->gaitsequence = LookupActivity( ACT_CROUCH ); + } + else + { + pev->gaitsequence = LookupActivity( ACT_CROUCH ); + } + } + else if ( speed > 220 ) + { + pev->gaitsequence = LookupActivity( ACT_RUN ); + } + else if (speed > 0) + { + pev->gaitsequence = LookupActivity( ACT_WALK ); + } + else + { + // pev->gaitsequence = LookupActivity( ACT_WALK ); + pev->gaitsequence = LookupSequence( "deep_idle" ); + } + + + // Already using the desired animation? + if (pev->sequence == animDesired) + return; + + //ALERT( at_console, "Set animation to %d\n", animDesired ); + // Reset to first frame of desired animation + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); +} + +/* +=========== +TabulateAmmo +This function is used to find and store +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" ) ); +} + + +/* +=========== +WaterMove +============ +*/ +#define AIRTIME 12 // lung full of air lasts this many seconds + +void CBasePlayer::WaterMove() +{ + int air; + + if (pev->movetype == MOVETYPE_NOCLIP) + return; + + if (pev->health < 0) + return; + + // waterlevel 0 - not in water + // waterlevel 1 - feet in water + // waterlevel 2 - waist in water + // waterlevel 3 - head in water + + if (pev->waterlevel != 3) + { + // not underwater + + // play 'up for air' sound + if (pev->air_finished < gpGlobals->time) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_wade1.wav", 1, ATTN_NORM); + else if (pev->air_finished < gpGlobals->time + 9) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_wade2.wav", 1, ATTN_NORM); + + pev->air_finished = gpGlobals->time + AIRTIME; + pev->dmg = 2; + + // if we took drowning damage, give it back slowly + if (m_idrowndmg > m_idrownrestored) + { + // set drowning damage bit. hack - dmg_drownrecover actually + // makes the time based damage code 'give back' health over time. + // make sure counter is cleared so we start count correctly. + + // NOTE: this actually causes the count to continue restarting + // until all drowning damage is healed. + + m_bitsDamageType |= DMG_DROWNRECOVER; + m_bitsDamageType &= ~DMG_DROWN; + m_rgbTimeBasedDamage[itbd_DrownRecover] = 0; + } + + } + else + { // fully under water + // stop restoring damage while underwater + m_bitsDamageType &= ~DMG_DROWNRECOVER; + m_rgbTimeBasedDamage[itbd_DrownRecover] = 0; + + if (pev->air_finished < gpGlobals->time) // drown! + { + if (pev->pain_finished < gpGlobals->time) + { + // take drowning damage + pev->dmg += 1; + if (pev->dmg > 5) + pev->dmg = 5; + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), pev->dmg, DMG_DROWN); + pev->pain_finished = gpGlobals->time + 1; + + // track drowning damage, give it back when + // player finally takes a breath + + m_idrowndmg += static_cast(pev->dmg); + } + } + else + { + m_bitsDamageType &= ~DMG_DROWN; + } + } + + if (!pev->waterlevel) + { + if (FBitSet(pev->flags, FL_INWATER)) + { + ClearBits(pev->flags, FL_INWATER); + } + return; + } + + // make bubbles + + air = (int)(pev->air_finished - gpGlobals->time); + if (!RANDOM_LONG(0,0x1f) && RANDOM_LONG(0,AIRTIME-1) >= air) + { + switch (RANDOM_LONG(0,3)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim1.wav", 0.8, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim2.wav", 0.8, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim3.wav", 0.8, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim4.wav", 0.8, ATTN_NORM); break; + } + } + + if (pev->watertype == CONTENT_LAVA) // do damage + { + if (pev->dmgtime < gpGlobals->time) + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), 10 * pev->waterlevel, DMG_BURN); + } + else if (pev->watertype == CONTENT_SLIME) // do damage + { + pev->dmgtime = gpGlobals->time + 1; + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), 4 * pev->waterlevel, DMG_ACID); + } + + if (!FBitSet(pev->flags, FL_INWATER)) + { + SetBits(pev->flags, FL_INWATER); + pev->dmgtime = 0; + } +} + + +// TRUE if the player is attached to a ladder +BOOL CBasePlayer::IsOnLadder( void ) +{ + return ( pev->movetype == MOVETYPE_FLY ); +} + +void CBasePlayer::PlayerDeathThink(void) +{ + float flForward; + + if (FBitSet(pev->flags, FL_ONGROUND)) + { + flForward = pev->velocity.Length() - 20; + if (flForward <= 0) + pev->velocity = g_vecZero; + else + pev->velocity = flForward * pev->velocity.Normalize(); + } + + if ( HasWeapons() ) + { + // we drop the guns here because weapons that have an area effect and can kill their user + // will sometimes crash coming back from CBasePlayer::Killed() if they kill their owner because the + // player class sometimes is freed. It's safer to manipulate the weapons once we know + // we aren't calling into any of their code anymore through the player pointer. + PackDeadPlayerItems(); + } + + + if (pev->modelindex && (!m_fSequenceFinished) && (pev->deadflag == DEAD_DYING)) + { + StudioFrameAdvance( ); + + m_iRespawnFrames++; // Note, these aren't necessarily real "frames", so behavior is dependent on # of client movement commands + if ( m_iRespawnFrames < 120 ) // Animations should be no longer than this + return; + } + + // once we're done animating our death and we're on the ground, we want to set movetype to None so our dead body won't do collisions and stuff anymore + // this prevents a bug where the dead body would go to a player's head if he walked over it while the dead player was clicking their button to respawn + if ( pev->movetype != MOVETYPE_NONE && FBitSet(pev->flags, FL_ONGROUND) ) + pev->movetype = MOVETYPE_NONE; + + if (pev->deadflag == DEAD_DYING) + pev->deadflag = DEAD_DEAD; + + StopAnimation(); + + pev->effects |= EF_NOINTERP; + pev->framerate = 0.0; + + BOOL fAnyButtonDown = (pev->button & ~IN_SCORE ); + + // wait for all buttons released + if (pev->deadflag == DEAD_DEAD) + { + if (fAnyButtonDown) + return; + + if ( g_pGameRules->FPlayerCanRespawn( this ) ) + { + m_fDeadTime = gpGlobals->time; + pev->deadflag = DEAD_RESPAWNABLE; + } + + return; + } + +// if the player has been dead for one second longer than allowed by forcerespawn, +// forcerespawn isn't on. Send the player off to an intermission camera until they +// choose to respawn. + if ( g_pGameRules->IsMultiplayer() && ( gpGlobals->time > (m_fDeadTime + 6) ) && !(m_afPhysicsFlags & PFLAG_OBSERVER) ) + { + // go to dead camera. + StartDeathCam(); + } + +// wait for any button down, or mp_forcerespawn is set and the respawn time is up + if (!fAnyButtonDown + && !( g_pGameRules->IsMultiplayer() && forcerespawn.value > 0 && (gpGlobals->time > (m_fDeadTime + 5))) ) + return; + + pev->button = 0; + m_iRespawnFrames = 0; + + //ALERT(at_console, "Respawn\n"); + + respawn(pev, !(m_afPhysicsFlags & PFLAG_OBSERVER) );// don't copy a corpse if we're in deathcam. + pev->nextthink = -1; +} + +//========================================================= +// StartDeathCam - find an intermission spot and send the +// player off into observer mode +//========================================================= +void CBasePlayer::StartDeathCam( void ) +{ + edict_t *pSpot, *pNewSpot; + int iRand; + + if ( pev->view_ofs == g_vecZero ) + { + // don't accept subsequent attempts to StartDeathCam() + return; + } + + pSpot = FIND_ENTITY_BY_CLASSNAME( NULL, "info_intermission"); + + if ( !FNullEnt( pSpot ) ) + { + // at least one intermission spot in the world. + iRand = RANDOM_LONG( 0, 3 ); + + while ( iRand > 0 ) + { + pNewSpot = FIND_ENTITY_BY_CLASSNAME( pSpot, "info_intermission"); + + if ( pNewSpot ) + { + pSpot = pNewSpot; + } + + iRand--; + } + + CopyToBodyQue( pev ); + StartObserver( pSpot->v.origin, pSpot->v.v_angle ); + } + else + { + // no intermission spot. Push them up in the air, looking down at their corpse + TraceResult tr; + CopyToBodyQue( pev ); + UTIL_TraceLine( pev->origin, pev->origin + Vector( 0, 0, 128 ), ignore_monsters, edict(), &tr ); + StartObserver( tr.vecEndPos, UTIL_VecToAngles( tr.vecEndPos - pev->origin ) ); + return; + } +} + +void CBasePlayer::StartObserver( Vector vecPosition, Vector vecViewAngle ) +{ + m_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 +// +#define PLAYER_SEARCH_RADIUS (float)64 + +void CBasePlayer::PlayerUse ( void ) +{ + // Was use pressed or released? + if ( ! ((pev->button | m_afButtonPressed | m_afButtonReleased) & IN_USE) ) + return; + + // Hit Use on a train? + if ( m_afButtonPressed & IN_USE ) + { + if ( m_pTank != 0 ) + { + // Stop controlling the tank + // TODO: Send HUD Update + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + return; + } + else + { + if ( m_afPhysicsFlags & PFLAG_ONTRAIN ) + { + m_afPhysicsFlags &= ~PFLAG_ONTRAIN; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + else + { // Start controlling the train! + CBaseEntity *pTrain = CBaseEntity::Instance( pev->groundentity ); + + if ( pTrain && !(pev->button & IN_JUMP) && FBitSet(pev->flags, FL_ONGROUND) && (pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) && pTrain->OnControls(pev) ) + { + m_afPhysicsFlags |= PFLAG_ONTRAIN; + m_iTrain = TrainSpeed(static_cast(pTrain->pev->speed), pTrain->pev->impulse); + m_iTrain |= TRAIN_NEW; + EMIT_SOUND( ENT(pev), CHAN_ITEM, "plats/train_use1.wav", 0.8, ATTN_NORM); + return; + } + } + } + } + + CBaseEntity *pObject = NULL; + CBaseEntity *pClosest = NULL; + Vector vecLOS; + float flMaxDot = VIEW_FIELD_NARROW; + float flDot; + + UTIL_MakeVectors ( pev->v_angle );// so we know which way we are facing + + while ((pObject = UTIL_FindEntityInSphere( pObject, pev->origin, PLAYER_SEARCH_RADIUS )) != NULL) + { + + if (pObject->ObjectCaps() & (FCAP_IMPULSE_USE | FCAP_CONTINUOUS_USE | FCAP_ONOFF_USE)) + { + // !!!PERFORMANCE- should this check be done on a per case basis AFTER we've determined that + // this object is actually usable? This dot is being done for every object within PLAYER_SEARCH_RADIUS + // when player hits the use key. How many objects can be in that area, anyway? (sjb) + vecLOS = (VecBModelOrigin( pObject->pev ) - (pev->origin + pev->view_ofs)); + + // This essentially moves the origin of the target to the corner nearest the player to test to see + // if it's "hull" is in the view cone + vecLOS = UTIL_ClampVectorToBox( vecLOS, pObject->pev->size * 0.5 ); + + flDot = DotProduct (vecLOS , gpGlobals->v_forward); + if (flDot > flMaxDot ) + {// only if the item is in front of the user + pClosest = pObject; + flMaxDot = flDot; +// ALERT( at_console, "%s : %f\n", STRING( pObject->pev->classname ), flDot ); + } +// ALERT( at_console, "%s : %f\n", STRING( pObject->pev->classname ), flDot ); + } + } + pObject = pClosest; + + // Found an object + if (pObject ) + { + //!!!UNDONE: traceline here to prevent USEing buttons through walls + int caps = pObject->ObjectCaps(); + + if ( m_afButtonPressed & IN_USE ) + EMIT_SOUND( ENT(pev), CHAN_ITEM, "common/wpn_select.wav", 0.4, ATTN_NORM); + + if ( ( (pev->button & IN_USE) && (caps & FCAP_CONTINUOUS_USE) ) || + ( (m_afButtonPressed & IN_USE) && (caps & (FCAP_IMPULSE_USE|FCAP_ONOFF_USE)) ) ) + { + if ( caps & FCAP_CONTINUOUS_USE ) + m_afPhysicsFlags |= PFLAG_USING; + + pObject->Use( this, this, USE_SET, 1 ); + } + // UNDONE: Send different USE codes for ON/OFF. Cache last ONOFF_USE object to send 'off' if you turn away + else if ( (m_afButtonReleased & IN_USE) && (pObject->ObjectCaps() & FCAP_ONOFF_USE) ) // BUGBUG This is an "off" use + { + pObject->Use( this, this, USE_SET, 0 ); + } + } + else + { + if ( m_afButtonPressed & IN_USE ) + EMIT_SOUND( ENT(pev), CHAN_ITEM, "common/wpn_denyselect.wav", 0.4, ATTN_NORM); + } +} + + + +void CBasePlayer::Jump() +{ + Vector vecWallCheckDir;// direction we're tracing a line to find a wall when walljumping + Vector vecAdjustedVelocity; + Vector vecSpot; + TraceResult tr; + + if (FBitSet(pev->flags, FL_WATERJUMP)) + return; + + if (pev->waterlevel >= 2) + { + return; + } + + // jump velocity is sqrt( height * gravity * 2) + + // If this isn't the first frame pressing the jump button, break out. + if ( !FBitSet( m_afButtonPressed, IN_JUMP ) ) + return; // don't pogo stick + + if ( !(pev->flags & FL_ONGROUND) || !pev->groundentity ) + { + return; + } + +// many features in this function use v_forward, so makevectors now. + UTIL_MakeVectors (pev->angles); + + // ClearBits(pev->flags, FL_ONGROUND); // don't stairwalk + + SetAnimation( PLAYER_JUMP ); + + if ( m_fLongJump && + (pev->button & IN_DUCK) && + ( pev->flDuckTime > 0 ) && + pev->velocity.Length() > 50 ) + { + SetAnimation( PLAYER_SUPERJUMP ); + } + + // If you're standing on a conveyor, add it's velocity to yours (for momentum) + entvars_t *pevGround = VARS(pev->groundentity); + if ( pevGround && (pevGround->flags & FL_CONVEYOR) ) + { + pev->velocity = pev->velocity + pev->basevelocity; + } +} + + + +// This is a glorious hack to find free space when you've crouched into some solid space +// Our crouching collisions do not work correctly for some reason and this is easier +// than fixing the problem :( +void FixPlayerCrouchStuck( edict_t *pPlayer ) +{ + TraceResult trace; + + // Move up as many as 18 pixels if the player is stuck. + for ( int i = 0; i < 18; i++ ) + { + UTIL_TraceHull( pPlayer->v.origin, pPlayer->v.origin, dont_ignore_monsters, head_hull, pPlayer, &trace ); + if ( trace.fStartSolid ) + pPlayer->v.origin.z ++; + else + break; + } +} + +void CBasePlayer::Duck( ) +{ + if (pev->button & IN_DUCK) + { + if ( m_IdealActivity != ACT_LEAP ) + { + SetAnimation( PLAYER_WALK ); + } + } +} + +// +// ID's player as such. +// +int CBasePlayer::Classify ( void ) +{ + return CLASS_PLAYER; +} + + +void CBasePlayer::AddPoints( int score, BOOL bAllowNegativeScore ) +{ + // Positive score always adds + if ( score < 0 ) + { + if ( !bAllowNegativeScore ) + { + if ( pev->frags < 0 ) // Can't go more negative + return; + + if ( -score > pev->frags ) // Will this go negative? + { + score = static_cast(-pev->frags); // Sum will be 0 + } + } + } + + pev->frags += score; + + MESSAGE_BEGIN( MSG_ALL, gmsgScoreInfo ); + WRITE_BYTE( ENTINDEX(edict()) ); + WRITE_SHORT( static_cast(pev->frags) ); + WRITE_SHORT( m_iDeaths ); + WRITE_SHORT( 0 ); + WRITE_SHORT( g_pGameRules->GetTeamIndex( m_szTeamName ) + 1 ); + MESSAGE_END(); +} + + +void CBasePlayer::AddPointsToTeam( int score, BOOL bAllowNegativeScore ) +{ + int index = entindex(); + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + if ( pPlayer && i != index ) + { + if ( g_pGameRules->PlayerRelationship( this, pPlayer ) == GR_TEAMMATE ) + { + pPlayer->AddPoints( score, bAllowNegativeScore ); + } + } + } +} + +//Player ID +void CBasePlayer::InitStatusBar() +{ + m_flStatusBarDisappearDelay = 0; + m_SbarString1[0] = m_SbarString0[0] = 0; +} + +void CBasePlayer::UpdateStatusBar() +{ + int newSBarState[ SBAR_END ]; + char sbuf0[ SBAR_STRING_SIZE ]; + char sbuf1[ SBAR_STRING_SIZE ]; + + memset( newSBarState, 0, sizeof(newSBarState) ); + strcpy( sbuf0, m_SbarString0 ); + strcpy( sbuf1, m_SbarString1 ); + + // Find an ID Target + TraceResult tr; + UTIL_MakeVectors( pev->v_angle + pev->punchangle ); + Vector vecSrc = EyePosition(); + Vector vecEnd = vecSrc + (gpGlobals->v_forward * MAX_ID_RANGE); + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, edict(), &tr); + + if (tr.flFraction != 1.0) + { + if ( !FNullEnt( tr.pHit ) ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + + if (pEntity->Classify() == CLASS_PLAYER ) + { + newSBarState[ SBAR_ID_TARGETNAME ] = ENTINDEX( pEntity->edict() ); + strcpy( sbuf1, "1 %p1\n2 Health: %i2%%\n3 Armor: %i3%%" ); + + // allies and medics get to see the targets health + if ( g_pGameRules->PlayerRelationship( this, pEntity ) == GR_TEAMMATE ) + { + newSBarState[ SBAR_ID_TARGETHEALTH ] = static_cast(100 * (pEntity->pev->health / pEntity->pev->max_health)); + newSBarState[ SBAR_ID_TARGETARMOR ] = static_cast(pEntity->pev->armorvalue); //No need to get it % based since 100 it's the max. + } + + m_flStatusBarDisappearDelay = gpGlobals->time + 1.0; + } + } + else if ( m_flStatusBarDisappearDelay > gpGlobals->time ) + { + // hold the values for a short amount of time after viewing the object + newSBarState[ SBAR_ID_TARGETNAME ] = m_izSBarState[ SBAR_ID_TARGETNAME ]; + newSBarState[ SBAR_ID_TARGETHEALTH ] = m_izSBarState[ SBAR_ID_TARGETHEALTH ]; + newSBarState[ SBAR_ID_TARGETARMOR ] = m_izSBarState[ SBAR_ID_TARGETARMOR ]; + } + } + + BOOL bForceResend = FALSE; + + if ( strcmp( sbuf0, m_SbarString0 ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgStatusText, NULL, pev ); + WRITE_BYTE( 0 ); + WRITE_STRING( sbuf0 ); + MESSAGE_END(); + + strcpy( m_SbarString0, sbuf0 ); + + // make sure everything's resent + bForceResend = TRUE; + } + + if ( strcmp( sbuf1, m_SbarString1 ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgStatusText, NULL, pev ); + WRITE_BYTE( 1 ); + WRITE_STRING( sbuf1 ); + MESSAGE_END(); + + strcpy( m_SbarString1, sbuf1 ); + + // make sure everything's resent + bForceResend = TRUE; + } + + // Check values and send if they don't match + for (int i = 1; i < SBAR_END; i++) + { + if ( newSBarState[i] != m_izSBarState[i] || bForceResend ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgStatusValue, NULL, pev ); + WRITE_BYTE( i ); + WRITE_SHORT( newSBarState[i] ); + MESSAGE_END(); + + m_izSBarState[i] = newSBarState[i]; + } + } +} + + + + + + + + + +#define CLIMB_SHAKE_FREQUENCY 22 // how many frames in between screen shakes when climbing +#define MAX_CLIMB_SPEED 200 // fastest vertical climbing speed possible +#define CLIMB_SPEED_DEC 15 // climbing deceleration rate +#define CLIMB_PUNCH_X -7 // how far to 'punch' client X axis when climbing +#define CLIMB_PUNCH_Z 7 // how far to 'punch' client Z axis when climbing + +void CBasePlayer::PreThink(void) +{ + int buttonsChanged = (m_afButtonLast ^ pev->button); // These buttons have changed this frame + + // Debounced button codes for pressed/released + // UNDONE: Do we need auto-repeat? + m_afButtonPressed = buttonsChanged & pev->button; // The changed ones still down are "pressed" + m_afButtonReleased = buttonsChanged & (~pev->button); // The ones not down are "released" + + g_pGameRules->PlayerThink( this ); + + if ( g_fGameOver ) + return; // intermission or finale + + UTIL_MakeVectors(pev->v_angle); // is this still used? + + ItemPreFrame( ); + WaterMove(); + + if ( g_pGameRules && g_pGameRules->FAllowFlashlight() ) + m_iHideHUD &= ~HIDEHUD_FLASHLIGHT; + else + m_iHideHUD |= HIDEHUD_FLASHLIGHT; + + + // JOHN: checks if new client data (for HUD and view control) needs to be sent to the client + UpdateClientData(); + + CheckTimeBasedDamage(); + + CheckSuitUpdate(); + + if (pev->deadflag >= DEAD_DYING) + { + PlayerDeathThink(); + return; + } + + // So the correct flags get sent to client asap. + // + if ( m_afPhysicsFlags & PFLAG_ONTRAIN ) + pev->flags |= FL_ONTRAIN; + else + pev->flags &= ~FL_ONTRAIN; + + // Train speed control + if ( m_afPhysicsFlags & PFLAG_ONTRAIN ) + { + CBaseEntity *pTrain = CBaseEntity::Instance( pev->groundentity ); + float vel; + + if ( !pTrain ) + { + TraceResult trainTrace; + // Maybe this is on the other side of a level transition + UTIL_TraceLine( pev->origin, pev->origin + Vector(0,0,-38), ignore_monsters, ENT(pev), &trainTrace ); + + // HACKHACK - Just look for the func_tracktrain classname + if ( trainTrace.flFraction != 1.0 && trainTrace.pHit ) + pTrain = CBaseEntity::Instance( trainTrace.pHit ); + + + if ( !pTrain || !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) || !pTrain->OnControls(pev) ) + { + //ALERT( at_error, "In train mode with no train!\n" ); + m_afPhysicsFlags &= ~PFLAG_ONTRAIN; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + } + else if ( !FBitSet( pev->flags, FL_ONGROUND ) || FBitSet( pTrain->pev->spawnflags, SF_TRACKTRAIN_NOCONTROL ) || (pev->button & (IN_MOVELEFT|IN_MOVERIGHT) ) ) + { + // Turn off the train if you jump, strafe, or the train controls go dead + m_afPhysicsFlags &= ~PFLAG_ONTRAIN; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + + pev->velocity = g_vecZero; + vel = 0; + if ( m_afButtonPressed & IN_FORWARD ) + { + vel = 1; + pTrain->Use( this, this, USE_SET, (float)vel ); + } + else if ( m_afButtonPressed & IN_BACK ) + { + vel = -1; + pTrain->Use( this, this, USE_SET, (float)vel ); + } + + if (vel) + { + m_iTrain = TrainSpeed(static_cast(pTrain->pev->speed), pTrain->pev->impulse); + m_iTrain |= TRAIN_ACTIVE|TRAIN_NEW; + } + + } else if (m_iTrain & TRAIN_ACTIVE) + m_iTrain = TRAIN_NEW; // turn off train + + if (pev->button & IN_JUMP) + { + // If on a ladder, jump off the ladder + // else Jump + Jump(); + } + + + // If trying to duck, already ducked, or in the process of ducking + if ((pev->button & IN_DUCK) || FBitSet(pev->flags,FL_DUCKING) || (m_afPhysicsFlags & PFLAG_DUCKING) ) + Duck(); + + if ( !FBitSet ( pev->flags, FL_ONGROUND ) ) + { + m_flFallVelocity = -pev->velocity.z; + } + + // StudioFrameAdvance( );//!!!HACKHACK!!! Can't be hit by traceline when not animating? + + // Clear out ladder pointer + m_hEnemy = NULL; + + if ( m_afPhysicsFlags & PFLAG_ONBARNACLE ) + { + pev->velocity = g_vecZero; + } +} +/* Time based Damage works as follows: + 1) There are several types of timebased damage: + + #define DMG_PARALYZE (1 << 14) // slows affected creature down + #define DMG_NERVEGAS (1 << 15) // nerve toxins, very bad + #define DMG_POISON (1 << 16) // blood poisioning + #define DMG_RADIATION (1 << 17) // radiation exposure + #define DMG_DROWNRECOVER (1 << 18) // drown recovery + #define DMG_ACID (1 << 19) // toxic chemicals or acid burns + #define DMG_SLOWBURN (1 << 20) // in an oven + #define DMG_SLOWFREEZE (1 << 21) // in a subzero freezer + + 2) A new hit inflicting tbd restarts the tbd counter - each monster has an 8bit counter, + per damage type. The counter is decremented every second, so the maximum time + an effect will last is 255/60 = 4.25 minutes. Of course, staying within the radius + of a damaging effect like fire, nervegas, radiation will continually reset the counter to max. + + 3) Every second that a tbd counter is running, the player takes damage. The damage + is determined by the type of tdb. + Paralyze - 1/2 movement rate, 30 second duration. + Nervegas - 5 points per second, 16 second duration = 80 points max dose. + Poison - 2 points per second, 25 second duration = 50 points max dose. + Radiation - 1 point per second, 50 second duration = 50 points max dose. + Drown - 5 points per second, 2 second duration. + Acid/Chemical - 5 points per second, 10 second duration = 50 points max. + Burn - 10 points per second, 2 second duration. + Freeze - 3 points per second, 10 second duration = 30 points max. + + 4) Certain actions or countermeasures counteract the damaging effects of tbds: + + Armor/Heater/Cooler - Chemical(acid),burn, freeze all do damage to armor power, then to body + - recharged by suit recharger + Air In Lungs - drowning damage is done to air in lungs first, then to body + - recharged by poking head out of water + - 10 seconds if swiming fast + Air In SCUBA - drowning damage is done to air in tanks first, then to body + - 2 minutes in tanks. Need new tank once empty. + Radiation Syringe - Each syringe full provides protection vs one radiation dosage + Antitoxin Syringe - Each syringe full provides protection vs one poisoning (nervegas or poison). + Health kit - Immediate stop to acid/chemical, fire or freeze damage. + Radiation Shower - Immediate stop to radiation damage, acid/chemical or fire damage. + + +*/ + +// If player is taking time based damage, continue doing damage to player - +// this simulates the effect of being poisoned, gassed, dosed with radiation etc - +// anything that continues to do damage even after the initial contact stops. +// Update all time based damage counters, and shut off any that are done. + +// The m_bitsDamageType bit MUST be set if any damage is to be taken. +// This routine will detect the initial on value of the m_bitsDamageType +// and init the appropriate counter. Only processes damage every second. + +//#define PARALYZE_DURATION 30 // number of 2 second intervals to take damage +//#define PARALYZE_DAMAGE 0.0 // damage to take each 2 second interval + +//#define NERVEGAS_DURATION 16 +//#define NERVEGAS_DAMAGE 5.0 + +//#define POISON_DURATION 25 +//#define POISON_DAMAGE 2.0 + +//#define RADIATION_DURATION 50 +//#define RADIATION_DAMAGE 1.0 + +//#define ACID_DURATION 10 +//#define ACID_DAMAGE 5.0 + +//#define SLOWBURN_DURATION 2 +//#define SLOWBURN_DAMAGE 1.0 + +//#define SLOWFREEZE_DURATION 1.0 +//#define SLOWFREEZE_DAMAGE 3.0 + +/* */ + + +void CBasePlayer::CheckTimeBasedDamage() +{ + int i; + BYTE bDuration = 0; + + if (!(m_bitsDamageType & DMG_TIMEBASED)) + return; + + // only check for time based damage approx. every 2 seconds + if (abs(static_cast(gpGlobals->time - m_tbdPrev)) < 2.0) + return; + + m_tbdPrev = gpGlobals->time; + + for (i = 0; i < CDMG_TIMEBASED; i++) + { + // make sure bit is set for damage type + if (m_bitsDamageType & (DMG_PARALYZE << i)) + { + switch (i) + { + case itbd_Paralyze: + // UNDONE - flag movement as half-speed + bDuration = PARALYZE_DURATION; + break; + case itbd_NerveGas: +// TakeDamage(pev, pev, NERVEGAS_DAMAGE, DMG_GENERIC); + bDuration = NERVEGAS_DURATION; + break; + case itbd_Poison: + TakeDamage(pev, pev, POISON_DAMAGE, DMG_GENERIC); + bDuration = POISON_DURATION; + break; + case itbd_Radiation: +// TakeDamage(pev, pev, RADIATION_DAMAGE, DMG_GENERIC); + bDuration = RADIATION_DURATION; + break; + case itbd_DrownRecover: + // NOTE: this hack is actually used to RESTORE health + // after the player has been drowning and finally takes a breath + if (m_idrowndmg > m_idrownrestored) + { + int idif = min(m_idrowndmg - m_idrownrestored, 10); + + TakeHealth(idif, DMG_GENERIC); + m_idrownrestored += idif; + } + bDuration = 4; // get up to 5*10 = 50 points back + break; + case itbd_Acid: +// TakeDamage(pev, pev, ACID_DAMAGE, DMG_GENERIC); + bDuration = ACID_DURATION; + break; + case itbd_SlowBurn: +// TakeDamage(pev, pev, SLOWBURN_DAMAGE, DMG_GENERIC); + bDuration = SLOWBURN_DURATION; + break; + case itbd_SlowFreeze: +// TakeDamage(pev, pev, SLOWFREEZE_DAMAGE, DMG_GENERIC); + bDuration = SLOWFREEZE_DURATION; + break; + default: + bDuration = 0; + } + + if (m_rgbTimeBasedDamage[i]) + { + // use up an antitoxin on poison or nervegas after a few seconds of damage + if (((i == itbd_NerveGas) && (m_rgbTimeBasedDamage[i] < NERVEGAS_DURATION)) || + ((i == itbd_Poison) && (m_rgbTimeBasedDamage[i] < POISON_DURATION))) + { + if (m_rgItems[ITEM_ANTIDOTE]) + { + m_rgbTimeBasedDamage[i] = 0; + m_rgItems[ITEM_ANTIDOTE]--; + SetSuitUpdate("!HEV_HEAL4", FALSE, SUIT_REPEAT_OK); + } + } + + + // decrement damage duration, detect when done. + if (!m_rgbTimeBasedDamage[i] || --m_rgbTimeBasedDamage[i] == 0) + { + m_rgbTimeBasedDamage[i] = 0; + // if we're done, clear damage bits + m_bitsDamageType &= ~(DMG_PARALYZE << i); + } + } + else + // first time taking this damage type - init damage duration + m_rgbTimeBasedDamage[i] = bDuration; + } + } +} + +/* +THE POWER SUIT + +The Suit provides 3 main functions: Protection, Notification and Augmentation. +Some functions are automatic, some require power. +The player gets the suit shortly after getting off the train in C1A0 and it stays +with him for the entire game. + +Protection + + Heat/Cold + When the player enters a hot/cold area, the heating/cooling indicator on the suit + will come on and the battery will drain while the player stays in the area. + After the battery is dead, the player starts to take damage. + This feature is built into the suit and is automatically engaged. + Radiation Syringe + This will cause the player to be immune from the effects of radiation for N seconds. Single use item. + Anti-Toxin Syringe + This will cure the player from being poisoned. Single use item. + Health + Small (1st aid kits, food, etc.) + Large (boxes on walls) + Armor + The armor works using energy to create a protective field that deflects a + percentage of damage projectile and explosive attacks. After the armor has been deployed, + it will attempt to recharge itself to full capacity with the energy reserves from the battery. + It takes the armor N seconds to fully charge. + +Notification (via the HUD) + +x Health +x Ammo +x Automatic Health Care + Notifies the player when automatic healing has been engaged. +x Geiger counter + Classic Geiger counter sound and status bar at top of HUD + alerts player to dangerous levels of radiation. This is not visible when radiation levels are normal. +x Poison + Armor + Displays the current level of armor. + +Augmentation + + Reanimation (w/adrenaline) + Causes the player to come back to life after he has been dead for 3 seconds. + Will not work if player was gibbed. Single use. + Long Jump + Used by hitting the ??? key(s). Caused the player to further than normal. + SCUBA + Used automatically after picked up and after player enters the water. + Works for N seconds. Single use. + +Things powered by the battery + + Armor + Uses N watts for every M units of damage. + Heat/Cool + Uses N watts for every second in hot/cold area. + Long Jump + Uses N watts for every jump. + Alien Cloak + Uses N watts for each use. Each use lasts M seconds. + Alien Shield + Augments armor. Reduces Armor drain by one half + +*/ + +// if in range of radiation source, ping geiger counter + +#define GEIGERDELAY 0.25 + +void CBasePlayer :: UpdateGeigerCounter( void ) +{ + BYTE range; + + // delay per update ie: don't flood net with these msgs + if (gpGlobals->time < m_flgeigerDelay) + return; + + m_flgeigerDelay = gpGlobals->time + GEIGERDELAY; + + // send range to radition source to client + + range = (BYTE) (m_flgeigerRange / 4); + + if (range != m_igeigerRangePrev) + { + m_igeigerRangePrev = range; + + MESSAGE_BEGIN( MSG_ONE, gmsgGeigerRange, NULL, pev ); + WRITE_BYTE( range ); + MESSAGE_END(); + } + + // reset counter and semaphore + if (!RANDOM_LONG(0,3)) + m_flgeigerRange = 1000; + +} + +/* +================ +CheckSuitUpdate + +Play suit update if it's time +================ +*/ + +#define SUITUPDATETIME 3.5 +#define SUITFIRSTUPDATETIME 0.1 + +void CBasePlayer::CheckSuitUpdate() +{ + int i; + int isentence = 0; + int isearch = m_iSuitPlayNext; + + // Ignore suit updates if no suit + if ( !(pev->weapons & (1<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 + +Check for turning off powerups + +GLOBALS ASSUMED SET: g_ulModelIndexPlayer +================ +*/ + static void +CheckPowerups(entvars_t *pev) +{ + if (pev->health <= 0) + return; + + pev->modelindex = g_ulModelIndexPlayer; // don't use eyes +} + + +//========================================================= +// UpdatePlayerSound - updates the position of the player's +// reserved sound slot in the sound list. +//========================================================= +void CBasePlayer :: UpdatePlayerSound ( void ) +{ + int iBodyVolume; + int iVolume; + CSound *pSound; + + pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt :: ClientSoundIndex( edict() ) ); + + if ( !pSound ) + { + ALERT ( at_console, "Client lost reserved sound!\n" ); + return; + } + + pSound->m_iType = bits_SOUND_NONE; + + // now calculate the best target volume for the sound. If the player's weapon + // is louder than his body/movement, use the weapon volume, else, use the body volume. + + if ( FBitSet ( pev->flags, FL_ONGROUND ) ) + { + iBodyVolume = static_cast(pev->velocity.Length()); + + // clamp the noise that can be made by the body, in case a push trigger, + // weapon recoil, or anything shoves the player abnormally fast. + if ( iBodyVolume > 512 ) + { + iBodyVolume = 512; + } + } + else + { + iBodyVolume = 0; + } + + if ( pev->button & IN_JUMP ) + { + iBodyVolume += 100; + } + +// convert player move speed and actions into sound audible by monsters. + if ( m_iWeaponVolume > iBodyVolume ) + { + m_iTargetVolume = m_iWeaponVolume; + + // OR in the bits for COMBAT sound if the weapon is being louder than the player. + pSound->m_iType |= bits_SOUND_COMBAT; + } + else + { + m_iTargetVolume = iBodyVolume; + } + + // decay weapon volume over time so bits_SOUND_COMBAT stays set for a while + m_iWeaponVolume -= static_cast(250 * gpGlobals->frametime); + if ( m_iWeaponVolume < 0 ) + { + iVolume = 0; + } + + + // if target volume is greater than the player sound's current volume, we paste the new volume in + // immediately. If target is less than the current volume, current volume is not set immediately to the + // lower volume, rather works itself towards target volume over time. This gives monsters a much better chance + // to hear a sound, especially if they don't listen every frame. + iVolume = pSound->m_iVolume; + + if ( m_iTargetVolume > iVolume ) + { + iVolume = m_iTargetVolume; + } + else if ( iVolume > m_iTargetVolume ) + { + iVolume -= static_cast(250 * gpGlobals->frametime); + + if ( iVolume < m_iTargetVolume ) + { + iVolume = 0; + } + } + + if ( m_fNoPlayerSound ) + { + // debugging flag, lets players move around and shoot without monsters hearing. + iVolume = 0; + } + + if ( gpGlobals->time > m_flStopExtraSoundTime ) + { + // since the extra sound that a weapon emits only lasts for one client frame, we keep that sound around for a server frame or two + // after actual emission to make sure it gets heard. + m_iExtraSoundTypes = 0; + } + + if ( pSound ) + { + pSound->m_vecOrigin = pev->origin; + pSound->m_iType |= ( bits_SOUND_PLAYER | m_iExtraSoundTypes ); + pSound->m_iVolume = iVolume; + } + + // keep track of virtual muzzle flash + m_iWeaponFlash -= static_cast(256 * gpGlobals->frametime); + if (m_iWeaponFlash < 0) + m_iWeaponFlash = 0; + + //UTIL_MakeVectors ( pev->angles ); + //gpGlobals->v_forward.z = 0; + + // Below are a couple of useful little bits that make it easier to determine just how much noise the + // player is making. + // UTIL_ParticleEffect ( pev->origin + gpGlobals->v_forward * iVolume, g_vecZero, 255, 25 ); + //ALERT ( at_console, "%d/%d\n", iVolume, m_iTargetVolume ); +} + + +void CBasePlayer::PostThink() +{ + if ( g_fGameOver ) + goto pt_end; // intermission or finale + + if (!IsAlive()) + goto pt_end; + + // Handle Tank controlling + if ( m_pTank != 0 ) + { // if they've moved too far from the gun, or selected a weapon, unuse the gun + if ( m_pTank->OnControls( pev ) && !pev->weaponmodel ) + { + m_pTank->Use( this, this, USE_SET, 2 ); // try fire the gun + } + else + { // they've moved off the platform + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + } + } + +// do weapon stuff + ItemPostFrame( ); + +// check to see if player landed hard enough to make a sound +// falling farther than half of the maximum safe distance, but not as far a max safe distance will +// play a bootscrape sound, and no damage will be inflicted. Fallling a distance shorter than half +// of maximum safe distance will make no sound. Falling farther than max safe distance will play a +// fallpain sound, and damage will be inflicted based on how far the player fell + + if ( (FBitSet(pev->flags, FL_ONGROUND)) && (pev->health > 0) && m_flFallVelocity >= PLAYER_FALL_PUNCH_THRESHHOLD ) + { + // ALERT ( at_console, "%f\n", m_flFallVelocity ); + + if (pev->watertype == CONTENT_WATER) + { + // Did he hit the world or a non-moving entity? + // BUG - this happens all the time in water, especially when + // BUG - water has current force + // if ( !pev->groundentity || VARS(pev->groundentity)->velocity.z == 0 ) + // EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM); + } + else if ( m_flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED ) + {// after this point, we start doing damage + + float flFallDamage = g_pGameRules->FlPlayerFallDamage( this ); + + if ( flFallDamage > pev->health ) + {//splat + // note: play on item channel because we play footstep landing on body channel + EMIT_SOUND(ENT(pev), CHAN_ITEM, "common/bodysplat.wav", 1, ATTN_NORM); + } + + if ( flFallDamage > 0 ) + { + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), flFallDamage, DMG_FALL ); + pev->punchangle.x = 0; + } + } + + if ( IsAlive() ) + { + SetAnimation( PLAYER_WALK ); + } + } + + if (FBitSet(pev->flags, FL_ONGROUND)) + { + if (m_flFallVelocity > 64 && !g_pGameRules->IsMultiplayer()) + { + CSoundEnt::InsertSound ( bits_SOUND_PLAYER, pev->origin, static_cast(m_flFallVelocity), 0.2 ); + // ALERT( at_console, "fall %f\n", m_flFallVelocity ); + } + m_flFallVelocity = 0; + } + + // select the proper animation for the player character + if ( IsAlive() ) + { + if (!pev->velocity.x && !pev->velocity.y) + SetAnimation( PLAYER_IDLE ); + else if ((pev->velocity.x || pev->velocity.y) && (FBitSet(pev->flags, FL_ONGROUND))) + SetAnimation( PLAYER_WALK ); + else if (pev->waterlevel > 1) + SetAnimation( PLAYER_WALK ); + } + + StudioFrameAdvance( ); + CheckPowerups(pev); + + UpdatePlayerSound(); + + // Track button info so we can detect 'pressed' and 'released' buttons next frame + m_afButtonLast = pev->button; + +pt_end: +#if defined( CLIENT_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++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + CBasePlayerItem *pPlayerItem = m_rgpPlayerItems[ i ]; + + while ( pPlayerItem ) + { + CBasePlayerWeapon *gun; + + gun = (CBasePlayerWeapon *)pPlayerItem->GetWeaponPtr(); + + if ( gun && gun->UseDecrement() ) + { + gun->m_flNextPrimaryAttack = max( gun->m_flNextPrimaryAttack - gpGlobals->frametime, -1.0 ); + gun->m_flNextSecondaryAttack = max( gun->m_flNextSecondaryAttack - gpGlobals->frametime, -0.001 ); + + if ( gun->m_flTimeWeaponIdle != 1000 ) + { + gun->m_flTimeWeaponIdle = max( gun->m_flTimeWeaponIdle - gpGlobals->frametime, -0.001 ); + } + + if ( gun->pev->fuser1 != 1000 ) + { + gun->pev->fuser1 = max( gun->pev->fuser1 - gpGlobals->frametime, -0.001 ); + } + + // Only decrement if not flagged as NO_DECREMENT +// if ( gun->m_flPumpTime != 1000 ) + // { + // gun->m_flPumpTime = max( gun->m_flPumpTime - gpGlobals->frametime, -0.001 ); + // } + + } + + pPlayerItem = pPlayerItem->m_pNext; + } + } + } + + 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 +} + + +// checks if the spot is clear of players +BOOL IsSpawnPointValid( CBaseEntity *pPlayer, CBaseEntity *pSpot ) +{ + CBaseEntity *ent = NULL; + + if ( !pSpot->IsTriggered( pPlayer ) ) + { + return FALSE; + } + + while ( (ent = UTIL_FindEntityInSphere( ent, pSpot->pev->origin, 128 )) != NULL ) + { + // if ent is a client, don't spawn on 'em + if ( ent->IsPlayer() && ent != pPlayer ) + return FALSE; + } + + return TRUE; +} + + +DLL_GLOBAL CBaseEntity *g_pLastSpawn; +inline int FNullEnt( CBaseEntity *ent ) { return (ent == NULL) || FNullEnt( ent->edict() ); } + +/* +============ +EntSelectSpawnPoint + +Returns the entity to spawn at + +USES AND SETS GLOBAL g_pLastSpawn +============ +*/ +edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ) +{ + CBaseEntity *pSpot; + edict_t *player; + + player = pPlayer->edict(); + +// choose a info_player_deathmatch point + if (g_pGameRules->IsCoOp()) + { + pSpot = UTIL_FindEntityByClassname( g_pLastSpawn, "info_player_coop"); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + pSpot = UTIL_FindEntityByClassname( g_pLastSpawn, "info_player_start"); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + } + else if ( g_pGameRules->IsDeathmatch() ) + { + pSpot = g_pLastSpawn; + // Randomize the start spot + for ( int i = RANDOM_LONG(1,5); i > 0; i-- ) + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + if ( FNullEnt( pSpot ) ) // skip over the null point + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + + CBaseEntity *pFirstSpot = pSpot; + + do + { + if ( pSpot ) + { + // check if pSpot is valid + if ( IsSpawnPointValid( pPlayer, pSpot ) ) + { + if ( pSpot->pev->origin == Vector( 0, 0, 0 ) ) + { + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + continue; + } + + // if so, go to pSpot + goto ReturnSpot; + } + } + // increment pSpot + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + } while ( pSpot != pFirstSpot ); // loop if we're not back to the start + + // we haven't found a place to spawn yet, so kill any guy at the first spawn point and spawn there + if ( !FNullEnt( pSpot ) ) + { + CBaseEntity *ent = NULL; + while ( (ent = UTIL_FindEntityInSphere( ent, pSpot->pev->origin, 128 )) != NULL ) + { + // if ent is a client, kill em (unless they are ourselves) + if ( ent->IsPlayer() && !(ent->edict() == player) ) + ent->TakeDamage( VARS(INDEXENT(0)), VARS(INDEXENT(0)), 300, DMG_GENERIC ); + } + goto ReturnSpot; + } + } + + // If startspot is set, (re)spawn there. + if ( FStringNull( gpGlobals->startspot ) || !strlen(STRING(gpGlobals->startspot))) + { + pSpot = UTIL_FindEntityByClassname(NULL, "info_player_start"); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + } + else + { + pSpot = UTIL_FindEntityByTargetname( NULL, STRING(gpGlobals->startspot) ); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + } + +ReturnSpot: + if ( FNullEnt( pSpot ) ) + { + ALERT(at_error, "PutClientInServer: no info_player_start on level"); + return INDEXENT(0); + } + + g_pLastSpawn = pSpot; + return pSpot->edict(); +} + +void CBasePlayer::Spawn( void ) +{ + pev->classname = MAKE_STRING("player"); + pev->health = 100; + pev->armorvalue = 0; + pev->takedamage = DAMAGE_AIM; + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_WALK; + pev->max_health = pev->health; + pev->flags &= FL_PROXY; // keep proxy flag sey by engine + pev->flags |= FL_CLIENT; + pev->air_finished = gpGlobals->time + 12; + pev->dmg = 2; // initial water damage + pev->effects = 0; + pev->deadflag = DEAD_NO; + pev->dmg_take = 0; + pev->dmg_save = 0; + pev->friction = 1.0; + pev->gravity = 1.0; + m_bitsHUDDamage = -1; + m_bitsDamageType = 0; + m_afPhysicsFlags = 0; + m_fLongJump = FALSE;// no longjump module. + + g_engfuncs.pfnSetPhysicsKeyValue( edict(), "slj", "0" ); + g_engfuncs.pfnSetPhysicsKeyValue( edict(), "hl", "1" ); + + pev->fov = m_iFOV = 0;// init field of view. + m_iClientFOV = -1; // make sure fov reset is sent + + m_flNextDecalTime = 0;// let this player decal as soon as he spawns. + + m_flgeigerDelay = gpGlobals->time + 2.0; // wait a few seconds until user-defined message registrations + // are recieved by all clients + + m_flTimeStepSound = 0; + m_iStepLeft = 0; + m_flFieldOfView = 0.5;// some monsters use this to determine whether or not the player is looking at them. + + m_bloodColor = BLOOD_COLOR_RED; + m_flNextAttack = UTIL_WeaponTimeBase(); + StartSneaking(); + + m_iFlashBattery = 99; + m_flFlashLightTime = 1; // force first message + +// dont let uninitialized value here hurt the player + m_flFallVelocity = 0; + + g_pGameRules->SetDefaultPlayerTeam( this ); + g_pGameRules->GetPlayerSpawnSpot( this ); + + SET_MODEL(ENT(pev), "models/player.mdl"); + g_ulModelIndexPlayer = pev->modelindex; + pev->sequence = LookupActivity( ACT_IDLE ); + + if ( FBitSet(pev->flags, FL_DUCKING) ) + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + else + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + + pev->view_ofs = VEC_VIEW; + Precache(); + m_HackedGunPos = Vector( 0, 32, 0 ); + + if ( m_iPlayerSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_console, "Couldn't alloc player sound slot!\n" ); + } + + m_fNoPlayerSound = FALSE;// normal sound behavior. + + m_pLastItem = NULL; + m_fInitHUD = TRUE; + m_iClientHideHUD = -1; // force this to be recalculated + m_fWeapon = FALSE; + m_pClientActiveItem = NULL; + m_iClientBattery = -1; + + // reset all ammo values to 0 + for ( int i = 0; i < MAX_AMMO_SLOTS; i++ ) + { + m_rgAmmo[i] = 0; + m_rgAmmoLast[i] = 0; // client ammo values also have to be reset (the death hud clear messages does on the client side) + } + + m_lastx = m_lasty = 0; + + m_flNextChatTime = gpGlobals->time; + + g_pGameRules->PlayerSpawn( this ); +} + + +void CBasePlayer :: Precache( void ) +{ + // in the event that the player JUST spawned, and the level node graph + // was loaded, fix all of the node graph pointers before the game starts. + + // !!!BUGBUG - now that we have multiplayer, this needs to be moved! + if ( WorldGraph.m_fGraphPresent && !WorldGraph.m_fGraphPointersSet ) + { + if ( !WorldGraph.FSetGraphPointers() ) + { + ALERT ( at_console, "**Graph pointers were not set!\n"); + } + else + { + ALERT ( at_console, "**Graph Pointers Set!\n" ); + } + } + + // SOUNDS / MODELS ARE PRECACHED in ClientPrecache() (game specific) + // because they need to precache before any clients have connected + + // init geiger counter vars during spawn and each time + // we cross a level transition + + m_flgeigerRange = 1000; + m_igeigerRangePrev = 1000; + + m_bitsDamageType = 0; + m_bitsHUDDamage = -1; + + m_iClientBattery = -1; + + m_iTrain = TRAIN_NEW; + + // Make sure any necessary user messages have been registered + LinkUserMessages(); + + m_iUpdateTime = 5; // won't update for 1/2 a second + + if ( gInitHUD ) + m_fInitHUD = TRUE; +} + + +int CBasePlayer::Save( CSave &save ) +{ + if ( !CBaseMonster::Save(save) ) + return 0; + + return save.WriteFields( "PLAYER", this, m_playerSaveData, ARRAYSIZE(m_playerSaveData) ); +} + + +// +// Marks everything as new so the player will resend this to the hud. +// +void CBasePlayer::RenewItems(void) +{ + +} + + +int CBasePlayer::Restore( CRestore &restore ) +{ + if ( !CBaseMonster::Restore(restore) ) + return 0; + + int status = restore.ReadFields( "PLAYER", this, m_playerSaveData, ARRAYSIZE(m_playerSaveData) ); + + SAVERESTOREDATA *pSaveData = (SAVERESTOREDATA *)gpGlobals->pSaveData; + // landmark isn't present. + if ( !pSaveData->fUseLandmark ) + { + ALERT( at_console, "No Landmark:%s\n", pSaveData->szLandmarkName ); + + // default to normal spawn + edict_t* pentSpawnSpot = EntSelectSpawnPoint( this ); + pev->origin = VARS(pentSpawnSpot)->origin + Vector(0,0,1); + pev->angles = VARS(pentSpawnSpot)->angles; + } + pev->v_angle.z = 0; // Clear out roll + pev->angles = pev->v_angle; + + pev->fixangle = TRUE; // turn this way immediately + +// Copied from spawn() for now + m_bloodColor = BLOOD_COLOR_RED; + + g_ulModelIndexPlayer = pev->modelindex; + + if ( FBitSet(pev->flags, FL_DUCKING) ) + { + // Use the crouch HACK + //FixPlayerCrouchStuck( edict() ); + // Don't need to do this with new player prediction code. + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + } + else + { + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + } + + g_engfuncs.pfnSetPhysicsKeyValue( edict(), "hl", "1" ); + + if ( m_fLongJump ) + { + g_engfuncs.pfnSetPhysicsKeyValue( edict(), "slj", "1" ); + } + else + { + g_engfuncs.pfnSetPhysicsKeyValue( edict(), "slj", "0" ); + } + + RenewItems(); + +#if defined( CLIENT_WEAPONS ) + // HACK: This variable is saved/restored in CBaseMonster as a time variable, but we're using it + // as just a counter. Ideally, this needs its own variable that's saved as a plain float. + // Barring that, we clear it out here instead of using the incorrect restored time value. + m_flNextAttack = UTIL_WeaponTimeBase(); +#endif + + return status; +} + + + +void CBasePlayer::SelectNextItem( int iItem ) +{ + CBasePlayerItem *pItem; + + pItem = m_rgpPlayerItems[ iItem ]; + + if (!pItem) + return; + + if (pItem == m_pActiveItem) + { + // select the next one in the chain + pItem = m_pActiveItem->m_pNext; + if (! pItem) + { + return; + } + + CBasePlayerItem *pLast; + pLast = pItem; + while (pLast->m_pNext) + pLast = pLast->m_pNext; + + // relink chain + pLast->m_pNext = m_pActiveItem; + m_pActiveItem->m_pNext = NULL; + m_rgpPlayerItems[ iItem ] = pItem; + } + + ResetAutoaim( ); + + // FIX, this needs to queue them up and delay + if (m_pActiveItem) + { + m_pActiveItem->Holster( ); + } + + m_pActiveItem = pItem; + + if (m_pActiveItem) + { + m_pActiveItem->Deploy( ); + m_pActiveItem->UpdateItemInfo( ); + } +} + +void CBasePlayer::SelectItem(const char *pstr) +{ + if (!pstr) + return; + + CBasePlayerItem *pItem = NULL; + + for (int i = 0; i < MAX_ITEM_TYPES; i++) + { + if (m_rgpPlayerItems[i]) + { + pItem = m_rgpPlayerItems[i]; + + while (pItem) + { + if (FClassnameIs(pItem->pev, pstr)) + break; + pItem = pItem->m_pNext; + } + } + + if (pItem) + break; + } + + if (!pItem) + return; + + + if (pItem == m_pActiveItem) + return; + + ResetAutoaim( ); + + // FIX, this needs to queue them up and delay + if (m_pActiveItem) + m_pActiveItem->Holster( ); + + m_pLastItem = m_pActiveItem; + m_pActiveItem = pItem; + + if (m_pActiveItem) + { + m_pActiveItem->Deploy( ); + m_pActiveItem->UpdateItemInfo( ); + } +} + + +void CBasePlayer::SelectLastItem(void) +{ + if (!m_pLastItem) + { + return; + } + + if ( m_pActiveItem && !m_pActiveItem->CanHolster() ) + { + return; + } + + ResetAutoaim( ); + + // FIX, this needs to queue them up and delay + if (m_pActiveItem) + m_pActiveItem->Holster( ); + + CBasePlayerItem *pTemp = m_pActiveItem; + m_pActiveItem = m_pLastItem; + m_pLastItem = pTemp; + m_pActiveItem->Deploy( ); + m_pActiveItem->UpdateItemInfo( ); +} + +//============================================== +// HasWeapons - do I have any weapons at all? +//============================================== +BOOL CBasePlayer::HasWeapons( void ) +{ + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + return TRUE; + } + } + + return FALSE; +} + +void CBasePlayer::SelectPrevItem( int iItem ) +{ +} + + +const char *CBasePlayer::TeamID( void ) +{ + if ( pev == NULL ) // Not fully connected yet + return ""; + + // return their team name + return m_szTeamName; +} + + +//============================================== +// !!!UNDONE:ultra temporary SprayCan entity to apply +// decal frame at a time. For PreAlpha CD +//============================================== +class CSprayCan : public CBaseEntity +{ +public: + void Spawn ( entvars_t *pevOwner ); + void Think( void ); + + virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +}; + +void CSprayCan::Spawn ( entvars_t *pevOwner ) +{ + pev->origin = pevOwner->origin + Vector ( 0 , 0 , 32 ); + pev->angles = pevOwner->v_angle; + pev->owner = ENT(pevOwner); + pev->frame = 0; + + pev->nextthink = gpGlobals->time + 0.1; + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/sprayer.wav", 1, ATTN_NORM); +} + +void CSprayCan::Think( void ) +{ + TraceResult tr; + int playernum; + int nFrames; + CBasePlayer *pPlayer; + + pPlayer = (CBasePlayer *)GET_PRIVATE(pev->owner); + + if (pPlayer) + nFrames = pPlayer->GetCustomDecalFrames(); + else + nFrames = -1; + + playernum = ENTINDEX(pev->owner); + + // ALERT(at_console, "Spray by player %i, %i of %i\n", playernum, (int)(pev->frame + 1), nFrames); + + UTIL_MakeVectors(pev->angles); + UTIL_TraceLine ( pev->origin, pev->origin + gpGlobals->v_forward * 128, ignore_monsters, pev->owner, & tr); + + // No customization present. + if (nFrames == -1) + { + UTIL_DecalTrace( &tr, DECAL_LAMBDA6 ); + UTIL_Remove( this ); + } + else + { + UTIL_PlayerDecalTrace( &tr, playernum, static_cast(pev->frame), TRUE ); + // Just painted last custom frame. + if ( pev->frame++ >= (nFrames - 1)) + UTIL_Remove( this ); + } + + pev->nextthink = gpGlobals->time + 0.1; +} + +class CBloodSplat : public CBaseEntity +{ +public: + void Spawn ( entvars_t *pevOwner ); + void Spray ( void ); +}; + +void CBloodSplat::Spawn ( entvars_t *pevOwner ) +{ + pev->origin = pevOwner->origin + Vector ( 0 , 0 , 32 ); + pev->angles = pevOwner->v_angle; + pev->owner = ENT(pevOwner); + + SetThink ( &CBloodSplat::Spray ); + pev->nextthink = gpGlobals->time + 0.1; +} + +void CBloodSplat::Spray ( void ) +{ + TraceResult tr; + + if ( g_Language != LANGUAGE_GERMAN ) + { + UTIL_MakeVectors(pev->angles); + UTIL_TraceLine ( pev->origin, pev->origin + gpGlobals->v_forward * 128, ignore_monsters, pev->owner, & tr); + + UTIL_BloodDecalTrace( &tr, BLOOD_COLOR_RED ); + } + SetThink ( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; +} + +//============================================== + + + +void CBasePlayer::GiveNamedItem( const char *pszName ) +{ + edict_t *pent; + + int istr = MAKE_STRING(pszName); + + pent = CREATE_NAMED_ENTITY(istr); + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in GiveNamedItem!\n" ); + return; + } + VARS( pent )->origin = pev->origin; + pent->v.spawnflags |= SF_NORESPAWN; + + DispatchSpawn( pent ); + DispatchTouch( pent, ENT( pev ) ); +} + + + +CBaseEntity *FindEntityForward( CBaseEntity *pMe ) +{ + TraceResult tr; + + UTIL_MakeVectors(pMe->pev->v_angle); + UTIL_TraceLine(pMe->pev->origin + pMe->pev->view_ofs,pMe->pev->origin + pMe->pev->view_ofs + gpGlobals->v_forward * 8192,dont_ignore_monsters, pMe->edict(), &tr ); + if ( tr.flFraction != 1.0 && !FNullEnt( tr.pHit) ) + { + CBaseEntity *pHit = CBaseEntity::Instance( tr.pHit ); + return pHit; + } + return NULL; +} + + +BOOL CBasePlayer :: FlashlightIsOn( void ) +{ + return FBitSet(pev->effects, EF_DIMLIGHT); +} + + +void CBasePlayer :: FlashlightTurnOn( void ) +{ + if ( !g_pGameRules->FAllowFlashlight() ) + { + return; + } + + if ( (pev->weapons & (1<effects, EF_DIMLIGHT); + MESSAGE_BEGIN( MSG_ONE, gmsgFlashlight, NULL, pev ); + WRITE_BYTE(1); + WRITE_BYTE(m_iFlashBattery); + MESSAGE_END(); + + m_flFlashLightTime = FLASH_DRAIN_TIME + gpGlobals->time; + + } +} + + +void CBasePlayer :: FlashlightTurnOff( void ) +{ + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, SOUND_FLASHLIGHT_OFF, 1.0, ATTN_NORM, 0, PITCH_NORM ); + ClearBits(pev->effects, EF_DIMLIGHT); + MESSAGE_BEGIN( MSG_ONE, gmsgFlashlight, NULL, pev ); + WRITE_BYTE(0); + WRITE_BYTE(m_iFlashBattery); + MESSAGE_END(); + + m_flFlashLightTime = FLASH_CHARGE_TIME + gpGlobals->time; + +} + +/* +=============== +ForceClientDllUpdate + +When recording a demo, we need to have the server tell us the entire client state +so that the client side .dll can behave correctly. +Reset stuff so that the state is transmitted. +=============== +*/ +void CBasePlayer :: ForceClientDllUpdate( void ) +{ + m_iClientHealth = -1; + m_iClientBattery = -1; + m_iTrain |= TRAIN_NEW; // Force new train message. + m_fWeapon = FALSE; // Force weapon send + m_fKnownItem = FALSE; // Force weaponinit messages. + m_fInitHUD = TRUE; // Force HUD gmsgResetHUD message + + // Now force all the necessary messages + // to be sent. + UpdateClientData(); +} + +/* +============ +ImpulseCommands +============ +*/ +extern float g_flWeaponCheat; + +void CBasePlayer::ImpulseCommands( ) +{ + TraceResult tr;// UNDONE: kill me! This is temporary for PreAlpha CDs + + // Handle use events + PlayerUse(); + + int iImpulse = (int)pev->impulse; + 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() ) + { + FlashlightTurnOff(); + } + else + { + FlashlightTurnOn(); + } + break; + + case 201:// paint decal + + if ( gpGlobals->time < m_flNextDecalTime ) + { + // too early! + break; + } + + UTIL_MakeVectors(pev->v_angle); + UTIL_TraceLine ( pev->origin + pev->view_ofs, pev->origin + pev->view_ofs + gpGlobals->v_forward * 128, ignore_monsters, ENT(pev), & tr); + + if ( tr.flFraction != 1.0 ) + {// line hit something, so paint a decal + m_flNextDecalTime = gpGlobals->time + decalfrequency.value; + CSprayCan *pCan = GetClassPtr((CSprayCan *)NULL); + pCan->Spawn( pev ); + } + + break; + + default: + // check all of the cheat impulse commands now + CheatImpulseCommands( iImpulse ); + break; + } + + pev->impulse = 0; +} + +//========================================================= +//========================================================= +void CBasePlayer::CheatImpulseCommands( int iImpulse ) +{ +#if !defined( HLDEMO_BUILD ) + if ( g_flWeaponCheat == 0.0 ) + { + return; + } + + CBaseEntity *pEntity; + TraceResult tr; + + switch ( iImpulse ) + { + case 76: + { + if (!giPrecacheGrunt) + { + giPrecacheGrunt = 1; + ALERT(at_console, "You must now restart to use Grunt-o-matic.\n"); + } + else + { + UTIL_MakeVectors( Vector( 0, pev->v_angle.y, 0 ) ); + Create("monster_human_grunt", pev->origin + gpGlobals->v_forward * 128, pev->angles); + } + break; + } + + + case 101: + gEvilImpulse101 = TRUE; + 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; + + case 102: + // Gibbage!!! + CGib::SpawnRandomGibs( pev, 1, 1 ); + break; + + case 103: + // What the hell are you doing? + pEntity = FindEntityForward( this ); + if ( pEntity ) + { + CBaseMonster *pMonster = pEntity->MyMonsterPointer(); + if ( pMonster ) + pMonster->ReportAIState(); + } + break; + + case 104: + // Dump all of the global state varaibles (and global entity names) + gGlobalState.DumpGlobals(); + break; + + case 105:// player makes no sound for monsters to hear. + { + if ( m_fNoPlayerSound ) + { + ALERT ( at_console, "Player is audible\n" ); + m_fNoPlayerSound = FALSE; + } + else + { + ALERT ( at_console, "Player is silent\n" ); + m_fNoPlayerSound = TRUE; + } + break; + } + + case 106: + // Give me the classname and targetname of this entity. + pEntity = FindEntityForward( this ); + if ( pEntity ) + { + ALERT ( at_console, "Classname: %s", STRING( pEntity->pev->classname ) ); + + if ( !FStringNull ( pEntity->pev->targetname ) ) + { + ALERT ( at_console, " - Targetname: %s\n", STRING( pEntity->pev->targetname ) ); + } + else + { + ALERT ( at_console, " - TargetName: No Targetname\n" ); + } + + ALERT ( at_console, "Model: %s\n", STRING( pEntity->pev->model ) ); + if ( pEntity->pev->globalname ) + ALERT ( at_console, "Globalname: %s\n", STRING( pEntity->pev->globalname ) ); + } + break; + + case 107: + { + TraceResult tr; + + edict_t *pWorld = g_engfuncs.pfnPEntityOfEntIndex( 0 ); + + Vector start = pev->origin + pev->view_ofs; + Vector end = start + gpGlobals->v_forward * 1024; + UTIL_TraceLine( start, end, ignore_monsters, edict(), &tr ); + if ( tr.pHit ) + pWorld = tr.pHit; + const char *pTextureName = TRACE_TEXTURE( pWorld, start, end ); + if ( pTextureName ) + ALERT( at_console, "Texture: %s\n", pTextureName ); + } + break; + case 195:// show shortest paths for entire level to nearest node + { + Create("node_viewer_fly", pev->origin, pev->angles); + } + break; + case 196:// show shortest paths for entire level to nearest node + { + Create("node_viewer_large", pev->origin, pev->angles); + } + break; + case 197:// show shortest paths for entire level to nearest node + { + Create("node_viewer_human", pev->origin, pev->angles); + } + break; + case 199:// show nearest node and all connections + { + ALERT ( at_console, "%d\n", WorldGraph.FindNearestNode ( pev->origin, bits_NODE_GROUP_REALM ) ); + WorldGraph.ShowNodeConnections ( WorldGraph.FindNearestNode ( pev->origin, bits_NODE_GROUP_REALM ) ); + } + break; + case 202:// Random blood splatter + UTIL_MakeVectors(pev->v_angle); + UTIL_TraceLine ( pev->origin + pev->view_ofs, pev->origin + pev->view_ofs + gpGlobals->v_forward * 128, ignore_monsters, ENT(pev), & tr); + + if ( tr.flFraction != 1.0 ) + {// line hit something, so paint a decal + CBloodSplat *pBlood = GetClassPtr((CBloodSplat *)NULL); + pBlood->Spawn( pev ); + } + break; + case 203:// remove creature. + pEntity = FindEntityForward( this ); + if ( pEntity ) + { + if ( pEntity->pev->takedamage ) + pEntity->SetThink(&CBaseEntity::SUB_Remove); + } + break; + } +#endif // HLDEMO_BUILD +} + +// +// Add a weapon to the player (Item == Weapon == Selectable Object) +// +int CBasePlayer::AddPlayerItem( CBasePlayerItem *pItem ) +{ + CBasePlayerItem *pInsert; + + pInsert = m_rgpPlayerItems[pItem->iItemSlot()]; + + while (pInsert) + { + if (FClassnameIs( pInsert->pev, STRING( pItem->pev->classname) )) + { + if (pItem->AddDuplicate( pInsert )) + { + g_pGameRules->PlayerGotWeapon ( this, pItem ); + pItem->CheckRespawn(); + + // ugly hack to update clip w/o an update clip message + pInsert->UpdateItemInfo( ); + if (m_pActiveItem) + m_pActiveItem->UpdateItemInfo( ); + + pItem->Kill( ); + } + else if (gEvilImpulse101) + { + // FIXME: remove anyway for deathmatch testing + pItem->Kill( ); + } + return FALSE; + } + pInsert = pInsert->m_pNext; + } + + + if (pItem->AddToPlayer( this )) + { + g_pGameRules->PlayerGotWeapon ( this, pItem ); + pItem->CheckRespawn(); + + pItem->m_pNext = m_rgpPlayerItems[pItem->iItemSlot()]; + m_rgpPlayerItems[pItem->iItemSlot()] = pItem; + + // should we switch to this item? + if ( g_pGameRules->FShouldSwitchWeapon( this, pItem ) ) + { + SwitchWeapon( pItem ); + } + + return TRUE; + } + else if (gEvilImpulse101) + { + // FIXME: remove anyway for deathmatch testing + pItem->Kill( ); + } + return FALSE; +} + + + +int CBasePlayer::RemovePlayerItem( CBasePlayerItem *pItem ) +{ + if (m_pActiveItem == pItem) + { + ResetAutoaim( ); + pItem->Holster( ); + pItem->pev->nextthink = 0;// crowbar may be trying to swing again, etc. + pItem->SetThink( NULL ); + m_pActiveItem = NULL; + pev->viewmodel = 0; + pev->weaponmodel = 0; + } + else if ( m_pLastItem == pItem ) + m_pLastItem = NULL; + + CBasePlayerItem *pPrev = m_rgpPlayerItems[pItem->iItemSlot()]; + + if (pPrev == pItem) + { + m_rgpPlayerItems[pItem->iItemSlot()] = pItem->m_pNext; + return TRUE; + } + else + { + while (pPrev && pPrev->m_pNext != pItem) + { + pPrev = pPrev->m_pNext; + } + if (pPrev) + { + pPrev->m_pNext = pItem->m_pNext; + return TRUE; + } + } + return FALSE; +} + + +// +// Returns the unique ID for the ammo, or -1 if error +// +int CBasePlayer :: GiveAmmo( int iCount, char *szName, int iMax ) +{ + if ( !szName ) + { + // no ammo. + return -1; + } + + if ( !g_pGameRules->CanHaveAmmo( this, szName, iMax ) ) + { + // game rules say I can't have any more of this ammo type. + return -1; + } + + int i = 0; + + i = GetAmmoIndex( szName ); + + if ( i < 0 || i >= MAX_AMMO_SLOTS ) + return -1; + + int iAdd = min( iCount, iMax - m_rgAmmo[i] ); + if ( iAdd < 1 ) + return i; + + m_rgAmmo[ i ] += iAdd; + + + if ( gmsgAmmoPickup ) // make sure the ammo messages have been linked first + { + // Send the message that ammo has been picked up + MESSAGE_BEGIN( MSG_ONE, gmsgAmmoPickup, NULL, pev ); + WRITE_BYTE( GetAmmoIndex(szName) ); // ammo ID + WRITE_BYTE( iAdd ); // amount + MESSAGE_END(); + } + + TabulateAmmo(); + + return i; +} + + +/* +============ +ItemPreFrame + +Called every frame by the player PreThink +============ +*/ +void CBasePlayer::ItemPreFrame() +{ +#if defined( CLIENT_WEAPONS ) + if ( m_flNextAttack > 0 ) +#else + if ( gpGlobals->time < m_flNextAttack ) +#endif + { + return; + } + + if (!m_pActiveItem) + return; + + m_pActiveItem->ItemPreFrame( ); +} + + +/* +============ +ItemPostFrame + +Called every frame by the player PostThink +============ +*/ +void CBasePlayer::ItemPostFrame() +{ + // check if the player is using a tank + if ( m_pTank != 0 ) + return; + +#if defined( CLIENT_WEAPONS ) + if ( m_flNextAttack > 0 ) +#else + if ( gpGlobals->time < m_flNextAttack ) +#endif + { + return; + } + + ImpulseCommands(); + + if (!m_pActiveItem) + return; + + m_pActiveItem->ItemPostFrame( ); +} + +int CBasePlayer::AmmoInventory( int iAmmoIndex ) +{ + if (iAmmoIndex == -1) + { + return -1; + } + + return m_rgAmmo[ iAmmoIndex ]; +} + +int CBasePlayer::GetAmmoIndex(const char *psz) +{ + int i; + + if (!psz) + return -1; + + for (i = 1; i < MAX_AMMO_SLOTS; i++) + { + if ( !CBasePlayerItem::AmmoInfoArray[i].pszName ) + continue; + + if (stricmp( psz, CBasePlayerItem::AmmoInfoArray[i].pszName ) == 0) + return i; + } + + return -1; +} + +// Called from UpdateClientData +// makes sure the client has all the necessary ammo info, if values have changed +void CBasePlayer::SendAmmoUpdate(void) +{ + for (int i=0; i < MAX_AMMO_SLOTS;i++) + { + if (m_rgAmmo[i] != m_rgAmmoLast[i]) + { + m_rgAmmoLast[i] = m_rgAmmo[i]; + + ASSERT( m_rgAmmo[i] >= 0 ); + ASSERT( m_rgAmmo[i] < 255 ); + + // send "Ammo" update message + MESSAGE_BEGIN( MSG_ONE, gmsgAmmoX, NULL, pev ); + WRITE_BYTE( i ); + WRITE_BYTE( max( min( m_rgAmmo[i], 254 ), 0 ) ); // clamp the value to one byte + MESSAGE_END(); + } + } +} + +/* +========================================================= + UpdateClientData + +resends any changed player HUD info to the client. +Called every frame by PlayerPreThink +Also called at start of demo recording and playback by +ForceClientDllUpdate to ensure the demo gets messages +reflecting all of the HUD state info. +========================================================= +*/ +void CBasePlayer :: UpdateClientData( void ) +{ + if (m_fInitHUD) + { + m_fInitHUD = FALSE; + gInitHUD = FALSE; + + MESSAGE_BEGIN( MSG_ONE, gmsgResetHUD, NULL, pev ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + + if ( !m_fGameHUDInitialized ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgInitHUD, NULL, pev ); + MESSAGE_END(); + + g_pGameRules->InitHUD( this ); + m_fGameHUDInitialized = TRUE; + if ( g_pGameRules->IsMultiplayer() ) + { + FireTargets( "game_playerjoin", this, this, USE_TOGGLE, 0 ); + } + } + + FireTargets( "game_playerspawn", this, this, USE_TOGGLE, 0 ); + + InitStatusBar(); + } + + if ( m_iHideHUD != m_iClientHideHUD ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgHideWeapon, NULL, pev ); + WRITE_BYTE( m_iHideHUD ); + MESSAGE_END(); + + m_iClientHideHUD = m_iHideHUD; + } + + if ( m_iFOV != m_iClientFOV ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgSetFOV, NULL, pev ); + WRITE_BYTE( m_iFOV ); + MESSAGE_END(); + + // cache FOV change at end of function, so weapon updates can see that FOV has changed + } + + // HACKHACK -- send the message to display the game title + if (gDisplayTitle) + { + MESSAGE_BEGIN( MSG_ONE, gmsgShowGameTitle, NULL, pev ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + gDisplayTitle = 0; + } + + if (pev->health != m_iClientHealth) + { + int iHealth = max( static_cast(pev->health), 0 ); // make sure that no negative health values are sent + + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgHealth, NULL, pev ); + WRITE_BYTE( iHealth ); + MESSAGE_END(); + + m_iClientHealth = static_cast(pev->health); + } + + + if (pev->armorvalue != m_iClientBattery) + { + m_iClientBattery = static_cast(pev->armorvalue); + + ASSERT( gmsgBattery > 0 ); + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgBattery, NULL, pev ); + WRITE_SHORT( (int)pev->armorvalue); + MESSAGE_END(); + } + + if (pev->dmg_take || pev->dmg_save || m_bitsHUDDamage != m_bitsDamageType) + { + // Comes from inside me if not set + Vector damageOrigin = pev->origin; + // send "damage" message + // causes screen to flash, and pain compass to show direction of damage + edict_t *other = pev->dmg_inflictor; + if ( other ) + { + CBaseEntity *pEntity = CBaseEntity::Instance(other); + if ( pEntity ) + damageOrigin = pEntity->Center(); + } + + // only send down damage type that have hud art + int visibleDamageBits = m_bitsDamageType & DMG_SHOWNHUD; + + MESSAGE_BEGIN( MSG_ONE, gmsgDamage, NULL, pev ); + WRITE_BYTE( static_cast(pev->dmg_save) ); + WRITE_BYTE( static_cast(pev->dmg_take) ); + WRITE_LONG( visibleDamageBits ); + WRITE_COORD( damageOrigin.x ); + WRITE_COORD( damageOrigin.y ); + WRITE_COORD( damageOrigin.z ); + MESSAGE_END(); + + pev->dmg_take = 0; + pev->dmg_save = 0; + m_bitsHUDDamage = m_bitsDamageType; + + // Clear off non-time-based damage indicators + m_bitsDamageType &= DMG_TIMEBASED; + } + + // Update Flashlight + if ((m_flFlashLightTime) && (m_flFlashLightTime <= gpGlobals->time)) + { + if (FlashlightIsOn()) + { + if (m_iFlashBattery) + { + m_flFlashLightTime = FLASH_DRAIN_TIME + gpGlobals->time; + m_iFlashBattery--; + + if (!m_iFlashBattery) + FlashlightTurnOff(); + } + } + else + { + if (m_iFlashBattery < 100) + { + m_flFlashLightTime = FLASH_CHARGE_TIME + gpGlobals->time; + m_iFlashBattery++; + } + else + m_flFlashLightTime = 0; + } + + MESSAGE_BEGIN( MSG_ONE, gmsgFlashBattery, NULL, pev ); + WRITE_BYTE(m_iFlashBattery); + MESSAGE_END(); + } + + + if (m_iTrain & TRAIN_NEW) + { + ASSERT( gmsgTrain > 0 ); + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgTrain, NULL, pev ); + WRITE_BYTE(m_iTrain & 0xF); + MESSAGE_END(); + + m_iTrain &= ~TRAIN_NEW; + } + + // + // New Weapon? + // + if (!m_fKnownItem) + { + m_fKnownItem = TRUE; + + // WeaponInit Message + // byte = # of weapons + // + // for each weapon: + // byte name str length (not including null) + // bytes... name + // byte Ammo Type + // byte Ammo2 Type + // byte bucket + // byte bucket pos + // byte flags + // ???? Icons + + // Send ALL the weapon info now + int i; + + for (i = 0; i < MAX_WEAPONS; i++) + { + ItemInfo& II = CBasePlayerItem::ItemInfoArray[i]; + + if ( !II.iId ) + continue; + + const char *pszName; + if (!II.pszName) + pszName = "Empty"; + else + pszName = II.pszName; + + MESSAGE_BEGIN( MSG_ONE, gmsgWeaponList, NULL, pev ); + WRITE_STRING(pszName); // string weapon name + WRITE_BYTE(GetAmmoIndex(II.pszAmmo1)); // byte Ammo Type + WRITE_BYTE(II.iMaxAmmo1); // byte Max Ammo 1 + WRITE_BYTE(GetAmmoIndex(II.pszAmmo2)); // byte Ammo2 Type + WRITE_BYTE(II.iMaxAmmo2); // byte Max Ammo 2 + WRITE_BYTE(II.iSlot); // byte bucket + WRITE_BYTE(II.iPosition); // byte bucket pos + WRITE_BYTE(II.iId); // byte id (bit index into pev->weapons) + WRITE_BYTE(II.iFlags); // byte Flags + MESSAGE_END(); + } + } + + + SendAmmoUpdate(); + + // Update all the items + for ( int i = 0; i < MAX_ITEM_TYPES; i++ ) + { + if ( m_rgpPlayerItems[i] ) // each item updates it's successors + m_rgpPlayerItems[i]->UpdateClientData( this ); + } + + // Cache and client weapon change + m_pClientActiveItem = m_pActiveItem; + m_iClientFOV = m_iFOV; + + // Update Status Bar + if ( m_flNextSBarUpdateTime < gpGlobals->time ) + { + UpdateStatusBar(); + m_flNextSBarUpdateTime = gpGlobals->time + 0.2; + } +} + + +//========================================================= +// FBecomeProne - Overridden for the player to set the proper +// physics flags when a barnacle grabs player. +//========================================================= +BOOL CBasePlayer :: FBecomeProne ( void ) +{ + m_afPhysicsFlags |= PFLAG_ONBARNACLE; + return TRUE; +} + +//========================================================= +// BarnacleVictimBitten - bad name for a function that is called +// by Barnacle victims when the barnacle pulls their head +// into its mouth. For the player, just die. +//========================================================= +void CBasePlayer :: BarnacleVictimBitten ( entvars_t *pevBarnacle ) +{ + TakeDamage ( pevBarnacle, pevBarnacle, pev->health + pev->armorvalue, DMG_SLASH | DMG_ALWAYSGIB ); +} + +//========================================================= +// BarnacleVictimReleased - overridden for player who has +// physics flags concerns. +//========================================================= +void CBasePlayer :: BarnacleVictimReleased ( void ) +{ + m_afPhysicsFlags &= ~PFLAG_ONBARNACLE; +} + + +//========================================================= +// Illumination +// return player light level plus virtual muzzle flash +//========================================================= +int CBasePlayer :: Illumination( void ) +{ + int iIllum = CBaseEntity::Illumination( ); + + iIllum += m_iWeaponFlash; + if (iIllum > 255) + return 255; + return iIllum; +} + + +void CBasePlayer :: EnableControl(BOOL fControl) +{ + if (!fControl) + pev->flags |= FL_FROZEN; + else + pev->flags &= ~FL_FROZEN; + +} + + +#define DOT_1DEGREE 0.9998476951564 +#define DOT_2DEGREE 0.9993908270191 +#define DOT_3DEGREE 0.9986295347546 +#define DOT_4DEGREE 0.9975640502598 +#define DOT_5DEGREE 0.9961946980917 +#define DOT_6DEGREE 0.9945218953683 +#define DOT_7DEGREE 0.9925461516413 +#define DOT_8DEGREE 0.9902680687416 +#define DOT_9DEGREE 0.9876883405951 +#define DOT_10DEGREE 0.9848077530122 +#define DOT_15DEGREE 0.9659258262891 +#define DOT_20DEGREE 0.9396926207859 +#define DOT_25DEGREE 0.9063077870367 + +//========================================================= +// Autoaim +// set crosshair position to point to enemey +//========================================================= +Vector CBasePlayer :: GetAutoaimVector( float flDelta ) +{ + if (g_iSkillLevel == SKILL_HARD) + { + UTIL_MakeVectors( pev->v_angle + pev->punchangle ); + return gpGlobals->v_forward; + } + + Vector vecSrc = GetGunPosition( ); + float flDist = 8192; + + // always use non-sticky autoaim + // UNDONE: use sever variable to chose! + if (1 || g_iSkillLevel == SKILL_MEDIUM) + { + m_vecAutoAim = Vector( 0, 0, 0 ); + // flDelta *= 0.5; + } + + BOOL m_fOldTargeting = m_fOnTarget; + Vector angles = AutoaimDeflection(vecSrc, flDist, flDelta ); + + // update ontarget if changed + if ( !g_pGameRules->AllowAutoTargetCrosshair() ) + m_fOnTarget = 0; + else if (m_fOldTargeting != m_fOnTarget) + { + m_pActiveItem->UpdateItemInfo( ); + } + + if (angles.x > 180) + angles.x -= 360; + if (angles.x < -180) + angles.x += 360; + if (angles.y > 180) + angles.y -= 360; + if (angles.y < -180) + angles.y += 360; + + if (angles.x > 25) + angles.x = 25; + if (angles.x < -25) + angles.x = -25; + if (angles.y > 12) + angles.y = 12; + if (angles.y < -12) + angles.y = -12; + + + // always use non-sticky autoaim + // UNDONE: use sever variable to chose! + if (0 || g_iSkillLevel == SKILL_EASY) + { + m_vecAutoAim = m_vecAutoAim * 0.67 + angles * 0.33; + } + else + { + m_vecAutoAim = angles * 0.9; + } + + // m_vecAutoAim = m_vecAutoAim * 0.99; + + // Don't send across network if sv_aim is 0 + if ( 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 = static_cast(m_vecAutoAim.x); + m_lasty = static_cast(m_vecAutoAim.y); + } + } + + // ALERT( at_console, "%f %f\n", angles.x, angles.y ); + + UTIL_MakeVectors( pev->v_angle + pev->punchangle + m_vecAutoAim ); + return gpGlobals->v_forward; +} + + +Vector CBasePlayer :: AutoaimDeflection( Vector &vecSrc, float flDist, float flDelta ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + float bestdot; + Vector bestdir; + edict_t *bestent; + TraceResult tr; + + if ( g_psv_aim->value == 0 ) + { + m_fOnTarget = FALSE; + return g_vecZero; + } + + UTIL_MakeVectors( pev->v_angle + pev->punchangle + m_vecAutoAim ); + + // try all possible entities + bestdir = gpGlobals->v_forward; + bestdot = flDelta; // +- 10 degrees + bestent = NULL; + + m_fOnTarget = FALSE; + + UTIL_TraceLine( vecSrc, vecSrc + bestdir * flDist, dont_ignore_monsters, edict(), &tr ); + + + if ( tr.pHit && tr.pHit->v.takedamage != DAMAGE_NO) + { + // don't look through water + if (!((pev->waterlevel != 3 && tr.pHit->v.waterlevel == 3) + || (pev->waterlevel == 3 && tr.pHit->v.waterlevel == 0))) + { + if (tr.pHit->v.takedamage == DAMAGE_AIM) + m_fOnTarget = TRUE; + + return m_vecAutoAim; + } + } + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + Vector center; + Vector dir; + float dot; + + if ( pEdict->free ) // Not in use + continue; + + if (pEdict->v.takedamage != DAMAGE_AIM) + continue; + if (pEdict == edict()) + continue; +// if (pev->team > 0 && pEdict->v.team == pev->team) +// continue; // don't aim at teammate + if ( !g_pGameRules->ShouldAutoAim( this, pEdict ) ) + continue; + + pEntity = Instance( pEdict ); + if (pEntity == NULL) + continue; + + if (!pEntity->IsAlive()) + continue; + + // don't look through water + if ((pev->waterlevel != 3 && pEntity->pev->waterlevel == 3) + || (pev->waterlevel == 3 && pEntity->pev->waterlevel == 0)) + continue; + + center = pEntity->BodyTarget( vecSrc ); + + dir = (center - vecSrc).Normalize( ); + + // make sure it's in front of the player + if (DotProduct (dir, gpGlobals->v_forward ) < 0) + continue; + + dot = fabs( DotProduct (dir, gpGlobals->v_right ) ) + + fabs( DotProduct (dir, gpGlobals->v_up ) ) * 0.5; + + // tweek for distance + dot *= 1.0 + 0.2 * ((center - vecSrc).Length() / flDist); + + if (dot > bestdot) + continue; // to far to turn + + UTIL_TraceLine( vecSrc, center, dont_ignore_monsters, edict(), &tr ); + if (tr.flFraction != 1.0 && tr.pHit != pEdict) + { + // ALERT( at_console, "hit %s, can't see %s\n", STRING( tr.pHit->v.classname ), STRING( pEdict->v.classname ) ); + continue; + } + + // don't shoot at friends + if (IRelationship( pEntity ) < 0) + { + if ( !pEntity->IsPlayer() && !g_pGameRules->IsDeathmatch()) + // ALERT( at_console, "friend\n"); + continue; + } + + // can shoot at this one + bestdot = dot; + bestent = pEdict; + bestdir = dir; + } + + if (bestent) + { + bestdir = UTIL_VecToAngles (bestdir); + bestdir.x = -bestdir.x; + bestdir = bestdir - pev->v_angle - pev->punchangle; + + if (bestent->v.takedamage == DAMAGE_AIM) + m_fOnTarget = TRUE; + + return bestdir; + } + + return Vector( 0, 0, 0 ); +} + + +void CBasePlayer :: ResetAutoaim( ) +{ + if (m_vecAutoAim.x != 0 || m_vecAutoAim.y != 0) + { + m_vecAutoAim = Vector( 0, 0, 0 ); + SET_CROSSHAIRANGLE( edict(), 0, 0 ); + } + m_fOnTarget = FALSE; +} + +/* +============= +SetCustomDecalFrames + + UNDONE: Determine real frame limit, 8 is a placeholder. + Note: -1 means no custom frames present. +============= +*/ +void CBasePlayer :: SetCustomDecalFrames( int nFrames ) +{ + if (nFrames > 0 && + nFrames < 8) + m_nCustomSprayFrames = nFrames; + else + m_nCustomSprayFrames = -1; +} + +/* +============= +GetCustomDecalFrames + + Returns the # of custom frames this player's custom clan logo contains. +============= +*/ +int CBasePlayer :: GetCustomDecalFrames( void ) +{ + return m_nCustomSprayFrames; +} + + +//========================================================= +// DropPlayerItem - drop the named item, or if no name, +// the active item. +//========================================================= +void CBasePlayer::DropPlayerItem ( char *pszItemName ) +{ + if ( !g_pGameRules->IsMultiplayer() || (weaponstay.value > 0) ) + { + // no dropping in single player. + return; + } + + if ( !strlen( pszItemName ) ) + { + // if this string has no length, the client didn't type a name! + // assume player wants to drop the active item. + // make the string null to make future operations in this function easier + pszItemName = NULL; + } + + CBasePlayerItem *pWeapon; + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + pWeapon = m_rgpPlayerItems[ i ]; + + while ( pWeapon ) + { + if ( pszItemName ) + { + // try to match by name. + if ( !strcmp( pszItemName, STRING( pWeapon->pev->classname ) ) ) + { + // match! + break; + } + } + else + { + // trying to drop active item + if ( pWeapon == m_pActiveItem ) + { + // active item! + break; + } + } + + pWeapon = pWeapon->m_pNext; + } + + + // if we land here with a valid pWeapon pointer, that's because we found the + // item we want to drop and hit a BREAK; pWeapon is the item. + if ( pWeapon ) + { + g_pGameRules->GetNextBestWeapon( this, pWeapon ); + + UTIL_MakeVectors ( pev->angles ); + + pev->weapons &= ~(1<m_iId);// take item off hud + + CWeaponBox *pWeaponBox = (CWeaponBox *)CBaseEntity::Create( "weaponbox", pev->origin + gpGlobals->v_forward * 10, pev->angles, edict() ); + pWeaponBox->pev->angles.x = 0; + pWeaponBox->pev->angles.z = 0; + pWeaponBox->PackWeapon( pWeapon ); + pWeaponBox->pev->velocity = gpGlobals->v_forward * 300 + gpGlobals->v_forward * 100; + + // drop half of the ammo for this weapon. + int iAmmoIndex; + + iAmmoIndex = GetAmmoIndex ( pWeapon->pszAmmo1() ); // ??? + + if ( iAmmoIndex != -1 ) + { + // this weapon weapon uses ammo, so pack an appropriate amount. + if ( pWeapon->iFlags() & ITEM_FLAG_EXHAUSTIBLE ) + { + // pack up all the ammo, this weapon is its own ammo type + pWeaponBox->PackAmmo( MAKE_STRING(pWeapon->pszAmmo1()), m_rgAmmo[ iAmmoIndex ] ); + m_rgAmmo[ iAmmoIndex ] = 0; + + } + else + { + // pack half of the ammo + pWeaponBox->PackAmmo( MAKE_STRING(pWeapon->pszAmmo1()), m_rgAmmo[ iAmmoIndex ] / 2 ); + m_rgAmmo[ iAmmoIndex ] /= 2; + } + + } + + return;// we're done, so stop searching with the FOR loop. + } + } +} + +//========================================================= +// HasPlayerItem Does the player already have this item? +//========================================================= +BOOL CBasePlayer::HasPlayerItem( CBasePlayerItem *pCheckItem ) +{ + CBasePlayerItem *pItem = m_rgpPlayerItems[pCheckItem->iItemSlot()]; + + while (pItem) + { + if (FClassnameIs( pItem->pev, STRING( pCheckItem->pev->classname) )) + { + return TRUE; + } + pItem = pItem->m_pNext; + } + + return FALSE; +} + +//========================================================= +// HasNamedPlayerItem Does the player already have this item? +//========================================================= +BOOL CBasePlayer::HasNamedPlayerItem( const char *pszItemName ) +{ + CBasePlayerItem *pItem; + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + pItem = m_rgpPlayerItems[ i ]; + + while (pItem) + { + if ( !strcmp( pszItemName, STRING( pItem->pev->classname ) ) ) + { + return TRUE; + } + pItem = pItem->m_pNext; + } + } + + return FALSE; +} + +//========================================================= +// +//========================================================= +BOOL CBasePlayer :: SwitchWeapon( CBasePlayerItem *pWeapon ) +{ + if ( !pWeapon->CanDeploy() ) + { + return FALSE; + } + + ResetAutoaim( ); + + if (m_pActiveItem) + { + m_pActiveItem->Holster( ); + } + + m_pActiveItem = pWeapon; + pWeapon->Deploy( ); + + return TRUE; +} + +//========================================================= +// Dead HEV suit prop +//========================================================= +class CDeadHEV : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_HUMAN_MILITARY; } + + void KeyValue( KeyValueData *pkvd ); + + int m_iPose;// which sequence to display -- temporary, don't need to save + static char *m_szPoses[4]; +}; + +char *CDeadHEV::m_szPoses[] = { "deadback", "deadsitting", "deadstomach", "deadtable" }; + +void CDeadHEV::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "pose")) + { + m_iPose = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +LINK_ENTITY_TO_CLASS( monster_hevsuit_dead, CDeadHEV ); + +//========================================================= +// ********** DeadHEV SPAWN ********** +//========================================================= +void CDeadHEV :: Spawn( void ) +{ + PRECACHE_MODEL("models/player.mdl"); + SET_MODEL(ENT(pev), "models/player.mdl"); + + pev->effects = 0; + pev->yaw_speed = 8; + pev->sequence = 0; + pev->body = 1; + m_bloodColor = BLOOD_COLOR_RED; + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + + if (pev->sequence == -1) + { + ALERT ( at_console, "Dead hevsuit with bad pose\n" ); + pev->sequence = 0; + pev->effects = EF_BRIGHTFIELD; + } + + // Corpses have less health + pev->health = 8; + + MonsterInitDead(); +} + + +class CStripWeapons : public CPointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +private: +}; + +LINK_ENTITY_TO_CLASS( player_weaponstrip, CStripWeapons ); + +void CStripWeapons :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBasePlayer *pPlayer = NULL; + + if ( pActivator && pActivator->IsPlayer() ) + { + pPlayer = (CBasePlayer *)pActivator; + } + else if ( !g_pGameRules->IsDeathmatch() ) + { + pPlayer = (CBasePlayer *)CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) ); + } + + if ( pPlayer ) + pPlayer->RemoveAllItems( FALSE ); +} + + +class CRevertSaved : public CPointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT MessageThink( void ); + void EXPORT LoadThink( void ); + void KeyValue( KeyValueData *pkvd ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + inline float Duration( void ) { return pev->dmg_take; } + inline float HoldTime( void ) { return pev->dmg_save; } + inline float MessageTime( void ) { return m_messageTime; } + inline float LoadTime( void ) { return m_loadTime; } + + inline void SetDuration( float duration ) { pev->dmg_take = duration; } + inline void SetHoldTime( float hold ) { pev->dmg_save = hold; } + inline void SetMessageTime( float time ) { m_messageTime = time; } + inline void SetLoadTime( float time ) { m_loadTime = time; } + +private: + float m_messageTime; + float m_loadTime; +}; + +LINK_ENTITY_TO_CLASS( player_loadsaved, CRevertSaved ); + +TYPEDESCRIPTION CRevertSaved::m_SaveData[] = +{ + DEFINE_FIELD( CRevertSaved, m_messageTime, FIELD_FLOAT ), // These are not actual times, but durations, so save as floats + DEFINE_FIELD( CRevertSaved, m_loadTime, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CRevertSaved, CPointEntity ); + +void CRevertSaved :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "duration")) + { + SetDuration( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "holdtime")) + { + SetHoldTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "messagetime")) + { + SetMessageTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "loadtime")) + { + SetLoadTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CRevertSaved :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + UTIL_ScreenFadeAll( pev->rendercolor, Duration(), HoldTime(), static_cast(pev->renderamt), FFADE_OUT ); + pev->nextthink = gpGlobals->time + MessageTime(); + SetThink( &CRevertSaved::MessageThink ); +} + + +void CRevertSaved :: MessageThink( void ) +{ + UTIL_ShowMessageAll( STRING(pev->message) ); + float nextThink = LoadTime() - MessageTime(); + if ( nextThink > 0 ) + { + pev->nextthink = gpGlobals->time + nextThink; + SetThink( &CRevertSaved::LoadThink ); + } + else + LoadThink(); +} + + +void CRevertSaved :: LoadThink( void ) +{ + if ( !gpGlobals->deathmatch ) + { + SERVER_COMMAND("reload\n"); + } +} + + +//========================================================= +// Multiplayer intermission spots. +//========================================================= +class CInfoIntermission:public CPointEntity +{ + void Spawn( void ); + void Think( void ); +}; + +void CInfoIntermission::Spawn( void ) +{ + UTIL_SetOrigin( pev, pev->origin ); + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; + pev->v_angle = g_vecZero; + + pev->nextthink = gpGlobals->time + 2;// let targets spawn! + +} + +void CInfoIntermission::Think ( void ) +{ + edict_t *pTarget; + + // find my target + pTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) ); + + if ( !FNullEnt(pTarget) ) + { + pev->v_angle = UTIL_VecToAngles( (pTarget->v.origin - pev->origin).Normalize() ); + pev->v_angle.x = -pev->v_angle.x; + } +} + +LINK_ENTITY_TO_CLASS( info_intermission, CInfoIntermission ); + diff --git a/hlsdk/dlls/player.h b/hlsdk/dlls/player.h new file mode 100644 index 00000000..a11d0754 --- /dev/null +++ b/hlsdk/dlls/player.h @@ -0,0 +1,324 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PLAYER_H +#define PLAYER_H + + +#include "pm_materials.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_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. + +// +// Player PHYSICS FLAGS bits +// +#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. + +// +// 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" + +#define TEAM_NAME_LENGTH 16 + +typedef enum +{ + PLAYER_IDLE, + PLAYER_WALK, + PLAYER_JUMP, + PLAYER_SUPERJUMP, + PLAYER_DIE, + PLAYER_ATTACK1, +} PLAYER_ANIM; + +#define MAX_ID_RANGE 2048 +#define SBAR_STRING_SIZE 128 + +enum sbar_data +{ + SBAR_ID_TARGETNAME = 1, + SBAR_ID_TARGETHEALTH, + SBAR_ID_TARGETARMOR, + SBAR_END, +}; + +#define CHAT_INTERVAL 1.0f + +class CBasePlayer : public CBaseMonster +{ +public: + int random_seed; // See that is shared between client & server for shared weapons code + + int m_iPlayerSound;// the index of the sound list slot reserved for this player + int m_iTargetVolume;// ideal sound volume. + int m_iWeaponVolume;// how loud the player's weapon is right now. + int m_iExtraSoundTypes;// additional classification for this weapon's sound + int m_iWeaponFlash;// brightness of the weapon flash + float m_flStopExtraSoundTime; + + float m_flFlashLightTime; // Time until next battery draw/Recharge + int m_iFlashBattery; // Flashlight Battery Draw + + int m_afButtonLast; + int m_afButtonPressed; + int m_afButtonReleased; + + edict_t *m_pentSndLast; // last sound entity to modify player room type + float m_flSndRoomtype; // last roomtype set by sound entity + float m_flSndRange; // dist from player to sound entity + + float m_flFallVelocity; + + int m_rgItems[MAX_ITEMS]; + int m_fKnownItem; // True when a new item needs to be added + int m_fNewAmmo; // True when a new item has been added + + 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 + + +// these are time-sensitive things that we keep track of + float m_flTimeStepSound; // when the last stepping sound was made + float m_flTimeWeaponIdle; // when to play another weapon idle animation. + float m_flSwimTime; // how long player has been underwater + 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 + + int m_idrowndmg; // track drowning damage taken + int m_idrownrestored; // track drowning damage restored + + int m_bitsHUDDamage; // Damage bits for the current fame. These get sent to + // the hude via the DAMAGE message + BOOL m_fInitHUD; // True when deferred HUD restart msg needs to be sent + BOOL m_fGameHUDInitialized; + int m_iTrain; // Train control position + BOOL m_fWeapon; // Set this to FALSE to force a reset of the current weapon HUD info + + EHANDLE m_pTank; // the tank which the player is currently controlling, NULL if no tank + float m_fDeadTime; // the time at which the player died (used in PlayerDeathThink()) + + BOOL m_fNoPlayerSound; // a debugging feature. Player makes no sound if this is true. + BOOL m_fLongJump; // does this player have the longjump module? + + 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 + int m_iClientFOV; // client's known FOV + // usable player items + CBasePlayerItem *m_rgpPlayerItems[MAX_ITEM_TYPES]; + CBasePlayerItem *m_pActiveItem; + CBasePlayerItem *m_pClientActiveItem; // client version of the active item + CBasePlayerItem *m_pLastItem; + // shared ammo slots + int m_rgAmmo[MAX_AMMO_SLOTS]; + int m_rgAmmoLast[MAX_AMMO_SLOTS]; + + Vector m_vecAutoAim; + BOOL m_fOnTarget; + int m_iDeaths; + float m_iRespawnFrames; // used in PlayerDeathThink() to make sure players can always respawn + + int m_lastx, m_lasty; // These are the previous update's crosshair angles, DON"T SAVE/RESTORE + + int m_nCustomSprayFrames;// Custom clan logo frames for this player + float m_flNextDecalTime;// next time this player can spray a decal + + char m_szTeamName[TEAM_NAME_LENGTH]; + + virtual void Spawn( void ); + void Pain( void ); + +// virtual void Think( void ); + virtual void Jump( void ); + virtual void Duck( void ); + virtual void PreThink( void ); + virtual void PostThink( 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 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 + virtual void StartSneaking( void ) { m_tSneaking = gpGlobals->time - 1; } + 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 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 + // Spectators should return TRUE for this + virtual const char *TeamID( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + void RenewItems(void); + void PackDeadPlayerItems( void ); + void RemoveAllItems( BOOL removeSuit ); + BOOL SwitchWeapon( CBasePlayerItem *pWeapon ); + + // JOHN: sends custom messages if player HUD data has changed (eg health, ammo) + virtual void UpdateClientData( void ); + + static TYPEDESCRIPTION m_playerSaveData[]; + + // Player is moved across the transition by other means + virtual int ObjectCaps( void ) { return CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual void Precache( void ); + BOOL IsOnLadder( void ); + BOOL FlashlightIsOn( void ); + void FlashlightTurnOn( void ); + void FlashlightTurnOff( void ); + + void UpdatePlayerSound ( void ); + void DeathSound ( void ); + + int Classify ( void ); + void SetAnimation( PLAYER_ANIM playerAnim ); + void SetWeaponAnimType( const char *szExtention ); + char m_szAnimExtention[32]; + + // custom player functions + virtual void ImpulseCommands( void ); + void CheatImpulseCommands( int iImpulse ); + + void StartDeathCam( void ); + void StartObserver( Vector vecPosition, Vector vecViewAngle ); + + void AddPoints( int score, BOOL bAllowNegativeScore ); + void AddPointsToTeam( int score, BOOL bAllowNegativeScore ); + BOOL AddPlayerItem( CBasePlayerItem *pItem ); + BOOL RemovePlayerItem( CBasePlayerItem *pItem ); + void DropPlayerItem ( char *pszItemName ); + BOOL HasPlayerItem( CBasePlayerItem *pCheckItem ); + BOOL HasNamedPlayerItem( const char *pszItemName ); + BOOL HasWeapons( void );// do I have ANY weapons? + void SelectPrevItem( int iItem ); + void SelectNextItem( int iItem ); + void SelectLastItem(void); + void SelectItem(const char *pstr); + void ItemPreFrame( void ); + void ItemPostFrame( void ); + void GiveNamedItem( const char *szName ); + void EnableControl(BOOL fControl); + + int GiveAmmo( int iAmount, char *szName, int iMax ); + void SendAmmoUpdate(void); + + void WaterMove( void ); + 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 ); + + void ResetAutoaim( void ); + Vector GetAutoaimVector( float flDelta ); + 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 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? + + //Player ID + void InitStatusBar( void ); + void UpdateStatusBar( void ); + int m_izSBarState[ SBAR_END ]; + float m_flNextSBarUpdateTime; + float m_flStatusBarDisappearDelay; + char m_SbarString0[ SBAR_STRING_SIZE ]; + char m_SbarString1[ SBAR_STRING_SIZE ]; + + float m_flNextChatTime; + +}; + +#define AUTOAIM_2DEGREES 0.0348994967025 +#define AUTOAIM_5DEGREES 0.08715574274766 +#define AUTOAIM_8DEGREES 0.1391731009601 +#define AUTOAIM_10DEGREES 0.1736481776669 + + +extern int gmsgHudText; +extern BOOL gInitHUD; + +#endif // PLAYER_H diff --git a/hlsdk/dlls/python.cpp b/hlsdk/dlls/python.cpp new file mode 100644 index 00000000..fec02d7c --- /dev/null +++ b/hlsdk/dlls/python.cpp @@ -0,0 +1,320 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "monsters.h" +#include "player.h" +#include "gamerules.h" + + +enum python_e { + PYTHON_IDLE1 = 0, + PYTHON_FIDGET, + PYTHON_FIRE1, + PYTHON_RELOAD, + PYTHON_HOLSTER, + PYTHON_DRAW, + PYTHON_IDLE2, + PYTHON_IDLE3 +}; + +LINK_ENTITY_TO_CLASS( weapon_python, CPython ); +LINK_ENTITY_TO_CLASS( weapon_357, CPython ); + +int CPython::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "357"; + p->iMaxAmmo1 = _357_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = PYTHON_MAX_CLIP; + p->iFlags = 0; + p->iSlot = 1; + p->iPosition = 1; + p->iId = m_iId = WEAPON_PYTHON; + p->iWeight = PYTHON_WEIGHT; + + return 1; +} + +int CPython::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + +void CPython::Spawn( ) +{ + pev->classname = MAKE_STRING("weapon_357"); // hack to allow for old names + Precache( ); + m_iId = WEAPON_PYTHON; + SET_MODEL(ENT(pev), "models/w_357.mdl"); + + m_iDefaultAmmo = PYTHON_DEFAULT_GIVE; + + FallInit();// get ready to fall down. +} + + +void CPython::Precache( void ) +{ + PRECACHE_MODEL("models/v_357.mdl"); + PRECACHE_MODEL("models/w_357.mdl"); + PRECACHE_MODEL("models/p_357.mdl"); + + PRECACHE_MODEL("models/w_357ammobox.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + + PRECACHE_SOUND ("weapons/357_reload1.wav"); + PRECACHE_SOUND ("weapons/357_cock1.wav"); + PRECACHE_SOUND ("weapons/357_shot1.wav"); + PRECACHE_SOUND ("weapons/357_shot2.wav"); + + m_usFirePython = PRECACHE_EVENT( 1, "events/python.sc" ); +} + +BOOL CPython::Deploy( ) +{ +#ifdef CLIENT_DLL + if ( bIsMultiplayer() ) +#else + if ( g_pGameRules->IsMultiplayer() ) +#endif + { + // enable laser sight geometry. + pev->body = 1; + } + else + { + pev->body = 0; + } + + return DefaultDeploy( "models/v_357.mdl", "models/p_357.mdl", PYTHON_DRAW, "python", UseDecrement(), pev->body ); +} + + +void CPython::Holster( int skiplocal /* = 0 */ ) +{ + m_fInReload = FALSE;// cancel any reload in progress. + + if ( m_fInZoom ) + { + SecondaryAttack(); + } + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.0; + m_flTimeWeaponIdle = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + SendWeaponAnim( PYTHON_HOLSTER ); +} + +void CPython::SecondaryAttack( void ) +{ +#ifdef CLIENT_DLL + if ( !bIsMultiplayer() ) +#else + if ( !g_pGameRules->IsMultiplayer() ) +#endif + { + return; + } + + if ( m_pPlayer->pev->fov != 0 ) + { + m_fInZoom = FALSE; + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = 0; // 0 means reset to default fov + } + else if ( m_pPlayer->pev->fov != 40 ) + { + m_fInZoom = TRUE; + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = 40; + } + + m_flNextSecondaryAttack = 0.5; +} + +void CPython::PrimaryAttack() +{ + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = 0.15; + return; + } + + if (m_iClip <= 0) + { + if (!m_fFireOnEmpty) + Reload( ); + else + { + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/357_cock1.wav", 0.8, ATTN_NORM); + m_flNextPrimaryAttack = 0.15; + } + + return; + } + + m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = BRIGHT_GUN_FLASH; + + m_iClip--; + + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = m_pPlayer->GetAutoaimVector( AUTOAIM_10DEGREES ); + + Vector vecDir; + vecDir = m_pPlayer->FireBulletsPlayer( 1, vecSrc, vecAiming, VECTOR_CONE_1DEGREES, 8192, BULLET_PLAYER_357, 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_usFirePython, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, vecDir.x, vecDir.y, 0, 0, 0, 0 ); + + if (!m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + // HEV suit - indicate out of ammo condition + m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + + m_flNextPrimaryAttack = 0.75; + m_flTimeWeaponIdle = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); +} + + +void CPython::Reload( void ) +{ + if ( m_pPlayer->ammo_357 <= 0 ) + return; + + if ( m_pPlayer->pev->fov != 0 ) + { + m_fInZoom = FALSE; + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = 0; // 0 means reset to default fov + } + + int bUseScope = FALSE; +#ifdef CLIENT_DLL + bUseScope = bIsMultiplayer(); +#else + bUseScope = g_pGameRules->IsMultiplayer(); +#endif + + if (DefaultReload( 6, PYTHON_RELOAD, 2.0, bUseScope )) + { + m_flSoundDelay = 1.5; + } +} + + +void CPython::WeaponIdle( void ) +{ + ResetEmptySound( ); + + m_pPlayer->GetAutoaimVector( AUTOAIM_10DEGREES ); + + // ALERT( at_console, "%.2f\n", gpGlobals->time - m_flSoundDelay ); + if (m_flSoundDelay != 0 && m_flSoundDelay <= UTIL_WeaponTimeBase() ) + { + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/357_reload1.wav", RANDOM_FLOAT(0.8, 0.9), ATTN_NORM); + m_flSoundDelay = 0; + } + + if (m_flTimeWeaponIdle > UTIL_WeaponTimeBase() ) + return; + + int iAnim; + float flRand = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + if (flRand <= 0.5) + { + iAnim = PYTHON_IDLE1; + m_flTimeWeaponIdle = (70.0/30.0); + } + else if (flRand <= 0.7) + { + iAnim = PYTHON_IDLE2; + m_flTimeWeaponIdle = (60.0/30.0); + } + else if (flRand <= 0.9) + { + iAnim = PYTHON_IDLE3; + m_flTimeWeaponIdle = (88.0/30.0); + } + else + { + iAnim = PYTHON_FIDGET; + m_flTimeWeaponIdle = (170.0/30.0); + } + + int bUseScope = FALSE; +#ifdef CLIENT_DLL + bUseScope = bIsMultiplayer(); +#else + bUseScope = g_pGameRules->IsMultiplayer(); +#endif + + SendWeaponAnim( iAnim, UseDecrement() ? 1 : 0, bUseScope ); +} + + + +class CPythonAmmo : public CBasePlayerAmmo +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_357ammobox.mdl"); + CBasePlayerAmmo::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_357ammobox.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + } + BOOL AddAmmo( CBaseEntity *pOther ) + { + if (pOther->GiveAmmo( AMMO_357BOX_GIVE, "357", _357_MAX_CARRY ) != -1) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + return TRUE; + } + return FALSE; + } +}; +LINK_ENTITY_TO_CLASS( ammo_357, CPythonAmmo ); + + +#endif diff --git a/hlsdk/dlls/rpg.cpp b/hlsdk/dlls/rpg.cpp new file mode 100644 index 00000000..a604219b --- /dev/null +++ b/hlsdk/dlls/rpg.cpp @@ -0,0 +1,617 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( OEM_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "gamerules.h" + + + + +enum rpg_e { + RPG_IDLE = 0, + RPG_FIDGET, + RPG_RELOAD, // to reload + RPG_FIRE2, // to empty + RPG_HOLSTER1, // loaded + RPG_DRAW1, // loaded + RPG_HOLSTER2, // unloaded + RPG_DRAW_UL, // unloaded + RPG_IDLE_UL, // unloaded idle + RPG_FIDGET_UL, // unloaded fidget +}; + +LINK_ENTITY_TO_CLASS( weapon_rpg, CRpg ); + +#ifndef CLIENT_DLL + +LINK_ENTITY_TO_CLASS( laser_spot, CLaserSpot ); + +//========================================================= +//========================================================= +CLaserSpot *CLaserSpot::CreateSpot( void ) +{ + CLaserSpot *pSpot = GetClassPtr( (CLaserSpot *)NULL ); + pSpot->Spawn(); + + pSpot->pev->classname = MAKE_STRING("laser_spot"); + + return pSpot; +} + +//========================================================= +//========================================================= +void CLaserSpot::Spawn( void ) +{ + Precache( ); + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT; + + pev->rendermode = kRenderGlow; + pev->renderfx = kRenderFxNoDissipation; + pev->renderamt = 255; + + SET_MODEL(ENT(pev), "sprites/laserdot.spr"); + UTIL_SetOrigin( pev, pev->origin ); +}; + +//========================================================= +// Suspend- make the laser sight invisible. +//========================================================= +void CLaserSpot::Suspend( float flSuspendTime ) +{ + pev->effects |= EF_NODRAW; + + SetThink( &CLaserSpot::Revive ); + pev->nextthink = gpGlobals->time + flSuspendTime; +} + +//========================================================= +// Revive - bring a suspended laser sight back. +//========================================================= +void CLaserSpot::Revive( void ) +{ + pev->effects &= ~EF_NODRAW; + + SetThink( NULL ); +} + +void CLaserSpot::Precache( void ) +{ + PRECACHE_MODEL("sprites/laserdot.spr"); +}; + +LINK_ENTITY_TO_CLASS( rpg_rocket, CRpgRocket ); + +//========================================================= +//========================================================= +CRpgRocket *CRpgRocket::CreateRpgRocket( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner, CRpg *pLauncher ) +{ + CRpgRocket *pRocket = GetClassPtr( (CRpgRocket *)NULL ); + + UTIL_SetOrigin( pRocket->pev, vecOrigin ); + pRocket->pev->angles = vecAngles; + pRocket->Spawn(); + pRocket->SetTouch( &CRpgRocket::RocketTouch ); + pRocket->m_pLauncher = pLauncher;// remember what RPG fired me. + pRocket->m_pLauncher->m_cActiveRockets++;// register this missile as active for the launcher + pRocket->pev->owner = pOwner->edict(); + + return pRocket; +} + +//========================================================= +//========================================================= +void CRpgRocket :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_BOUNCE; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/rpgrocket.mdl"); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + UTIL_SetOrigin( pev, pev->origin ); + + pev->classname = MAKE_STRING("rpg_rocket"); + + SetThink( &CRpgRocket::IgniteThink ); + SetTouch( &CGrenade::ExplodeTouch ); + + pev->angles.x -= 30; + UTIL_MakeVectors( pev->angles ); + pev->angles.x = -(pev->angles.x + 30); + + pev->velocity = gpGlobals->v_forward * 250; + pev->gravity = 0.5; + + pev->nextthink = gpGlobals->time + 0.4; + + pev->dmg = gSkillData.plrDmgRPG; +} + +//========================================================= +//========================================================= +void CRpgRocket :: RocketTouch ( CBaseEntity *pOther ) +{ + if ( m_pLauncher ) + { + // my launcher is still around, tell it I'm dead. + m_pLauncher->m_cActiveRockets--; + } + + STOP_SOUND( edict(), CHAN_VOICE, "weapons/rocket1.wav" ); + ExplodeTouch( pOther ); +} + +//========================================================= +//========================================================= +void CRpgRocket :: Precache( void ) +{ + PRECACHE_MODEL("models/rpgrocket.mdl"); + m_iTrail = PRECACHE_MODEL("sprites/smoke.spr"); + PRECACHE_SOUND ("weapons/rocket1.wav"); +} + + +void CRpgRocket :: IgniteThink( void ) +{ + // pev->movetype = MOVETYPE_TOSS; + + pev->movetype = MOVETYPE_FLY; + pev->effects |= EF_LIGHT; + + // make rocket sound + EMIT_SOUND( ENT(pev), CHAN_VOICE, "weapons/rocket1.wav", 1, 0.5 ); + + // rocket trail + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + + WRITE_BYTE( TE_BEAMFOLLOW ); + WRITE_SHORT(entindex()); // entity + WRITE_SHORT(m_iTrail ); // model + WRITE_BYTE( 40 ); // life + WRITE_BYTE( 5 ); // width + WRITE_BYTE( 224 ); // r, g, b + WRITE_BYTE( 224 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + + MESSAGE_END(); // move PHS/PVS data sending into here (SEND_ALL, SEND_PVS, SEND_PHS) + + m_flIgniteTime = gpGlobals->time; + + // set to follow laser spot + SetThink( &CRpgRocket::FollowThink ); + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CRpgRocket :: FollowThink( void ) +{ + CBaseEntity *pOther = NULL; + Vector vecTarget; + Vector vecDir; + float flDist, flMax, flDot; + TraceResult tr; + + UTIL_MakeAimVectors( pev->angles ); + + vecTarget = gpGlobals->v_forward; + flMax = 4096; + + // Examine all entities within a reasonable radius + while ((pOther = UTIL_FindEntityByClassname( pOther, "laser_spot" )) != NULL) + { + UTIL_TraceLine ( pev->origin, pOther->pev->origin, dont_ignore_monsters, ENT(pev), &tr ); + // ALERT( at_console, "%f\n", tr.flFraction ); + if (tr.flFraction >= 0.90) + { + vecDir = pOther->pev->origin - pev->origin; + flDist = vecDir.Length( ); + vecDir = vecDir.Normalize( ); + flDot = DotProduct( gpGlobals->v_forward, vecDir ); + if ((flDot > 0) && (flDist * (1 - flDot) < flMax)) + { + flMax = flDist * (1 - flDot); + vecTarget = vecDir; + } + } + } + + pev->angles = UTIL_VecToAngles( vecTarget ); + + // this acceleration and turning math is totally wrong, but it seems to respond well so don't change it. + float flSpeed = pev->velocity.Length(); + if (gpGlobals->time - m_flIgniteTime < 1.0) + { + pev->velocity = pev->velocity * 0.2 + vecTarget * (flSpeed * 0.8 + 400); + if (pev->waterlevel == 3) + { + // go slow underwater + if (pev->velocity.Length() > 300) + { + pev->velocity = pev->velocity.Normalize() * 300; + } + UTIL_BubbleTrail( pev->origin - pev->velocity * 0.1, pev->origin, 4 ); + } + else + { + if (pev->velocity.Length() > 2000) + { + pev->velocity = pev->velocity.Normalize() * 2000; + } + } + } + else + { + if (pev->effects & EF_LIGHT) + { + pev->effects = 0; + STOP_SOUND( ENT(pev), CHAN_VOICE, "weapons/rocket1.wav" ); + } + pev->velocity = pev->velocity * 0.2 + vecTarget * flSpeed * 0.798; + if (pev->waterlevel == 0 && pev->velocity.Length() < 1500) + { + Detonate( ); + } + } + // ALERT( at_console, "%.0f\n", flSpeed ); + + pev->nextthink = gpGlobals->time + 0.1; +} +#endif + + + +void CRpg::Reload( void ) +{ + int iResult = 0; + + if ( m_iClip == 1 ) + { + // don't bother with any of this if don't need to reload. + return; + } + + if ( m_pPlayer->ammo_rockets <= 0 ) + return; + + // because the RPG waits to autoreload when no missiles are active while the LTD is on, the + // weapons code is constantly calling into this function, but is often denied because + // a) missiles are in flight, but the LTD is on + // or + // b) player is totally out of ammo and has nothing to switch to, and should be allowed to + // shine the designator around + // + // Set the next attack time into the future so that WeaponIdle will get called more often + // than reload, allowing the RPG LTD to be updated + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.5; + + if ( m_cActiveRockets && m_fSpotActive ) + { + // no reloading when there are active missiles tracking the designator. + // ward off future autoreload attempts by setting next attack time into the future for a bit. + return; + } + +#ifndef CLIENT_DLL + if ( m_pSpot && m_fSpotActive ) + { + m_pSpot->Suspend( 2.1 ); + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 2.1; + } +#endif + + if ( m_iClip == 0 ) + iResult = DefaultReload( RPG_MAX_CLIP, RPG_RELOAD, 2 ); + + if ( iResult ) + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + +} + +void CRpg::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_RPG; + + SET_MODEL(ENT(pev), "models/w_rpg.mdl"); + m_fSpotActive = 1; + +#ifdef CLIENT_DLL + if ( bIsMultiplayer() ) +#else + if ( g_pGameRules->IsMultiplayer() ) +#endif + { + // more default ammo in multiplay. + m_iDefaultAmmo = RPG_DEFAULT_GIVE * 2; + } + else + { + m_iDefaultAmmo = RPG_DEFAULT_GIVE; + } + + FallInit();// get ready to fall down. +} + + +void CRpg::Precache( void ) +{ + PRECACHE_MODEL("models/w_rpg.mdl"); + PRECACHE_MODEL("models/v_rpg.mdl"); + PRECACHE_MODEL("models/p_rpg.mdl"); + + PRECACHE_SOUND("items/9mmclip1.wav"); + + UTIL_PrecacheOther( "laser_spot" ); + UTIL_PrecacheOther( "rpg_rocket" ); + + PRECACHE_SOUND("weapons/rocketfire1.wav"); + PRECACHE_SOUND("weapons/glauncher.wav"); // alternative fire sound + + m_usRpg = PRECACHE_EVENT ( 1, "events/rpg.sc" ); +} + + +int CRpg::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "rockets"; + p->iMaxAmmo1 = ROCKET_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = RPG_MAX_CLIP; + p->iSlot = 3; + p->iPosition = 0; + p->iId = m_iId = WEAPON_RPG; + p->iFlags = 0; + p->iWeight = RPG_WEIGHT; + + return 1; +} + +int CRpg::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + +BOOL CRpg::Deploy( ) +{ + if ( m_iClip == 0 ) + { + return DefaultDeploy( "models/v_rpg.mdl", "models/p_rpg.mdl", RPG_DRAW_UL, "rpg" ); + } + + return DefaultDeploy( "models/v_rpg.mdl", "models/p_rpg.mdl", RPG_DRAW1, "rpg" ); +} + + +BOOL CRpg::CanHolster( void ) +{ + if ( m_fSpotActive && m_cActiveRockets ) + { + // can't put away while guiding a missile. + return FALSE; + } + + return TRUE; +} + +void CRpg::Holster( int skiplocal /* = 0 */ ) +{ + m_fInReload = FALSE;// cancel any reload in progress. + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + + SendWeaponAnim( RPG_HOLSTER1 ); + +#ifndef CLIENT_DLL + if (m_pSpot) + { + m_pSpot->Killed( NULL, GIB_NEVER ); + m_pSpot = NULL; + } +#endif + +} + + + +void CRpg::PrimaryAttack() +{ + if ( m_iClip ) + { + m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = BRIGHT_GUN_FLASH; + +#ifndef CLIENT_DLL + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + UTIL_MakeVectors( m_pPlayer->pev->v_angle ); + Vector vecSrc = m_pPlayer->GetGunPosition( ) + gpGlobals->v_forward * 16 + gpGlobals->v_right * 8 + gpGlobals->v_up * -8; + + CRpgRocket *pRocket = CRpgRocket::CreateRpgRocket( vecSrc, m_pPlayer->pev->v_angle, m_pPlayer, this ); + + UTIL_MakeVectors( m_pPlayer->pev->v_angle );// RpgRocket::Create stomps on globals, so remake. + pRocket->pev->velocity = pRocket->pev->velocity + gpGlobals->v_forward * DotProduct( m_pPlayer->pev->velocity, gpGlobals->v_forward ); +#endif + + // firing RPG no longer turns on the designator. ALT fire is a toggle switch for the LTD. + // Ken signed up for this as a global change (sjb) + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT( flags, m_pPlayer->edict(), m_usRpg ); + + m_iClip--; + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 1.5; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.5; + } + else + { + PlayEmptySound( ); + } + UpdateSpot( ); +} + + +void CRpg::SecondaryAttack() +{ + m_fSpotActive = ! m_fSpotActive; + +#ifndef CLIENT_DLL + if (!m_fSpotActive && m_pSpot) + { + m_pSpot->Killed( NULL, GIB_NORMAL ); + m_pSpot = NULL; + } +#endif + + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.2; +} + + +void CRpg::WeaponIdle( void ) +{ + UpdateSpot( ); + + ResetEmptySound( ); + + if ( m_flTimeWeaponIdle > UTIL_WeaponTimeBase() ) + return; + + if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + int iAnim; + float flRand = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 0, 1 ); + if (flRand <= 0.75 || m_fSpotActive) + { + if ( m_iClip == 0 ) + iAnim = RPG_IDLE_UL; + else + iAnim = RPG_IDLE; + + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 90.0 / 15.0; + } + else + { + if ( m_iClip == 0 ) + iAnim = RPG_FIDGET_UL; + else + iAnim = RPG_FIDGET; + + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 3.0; + } + + SendWeaponAnim( iAnim ); + } + else + { + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1; + } +} + + + +void CRpg::UpdateSpot( void ) +{ + +#ifndef CLIENT_DLL + if (m_fSpotActive) + { + if (!m_pSpot) + { + m_pSpot = CLaserSpot::CreateSpot(); + } + + UTIL_MakeVectors( m_pPlayer->pev->v_angle ); + Vector vecSrc = m_pPlayer->GetGunPosition( );; + Vector vecAiming = gpGlobals->v_forward; + + TraceResult tr; + UTIL_TraceLine ( vecSrc, vecSrc + vecAiming * 8192, dont_ignore_monsters, ENT(m_pPlayer->pev), &tr ); + + UTIL_SetOrigin( m_pSpot->pev, tr.vecEndPos ); + } +#endif + +} + + +class CRpgAmmo : public CBasePlayerAmmo +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_rpgammo.mdl"); + CBasePlayerAmmo::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_rpgammo.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + } + BOOL AddAmmo( CBaseEntity *pOther ) + { + int iGive; + +#ifdef CLIENT_DLL + if ( bIsMultiplayer() ) +#else + if ( g_pGameRules->IsMultiplayer() ) +#endif + { + // hand out more ammo per rocket in multiplayer. + iGive = AMMO_RPGCLIP_GIVE * 2; + } + else + { + iGive = AMMO_RPGCLIP_GIVE; + } + + if (pOther->GiveAmmo( iGive, "rockets", ROCKET_MAX_CARRY ) != -1) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + return TRUE; + } + return FALSE; + } +}; +LINK_ENTITY_TO_CLASS( ammo_rpgclip, CRpgAmmo ); + +#endif diff --git a/hlsdk/dlls/satchel.cpp b/hlsdk/dlls/satchel.cpp new file mode 100644 index 00000000..eedefc48 --- /dev/null +++ b/hlsdk/dlls/satchel.cpp @@ -0,0 +1,492 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "gamerules.h" + +enum satchel_e { + SATCHEL_IDLE1 = 0, + SATCHEL_FIDGET1, + SATCHEL_DRAW, + SATCHEL_DROP +}; + +enum satchel_radio_e { + SATCHEL_RADIO_IDLE1 = 0, + SATCHEL_RADIO_FIDGET1, + SATCHEL_RADIO_DRAW, + SATCHEL_RADIO_FIRE, + SATCHEL_RADIO_HOLSTER +}; + + + +class CSatchelCharge : public CGrenade +{ + void Spawn( void ); + void Precache( void ); + void BounceSound( void ); + + void EXPORT SatchelSlide( CBaseEntity *pOther ); + void EXPORT SatchelThink( void ); + +public: + void Deactivate( void ); +}; +LINK_ENTITY_TO_CLASS( monster_satchel, CSatchelCharge ); + +//========================================================= +// Deactivate - do whatever it is we do to an orphaned +// satchel when we don't want it in the world anymore. +//========================================================= +void CSatchelCharge::Deactivate( void ) +{ + pev->solid = SOLID_NOT; + UTIL_Remove( this ); +} + + +void CSatchelCharge :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_BOUNCE; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/w_satchel.mdl"); + //UTIL_SetSize(pev, Vector( -16, -16, -4), Vector(16, 16, 32)); // Old box -- size of headcrab monsters/players get blocked by this + UTIL_SetSize(pev, Vector( -4, -4, -4), Vector(4, 4, 4)); // Uses point-sized, and can be stepped over + UTIL_SetOrigin( pev, pev->origin ); + + SetTouch( &CSatchelCharge::SatchelSlide ); + SetUse( &CGrenade::DetonateUse ); + SetThink( &CSatchelCharge::SatchelThink ); + pev->nextthink = gpGlobals->time + 0.1; + + pev->gravity = 0.5; + pev->friction = 0.8; + + pev->dmg = gSkillData.plrDmgSatchel; + // ResetSequenceInfo( ); + pev->sequence = 1; +} + + +void CSatchelCharge::SatchelSlide( CBaseEntity *pOther ) +{ + // don't hit the guy that launched this grenade + if ( pOther->edict() == pev->owner ) + return; + + // pev->avelocity = Vector (300, 300, 300); + pev->gravity = 1;// normal gravity now + + // HACKHACK - On ground isn't always set, so look for ground underneath + TraceResult tr; + UTIL_TraceLine( pev->origin, pev->origin - Vector(0,0,10), ignore_monsters, edict(), &tr ); + + if ( tr.flFraction < 1.0 ) + { + // add a bit of static friction + pev->velocity = pev->velocity * 0.95; + pev->avelocity = pev->avelocity * 0.9; + // play sliding sound, volume based on velocity + } + if ( !(pev->flags & FL_ONGROUND) && pev->velocity.Length2D() > 10 ) + { + BounceSound(); + } + StudioFrameAdvance( ); +} + + +void CSatchelCharge :: SatchelThink( void ) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + if (pev->waterlevel == 3) + { + pev->movetype = MOVETYPE_FLY; + pev->velocity = pev->velocity * 0.8; + pev->avelocity = pev->avelocity * 0.9; + pev->velocity.z += 8; + } + else if (pev->waterlevel == 0) + { + pev->movetype = MOVETYPE_BOUNCE; + } + else + { + pev->velocity.z -= 8; + } +} + +void CSatchelCharge :: Precache( void ) +{ + PRECACHE_MODEL("models/grenade.mdl"); + PRECACHE_SOUND("weapons/g_bounce1.wav"); + PRECACHE_SOUND("weapons/g_bounce2.wav"); + PRECACHE_SOUND("weapons/g_bounce3.wav"); +} + +void CSatchelCharge :: BounceSound( void ) +{ + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/g_bounce1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/g_bounce2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/g_bounce3.wav", 1, ATTN_NORM); break; + } +} + + +LINK_ENTITY_TO_CLASS( weapon_satchel, CSatchel ); + + +//========================================================= +// CALLED THROUGH the newly-touched weapon's instance. The existing player weapon is pOriginal +//========================================================= +int CSatchel::AddDuplicate( CBasePlayerItem *pOriginal ) +{ + CSatchel *pSatchel; + +#ifdef CLIENT_DLL + if ( bIsMultiplayer() ) +#else + if ( g_pGameRules->IsMultiplayer() ) +#endif + { + pSatchel = (CSatchel *)pOriginal; + + if ( pSatchel->m_chargeReady != 0 ) + { + // player has some satchels deployed. Refuse to add more. + return FALSE; + } + } + + return CBasePlayerWeapon::AddDuplicate ( pOriginal ); +} + +//========================================================= +//========================================================= +int CSatchel::AddToPlayer( CBasePlayer *pPlayer ) +{ + int bResult = CBasePlayerItem::AddToPlayer( pPlayer ); + + pPlayer->pev->weapons |= (1<pszName = STRING(pev->classname); + p->pszAmmo1 = "Satchel Charge"; + p->iMaxAmmo1 = SATCHEL_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 4; + p->iPosition = 1; + p->iFlags = ITEM_FLAG_SELECTONEMPTY | ITEM_FLAG_LIMITINWORLD | ITEM_FLAG_EXHAUSTIBLE; + p->iId = m_iId = WEAPON_SATCHEL; + p->iWeight = SATCHEL_WEIGHT; + + return 1; +} + +//========================================================= +//========================================================= +BOOL CSatchel::IsUseable( void ) +{ + if ( m_pPlayer->m_rgAmmo[ PrimaryAmmoIndex() ] > 0 ) + { + // player is carrying some satchels + return TRUE; + } + + if ( m_chargeReady != 0 ) + { + // player isn't carrying any satchels, but has some out + return TRUE; + } + + return FALSE; +} + +BOOL CSatchel::CanDeploy( void ) +{ + if ( m_pPlayer->m_rgAmmo[ PrimaryAmmoIndex() ] > 0 ) + { + // player is carrying some satchels + return TRUE; + } + + if ( m_chargeReady != 0 ) + { + // player isn't carrying any satchels, but has some out + return TRUE; + } + + return FALSE; +} + +BOOL CSatchel::Deploy( ) +{ + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.0; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + + if ( m_chargeReady ) + return DefaultDeploy( "models/v_satchel_radio.mdl", "models/p_satchel_radio.mdl", SATCHEL_RADIO_DRAW, "hive" ); + else + return DefaultDeploy( "models/v_satchel.mdl", "models/p_satchel.mdl", SATCHEL_DRAW, "trip" ); + + + return TRUE; +} + + +void CSatchel::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + + if ( m_chargeReady ) + { + SendWeaponAnim( SATCHEL_RADIO_HOLSTER ); + } + else + { + SendWeaponAnim( SATCHEL_DROP ); + } + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "common/null.wav", 1.0, ATTN_NORM); + + if ( !m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] && !m_chargeReady ) + { + m_pPlayer->pev->weapons &= ~(1<nextthink = gpGlobals->time + 0.1; + } +} + + + +void CSatchel::PrimaryAttack() +{ + switch (m_chargeReady) + { + case 0: + { + Throw( ); + } + break; + case 1: + { + SendWeaponAnim( SATCHEL_RADIO_FIRE ); + + edict_t *pPlayer = m_pPlayer->edict( ); + + CBaseEntity *pSatchel = NULL; + + while ((pSatchel = UTIL_FindEntityInSphere( pSatchel, m_pPlayer->pev->origin, 4096 )) != NULL) + { + if (FClassnameIs( pSatchel->pev, "monster_satchel")) + { + if (pSatchel->pev->owner == pPlayer) + { + pSatchel->Use( m_pPlayer, m_pPlayer, USE_ON, 0 ); + m_chargeReady = 2; + } + } + } + + m_chargeReady = 2; + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.5; + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.5; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.5; + break; + } + + case 2: + // we're reloading, don't allow fire + { + } + break; + } +} + + +void CSatchel::SecondaryAttack( void ) +{ + if ( m_chargeReady != 2 ) + { + Throw( ); + } +} + + +void CSatchel::Throw( void ) +{ + if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] ) + { + Vector vecSrc = m_pPlayer->pev->origin; + + Vector vecThrow = gpGlobals->v_forward * 274 + m_pPlayer->pev->velocity; + +#ifndef CLIENT_DLL + CBaseEntity *pSatchel = Create( "monster_satchel", vecSrc, Vector( 0, 0, 0), m_pPlayer->edict() ); + pSatchel->pev->velocity = vecThrow; + pSatchel->pev->avelocity.y = 400; + + m_pPlayer->pev->viewmodel = MAKE_STRING("models/v_satchel_radio.mdl"); + m_pPlayer->pev->weaponmodel = MAKE_STRING("models/p_satchel_radio.mdl"); +#else + LoadVModel ( "models/v_satchel_radio.mdl", m_pPlayer ); +#endif + + SendWeaponAnim( SATCHEL_RADIO_DRAW ); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_chargeReady = 1; + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 1.0; + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.5; + } +} + + +void CSatchel::WeaponIdle( void ) +{ + if ( m_flTimeWeaponIdle > UTIL_WeaponTimeBase() ) + return; + + switch( m_chargeReady ) + { + case 0: + SendWeaponAnim( SATCHEL_FIDGET1 ); + // use tripmine animations + strcpy( m_pPlayer->m_szAnimExtention, "trip" ); + break; + case 1: + SendWeaponAnim( SATCHEL_RADIO_FIDGET1 ); + // use hivehand animations + strcpy( m_pPlayer->m_szAnimExtention, "hive" ); + break; + case 2: + if ( !m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] ) + { + m_chargeReady = 0; + RetireWeapon(); + return; + } + +#ifndef CLIENT_DLL + m_pPlayer->pev->viewmodel = MAKE_STRING("models/v_satchel.mdl"); + m_pPlayer->pev->weaponmodel = MAKE_STRING("models/p_satchel.mdl"); +#else + LoadVModel ( "models/v_satchel.mdl", m_pPlayer ); +#endif + + SendWeaponAnim( SATCHEL_DRAW ); + + // use tripmine animations + strcpy( m_pPlayer->m_szAnimExtention, "trip" ); + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.5; + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.5; + m_chargeReady = 0; + break; + } + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 );// how long till we do this again. +} + +//========================================================= +// DeactivateSatchels - removes all satchels owned by +// the provided player. Should only be used upon death. +// +// Made this global on purpose. +//========================================================= +void DeactivateSatchels( CBasePlayer *pOwner ) +{ + edict_t *pFind; + + pFind = FIND_ENTITY_BY_CLASSNAME( NULL, "monster_satchel" ); + + while ( !FNullEnt( pFind ) ) + { + CBaseEntity *pEnt = CBaseEntity::Instance( pFind ); + CSatchelCharge *pSatchel = (CSatchelCharge *)pEnt; + + if ( pSatchel ) + { + if ( pSatchel->pev->owner == pOwner->edict() ) + { + pSatchel->Deactivate(); + } + } + + pFind = FIND_ENTITY_BY_CLASSNAME( pFind, "monster_satchel" ); + } +} + +#endif diff --git a/hlsdk/dlls/saverestore.h b/hlsdk/dlls/saverestore.h new file mode 100644 index 00000000..f1cd4187 --- /dev/null +++ b/hlsdk/dlls/saverestore.h @@ -0,0 +1,176 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Implementation in UTIL.CPP +#ifndef SAVERESTORE_H +#define SAVERESTORE_H + +class CBaseEntity; + +class CSaveRestoreBuffer +{ +public: + CSaveRestoreBuffer( void ); + CSaveRestoreBuffer( SAVERESTOREDATA *pdata ); + virtual ~CSaveRestoreBuffer( void ); + + int EntityIndex( entvars_t *pevLookup ); + int EntityIndex( edict_t *pentLookup ); + int EntityIndex( EOFFSET eoLookup ); + int EntityIndex( CBaseEntity *pEntity ); + + int EntityFlags( int entityIndex, int flags ) { return EntityFlagsSet( entityIndex, 0 ); } + int EntityFlagsSet( int entityIndex, int flags ); + + edict_t *EntityFromIndex( int entityIndex ); + + unsigned short TokenHash( const char *pszToken ); + +protected: + SAVERESTOREDATA *m_pdata; + void BufferRewind( int size ); + unsigned int HashString( const char *pszToken ); +private: + // effc++ rule 11 + void operator=(CSaveRestoreBuffer&); + CSaveRestoreBuffer(const CSaveRestoreBuffer&); +}; + + +class CSave : public CSaveRestoreBuffer +{ +public: + CSave( SAVERESTOREDATA *pdata ) : CSaveRestoreBuffer( pdata ) {}; + + void WriteShort( const char *pname, const short *value, int count ); + void WriteInt( const char *pname, const int *value, int count ); // Save an int + void WriteFloat( const char *pname, const float *value, int count ); // Save a float + void WriteTime( const char *pname, const float *value, int count ); // Save a float (timevalue) + void WriteData( const char *pname, int size, const char *pdata ); // Save a binary data block + void WriteString( const char *pname, const char *pstring ); // Save a null-terminated string + void WriteString( const char *pname, const int *stringId, int count ); // Save a null-terminated string (engine string) + void WriteVector( const char *pname, const Vector &value ); // Save a vector + void WriteVector( const char *pname, const float *value, int count ); // Save a vector + void WritePositionVector( const char *pname, const Vector &value ); // Offset for landmark if necessary + void WritePositionVector( const char *pname, const float *value, int count ); // array of pos vectors + void WriteFunction( const char *pname, const int *value, int count ); // Save a function pointer + int WriteEntVars( const char *pname, entvars_t *pev ); // Save entvars_t (entvars_t) + int WriteFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); + +private: + int DataEmpty( const char *pdata, int size ); + void BufferField( const char *pname, int size, const char *pdata ); + void BufferString( char *pdata, int len ); + void BufferData( const char *pdata, int size ); + void BufferHeader( const char *pname, int size ); +}; + +typedef struct +{ + unsigned short size; + unsigned short token; + char *pData; +} HEADER; + +class CRestore : public CSaveRestoreBuffer +{ +public: + CRestore( SAVERESTOREDATA *pdata ) : CSaveRestoreBuffer( pdata ), m_global(0), m_precache(TRUE) { } + int ReadEntVars( const char *pname, entvars_t *pev ); // entvars_t + int ReadFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); + int ReadField( void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount, int startField, int size, char *pName, void *pData ); + int ReadInt( void ); + short ReadShort( void ); + int ReadNamedInt( const char *pName ); + char *ReadNamedString( const char *pName ); + int Empty( void ) { return (m_pdata == NULL) || ((m_pdata->pCurrentData-m_pdata->pBaseData)>=m_pdata->bufferSize); } + inline void SetGlobalMode( int global ) { m_global = global; } + void PrecacheMode( BOOL mode ) { m_precache = mode; } + +private: + char *BufferPointer( void ); + void BufferReadBytes( char *pOutput, int size ); + void BufferSkipBytes( int bytes ); + int BufferSkipZString( void ); + int BufferCheckZString( const char *string ); + + void BufferReadHeader( HEADER *pheader ); + + int m_global; // Restoring a global entity? + BOOL m_precache; +}; + +#define MAX_ENTITYARRAY 64 + +//#define ARRAYSIZE(p) (sizeof(p)/sizeof(p[0])) + +#define IMPLEMENT_SAVERESTORE(derivedClass,baseClass) \ + int derivedClass::Save( CSave &save )\ + {\ + if ( !baseClass::Save(save) )\ + return 0;\ + return save.WriteFields( #derivedClass, this, m_SaveData, ARRAYSIZE(m_SaveData) );\ + }\ + int derivedClass::Restore( CRestore &restore )\ + {\ + if ( !baseClass::Restore(restore) )\ + return 0;\ + return restore.ReadFields( #derivedClass, this, m_SaveData, ARRAYSIZE(m_SaveData) );\ + } + + +typedef enum { GLOBAL_OFF = 0, GLOBAL_ON = 1, GLOBAL_DEAD = 2 } GLOBALESTATE; + +typedef struct globalentity_s globalentity_t; + +struct globalentity_s +{ + char name[64]; + char levelName[32]; + GLOBALESTATE state; + globalentity_t *pNext; +}; + +class CGlobalState +{ +public: + CGlobalState(); + void Reset( void ); + void ClearStates( void ); + void EntityAdd( string_t globalname, string_t mapName, GLOBALESTATE state ); + void EntitySetState( string_t globalname, GLOBALESTATE state ); + void EntityUpdate( string_t globalname, string_t mapname ); + const globalentity_t *EntityFromTable( string_t globalname ); + GLOBALESTATE EntityGetState( string_t globalname ); + int EntityInTable( string_t globalname ) { return (Find( globalname ) != NULL) ? 1 : 0; } + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +//#ifdef _DEBUG + void DumpGlobals( void ); +//#endif + +private: + globalentity_t *Find( string_t globalname ); + globalentity_t *m_pList; + int m_listCount; + // effc++ rule 11 + void operator=(CGlobalState&); + CGlobalState(const CGlobalState&); +}; + +extern CGlobalState gGlobalState; + +#endif //SAVERESTORE_H diff --git a/hlsdk/dlls/schedule.h b/hlsdk/dlls/schedule.h new file mode 100644 index 00000000..52ea0442 --- /dev/null +++ b/hlsdk/dlls/schedule.h @@ -0,0 +1,31 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// Scheduling +//========================================================= +#ifndef SCHEDULE_H +#define SCHEDULE_H + +#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_LIGHT_DAMAGE ( 1 << 8 ) // hurt a little +#define bits_COND_HEAVY_DAMAGE ( 1 << 9 ) // hurt a lot +#define bits_COND_SEE_CLIENT ( 1 << 21) // see a client +#define bits_COND_SEE_NEMESIS ( 1 << 22) // see my nemesis + + +#endif // SCHEDULE_H diff --git a/hlsdk/dlls/scriptevent.h b/hlsdk/dlls/scriptevent.h new file mode 100644 index 00000000..42377cf0 --- /dev/null +++ b/hlsdk/dlls/scriptevent.h @@ -0,0 +1,29 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef SCRIPTEVENT_H +#define SCRIPTEVENT_H + +#define SCRIPT_EVENT_DEAD 1000 // character is now dead +#define SCRIPT_EVENT_NOINTERRUPT 1001 // does not allow interrupt +#define SCRIPT_EVENT_CANINTERRUPT 1002 // will allow interrupt +#define SCRIPT_EVENT_FIREEVENT 1003 // event now fires +#define SCRIPT_EVENT_SOUND 1004 // Play named wave file (on CHAN_BODY) +#define SCRIPT_EVENT_SENTENCE 1005 // Play named sentence +#define SCRIPT_EVENT_INAIR 1006 // Leave the character in air at the end of the sequence (don't find the floor) +#define SCRIPT_EVENT_ENDANIMATION 1007 // Set the animation by name after the sequence completes +#define SCRIPT_EVENT_SOUND_VOICE 1008 // Play named wave file (on CHAN_VOICE) +#define SCRIPT_EVENT_SENTENCE_RND1 1009 // Play sentence group 25% of the time +#define SCRIPT_EVENT_NOT_DEAD 1010 // Bring back to life (for life/death sequences) +#endif //SCRIPTEVENT_H diff --git a/hlsdk/dlls/shotgun.cpp b/hlsdk/dlls/shotgun.cpp new file mode 100644 index 00000000..f0575482 --- /dev/null +++ b/hlsdk/dlls/shotgun.cpp @@ -0,0 +1,401 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "gamerules.h" + +// special deathmatch shotgun spreads +#define VECTOR_CONE_DM_SHOTGUN Vector( 0.08716, 0.04362, 0.00 )// 10 degrees by 5 degrees +#define VECTOR_CONE_DM_DOUBLESHOTGUN Vector( 0.17365, 0.04362, 0.00 ) // 20 degrees by 5 degrees + +enum shotgun_e { + SHOTGUN_IDLE = 0, + SHOTGUN_FIRE, + SHOTGUN_FIRE2, + SHOTGUN_RELOAD, + SHOTGUN_PUMP, + SHOTGUN_START_RELOAD, + SHOTGUN_DRAW, + SHOTGUN_HOLSTER, + SHOTGUN_IDLE4, + SHOTGUN_IDLE_DEEP +}; + +LINK_ENTITY_TO_CLASS( weapon_shotgun, CShotgun ); + +void CShotgun::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_SHOTGUN; + SET_MODEL(ENT(pev), "models/w_shotgun.mdl"); + + m_iDefaultAmmo = SHOTGUN_DEFAULT_GIVE; + + FallInit();// get ready to fall +} + + +void CShotgun::Precache( void ) +{ + PRECACHE_MODEL("models/v_shotgun.mdl"); + PRECACHE_MODEL("models/w_shotgun.mdl"); + PRECACHE_MODEL("models/p_shotgun.mdl"); + + m_iShell = PRECACHE_MODEL ("models/shotgunshell.mdl");// shotgun shell + + PRECACHE_SOUND("items/9mmclip1.wav"); + + PRECACHE_SOUND ("weapons/dbarrel1.wav");//shotgun + PRECACHE_SOUND ("weapons/sbarrel1.wav");//shotgun + + PRECACHE_SOUND ("weapons/reload1.wav"); // shotgun reload + PRECACHE_SOUND ("weapons/reload3.wav"); // shotgun reload + +// PRECACHE_SOUND ("weapons/sshell1.wav"); // shotgun reload - played on client +// PRECACHE_SOUND ("weapons/sshell3.wav"); // shotgun reload - played on client + + PRECACHE_SOUND ("weapons/357_cock1.wav"); // gun empty sound + PRECACHE_SOUND ("weapons/scock1.wav"); // cock gun + + m_usSingleFire = PRECACHE_EVENT( 1, "events/shotgun1.sc" ); + m_usDoubleFire = PRECACHE_EVENT( 1, "events/shotgun2.sc" ); +} + +int CShotgun::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + + +int CShotgun::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "buckshot"; + p->iMaxAmmo1 = BUCKSHOT_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = SHOTGUN_MAX_CLIP; + p->iSlot = 2; + p->iPosition = 1; + p->iFlags = 0; + p->iId = m_iId = WEAPON_SHOTGUN; + p->iWeight = SHOTGUN_WEIGHT; + + return 1; +} + + + +BOOL CShotgun::Deploy( ) +{ + return DefaultDeploy( "models/v_shotgun.mdl", "models/p_shotgun.mdl", SHOTGUN_DRAW, "shotgun" ); +} + +void CShotgun::PrimaryAttack() +{ + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.15; + return; + } + + if (m_iClip <= 0) + { + Reload( ); + if (m_iClip == 0) + PlayEmptySound( ); + return; + } + + m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; + + m_iClip--; + + 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 = m_pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); + + Vector vecDir; + +#ifdef CLIENT_DLL + if ( bIsMultiplayer() ) +#else + if ( g_pGameRules->IsMultiplayer() ) +#endif + { + vecDir = m_pPlayer->FireBulletsPlayer( 4, vecSrc, vecAiming, VECTOR_CONE_DM_SHOTGUN, 2048, BULLET_PLAYER_BUCKSHOT, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed ); + } + else + { + // regular old, untouched spread. + vecDir = m_pPlayer->FireBulletsPlayer( 6, vecSrc, vecAiming, VECTOR_CONE_10DEGREES, 2048, BULLET_PLAYER_BUCKSHOT, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed ); + } + + PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usSingleFire, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, vecDir.x, vecDir.y, 0, 0, 0, 0 ); + + + if (!m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + // HEV suit - indicate out of ammo condition + m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + + if (m_iClip != 0) + m_flPumpTime = gpGlobals->time + 0.5; + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.75; + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.75; + if (m_iClip != 0) + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 5.0; + else + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.75; + m_fInSpecialReload = 0; +} + + +void CShotgun::SecondaryAttack( void ) +{ + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.15; + return; + } + + if (m_iClip <= 1) + { + Reload( ); + PlayEmptySound( ); + return; + } + + m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; + + m_iClip -= 2; + + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = m_pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); + + Vector vecDir; + +#ifdef CLIENT_DLL + if ( bIsMultiplayer() ) +#else + if ( g_pGameRules->IsMultiplayer() ) +#endif + { + // tuned for deathmatch + vecDir = m_pPlayer->FireBulletsPlayer( 8, vecSrc, vecAiming, VECTOR_CONE_DM_DOUBLESHOTGUN, 2048, BULLET_PLAYER_BUCKSHOT, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed ); + } + else + { + // untouched default single player + vecDir = m_pPlayer->FireBulletsPlayer( 12, vecSrc, vecAiming, VECTOR_CONE_10DEGREES, 2048, BULLET_PLAYER_BUCKSHOT, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed ); + } + + PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usDoubleFire, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, vecDir.x, vecDir.y, 0, 0, 0, 0 ); + + if (!m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + // HEV suit - indicate out of ammo condition + m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + + if (m_iClip != 0) + m_flPumpTime = gpGlobals->time + 0.95; + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 1.5; + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 1.5; + if (m_iClip != 0) + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 6.0; + else + m_flTimeWeaponIdle = 1.5; + + m_fInSpecialReload = 0; + +} + + +void CShotgun::Reload( void ) +{ + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0 || m_iClip == SHOTGUN_MAX_CLIP) + return; + + // don't reload until recoil is done + if (m_flNextPrimaryAttack > UTIL_WeaponTimeBase()) + return; + + // check to see if we're ready to reload + if (m_fInSpecialReload == 0) + { + SendWeaponAnim( SHOTGUN_START_RELOAD ); + m_fInSpecialReload = 1; + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.6; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.6; + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 1.0; + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 1.0; + return; + } + else if (m_fInSpecialReload == 1) + { + if (m_flTimeWeaponIdle > UTIL_WeaponTimeBase()) + return; + // was waiting for gun to move to side + m_fInSpecialReload = 2; + + if (RANDOM_LONG(0,1)) + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/reload1.wav", 1, ATTN_NORM, 0, 85 + RANDOM_LONG(0,0x1f)); + else + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/reload3.wav", 1, ATTN_NORM, 0, 85 + RANDOM_LONG(0,0x1f)); + + SendWeaponAnim( SHOTGUN_RELOAD ); + + m_flNextReload = UTIL_WeaponTimeBase() + 0.5; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.5; + } + else + { + // Add them to the clip + m_iClip += 1; + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= 1; + m_fInSpecialReload = 1; + } +} + + +void CShotgun::WeaponIdle( void ) +{ + ResetEmptySound( ); + + m_pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); + + if ( m_flPumpTime && m_flPumpTime < gpGlobals->time ) + { + // play pumping sound + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/scock1.wav", 1, ATTN_NORM, 0, 95 + RANDOM_LONG(0,0x1f)); + m_flPumpTime = 0; + } + + if (m_flTimeWeaponIdle < UTIL_WeaponTimeBase() ) + { + if (m_iClip == 0 && m_fInSpecialReload == 0 && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + Reload( ); + } + else if (m_fInSpecialReload != 0) + { + if (m_iClip != 8 && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + Reload( ); + } + else + { + // reload debounce has timed out + SendWeaponAnim( SHOTGUN_PUMP ); + + // play cocking sound + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/scock1.wav", 1, ATTN_NORM, 0, 95 + RANDOM_LONG(0,0x1f)); + m_fInSpecialReload = 0; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.5; + } + } + else + { + int iAnim; + float flRand = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 0, 1 ); + if (flRand <= 0.8) + { + iAnim = SHOTGUN_IDLE_DEEP; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + (60.0/12.0);// * RANDOM_LONG(2, 5); + } + else if (flRand <= 0.95) + { + iAnim = SHOTGUN_IDLE; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + (20.0/9.0); + } + else + { + iAnim = SHOTGUN_IDLE4; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + (20.0/9.0); + } + SendWeaponAnim( iAnim ); + } + } +} + + + +class CShotgunAmmo : public CBasePlayerAmmo +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_shotbox.mdl"); + CBasePlayerAmmo::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_shotbox.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + } + BOOL AddAmmo( CBaseEntity *pOther ) + { + if (pOther->GiveAmmo( AMMO_BUCKSHOTBOX_GIVE, "buckshot", BUCKSHOT_MAX_CARRY ) != -1) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + return TRUE; + } + return FALSE; + } +}; +LINK_ENTITY_TO_CLASS( ammo_buckshot, CShotgunAmmo ); + + diff --git a/hlsdk/dlls/singleplay_gamerules.cpp b/hlsdk/dlls/singleplay_gamerules.cpp new file mode 100644 index 00000000..398b8cc7 --- /dev/null +++ b/hlsdk/dlls/singleplay_gamerules.cpp @@ -0,0 +1,328 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// teamplay_gamerules.cpp +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "skill.h" +#include "items.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 ( g_iSkillLevel == SKILL_EASY ); +} + +//========================================================= +//========================================================= +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/hlsdk/dlls/skill.cpp b/hlsdk/dlls/skill.cpp new file mode 100644 index 00000000..d82a607b --- /dev/null +++ b/hlsdk/dlls/skill.cpp @@ -0,0 +1,47 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// skill.cpp - code for skill level concerns +//========================================================= +#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 +// on, and return the value.of that Cvar +//========================================================= +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; +} + diff --git a/hlsdk/dlls/skill.h b/hlsdk/dlls/skill.h new file mode 100644 index 00000000..5ec320c2 --- /dev/null +++ b/hlsdk/dlls/skill.h @@ -0,0 +1,147 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// skill.h - skill level concerns +//========================================================= + +struct skilldata_t +{ + + int iSkillLevel; // game skill level + +// Monster Health & Damage + float agruntHealth; + float agruntDmgPunch; + + float apacheHealth; + + float barneyHealth; + + float bigmommaHealthFactor; // Multiply each node's health by this + float bigmommaDmgSlash; // melee attack damage + float bigmommaDmgBlast; // mortar attack damage + float bigmommaRadiusBlast; // mortar attack radius + + float bullsquidHealth; + float bullsquidDmgBite; + float bullsquidDmgWhip; + float bullsquidDmgSpit; + + float gargantuaHealth; + float gargantuaDmgSlash; + float gargantuaDmgFire; + float gargantuaDmgStomp; + + float hassassinHealth; + + float headcrabHealth; + float headcrabDmgBite; + + float hgruntHealth; + float hgruntDmgKick; + float hgruntShotgunPellets; + float hgruntGrenadeSpeed; + + float houndeyeHealth; + float houndeyeDmgBlast; + + float slaveHealth; + float slaveDmgClaw; + float slaveDmgClawrake; + float slaveDmgZap; + + float ichthyosaurHealth; + float ichthyosaurDmgShake; + + float leechHealth; + float leechDmgBite; + + float controllerHealth; + float controllerDmgZap; + float controllerSpeedBall; + float controllerDmgBall; + + float nihilanthHealth; + float nihilanthZap; + + float scientistHealth; + + float snarkHealth; + float snarkDmgBite; + float snarkDmgPop; + + float zombieHealth; + float zombieDmgOneSlash; + float zombieDmgBothSlash; + + float turretHealth; + float miniturretHealth; + float sentryHealth; + + +// Player Weapons + float plrDmgCrowbar; + float plrDmg9MM; + float plrDmg357; + float plrDmgMP5; + float plrDmgM203Grenade; + float plrDmgBuckshot; + float plrDmgCrossbowClient; + float plrDmgCrossbowMonster; + float plrDmgRPG; + float plrDmgGauss; + float plrDmgEgonNarrow; + float plrDmgEgonWide; + float plrDmgHornet; + float plrDmgHandGrenade; + float plrDmgSatchel; + float plrDmgTripmine; + +// weapons shared by monsters + float monDmg9MM; + float monDmgMP5; + float monDmg12MM; + float monDmgHornet; + +// health/suit charge + float suitchargerCapacity; + float batteryCapacity; + float healthchargerCapacity; + float healthkitCapacity; + float scientistHeal; + +// monster damage adj + float monHead; + float monChest; + float monStomach; + float monLeg; + float monArm; + +// player damage adj + float plrHead; + float plrChest; + float plrStomach; + float plrLeg; + float plrArm; +}; + +extern DLL_GLOBAL skilldata_t gSkillData; +float GetSkillCvar( char *pName ); + +extern DLL_GLOBAL int g_iSkillLevel; + +#define SKILL_EASY 1 +#define SKILL_MEDIUM 2 +#define SKILL_HARD 3 diff --git a/hlsdk/dlls/sound.cpp b/hlsdk/dlls/sound.cpp new file mode 100644 index 00000000..0492ad62 --- /dev/null +++ b/hlsdk/dlls/sound.cpp @@ -0,0 +1,1978 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// sound.cpp +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "player.h" +#include "talkmonster.h" +#include "gamerules.h" + + +static char *memfgets( byte *pMemFile, int fileSize, int &filePos, char *pBuffer, int bufferSize ); + + +// ==================== GENERIC AMBIENT SOUND ====================================== + +// runtime pitch shift and volume fadein/out structure + +// NOTE: IF YOU CHANGE THIS STRUCT YOU MUST CHANGE THE SAVE/RESTORE VERSION NUMBER +// SEE BELOW (in the typedescription for the class) +typedef struct dynpitchvol +{ + // NOTE: do not change the order of these parameters + // NOTE: unless you also change order of rgdpvpreset array elements! + int preset; + + int pitchrun; // pitch shift % when sound is running 0 - 255 + int pitchstart; // pitch shift % when sound stops or starts 0 - 255 + int spinup; // spinup time 0 - 100 + int spindown; // spindown time 0 - 100 + + int volrun; // volume change % when sound is running 0 - 10 + int volstart; // volume change % when sound stops or starts 0 - 10 + int fadein; // volume fade in time 0 - 100 + int fadeout; // volume fade out time 0 - 100 + + // Low Frequency Oscillator + int lfotype; // 0) off 1) square 2) triangle 3) random + int lforate; // 0 - 1000, how fast lfo osciallates + + int lfomodpitch; // 0-100 mod of current pitch. 0 is off. + int lfomodvol; // 0-100 mod of current volume. 0 is off. + + int cspinup; // each trigger hit increments counter and spinup pitch + + + int cspincount; + + int pitch; + int spinupsav; + int spindownsav; + int pitchfrac; + + int vol; + int fadeinsav; + int fadeoutsav; + int volfrac; + + int lfofrac; + int lfomult; + + +} dynpitchvol_t; + +#define CDPVPRESETMAX 27 + +// presets for runtime pitch and vol modulation of ambient sounds + +dynpitchvol_t rgdpvpreset[CDPVPRESETMAX] = +{ +// pitch pstart spinup spindwn volrun volstrt fadein fadeout lfotype lforate modptch modvol cspnup +{1, 255, 75, 95, 95, 10, 1, 50, 95, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{2, 255, 85, 70, 88, 10, 1, 20, 88, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{3, 255, 100, 50, 75, 10, 1, 10, 75, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{4, 100, 100, 0, 0, 10, 1, 90, 90, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{5, 100, 100, 0, 0, 10, 1, 80, 80, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{6, 100, 100, 0, 0, 10, 1, 50, 70, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{7, 100, 100, 0, 0, 5, 1, 40, 50, 1, 50, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, +{8, 100, 100, 0, 0, 5, 1, 40, 50, 1, 150, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, +{9, 100, 100, 0, 0, 5, 1, 40, 50, 1, 750, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, +{10,128, 100, 50, 75, 10, 1, 30, 40, 2, 8, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{11,128, 100, 50, 75, 10, 1, 30, 40, 2, 25, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{12,128, 100, 50, 75, 10, 1, 30, 40, 2, 70, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{13,50, 50, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{14,70, 70, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{15,90, 90, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{16,120, 120, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{17,180, 180, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{18,255, 255, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{19,200, 75, 90, 90, 10, 1, 50, 90, 2, 100, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{20,255, 75, 97, 90, 10, 1, 50, 90, 1, 40, 50, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{21,100, 100, 0, 0, 10, 1, 30, 50, 3, 15, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{22,160, 160, 0, 0, 10, 1, 50, 50, 3, 500, 25, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{23,255, 75, 88, 0, 10, 1, 40, 0, 0, 0, 0, 0, 5, 0,0,0,0,0,0,0,0,0,0}, +{24,200, 20, 95, 70, 10, 1, 70, 70, 3, 20, 50, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{25,180, 100, 50, 60, 10, 1, 40, 60, 2, 90, 100, 100, 0, 0,0,0,0,0,0,0,0,0,0}, +{26,60, 60, 0, 0, 10, 1, 40, 70, 3, 80, 20, 50, 0, 0,0,0,0,0,0,0,0,0,0}, +{27,128, 90, 10, 10, 10, 1, 20, 40, 1, 5, 10, 20, 0, 0,0,0,0,0,0,0,0,0,0} +}; + +class CAmbientGeneric : public CBaseEntity +{ +public: + void KeyValue( KeyValueData* pkvd); + void Spawn( void ); + void Precache( void ); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT RampThink( void ); + void InitModulationParms(void); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + + float m_flAttenuation; // attenuation value + dynpitchvol_t m_dpv; + + BOOL m_fActive; // only TRUE when the entity is playing a looping sound + BOOL m_fLooping; // TRUE when the sound played will loop +}; + +LINK_ENTITY_TO_CLASS( ambient_generic, CAmbientGeneric ); +TYPEDESCRIPTION CAmbientGeneric::m_SaveData[] = +{ + DEFINE_FIELD( CAmbientGeneric, m_flAttenuation, FIELD_FLOAT ), + DEFINE_FIELD( CAmbientGeneric, m_fActive, FIELD_BOOLEAN ), + DEFINE_FIELD( CAmbientGeneric, m_fLooping, FIELD_BOOLEAN ), + + // HACKHACK - This is not really in the spirit of the save/restore design, but save this + // out as a binary data block. If the dynpitchvol_t is changed, old saved games will NOT + // load these correctly, so bump the save/restore version if you change the size of the struct + // The right way to do this is to split the input parms (read in keyvalue) into members and re-init this + // struct in Precache(), but it's unlikely that the struct will change, so it's not worth the time right now. + DEFINE_ARRAY( CAmbientGeneric, m_dpv, FIELD_CHARACTER, sizeof(dynpitchvol_t) ), +}; + +IMPLEMENT_SAVERESTORE( CAmbientGeneric, CBaseEntity ); + +// +// ambient_generic - general-purpose user-defined static sound +// +void CAmbientGeneric :: Spawn( void ) +{ +/* + -1 : "Default" + 0 : "Everywhere" + 200 : "Small Radius" + 125 : "Medium Radius" + 80 : "Large Radius" +*/ + + if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_EVERYWHERE) ) + { + m_flAttenuation = ATTN_NONE; + } + else if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_SMALLRADIUS) ) + { + m_flAttenuation = ATTN_IDLE; + } + else if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_MEDIUMRADIUS) ) + { + m_flAttenuation = ATTN_STATIC; + } + else if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_LARGERADIUS) ) + { + m_flAttenuation = ATTN_NORM; + } + else + {// if the designer didn't set a sound attenuation, default to one. + m_flAttenuation = ATTN_STATIC; + } + + char* szSoundFile = (char*) STRING(pev->message); + + if ( FStringNull( pev->message ) || strlen( szSoundFile ) < 1 ) + { + ALERT( at_error, "EMPTY AMBIENT AT: %f, %f, %f\n", pev->origin.x, pev->origin.y, pev->origin.z ); + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CBaseEntity::SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + // Set up think function for dynamic modification + // of ambient sound's pitch or volume. Don't + // start thinking yet. + + SetThink(&CAmbientGeneric::RampThink); + pev->nextthink = 0; + + // allow on/off switching via 'use' function. + + SetUse ( &CAmbientGeneric::ToggleUse ); + + m_fActive = FALSE; + + if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_NOT_LOOPING ) ) + m_fLooping = FALSE; + else + m_fLooping = TRUE; + Precache( ); +} + + +void CAmbientGeneric :: Precache( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + + if ( !FStringNull( pev->message ) && strlen( szSoundFile ) > 1 ) + { + if (*szSoundFile != '!') + PRECACHE_SOUND(szSoundFile); + } + // init all dynamic modulation parms + InitModulationParms(); + + if ( !FBitSet (pev->spawnflags, AMBIENT_SOUND_START_SILENT ) ) + { + // start the sound ASAP + if (m_fLooping) + m_fActive = TRUE; + } + if ( m_fActive ) + { + UTIL_EmitAmbientSound ( ENT(pev), pev->origin, szSoundFile, + (m_dpv.vol * 0.01), m_flAttenuation, SND_SPAWNING, m_dpv.pitch); + + pev->nextthink = gpGlobals->time + 0.1; + } +} + +// RampThink - Think at 5hz if we are dynamically modifying +// pitch or volume of the playing sound. This function will +// ramp pitch and/or volume up or down, modify pitch/volume +// with lfo if active. + +void CAmbientGeneric :: RampThink( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + int pitch = m_dpv.pitch; + int vol = m_dpv.vol; + int flags = 0; + int fChanged = 0; // FALSE if pitch and vol remain unchanged this round + int prev; + + if (!m_dpv.spinup && !m_dpv.spindown && !m_dpv.fadein && !m_dpv.fadeout && !m_dpv.lfotype) + return; // no ramps or lfo, stop thinking + + // ============== + // pitch envelope + // ============== + if (m_dpv.spinup || m_dpv.spindown) + { + prev = m_dpv.pitchfrac >> 8; + + if (m_dpv.spinup > 0) + m_dpv.pitchfrac += m_dpv.spinup; + else if (m_dpv.spindown > 0) + m_dpv.pitchfrac -= m_dpv.spindown; + + pitch = m_dpv.pitchfrac >> 8; + + if (pitch > m_dpv.pitchrun) + { + pitch = m_dpv.pitchrun; + m_dpv.spinup = 0; // done with ramp up + } + + if (pitch < m_dpv.pitchstart) + { + pitch = m_dpv.pitchstart; + m_dpv.spindown = 0; // done with ramp down + + // shut sound off + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + + // return without setting nextthink + return; + } + + if (pitch > 255) pitch = 255; + if (pitch < 1) pitch = 1; + + m_dpv.pitch = pitch; + + fChanged |= (prev != pitch); + flags |= SND_CHANGE_PITCH; + } + + // ================== + // amplitude envelope + // ================== + if (m_dpv.fadein || m_dpv.fadeout) + { + prev = m_dpv.volfrac >> 8; + + if (m_dpv.fadein > 0) + m_dpv.volfrac += m_dpv.fadein; + else if (m_dpv.fadeout > 0) + m_dpv.volfrac -= m_dpv.fadeout; + + vol = m_dpv.volfrac >> 8; + + if (vol > m_dpv.volrun) + { + vol = m_dpv.volrun; + m_dpv.fadein = 0; // done with ramp up + } + + if (vol < m_dpv.volstart) + { + vol = m_dpv.volstart; + m_dpv.fadeout = 0; // done with ramp down + + // shut sound off + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + + // return without setting nextthink + return; + } + + if (vol > 100) vol = 100; + if (vol < 1) vol = 1; + + m_dpv.vol = vol; + + fChanged |= (prev != vol); + flags |= SND_CHANGE_VOL; + } + + // =================== + // pitch/amplitude LFO + // =================== + if (m_dpv.lfotype) + { + int pos; + + if (m_dpv.lfofrac > 0x6fffffff) + m_dpv.lfofrac = 0; + + // update lfo, lfofrac/255 makes a triangle wave 0-255 + m_dpv.lfofrac += m_dpv.lforate; + pos = m_dpv.lfofrac >> 8; + + if (m_dpv.lfofrac < 0) + { + m_dpv.lfofrac = 0; + m_dpv.lforate = abs(m_dpv.lforate); + pos = 0; + } + else if (pos > 255) + { + pos = 255; + m_dpv.lfofrac = (255 << 8); + m_dpv.lforate = -abs(m_dpv.lforate); + } + + switch(m_dpv.lfotype) + { + case LFO_SQUARE: + if (pos < 128) + m_dpv.lfomult = 255; + else + m_dpv.lfomult = 0; + + break; + case LFO_RANDOM: + if (pos == 255) + m_dpv.lfomult = RANDOM_LONG(0, 255); + break; + case LFO_TRIANGLE: + default: + m_dpv.lfomult = pos; + break; + } + + if (m_dpv.lfomodpitch) + { + prev = pitch; + + // pitch 0-255 + pitch += ((m_dpv.lfomult - 128) * m_dpv.lfomodpitch) / 100; + + if (pitch > 255) pitch = 255; + if (pitch < 1) pitch = 1; + + + fChanged |= (prev != pitch); + flags |= SND_CHANGE_PITCH; + } + + if (m_dpv.lfomodvol) + { + // vol 0-100 + prev = vol; + + vol += ((m_dpv.lfomult - 128) * m_dpv.lfomodvol) / 100; + + if (vol > 100) vol = 100; + if (vol < 0) vol = 0; + + fChanged |= (prev != vol); + flags |= SND_CHANGE_VOL; + } + + } + + // Send update to playing sound only if we actually changed + // pitch or volume in this routine. + + if (flags && fChanged) + { + if (pitch == PITCH_NORM) + pitch = PITCH_NORM + 1; // don't send 'no pitch' ! + + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + (vol * 0.01), m_flAttenuation, flags, pitch); + } + + // update ramps at 5hz + pev->nextthink = gpGlobals->time + 0.2; + return; +} + +// Init all ramp params in preparation to +// play a new sound + +void CAmbientGeneric :: InitModulationParms(void) +{ + int pitchinc; + + m_dpv.volrun = static_cast(pev->health * 10); // 0 - 100 + if (m_dpv.volrun > 100) m_dpv.volrun = 100; + if (m_dpv.volrun < 0) m_dpv.volrun = 0; + + // get presets + if (m_dpv.preset != 0 && m_dpv.preset <= CDPVPRESETMAX) + { + // load preset values + m_dpv = rgdpvpreset[m_dpv.preset - 1]; + + // fixup preset values, just like + // fixups in KeyValue routine. + if (m_dpv.spindown > 0) + m_dpv.spindown = (101 - m_dpv.spindown) * 64; + if (m_dpv.spinup > 0) + m_dpv.spinup = (101 - m_dpv.spinup) * 64; + + m_dpv.volstart *= 10; + m_dpv.volrun *= 10; + + if (m_dpv.fadein > 0) + m_dpv.fadein = (101 - m_dpv.fadein) * 64; + if (m_dpv.fadeout > 0) + m_dpv.fadeout = (101 - m_dpv.fadeout) * 64; + + m_dpv.lforate *= 256; + + m_dpv.fadeinsav = m_dpv.fadein; + m_dpv.fadeoutsav = m_dpv.fadeout; + m_dpv.spinupsav = m_dpv.spinup; + m_dpv.spindownsav = m_dpv.spindown; + } + + m_dpv.fadein = m_dpv.fadeinsav; + m_dpv.fadeout = 0; + + if (m_dpv.fadein) + m_dpv.vol = m_dpv.volstart; + else + m_dpv.vol = m_dpv.volrun; + + m_dpv.spinup = m_dpv.spinupsav; + m_dpv.spindown = 0; + + if (m_dpv.spinup) + m_dpv.pitch = m_dpv.pitchstart; + else + m_dpv.pitch = m_dpv.pitchrun; + + if (m_dpv.pitch == 0) + m_dpv.pitch = PITCH_NORM; + + m_dpv.pitchfrac = m_dpv.pitch << 8; + m_dpv.volfrac = m_dpv.vol << 8; + + m_dpv.lfofrac = 0; + m_dpv.lforate = abs(m_dpv.lforate); + + m_dpv.cspincount = 1; + + if (m_dpv.cspinup) + { + pitchinc = (255 - m_dpv.pitchstart) / m_dpv.cspinup; + + m_dpv.pitchrun = m_dpv.pitchstart + pitchinc; + if (m_dpv.pitchrun > 255) m_dpv.pitchrun = 255; + } + + if ((m_dpv.spinupsav || m_dpv.spindownsav || (m_dpv.lfotype && m_dpv.lfomodpitch)) + && (m_dpv.pitch == PITCH_NORM)) + m_dpv.pitch = PITCH_NORM + 1; // must never send 'no pitch' as first pitch + // if we intend to pitch shift later! +} + +// +// ToggleUse - turns an ambient sound on or off. If the +// ambient is a looping sound, mark sound as active (m_fActive) +// if it's playing, innactive if not. If the sound is not +// a looping sound, never mark it as active. +// +void CAmbientGeneric :: ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + char* szSoundFile = (char*) STRING(pev->message); + float fraction; + + if ( useType != USE_TOGGLE ) + { + if ( (m_fActive && useType == USE_ON) || (!m_fActive && useType == USE_OFF) ) + return; + } + // Directly change pitch if arg passed. Only works if sound is already playing. + + if (useType == USE_SET && m_fActive) // Momentary buttons will pass down a float in here + { + + fraction = value; + + if ( fraction > 1.0 ) + fraction = 1.0; + if (fraction < 0.0) + fraction = 0.01; + + m_dpv.pitch = static_cast(fraction * 255); + + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_CHANGE_PITCH, m_dpv.pitch); + + return; + } + + // Toggle + + // m_fActive is TRUE only if a looping sound is playing. + + if ( m_fActive ) + {// turn sound off + + if (m_dpv.cspinup) + { + // Don't actually shut off. Each toggle causes + // incremental spinup to max pitch + + if (m_dpv.cspincount <= m_dpv.cspinup) + { + int pitchinc; + + // start a new spinup + m_dpv.cspincount++; + + pitchinc = (255 - m_dpv.pitchstart) / m_dpv.cspinup; + + m_dpv.spinup = m_dpv.spinupsav; + m_dpv.spindown = 0; + + m_dpv.pitchrun = m_dpv.pitchstart + pitchinc * m_dpv.cspincount; + if (m_dpv.pitchrun > 255) m_dpv.pitchrun = 255; + + pev->nextthink = gpGlobals->time + 0.1; + } + + } + else + { + m_fActive = FALSE; + + // HACKHACK - this makes the code in Precache() work properly after a save/restore + pev->spawnflags |= AMBIENT_SOUND_START_SILENT; + + if (m_dpv.spindownsav || m_dpv.fadeoutsav) + { + // spin it down (or fade it) before shutoff if spindown is set + m_dpv.spindown = m_dpv.spindownsav; + m_dpv.spinup = 0; + + m_dpv.fadeout = m_dpv.fadeoutsav; + m_dpv.fadein = 0; + pev->nextthink = gpGlobals->time + 0.1; + } + else + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + } + } + else + {// turn sound on + + // only toggle if this is a looping sound. If not looping, each + // trigger will cause the sound to play. If the sound is still + // playing from a previous trigger press, it will be shut off + // and then restarted. + + if (m_fLooping) + m_fActive = TRUE; + else + // shut sound off now - may be interrupting a long non-looping sound + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + + // init all ramp params for startup + + InitModulationParms(); + + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + (m_dpv.vol * 0.01), m_flAttenuation, 0, m_dpv.pitch); + + pev->nextthink = gpGlobals->time + 0.1; + + } +} +// KeyValue - load keyvalue pairs into member data of the +// ambient generic. NOTE: called BEFORE spawn! + +void CAmbientGeneric :: KeyValue( KeyValueData *pkvd ) +{ + // NOTE: changing any of the modifiers in this code + // NOTE: also requires changing InitModulationParms code. + + // preset + if (FStrEq(pkvd->szKeyName, "preset")) + { + m_dpv.preset = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + + // pitchrun + else if (FStrEq(pkvd->szKeyName, "pitch")) + { + m_dpv.pitchrun = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + if (m_dpv.pitchrun > 255) m_dpv.pitchrun = 255; + if (m_dpv.pitchrun < 0) m_dpv.pitchrun = 0; + } + + // pitchstart + else if (FStrEq(pkvd->szKeyName, "pitchstart")) + { + m_dpv.pitchstart = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + if (m_dpv.pitchstart > 255) m_dpv.pitchstart = 255; + if (m_dpv.pitchstart < 0) m_dpv.pitchstart = 0; + } + + // spinup + else if (FStrEq(pkvd->szKeyName, "spinup")) + { + m_dpv.spinup = atoi(pkvd->szValue); + + if (m_dpv.spinup > 100) m_dpv.spinup = 100; + if (m_dpv.spinup < 0) m_dpv.spinup = 0; + + if (m_dpv.spinup > 0) + m_dpv.spinup = (101 - m_dpv.spinup) * 64; + m_dpv.spinupsav = m_dpv.spinup; + pkvd->fHandled = TRUE; + } + + // spindown + else if (FStrEq(pkvd->szKeyName, "spindown")) + { + m_dpv.spindown = atoi(pkvd->szValue); + + if (m_dpv.spindown > 100) m_dpv.spindown = 100; + if (m_dpv.spindown < 0) m_dpv.spindown = 0; + + if (m_dpv.spindown > 0) + m_dpv.spindown = (101 - m_dpv.spindown) * 64; + m_dpv.spindownsav = m_dpv.spindown; + pkvd->fHandled = TRUE; + } + + // volstart + else if (FStrEq(pkvd->szKeyName, "volstart")) + { + m_dpv.volstart = atoi(pkvd->szValue); + + if (m_dpv.volstart > 10) m_dpv.volstart = 10; + if (m_dpv.volstart < 0) m_dpv.volstart = 0; + + m_dpv.volstart *= 10; // 0 - 100 + + pkvd->fHandled = TRUE; + } + + // fadein + else if (FStrEq(pkvd->szKeyName, "fadein")) + { + m_dpv.fadein = atoi(pkvd->szValue); + + if (m_dpv.fadein > 100) m_dpv.fadein = 100; + if (m_dpv.fadein < 0) m_dpv.fadein = 0; + + if (m_dpv.fadein > 0) + m_dpv.fadein = (101 - m_dpv.fadein) * 64; + m_dpv.fadeinsav = m_dpv.fadein; + pkvd->fHandled = TRUE; + } + + // fadeout + else if (FStrEq(pkvd->szKeyName, "fadeout")) + { + m_dpv.fadeout = atoi(pkvd->szValue); + + if (m_dpv.fadeout > 100) m_dpv.fadeout = 100; + if (m_dpv.fadeout < 0) m_dpv.fadeout = 0; + + if (m_dpv.fadeout > 0) + m_dpv.fadeout = (101 - m_dpv.fadeout) * 64; + m_dpv.fadeoutsav = m_dpv.fadeout; + pkvd->fHandled = TRUE; + } + + // lfotype + else if (FStrEq(pkvd->szKeyName, "lfotype")) + { + m_dpv.lfotype = atoi(pkvd->szValue); + if (m_dpv.lfotype > 4) m_dpv.lfotype = LFO_TRIANGLE; + pkvd->fHandled = TRUE; + } + + // lforate + else if (FStrEq(pkvd->szKeyName, "lforate")) + { + m_dpv.lforate = atoi(pkvd->szValue); + + if (m_dpv.lforate > 1000) m_dpv.lforate = 1000; + if (m_dpv.lforate < 0) m_dpv.lforate = 0; + + m_dpv.lforate *= 256; + + pkvd->fHandled = TRUE; + } + // lfomodpitch + else if (FStrEq(pkvd->szKeyName, "lfomodpitch")) + { + m_dpv.lfomodpitch = atoi(pkvd->szValue); + if (m_dpv.lfomodpitch > 100) m_dpv.lfomodpitch = 100; + if (m_dpv.lfomodpitch < 0) m_dpv.lfomodpitch = 0; + + + pkvd->fHandled = TRUE; + } + + // lfomodvol + else if (FStrEq(pkvd->szKeyName, "lfomodvol")) + { + m_dpv.lfomodvol = atoi(pkvd->szValue); + if (m_dpv.lfomodvol > 100) m_dpv.lfomodvol = 100; + if (m_dpv.lfomodvol < 0) m_dpv.lfomodvol = 0; + + pkvd->fHandled = TRUE; + } + + // cspinup + else if (FStrEq(pkvd->szKeyName, "cspinup")) + { + m_dpv.cspinup = atoi(pkvd->szValue); + if (m_dpv.cspinup > 100) m_dpv.cspinup = 100; + if (m_dpv.cspinup < 0) m_dpv.cspinup = 0; + + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +// =================== ROOM SOUND FX ========================================== + +class CEnvSound : public CPointEntity +{ +public: + void KeyValue( KeyValueData* pkvd); + void Spawn( void ); + + void Think( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + float m_flRadius; + float m_flRoomtype; +}; + +LINK_ENTITY_TO_CLASS( env_sound, CEnvSound ); +TYPEDESCRIPTION CEnvSound::m_SaveData[] = +{ + DEFINE_FIELD( CEnvSound, m_flRadius, FIELD_FLOAT ), + DEFINE_FIELD( CEnvSound, m_flRoomtype, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CEnvSound, CBaseEntity ); + + +void CEnvSound :: KeyValue( KeyValueData *pkvd ) +{ + + if (FStrEq(pkvd->szKeyName, "radius")) + { + m_flRadius = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + if (FStrEq(pkvd->szKeyName, "roomtype")) + { + m_flRoomtype = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } +} + +// returns TRUE if the given sound entity (pev) is in range +// and can see the given player entity (pevTarget) + +BOOL FEnvSoundInRange(entvars_t *pev, entvars_t *pevTarget, float *pflRange) +{ + CEnvSound *pSound = GetClassPtr( (CEnvSound *)pev ); + Vector vecSpot1 = pev->origin + pev->view_ofs; + Vector vecSpot2 = pevTarget->origin + pevTarget->view_ofs; + Vector vecRange; + float flRange; + TraceResult tr; + + UTIL_TraceLine(vecSpot1, vecSpot2, ignore_monsters, ENT(pev), &tr); + + // check if line of sight crosses water boundary, or is blocked + + if ((tr.fInOpen && tr.fInWater) || tr.flFraction != 1) + return FALSE; + + // calc range from sound entity to player + + vecRange = tr.vecEndPos - vecSpot1; + flRange = vecRange.Length(); + + if (pSound->m_flRadius < flRange) + return FALSE; + + if (pflRange) + *pflRange = flRange; + + return TRUE; +} + +// +// A client that is visible and in range of a sound entity will +// have its room_type set by that sound entity. If two or more +// sound entities are contending for a client, then the nearest +// sound entity to the client will set the client's room_type. +// A client's room_type will remain set to its prior value until +// a new in-range, visible sound entity resets a new room_type. +// + +// CONSIDER: if player in water state, autoset roomtype to 14,15 or 16. + +void CEnvSound :: Think( void ) +{ + // get pointer to client if visible; FIND_CLIENT_IN_PVS will + // cycle through visible clients on consecutive calls. + + edict_t *pentPlayer = FIND_CLIENT_IN_PVS(edict()); + CBasePlayer *pPlayer = NULL; + + if (FNullEnt(pentPlayer)) + goto env_sound_Think_slow; // no player in pvs of sound entity, slow it down + + pPlayer = GetClassPtr( (CBasePlayer *)VARS(pentPlayer)); + float flRange; + + // check to see if this is the sound entity that is + // currently affecting this player + + if(!FNullEnt(pPlayer->m_pentSndLast) && (pPlayer->m_pentSndLast == ENT(pev))) { + + // this is the entity currently affecting player, check + // for validity + + if (pPlayer->m_flSndRoomtype != 0 && pPlayer->m_flSndRange != 0) { + + // we're looking at a valid sound entity affecting + // player, make sure it's still valid, update range + + if (FEnvSoundInRange(pev, VARS(pentPlayer), &flRange)) { + pPlayer->m_flSndRange = flRange; + goto env_sound_Think_fast; + } else { + + // current sound entity affecting player is no longer valid, + // flag this state by clearing room_type and range. + // NOTE: we do not actually change the player's room_type + // NOTE: until we have a new valid room_type to change it to. + + pPlayer->m_flSndRange = 0; + pPlayer->m_flSndRoomtype = 0; + goto env_sound_Think_slow; + } + } else { + // entity is affecting player but is out of range, + // wait passively for another entity to usurp it... + goto env_sound_Think_slow; + } + } + + // if we got this far, we're looking at an entity that is contending + // for current player sound. the closest entity to player wins. + + if (FEnvSoundInRange(pev, VARS(pentPlayer), &flRange)) + { + if (flRange < pPlayer->m_flSndRange || pPlayer->m_flSndRange == 0) + { + // new entity is closer to player, so it wins. + pPlayer->m_pentSndLast = ENT(pev); + pPlayer->m_flSndRoomtype = m_flRoomtype; + pPlayer->m_flSndRange = flRange; + + // send room_type command to player's server. + // this should be a rare event - once per change of room_type + // only! + + //CLIENT_COMMAND(pentPlayer, "room_type %f", m_flRoomtype); + + MESSAGE_BEGIN( MSG_ONE, SVC_ROOMTYPE, NULL, pentPlayer ); // use the magic #1 for "one client" + WRITE_SHORT( (short)m_flRoomtype ); // sequence number + MESSAGE_END(); + + // crank up nextthink rate for new active sound entity + // by falling through to think_fast... + } + // player is not closer to the contending sound entity, + // just fall through to think_fast. this effectively + // cranks up the think_rate of entities near the player. + } + + // player is in pvs of sound entity, but either not visible or + // not in range. do nothing, fall through to think_fast... + +env_sound_Think_fast: + pev->nextthink = gpGlobals->time + 0.25; + return; + +env_sound_Think_slow: + pev->nextthink = gpGlobals->time + 0.75; + return; +} + +// +// env_sound - spawn a sound entity that will set player roomtype +// when player moves in range and sight. +// +// +void CEnvSound :: Spawn( ) +{ + // spread think times + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(0.0, 0.5); +} + +// ==================== SENTENCE GROUPS, UTILITY FUNCTIONS ====================================== + +#define CSENTENCE_LRU_MAX 32 // max number of elements per sentence group + +// group of related sentences + +typedef struct sentenceg +{ + char szgroupname[CBSENTENCENAME_MAX]; + int count; + unsigned char rgblru[CSENTENCE_LRU_MAX]; + +} SENTENCEG; + +#define CSENTENCEG_MAX 200 // max number of sentence groups +// globals + +SENTENCEG rgsentenceg[CSENTENCEG_MAX]; +int fSentencesInit = FALSE; + +char gszallsentencenames[CVOXFILESENTENCEMAX][CBSENTENCENAME_MAX]; +int gcallsentences = 0; + +// randomize list of sentence name indices + +void USENTENCEG_InitLRU(unsigned char *plru, int count) +{ + int i, j, k; + unsigned char temp; + + if (!fSentencesInit) + return; + + if (count > CSENTENCE_LRU_MAX) + count = CSENTENCE_LRU_MAX; + + for (i = 0; i < count; i++) + plru[i] = (unsigned char) i; + + // randomize array + for (i = 0; i < (count * 4); i++) + { + j = RANDOM_LONG(0,count-1); + k = RANDOM_LONG(0,count-1); + temp = plru[j]; + plru[j] = plru[k]; + plru[k] = temp; + } +} + +// ignore lru. pick next sentence from sentence group. Go in order until we hit the last sentence, +// then repeat list if freset is true. If freset is false, then repeat last sentence. +// ipick is passed in as the requested sentence ordinal. +// ipick 'next' is returned. +// return of -1 indicates an error. + +int USENTENCEG_PickSequential(int isentenceg, char *szfound, int ipick, int freset) +{ + char *szgroupname; + unsigned char count; + char sznum[8]; + + if (!fSentencesInit) + return -1; + + if (isentenceg < 0) + return -1; + + szgroupname = rgsentenceg[isentenceg].szgroupname; + count = rgsentenceg[isentenceg].count; + + if (count == 0) + return -1; + + if (ipick >= count) + ipick = count-1; + + strcpy(szfound, "!"); + strcat(szfound, szgroupname); + sprintf(sznum, "%d", ipick); + strcat(szfound, sznum); + + if (ipick >= count) + { + if (freset) + // reset at end of list + return 0; + else + return count; + } + + return ipick + 1; +} + + + +// pick a random sentence from rootname0 to rootnameX. +// picks from the rgsentenceg[isentenceg] least +// recently used, modifies lru array. returns the sentencename. +// note, lru must be seeded with 0-n randomized sentence numbers, with the +// rest of the lru filled with -1. The first integer in the lru is +// actually the size of the list. Returns ipick, the ordinal +// of the picked sentence within the group. + +int USENTENCEG_Pick(int isentenceg, char *szfound) +{ + char *szgroupname; + unsigned char *plru; + unsigned char i; + unsigned char count; + char sznum[8]; + unsigned char ipick; + int ffound = FALSE; + + if (!fSentencesInit) + return -1; + + if (isentenceg < 0) + return -1; + + szgroupname = rgsentenceg[isentenceg].szgroupname; + count = rgsentenceg[isentenceg].count; + plru = rgsentenceg[isentenceg].rgblru; + + while (!ffound) + { + for (i = 0; i < count; i++) + if (plru[i] != 0xFF) + { + ipick = plru[i]; + plru[i] = 0xFF; + ffound = TRUE; + break; + } + + if (!ffound) + USENTENCEG_InitLRU(plru, count); + else + { + strcpy(szfound, "!"); + strcat(szfound, szgroupname); + sprintf(sznum, "%d", ipick); + strcat(szfound, sznum); + return ipick; + } + } + return -1; +} + +// ===================== SENTENCE GROUPS, MAIN ROUTINES ======================== + +// Given sentence group rootname (name without number suffix), +// get sentence group index (isentenceg). Returns -1 if no such name. + +int SENTENCEG_GetIndex(const char *szgroupname) +{ + int i; + + if (!fSentencesInit || !szgroupname) + return -1; + + // search rgsentenceg for match on szgroupname + + i = 0; + while (rgsentenceg[i].count) + { + if (!strcmp(szgroupname, rgsentenceg[i].szgroupname)) + return i; + i++; + } + + return -1; +} + +// given sentence group index, play random sentence for given entity. +// returns ipick - which sentence was picked to +// play from the group. Ipick is only needed if you plan on stopping +// the sound before playback is done (see SENTENCEG_Stop). + +int SENTENCEG_PlayRndI(edict_t *entity, int isentenceg, + float volume, float attenuation, int flags, int pitch) +{ + char name[64]; + int ipick; + + if (!fSentencesInit) + return -1; + + name[0] = 0; + + ipick = USENTENCEG_Pick(isentenceg, name); + if (ipick > 0 && name) + EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch); + return ipick; +} + +// same as above, but takes sentence group name instead of index + +int SENTENCEG_PlayRndSz(edict_t *entity, const char *szgroupname, + float volume, float attenuation, int flags, int pitch) +{ + char name[64]; + int ipick; + int isentenceg; + + if (!fSentencesInit) + return -1; + + name[0] = 0; + + isentenceg = SENTENCEG_GetIndex(szgroupname); + if (isentenceg < 0) + { + ALERT( at_console, "No such sentence group %s\n", szgroupname ); + return -1; + } + + ipick = USENTENCEG_Pick(isentenceg, name); + if (ipick >= 0 && name[0]) + EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch); + + return ipick; +} + +// play sentences in sequential order from sentence group. Reset after last sentence. + +int SENTENCEG_PlaySequentialSz(edict_t *entity, const char *szgroupname, + float volume, float attenuation, int flags, int pitch, int ipick, int freset) +{ + char name[64]; + int ipicknext; + int isentenceg; + + if (!fSentencesInit) + return -1; + + name[0] = 0; + + isentenceg = SENTENCEG_GetIndex(szgroupname); + if (isentenceg < 0) + return -1; + + ipicknext = USENTENCEG_PickSequential(isentenceg, name, ipick, freset); + if (ipicknext >= 0 && name[0]) + EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch); + return ipicknext; +} + + +// for this entity, for the given sentence within the sentence group, stop +// the sentence. + +void SENTENCEG_Stop(edict_t *entity, int isentenceg, int ipick) +{ + char buffer[64]; + char sznum[8]; + + if (!fSentencesInit) + return; + + if (isentenceg < 0 || ipick < 0) + return; + + strcpy(buffer, "!"); + strcat(buffer, rgsentenceg[isentenceg].szgroupname); + sprintf(sznum, "%d", ipick); + strcat(buffer, sznum); + + STOP_SOUND(entity, CHAN_VOICE, buffer); +} + +// open sentences.txt, scan for groups, build rgsentenceg +// Should be called from world spawn, only works on the +// first call and is ignored subsequently. + +void SENTENCEG_Init() +{ + char buffer[512]; + char szgroup[64]; + int i, j; + int isentencegs; + + if (fSentencesInit) + return; + + memset(gszallsentencenames, 0, CVOXFILESENTENCEMAX * CBSENTENCENAME_MAX); + gcallsentences = 0; + + memset(rgsentenceg, 0, CSENTENCEG_MAX * sizeof(SENTENCEG)); + memset(buffer, 0, 512); + memset(szgroup, 0, 64); + isentencegs = -1; + + + int filePos = 0, fileSize; + byte *pMemFile = g_engfuncs.pfnLoadFileForMe( "sound/sentences.txt", &fileSize ); + if ( !pMemFile ) + return; + + // for each line in the file... + while ( memfgets(pMemFile, fileSize, filePos, buffer, 511) != NULL ) + { + // skip whitespace + i = 0; + while(buffer[i] && buffer[i] == ' ') + i++; + + if (!buffer[i]) + continue; + + if (buffer[i] == '/' || !isalpha(buffer[i])) + continue; + + // get sentence name + j = i; + while (buffer[j] && buffer[j] != ' ') + j++; + + if (!buffer[j]) + continue; + + if (gcallsentences > CVOXFILESENTENCEMAX) + { + ALERT (at_error, "Too many sentences in sentences.txt!\n"); + break; + } + + // null-terminate name and save in sentences array + buffer[j] = 0; + const char *pString = buffer + i; + + if ( strlen( pString ) >= CBSENTENCENAME_MAX ) + ALERT( at_warning, "Sentence %s longer than %d letters\n", pString, CBSENTENCENAME_MAX-1 ); + + strcpy( gszallsentencenames[gcallsentences++], pString ); + + j--; + if (j <= i) + continue; + if (!isdigit(buffer[j])) + continue; + + // cut out suffix numbers + while (j > i && isdigit(buffer[j])) + j--; + + if (j <= i) + continue; + + buffer[j+1] = 0; + + // if new name doesn't match previous group name, + // make a new group. + + if (strcmp(szgroup, &(buffer[i]))) + { + // name doesn't match with prev name, + // copy name into group, init count to 1 + isentencegs++; + if (isentencegs >= CSENTENCEG_MAX) + { + ALERT (at_error, "Too many sentence groups in sentences.txt!\n"); + break; + } + + strcpy(rgsentenceg[isentencegs].szgroupname, &(buffer[i])); + rgsentenceg[isentencegs].count = 1; + + strcpy(szgroup, &(buffer[i])); + + continue; + } + else + { + //name matches with previous, increment group count + if (isentencegs >= 0) + rgsentenceg[isentencegs].count++; + } + } + + g_engfuncs.pfnFreeFile( pMemFile ); + + fSentencesInit = TRUE; + + // init lru lists + + i = 0; + + while (rgsentenceg[i].count && i < CSENTENCEG_MAX) + { + USENTENCEG_InitLRU(&(rgsentenceg[i].rgblru[0]), rgsentenceg[i].count); + i++; + } + +} + +// convert sentence (sample) name to !sentencenum, return !sentencenum + +int SENTENCEG_Lookup(const char *sample, char *sentencenum) +{ + char sznum[8]; + + int i; + // this is a sentence name; lookup sentence number + // and give to engine as string. + for (i = 0; i < gcallsentences; i++) + if (!stricmp(gszallsentencenames[i], sample+1)) + { + if (sentencenum) + { + strcpy(sentencenum, "!"); + sprintf(sznum, "%d", i); + strcat(sentencenum, sznum); + } + return i; + } + // sentence name not found! + return -1; +} + +void EMIT_SOUND_DYN(edict_t *entity, int channel, const char *sample, float volume, float attenuation, + int flags, int pitch) +{ + if (sample && *sample == '!') + { + char name[32]; + if (SENTENCEG_Lookup(sample, name) >= 0) + EMIT_SOUND_DYN2(entity, channel, name, volume, attenuation, flags, pitch); + else + ALERT( at_aiconsole, "Unable to find %s in sentences.txt\n", sample ); + } + else + 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 +// texture name to a material type. Play footstep sound based +// on material type. + +int fTextureTypeInit = FALSE; + +#define CTEXTURESMAX 512 // max number of textures loaded + +int gcTextures = 0; +char grgszTextureName[CTEXTURESMAX][CBTEXTURENAMEMAX]; // texture names +char grgchTextureType[CTEXTURESMAX]; // parallel array of texture types + +// open materials.txt, get size, alloc space, +// save in array. Only works first time called, +// ignored on subsequent calls. + +static char *memfgets( byte *pMemFile, int fileSize, int &filePos, char *pBuffer, int bufferSize ) +{ + // Bullet-proofing + if ( !pMemFile || !pBuffer ) + return NULL; + + if ( filePos >= fileSize ) + return NULL; + + int i = filePos; + int last = fileSize; + + // fgets always NULL terminates, so only read bufferSize-1 characters + if ( last - filePos > (bufferSize-1) ) + last = filePos + (bufferSize-1); + + int stop = 0; + + // Stop at the next newline (inclusive) or end of buffer + while ( i < last && !stop ) + { + if ( pMemFile[i] == '\n' ) + stop = 1; + i++; + } + + + // If we actually advanced the pointer, copy it over + if ( i != filePos ) + { + // We read in size bytes + int size = i - filePos; + // copy it out + memcpy( pBuffer, pMemFile + filePos, sizeof(byte)*size ); + + // If the buffer isn't full, terminate (this is always true) + if ( size < bufferSize ) + pBuffer[size] = 0; + + // Update file pointer + filePos = i; + return pBuffer; + } + + // No data read, bail + return NULL; +} + + +void TEXTURETYPE_Init() +{ + char buffer[512]; + int i, j; + byte *pMemFile; + int fileSize, filePos; + + 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 ); + if ( !pMemFile ) + return; + + // for each line in the file... + while (memfgets(pMemFile, fileSize, filePos, buffer, 511) != NULL && (gcTextures < CTEXTURESMAX)) + { + // skip whitespace + i = 0; + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // skip comment lines + if (buffer[i] == '/' || !isalpha(buffer[i])) + continue; + + // get texture type + grgchTextureType[gcTextures] = toupper(buffer[i++]); + + // skip whitespace + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // get sentence name + j = i; + while (buffer[j] && !isspace(buffer[j])) + j++; + + if (!buffer[j]) + continue; + + // null-terminate name and save in sentences array + j = min (j, CBTEXTURENAMEMAX-1+i); + buffer[j] = 0; + strcpy(&(grgszTextureName[gcTextures++][0]), &(buffer[i])); + } + + g_engfuncs.pfnFreeFile( pMemFile ); + + fTextureTypeInit = TRUE; +} + +// given texture name, find texture type +// if not found, return type 'concrete' + +// NOTE: this routine should ONLY be called if the +// current texture under the player changes! + +char TEXTURETYPE_Find(char *name) +{ + // CONSIDER: pre-sort texture names and perform faster binary search here + + for (int i = 0; i < gcTextures; i++) + { + if (!strnicmp(name, &(grgszTextureName[i][0]), CBTEXTURENAMEMAX-1)) + return (grgchTextureType[i]); + } + + return CHAR_TEX_CONCRETE; +} + +// 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 + +float TEXTURETYPE_PlaySound(TraceResult *ptr, Vector vecSrc, Vector vecEnd, int iBulletType) +{ +// hit the world, try to play sound based on texture material type + + char chTextureType; + float fvol; + float fvolbar; + char szbuffer[64]; + const char *pTextureName; + float rgfl1[3]; + float rgfl2[3]; + char *rgsz[4]; + int cnt; + float fattn = ATTN_NORM; + + if ( !g_pGameRules->PlayTextureSounds() ) + return 0.0; + + CBaseEntity *pEntity = CBaseEntity::Instance(ptr->pHit); + + chTextureType = 0; + + if (pEntity && pEntity->Classify() != CLASS_NONE && pEntity->Classify() != CLASS_MACHINE) + // hit body + chTextureType = CHAR_TEX_FLESH; + else + { + // hit world + + // find texture under strike, get material type + + // copy trace vector into array for trace_texture + + vecSrc.CopyToArray(rgfl1); + vecEnd.CopyToArray(rgfl2); + + // get texture from entity or world (world is ent(0)) + if (pEntity) + pTextureName = TRACE_TEXTURE( ENT(pEntity->pev), rgfl1, rgfl2 ); + else + pTextureName = TRACE_TEXTURE( ENT(0), rgfl1, rgfl2 ); + + if ( pTextureName ) + { + // strip leading '-0' or '+0~' or '{' or '!' + if (*pTextureName == '-' || *pTextureName == '+') + pTextureName += 2; + + if (*pTextureName == '{' || *pTextureName == '!' || *pTextureName == '~' || *pTextureName == ' ') + pTextureName++; + // '}}' + strcpy(szbuffer, pTextureName); + szbuffer[CBTEXTURENAMEMAX - 1] = 0; + + // ALERT ( at_console, "texture hit: %s\n", szbuffer); + + // get texture type + chTextureType = TEXTURETYPE_Find(szbuffer); + } + } + + switch (chTextureType) + { + default: + case CHAR_TEX_CONCRETE: fvol = 0.9; fvolbar = 0.6; + rgsz[0] = "player/pl_step1.wav"; + rgsz[1] = "player/pl_step2.wav"; + cnt = 2; + break; + case CHAR_TEX_METAL: fvol = 0.9; fvolbar = 0.3; + rgsz[0] = "player/pl_metal1.wav"; + rgsz[1] = "player/pl_metal2.wav"; + cnt = 2; + break; + case CHAR_TEX_DIRT: fvol = 0.9; fvolbar = 0.1; + rgsz[0] = "player/pl_dirt1.wav"; + rgsz[1] = "player/pl_dirt2.wav"; + rgsz[2] = "player/pl_dirt3.wav"; + cnt = 3; + break; + case CHAR_TEX_VENT: fvol = 0.5; fvolbar = 0.3; + rgsz[0] = "player/pl_duct1.wav"; + rgsz[1] = "player/pl_duct1.wav"; + cnt = 2; + break; + case CHAR_TEX_GRATE: fvol = 0.9; fvolbar = 0.5; + rgsz[0] = "player/pl_grate1.wav"; + rgsz[1] = "player/pl_grate4.wav"; + cnt = 2; + break; + case CHAR_TEX_TILE: fvol = 0.8; fvolbar = 0.2; + rgsz[0] = "player/pl_tile1.wav"; + rgsz[1] = "player/pl_tile3.wav"; + rgsz[2] = "player/pl_tile2.wav"; + rgsz[3] = "player/pl_tile4.wav"; + cnt = 4; + break; + case CHAR_TEX_SLOSH: fvol = 0.9; fvolbar = 0.0; + rgsz[0] = "player/pl_slosh1.wav"; + rgsz[1] = "player/pl_slosh3.wav"; + rgsz[2] = "player/pl_slosh2.wav"; + rgsz[3] = "player/pl_slosh4.wav"; + cnt = 4; + break; + case CHAR_TEX_WOOD: fvol = 0.9; fvolbar = 0.2; + rgsz[0] = "debris/wood1.wav"; + rgsz[1] = "debris/wood2.wav"; + rgsz[2] = "debris/wood3.wav"; + cnt = 3; + break; + case CHAR_TEX_GLASS: + case CHAR_TEX_COMPUTER: + fvol = 0.8; fvolbar = 0.2; + rgsz[0] = "debris/glass1.wav"; + rgsz[1] = "debris/glass2.wav"; + rgsz[2] = "debris/glass3.wav"; + 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"; + fattn = 1.0; + cnt = 2; + break; + } + + // did we hit a breakable? + + if (pEntity && FClassnameIs(pEntity->pev, "func_breakable")) + { + // drop volumes, the object will already play a damaged sound + fvol /= 1.5; + fvolbar /= 2.0; + } + else if (chTextureType == CHAR_TEX_COMPUTER) + { + // play random spark if computer + + if ( ptr->flFraction != 1.0 && RANDOM_LONG(0,1)) + { + UTIL_Sparks( ptr->vecEndPos ); + + float flVolume = RANDOM_FLOAT ( 0.7 , 1.0 );//random volume range + switch ( RANDOM_LONG(0,1) ) + { + case 0: UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, "buttons/spark5.wav", flVolume, ATTN_NORM, 0, 100); break; + case 1: UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, "buttons/spark6.wav", flVolume, ATTN_NORM, 0, 100); break; + // case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark5.wav", flVolume, ATTN_NORM); break; + // case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark6.wav", flVolume, ATTN_NORM); break; + } + } + } + + // play material hit sound + UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, rgsz[RANDOM_LONG(0,cnt-1)], fvol, fattn, 0, 96 + RANDOM_LONG(0,0xf)); + //EMIT_SOUND_DYN( ENT(m_pPlayer->pev), CHAN_WEAPON, rgsz[RANDOM_LONG(0,cnt-1)], fvol, ATTN_NORM, 0, 96 + RANDOM_LONG(0,0xf)); + + return fvolbar; +} + +// =================================================================================== +// +// Speaker class. Used for announcements per level, for door lock/unlock spoken voice. +// + +class CSpeaker : public CBaseEntity +{ +public: + void KeyValue( KeyValueData* pkvd); + void Spawn( void ); + void Precache( void ); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT SpeakerThink( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + + int m_preset; // preset number +}; + +LINK_ENTITY_TO_CLASS( speaker, CSpeaker ); +TYPEDESCRIPTION CSpeaker::m_SaveData[] = +{ + DEFINE_FIELD( CSpeaker, m_preset, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CSpeaker, CBaseEntity ); + +// +// ambient_generic - general-purpose user-defined static sound +// +void CSpeaker :: Spawn( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + + if ( !m_preset && (FStringNull( pev->message ) || strlen( szSoundFile ) < 1 )) + { + ALERT( at_error, "SPEAKER with no Level/Sentence! at: %f, %f, %f\n", pev->origin.x, pev->origin.y, pev->origin.z ); + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CBaseEntity::SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + + SetThink(&CSpeaker::SpeakerThink); + pev->nextthink = 0.0; + + // allow on/off switching via 'use' function. + + SetUse ( &CSpeaker::ToggleUse ); + + Precache( ); +} + +#define ANNOUNCE_MINUTES_MIN 0.25 +#define ANNOUNCE_MINUTES_MAX 2.25 + +void CSpeaker :: Precache( void ) +{ + if ( !FBitSet (pev->spawnflags, SPEAKER_START_SILENT ) ) + // set first announcement time for random n second + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(5.0, 15.0); +} +void CSpeaker :: SpeakerThink( void ) +{ + char *szSoundFile = NULL; + float flvolume = pev->health * 0.1; + 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) + { + // go lookup preset text, assign szSoundFile + switch (m_preset) + { + case 1: szSoundFile = "C1A0_"; break; + case 2: szSoundFile = "C1A1_"; break; + case 3: szSoundFile = "C1A2_"; break; + case 4: szSoundFile = "C1A3_"; break; + case 5: szSoundFile = "C1A4_"; break; + case 6: szSoundFile = "C2A1_"; break; + case 7: szSoundFile = "C2A2_"; break; + case 8: szSoundFile = "C2A3_"; break; + case 9: szSoundFile = "C2A4_"; break; + case 10: szSoundFile = "C2A5_"; break; + case 11: szSoundFile = "C3A1_"; break; + case 12: szSoundFile = "C3A2_"; break; + } + } else + szSoundFile = (char*) STRING(pev->message); + + if (szSoundFile[0] == '!') + { + // play single sentence, one shot + UTIL_EmitAmbientSound ( ENT(pev), pev->origin, szSoundFile, + flvolume, flattenuation, flags, pitch); + + // shut off and reset + pev->nextthink = 0.0; + } + else + { + // make random announcement from sentence group + + if (SENTENCEG_PlayRndSz(ENT(pev), szSoundFile, flvolume, flattenuation, flags, pitch) < 0) + ALERT(at_console, "Level Design Error!\nSPEAKER has bad sentence group name: %s\n",szSoundFile); + + // 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; +} + + +// +// ToggleUse - if an announcement is pending, cancel it. If no announcement is pending, start one. +// +void CSpeaker :: ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int fActive = (pev->nextthink > 0.0); + + // fActive is TRUE only if an announcement is pending + + if ( useType != USE_TOGGLE ) + { + // ignore if we're just turning something on that's already on, or + // turning something off that's already off. + if ( (fActive && useType == USE_ON) || (!fActive && useType == USE_OFF) ) + return; + } + + if ( useType == USE_ON ) + { + // turn on announcements + pev->nextthink = gpGlobals->time + 0.1; + return; + } + + if ( useType == USE_OFF ) + { + // turn off announcements + pev->nextthink = 0.0; + return; + + } + + // Toggle announcements + + + if ( fActive ) + { + // turn off announcements + pev->nextthink = 0.0; + } + else + { + // turn on announcements + pev->nextthink = gpGlobals->time + 0.1; + } +} + +// KeyValue - load keyvalue pairs into member data +// NOTE: called BEFORE spawn! + +void CSpeaker :: KeyValue( KeyValueData *pkvd ) +{ + + // preset + if (FStrEq(pkvd->szKeyName, "preset")) + { + m_preset = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} diff --git a/hlsdk/dlls/soundent.cpp b/hlsdk/dlls/soundent.cpp new file mode 100644 index 00000000..ad5ef337 --- /dev/null +++ b/hlsdk/dlls/soundent.cpp @@ -0,0 +1,379 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "soundent.h" + + +LINK_ENTITY_TO_CLASS( soundent, CSoundEnt ); + +CSoundEnt *pSoundEnt; + +//========================================================= +// CSound - Clear - zeros all fields for a sound +//========================================================= +void CSound :: Clear ( void ) +{ + m_vecOrigin = g_vecZero; + m_iType = 0; + m_iVolume = 0; + m_flExpireTime = 0; + m_iNext = SOUNDLIST_EMPTY; + m_iNextAudible = 0; +} + +//========================================================= +// Reset - clears the volume, origin, and type for a sound, +// but doesn't expire or unlink it. +//========================================================= +void CSound :: Reset ( void ) +{ + m_vecOrigin = g_vecZero; + m_iType = 0; + m_iVolume = 0; + m_iNext = SOUNDLIST_EMPTY; +} + +//========================================================= +// FIsSound - returns TRUE if the sound is an Audible sound +//========================================================= +BOOL CSound :: FIsSound ( void ) +{ + if ( m_iType & ( bits_SOUND_COMBAT | bits_SOUND_WORLD | bits_SOUND_PLAYER | bits_SOUND_DANGER ) ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// FIsScent - returns TRUE if the sound is actually a scent +//========================================================= +BOOL CSound :: FIsScent ( void ) +{ + if ( m_iType & ( bits_SOUND_CARCASS | bits_SOUND_MEAT | bits_SOUND_GARBAGE ) ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// Spawn +//========================================================= +void CSoundEnt :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + Initialize(); + + pev->nextthink = gpGlobals->time + 1; +} + +//========================================================= +// Think - at interval, the entire active sound list is checked +// for sounds that have ExpireTimes less than or equal +// to the current world time, and these sounds are deallocated. +//========================================================= +void CSoundEnt :: Think ( void ) +{ + int iSound; + int iPreviousSound; + + pev->nextthink = gpGlobals->time + 0.3;// how often to check the sound list. + + iPreviousSound = SOUNDLIST_EMPTY; + iSound = m_iActiveSound; + + while ( iSound != SOUNDLIST_EMPTY ) + { + if ( m_SoundPool[ iSound ].m_flExpireTime <= gpGlobals->time && m_SoundPool[ iSound ].m_flExpireTime != SOUND_NEVER_EXPIRE ) + { + int iNext = m_SoundPool[ iSound ].m_iNext; + + // move this sound back into the free list + FreeSound( iSound, iPreviousSound ); + + iSound = iNext; + } + else + { + iPreviousSound = iSound; + iSound = m_SoundPool[ iSound ].m_iNext; + } + } + + if ( m_fShowReport ) + { + ALERT ( at_aiconsole, "Soundlist: %d / %d (%d)\n", ISoundsInList( SOUNDLISTTYPE_ACTIVE ),ISoundsInList( SOUNDLISTTYPE_FREE ), ISoundsInList( SOUNDLISTTYPE_ACTIVE ) - m_cLastActiveSounds ); + m_cLastActiveSounds = ISoundsInList ( SOUNDLISTTYPE_ACTIVE ); + } + +} + +//========================================================= +// Precache - dummy function +//========================================================= +void CSoundEnt :: Precache ( void ) +{ +} + +//========================================================= +// FreeSound - clears the passed active sound and moves it +// to the top of the free list. TAKE CARE to only call this +// function for sounds in the Active list!! +//========================================================= +void CSoundEnt :: FreeSound ( int iSound, int iPrevious ) +{ + if ( !pSoundEnt ) + { + // no sound ent! + return; + } + + if ( iPrevious != SOUNDLIST_EMPTY ) + { + // iSound is not the head of the active list, so + // must fix the index for the Previous sound +// pSoundEnt->m_SoundPool[ iPrevious ].m_iNext = m_SoundPool[ iSound ].m_iNext; + pSoundEnt->m_SoundPool[ iPrevious ].m_iNext = pSoundEnt->m_SoundPool[ iSound ].m_iNext; + } + else + { + // the sound we're freeing IS the head of the active list. + pSoundEnt->m_iActiveSound = pSoundEnt->m_SoundPool [ iSound ].m_iNext; + } + + // make iSound the head of the Free list. + pSoundEnt->m_SoundPool[ iSound ].m_iNext = pSoundEnt->m_iFreeSound; + pSoundEnt->m_iFreeSound = iSound; +} + +//========================================================= +// IAllocSound - moves a sound from the Free list to the +// Active list returns the index of the alloc'd sound +//========================================================= +int CSoundEnt :: IAllocSound( void ) +{ + int iNewSound; + + if ( m_iFreeSound == SOUNDLIST_EMPTY ) + { + // no free sound! + ALERT ( at_console, "Free Sound List is full!\n" ); + return SOUNDLIST_EMPTY; + } + + // there is at least one sound available, so move it to the + // Active sound list, and return its SoundPool index. + + iNewSound = m_iFreeSound;// copy the index of the next free sound + + m_iFreeSound = m_SoundPool[ m_iFreeSound ].m_iNext;// move the index down into the free list. + + m_SoundPool[ iNewSound ].m_iNext = m_iActiveSound;// point the new sound at the top of the active list. + + m_iActiveSound = iNewSound;// now make the new sound the top of the active list. You're done. + + return iNewSound; +} + +//========================================================= +// InsertSound - Allocates a free sound and fills it with +// sound info. +//========================================================= +void CSoundEnt :: InsertSound ( int iType, const Vector &vecOrigin, int iVolume, float flDuration ) +{ + int iThisSound; + + if ( !pSoundEnt ) + { + // no sound ent! + return; + } + + iThisSound = pSoundEnt->IAllocSound(); + + if ( iThisSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_console, "Could not AllocSound() for InsertSound() (DLL)\n" ); + return; + } + + pSoundEnt->m_SoundPool[ iThisSound ].m_vecOrigin = vecOrigin; + pSoundEnt->m_SoundPool[ iThisSound ].m_iType = iType; + pSoundEnt->m_SoundPool[ iThisSound ].m_iVolume = iVolume; + pSoundEnt->m_SoundPool[ iThisSound ].m_flExpireTime = gpGlobals->time + flDuration; +} + +//========================================================= +// Initialize - clears all sounds and moves them into the +// free sound list. +//========================================================= +void CSoundEnt :: Initialize ( void ) +{ + int i; + int iSound; + + m_cLastActiveSounds = 0; + m_iFreeSound = 0; + m_iActiveSound = SOUNDLIST_EMPTY; + + for ( i = 0 ; i < MAX_WORLD_SOUNDS ; i++ ) + {// clear all sounds, and link them into the free sound list. + m_SoundPool[ i ].Clear(); + m_SoundPool[ i ].m_iNext = i + 1; + } + + m_SoundPool[ i - 1 ].m_iNext = SOUNDLIST_EMPTY;// terminate the list here. + + + // now reserve enough sounds for each client + for ( i = 0 ; i < gpGlobals->maxClients ; i++ ) + { + iSound = pSoundEnt->IAllocSound(); + + if ( iSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_console, "Could not AllocSound() for Client Reserve! (DLL)\n" ); + return; + } + + pSoundEnt->m_SoundPool[ iSound ].m_flExpireTime = SOUND_NEVER_EXPIRE; + } + + if ( CVAR_GET_FLOAT("displaysoundlist") == 1 ) + { + m_fShowReport = TRUE; + } + else + { + m_fShowReport = FALSE; + } +} + +//========================================================= +// ISoundsInList - returns the number of sounds in the desired +// sound list. +//========================================================= +int CSoundEnt :: ISoundsInList ( int iListType ) +{ + int i; + int iThisSound = 0; + + if ( iListType == SOUNDLISTTYPE_FREE ) + { + iThisSound = m_iFreeSound; + } + else if ( iListType == SOUNDLISTTYPE_ACTIVE ) + { + iThisSound = m_iActiveSound; + } + else + { + ALERT ( at_console, "Unknown Sound List Type!\n" ); + } + + if ( iThisSound == SOUNDLIST_EMPTY ) + { + return 0; + } + + i = 0; + + while ( iThisSound != SOUNDLIST_EMPTY ) + { + i++; + + iThisSound = m_SoundPool[ iThisSound ].m_iNext; + } + + return i; +} + +//========================================================= +// ActiveList - returns the head of the active sound list +//========================================================= +int CSoundEnt :: ActiveList ( void ) +{ + if ( !pSoundEnt ) + { + return SOUNDLIST_EMPTY; + } + + return pSoundEnt->m_iActiveSound; +} + +//========================================================= +// FreeList - returns the head of the free sound list +//========================================================= +int CSoundEnt :: FreeList ( void ) +{ + if ( !pSoundEnt ) + { + return SOUNDLIST_EMPTY; + } + + return pSoundEnt->m_iFreeSound; +} + +//========================================================= +// SoundPointerForIndex - returns a pointer to the instance +// of CSound at index's position in the sound pool. +//========================================================= +CSound* CSoundEnt :: SoundPointerForIndex( int iIndex ) +{ + if ( !pSoundEnt ) + { + return NULL; + } + + if ( iIndex > ( MAX_WORLD_SOUNDS - 1 ) ) + { + ALERT ( at_console, "SoundPointerForIndex() - Index too large!\n" ); + return NULL; + } + + if ( iIndex < 0 ) + { + ALERT ( at_console, "SoundPointerForIndex() - Index < 0!\n" ); + return NULL; + } + + return &pSoundEnt->m_SoundPool[ iIndex ]; +} + +//========================================================= +// Clients are numbered from 1 to MAXCLIENTS, but the client +// reserved sounds in the soundlist are from 0 to MAXCLIENTS - 1, +// so this function ensures that a client gets the proper index +// to his reserved sound in the soundlist. +//========================================================= +int CSoundEnt :: ClientSoundIndex ( edict_t *pClient ) +{ + int iReturn = ENTINDEX( pClient ) - 1; + +#ifdef _DEBUG + if ( iReturn < 0 || iReturn > gpGlobals->maxClients ) + { + ALERT ( at_console, "** ClientSoundIndex returning a bogus value! **\n" ); + } +#endif // _DEBUG + + return iReturn; +} diff --git a/hlsdk/dlls/soundent.h b/hlsdk/dlls/soundent.h new file mode 100644 index 00000000..150daac7 --- /dev/null +++ b/hlsdk/dlls/soundent.h @@ -0,0 +1,95 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// Soundent.h - the entity that spawns when the world +// spawns, and handles the world's active and free sound +// lists. +//========================================================= + +#define MAX_WORLD_SOUNDS 64 // maximum number of sounds handled by the world at one time. + +#define bits_SOUND_NONE 0 +#define bits_SOUND_COMBAT ( 1 << 0 )// gunshots, explosions +#define bits_SOUND_WORLD ( 1 << 1 )// door opening/closing, glass breaking +#define bits_SOUND_PLAYER ( 1 << 2 )// all noises generated by player. walking, shooting, falling, splashing +#define bits_SOUND_CARCASS ( 1 << 3 )// dead body +#define bits_SOUND_MEAT ( 1 << 4 )// gib or pork chop +#define bits_SOUND_DANGER ( 1 << 5 )// pending danger. Grenade that is about to explode, explosive barrel that is damaged, falling crate +#define bits_SOUND_GARBAGE ( 1 << 6 )// trash cans, banana peels, old fast food bags. + +#define bits_ALL_SOUNDS 0xFFFFFFFF + +#define SOUNDLIST_EMPTY -1 + +#define SOUNDLISTTYPE_FREE 1// identifiers passed to functions that can operate on either list, to indicate which list to operate on. +#define SOUNDLISTTYPE_ACTIVE 2 + +#define SOUND_NEVER_EXPIRE -1 // with this set as a sound's ExpireTime, the sound will never expire. + +//========================================================= +// CSound - an instance of a sound in the world. +//========================================================= +class CSound +{ +public: + + void Clear ( void ); + void Reset ( void ); + + Vector m_vecOrigin; // sound's location in space + int m_iType; // what type of sound this is + int m_iVolume; // how loud the sound is + float m_flExpireTime; // when the sound should be purged from the list + int m_iNext; // index of next sound in this list ( Active or Free ) + int m_iNextAudible; // temporary link that monsters use to build a list of audible sounds + + BOOL FIsSound( void ); + BOOL FIsScent( void ); +}; + +//========================================================= +// CSoundEnt - a single instance of this entity spawns when +// the world spawns. The SoundEnt's job is to update the +// world's Free and Active sound lists. +//========================================================= +class CSoundEnt : public CBaseEntity +{ +public: + + void Precache ( void ); + void Spawn( void ); + void Think( void ); + void Initialize ( void ); + + static void InsertSound ( int iType, const Vector &vecOrigin, int iVolume, float flDuration ); + static void FreeSound ( int iSound, int iPrevious ); + static int ActiveList( void );// return the head of the active list + static int FreeList( void );// return the head of the free list + static CSound* SoundPointerForIndex( int iIndex );// return a pointer for this index in the sound list + static int ClientSoundIndex ( edict_t *pClient ); + + BOOL IsEmpty( void ) { return m_iActiveSound == SOUNDLIST_EMPTY; } + int ISoundsInList ( int iListType ); + int IAllocSound ( void ); + virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } + + int m_iFreeSound; // index of the first sound in the free sound list + int m_iActiveSound; // indes of the first sound in the active sound list + int m_cLastActiveSounds; // keeps track of the number of active sounds at the last update. (for diagnostic work) + BOOL m_fShowReport; // if true, dump information about free/active sounds. + +private: + CSound m_SoundPool[ MAX_WORLD_SOUNDS ]; +}; diff --git a/hlsdk/dlls/spectator.cpp b/hlsdk/dlls/spectator.cpp new file mode 100644 index 00000000..5f91731e --- /dev/null +++ b/hlsdk/dlls/spectator.cpp @@ -0,0 +1,149 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// CBaseSpectator + +// YWB: UNDONE + +// Spectator functions +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "spectator.h" + +/* +=========== +SpectatorConnect + +called when a spectator connects to a server +============ +*/ +void CBaseSpectator::SpectatorConnect(void) +{ + pev->flags = FL_SPECTATOR; + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NOCLIP; + + m_pGoalEnt = NULL; +} + +/* +=========== +SpectatorDisconnect + +called when a spectator disconnects from a server +============ +*/ +void CBaseSpectator::SpectatorDisconnect(void) +{ +} + +/* +================ +SpectatorImpulseCommand + +Called by SpectatorThink if the spectator entered an impulse +================ +*/ +void CBaseSpectator::SpectatorImpulseCommand(void) +{ + static edict_t *pGoal = NULL; + edict_t *pPreviousGoal; + edict_t *pCurrentGoal; + BOOL bFound; + + switch (pev->impulse) + { + case 1: + // teleport the spectator to the next spawn point + // note that if the spectator is tracking, this doesn't do + // much + pPreviousGoal = pGoal; + pCurrentGoal = pGoal; + // Start at the current goal, skip the world, and stop if we looped + // back around + + bFound = FALSE; + while (1) + { + pCurrentGoal = FIND_ENTITY_BY_CLASSNAME(pCurrentGoal, "info_player_deathmatch"); + // Looped around, failure + if (pCurrentGoal == pPreviousGoal) + { + ALERT(at_console, "Could not find a spawn spot.\n"); + break; + } + // Found a non-world entity, set success, otherwise, look for the next one. + if (!FNullEnt(pCurrentGoal)) + { + bFound = TRUE; + break; + } + } + + if (!bFound) // Didn't find a good spot. + break; + + pGoal = pCurrentGoal; + UTIL_SetOrigin( pev, pGoal->v.origin ); + pev->angles = pGoal->v.angles; + pev->fixangle = FALSE; + break; + default: + ALERT(at_console, "Unknown spectator impulse\n"); + break; + } + + pev->impulse = 0; +} + +/* +================ +SpectatorThink + +Called every frame after physics are run +================ +*/ +void CBaseSpectator::SpectatorThink(void) +{ + if (!(pev->flags & FL_SPECTATOR)) + { + pev->flags = FL_SPECTATOR; + } + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NOCLIP; + + if (pev->impulse) + SpectatorImpulseCommand(); +} + +/* +=========== +Spawn + + Called when spectator is initialized: + UNDONE: Is this actually being called because spectators are not allocated in normal fashion? +============ +*/ +void CBaseSpectator::Spawn() +{ + pev->flags = FL_SPECTATOR; + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NOCLIP; + + m_pGoalEnt = NULL; +} diff --git a/hlsdk/dlls/spectator.h b/hlsdk/dlls/spectator.h new file mode 100644 index 00000000..2f755d6b --- /dev/null +++ b/hlsdk/dlls/spectator.h @@ -0,0 +1,27 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Spectator.h + +class CBaseSpectator : public CBaseEntity +{ +public: + void Spawn(); + void SpectatorConnect(void); + void SpectatorDisconnect(void); + void SpectatorThink(void); + +private: + void SpectatorImpulseCommand(void); +}; diff --git a/hlsdk/dlls/squeakgrenade.cpp b/hlsdk/dlls/squeakgrenade.cpp new file mode 100644 index 00000000..eb55e679 --- /dev/null +++ b/hlsdk/dlls/squeakgrenade.cpp @@ -0,0 +1,600 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "soundent.h" +#include "gamerules.h" + +enum w_squeak_e { + WSQUEAK_IDLE1 = 0, + WSQUEAK_FIDGET, + WSQUEAK_JUMP, + WSQUEAK_RUN, +}; + +enum squeak_e { + SQUEAK_IDLE1 = 0, + SQUEAK_FIDGETFIT, + SQUEAK_FIDGETNIP, + SQUEAK_DOWN, + SQUEAK_UP, + SQUEAK_THROW +}; + +#ifndef CLIENT_DLL + +class CSqueakGrenade : public CGrenade +{ + void Spawn( void ); + void Precache( void ); + int Classify( void ); + void EXPORT SuperBounceTouch( CBaseEntity *pOther ); + void EXPORT HuntThink( void ); + int BloodColor( void ) { return BLOOD_COLOR_YELLOW; } + void Killed( entvars_t *pevAttacker, int iGib ); + void GibMonster( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + static float m_flNextBounceSoundTime; + + // CBaseEntity *m_pTarget; + float m_flDie; + Vector m_vecTarget; + float m_flNextHunt; + float m_flNextHit; + Vector m_posPrev; + EHANDLE m_hOwner; + int m_iMyClass; +}; + +float CSqueakGrenade::m_flNextBounceSoundTime = 0; + +LINK_ENTITY_TO_CLASS( monster_snark, CSqueakGrenade ); +TYPEDESCRIPTION CSqueakGrenade::m_SaveData[] = +{ + DEFINE_FIELD( CSqueakGrenade, m_flDie, FIELD_TIME ), + DEFINE_FIELD( CSqueakGrenade, m_vecTarget, FIELD_VECTOR ), + DEFINE_FIELD( CSqueakGrenade, m_flNextHunt, FIELD_TIME ), + DEFINE_FIELD( CSqueakGrenade, m_flNextHit, FIELD_TIME ), + DEFINE_FIELD( CSqueakGrenade, m_posPrev, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CSqueakGrenade, m_hOwner, FIELD_EHANDLE ), +}; + +IMPLEMENT_SAVERESTORE( CSqueakGrenade, CGrenade ); + +#define SQUEEK_DETONATE_DELAY 15.0 + +int CSqueakGrenade :: Classify ( void ) +{ + if (m_iMyClass != 0) + return m_iMyClass; // protect against recursion + + if (m_hEnemy != 0) + { + m_iMyClass = CLASS_INSECT; // no one cares about it + switch( m_hEnemy->Classify( ) ) + { + case CLASS_PLAYER: + case CLASS_HUMAN_PASSIVE: + case CLASS_HUMAN_MILITARY: + m_iMyClass = 0; + return CLASS_ALIEN_MILITARY; // barney's get mad, grunts get mad at it + } + m_iMyClass = 0; + } + + return CLASS_ALIEN_BIOWEAPON; +} + +void CSqueakGrenade :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_BOUNCE; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/w_squeak.mdl"); + UTIL_SetSize(pev, Vector( -4, -4, 0), Vector(4, 4, 8)); + UTIL_SetOrigin( pev, pev->origin ); + + SetTouch( &CSqueakGrenade::SuperBounceTouch ); + SetThink( &CSqueakGrenade::HuntThink ); + pev->nextthink = gpGlobals->time + 0.1; + m_flNextHunt = gpGlobals->time + 1E6; + + pev->flags |= FL_MONSTER; + pev->takedamage = DAMAGE_AIM; + pev->health = gSkillData.snarkHealth; + pev->gravity = 0.5; + pev->friction = 0.5; + + pev->dmg = gSkillData.snarkDmgPop; + + m_flDie = gpGlobals->time + SQUEEK_DETONATE_DELAY; + + m_flFieldOfView = 0; // 180 degrees + + if ( pev->owner ) + m_hOwner = Instance( pev->owner ); + + m_flNextBounceSoundTime = gpGlobals->time;// reset each time a snark is spawned. + + pev->sequence = WSQUEAK_RUN; + ResetSequenceInfo( ); +} + +void CSqueakGrenade::Precache( void ) +{ + PRECACHE_MODEL("models/w_squeak.mdl"); + PRECACHE_SOUND("squeek/sqk_blast1.wav"); + PRECACHE_SOUND("common/bodysplat.wav"); + PRECACHE_SOUND("squeek/sqk_die1.wav"); + PRECACHE_SOUND("squeek/sqk_hunt1.wav"); + PRECACHE_SOUND("squeek/sqk_hunt2.wav"); + PRECACHE_SOUND("squeek/sqk_hunt3.wav"); + PRECACHE_SOUND("squeek/sqk_deploy1.wav"); +} + + +void CSqueakGrenade :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->model = iStringNull;// make invisible + SetThink( &CBaseEntity::SUB_Remove ); + SetTouch( NULL ); + pev->nextthink = gpGlobals->time + 0.1; + + // since squeak grenades never leave a body behind, clear out their takedamage now. + // Squeaks do a bit of radius damage when they pop, and that radius damage will + // continue to call this function unless we acknowledge the Squeak's death now. (sjb) + pev->takedamage = DAMAGE_NO; + + // play squeek blast + EMIT_SOUND_DYN(ENT(pev), CHAN_ITEM, "squeek/sqk_blast1.wav", 1, 0.5, 0, PITCH_NORM); + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, SMALL_EXPLOSION_VOLUME, 3.0 ); + + UTIL_BloodDrips( pev->origin, g_vecZero, BloodColor(), 80 ); + + if (m_hOwner != 0) + RadiusDamage ( pev, m_hOwner->pev, pev->dmg, CLASS_NONE, DMG_BLAST ); + else + RadiusDamage ( pev, pev, pev->dmg, CLASS_NONE, DMG_BLAST ); + + // reset owner so death message happens + if (m_hOwner != 0) + pev->owner = m_hOwner->edict(); + + CBaseMonster :: Killed( pevAttacker, GIB_ALWAYS ); +} + +void CSqueakGrenade :: GibMonster( void ) +{ + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "common/bodysplat.wav", 0.75, ATTN_NORM, 0, 200); +} + + + +void CSqueakGrenade::HuntThink( void ) +{ + // ALERT( at_console, "think\n" ); + + if (!IsInWorld()) + { + SetTouch( NULL ); + UTIL_Remove( this ); + return; + } + + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + // explode when ready + if (gpGlobals->time >= m_flDie) + { + g_vecAttackDir = pev->velocity.Normalize( ); + pev->health = -1; + Killed( pev, 0 ); + return; + } + + // float + if (pev->waterlevel != 0) + { + if (pev->movetype == MOVETYPE_BOUNCE) + { + pev->movetype = MOVETYPE_FLY; + } + pev->velocity = pev->velocity * 0.9; + pev->velocity.z += 8.0; + } + else if (pev->movetype == MOVETYPE_FLY) + { + pev->movetype = MOVETYPE_BOUNCE; + } + + // return if not time to hunt + if (m_flNextHunt > gpGlobals->time) + return; + + m_flNextHunt = gpGlobals->time + 2.0; + + Vector vecDir; + TraceResult tr; + + Vector vecFlat = pev->velocity; + vecFlat.z = 0; + vecFlat = vecFlat.Normalize( ); + + UTIL_MakeVectors( pev->angles ); + + if (m_hEnemy == 0 || !m_hEnemy->IsAlive()) + { + // find target, bounce a bit towards it. + Look( 512 ); + m_hEnemy = BestVisibleEnemy( ); + } + + // squeek if it's about time blow up + if ((m_flDie - gpGlobals->time <= 0.5) && (m_flDie - gpGlobals->time >= 0.3)) + { + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "squeek/sqk_die1.wav", 1, ATTN_NORM, 0, 100 + RANDOM_LONG(0,0x3F)); + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 256, 0.25 ); + } + + // higher pitch as squeeker gets closer to detonation time + float flpitch = 155.0 - 60.0 * ((m_flDie - gpGlobals->time) / SQUEEK_DETONATE_DELAY); + if (flpitch < 80) + flpitch = 80; + + if (m_hEnemy != 0) + { + if (FVisible( m_hEnemy )) + { + vecDir = m_hEnemy->EyePosition() - pev->origin; + m_vecTarget = vecDir.Normalize( ); + } + + float flVel = pev->velocity.Length(); + float flAdj = 50.0 / (flVel + 10.0); + + if (flAdj > 1.2) + flAdj = 1.2; + + // ALERT( at_console, "think : enemy\n"); + + // ALERT( at_console, "%.0f %.2f %.2f %.2f\n", flVel, m_vecTarget.x, m_vecTarget.y, m_vecTarget.z ); + + pev->velocity = pev->velocity * flAdj + m_vecTarget * 300; + } + + if (pev->flags & FL_ONGROUND) + { + pev->avelocity = Vector( 0, 0, 0 ); + } + else + { + if (pev->avelocity == Vector( 0, 0, 0)) + { + pev->avelocity.x = RANDOM_FLOAT( -100, 100 ); + pev->avelocity.z = RANDOM_FLOAT( -100, 100 ); + } + } + + if ((pev->origin - m_posPrev).Length() < 1.0) + { + pev->velocity.x = RANDOM_FLOAT( -100, 100 ); + pev->velocity.y = RANDOM_FLOAT( -100, 100 ); + } + m_posPrev = pev->origin; + + pev->angles = UTIL_VecToAngles( pev->velocity ); + pev->angles.z = 0; + pev->angles.x = 0; +} + + +void CSqueakGrenade::SuperBounceTouch( CBaseEntity *pOther ) +{ + float flpitch; + + TraceResult tr = UTIL_GetGlobalTrace( ); + + // don't hit the guy that launched this grenade + if ( pev->owner && pOther->edict() == pev->owner ) + return; + + // at least until we've bounced once + pev->owner = NULL; + + pev->angles.x = 0; + pev->angles.z = 0; + + // avoid bouncing too much + if (m_flNextHit > gpGlobals->time) + return; + + // higher pitch as squeeker gets closer to detonation time + flpitch = 155.0 - 60.0 * ((m_flDie - gpGlobals->time) / SQUEEK_DETONATE_DELAY); + + if ( pOther->pev->takedamage && m_flNextAttack < gpGlobals->time ) + { + // attack! + + // make sure it's me who has touched them + if (tr.pHit == pOther->edict()) + { + // and it's not another squeakgrenade + if (tr.pHit->v.modelindex != pev->modelindex) + { + // ALERT( at_console, "hit enemy\n"); + ClearMultiDamage( ); + pOther->TraceAttack(pev, gSkillData.snarkDmgBite, gpGlobals->v_forward, &tr, DMG_SLASH ); + if (m_hOwner != 0) + ApplyMultiDamage( pev, m_hOwner->pev ); + else + ApplyMultiDamage( pev, pev ); + + pev->dmg += gSkillData.snarkDmgPop; // add more explosion damage + // m_flDie += 2.0; // add more life + + // make bite sound + EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, "squeek/sqk_deploy1.wav", 1.0, ATTN_NORM, 0, (int)flpitch); + m_flNextAttack = gpGlobals->time + 0.5; + } + } + else + { + // ALERT( at_console, "been hit\n"); + } + } + + m_flNextHit = gpGlobals->time + 0.1; + m_flNextHunt = gpGlobals->time; + + if ( g_pGameRules->IsMultiplayer() ) + { + // in multiplayer, we limit how often snarks can make their bounce sounds to prevent overflows. + if ( gpGlobals->time < m_flNextBounceSoundTime ) + { + // too soon! + return; + } + } + + if (!(pev->flags & FL_ONGROUND)) + { + // play bounce sound + float flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.33 ) + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "squeek/sqk_hunt1.wav", 1, ATTN_NORM, 0, (int)flpitch); + else if (flRndSound <= 0.66) + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "squeek/sqk_hunt2.wav", 1, ATTN_NORM, 0, (int)flpitch); + else + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "squeek/sqk_hunt3.wav", 1, ATTN_NORM, 0, (int)flpitch); + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 256, 0.25 ); + } + else + { + // skittering sound + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 100, 0.1 ); + } + + m_flNextBounceSoundTime = gpGlobals->time + 0.5;// half second. +} + +#endif + +LINK_ENTITY_TO_CLASS( weapon_snark, CSqueak ); + + +void CSqueak::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_SNARK; + SET_MODEL(ENT(pev), "models/w_sqknest.mdl"); + + FallInit();//get ready to fall down. + + m_iDefaultAmmo = SNARK_DEFAULT_GIVE; + + pev->sequence = 1; + pev->animtime = gpGlobals->time; + pev->framerate = 1.0; +} + + +void CSqueak::Precache( void ) +{ + PRECACHE_MODEL("models/w_sqknest.mdl"); + PRECACHE_MODEL("models/v_squeak.mdl"); + PRECACHE_MODEL("models/p_squeak.mdl"); + PRECACHE_SOUND("squeek/sqk_hunt2.wav"); + PRECACHE_SOUND("squeek/sqk_hunt3.wav"); + UTIL_PrecacheOther("monster_snark"); + + m_usSnarkFire = PRECACHE_EVENT ( 1, "events/snarkfire.sc" ); +} + + +int CSqueak::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "Snarks"; + p->iMaxAmmo1 = SNARK_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 4; + p->iPosition = 3; + p->iId = m_iId = WEAPON_SNARK; + p->iWeight = SNARK_WEIGHT; + p->iFlags = ITEM_FLAG_LIMITINWORLD | ITEM_FLAG_EXHAUSTIBLE; + + return 1; +} + + + +BOOL CSqueak::Deploy( ) +{ + // play hunt sound + float flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.5 ) + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "squeek/sqk_hunt2.wav", 1, ATTN_NORM, 0, 100); + else + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "squeek/sqk_hunt3.wav", 1, ATTN_NORM, 0, 100); + + m_pPlayer->m_iWeaponVolume = QUIET_GUN_VOLUME; + + return DefaultDeploy( "models/v_squeak.mdl", "models/p_squeak.mdl", SQUEAK_UP, "squeak" ); +} + + +void CSqueak::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + + if ( !m_pPlayer->m_rgAmmo[ m_iPrimaryAmmoType ] ) + { + m_pPlayer->pev->weapons &= ~(1<nextthink = gpGlobals->time + 0.1; + return; + } + + SendWeaponAnim( SQUEAK_DOWN ); + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "common/null.wav", 1.0, ATTN_NORM); +} + + +void CSqueak::PrimaryAttack() +{ + if ( m_pPlayer->m_rgAmmo[ m_iPrimaryAmmoType ] ) + { + UTIL_MakeVectors( m_pPlayer->pev->v_angle ); + TraceResult tr; + Vector trace_origin; + + // HACK HACK: Ugly hacks to handle change in origin based on new physics code for players + // Move origin up if crouched and start trace a bit outside of body ( 20 units instead of 16 ) + trace_origin = m_pPlayer->pev->origin; + if ( m_pPlayer->pev->flags & FL_DUCKING ) + { + trace_origin = trace_origin - ( VEC_HULL_MIN - VEC_DUCK_HULL_MIN ); + } + + // find place to toss monster + UTIL_TraceLine( trace_origin + gpGlobals->v_forward * 20, trace_origin + gpGlobals->v_forward * 64, dont_ignore_monsters, NULL, &tr ); + + int flags; +#ifdef CLIENT_WEAPONS + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usSnarkFire, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0 ); + + if ( tr.fAllSolid == 0 && tr.fStartSolid == 0 && tr.flFraction > 0.25 ) + { + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + +#ifndef CLIENT_DLL + CBaseEntity *pSqueak = CBaseEntity::Create( "monster_snark", tr.vecEndPos, m_pPlayer->pev->v_angle, m_pPlayer->edict() ); + pSqueak->pev->velocity = gpGlobals->v_forward * 200 + m_pPlayer->pev->velocity; +#endif + + // play hunt sound + float flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.5 ) + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "squeek/sqk_hunt2.wav", 1, ATTN_NORM, 0, 105); + else + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "squeek/sqk_hunt3.wav", 1, ATTN_NORM, 0, 105); + + m_pPlayer->m_iWeaponVolume = QUIET_GUN_VOLUME; + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + m_fJustThrown = 1; + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.3; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.0; + } + } +} + + +void CSqueak::SecondaryAttack( void ) +{ + +} + + +void CSqueak::WeaponIdle( void ) +{ + if ( m_flTimeWeaponIdle > UTIL_WeaponTimeBase() ) + return; + + if (m_fJustThrown) + { + m_fJustThrown = 0; + + if ( !m_pPlayer->m_rgAmmo[PrimaryAmmoIndex()] ) + { + RetireWeapon(); + return; + } + + SendWeaponAnim( SQUEAK_UP ); + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + return; + } + + int iAnim; + float flRand = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 0, 1 ); + if (flRand <= 0.75) + { + iAnim = SQUEAK_IDLE1; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 30.0 / 16 * (2); + } + else if (flRand <= 0.875) + { + iAnim = SQUEAK_FIDGETFIT; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 70.0 / 16.0; + } + else + { + iAnim = SQUEAK_FIDGETNIP; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 80.0 / 16.0; + } + SendWeaponAnim( iAnim ); +} + +#endif diff --git a/hlsdk/dlls/subs.cpp b/hlsdk/dlls/subs.cpp new file mode 100644 index 00000000..d3560bd5 --- /dev/null +++ b/hlsdk/dlls/subs.cpp @@ -0,0 +1,559 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== subs.cpp ======================================================== + + frequently used global functions + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "nodes.h" +#include "doors.h" + +extern CGraph WorldGraph; + +extern BOOL FEntIsVisible(entvars_t* pev, entvars_t* pevTarget); + +extern DLL_GLOBAL int g_iSkillLevel; + + +// Landmark class +void CPointEntity :: Spawn( void ) +{ + pev->solid = SOLID_NOT; +// UTIL_SetSize(pev, g_vecZero, g_vecZero); +} + + +class CNullEntity : public CBaseEntity +{ +public: + void Spawn( void ); +}; + + +// Null Entity, remove on startup +void CNullEntity :: Spawn( void ) +{ + REMOVE_ENTITY(ENT(pev)); +} +LINK_ENTITY_TO_CLASS(info_null,CNullEntity); + +class CBaseDMStart : public CPointEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + BOOL IsTriggered( CBaseEntity *pEntity ); + +private: +}; + +// These are the new entry points to entities. +LINK_ENTITY_TO_CLASS(info_player_deathmatch,CBaseDMStart); +LINK_ENTITY_TO_CLASS(info_player_start,CPointEntity); +LINK_ENTITY_TO_CLASS(info_landmark,CPointEntity); + +void CBaseDMStart::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "master")) + { + pev->netname = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +BOOL CBaseDMStart::IsTriggered( CBaseEntity *pEntity ) +{ + BOOL master = UTIL_IsMasterTriggered( pev->netname, pEntity ); + + return master; +} + +// This updates global tables that need to know about entities being removed +void CBaseEntity::UpdateOnRemove( void ) +{ + int i; + + if ( FBitSet( pev->flags, FL_GRAPHED ) ) + { + // this entity was a LinkEnt in the world node graph, so we must remove it from + // the graph since we are removing it from the world. + for ( i = 0 ; i < WorldGraph.m_cLinks ; i++ ) + { + if ( WorldGraph.m_pLinkPool [ i ].m_pLinkEnt == pev ) + { + // if this link has a link ent which is the same ent that is removing itself, remove it! + WorldGraph.m_pLinkPool [ i ].m_pLinkEnt = NULL; + } + } + } + if ( pev->globalname ) + gGlobalState.EntitySetState( pev->globalname, GLOBAL_DEAD ); +} + +// Convenient way to delay removing oneself +void CBaseEntity :: SUB_Remove( void ) +{ + UpdateOnRemove(); + if (pev->health > 0) + { + // this situation can screw up monsters who can't tell their entity pointers are invalid. + pev->health = 0; + ALERT( at_aiconsole, "SUB_Remove called on entity with health > 0\n"); + } + + REMOVE_ENTITY(ENT(pev)); +} + + +// Convenient way to explicitly do nothing (passed to functions that require a method) +void CBaseEntity :: SUB_DoNothing( void ) +{ +} + + +// Global Savedata for Delay +TYPEDESCRIPTION CBaseDelay::m_SaveData[] = +{ + DEFINE_FIELD( CBaseDelay, m_flDelay, FIELD_FLOAT ), + DEFINE_FIELD( CBaseDelay, m_iszKillTarget, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CBaseDelay, CBaseEntity ); + +void CBaseDelay :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "delay")) + { + m_flDelay = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "killtarget")) + { + m_iszKillTarget = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + { + CBaseEntity::KeyValue( pkvd ); + } +} + + +/* +============================== +SUB_UseTargets + +If self.delay is set, a DelayedUse entity will be created that will actually +do the SUB_UseTargets after that many seconds have passed. + +Removes all entities with a targetname that match self.killtarget, +and removes them, so some events can remove other triggers. + +Search for (string)targetname in all entities that +match (string)self.target and call their .use function (if they have one) + +============================== +*/ +void CBaseEntity :: SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ) +{ + // + // fire targets + // + if (!FStringNull(pev->target)) + { + FireTargets( STRING(pev->target), pActivator, this, useType, value ); + } +} + + +void FireTargets( const char *targetName, CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + edict_t *pentTarget = NULL; + if ( !targetName ) + return; + + ALERT( at_aiconsole, "Firing: (%s)\n", targetName ); + + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, targetName); + if (FNullEnt(pentTarget)) + break; + + CBaseEntity *pTarget = CBaseEntity::Instance( pentTarget ); + if ( pTarget && !(pTarget->pev->flags & FL_KILLME) ) // Don't use dying ents + { + ALERT( at_aiconsole, "Found: %s, firing (%s)\n", STRING(pTarget->pev->classname), targetName ); + pTarget->Use( pActivator, pCaller, useType, value ); + } + } +} + +LINK_ENTITY_TO_CLASS( DelayedUse, CBaseDelay ); + + +void CBaseDelay :: SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ) +{ + // + // exit immediatly if we don't have a target or kill target + // + if (FStringNull(pev->target) && !m_iszKillTarget) + return; + + // + // check for a delay + // + if (m_flDelay != 0) + { + // create a temp object to fire at a later time + CBaseDelay *pTemp = GetClassPtr( (CBaseDelay *)NULL); + pTemp->pev->classname = MAKE_STRING("DelayedUse"); + + pTemp->pev->nextthink = gpGlobals->time + m_flDelay; + + pTemp->SetThink( &CBaseDelay::DelayThink ); + + // Save the useType + pTemp->pev->button = (int)useType; + pTemp->m_iszKillTarget = m_iszKillTarget; + pTemp->m_flDelay = 0; // prevent "recursion" + pTemp->pev->target = pev->target; + + // HACKHACK + // This wasn't in the release build of Half-Life. We should have moved m_hActivator into this class + // but changing member variable hierarchy would break save/restore without some ugly code. + // This code is not as ugly as that code + if ( pActivator && pActivator->IsPlayer() ) // If a player activates, then save it + { + pTemp->pev->owner = pActivator->edict(); + } + else + { + pTemp->pev->owner = NULL; + } + + return; + } + + // + // kill the killtargets + // + + if ( m_iszKillTarget ) + { + edict_t *pentKillTarget = NULL; + + ALERT( at_aiconsole, "KillTarget: %s\n", STRING(m_iszKillTarget) ); + pentKillTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_iszKillTarget) ); + while ( !FNullEnt(pentKillTarget) ) + { + UTIL_Remove( CBaseEntity::Instance(pentKillTarget) ); + + ALERT( at_aiconsole, "killing %s\n", STRING( pentKillTarget->v.classname ) ); + pentKillTarget = FIND_ENTITY_BY_TARGETNAME( pentKillTarget, STRING(m_iszKillTarget) ); + } + } + + // + // fire targets + // + if (!FStringNull(pev->target)) + { + FireTargets( STRING(pev->target), pActivator, this, useType, value ); + } +} + + +/* +void CBaseDelay :: SUB_UseTargetsEntMethod( void ) +{ + SUB_UseTargets(pev); +} +*/ + +/* +QuakeEd only writes a single float for angles (bad idea), so up and down are +just constant angles. +*/ +void SetMovedir( entvars_t *pev ) +{ + if (pev->angles == Vector(0, -1, 0)) + { + pev->movedir = Vector(0, 0, 1); + } + else if (pev->angles == Vector(0, -2, 0)) + { + pev->movedir = Vector(0, 0, -1); + } + else + { + UTIL_MakeVectors(pev->angles); + pev->movedir = gpGlobals->v_forward; + } + + pev->angles = g_vecZero; +} + + + + +void CBaseDelay::DelayThink( void ) +{ + CBaseEntity *pActivator = NULL; + + if ( pev->owner != NULL ) // A player activated this on delay + { + pActivator = CBaseEntity::Instance( pev->owner ); + } + // The use type is cached (and stashed) in pev->button + SUB_UseTargets( pActivator, (USE_TYPE)pev->button, 0 ); + REMOVE_ENTITY(ENT(pev)); +} + + +// Global Savedata for Toggle +TYPEDESCRIPTION CBaseToggle::m_SaveData[] = +{ + DEFINE_FIELD( CBaseToggle, m_toggle_state, FIELD_INTEGER ), + DEFINE_FIELD( CBaseToggle, m_flActivateFinished, FIELD_TIME ), + DEFINE_FIELD( CBaseToggle, m_flMoveDistance, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flWait, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flLip, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flTWidth, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flTLength, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_vecPosition1, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_vecPosition2, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_vecAngle1, FIELD_VECTOR ), // UNDONE: Position could go through transition, but also angle? + DEFINE_FIELD( CBaseToggle, m_vecAngle2, FIELD_VECTOR ), // UNDONE: Position could go through transition, but also angle? + DEFINE_FIELD( CBaseToggle, m_cTriggersLeft, FIELD_INTEGER ), + DEFINE_FIELD( CBaseToggle, m_flHeight, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_hActivator, FIELD_EHANDLE ), + DEFINE_FIELD( CBaseToggle, m_pfnCallWhenMoveDone, FIELD_FUNCTION ), + DEFINE_FIELD( CBaseToggle, m_vecFinalDest, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_vecFinalAngle, FIELD_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_sMaster, FIELD_STRING), + DEFINE_FIELD( CBaseToggle, m_bitsDamageInflict, FIELD_INTEGER ), // damage type inflicted +}; +IMPLEMENT_SAVERESTORE( CBaseToggle, CBaseAnimating ); + + +void CBaseToggle::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "lip")) + { + m_flLip = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "master")) + { + m_sMaster = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "distance")) + { + m_flMoveDistance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + +/* +============= +LinearMove + +calculate pev->velocity and pev->nextthink to reach vecDest from +pev->origin traveling at flSpeed +=============== +*/ +void CBaseToggle :: LinearMove( Vector vecDest, float flSpeed ) +{ + ASSERTSZ(flSpeed != 0, "LinearMove: no speed is defined!"); +// ASSERTSZ(m_pfnCallWhenMoveDone != NULL, "LinearMove: no post-move function defined"); + + m_vecFinalDest = vecDest; + + // Already there? + if (vecDest == pev->origin) + { + LinearMoveDone(); + return; + } + + // set destdelta to the vector needed to move + Vector vecDestDelta = vecDest - pev->origin; + + // divide vector length by speed to get time to reach dest + float flTravelTime = vecDestDelta.Length() / flSpeed; + + // set nextthink to trigger a call to LinearMoveDone when dest is reached + pev->nextthink = pev->ltime + flTravelTime; + SetThink( &CBaseToggle::LinearMoveDone ); + + // scale the destdelta vector by the time spent traveling to get velocity + pev->velocity = vecDestDelta / flTravelTime; +} + + +/* +============ +After moving, set origin to exact final destination, call "move done" function +============ +*/ +void CBaseToggle :: LinearMoveDone( void ) +{ + UTIL_SetOrigin(pev, m_vecFinalDest); + pev->velocity = g_vecZero; + pev->nextthink = -1; + if ( m_pfnCallWhenMoveDone ) + (this->*m_pfnCallWhenMoveDone)(); +} + +BOOL CBaseToggle :: IsLockedByMaster( void ) +{ + if (m_sMaster && !UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + return TRUE; + else + return FALSE; +} + +/* +============= +AngularMove + +calculate pev->velocity and pev->nextthink to reach vecDest from +pev->origin traveling at flSpeed +Just like LinearMove, but rotational. +=============== +*/ +void CBaseToggle :: AngularMove( Vector vecDestAngle, float flSpeed ) +{ + ASSERTSZ(flSpeed != 0, "AngularMove: no speed is defined!"); +// ASSERTSZ(m_pfnCallWhenMoveDone != NULL, "AngularMove: no post-move function defined"); + + m_vecFinalAngle = vecDestAngle; + + // Already there? + if (vecDestAngle == pev->angles) + { + AngularMoveDone(); + return; + } + + // set destdelta to the vector needed to move + Vector vecDestDelta = vecDestAngle - pev->angles; + + // divide by speed to get time to reach dest + float flTravelTime = vecDestDelta.Length() / flSpeed; + + // set nextthink to trigger a call to AngularMoveDone when dest is reached + pev->nextthink = pev->ltime + flTravelTime; + SetThink( &CBaseToggle::AngularMoveDone ); + + // scale the destdelta vector by the time spent traveling to get velocity + pev->avelocity = vecDestDelta / flTravelTime; +} + + +/* +============ +After rotating, set angle to exact final angle, call "move done" function +============ +*/ +void CBaseToggle :: AngularMoveDone( void ) +{ + pev->angles = m_vecFinalAngle; + pev->avelocity = g_vecZero; + pev->nextthink = -1; + if ( m_pfnCallWhenMoveDone ) + (this->*m_pfnCallWhenMoveDone)(); +} + + +float CBaseToggle :: AxisValue( int flags, const Vector &angles ) +{ + if ( FBitSet(flags, SF_DOOR_ROTATE_Z) ) + return angles.z; + if ( FBitSet(flags, SF_DOOR_ROTATE_X) ) + return angles.x; + + return angles.y; +} + + +void CBaseToggle :: AxisDir( entvars_t *pev ) +{ + if ( FBitSet(pev->spawnflags, SF_DOOR_ROTATE_Z) ) + pev->movedir = Vector ( 0, 0, 1 ); // around z-axis + else if ( FBitSet(pev->spawnflags, SF_DOOR_ROTATE_X) ) + pev->movedir = Vector ( 1, 0, 0 ); // around x-axis + else + pev->movedir = Vector ( 0, 1, 0 ); // around y-axis +} + + +float CBaseToggle :: AxisDelta( int flags, const Vector &angle1, const Vector &angle2 ) +{ + if ( FBitSet (flags, SF_DOOR_ROTATE_Z) ) + return angle1.z - angle2.z; + + if ( FBitSet (flags, SF_DOOR_ROTATE_X) ) + return angle1.x - angle2.x; + + return angle1.y - angle2.y; +} + + +/* +============= +FEntIsVisible + +returns TRUE if the passed entity is visible to caller, even if not infront () +============= +*/ + BOOL +FEntIsVisible( + entvars_t* pev, + entvars_t* pevTarget) + { + Vector vecSpot1 = pev->origin + pev->view_ofs; + Vector vecSpot2 = pevTarget->origin + pevTarget->view_ofs; + TraceResult tr; + + UTIL_TraceLine(vecSpot1, vecSpot2, ignore_monsters, ENT(pev), &tr); + + if (tr.fInOpen && tr.fInWater) + return FALSE; // sight line crossed contents + + if (tr.flFraction == 1) + return TRUE; + + return FALSE; + } + + diff --git a/hlsdk/dlls/talkmonster.h b/hlsdk/dlls/talkmonster.h new file mode 100644 index 00000000..25c1fc6d --- /dev/null +++ b/hlsdk/dlls/talkmonster.h @@ -0,0 +1,26 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef TALKMONSTER_H +#define TALKMONSTER_H + +class CTalkMonster : public CBaseMonster +{ +public: + static float g_talkWaitTime; + +}; + +#endif //TALKMONSTER_H diff --git a/hlsdk/dlls/teamplay_gamerules.cpp b/hlsdk/dlls/teamplay_gamerules.cpp new file mode 100644 index 00000000..8443fea0 --- /dev/null +++ b/hlsdk/dlls/teamplay_gamerules.cpp @@ -0,0 +1,627 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// 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" + +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 = static_cast(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; + + // 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( 1 ); // 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 ); + 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( static_cast(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><%s><%s>\" joined team \"%s\"\n", + STRING(pPlayer->pev->netname), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( 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 ); + + 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_STRING( "teammate" ); // flag this as a teammate kill + 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] += static_cast(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/hlsdk/dlls/teamplay_gamerules.h b/hlsdk/dlls/teamplay_gamerules.h new file mode 100644 index 00000000..5d4246bd --- /dev/null +++ b/hlsdk/dlls/teamplay_gamerules.h @@ -0,0 +1,57 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// teamplay_gamerules.h +// + +#define MAX_TEAMNAME_LENGTH 16 +#define MAX_TEAMS 32 + +#define TEAMPLAY_TEAMLISTLENGTH MAX_TEAMS*MAX_TEAMNAME_LENGTH + +class CHalfLifeTeamplay : public CHalfLifeMultiplay +{ +public: + CHalfLifeTeamplay(); + + virtual BOOL ClientCommand( CBasePlayer *pPlayer, const char *pcmd ); + virtual void ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ); + virtual BOOL IsTeamplay( void ); + virtual BOOL FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ); + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); + virtual const char *GetTeamID( CBaseEntity *pEntity ); + virtual BOOL ShouldAutoAim( CBasePlayer *pPlayer, edict_t *target ); + 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 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 Think ( void ); + virtual int GetTeamIndex( const char *pTeamName ); + virtual const char *GetIndexedTeamName( int teamIndex ); + virtual BOOL IsValidTeam( const char *pTeamName ); + const char *SetDefaultPlayerTeam( CBasePlayer *pPlayer ); + virtual void ChangePlayerTeam( CBasePlayer *pPlayer, const char *pTeamName, BOOL bKill, BOOL bGib ); + +private: + void RecountTeams( bool bResendInfo = FALSE ); + const char *TeamWithFewestPlayers( void ); + + BOOL m_DisableDeathMessages; + BOOL m_DisableDeathPenalty; + BOOL m_teamLimit; // This means the server set only some teams as valid + char m_szTeamList[TEAMPLAY_TEAMLISTLENGTH]; +}; diff --git a/hlsdk/dlls/trains.h b/hlsdk/dlls/trains.h new file mode 100644 index 00000000..87aec763 --- /dev/null +++ b/hlsdk/dlls/trains.h @@ -0,0 +1,127 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef TRAINS_H +#define TRAINS_H + +// Tracktrain spawn flags +#define SF_TRACKTRAIN_NOPITCH 0x0001 +#define SF_TRACKTRAIN_NOCONTROL 0x0002 +#define SF_TRACKTRAIN_FORWARDONLY 0x0004 +#define SF_TRACKTRAIN_PASSABLE 0x0008 + +// Spawnflag for CPathTrack +#define SF_PATH_DISABLED 0x00000001 +#define SF_PATH_FIREONCE 0x00000002 +#define SF_PATH_ALTREVERSE 0x00000004 +#define SF_PATH_DISABLE_TRAIN 0x00000008 +#define SF_PATH_ALTERNATE 0x00008000 + +// Spawnflags of CPathCorner +#define SF_CORNER_WAITFORTRIG 0x001 +#define SF_CORNER_TELEPORT 0x002 +#define SF_CORNER_FIREONCE 0x004 + +//#define PATH_SPARKLE_DEBUG 1 // This makes a particle effect around path_track entities for debugging +class CPathTrack : public CPointEntity +{ +public: + void Spawn( void ); + void Activate( void ); + void KeyValue( KeyValueData* pkvd); + + void SetPrevious( CPathTrack *pprevious ); + void Link( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + CPathTrack *ValidPath( CPathTrack *ppath, int testFlag ); // Returns ppath if enabled, NULL otherwise + void Project( CPathTrack *pstart, CPathTrack *pend, Vector *origin, float dist ); + + static CPathTrack *Instance( edict_t *pent ); + + CPathTrack *LookAhead( Vector *origin, float dist, int move ); + CPathTrack *Nearest( Vector origin ); + + CPathTrack *GetNext( void ); + CPathTrack *GetPrevious( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; +#if PATH_SPARKLE_DEBUG + void EXPORT Sparkle(void); +#endif + + float m_length; + string_t m_altName; + CPathTrack *m_pnext; + CPathTrack *m_pprevious; + CPathTrack *m_paltpath; +}; + + +class CFuncTrackTrain : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + + void Blocked( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData* pkvd ); + + void EXPORT Next( void ); + void EXPORT Find( void ); + void EXPORT NearestPath( void ); + void EXPORT DeadEnd( void ); + + void NextThink( float thinkTime, BOOL alwaysThink ); + + void SetTrack( CPathTrack *track ) { m_ppath = track->Nearest(pev->origin); } + void SetControls( entvars_t *pevControls ); + BOOL OnControls( entvars_t *pev ); + + void StopSound ( void ); + void UpdateSound ( void ); + + static CFuncTrackTrain *Instance( edict_t *pent ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DIRECTIONAL_USE; } + + virtual void OverrideReset( void ); + + CPathTrack *m_ppath; + float m_length; + float m_height; + float m_speed; + float m_dir; + float m_startSpeed; + Vector m_controlMins; + Vector m_controlMaxs; + int m_soundPlaying; + int m_sounds; + float m_flVolume; + float m_flBank; + float m_oldSpeed; + +private: + unsigned short m_usAdjustPitch; +}; + +#endif diff --git a/hlsdk/dlls/triggers.cpp b/hlsdk/dlls/triggers.cpp new file mode 100644 index 00000000..c5681f26 --- /dev/null +++ b/hlsdk/dlls/triggers.cpp @@ -0,0 +1,2429 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== triggers.cpp ======================================================== + + spawn and use functions for editor-placed triggers + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "saverestore.h" +#include "trains.h" // trigger_camera has train functionality +#include "gamerules.h" + +#define SF_TRIGGER_PUSH_START_OFF 2//spawnflag that makes trigger_push spawn turned OFF +#define SF_TRIGGER_HURT_TARGETONCE 1// Only fire hurt target once +#define SF_TRIGGER_HURT_START_OFF 2//spawnflag that makes trigger_push spawn turned OFF +#define SF_TRIGGER_HURT_NO_CLIENTS 8//spawnflag that makes trigger_push spawn turned OFF +#define SF_TRIGGER_HURT_CLIENTONLYFIRE 16// trigger hurt will only fire its target if it is hurting a client +#define SF_TRIGGER_HURT_CLIENTONLYTOUCH 32// only clients may touch this trigger. + +extern DLL_GLOBAL BOOL g_fGameOver; + +extern void SetMovedir(entvars_t* pev); +extern Vector VecBModelOrigin( entvars_t* pevBModel ); + +class CFrictionModifier : public CBaseEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT ChangeFriction( CBaseEntity *pOther ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + static TYPEDESCRIPTION m_SaveData[]; + + float m_frictionFraction; // Sorry, couldn't resist this name :) +}; + +LINK_ENTITY_TO_CLASS( func_friction, CFrictionModifier ); + +// Global Savedata for changelevel friction modifier +TYPEDESCRIPTION CFrictionModifier::m_SaveData[] = +{ + DEFINE_FIELD( CFrictionModifier, m_frictionFraction, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE(CFrictionModifier,CBaseEntity); + + +// Modify an entity's friction +void CFrictionModifier :: Spawn( void ) +{ + pev->solid = SOLID_TRIGGER; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->movetype = MOVETYPE_NONE; + SetTouch( &CFrictionModifier::ChangeFriction ); +} + + +// Sets toucher's friction to m_frictionFraction (1.0 = normal friction) +void CFrictionModifier :: ChangeFriction( CBaseEntity *pOther ) +{ + if ( pOther->pev->movetype != MOVETYPE_BOUNCEMISSILE && pOther->pev->movetype != MOVETYPE_BOUNCE ) + pOther->pev->friction = m_frictionFraction; +} + + + +// Sets toucher's friction to m_frictionFraction (1.0 = normal friction) +void CFrictionModifier :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "modifier")) + { + m_frictionFraction = atof(pkvd->szValue) / 100.0; + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +// This trigger will fire when the level spawns (or respawns if not fire once) +// It will check a global state before firing. It supports delay and killtargets + +#define SF_AUTO_FIREONCE 0x0001 + +class CAutoTrigger : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Precache( void ); + void Think( void ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_globalstate; + USE_TYPE triggerType; +}; +LINK_ENTITY_TO_CLASS( trigger_auto, CAutoTrigger ); + +TYPEDESCRIPTION CAutoTrigger::m_SaveData[] = +{ + DEFINE_FIELD( CAutoTrigger, m_globalstate, FIELD_STRING ), + DEFINE_FIELD( CAutoTrigger, triggerType, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE(CAutoTrigger,CBaseDelay); + +void CAutoTrigger::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "globalstate")) + { + m_globalstate = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "triggerstate")) + { + int type = atoi( pkvd->szValue ); + switch( type ) + { + case 0: + triggerType = USE_OFF; + break; + case 2: + triggerType = USE_TOGGLE; + break; + default: + triggerType = USE_ON; + break; + } + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + + +void CAutoTrigger::Spawn( void ) +{ + Precache(); +} + + +void CAutoTrigger::Precache( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CAutoTrigger::Think( void ) +{ + if ( !m_globalstate || gGlobalState.EntityGetState( m_globalstate ) == GLOBAL_ON ) + { + SUB_UseTargets( this, triggerType, 0 ); + if ( pev->spawnflags & SF_AUTO_FIREONCE ) + UTIL_Remove( this ); + } +} + + + +#define SF_RELAY_FIREONCE 0x0001 + +class CTriggerRelay : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + USE_TYPE triggerType; +}; +LINK_ENTITY_TO_CLASS( trigger_relay, CTriggerRelay ); + +TYPEDESCRIPTION CTriggerRelay::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerRelay, triggerType, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerRelay,CBaseDelay); + +void CTriggerRelay::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "triggerstate")) + { + int type = atoi( pkvd->szValue ); + switch( type ) + { + case 0: + triggerType = USE_OFF; + break; + case 2: + triggerType = USE_TOGGLE; + break; + default: + triggerType = USE_ON; + break; + } + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + + +void CTriggerRelay::Spawn( void ) +{ +} + + + + +void CTriggerRelay::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SUB_UseTargets( this, triggerType, 0 ); + if ( pev->spawnflags & SF_RELAY_FIREONCE ) + UTIL_Remove( this ); +} + + +//********************************************************** +// The Multimanager Entity - when fired, will fire up to 16 targets +// at specified times. +// FLAG: THREAD (create clones when triggered) +// FLAG: CLONE (this is a clone for a threaded execution) + +#define SF_MULTIMAN_CLONE 0x80000000 +#define SF_MULTIMAN_THREAD 0x00000001 + +class CMultiManager : public CBaseToggle +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn ( void ); + void EXPORT ManagerThink ( void ); + void EXPORT ManagerUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +#if _DEBUG + void EXPORT ManagerReport( void ); +#endif + + BOOL HasTarget( string_t targetname ); + + int ObjectCaps( void ) { return CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_cTargets; // the total number of targets in this manager's fire list. + int m_index; // Current target + float m_startTime;// Time we started firing + int m_iTargetName [ MAX_MULTI_TARGETS ];// list if indexes into global string array + float m_flTargetDelay [ MAX_MULTI_TARGETS ];// delay (in seconds) from time of manager fire to target fire +private: + inline BOOL IsClone( void ) { return (pev->spawnflags & SF_MULTIMAN_CLONE) ? TRUE : FALSE; } + inline BOOL ShouldClone( void ) + { + if ( IsClone() ) + return FALSE; + + return (pev->spawnflags & SF_MULTIMAN_THREAD) ? TRUE : FALSE; + } + + CMultiManager *Clone( void ); +}; +LINK_ENTITY_TO_CLASS( multi_manager, CMultiManager ); + +// Global Savedata for multi_manager +TYPEDESCRIPTION CMultiManager::m_SaveData[] = +{ + DEFINE_FIELD( CMultiManager, m_cTargets, FIELD_INTEGER ), + DEFINE_FIELD( CMultiManager, m_index, FIELD_INTEGER ), + DEFINE_FIELD( CMultiManager, m_startTime, FIELD_TIME ), + DEFINE_ARRAY( CMultiManager, m_iTargetName, FIELD_STRING, MAX_MULTI_TARGETS ), + DEFINE_ARRAY( CMultiManager, m_flTargetDelay, FIELD_FLOAT, MAX_MULTI_TARGETS ), +}; + +IMPLEMENT_SAVERESTORE(CMultiManager,CBaseToggle); + +void CMultiManager :: KeyValue( KeyValueData *pkvd ) +{ + // UNDONE: Maybe this should do something like this: + //CBaseToggle::KeyValue( pkvd ); + // if ( !pkvd->fHandled ) + // ... etc. + + if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else // add this field to the target list + { + // this assumes that additional fields are targetnames and their values are delay values. + if ( m_cTargets < MAX_MULTI_TARGETS ) + { + char tmp[128]; + + UTIL_StripToken( pkvd->szKeyName, tmp ); + m_iTargetName [ m_cTargets ] = ALLOC_STRING( tmp ); + m_flTargetDelay [ m_cTargets ] = atof (pkvd->szValue); + m_cTargets++; + pkvd->fHandled = TRUE; + } + } +} + + +void CMultiManager :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + SetUse ( &CMultiManager::ManagerUse ); + SetThink ( &CMultiManager::ManagerThink); + + // Sort targets + // Quick and dirty bubble sort + int swapped = 1; + + while ( swapped ) + { + swapped = 0; + for ( int i = 1; i < m_cTargets; i++ ) + { + if ( m_flTargetDelay[i] < m_flTargetDelay[i-1] ) + { + // Swap out of order elements + int name = m_iTargetName[i]; + float delay = m_flTargetDelay[i]; + m_iTargetName[i] = m_iTargetName[i-1]; + m_flTargetDelay[i] = m_flTargetDelay[i-1]; + m_iTargetName[i-1] = name; + m_flTargetDelay[i-1] = delay; + swapped = 1; + } + } + } +} + + +BOOL CMultiManager::HasTarget( string_t targetname ) +{ + for ( int i = 0; i < m_cTargets; i++ ) + if ( FStrEq(STRING(targetname), STRING(m_iTargetName[i])) ) + return TRUE; + + return FALSE; +} + + +// Designers were using this to fire targets that may or may not exist -- +// so I changed it to use the standard target fire code, made it a little simpler. +void CMultiManager :: ManagerThink ( void ) +{ + float time; + + time = gpGlobals->time - m_startTime; + while ( m_index < m_cTargets && m_flTargetDelay[ m_index ] <= time ) + { + FireTargets( STRING( m_iTargetName[ m_index ] ), m_hActivator, this, USE_TOGGLE, 0 ); + m_index++; + } + + if ( m_index >= m_cTargets )// have we fired all targets? + { + SetThink( NULL ); + if ( IsClone() ) + { + UTIL_Remove( this ); + return; + } + SetUse ( &CMultiManager::ManagerUse );// allow manager re-use + } + else + pev->nextthink = m_startTime + m_flTargetDelay[ m_index ]; +} + +CMultiManager *CMultiManager::Clone( void ) +{ + CMultiManager *pMulti = GetClassPtr( (CMultiManager *)NULL ); + + edict_t *pEdict = pMulti->pev->pContainingEntity; + memcpy( pMulti->pev, pev, sizeof(*pev) ); + pMulti->pev->pContainingEntity = pEdict; + + pMulti->pev->spawnflags |= SF_MULTIMAN_CLONE; + pMulti->m_cTargets = m_cTargets; + memcpy( pMulti->m_iTargetName, m_iTargetName, sizeof( m_iTargetName ) ); + memcpy( pMulti->m_flTargetDelay, m_flTargetDelay, sizeof( m_flTargetDelay ) ); + + return pMulti; +} + + +// The USE function builds the time table and starts the entity thinking. +void CMultiManager :: ManagerUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // In multiplayer games, clone the MM and execute in the clone (like a thread) + // to allow multiple players to trigger the same multimanager + if ( ShouldClone() ) + { + CMultiManager *pClone = Clone(); + pClone->ManagerUse( pActivator, pCaller, useType, value ); + return; + } + + m_hActivator = pActivator; + m_index = 0; + m_startTime = gpGlobals->time; + + SetUse( NULL );// disable use until all targets have fired + + SetThink ( &CMultiManager::ManagerThink ); + pev->nextthink = gpGlobals->time; +} + +#if _DEBUG +void CMultiManager :: ManagerReport ( void ) +{ + int cIndex; + + for ( cIndex = 0 ; cIndex < m_cTargets ; cIndex++ ) + { + ALERT ( at_console, "%s %f\n", STRING(m_iTargetName[cIndex]), m_flTargetDelay[cIndex] ); + } +} +#endif + +//*********************************************************** + + +// +// Render parameters trigger +// +// This entity will copy its render parameters (renderfx, rendermode, rendercolor, renderamt) +// to its targets when triggered. +// + + +// Flags to indicate masking off various render parameters that are normally copied to the targets +#define SF_RENDER_MASKFX (1<<0) +#define SF_RENDER_MASKAMT (1<<1) +#define SF_RENDER_MASKMODE (1<<2) +#define SF_RENDER_MASKCOLOR (1<<3) + +class CRenderFxManager : public CBaseEntity +{ +public: + void Spawn( void ); + void Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; + +LINK_ENTITY_TO_CLASS( env_render, CRenderFxManager ); + + +void CRenderFxManager :: Spawn ( void ) +{ + pev->solid = SOLID_NOT; +} + +void CRenderFxManager :: Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (!FStringNull(pev->target)) + { + edict_t* pentTarget = NULL; + while ( 1 ) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->target)); + if (FNullEnt(pentTarget)) + break; + + entvars_t *pevTarget = VARS( pentTarget ); + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKFX ) ) + pevTarget->renderfx = pev->renderfx; + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKAMT ) ) + pevTarget->renderamt = pev->renderamt; + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKMODE ) ) + pevTarget->rendermode = pev->rendermode; + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKCOLOR ) ) + pevTarget->rendercolor = pev->rendercolor; + } + } +} + + + +class CBaseTrigger : public CBaseToggle +{ +public: + void EXPORT TeleportTouch ( CBaseEntity *pOther ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT MultiTouch( CBaseEntity *pOther ); + void EXPORT HurtTouch ( CBaseEntity *pOther ); + void EXPORT CDAudioTouch ( CBaseEntity *pOther ); + void ActivateMultiTrigger( CBaseEntity *pActivator ); + void EXPORT MultiWaitOver( void ); + void EXPORT CounterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void InitTrigger( void ); + + virtual int ObjectCaps( void ) { return CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +LINK_ENTITY_TO_CLASS( trigger, CBaseTrigger ); + +/* +================ +InitTrigger +================ +*/ +void CBaseTrigger::InitTrigger( ) +{ + // trigger angles are used for one-way touches. An angle of 0 is assumed + // to mean no restrictions, so use a yaw of 360 instead. + if (pev->angles != g_vecZero) + SetMovedir(pev); + pev->solid = SOLID_TRIGGER; + pev->movetype = MOVETYPE_NONE; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + if ( CVAR_GET_FLOAT("showtriggers") == 0 ) + SetBits( pev->effects, EF_NODRAW ); +} + + +// +// Cache user-entity-field values until spawn is called. +// + +void CBaseTrigger :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "damage")) + { + pev->dmg = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "count")) + { + m_cTriggersLeft = (int) atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damagetype")) + { + m_bitsDamageInflict = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +class CTriggerHurt : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT RadiationThink( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_hurt, CTriggerHurt ); + +// +// trigger_monsterjump +// +class CTriggerMonsterJump : public CBaseTrigger +{ +public: + void Spawn( void ); + void Touch( CBaseEntity *pOther ); + void Think( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_monsterjump, CTriggerMonsterJump ); + + +void CTriggerMonsterJump :: Spawn ( void ) +{ + SetMovedir ( pev ); + + InitTrigger (); + + pev->nextthink = 0; + pev->speed = 200; + m_flHeight = 150; + + if ( !FStringNull ( pev->targetname ) ) + {// if targetted, spawn turned off + pev->solid = SOLID_NOT; + UTIL_SetOrigin( pev, pev->origin ); // Unlink from trigger list + SetUse( &CBaseTrigger::ToggleUse ); + } +} + + +void CTriggerMonsterJump :: Think( void ) +{ + pev->solid = SOLID_NOT;// kill the trigger for now !!!UNDONE + UTIL_SetOrigin( pev, pev->origin ); // Unlink from trigger list + SetThink( NULL ); +} + +void CTriggerMonsterJump :: Touch( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + 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->nextthink = gpGlobals->time; +} + + +//===================================== +// +// trigger_cdaudio - starts/stops cd audio tracks +// +class CTriggerCDAudio : public CBaseTrigger +{ +public: + void Spawn( void ); + + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void PlayTrack( void ); + void Touch ( CBaseEntity *pOther ); +}; + +LINK_ENTITY_TO_CLASS( trigger_cdaudio, CTriggerCDAudio ); + +// +// Changes tracks or stops CD when player touches +// +// !!!HACK - overloaded HEALTH to avoid adding new field +void CTriggerCDAudio :: Touch ( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + {// only clients may trigger these events + return; + } + + PlayTrack(); +} + +void CTriggerCDAudio :: Spawn( void ) +{ + InitTrigger(); +} + +void CTriggerCDAudio::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + PlayTrack(); +} + +void PlayCDTrack( int iTrack ) +{ + edict_t *pClient; + + // manually find the single player. + pClient = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + // Can't play if the client is not connected! + if ( !pClient ) + return; + + if ( iTrack < -1 || iTrack > 30 ) + { + ALERT ( at_console, "TriggerCDAudio - Track %d out of range\n" ); + return; + } + + if ( iTrack == -1 ) + { + CLIENT_COMMAND ( pClient, "cd pause\n"); + } + else + { + char string [ 64 ]; + + sprintf( string, "cd play %3d\n", iTrack ); + CLIENT_COMMAND ( pClient, string); + } +} + + +// only plays for ONE client, so only use in single play! +void CTriggerCDAudio :: PlayTrack( void ) +{ + PlayCDTrack( (int)pev->health ); + + SetTouch( NULL ); + UTIL_Remove( this ); +} + + +// This plays a CD track when fired or when the player enters it's radius +class CTargetCDAudio : public CPointEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Think( void ); + void Play( void ); +}; + +LINK_ENTITY_TO_CLASS( target_cdaudio, CTargetCDAudio ); + +void CTargetCDAudio :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "radius")) + { + pev->scale = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CTargetCDAudio :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + if ( pev->scale > 0 ) + pev->nextthink = gpGlobals->time + 1.0; +} + +void CTargetCDAudio::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + Play(); +} + +// only plays for ONE client, so only use in single play! +void CTargetCDAudio::Think( void ) +{ + edict_t *pClient; + + // manually find the single player. + pClient = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + // Can't play if the client is not connected! + if ( !pClient ) + return; + + pev->nextthink = gpGlobals->time + 0.5; + + if ( (pClient->v.origin - pev->origin).Length() <= pev->scale ) + Play(); + +} + +void CTargetCDAudio::Play( void ) +{ + PlayCDTrack( (int)pev->health ); + UTIL_Remove(this); +} + +//===================================== + +// +// trigger_hurt - hurts anything that touches it. if the trigger has a targetname, firing it will toggle state +// +//int gfToggleState = 0; // used to determine when all radiation trigger hurts have called 'RadiationThink' + +void CTriggerHurt :: Spawn( void ) +{ + InitTrigger(); + SetTouch ( &CBaseTrigger::HurtTouch ); + + if ( !FStringNull ( pev->targetname ) ) + { + SetUse ( &CBaseTrigger::ToggleUse ); + } + else + { + SetUse ( NULL ); + } + + if (m_bitsDamageInflict & DMG_RADIATION) + { + SetThink ( &CTriggerHurt::RadiationThink ); + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(0.0, 0.5); + } + + if ( FBitSet (pev->spawnflags, SF_TRIGGER_HURT_START_OFF) )// if flagged to Start Turned Off, make trigger nonsolid. + pev->solid = SOLID_NOT; + + UTIL_SetOrigin( pev, pev->origin ); // Link into the list +} + +// trigger hurt that causes radiation will do a radius +// check and set the player's geiger counter level +// according to distance from center of trigger + +void CTriggerHurt :: RadiationThink( void ) +{ + + edict_t *pentPlayer; + CBasePlayer *pPlayer = NULL; + float flRange; + entvars_t *pevTarget; + Vector vecSpot1; + Vector vecSpot2; + Vector vecRange; + Vector origin; + Vector view_ofs; + + // check to see if a player is in pvs + // if not, continue + + // set origin to center of trigger so that this check works + origin = pev->origin; + view_ofs = pev->view_ofs; + + pev->origin = (pev->absmin + pev->absmax) * 0.5; + pev->view_ofs = pev->view_ofs * 0.0; + + pentPlayer = FIND_CLIENT_IN_PVS(edict()); + + pev->origin = origin; + pev->view_ofs = view_ofs; + + // reset origin + + if (!FNullEnt(pentPlayer)) + { + + pPlayer = GetClassPtr( (CBasePlayer *)VARS(pentPlayer)); + + pevTarget = VARS(pentPlayer); + + // get range to player; + + vecSpot1 = (pev->absmin + pev->absmax) * 0.5; + vecSpot2 = (pevTarget->absmin + pevTarget->absmax) * 0.5; + + 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; +} + +// +// ToggleUse - If this is the USE function for a trigger, its state will toggle every time it's fired +// +void CBaseTrigger :: ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (pev->solid == SOLID_NOT) + {// if the trigger is off, turn it on + pev->solid = SOLID_TRIGGER; + + // Force retouch + gpGlobals->force_retouch++; + } + else + {// turn the trigger off + pev->solid = SOLID_NOT; + } + UTIL_SetOrigin( pev, pev->origin ); +} + +// When touched, a hurt trigger does DMG points of damage each half-second +void CBaseTrigger :: HurtTouch ( CBaseEntity *pOther ) +{ + float fldmg; + + if ( !pOther->pev->takedamage ) + return; + + if ( (pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYTOUCH) && !pOther->IsPlayer() ) + { + // this trigger is only allowed to touch clients, and this ain't a client. + return; + } + + if ( (pev->spawnflags & SF_TRIGGER_HURT_NO_CLIENTS) && pOther->IsPlayer() ) + return; + + // HACKHACK -- In multiplayer, players touch this based on packet receipt. + // So the players who send packets later aren't always hurt. Keep track of + // how much time has passed and whether or not you've touched that player + if ( g_pGameRules->IsMultiplayer() ) + { + if ( pev->dmgtime > gpGlobals->time ) + { + if ( gpGlobals->time != pev->pain_finished ) + {// too early to hurt again, and not same frame with a different entity + if ( pOther->IsPlayer() ) + { + int playerMask = 1 << (pOther->entindex() - 1); + + // If I've already touched this player (this time), then bail out + if ( pev->impulse & playerMask ) + return; + + // Mark this player as touched + // BUGBUG - There can be only 32 players! + pev->impulse |= playerMask; + } + else + { + return; + } + } + } + else + { + // New clock, "un-touch" all players + pev->impulse = 0; + if ( pOther->IsPlayer() ) + { + int playerMask = 1 << (pOther->entindex() - 1); + + // Mark this player as touched + // BUGBUG - There can be only 32 players! + pev->impulse |= playerMask; + } + } + } + else // Original code -- single player + { + if ( pev->dmgtime > gpGlobals->time && gpGlobals->time != pev->pain_finished ) + {// too early to hurt again, and not same frame with a different entity + return; + } + } + + + + // If this is time_based damage (poison, radiation), override the pev->dmg with a + // default for the given damage type. Monsters only take time-based damage + // while touching the trigger. Player continues taking damage for a while after + // leaving the trigger + + fldmg = pev->dmg * 0.5; // 0.5 seconds worth of damage, pev->dmg is damage/second + + + // JAY: Cut this because it wasn't fully realized. Damage is simpler now. +#if 0 + switch (m_bitsDamageInflict) + { + default: break; + case DMG_POISON: fldmg = POISON_DAMAGE/4; break; + case DMG_NERVEGAS: fldmg = NERVEGAS_DAMAGE/4; break; + case DMG_RADIATION: fldmg = RADIATION_DAMAGE/4; break; + case DMG_PARALYZE: fldmg = PARALYZE_DAMAGE/4; break; // UNDONE: cut this? should slow movement to 50% + case DMG_ACID: fldmg = ACID_DAMAGE/4; break; + case DMG_SLOWBURN: fldmg = SLOWBURN_DAMAGE/4; break; + case DMG_SLOWFREEZE: fldmg = SLOWFREEZE_DAMAGE/4; break; + } +#endif + + if ( fldmg < 0 ) + pOther->TakeHealth( -fldmg, m_bitsDamageInflict ); + else + pOther->TakeDamage( pev, pev, fldmg, m_bitsDamageInflict ); + + // Store pain time so we can get all of the other entities on this frame + pev->pain_finished = gpGlobals->time; + + // Apply damage every half second + pev->dmgtime = gpGlobals->time + 0.5;// half second delay until this trigger can hurt toucher again + + + + if ( pev->target ) + { + // trigger has a target it wants to fire. + if ( pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYFIRE ) + { + // if the toucher isn't a client, don't fire the target! + if ( !pOther->IsPlayer() ) + { + return; + } + } + + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); + if ( pev->spawnflags & SF_TRIGGER_HURT_TARGETONCE ) + pev->target = 0; + } +} + + +/*QUAKED trigger_multiple (.5 .5 .5) ? notouch +Variable sized repeatable trigger. Must be targeted at one or more entities. +If "health" is set, the trigger must be killed to activate each time. +If "delay" is set, the trigger waits some time after activating before firing. +"wait" : Seconds between triggerings. (.2 default) +If notouch is set, the trigger is only fired by other entities, not by touching. +NOTOUCH has been obsoleted by trigger_relay! +sounds +1) secret +2) beep beep +3) large switch +4) +NEW +if a trigger has a NETNAME, that NETNAME will become the TARGET of the triggered object. +*/ +class CTriggerMultiple : public CBaseTrigger +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_multiple, CTriggerMultiple ); + + +void CTriggerMultiple :: Spawn( void ) +{ + if (m_flWait == 0) + m_flWait = 0.2; + + InitTrigger(); + + ASSERTSZ(pev->health == 0, "trigger_multiple with health"); +// UTIL_SetOrigin(pev, pev->origin); +// SET_MODEL( ENT(pev), STRING(pev->model) ); +// if (pev->health > 0) +// { +// if (FBitSet(pev->spawnflags, SPAWNFLAG_NOTOUCH)) +// ALERT(at_error, "trigger_multiple spawn: health and notouch don't make sense"); +// pev->max_health = pev->health; +//UNDONE: where to get pfnDie from? +// pev->pfnDie = multi_killed; +// pev->takedamage = DAMAGE_YES; +// pev->solid = SOLID_BBOX; +// UTIL_SetOrigin(pev, pev->origin); // make sure it links into the world +// } +// else + { + SetTouch( &CBaseTrigger::MultiTouch ); + } + } + + +/*QUAKED trigger_once (.5 .5 .5) ? notouch +Variable sized trigger. Triggers once, then removes itself. You must set the key "target" to the name of another object in the level that has a matching +"targetname". If "health" is set, the trigger must be killed to activate. +If notouch is set, the trigger is only fired by other entities, not by touching. +if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired. +if "angle" is set, the trigger will only fire when someone is facing the direction of the angle. Use "360" for an angle of 0. +sounds +1) secret +2) beep beep +3) large switch +4) +*/ +class CTriggerOnce : public CTriggerMultiple +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_once, CTriggerOnce ); +void CTriggerOnce::Spawn( void ) +{ + m_flWait = -1; + + CTriggerMultiple :: Spawn(); +} + + + +void CBaseTrigger :: MultiTouch( CBaseEntity *pOther ) +{ + entvars_t *pevToucher; + + pevToucher = pOther->pev; + + // Only touch clients, monsters, or pushables (depending on flags) + if ( ((pevToucher->flags & FL_CLIENT) && !(pev->spawnflags & SF_TRIGGER_NOCLIENTS)) || + ((pevToucher->flags & FL_MONSTER) && (pev->spawnflags & SF_TRIGGER_ALLOWMONSTERS)) || + (pev->spawnflags & SF_TRIGGER_PUSHABLES) && FClassnameIs(pevToucher,"func_pushable") ) + { + +#if 0 + // if the trigger has an angles field, check player's facing direction + if (pev->movedir != g_vecZero) + { + UTIL_MakeVectors( pevToucher->angles ); + if ( DotProduct( gpGlobals->v_forward, pev->movedir ) < 0 ) + return; // not facing the right way + } +#endif + + ActivateMultiTrigger( pOther ); + } +} + + +// +// the trigger was just touched/killed/used +// self.enemy should be set to the activator so it can be held through a delay +// so wait for the delay time before firing +// +void CBaseTrigger :: ActivateMultiTrigger( CBaseEntity *pActivator ) +{ + if (pev->nextthink > gpGlobals->time) + return; // still waiting for reset time + + if (!UTIL_IsMasterTriggered(m_sMaster,pActivator)) + return; + + if (FClassnameIs(pev, "trigger_secret")) + { + if ( pev->enemy == NULL || !FClassnameIs(pev->enemy, "player")) + return; + gpGlobals->found_secrets++; + } + + if (!FStringNull(pev->noise)) + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + +// don't trigger again until reset +// pev->takedamage = DAMAGE_NO; + + m_hActivator = pActivator; + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + + if ( pev->message && pActivator->IsPlayer() ) + { + UTIL_ShowMessage( STRING(pev->message), pActivator ); +// CLIENT_PRINTF( ENT( pActivator->pev ), print_center, STRING(pev->message) ); + } + + if (m_flWait > 0) + { + SetThink( &CBaseTrigger::MultiWaitOver ); + pev->nextthink = gpGlobals->time + m_flWait; + } + else + { + // we can't just remove (self) here, because this is a touch function + // called while C code is looping through area links... + SetTouch( NULL ); + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CBaseEntity::SUB_Remove ); + } +} + + +// the wait time has passed, so set back up for another activation +void CBaseTrigger :: MultiWaitOver( void ) +{ +// if (pev->max_health) +// { +// pev->health = pev->max_health; +// pev->takedamage = DAMAGE_YES; +// pev->solid = SOLID_BBOX; +// } + SetThink( NULL ); +} + + +// ========================= COUNTING TRIGGER ===================================== + +// +// GLOBALS ASSUMED SET: g_eoActivator +// +void CBaseTrigger::CounterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_cTriggersLeft--; + m_hActivator = pActivator; + + if (m_cTriggersLeft < 0) + return; + + BOOL fTellActivator = + (m_hActivator != 0) && + FClassnameIs(m_hActivator->pev, "player") && + !FBitSet(pev->spawnflags, SPAWNFLAG_NOMESSAGE); + if (m_cTriggersLeft != 0) + { + if (fTellActivator) + { + // UNDONE: I don't think we want these Quakesque messages + switch (m_cTriggersLeft) + { + case 1: ALERT(at_console, "Only 1 more to go..."); break; + case 2: ALERT(at_console, "Only 2 more to go..."); break; + case 3: ALERT(at_console, "Only 3 more to go..."); break; + default: ALERT(at_console, "There are more to go..."); break; + } + } + return; + } + + // !!!UNDONE: I don't think we want these Quakesque messages + if (fTellActivator) + ALERT(at_console, "Sequence completed!"); + + ActivateMultiTrigger( m_hActivator ); +} + + +/*QUAKED trigger_counter (.5 .5 .5) ? nomessage +Acts as an intermediary for an action that takes multiple inputs. +If nomessage is not set, it will print "1 more.. " etc when triggered and +"sequence complete" when finished. After the counter has been triggered "cTriggersLeft" +times (default 2), it will fire all of it's targets and remove itself. +*/ +class CTriggerCounter : public CBaseTrigger +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS( trigger_counter, CTriggerCounter ); + +void CTriggerCounter :: Spawn( void ) +{ + // By making the flWait be -1, this counter-trigger will disappear after it's activated + // (but of course it needs cTriggersLeft "uses" before that happens). + m_flWait = -1; + + if (m_cTriggersLeft == 0) + m_cTriggersLeft = 2; + SetUse( &CBaseTrigger::CounterUse ); +} + +// ====================== TRIGGER_CHANGELEVEL ================================ + +class CTriggerVolume : public CPointEntity // Derive from point entity so this doesn't move across levels +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_transition, CTriggerVolume ); + +// Define space that travels across a level transition +void CTriggerVolume :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->model = 0; + pev->modelindex = 0; +} + + +// Fires a target after level transition and then dies +class CFireAndDie : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void Think( void ); + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() | FCAP_FORCE_TRANSITION; } // Always go across transitions +}; +LINK_ENTITY_TO_CLASS( fireanddie, CFireAndDie ); + +void CFireAndDie::Spawn( void ) +{ + pev->classname = MAKE_STRING("fireanddie"); + // Don't call Precache() - it should be called on restore +} + + +void CFireAndDie::Precache( void ) +{ + // This gets called on restore + pev->nextthink = gpGlobals->time + m_flDelay; +} + + +void CFireAndDie::Think( void ) +{ + SUB_UseTargets( this, USE_TOGGLE, 0 ); + UTIL_Remove( this ); +} + + +#define SF_CHANGELEVEL_USEONLY 0x0002 +class CChangeLevel : public CBaseTrigger +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT UseChangeLevel ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT TriggerChangeLevel( void ); + void EXPORT ExecuteChangeLevel( void ); + void EXPORT TouchChangeLevel( CBaseEntity *pOther ); + void ChangeLevelNow( CBaseEntity *pActivator ); + + static edict_t *FindLandmark( const char *pLandmarkName ); + static int ChangeList( LEVELLIST *pLevelList, int maxList ); + static int AddTransitionToList( LEVELLIST *pLevelList, int listCount, const char *pMapName, const char *pLandmarkName, edict_t *pentLandmark ); + static int InTransitionVolume( CBaseEntity *pEntity, char *pVolumeName ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + char m_szMapName[cchMapNameMost]; // trigger_changelevel only: next map + char m_szLandmarkName[cchMapNameMost]; // trigger_changelevel only: landmark on next map + int m_changeTarget; + float m_changeTargetDelay; +}; +LINK_ENTITY_TO_CLASS( trigger_changelevel, CChangeLevel ); + +// Global Savedata for changelevel trigger +TYPEDESCRIPTION CChangeLevel::m_SaveData[] = +{ + DEFINE_ARRAY( CChangeLevel, m_szMapName, FIELD_CHARACTER, cchMapNameMost ), + DEFINE_ARRAY( CChangeLevel, m_szLandmarkName, FIELD_CHARACTER, cchMapNameMost ), + DEFINE_FIELD( CChangeLevel, m_changeTarget, FIELD_STRING ), + DEFINE_FIELD( CChangeLevel, m_changeTargetDelay, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE(CChangeLevel,CBaseTrigger); + +// +// Cache user-entity-field values until spawn is called. +// + +void CChangeLevel :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "map")) + { + if (strlen(pkvd->szValue) >= cchMapNameMost) + ALERT( at_error, "Map name '%s' too long (32 chars)\n", pkvd->szValue ); + strcpy(m_szMapName, pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "landmark")) + { + if (strlen(pkvd->szValue) >= cchMapNameMost) + ALERT( at_error, "Landmark name '%s' too long (32 chars)\n", pkvd->szValue ); + strcpy(m_szLandmarkName, pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "changetarget")) + { + m_changeTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "changedelay")) + { + m_changeTargetDelay = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseTrigger::KeyValue( pkvd ); +} + + +/*QUAKED trigger_changelevel (0.5 0.5 0.5) ? NO_INTERMISSION +When the player touches this, he gets sent to the map listed in the "map" variable. Unless the NO_INTERMISSION flag is set, the view will go to the info_intermission spot and display stats. +*/ + +void CChangeLevel :: Spawn( void ) +{ + if ( FStrEq( m_szMapName, "" ) ) + ALERT( at_console, "a trigger_changelevel doesn't have a map" ); + + if ( FStrEq( m_szLandmarkName, "" ) ) + ALERT( at_console, "trigger_changelevel to %s doesn't have a landmark", m_szMapName ); + + if (!FStringNull ( pev->targetname ) ) + { + SetUse ( &CChangeLevel::UseChangeLevel ); + } + InitTrigger(); + if ( !(pev->spawnflags & SF_CHANGELEVEL_USEONLY) ) + SetTouch( &CChangeLevel::TouchChangeLevel ); +// ALERT( at_console, "TRANSITION: %s (%s)\n", m_szMapName, m_szLandmarkName ); +} + + +void CChangeLevel :: ExecuteChangeLevel( void ) +{ + MESSAGE_BEGIN( MSG_ALL, SVC_CDTRACK ); + WRITE_BYTE( 3 ); + WRITE_BYTE( 3 ); + MESSAGE_END(); + + MESSAGE_BEGIN(MSG_ALL, SVC_INTERMISSION); + MESSAGE_END(); +} + + +FILE_GLOBAL char st_szNextMap[cchMapNameMost]; +FILE_GLOBAL char st_szNextSpot[cchMapNameMost]; + +edict_t *CChangeLevel :: FindLandmark( const char *pLandmarkName ) +{ + edict_t *pentLandmark; + + pentLandmark = FIND_ENTITY_BY_STRING( NULL, "targetname", pLandmarkName ); + while ( !FNullEnt( pentLandmark ) ) + { + // Found the landmark + if ( FClassnameIs( pentLandmark, "info_landmark" ) ) + return pentLandmark; + else + pentLandmark = FIND_ENTITY_BY_STRING( pentLandmark, "targetname", pLandmarkName ); + } + ALERT( at_error, "Can't find landmark %s\n", pLandmarkName ); + return NULL; +} + + +//========================================================= +// CChangeLevel :: Use - allows level transitions to be +// triggered by buttons, etc. +// +//========================================================= +void CChangeLevel :: UseChangeLevel ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + ChangeLevelNow( pActivator ); +} + +void CChangeLevel :: ChangeLevelNow( CBaseEntity *pActivator ) +{ + edict_t *pentLandmark; + + ASSERT(!FStrEq(m_szMapName, "")); + + // Don't work in deathmatch + if ( g_pGameRules->IsDeathmatch() ) + return; + + // Some people are firing these multiple times in a frame, disable + if ( gpGlobals->time == pev->dmgtime ) + return; + + pev->dmgtime = gpGlobals->time; + + + CBaseEntity *pPlayer = CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) ); + if ( !InTransitionVolume( pPlayer, m_szLandmarkName ) ) + { + ALERT( at_aiconsole, "Player isn't in the transition volume %s, aborting\n", m_szLandmarkName ); + return; + } + + // Create an entity to fire the changetarget + if ( m_changeTarget ) + { + CFireAndDie *pFireAndDie = GetClassPtr( (CFireAndDie *)NULL ); + if ( pFireAndDie ) + { + // Set target and delay + pFireAndDie->pev->target = m_changeTarget; + pFireAndDie->m_flDelay = m_changeTargetDelay; + pFireAndDie->pev->origin = pPlayer->pev->origin; + // Call spawn + DispatchSpawn( pFireAndDie->edict() ); + } + } + // This object will get removed in the call to CHANGE_LEVEL, copy the params into "safe" memory + strcpy(st_szNextMap, m_szMapName); + + m_hActivator = pActivator; + SUB_UseTargets( pActivator, USE_TOGGLE, 0 ); + st_szNextSpot[0] = 0; // Init landmark to NULL + + // look for a landmark entity + pentLandmark = FindLandmark( m_szLandmarkName ); + if ( !FNullEnt( pentLandmark ) ) + { + strcpy(st_szNextSpot, m_szLandmarkName); + gpGlobals->vecLandmarkOffset = VARS(pentLandmark)->origin; + } +// ALERT( at_console, "Level touches %d levels\n", ChangeList( levels, 16 ) ); + ALERT( at_console, "CHANGE LEVEL: %s %s\n", st_szNextMap, st_szNextSpot ); + CHANGE_LEVEL( st_szNextMap, st_szNextSpot ); +} + +// +// GLOBALS ASSUMED SET: st_szNextMap +// +void CChangeLevel :: TouchChangeLevel( CBaseEntity *pOther ) +{ + if (!FClassnameIs(pOther->pev, "player")) + return; + + ChangeLevelNow( pOther ); +} + + +// Add a transition to the list, but ignore duplicates +// (a designer may have placed multiple trigger_changelevels with the same landmark) +int CChangeLevel::AddTransitionToList( LEVELLIST *pLevelList, int listCount, const char *pMapName, const char *pLandmarkName, edict_t *pentLandmark ) +{ + int i; + + if ( !pLevelList || !pMapName || !pLandmarkName || !pentLandmark ) + return 0; + + for ( i = 0; i < listCount; i++ ) + { + if ( pLevelList[i].pentLandmark == pentLandmark && strcmp( pLevelList[i].mapName, pMapName ) == 0 ) + return 0; + } + strcpy( pLevelList[listCount].mapName, pMapName ); + strcpy( pLevelList[listCount].landmarkName, pLandmarkName ); + pLevelList[listCount].pentLandmark = pentLandmark; + pLevelList[listCount].vecLandmarkOrigin = VARS(pentLandmark)->origin; + + return 1; +} + +int BuildChangeList( LEVELLIST *pLevelList, int maxList ) +{ + return CChangeLevel::ChangeList( pLevelList, maxList ); +} + + +int CChangeLevel::InTransitionVolume( CBaseEntity *pEntity, char *pVolumeName ) +{ + edict_t *pentVolume; + + + if ( pEntity->ObjectCaps() & FCAP_FORCE_TRANSITION ) + return 1; + + // If you're following another entity, follow it through the transition (weapons follow the player) + if ( pEntity->pev->movetype == MOVETYPE_FOLLOW ) + { + if ( pEntity->pev->aiment != NULL ) + pEntity = CBaseEntity::Instance( pEntity->pev->aiment ); + } + + int inVolume = 1; // Unless we find a trigger_transition, everything is in the volume + + pentVolume = FIND_ENTITY_BY_TARGETNAME( NULL, pVolumeName ); + while ( !FNullEnt( pentVolume ) ) + { + CBaseEntity *pVolume = CBaseEntity::Instance( pentVolume ); + + if ( pVolume && FClassnameIs( pVolume->pev, "trigger_transition" ) ) + { + if ( pVolume->Intersects( pEntity ) ) // It touches one, it's in the volume + return 1; + else + inVolume = 0; // Found a trigger_transition, but I don't intersect it -- if I don't find another, don't go! + } + pentVolume = FIND_ENTITY_BY_TARGETNAME( pentVolume, pVolumeName ); + } + + return inVolume; +} + + +// We can only ever move 512 entities across a transition +#define MAX_ENTITY 512 + +// This has grown into a complicated beast +// Can we make this more elegant? +// This builds the list of all transitions on this level and which entities are in their PVS's and can / should +// be moved across. +int CChangeLevel::ChangeList( LEVELLIST *pLevelList, int maxList ) +{ + edict_t *pentChangelevel, *pentLandmark; + int i, count; + + count = 0; + + // Find all of the possible level changes on this BSP + pentChangelevel = FIND_ENTITY_BY_STRING( NULL, "classname", "trigger_changelevel" ); + if ( FNullEnt( pentChangelevel ) ) + return 0; + while ( !FNullEnt( pentChangelevel ) ) + { + CChangeLevel *pTrigger; + + pTrigger = GetClassPtr((CChangeLevel *)VARS(pentChangelevel)); + if ( pTrigger ) + { + // Find the corresponding landmark + pentLandmark = FindLandmark( pTrigger->m_szLandmarkName ); + if ( pentLandmark ) + { + // Build a list of unique transitions + if ( AddTransitionToList( pLevelList, count, pTrigger->m_szMapName, pTrigger->m_szLandmarkName, pentLandmark ) ) + { + count++; + if ( count >= maxList ) // FULL!! + break; + } + } + } + pentChangelevel = FIND_ENTITY_BY_STRING( pentChangelevel, "classname", "trigger_changelevel" ); + } + + if ( gpGlobals->pSaveData && ((SAVERESTOREDATA *)gpGlobals->pSaveData)->pTable ) + { + CSave saveHelper( (SAVERESTOREDATA *)gpGlobals->pSaveData ); + + for ( i = 0; i < count; i++ ) + { + int j, entityCount = 0; + CBaseEntity *pEntList[ MAX_ENTITY ]; + int entityFlags[ MAX_ENTITY ]; + + // Follow the linked list of entities in the PVS of the transition landmark + edict_t *pent = UTIL_EntitiesInPVS( pLevelList[i].pentLandmark ); + + // Build a list of valid entities in this linked list (we're going to use pent->v.chain again) + while ( !FNullEnt( pent ) ) + { + CBaseEntity *pEntity = CBaseEntity::Instance(pent); + if ( pEntity ) + { +// ALERT( at_console, "Trying %s\n", STRING(pEntity->pev->classname) ); + int caps = pEntity->ObjectCaps(); + if ( !(caps & FCAP_DONT_SAVE) ) + { + int flags = 0; + + // If this entity can be moved or is global, mark it + if ( caps & FCAP_ACROSS_TRANSITION ) + flags |= FENTTABLE_MOVEABLE; + if ( pEntity->pev->globalname && !pEntity->IsDormant() ) + flags |= FENTTABLE_GLOBAL; + if ( flags ) + { + pEntList[ entityCount ] = pEntity; + entityFlags[ entityCount ] = flags; + entityCount++; + if ( entityCount > MAX_ENTITY ) + ALERT( at_error, "Too many entities across a transition!" ); + } +// else +// ALERT( at_console, "Failed %s\n", STRING(pEntity->pev->classname) ); + } +// else +// ALERT( at_console, "DON'T SAVE %s\n", STRING(pEntity->pev->classname) ); + } + pent = pent->v.chain; + } + + for ( j = 0; j < entityCount; j++ ) + { + // Check to make sure the entity isn't screened out by a trigger_transition + if ( entityFlags[j] && InTransitionVolume( pEntList[j], pLevelList[i].landmarkName ) ) + { + // Mark entity table with 1<pev->classname) ); + + } + } + } + + return count; +} + +/* +go to the next level for deathmatch +only called if a time or frag limit has expired +*/ +void NextLevel( void ) +{ + edict_t* pent; + CChangeLevel *pChange; + + // find a trigger_changelevel + pent = FIND_ENTITY_BY_CLASSNAME(NULL, "trigger_changelevel"); + + // go back to start if no trigger_changelevel + if (FNullEnt(pent)) + { + gpGlobals->mapname = ALLOC_STRING("start"); + pChange = GetClassPtr( (CChangeLevel *)NULL ); + strcpy(pChange->m_szMapName, "start"); + } + else + pChange = GetClassPtr( (CChangeLevel *)VARS(pent)); + + strcpy(st_szNextMap, pChange->m_szMapName); + g_fGameOver = TRUE; + + if (pChange->pev->nextthink < gpGlobals->time) + { + pChange->SetThink( &CChangeLevel::ExecuteChangeLevel ); + pChange->pev->nextthink = gpGlobals->time + 0.1; + } +} + + +// ============================== LADDER ======================================= + +class CLadder : public CBaseTrigger +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS( func_ladder, CLadder ); + + +void CLadder :: KeyValue( KeyValueData *pkvd ) +{ + CBaseTrigger::KeyValue( pkvd ); +} + + +//========================================================= +// func_ladder - makes an area vertically negotiable +//========================================================= +void CLadder :: Precache( void ) +{ + // Do all of this in here because we need to 'convert' old saved games + pev->solid = SOLID_NOT; + pev->skin = CONTENTS_LADDER; + if ( CVAR_GET_FLOAT("showtriggers") == 0 ) + { + pev->rendermode = kRenderTransTexture; + pev->renderamt = 0; + } + pev->effects &= ~EF_NODRAW; +} + + +void CLadder :: Spawn( void ) +{ + Precache(); + + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->movetype = MOVETYPE_PUSH; +} + + +// ========================== A TRIGGER THAT PUSHES YOU =============================== + +class CTriggerPush : public CBaseTrigger +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Touch( CBaseEntity *pOther ); +}; +LINK_ENTITY_TO_CLASS( trigger_push, CTriggerPush ); + + +void CTriggerPush :: KeyValue( KeyValueData *pkvd ) +{ + CBaseTrigger::KeyValue( pkvd ); +} + + +/*QUAKED trigger_push (.5 .5 .5) ? TRIG_PUSH_ONCE +Pushes the player +*/ + +void CTriggerPush :: Spawn( ) +{ + if ( pev->angles == g_vecZero ) + pev->angles.y = 360; + InitTrigger(); + + if (pev->speed == 0) + pev->speed = 100; + + if ( FBitSet (pev->spawnflags, SF_TRIGGER_PUSH_START_OFF) )// if flagged to Start Turned Off, make trigger nonsolid. + pev->solid = SOLID_NOT; + + SetUse( &CBaseTrigger::ToggleUse ); + + UTIL_SetOrigin( pev, pev->origin ); // Link into the list +} + + +void CTriggerPush :: Touch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + + // UNDONE: Is there a better way than health to detect things that have physics? (clients/monsters) + switch( pevToucher->movetype ) + { + case MOVETYPE_NONE: + case MOVETYPE_PUSH: + case MOVETYPE_NOCLIP: + case MOVETYPE_FOLLOW: + return; + } + + if ( pevToucher->solid != SOLID_NOT && pevToucher->solid != SOLID_BSP ) + { + // Instant trigger, just transfer velocity and remove + if (FBitSet(pev->spawnflags, SF_TRIG_PUSH_ONCE)) + { + pevToucher->velocity = pevToucher->velocity + (pev->speed * pev->movedir); + if ( pevToucher->velocity.z > 0 ) + pevToucher->flags &= ~FL_ONGROUND; + UTIL_Remove( this ); + } + else + { // Push field, transfer to base velocity + Vector vecPush = (pev->speed * pev->movedir); + if ( pevToucher->flags & FL_BASEVELOCITY ) + vecPush = vecPush + pevToucher->basevelocity; + + pevToucher->basevelocity = vecPush; + + pevToucher->flags |= FL_BASEVELOCITY; +// ALERT( at_console, "Vel %f, base %f\n", pevToucher->velocity.z, pevToucher->basevelocity.z ); + } + } +} + + +//====================================== +// teleport trigger +// +// + +void CBaseTrigger :: TeleportTouch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + edict_t *pentTarget = NULL; + + // Only teleport monsters or clients + if ( !FBitSet( pevToucher->flags, FL_CLIENT|FL_MONSTER ) ) + return; + + if (!UTIL_IsMasterTriggered(m_sMaster, pOther)) + return; + + if ( !( pev->spawnflags & SF_TRIGGER_ALLOWMONSTERS ) ) + {// no monsters allowed! + if ( FBitSet( pevToucher->flags, FL_MONSTER ) ) + { + return; + } + } + + if ( ( pev->spawnflags & SF_TRIGGER_NOCLIENTS ) ) + {// no clients allowed + if ( pOther->IsPlayer() ) + { + return; + } + } + + pentTarget = FIND_ENTITY_BY_TARGETNAME( pentTarget, STRING(pev->target) ); + if (FNullEnt(pentTarget)) + return; + + Vector tmp = VARS( pentTarget )->origin; + + if ( pOther->IsPlayer() ) + { + tmp.z -= pOther->pev->mins.z;// make origin adjustments in case the teleportee is a player. (origin in center, not at feet) + } + + tmp.z++; + + pevToucher->flags &= ~FL_ONGROUND; + + UTIL_SetOrigin( pevToucher, tmp ); + + pevToucher->angles = pentTarget->v.angles; + + if ( pOther->IsPlayer() ) + { + pevToucher->v_angle = pentTarget->v.angles; + } + + pevToucher->fixangle = TRUE; + pevToucher->velocity = pevToucher->basevelocity = g_vecZero; +} + + +class CTriggerTeleport : public CBaseTrigger +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS( trigger_teleport, CTriggerTeleport ); + +void CTriggerTeleport :: Spawn( void ) +{ + InitTrigger(); + + SetTouch( &CBaseTrigger::TeleportTouch ); +} + + +LINK_ENTITY_TO_CLASS( info_teleport_destination, CPointEntity ); + + + +class CTriggerSave : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT SaveTouch( CBaseEntity *pOther ); +}; +LINK_ENTITY_TO_CLASS( trigger_autosave, CTriggerSave ); + +void CTriggerSave::Spawn( void ) +{ + if ( g_pGameRules->IsDeathmatch() ) + { + REMOVE_ENTITY( ENT(pev) ); + return; + } + + InitTrigger(); + SetTouch( &CTriggerSave::SaveTouch ); +} + +void CTriggerSave::SaveTouch( CBaseEntity *pOther ) +{ + if ( !UTIL_IsMasterTriggered( m_sMaster, pOther ) ) + return; + + // Only save on clients + if ( !pOther->IsPlayer() ) + return; + + SetTouch( NULL ); + UTIL_Remove( this ); + SERVER_COMMAND( "autosave\n" ); +} + +#define SF_ENDSECTION_USEONLY 0x0001 + +class CTriggerEndSection : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT EndSectionTouch( CBaseEntity *pOther ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT EndSectionUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; +LINK_ENTITY_TO_CLASS( trigger_endsection, CTriggerEndSection ); + + +void CTriggerEndSection::EndSectionUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Only save on clients + if ( pActivator && !pActivator->IsNetClient() ) + return; + + SetUse( NULL ); + + if ( pev->message ) + { + g_engfuncs.pfnEndSection(STRING(pev->message)); + } + UTIL_Remove( this ); +} + +void CTriggerEndSection::Spawn( void ) +{ + if ( g_pGameRules->IsDeathmatch() ) + { + REMOVE_ENTITY( ENT(pev) ); + return; + } + + InitTrigger(); + + SetUse ( &CTriggerEndSection::EndSectionUse ); + // If it is a "use only" trigger, then don't set the touch function. + if ( ! (pev->spawnflags & SF_ENDSECTION_USEONLY) ) + SetTouch( &CTriggerEndSection::EndSectionTouch ); +} + +void CTriggerEndSection::EndSectionTouch( CBaseEntity *pOther ) +{ + // Only save on clients + if ( !pOther->IsNetClient() ) + return; + + SetTouch( NULL ); + + if (pev->message) + { + g_engfuncs.pfnEndSection(STRING(pev->message)); + } + UTIL_Remove( this ); +} + +void CTriggerEndSection :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "section")) + { +// m_iszSectionName = ALLOC_STRING( pkvd->szValue ); + // Store this in message so we don't have to write save/restore for this ent + pev->message = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseTrigger::KeyValue( pkvd ); +} + + +class CTriggerGravity : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT GravityTouch( CBaseEntity *pOther ); +}; +LINK_ENTITY_TO_CLASS( trigger_gravity, CTriggerGravity ); + +void CTriggerGravity::Spawn( void ) +{ + InitTrigger(); + SetTouch( &CTriggerGravity::GravityTouch ); +} + +void CTriggerGravity::GravityTouch( CBaseEntity *pOther ) +{ + // Only save on clients + if ( !pOther->IsPlayer() ) + return; + + pOther->pev->gravity = pev->gravity; +} + + + + + + + +// this is a really bad idea. +class CTriggerChangeTarget : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_iszNewTarget; +}; +LINK_ENTITY_TO_CLASS( trigger_changetarget, CTriggerChangeTarget ); + +TYPEDESCRIPTION CTriggerChangeTarget::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerChangeTarget, m_iszNewTarget, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerChangeTarget,CBaseDelay); + +void CTriggerChangeTarget::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszNewTarget")) + { + m_iszNewTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + +void CTriggerChangeTarget::Spawn( void ) +{ +} + + +void CTriggerChangeTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBaseEntity *pTarget = UTIL_FindEntityByString( NULL, "targetname", STRING( pev->target ) ); + + if (pTarget) + { + pTarget->pev->target = m_iszNewTarget; + CBaseMonster *pMonster = pTarget->MyMonsterPointer( ); + if (pMonster) + { + pMonster->m_pGoalEnt = NULL; + } + } +} + + + + +#define SF_CAMERA_PLAYER_POSITION 1 +#define SF_CAMERA_PLAYER_TARGET 2 +#define SF_CAMERA_PLAYER_TAKECONTROL 4 + +class CTriggerCamera : public CBaseDelay +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT FollowTarget( void ); + void Move(void); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + static TYPEDESCRIPTION m_SaveData[]; + + EHANDLE m_hPlayer; + EHANDLE m_hTarget; + CBaseEntity *m_pentPath; + int m_sPath; + float m_flWait; + float m_flReturnTime; + float m_flStopTime; + float m_moveDistance; + float m_targetSpeed; + float m_initialSpeed; + float m_acceleration; + float m_deceleration; + int m_state; + +}; +LINK_ENTITY_TO_CLASS( trigger_camera, CTriggerCamera ); + +// Global Savedata for changelevel friction modifier +TYPEDESCRIPTION CTriggerCamera::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerCamera, m_hPlayer, FIELD_EHANDLE ), + DEFINE_FIELD( CTriggerCamera, m_hTarget, FIELD_EHANDLE ), + DEFINE_FIELD( CTriggerCamera, m_pentPath, FIELD_CLASSPTR ), + DEFINE_FIELD( CTriggerCamera, m_sPath, FIELD_STRING ), + DEFINE_FIELD( CTriggerCamera, m_flWait, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_flReturnTime, FIELD_TIME ), + DEFINE_FIELD( CTriggerCamera, m_flStopTime, FIELD_TIME ), + DEFINE_FIELD( CTriggerCamera, m_moveDistance, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_targetSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_initialSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_acceleration, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_deceleration, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_state, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerCamera,CBaseDelay); + +void CTriggerCamera::Spawn( void ) +{ + pev->movetype = MOVETYPE_NOCLIP; + pev->solid = SOLID_NOT; // Remove model & collisions + pev->renderamt = 0; // The engine won't draw this model if this is set to 0 and blending is on + pev->rendermode = kRenderTransTexture; + + m_initialSpeed = pev->speed; + if ( m_acceleration == 0 ) + m_acceleration = 500; + if ( m_deceleration == 0 ) + m_deceleration = 500; +} + + +void CTriggerCamera :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "moveto")) + { + m_sPath = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "acceleration")) + { + m_acceleration = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "deceleration")) + { + m_deceleration = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + + + +void CTriggerCamera::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_state ) ) + return; + + // Toggle state + m_state = !m_state; + if (m_state == 0) + { + m_flReturnTime = gpGlobals->time; + return; + } + if ( !pActivator || !pActivator->IsPlayer() ) + { + pActivator = CBaseEntity::Instance(g_engfuncs.pfnPEntityOfEntIndex( 1 )); + } + + m_hPlayer = pActivator; + + m_flReturnTime = gpGlobals->time + m_flWait; + pev->speed = m_initialSpeed; + m_targetSpeed = m_initialSpeed; + + if (FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_TARGET ) ) + { + m_hTarget = m_hPlayer; + } + else + { + m_hTarget = GetNextTarget(); + } + + // Nothing to look at! + if ( m_hTarget == 0 ) + { + return; + } + + + if (FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_TAKECONTROL ) ) + { + ((CBasePlayer *)pActivator)->EnableControl(FALSE); + } + + if ( m_sPath ) + { + m_pentPath = Instance( FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(m_sPath)) ); + } + else + { + m_pentPath = NULL; + } + + m_flStopTime = gpGlobals->time; + if ( m_pentPath ) + { + if ( m_pentPath->pev->speed != 0 ) + m_targetSpeed = m_pentPath->pev->speed; + + m_flStopTime += m_pentPath->GetDelay(); + } + + // copy over player information + if (FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_POSITION ) ) + { + UTIL_SetOrigin( pev, pActivator->pev->origin + pActivator->pev->view_ofs ); + pev->angles.x = -pActivator->pev->angles.x; + pev->angles.y = pActivator->pev->angles.y; + pev->angles.z = 0; + pev->velocity = pActivator->pev->velocity; + } + else + { + pev->velocity = Vector( 0, 0, 0 ); + } + + SET_VIEW( pActivator->edict(), edict() ); + + SET_MODEL(ENT(pev), STRING(pActivator->pev->model) ); + + // follow the player down + SetThink( &CTriggerCamera::FollowTarget ); + pev->nextthink = gpGlobals->time; + + m_moveDistance = 0; + Move(); +} + + +void CTriggerCamera::FollowTarget( ) +{ + if (m_hPlayer == 0) + return; + + if (m_hTarget == 0 || m_flReturnTime < gpGlobals->time) + { + if (m_hPlayer->IsAlive( )) + { + SET_VIEW( m_hPlayer->edict(), m_hPlayer->edict() ); + ((CBasePlayer *)((CBaseEntity *)m_hPlayer))->EnableControl(TRUE); + } + SUB_UseTargets( this, USE_TOGGLE, 0 ); + pev->avelocity = Vector( 0, 0, 0 ); + m_state = 0; + return; + } + + Vector vecGoal = UTIL_VecToAngles( m_hTarget->pev->origin - pev->origin ); + vecGoal.x = -vecGoal.x; + + if (pev->angles.y > 360) + pev->angles.y -= 360; + + if (pev->angles.y < 0) + pev->angles.y += 360; + + float dx = vecGoal.x - pev->angles.x; + float dy = vecGoal.y - pev->angles.y; + + if (dx < -180) + dx += 360; + if (dx > 180) + dx = dx - 360; + + if (dy < -180) + dy += 360; + if (dy > 180) + dy = dy - 360; + + pev->avelocity.x = dx * 40 * gpGlobals->frametime; + pev->avelocity.y = dy * 40 * gpGlobals->frametime; + + + if (!(FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_TAKECONTROL))) + { + pev->velocity = pev->velocity * 0.8; + if (pev->velocity.Length( ) < 10.0) + pev->velocity = g_vecZero; + } + + pev->nextthink = gpGlobals->time; + + Move(); +} + +void CTriggerCamera::Move() +{ + // Not moving on a path, return + if (!m_pentPath) + return; + + // Subtract movement from the previous frame + m_moveDistance -= pev->speed * gpGlobals->frametime; + + // Have we moved enough to reach the target? + if ( m_moveDistance <= 0 ) + { + // Fire the passtarget if there is one + if ( m_pentPath->pev->message ) + { + FireTargets( STRING(m_pentPath->pev->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( m_pentPath->pev->spawnflags, SF_CORNER_FIREONCE ) ) + m_pentPath->pev->message = 0; + } + // Time to go to the next target + m_pentPath = m_pentPath->GetNextTarget(); + + // Set up next corner + if ( !m_pentPath ) + { + pev->velocity = g_vecZero; + } + else + { + if ( m_pentPath->pev->speed != 0 ) + m_targetSpeed = m_pentPath->pev->speed; + + Vector delta = m_pentPath->pev->origin - pev->origin; + m_moveDistance = delta.Length(); + pev->movedir = delta.Normalize(); + m_flStopTime = gpGlobals->time + m_pentPath->GetDelay(); + } + } + + if ( m_flStopTime > gpGlobals->time ) + pev->speed = UTIL_Approach( 0, pev->speed, m_deceleration * gpGlobals->frametime ); + else + pev->speed = UTIL_Approach( m_targetSpeed, pev->speed, m_acceleration * gpGlobals->frametime ); + + float fraction = 2 * gpGlobals->frametime; + pev->velocity = ((pev->movedir * pev->speed) * fraction) + (pev->velocity * (1-fraction)); +} diff --git a/hlsdk/dlls/tripmine.cpp b/hlsdk/dlls/tripmine.cpp new file mode 100644 index 00000000..1376924a --- /dev/null +++ b/hlsdk/dlls/tripmine.cpp @@ -0,0 +1,526 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "effects.h" +#include "gamerules.h" + +#define TRIPMINE_PRIMARY_VOLUME 450 + + + +enum tripmine_e { + TRIPMINE_IDLE1 = 0, + TRIPMINE_IDLE2, + TRIPMINE_ARM1, + TRIPMINE_ARM2, + TRIPMINE_FIDGET, + TRIPMINE_HOLSTER, + TRIPMINE_DRAW, + TRIPMINE_WORLD, + TRIPMINE_GROUND, +}; + + +#ifndef CLIENT_DLL + +class CTripmineGrenade : public CGrenade +{ + void Spawn( void ); + void Precache( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + void EXPORT WarningThink( void ); + void EXPORT PowerupThink( void ); + void EXPORT BeamBreakThink( void ); + void EXPORT DelayDeathThink( void ); + void Killed( entvars_t *pevAttacker, int iGib ); + + void MakeBeam( void ); + void KillBeam( void ); + + float m_flPowerUp; + Vector m_vecDir; + Vector m_vecEnd; + float m_flBeamLength; + + EHANDLE m_hOwner; + CBeam *m_pBeam; + Vector m_posOwner; + Vector m_angleOwner; + edict_t *m_pRealOwner;// tracelines don't hit PEV->OWNER, which means a player couldn't detonate his own trip mine, so we store the owner here. +}; + +LINK_ENTITY_TO_CLASS( monster_tripmine, CTripmineGrenade ); + +TYPEDESCRIPTION CTripmineGrenade::m_SaveData[] = +{ + DEFINE_FIELD( CTripmineGrenade, m_flPowerUp, FIELD_TIME ), + DEFINE_FIELD( CTripmineGrenade, m_vecDir, FIELD_VECTOR ), + DEFINE_FIELD( CTripmineGrenade, m_vecEnd, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CTripmineGrenade, m_flBeamLength, FIELD_FLOAT ), + DEFINE_FIELD( CTripmineGrenade, m_hOwner, FIELD_EHANDLE ), + DEFINE_FIELD( CTripmineGrenade, m_pBeam, FIELD_CLASSPTR ), + DEFINE_FIELD( CTripmineGrenade, m_posOwner, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CTripmineGrenade, m_angleOwner, FIELD_VECTOR ), + DEFINE_FIELD( CTripmineGrenade, m_pRealOwner, FIELD_EDICT ), +}; + +IMPLEMENT_SAVERESTORE(CTripmineGrenade,CGrenade); + + +void CTripmineGrenade :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_NOT; + + SET_MODEL(ENT(pev), "models/v_tripmine.mdl"); + pev->frame = 0; + pev->body = 3; + pev->sequence = TRIPMINE_WORLD; + ResetSequenceInfo( ); + pev->framerate = 0; + + UTIL_SetSize(pev, Vector( -8, -8, -8), Vector(8, 8, 8)); + UTIL_SetOrigin( pev, pev->origin ); + + if (pev->spawnflags & 1) + { + // power up quickly + m_flPowerUp = gpGlobals->time + 1.0; + } + else + { + // power up in 2.5 seconds + m_flPowerUp = gpGlobals->time + 2.5; + } + + SetThink( &CTripmineGrenade::PowerupThink ); + pev->nextthink = gpGlobals->time + 0.2; + + pev->takedamage = DAMAGE_YES; + pev->dmg = gSkillData.plrDmgTripmine; + pev->health = 1; // don't let die normally + + if (pev->owner != NULL) + { + // play deploy sound + EMIT_SOUND( ENT(pev), CHAN_VOICE, "weapons/mine_deploy.wav", 1.0, ATTN_NORM ); + EMIT_SOUND( ENT(pev), CHAN_BODY, "weapons/mine_charge.wav", 0.2, ATTN_NORM ); // chargeup + + m_pRealOwner = pev->owner;// see CTripmineGrenade for why. + } + + UTIL_MakeAimVectors( pev->angles ); + + m_vecDir = gpGlobals->v_forward; + m_vecEnd = pev->origin + m_vecDir * 2048; +} + + +void CTripmineGrenade :: Precache( void ) +{ + PRECACHE_MODEL("models/v_tripmine.mdl"); + PRECACHE_SOUND("weapons/mine_deploy.wav"); + PRECACHE_SOUND("weapons/mine_activate.wav"); + PRECACHE_SOUND("weapons/mine_charge.wav"); +} + + +void CTripmineGrenade :: WarningThink( void ) +{ + // play warning sound + // EMIT_SOUND( ENT(pev), CHAN_VOICE, "buttons/Blip2.wav", 1.0, ATTN_NORM ); + + // set to power up + SetThink( &CTripmineGrenade::PowerupThink ); + pev->nextthink = gpGlobals->time + 1.0; +} + + +void CTripmineGrenade :: PowerupThink( void ) +{ + TraceResult tr; + + if (m_hOwner == 0) + { + // find an owner + edict_t *oldowner = pev->owner; + pev->owner = NULL; + UTIL_TraceLine( pev->origin + m_vecDir * 8, pev->origin - m_vecDir * 32, dont_ignore_monsters, ENT( pev ), &tr ); + if (tr.fStartSolid || (oldowner && tr.pHit == oldowner)) + { + pev->owner = oldowner; + m_flPowerUp += 0.1; + pev->nextthink = gpGlobals->time + 0.1; + return; + } + if (tr.flFraction < 1.0) + { + pev->owner = tr.pHit; + m_hOwner = CBaseEntity::Instance( pev->owner ); + m_posOwner = m_hOwner->pev->origin; + m_angleOwner = m_hOwner->pev->angles; + } + else + { + STOP_SOUND( ENT(pev), CHAN_VOICE, "weapons/mine_deploy.wav" ); + STOP_SOUND( ENT(pev), CHAN_BODY, "weapons/mine_charge.wav" ); + SetThink( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; + ALERT( at_console, "WARNING:Tripmine at %.0f, %.0f, %.0f removed\n", pev->origin.x, pev->origin.y, pev->origin.z ); + KillBeam(); + return; + } + } + else if (m_posOwner != m_hOwner->pev->origin || m_angleOwner != m_hOwner->pev->angles) + { + // disable + STOP_SOUND( ENT(pev), CHAN_VOICE, "weapons/mine_deploy.wav" ); + STOP_SOUND( ENT(pev), CHAN_BODY, "weapons/mine_charge.wav" ); + CBaseEntity *pMine = Create( "weapon_tripmine", pev->origin + m_vecDir * 24, pev->angles ); + pMine->pev->spawnflags |= SF_NORESPAWN; + + SetThink( &CBaseEntity::SUB_Remove ); + KillBeam(); + pev->nextthink = gpGlobals->time + 0.1; + return; + } + // ALERT( at_console, "%d %.0f %.0f %0.f\n", pev->owner, m_pOwner->pev->origin.x, m_pOwner->pev->origin.y, m_pOwner->pev->origin.z ); + + if (gpGlobals->time > m_flPowerUp) + { + // make solid + pev->solid = SOLID_BBOX; + UTIL_SetOrigin( pev, pev->origin ); + + MakeBeam( ); + + // play enabled sound + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "weapons/mine_activate.wav", 0.5, ATTN_NORM, 1, 75 ); + } + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CTripmineGrenade :: KillBeam( void ) +{ + if ( m_pBeam ) + { + UTIL_Remove( m_pBeam ); + m_pBeam = NULL; + } +} + + +void CTripmineGrenade :: MakeBeam( void ) +{ + TraceResult tr; + + // ALERT( at_console, "serverflags %f\n", gpGlobals->serverflags ); + + UTIL_TraceLine( pev->origin, m_vecEnd, dont_ignore_monsters, ENT( pev ), &tr ); + + m_flBeamLength = tr.flFraction; + + // set to follow laser spot + SetThink( &CTripmineGrenade::BeamBreakThink ); + pev->nextthink = gpGlobals->time + 0.1; + + Vector vecTmpEnd = pev->origin + m_vecDir * 2048 * m_flBeamLength; + + m_pBeam = CBeam::BeamCreate( g_pModelNameLaser, 10 ); + m_pBeam->PointEntInit( vecTmpEnd, entindex() ); + m_pBeam->SetColor( 0, 214, 198 ); + m_pBeam->SetScrollRate( 255 ); + m_pBeam->SetBrightness( 64 ); +} + + +void CTripmineGrenade :: BeamBreakThink( void ) +{ + BOOL bBlowup = 0; + + TraceResult tr; + + // HACKHACK Set simple box using this really nice global! + gpGlobals->trace_flags = FTRACE_SIMPLEBOX; + UTIL_TraceLine( pev->origin, m_vecEnd, dont_ignore_monsters, ENT( pev ), &tr ); + + // ALERT( at_console, "%f : %f\n", tr.flFraction, m_flBeamLength ); + + // respawn detect. + if ( !m_pBeam ) + { + MakeBeam( ); + if ( tr.pHit ) + m_hOwner = CBaseEntity::Instance( tr.pHit ); // reset owner too + } + + if (fabs( m_flBeamLength - tr.flFraction ) > 0.001) + { + bBlowup = 1; + } + else + { + if (m_hOwner == 0) + bBlowup = 1; + else if (m_posOwner != m_hOwner->pev->origin) + bBlowup = 1; + else if (m_angleOwner != m_hOwner->pev->angles) + bBlowup = 1; + } + + if (bBlowup) + { + // a bit of a hack, but all CGrenade code passes pev->owner along to make sure the proper player gets credit for the kill + // so we have to restore pev->owner from pRealOwner, because an entity's tracelines don't strike it's pev->owner which meant + // that a player couldn't trigger his own tripmine. Now that the mine is exploding, it's safe the restore the owner so the + // CGrenade code knows who the explosive really belongs to. + pev->owner = m_pRealOwner; + pev->health = 0; + Killed( VARS( pev->owner ), GIB_NORMAL ); + return; + } + + pev->nextthink = gpGlobals->time + 0.1; +} + +int CTripmineGrenade :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if (gpGlobals->time < m_flPowerUp && flDamage < pev->health) + { + // disable + // Create( "weapon_tripmine", pev->origin + m_vecDir * 24, pev->angles ); + SetThink( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; + KillBeam(); + return FALSE; + } + return CGrenade::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +void CTripmineGrenade::Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->takedamage = DAMAGE_NO; + + if ( pevAttacker && ( pevAttacker->flags & FL_CLIENT ) ) + { + // some client has destroyed this mine, he'll get credit for any kills + pev->owner = ENT( pevAttacker ); + } + + SetThink( &CTripmineGrenade::DelayDeathThink ); + pev->nextthink = gpGlobals->time + RANDOM_FLOAT( 0.1, 0.3 ); + + EMIT_SOUND( ENT(pev), CHAN_BODY, "common/null.wav", 0.5, ATTN_NORM ); // shut off chargeup +} + + +void CTripmineGrenade::DelayDeathThink( void ) +{ + KillBeam(); + TraceResult tr; + UTIL_TraceLine ( pev->origin + m_vecDir * 8, pev->origin - m_vecDir * 64, dont_ignore_monsters, ENT(pev), & tr); + + Explode( &tr, DMG_BLAST ); +} +#endif + +LINK_ENTITY_TO_CLASS( weapon_tripmine, CTripmine ); + +void CTripmine::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_TRIPMINE; + SET_MODEL(ENT(pev), "models/v_tripmine.mdl"); + pev->frame = 0; + pev->body = 3; + pev->sequence = TRIPMINE_GROUND; + // ResetSequenceInfo( ); + pev->framerate = 0; + + FallInit();// get ready to fall down + + m_iDefaultAmmo = TRIPMINE_DEFAULT_GIVE; + +#ifdef CLIENT_DLL + if ( !bIsMultiplayer() ) +#else + if ( !g_pGameRules->IsDeathmatch() ) +#endif + { + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 28) ); + } +} + +void CTripmine::Precache( void ) +{ + PRECACHE_MODEL ("models/v_tripmine.mdl"); + PRECACHE_MODEL ("models/p_tripmine.mdl"); + UTIL_PrecacheOther( "monster_tripmine" ); + + m_usTripFire = PRECACHE_EVENT( 1, "events/tripfire.sc" ); +} + +int CTripmine::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "Trip Mine"; + p->iMaxAmmo1 = TRIPMINE_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 4; + p->iPosition = 2; + p->iId = m_iId = WEAPON_TRIPMINE; + p->iWeight = TRIPMINE_WEIGHT; + p->iFlags = ITEM_FLAG_LIMITINWORLD | ITEM_FLAG_EXHAUSTIBLE; + + return 1; +} + +BOOL CTripmine::Deploy( ) +{ + //pev->body = 0; + return DefaultDeploy( "models/v_tripmine.mdl", "models/p_tripmine.mdl", TRIPMINE_DRAW, "trip" ); +} + + +void CTripmine::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + + if (!m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + // out of mines + m_pPlayer->pev->weapons &= ~(1<nextthink = gpGlobals->time + 0.1; + } + + SendWeaponAnim( TRIPMINE_HOLSTER ); + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "common/null.wav", 1.0, ATTN_NORM); +} + +void CTripmine::PrimaryAttack( void ) +{ + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + return; + + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = gpGlobals->v_forward; + + TraceResult tr; + + UTIL_TraceLine( vecSrc, vecSrc + vecAiming * 128, dont_ignore_monsters, ENT( m_pPlayer->pev ), &tr ); + + int flags; +#ifdef CLIENT_WEAPONS + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usTripFire, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0 ); + + if (tr.flFraction < 1.0) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + if ( pEntity && !(pEntity->pev->flags & FL_CONVEYOR) ) + { + Vector angles = UTIL_VecToAngles( tr.vecPlaneNormal ); + + CBaseEntity::Create( "monster_tripmine", tr.vecEndPos + tr.vecPlaneNormal * 8, angles, m_pPlayer->edict() ); + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0 ) + { + // no more mines! + RetireWeapon(); + return; + } + } + else + { + // ALERT( at_console, "no deploy\n" ); + } + } + else + { + + } + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.3; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); +} + +void CTripmine::WeaponIdle( void ) +{ + if ( m_flTimeWeaponIdle > UTIL_WeaponTimeBase() ) + return; + + if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] > 0 ) + { + SendWeaponAnim( TRIPMINE_DRAW ); + } + else + { + RetireWeapon(); + return; + } + + int iAnim; + float flRand = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 0, 1 ); + if (flRand <= 0.25) + { + iAnim = TRIPMINE_IDLE1; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 90.0 / 30.0; + } + else if (flRand <= 0.75) + { + iAnim = TRIPMINE_IDLE2; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 60.0 / 30.0; + } + else + { + iAnim = TRIPMINE_FIDGET; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 100.0 / 30.0; + } + + SendWeaponAnim( iAnim ); +} + + + + diff --git a/hlsdk/dlls/util.cpp b/hlsdk/dlls/util.cpp new file mode 100644 index 00000000..296b1b07 --- /dev/null +++ b/hlsdk/dlls/util.cpp @@ -0,0 +1,2545 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== util.cpp ======================================================== + + Utility code. Really not optional after all. + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include +#include "shake.h" +#include "decals.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" + +float UTIL_WeaponTimeBase( void ) +{ +#if defined( CLIENT_WEAPONS ) + return 0.0; +#else + return gpGlobals->time; +#endif +} + +static unsigned int glSeed = 0; + +unsigned int seed_table[ 256 ] = +{ + 28985, 27138, 26457, 9451, 17764, 10909, 28790, 8716, 6361, 4853, 17798, 21977, 19643, 20662, 10834, 20103, + 27067, 28634, 18623, 25849, 8576, 26234, 23887, 18228, 32587, 4836, 3306, 1811, 3035, 24559, 18399, 315, + 26766, 907, 24102, 12370, 9674, 2972, 10472, 16492, 22683, 11529, 27968, 30406, 13213, 2319, 23620, 16823, + 10013, 23772, 21567, 1251, 19579, 20313, 18241, 30130, 8402, 20807, 27354, 7169, 21211, 17293, 5410, 19223, + 10255, 22480, 27388, 9946, 15628, 24389, 17308, 2370, 9530, 31683, 25927, 23567, 11694, 26397, 32602, 15031, + 18255, 17582, 1422, 28835, 23607, 12597, 20602, 10138, 5212, 1252, 10074, 23166, 19823, 31667, 5902, 24630, + 18948, 14330, 14950, 8939, 23540, 21311, 22428, 22391, 3583, 29004, 30498, 18714, 4278, 2437, 22430, 3439, + 28313, 23161, 25396, 13471, 19324, 15287, 2563, 18901, 13103, 16867, 9714, 14322, 15197, 26889, 19372, 26241, + 31925, 14640, 11497, 8941, 10056, 6451, 28656, 10737, 13874, 17356, 8281, 25937, 1661, 4850, 7448, 12744, + 21826, 5477, 10167, 16705, 26897, 8839, 30947, 27978, 27283, 24685, 32298, 3525, 12398, 28726, 9475, 10208, + 617, 13467, 22287, 2376, 6097, 26312, 2974, 9114, 21787, 28010, 4725, 15387, 3274, 10762, 31695, 17320, + 18324, 12441, 16801, 27376, 22464, 7500, 5666, 18144, 15314, 31914, 31627, 6495, 5226, 31203, 2331, 4668, + 12650, 18275, 351, 7268, 31319, 30119, 7600, 2905, 13826, 11343, 13053, 15583, 30055, 31093, 5067, 761, + 9685, 11070, 21369, 27155, 3663, 26542, 20169, 12161, 15411, 30401, 7580, 31784, 8985, 29367, 20989, 14203, + 29694, 21167, 10337, 1706, 28578, 887, 3373, 19477, 14382, 675, 7033, 15111, 26138, 12252, 30996, 21409, + 25678, 18555, 13256, 23316, 22407, 16727, 991, 9236, 5373, 29402, 6117, 15241, 27715, 19291, 19888, 19847 +}; + +unsigned int U_Random( void ) +{ + glSeed *= 69069; + glSeed += seed_table[ glSeed & 0xff ]; + + return ( ++glSeed & 0x0fffffff ); +} + +void U_Srand( unsigned int seed ) +{ + glSeed = seed_table[ seed & 0xff ]; +} + +/* +===================== +UTIL_SharedRandomLong +===================== +*/ +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ) +{ + unsigned int range; + + U_Srand( (int)seed + low + high ); + + range = high - low + 1; + if ( !(range - 1) ) + { + return low; + } + else + { + int offset; + int rnum; + + rnum = U_Random(); + + offset = rnum % range; + + return (low + offset); + } +} + +/* +===================== +UTIL_SharedRandomFloat +===================== +*/ +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ) +{ + // + unsigned int range; + + U_Srand( (int)seed + *(int *)&low + *(int *)&high ); + + U_Random(); + U_Random(); + + range = static_cast(high - low); + if ( !range ) + { + return low; + } + else + { + int tensixrand; + float offset; + + tensixrand = U_Random() & 65535; + + offset = (float)tensixrand / 65536.0; + + return (low + offset * range ); + } +} + +void UTIL_ParametricRocket( entvars_t *pev, Vector vecOrigin, Vector vecAngles, edict_t *owner ) +{ + pev->startpos = vecOrigin; + // Trace out line to end pos + TraceResult tr; + UTIL_MakeVectors( vecAngles ); + UTIL_TraceLine( pev->startpos, pev->startpos + gpGlobals->v_forward * 8192, ignore_monsters, owner, &tr); + pev->endpos = tr.vecEndPos; + + // Now compute how long it will take based on current velocity + Vector vecTravel = pev->endpos - pev->startpos; + float travelTime = 0.0; + if ( pev->velocity.Length() > 0 ) + { + travelTime = vecTravel.Length() / pev->velocity.Length(); + } + pev->starttime = gpGlobals->time; + pev->impacttime = gpGlobals->time + travelTime; +} + +int g_groupmask = 0; +int g_groupop = 0; + +// Normal overrides +void UTIL_SetGroupTrace( int groupmask, int op ) +{ + g_groupmask = groupmask; + g_groupop = op; + + ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); +} + +void UTIL_UnsetGroupTrace( void ) +{ + g_groupmask = 0; + g_groupop = 0; + + ENGINE_SETGROUPMASK( 0, 0 ); +} + +// Smart version, it'll clean itself up when it pops off stack +UTIL_GroupTrace::UTIL_GroupTrace( int groupmask, int op ) +{ + m_oldgroupmask = g_groupmask; + m_oldgroupop = g_groupop; + + g_groupmask = groupmask; + g_groupop = op; + + ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); +} + +UTIL_GroupTrace::~UTIL_GroupTrace( void ) +{ + g_groupmask = m_oldgroupmask; + g_groupop = m_oldgroupop; + + ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); +} + +TYPEDESCRIPTION gEntvarsDescription[] = +{ + DEFINE_ENTITY_FIELD( classname, FIELD_STRING ), + DEFINE_ENTITY_GLOBAL_FIELD( globalname, FIELD_STRING ), + + DEFINE_ENTITY_FIELD( origin, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_FIELD( oldorigin, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_FIELD( velocity, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( basevelocity, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( movedir, FIELD_VECTOR ), + + DEFINE_ENTITY_FIELD( angles, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( avelocity, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( punchangle, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( v_angle, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( fixangle, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( idealpitch, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( pitch_speed, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( ideal_yaw, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( yaw_speed, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( modelindex, FIELD_INTEGER ), + DEFINE_ENTITY_GLOBAL_FIELD( model, FIELD_MODELNAME ), + + DEFINE_ENTITY_FIELD( viewmodel, FIELD_MODELNAME ), + DEFINE_ENTITY_FIELD( weaponmodel, FIELD_MODELNAME ), + + DEFINE_ENTITY_FIELD( absmin, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_FIELD( absmax, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_GLOBAL_FIELD( mins, FIELD_VECTOR ), + DEFINE_ENTITY_GLOBAL_FIELD( maxs, FIELD_VECTOR ), + DEFINE_ENTITY_GLOBAL_FIELD( size, FIELD_VECTOR ), + + DEFINE_ENTITY_FIELD( ltime, FIELD_TIME ), + DEFINE_ENTITY_FIELD( nextthink, FIELD_TIME ), + + DEFINE_ENTITY_FIELD( solid, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( movetype, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( skin, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( body, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( effects, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( gravity, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( friction, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( light_level, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( frame, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( scale, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( sequence, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( animtime, FIELD_TIME ), + DEFINE_ENTITY_FIELD( framerate, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( controller, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( blending, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( rendermode, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( renderamt, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( rendercolor, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( renderfx, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( health, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( frags, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( weapons, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( takedamage, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( deadflag, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( view_ofs, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( button, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( impulse, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( chain, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( dmg_inflictor, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( enemy, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( aiment, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( owner, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( groundentity, FIELD_EDICT ), + + DEFINE_ENTITY_FIELD( spawnflags, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( flags, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( colormap, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( team, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( max_health, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( teleport_time, FIELD_TIME ), + DEFINE_ENTITY_FIELD( armortype, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( armorvalue, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( waterlevel, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( watertype, FIELD_INTEGER ), + + // Having these fields be local to the individual levels makes it easier to test those levels individually. + DEFINE_ENTITY_GLOBAL_FIELD( target, FIELD_STRING ), + DEFINE_ENTITY_GLOBAL_FIELD( targetname, FIELD_STRING ), + DEFINE_ENTITY_FIELD( netname, FIELD_STRING ), + DEFINE_ENTITY_FIELD( message, FIELD_STRING ), + + DEFINE_ENTITY_FIELD( dmg_take, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( dmg_save, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( dmg, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( dmgtime, FIELD_TIME ), + + DEFINE_ENTITY_FIELD( noise, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( noise1, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( noise2, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( noise3, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( speed, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( air_finished, FIELD_TIME ), + DEFINE_ENTITY_FIELD( pain_finished, FIELD_TIME ), + DEFINE_ENTITY_FIELD( radsuit_finished, FIELD_TIME ), +}; + +#define ENTVARS_COUNT (sizeof(gEntvarsDescription)/sizeof(gEntvarsDescription[0])) + + +#ifdef DEBUG +edict_t *DBG_EntOfVars( const entvars_t *pev ) +{ + if (pev->pContainingEntity != NULL) + return pev->pContainingEntity; + ALERT(at_console, "entvars_t pContainingEntity is NULL, calling into engine"); + edict_t* pent = (*g_engfuncs.pfnFindEntityByVars)((entvars_t*)pev); + if (pent == NULL) + ALERT(at_console, "DAMN! Even the engine couldn't FindEntityByVars!"); + ((entvars_t *)pev)->pContainingEntity = pent; + return pent; +} +#endif //DEBUG + + +#ifdef DEBUG + void +DBG_AssertFunction( + BOOL fExpr, + const char* szExpr, + const char* szFile, + int szLine, + const char* szMessage) + { + if (fExpr) + return; + char szOut[512]; + if (szMessage != NULL) + sprintf(szOut, "ASSERT FAILED:\n %s \n(%s@%d)\n%s", szExpr, szFile, szLine, szMessage); + else + sprintf(szOut, "ASSERT FAILED:\n %s \n(%s@%d)", szExpr, szFile, szLine); + ALERT(at_console, szOut); + } +#endif // DEBUG + +BOOL UTIL_GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) +{ + return g_pGameRules->GetNextBestWeapon( pPlayer, pCurrentWeapon ); +} + +// ripped this out of the engine +float UTIL_AngleMod(float a) +{ + if (a < 0) + { + a = a + 360 * ((int)(a / 360) + 1); + } + else if (a >= 360) + { + a = a - 360 * ((int)(a / 360)); + } + // a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); + return a; +} + +float UTIL_AngleDiff( float destAngle, float srcAngle ) +{ + float delta; + + delta = destAngle - srcAngle; + if ( destAngle > srcAngle ) + { + if ( delta >= 180 ) + delta -= 360; + } + else + { + if ( delta <= -180 ) + delta += 360; + } + return delta; +} + +Vector UTIL_VecToAngles( const Vector &vec ) +{ + float rgflVecOut[3]; + VEC_TO_ANGLES(vec, rgflVecOut); + return Vector(rgflVecOut); +} + +// float UTIL_MoveToOrigin( edict_t *pent, const Vector vecGoal, float flDist, int iMoveType ) +void UTIL_MoveToOrigin( edict_t *pent, const Vector &vecGoal, float flDist, int iMoveType ) +{ + float rgfl[3]; + vecGoal.CopyToArray(rgfl); +// return MOVE_TO_ORIGIN ( pent, rgfl, flDist, iMoveType ); + MOVE_TO_ORIGIN ( pent, rgfl, flDist, iMoveType ); +} + + +int UTIL_EntitiesInBox( CBaseEntity **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + int count; + + count = 0; + + if ( !pEdict ) + return count; + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + if ( pEdict->free ) // Not in use + continue; + + if ( flagMask && !(pEdict->v.flags & flagMask) ) // Does it meet the criteria? + continue; + + if ( mins.x > pEdict->v.absmax.x || + mins.y > pEdict->v.absmax.y || + mins.z > pEdict->v.absmax.z || + maxs.x < pEdict->v.absmin.x || + maxs.y < pEdict->v.absmin.y || + maxs.z < pEdict->v.absmin.z ) + continue; + + pEntity = CBaseEntity::Instance(pEdict); + if ( !pEntity ) + continue; + + pList[ count ] = pEntity; + count++; + + if ( count >= listMax ) + return count; + } + + return count; +} + + +int UTIL_MonstersInSphere( CBaseEntity **pList, int listMax, const Vector ¢er, float radius ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + int count; + float distance, delta; + + count = 0; + float radiusSquared = radius * radius; + + if ( !pEdict ) + return count; + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + if ( pEdict->free ) // Not in use + continue; + + if ( !(pEdict->v.flags & (FL_CLIENT|FL_MONSTER)) ) // Not a client/monster ? + continue; + + // Use origin for X & Y since they are centered for all monsters + // Now X + delta = center.x - pEdict->v.origin.x;//(pEdict->v.absmin.x + pEdict->v.absmax.x)*0.5; + delta *= delta; + + if ( delta > radiusSquared ) + continue; + distance = delta; + + // Now Y + delta = center.y - pEdict->v.origin.y;//(pEdict->v.absmin.y + pEdict->v.absmax.y)*0.5; + delta *= delta; + + distance += delta; + if ( distance > radiusSquared ) + continue; + + // Now Z + delta = center.z - (pEdict->v.absmin.z + pEdict->v.absmax.z)*0.5; + delta *= delta; + + distance += delta; + if ( distance > radiusSquared ) + continue; + + pEntity = CBaseEntity::Instance(pEdict); + if ( !pEntity ) + continue; + + pList[ count ] = pEntity; + count++; + + if ( count >= listMax ) + return count; + } + + + return count; +} + + +CBaseEntity *UTIL_FindEntityInSphere( CBaseEntity *pStartEntity, const Vector &vecCenter, float flRadius ) +{ + edict_t *pentEntity; + + if (pStartEntity) + pentEntity = pStartEntity->edict(); + else + pentEntity = NULL; + + pentEntity = FIND_ENTITY_IN_SPHERE( pentEntity, vecCenter, flRadius); + + if (!FNullEnt(pentEntity)) + return CBaseEntity::Instance(pentEntity); + return NULL; +} + + +CBaseEntity *UTIL_FindEntityByString( CBaseEntity *pStartEntity, const char *szKeyword, const char *szValue ) +{ + edict_t *pentEntity; + + if (pStartEntity) + pentEntity = pStartEntity->edict(); + else + pentEntity = NULL; + + pentEntity = FIND_ENTITY_BY_STRING( pentEntity, szKeyword, szValue ); + + if (!FNullEnt(pentEntity)) + return CBaseEntity::Instance(pentEntity); + return NULL; +} + +CBaseEntity *UTIL_FindEntityByClassname( CBaseEntity *pStartEntity, const char *szName ) +{ + return UTIL_FindEntityByString( pStartEntity, "classname", szName ); +} + +CBaseEntity *UTIL_FindEntityByTargetname( CBaseEntity *pStartEntity, const char *szName ) +{ + return UTIL_FindEntityByString( pStartEntity, "targetname", szName ); +} + + +CBaseEntity *UTIL_FindEntityGeneric( const char *szWhatever, Vector &vecSrc, float flRadius ) +{ + CBaseEntity *pEntity = NULL; + + pEntity = UTIL_FindEntityByTargetname( NULL, szWhatever ); + if (pEntity) + return pEntity; + + CBaseEntity *pSearch = NULL; + float flMaxDist2 = flRadius * flRadius; + while ((pSearch = UTIL_FindEntityByClassname( pSearch, szWhatever )) != NULL) + { + float flDist2 = (pSearch->pev->origin - vecSrc).Length(); + flDist2 = flDist2 * flDist2; + if (flMaxDist2 > flDist2) + { + pEntity = pSearch; + flMaxDist2 = flDist2; + } + } + return pEntity; +} + + +// returns a CBaseEntity pointer to a player by index. Only returns if the player is spawned and connected +// otherwise returns NULL +// Index is 1 based +CBaseEntity *UTIL_PlayerByIndex( int playerIndex ) +{ + CBaseEntity *pPlayer = NULL; + + if ( playerIndex > 0 && playerIndex <= gpGlobals->maxClients ) + { + edict_t *pPlayerEdict = INDEXENT( playerIndex ); + if ( pPlayerEdict && !pPlayerEdict->free ) + { + pPlayer = CBaseEntity::Instance( pPlayerEdict ); + } + } + + return pPlayer; +} + + +void UTIL_MakeVectors( const Vector &vecAngles ) +{ + MAKE_VECTORS( vecAngles ); +} + + +void UTIL_MakeAimVectors( const Vector &vecAngles ) +{ + float rgflVec[3]; + vecAngles.CopyToArray(rgflVec); + rgflVec[0] = -rgflVec[0]; + MAKE_VECTORS(rgflVec); +} + + +#define SWAP(a,b,temp) ((temp)=(a),(a)=(b),(b)=(temp)) + +void UTIL_MakeInvVectors( const Vector &vec, globalvars_t *pgv ) +{ + MAKE_VECTORS(vec); + + float tmp; + pgv->v_right = pgv->v_right * -1; + + SWAP(pgv->v_forward.y, pgv->v_right.x, tmp); + SWAP(pgv->v_forward.z, pgv->v_up.x, tmp); + SWAP(pgv->v_right.z, pgv->v_up.y, tmp); +} + + +void UTIL_EmitAmbientSound( edict_t *entity, const Vector &vecOrigin, const char *samp, float vol, float attenuation, int fFlags, int pitch ) +{ + float rgfl[3]; + vecOrigin.CopyToArray(rgfl); + + if (samp && *samp == '!') + { + char name[32]; + if (SENTENCEG_Lookup(samp, name) >= 0) + EMIT_AMBIENT_SOUND(entity, rgfl, name, vol, attenuation, fFlags, pitch); + } + else + EMIT_AMBIENT_SOUND(entity, rgfl, samp, vol, attenuation, fFlags, pitch); +} + +static unsigned short FixedUnsigned16( float value, float scale ) +{ + int output; + + output = static_cast(value * scale); + if ( output < 0 ) + output = 0; + if ( output > 0xFFFF ) + output = 0xFFFF; + + return (unsigned short)output; +} + +static short FixedSigned16( float value, float scale ) +{ + int output; + + output = static_cast(value * scale); + + if ( output > 32767 ) + output = 32767; + + if ( output < -32768 ) + output = -32768; + + return (short)output; +} + +// Shake the screen of all clients within radius +// radius == 0, shake all clients +// UNDONE: Allow caller to shake clients not ONGROUND? +// UNDONE: Fix falloff model (disabled)? +// UNDONE: Affect user controls? +void UTIL_ScreenShake( const Vector ¢er, float amplitude, float frequency, float duration, float radius ) +{ + int i; + float localAmplitude; + ScreenShake shake; + + shake.duration = FixedUnsigned16( duration, 1<<12 ); // 4.12 fixed + shake.frequency = FixedUnsigned16( frequency, 1<<8 ); // 8.8 fixed + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + if ( !pPlayer || !(pPlayer->pev->flags & FL_ONGROUND) ) // Don't shake if not onground + continue; + + localAmplitude = 0; + + if ( radius <= 0 ) + localAmplitude = amplitude; + else + { + Vector delta = center - pPlayer->pev->origin; + float distance = delta.Length(); + + // Had to get rid of this falloff - it didn't work well + if ( distance < radius ) + localAmplitude = amplitude;//radius - distance; + } + if ( localAmplitude ) + { + shake.amplitude = FixedUnsigned16( localAmplitude, 1<<12 ); // 4.12 fixed + + MESSAGE_BEGIN( MSG_ONE, gmsgShake, NULL, pPlayer->edict() ); // use the magic #1 for "one client" + + WRITE_SHORT( shake.amplitude ); // shake amount + WRITE_SHORT( shake.duration ); // shake lasts this long + WRITE_SHORT( shake.frequency ); // shake noise frequency + + MESSAGE_END(); + } + } +} + + + +void UTIL_ScreenShakeAll( const Vector ¢er, float amplitude, float frequency, float duration ) +{ + UTIL_ScreenShake( center, amplitude, frequency, duration, 0 ); +} + + +void UTIL_ScreenFadeBuild( ScreenFade &fade, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) +{ + fade.duration = FixedUnsigned16( fadeTime, 1<<12 ); // 4.12 fixed + fade.holdTime = FixedUnsigned16( fadeHold, 1<<12 ); // 4.12 fixed + fade.r = (int)color.x; + fade.g = (int)color.y; + fade.b = (int)color.z; + fade.a = alpha; + fade.fadeFlags = flags; +} + + +void UTIL_ScreenFadeWrite( const ScreenFade &fade, CBaseEntity *pEntity ) +{ + if ( !pEntity || !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, gmsgFade, NULL, pEntity->edict() ); // use the magic #1 for "one client" + + WRITE_SHORT( fade.duration ); // fade lasts this long + WRITE_SHORT( fade.holdTime ); // fade lasts this long + WRITE_SHORT( fade.fadeFlags ); // fade type (in / out) + WRITE_BYTE( fade.r ); // fade red + WRITE_BYTE( fade.g ); // fade green + WRITE_BYTE( fade.b ); // fade blue + WRITE_BYTE( fade.a ); // fade blue + + MESSAGE_END(); +} + + +void UTIL_ScreenFadeAll( const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) +{ + int i; + ScreenFade fade; + + + UTIL_ScreenFadeBuild( fade, color, fadeTime, fadeHold, alpha, flags ); + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + UTIL_ScreenFadeWrite( fade, pPlayer ); + } +} + + +void UTIL_ScreenFade( CBaseEntity *pEntity, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) +{ + ScreenFade fade; + + UTIL_ScreenFadeBuild( fade, color, fadeTime, fadeHold, alpha, flags ); + UTIL_ScreenFadeWrite( fade, pEntity ); +} + + +void UTIL_HudMessage( CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage ) +{ + if ( !pEntity || !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, SVC_TEMPENTITY, NULL, pEntity->edict() ); + WRITE_BYTE( TE_TEXTMESSAGE ); + WRITE_BYTE( textparms.channel & 0xFF ); + + WRITE_SHORT( FixedSigned16( textparms.x, 1<<13 ) ); + WRITE_SHORT( FixedSigned16( textparms.y, 1<<13 ) ); + WRITE_BYTE( textparms.effect ); + + WRITE_BYTE( textparms.r1 ); + WRITE_BYTE( textparms.g1 ); + WRITE_BYTE( textparms.b1 ); + WRITE_BYTE( textparms.a1 ); + + WRITE_BYTE( textparms.r2 ); + WRITE_BYTE( textparms.g2 ); + WRITE_BYTE( textparms.b2 ); + WRITE_BYTE( textparms.a2 ); + + WRITE_SHORT( FixedUnsigned16( textparms.fadeinTime, 1<<8 ) ); + WRITE_SHORT( FixedUnsigned16( textparms.fadeoutTime, 1<<8 ) ); + WRITE_SHORT( FixedUnsigned16( textparms.holdTime, 1<<8 ) ); + + if ( textparms.effect == 2 ) + WRITE_SHORT( FixedUnsigned16( textparms.fxTime, 1<<8 ) ); + + if ( strlen( pMessage ) < 512 ) + { + WRITE_STRING( pMessage ); + } + else + { + char tmp[512]; + strncpy( tmp, pMessage, 511 ); + tmp[511] = 0; + WRITE_STRING( tmp ); + } + MESSAGE_END(); +} + +void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage ) +{ + int i; + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + UTIL_HudMessage( pPlayer, textparms, pMessage ); + } +} + + +extern int gmsgTextMsg, gmsgSayText; +void UTIL_ClientPrintAll( int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) +{ + MESSAGE_BEGIN( MSG_ALL, gmsgTextMsg ); + WRITE_BYTE( msg_dest ); + WRITE_STRING( msg_name ); + + if ( param1 ) + WRITE_STRING( param1 ); + if ( param2 ) + WRITE_STRING( param2 ); + if ( param3 ) + WRITE_STRING( param3 ); + if ( param4 ) + WRITE_STRING( param4 ); + + MESSAGE_END(); +} + +void ClientPrint( entvars_t *client, int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) +{ + MESSAGE_BEGIN( MSG_ONE, gmsgTextMsg, NULL, client ); + WRITE_BYTE( msg_dest ); + WRITE_STRING( msg_name ); + + if ( param1 ) + WRITE_STRING( param1 ); + if ( param2 ) + WRITE_STRING( param2 ); + if ( param3 ) + WRITE_STRING( param3 ); + if ( param4 ) + WRITE_STRING( param4 ); + + MESSAGE_END(); +} + +void UTIL_SayText( const char *pText, CBaseEntity *pEntity ) +{ + if ( !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, pEntity->edict() ); + WRITE_BYTE( pEntity->entindex() ); + WRITE_STRING( pText ); + MESSAGE_END(); +} + +void UTIL_SayTextAll( const char *pText, CBaseEntity *pEntity ) +{ + MESSAGE_BEGIN( MSG_ALL, gmsgSayText, NULL ); + WRITE_BYTE( pEntity->entindex() ); + WRITE_STRING( pText ); + MESSAGE_END(); +} + + +char *UTIL_dtos1( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +char *UTIL_dtos2( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +char *UTIL_dtos3( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +char *UTIL_dtos4( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +void UTIL_ShowMessage( const char *pString, CBaseEntity *pEntity ) +{ + if ( !pEntity || !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, gmsgHudText, NULL, pEntity->edict() ); + WRITE_STRING( pString ); + MESSAGE_END(); +} + + +void UTIL_ShowMessageAll( const char *pString ) +{ + int i; + + // loop through all players + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + UTIL_ShowMessage( pString, pPlayer ); + } +} + +// Overloaded to add IGNORE_GLASS +void UTIL_TraceLine( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, IGNORE_GLASS ignoreGlass, edict_t *pentIgnore, TraceResult *ptr ) +{ + TRACE_LINE( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE) | (ignoreGlass?0x100:0), pentIgnore, ptr ); +} + + +void UTIL_TraceLine( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, edict_t *pentIgnore, TraceResult *ptr ) +{ + TRACE_LINE( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE), pentIgnore, ptr ); +} + + +void UTIL_TraceHull( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, int hullNumber, edict_t *pentIgnore, TraceResult *ptr ) +{ + TRACE_HULL( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE), hullNumber, pentIgnore, ptr ); +} + +void UTIL_TraceModel( const Vector &vecStart, const Vector &vecEnd, int hullNumber, edict_t *pentModel, TraceResult *ptr ) +{ + g_engfuncs.pfnTraceModel( vecStart, vecEnd, hullNumber, pentModel, ptr ); +} + + +TraceResult UTIL_GetGlobalTrace( ) +{ + TraceResult tr; + + tr.fAllSolid = static_cast(gpGlobals->trace_allsolid); + tr.fStartSolid = static_cast(gpGlobals->trace_startsolid); + tr.fInOpen = static_cast(gpGlobals->trace_inopen); + tr.fInWater = static_cast(gpGlobals->trace_inwater); + tr.flFraction = gpGlobals->trace_fraction; + tr.flPlaneDist = gpGlobals->trace_plane_dist; + tr.pHit = gpGlobals->trace_ent; + tr.vecEndPos = gpGlobals->trace_endpos; + tr.vecPlaneNormal = gpGlobals->trace_plane_normal; + tr.iHitgroup = gpGlobals->trace_hitgroup; + return tr; +} + + +void UTIL_SetSize( entvars_t *pev, const Vector &vecMin, const Vector &vecMax ) +{ + SET_SIZE( ENT(pev), vecMin, vecMax ); +} + + +float UTIL_VecToYaw( const Vector &vec ) +{ + return VEC_TO_YAW(vec); +} + + +void UTIL_SetOrigin( entvars_t *pev, const Vector &vecOrigin ) +{ + SET_ORIGIN(ENT(pev), vecOrigin ); +} + +void UTIL_ParticleEffect( const Vector &vecOrigin, const Vector &vecDirection, ULONG ulColor, ULONG ulCount ) +{ + PARTICLE_EFFECT( vecOrigin, vecDirection, (float)ulColor, (float)ulCount ); +} + + +float UTIL_Approach( float target, float value, float speed ) +{ + float delta = target - value; + + if ( delta > speed ) + value += speed; + else if ( delta < -speed ) + value -= speed; + else + value = target; + + return value; +} + + +float UTIL_ApproachAngle( float target, float value, float speed ) +{ + target = UTIL_AngleMod( target ); + value = UTIL_AngleMod( target ); + + float delta = target - value; + + // Speed is assumed to be positive + if ( speed < 0 ) + speed = -speed; + + if ( delta < -180 ) + delta += 360; + else if ( delta > 180 ) + delta -= 360; + + if ( delta > speed ) + value += speed; + else if ( delta < -speed ) + value -= speed; + else + value = target; + + return value; +} + + +float UTIL_AngleDistance( float next, float cur ) +{ + float delta = next - cur; + + if ( delta < -180 ) + delta += 360; + else if ( delta > 180 ) + delta -= 360; + + return delta; +} + + +float UTIL_SplineFraction( float value, float scale ) +{ + value = scale * value; + float valueSquared = value * value; + + // Nice little ease-in, ease-out spline-like curve + return 3 * valueSquared - 2 * valueSquared * value; +} + + +char* UTIL_VarArgs( char *format, ... ) +{ + va_list argptr; + static char string[1024]; + + va_start (argptr, format); + vsprintf (string, format,argptr); + va_end (argptr); + + return string; +} + +Vector UTIL_GetAimVector( edict_t *pent, float flSpeed ) +{ + Vector tmp; + GET_AIM_VECTOR(pent, flSpeed, tmp); + return tmp; +} + +int UTIL_IsMasterTriggered(string_t sMaster, CBaseEntity *pActivator) +{ + if (sMaster) + { + edict_t *pentTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(sMaster)); + + if ( !FNullEnt(pentTarget) ) + { + CBaseEntity *pMaster = CBaseEntity::Instance(pentTarget); + if ( pMaster && (pMaster->ObjectCaps() & FCAP_MASTER) ) + return pMaster->IsTriggered( pActivator ); + } + + ALERT(at_console, "Master was null or not a master!\n"); + } + + // if this isn't a master entity, just say yes. + return 1; +} + +BOOL UTIL_ShouldShowBlood( int color ) +{ + if ( color != DONT_BLEED ) + { + if ( color == BLOOD_COLOR_RED ) + { + if ( CVAR_GET_FLOAT("violence_hblood") != 0 ) + return TRUE; + } + else + { + if ( CVAR_GET_FLOAT("violence_ablood") != 0 ) + return TRUE; + } + } + return FALSE; +} + +int UTIL_PointContents( const Vector &vec ) +{ + return POINT_CONTENTS(vec); +} + +void UTIL_BloodStream( const Vector &origin, const Vector &direction, int color, int amount ) +{ + if ( !UTIL_ShouldShowBlood( color ) ) + return; + + if ( g_Language == LANGUAGE_GERMAN && color == BLOOD_COLOR_RED ) + color = 0; + + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, origin ); + WRITE_BYTE( TE_BLOODSTREAM ); + WRITE_COORD( origin.x ); + WRITE_COORD( origin.y ); + WRITE_COORD( origin.z ); + WRITE_COORD( direction.x ); + WRITE_COORD( direction.y ); + WRITE_COORD( direction.z ); + WRITE_BYTE( color ); + WRITE_BYTE( min( amount, 255 ) ); + MESSAGE_END(); +} + +void UTIL_BloodDrips( const Vector &origin, const Vector &direction, int color, int amount ) +{ + if ( !UTIL_ShouldShowBlood( color ) ) + return; + + if ( color == DONT_BLEED || amount == 0 ) + return; + + if ( g_Language == LANGUAGE_GERMAN && color == BLOOD_COLOR_RED ) + color = 0; + + if ( g_pGameRules->IsMultiplayer() ) + { + // scale up blood effect in multiplayer for better visibility + amount *= 2; + } + + if ( amount > 255 ) + amount = 255; + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, origin ); + WRITE_BYTE( TE_BLOODSPRITE ); + WRITE_COORD( origin.x); // pos + WRITE_COORD( origin.y); + WRITE_COORD( origin.z); + WRITE_SHORT( g_sModelIndexBloodSpray ); // initial sprite model + 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(); +} + +Vector UTIL_RandomBloodVector( void ) +{ + Vector direction; + + direction.x = RANDOM_FLOAT ( -1, 1 ); + direction.y = RANDOM_FLOAT ( -1, 1 ); + direction.z = RANDOM_FLOAT ( 0, 1 ); + + return direction; +} + + +void UTIL_BloodDecalTrace( TraceResult *pTrace, int bloodColor ) +{ + if ( UTIL_ShouldShowBlood( bloodColor ) ) + { + if ( bloodColor == BLOOD_COLOR_RED ) + UTIL_DecalTrace( pTrace, DECAL_BLOOD1 + RANDOM_LONG(0,5) ); + else + UTIL_DecalTrace( pTrace, DECAL_YBLOOD1 + RANDOM_LONG(0,5) ); + } +} + + +void UTIL_DecalTrace( TraceResult *pTrace, int decalNumber ) +{ + short entityIndex; + int index; + int message; + + if ( decalNumber < 0 ) + return; + + index = gDecals[ decalNumber ].index; + + if ( index < 0 ) + return; + + if (pTrace->flFraction == 1.0) + return; + + // Only decal BSP models + if ( pTrace->pHit ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( pTrace->pHit ); + if ( pEntity && !pEntity->IsBSPModel() ) + return; + entityIndex = ENTINDEX( pTrace->pHit ); + } + else + entityIndex = 0; + + message = TE_DECAL; + if ( entityIndex != 0 ) + { + if ( index > 255 ) + { + message = TE_DECALHIGH; + index -= 256; + } + } + else + { + message = TE_WORLDDECAL; + if ( index > 255 ) + { + message = TE_WORLDDECALHIGH; + index -= 256; + } + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( message ); + WRITE_COORD( pTrace->vecEndPos.x ); + WRITE_COORD( pTrace->vecEndPos.y ); + WRITE_COORD( pTrace->vecEndPos.z ); + WRITE_BYTE( index ); + if ( entityIndex ) + WRITE_SHORT( entityIndex ); + MESSAGE_END(); +} + +/* +============== +UTIL_PlayerDecalTrace + +A player is trying to apply his custom decal for the spray can. +Tell connected clients to display it, or use the default spray can decal +if the custom can't be loaded. +============== +*/ +void UTIL_PlayerDecalTrace( TraceResult *pTrace, int playernum, int decalNumber, BOOL bIsCustom ) +{ + int index; + + if (!bIsCustom) + { + if ( decalNumber < 0 ) + return; + + index = gDecals[ decalNumber ].index; + if ( index < 0 ) + return; + } + else + index = decalNumber; + + if (pTrace->flFraction == 1.0) + return; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_PLAYERDECAL ); + WRITE_BYTE ( playernum ); + WRITE_COORD( pTrace->vecEndPos.x ); + WRITE_COORD( pTrace->vecEndPos.y ); + WRITE_COORD( pTrace->vecEndPos.z ); + WRITE_SHORT( (short)ENTINDEX(pTrace->pHit) ); + WRITE_BYTE( index ); + MESSAGE_END(); +} + +void UTIL_GunshotDecalTrace( TraceResult *pTrace, int decalNumber ) +{ + if ( decalNumber < 0 ) + return; + + int index = gDecals[ decalNumber ].index; + if ( index < 0 ) + return; + + if (pTrace->flFraction == 1.0) + return; + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pTrace->vecEndPos ); + WRITE_BYTE( TE_GUNSHOTDECAL ); + WRITE_COORD( pTrace->vecEndPos.x ); + WRITE_COORD( pTrace->vecEndPos.y ); + WRITE_COORD( pTrace->vecEndPos.z ); + WRITE_SHORT( (short)ENTINDEX(pTrace->pHit) ); + WRITE_BYTE( index ); + MESSAGE_END(); +} + + +void UTIL_Sparks( const Vector &position ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, position ); + WRITE_BYTE( TE_SPARKS ); + WRITE_COORD( position.x ); + WRITE_COORD( position.y ); + WRITE_COORD( position.z ); + MESSAGE_END(); +} + + +void UTIL_Ricochet( const Vector &position, float scale ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, position ); + WRITE_BYTE( TE_ARMOR_RICOCHET ); + WRITE_COORD( position.x ); + WRITE_COORD( position.y ); + WRITE_COORD( position.z ); + WRITE_BYTE( (int)(scale*10) ); + MESSAGE_END(); +} + + +BOOL UTIL_TeamsMatch( const char *pTeamName1, const char *pTeamName2 ) +{ + // Everyone matches unless it's teamplay + if ( !g_pGameRules->IsTeamplay() ) + return TRUE; + + // Both on a team? + if ( *pTeamName1 != 0 && *pTeamName2 != 0 ) + { + if ( !stricmp( pTeamName1, pTeamName2 ) ) // Same Team? + return TRUE; + } + + return FALSE; +} + + +void UTIL_StringToVector( float *pVector, const char *pString ) +{ + char *pstr, *pfront, tempString[128]; + int j; + + strcpy( tempString, pString ); + pstr = pfront = tempString; + + for ( j = 0; j < 3; j++ ) // lifted from pr_edict.c + { + pVector[j] = atof( pfront ); + + while ( *pstr && *pstr != ' ' ) + pstr++; + if (!*pstr) + break; + pstr++; + pfront = pstr; + } + if (j < 2) + { + /* + ALERT( at_error, "Bad field in entity!! %s:%s == \"%s\"\n", + pkvd->szClassName, pkvd->szKeyName, pkvd->szValue ); + */ + for (j = j+1;j < 3; j++) + pVector[j] = 0; + } +} + + +void UTIL_StringToIntArray( int *pVector, int count, const char *pString ) +{ + char *pstr, *pfront, tempString[128]; + int j; + + strcpy( tempString, pString ); + pstr = pfront = tempString; + + for ( j = 0; j < count; j++ ) // lifted from pr_edict.c + { + pVector[j] = atoi( pfront ); + + while ( *pstr && *pstr != ' ' ) + pstr++; + if (!*pstr) + break; + pstr++; + pfront = pstr; + } + + for ( j++; j < count; j++ ) + { + pVector[j] = 0; + } +} + +Vector UTIL_ClampVectorToBox( const Vector &input, const Vector &clampSize ) +{ + Vector sourceVector = input; + + if ( sourceVector.x > clampSize.x ) + sourceVector.x -= clampSize.x; + else if ( sourceVector.x < -clampSize.x ) + sourceVector.x += clampSize.x; + else + sourceVector.x = 0; + + if ( sourceVector.y > clampSize.y ) + sourceVector.y -= clampSize.y; + else if ( sourceVector.y < -clampSize.y ) + sourceVector.y += clampSize.y; + else + sourceVector.y = 0; + + if ( sourceVector.z > clampSize.z ) + sourceVector.z -= clampSize.z; + else if ( sourceVector.z < -clampSize.z ) + sourceVector.z += clampSize.z; + else + sourceVector.z = 0; + + return sourceVector.Normalize(); +} + + +float UTIL_WaterLevel( const Vector &position, float minz, float maxz ) +{ + Vector midUp = position; + midUp.z = minz; + + if (UTIL_PointContents(midUp) != CONTENTS_WATER) + return minz; + + midUp.z = maxz; + if (UTIL_PointContents(midUp) == CONTENTS_WATER) + return maxz; + + float diff = maxz - minz; + while (diff > 1.0) + { + midUp.z = minz + diff/2.0; + if (UTIL_PointContents(midUp) == CONTENTS_WATER) + { + minz = midUp.z; + } + else + { + maxz = midUp.z; + } + diff = maxz - minz; + } + + return midUp.z; +} + + +extern DLL_GLOBAL short g_sModelIndexBubbles;// holds the index for the bubbles model + +void UTIL_Bubbles( Vector mins, Vector maxs, int count ) +{ + Vector mid = (mins + maxs) * 0.5; + + float flHeight = UTIL_WaterLevel( mid, mid.z, mid.z + 1024 ); + flHeight = flHeight - mins.z; + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, mid ); + WRITE_BYTE( TE_BUBBLES ); + WRITE_COORD( mins.x ); // mins + WRITE_COORD( mins.y ); + WRITE_COORD( mins.z ); + WRITE_COORD( maxs.x ); // maxz + WRITE_COORD( maxs.y ); + WRITE_COORD( maxs.z ); + WRITE_COORD( flHeight ); // height + WRITE_SHORT( g_sModelIndexBubbles ); + WRITE_BYTE( count ); // count + WRITE_COORD( 8 ); // speed + MESSAGE_END(); +} + +void UTIL_BubbleTrail( Vector from, Vector to, int count ) +{ + float flHeight = UTIL_WaterLevel( from, from.z, from.z + 256 ); + flHeight = flHeight - from.z; + + if (flHeight < 8) + { + flHeight = UTIL_WaterLevel( to, to.z, to.z + 256 ); + flHeight = flHeight - to.z; + if (flHeight < 8) + return; + + // UNDONE: do a ploink sound + flHeight = flHeight + to.z - from.z; + } + + if (count > 255) + count = 255; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BUBBLETRAIL ); + WRITE_COORD( from.x ); // mins + WRITE_COORD( from.y ); + WRITE_COORD( from.z ); + WRITE_COORD( to.x ); // maxz + WRITE_COORD( to.y ); + WRITE_COORD( to.z ); + WRITE_COORD( flHeight ); // height + WRITE_SHORT( g_sModelIndexBubbles ); + WRITE_BYTE( count ); // count + WRITE_COORD( 8 ); // speed + MESSAGE_END(); +} + + +void UTIL_Remove( CBaseEntity *pEntity ) +{ + if ( !pEntity ) + return; + + pEntity->UpdateOnRemove(); + pEntity->pev->flags |= FL_KILLME; + pEntity->pev->targetname = 0; +} + + +BOOL UTIL_IsValidEntity( edict_t *pent ) +{ + if ( !pent || pent->free || (pent->v.flags & FL_KILLME) ) + return FALSE; + return TRUE; +} + + +void UTIL_PrecacheOther( const char *szClassname ) +{ + edict_t *pent; + + pent = CREATE_NAMED_ENTITY( MAKE_STRING( szClassname ) ); + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in UTIL_PrecacheOther\n" ); + return; + } + + CBaseEntity *pEntity = CBaseEntity::Instance (VARS( pent )); + if (pEntity) + pEntity->Precache( ); + REMOVE_ENTITY(pent); +} + +//========================================================= +// UTIL_LogPrintf - Prints a logged message to console. +// Preceded by LOG: ( timestamp ) < message > +//========================================================= +void UTIL_LogPrintf( char *fmt, ... ) +{ + va_list argptr; + static char string[1024]; + + va_start ( argptr, fmt ); + vsprintf ( string, fmt, argptr ); + va_end ( argptr ); + + // Print to server console + ALERT( at_logged, "%s", string ); +} + +//========================================================= +// UTIL_DotPoints - returns the dot product of a line from +// src to check and vecdir. +//========================================================= +float UTIL_DotPoints ( const Vector &vecSrc, const Vector &vecCheck, const Vector &vecDir ) +{ + Vector2D vec2LOS; + + vec2LOS = ( vecCheck - vecSrc ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + return DotProduct (vec2LOS , ( vecDir.Make2D() ) ); +} + + +//========================================================= +// UTIL_StripToken - for redundant keynames +//========================================================= +void UTIL_StripToken( const char *pKey, char *pDest ) +{ + int i = 0; + + while ( pKey[i] && pKey[i] != '#' ) + { + pDest[i] = pKey[i]; + i++; + } + pDest[i] = 0; +} + + +// -------------------------------------------------------------- +// +// CSave +// +// -------------------------------------------------------------- +static int gSizes[FIELD_TYPECOUNT] = +{ + sizeof(float), // FIELD_FLOAT + sizeof(int), // FIELD_STRING + sizeof(int), // FIELD_ENTITY + sizeof(int), // FIELD_CLASSPTR + sizeof(int), // FIELD_EHANDLE + sizeof(int), // FIELD_entvars_t + sizeof(int), // FIELD_EDICT + sizeof(float)*3, // FIELD_VECTOR + sizeof(float)*3, // FIELD_POSITION_VECTOR + sizeof(int *), // FIELD_POINTER + sizeof(int), // FIELD_INTEGER + sizeof(int *), // FIELD_FUNCTION + sizeof(int), // FIELD_BOOLEAN + sizeof(short), // FIELD_SHORT + sizeof(char), // FIELD_CHARACTER + sizeof(float), // FIELD_TIME + sizeof(int), // FIELD_MODELNAME + sizeof(int), // FIELD_SOUNDNAME +}; + + +// Base class includes common SAVERESTOREDATA pointer, and manages the entity table +CSaveRestoreBuffer :: CSaveRestoreBuffer( void ) +{ + m_pdata = NULL; +} + + +CSaveRestoreBuffer :: CSaveRestoreBuffer( SAVERESTOREDATA *pdata ) +{ + m_pdata = pdata; +} + + +CSaveRestoreBuffer :: ~CSaveRestoreBuffer( void ) +{ +} + +int CSaveRestoreBuffer :: EntityIndex( CBaseEntity *pEntity ) +{ + if ( pEntity == NULL ) + return -1; + return EntityIndex( pEntity->pev ); +} + + +int CSaveRestoreBuffer :: EntityIndex( entvars_t *pevLookup ) +{ + if ( pevLookup == NULL ) + return -1; + return EntityIndex( ENT( pevLookup ) ); +} + +int CSaveRestoreBuffer :: EntityIndex( EOFFSET eoLookup ) +{ + return EntityIndex( ENT( eoLookup ) ); +} + + +int CSaveRestoreBuffer :: EntityIndex( edict_t *pentLookup ) +{ + if ( !m_pdata || pentLookup == NULL ) + return -1; + + int i; + ENTITYTABLE *pTable; + + for ( i = 0; i < m_pdata->tableCount; i++ ) + { + pTable = m_pdata->pTable + i; + if ( pTable->pent == pentLookup ) + return i; + } + return -1; +} + + +edict_t *CSaveRestoreBuffer :: EntityFromIndex( int entityIndex ) +{ + if ( !m_pdata || entityIndex < 0 ) + return NULL; + + int i; + ENTITYTABLE *pTable; + + for ( i = 0; i < m_pdata->tableCount; i++ ) + { + pTable = m_pdata->pTable + i; + if ( pTable->id == entityIndex ) + return pTable->pent; + } + return NULL; +} + + +int CSaveRestoreBuffer :: EntityFlagsSet( int entityIndex, int flags ) +{ + if ( !m_pdata || entityIndex < 0 ) + return 0; + if ( entityIndex > m_pdata->tableCount ) + return 0; + + m_pdata->pTable[ entityIndex ].flags |= flags; + + return m_pdata->pTable[ entityIndex ].flags; +} + + +void CSaveRestoreBuffer :: BufferRewind( int size ) +{ + if ( !m_pdata ) + return; + + if ( m_pdata->size < size ) + size = m_pdata->size; + + m_pdata->pCurrentData -= size; + m_pdata->size -= size; +} + +#ifndef _WIN32 +extern "C" { +unsigned _rotr ( unsigned val, int shift) +{ + register unsigned lobit; /* non-zero means lo bit set */ + register unsigned num = val; /* number to rotate */ + + shift &= 0x1f; /* modulo 32 -- this will also make + negative shifts work */ + + while (shift--) { + lobit = num & 1; /* get high bit */ + num >>= 1; /* shift right one bit */ + if (lobit) + num |= 0x80000000; /* set hi bit if lo bit was set */ + } + + return num; +} +} +#endif + +unsigned int CSaveRestoreBuffer :: HashString( const char *pszToken ) +{ + unsigned int hash = 0; + + while ( *pszToken ) + hash = _rotr( hash, 4 ) ^ *pszToken++; + + return hash; +} + +unsigned short CSaveRestoreBuffer :: TokenHash( const char *pszToken ) +{ + unsigned short hash = (unsigned short)(HashString( pszToken ) % (unsigned)m_pdata->tokenCount ); + +#if _DEBUG + static int tokensparsed = 0; + tokensparsed++; + if ( !m_pdata->tokenCount || !m_pdata->pTokens ) + ALERT( at_error, "No token table array in TokenHash()!" ); +#endif + + for ( int i=0; itokenCount; i++ ) + { +#if _DEBUG + static qboolean beentheredonethat = FALSE; + if ( i > 50 && !beentheredonethat ) + { + beentheredonethat = TRUE; + ALERT( at_error, "CSaveRestoreBuffer :: TokenHash() is getting too full!" ); + } +#endif + + int index = hash + i; + if ( index >= m_pdata->tokenCount ) + index -= m_pdata->tokenCount; + + if ( !m_pdata->pTokens[index] || strcmp( pszToken, m_pdata->pTokens[index] ) == 0 ) + { + m_pdata->pTokens[index] = (char *)pszToken; + return index; + } + } + + // Token hash table full!!! + // [Consider doing overflow table(s) after the main table & limiting linear hash table search] + ALERT( at_error, "CSaveRestoreBuffer :: TokenHash() is COMPLETELY FULL!" ); + return 0; +} + +void CSave :: WriteData( const char *pname, int size, const char *pdata ) +{ + BufferField( pname, size, pdata ); +} + + +void CSave :: WriteShort( const char *pname, const short *data, int count ) +{ + BufferField( pname, sizeof(short) * count, (const char *)data ); +} + + +void CSave :: WriteInt( const char *pname, const int *data, int count ) +{ + BufferField( pname, sizeof(int) * count, (const char *)data ); +} + + +void CSave :: WriteFloat( const char *pname, const float *data, int count ) +{ + BufferField( pname, sizeof(float) * count, (const char *)data ); +} + + +void CSave :: WriteTime( const char *pname, const float *data, int count ) +{ + int i; + Vector tmp, input; + + BufferHeader( pname, sizeof(float) * count ); + for ( i = 0; i < count; i++ ) + { + float tmp = data[0]; + + // Always encode time as a delta from the current time so it can be re-based if loaded in a new level + // Times of 0 are never written to the file, so they will be restored as 0, not a relative time + if ( m_pdata ) + tmp -= m_pdata->time; + + BufferData( (const char *)&tmp, sizeof(float) ); + data ++; + } +} + + +void CSave :: WriteString( const char *pname, const char *pdata ) +{ +#ifdef TOKENIZE + short token = (short)TokenHash( pdata ); + WriteShort( pname, &token, 1 ); +#else + BufferField( pname, strlen(pdata) + 1, pdata ); +#endif +} + + +void CSave :: WriteString( const char *pname, const int *stringId, int count ) +{ + int i, size; + +#ifdef TOKENIZE + short token = (short)TokenHash( STRING( *stringId ) ); + WriteShort( pname, &token, 1 ); +#else +#if 0 + if ( count != 1 ) + ALERT( at_error, "No string arrays!\n" ); + WriteString( pname, (char *)STRING(*stringId) ); +#endif + + size = 0; + for ( i = 0; i < count; i++ ) + size += strlen( STRING( stringId[i] ) ) + 1; + + BufferHeader( pname, size ); + for ( i = 0; i < count; i++ ) + { + const char *pString = STRING(stringId[i]); + BufferData( pString, strlen(pString)+1 ); + } +#endif +} + + +void CSave :: WriteVector( const char *pname, const Vector &value ) +{ + WriteVector( pname, &value.x, 1 ); +} + + +void CSave :: WriteVector( const char *pname, const float *value, int count ) +{ + BufferHeader( pname, sizeof(float) * 3 * count ); + BufferData( (const char *)value, sizeof(float) * 3 * count ); +} + + + +void CSave :: WritePositionVector( const char *pname, const Vector &value ) +{ + + if ( m_pdata && m_pdata->fUseLandmark ) + { + Vector tmp = value - m_pdata->vecLandmarkOffset; + WriteVector( pname, tmp ); + } + + WriteVector( pname, value ); +} + + +void CSave :: WritePositionVector( const char *pname, const float *value, int count ) +{ + int i; + Vector tmp, input; + + BufferHeader( pname, sizeof(float) * 3 * count ); + for ( i = 0; i < count; i++ ) + { + Vector tmp( value[0], value[1], value[2] ); + + if ( m_pdata && m_pdata->fUseLandmark ) + tmp = tmp - m_pdata->vecLandmarkOffset; + + BufferData( (const char *)&tmp.x, sizeof(float) * 3 ); + value += 3; + } +} + + +void CSave :: WriteFunction( const char *pname, const int *data, int count ) +{ + const char *functionName; + + functionName = NAME_FOR_FUNCTION( *data ); + if ( functionName ) + BufferField( pname, strlen(functionName) + 1, functionName ); + else + ALERT( at_error, "Invalid function pointer in entity!" ); +} + + +void EntvarsKeyvalue( entvars_t *pev, KeyValueData *pkvd ) +{ + int i; + TYPEDESCRIPTION *pField; + + for ( i = 0; i < static_cast(ENTVARS_COUNT); i++ ) + { + pField = &gEntvarsDescription[i]; + + if ( !stricmp( pField->fieldName, pkvd->szKeyName ) ) + { + switch( pField->fieldType ) + { + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + case FIELD_STRING: + (*(int *)((char *)pev + pField->fieldOffset)) = ALLOC_STRING( pkvd->szValue ); + break; + + case FIELD_TIME: + case FIELD_FLOAT: + (*(float *)((char *)pev + pField->fieldOffset)) = atof( pkvd->szValue ); + break; + + case FIELD_INTEGER: + (*(int *)((char *)pev + pField->fieldOffset)) = atoi( pkvd->szValue ); + break; + + case FIELD_POSITION_VECTOR: + case FIELD_VECTOR: + UTIL_StringToVector( (float *)((char *)pev + pField->fieldOffset), pkvd->szValue ); + break; + + default: + case FIELD_EVARS: + case FIELD_CLASSPTR: + case FIELD_EDICT: + case FIELD_ENTITY: + case FIELD_POINTER: + ALERT( at_error, "Bad field in entity!!\n" ); + break; + } + pkvd->fHandled = TRUE; + return; + } + } +} + + + +int CSave :: WriteEntVars( const char *pname, entvars_t *pev ) +{ + return WriteFields( pname, pev, gEntvarsDescription, ENTVARS_COUNT ); +} + + + +int CSave :: WriteFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + int i, j, actualCount, emptyCount; + TYPEDESCRIPTION *pTest; + int entityArray[MAX_ENTITYARRAY]; + + // Precalculate the number of empty fields + emptyCount = 0; + for ( i = 0; i < fieldCount; i++ ) + { + void *pOutputData; + pOutputData = ((char *)pBaseData + pFields[i].fieldOffset ); + if ( DataEmpty( (const char *)pOutputData, pFields[i].fieldSize * gSizes[pFields[i].fieldType] ) ) + emptyCount++; + } + + // Empty fields will not be written, write out the actual number of fields to be written + actualCount = fieldCount - emptyCount; + WriteInt( pname, &actualCount, 1 ); + + for ( i = 0; i < fieldCount; i++ ) + { + void *pOutputData; + pTest = &pFields[ i ]; + pOutputData = ((char *)pBaseData + pTest->fieldOffset ); + + // UNDONE: Must we do this twice? + if ( DataEmpty( (const char *)pOutputData, pTest->fieldSize * gSizes[pTest->fieldType] ) ) + continue; + + switch( pTest->fieldType ) + { + case FIELD_FLOAT: + WriteFloat( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + case FIELD_TIME: + WriteTime( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + case FIELD_STRING: + WriteString( pTest->fieldName, (int *)pOutputData, pTest->fieldSize ); + break; + case FIELD_CLASSPTR: + case FIELD_EVARS: + case FIELD_EDICT: + case FIELD_ENTITY: + case FIELD_EHANDLE: + if ( pTest->fieldSize > MAX_ENTITYARRAY ) + ALERT( at_error, "Can't save more than %d entities in an array!!!\n", MAX_ENTITYARRAY ); + for ( j = 0; j < pTest->fieldSize; j++ ) + { + switch( pTest->fieldType ) + { + case FIELD_EVARS: + entityArray[j] = EntityIndex( ((entvars_t **)pOutputData)[j] ); + break; + case FIELD_CLASSPTR: + entityArray[j] = EntityIndex( ((CBaseEntity **)pOutputData)[j] ); + break; + case FIELD_EDICT: + entityArray[j] = EntityIndex( ((edict_t **)pOutputData)[j] ); + break; + case FIELD_ENTITY: + entityArray[j] = EntityIndex( ((EOFFSET *)pOutputData)[j] ); + break; + case FIELD_EHANDLE: + entityArray[j] = EntityIndex( (CBaseEntity *)(((EHANDLE *)pOutputData)[j]) ); + break; + default: + break; + } + } + WriteInt( pTest->fieldName, entityArray, pTest->fieldSize ); + break; + case FIELD_POSITION_VECTOR: + WritePositionVector( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + case FIELD_VECTOR: + WriteVector( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + + case FIELD_BOOLEAN: + case FIELD_INTEGER: + WriteInt( pTest->fieldName, (int *)pOutputData, pTest->fieldSize ); + break; + + case FIELD_SHORT: + WriteData( pTest->fieldName, 2 * pTest->fieldSize, ((char *)pOutputData) ); + break; + + case FIELD_CHARACTER: + WriteData( pTest->fieldName, pTest->fieldSize, ((char *)pOutputData) ); + break; + + // For now, just write the address out, we're not going to change memory while doing this yet! + case FIELD_POINTER: + WriteInt( pTest->fieldName, (int *)(char *)pOutputData, pTest->fieldSize ); + break; + + case FIELD_FUNCTION: + WriteFunction( pTest->fieldName, (int *)(char *)pOutputData, pTest->fieldSize ); + break; + default: + ALERT( at_error, "Bad field type\n" ); + } + } + + return 1; +} + + +void CSave :: BufferString( char *pdata, int len ) +{ + char c = 0; + + BufferData( pdata, len ); // Write the string + BufferData( &c, 1 ); // Write a null terminator +} + + +int CSave :: DataEmpty( const char *pdata, int size ) +{ + for ( int i = 0; i < size; i++ ) + { + if ( pdata[i] ) + return 0; + } + return 1; +} + + +void CSave :: BufferField( const char *pname, int size, const char *pdata ) +{ + BufferHeader( pname, size ); + BufferData( pdata, size ); +} + + +void CSave :: BufferHeader( const char *pname, int size ) +{ + short hashvalue = TokenHash( pname ); + if ( size > 1<<(sizeof(short)*8) ) + ALERT( at_error, "CSave :: BufferHeader() size parameter exceeds 'short'!" ); + BufferData( (const char *)&size, sizeof(short) ); + BufferData( (const char *)&hashvalue, sizeof(short) ); +} + + +void CSave :: BufferData( const char *pdata, int size ) +{ + if ( !m_pdata ) + return; + + if ( m_pdata->size + size > m_pdata->bufferSize ) + { + ALERT( at_error, "Save/Restore overflow!" ); + m_pdata->size = m_pdata->bufferSize; + return; + } + + memcpy( m_pdata->pCurrentData, pdata, size ); + m_pdata->pCurrentData += size; + m_pdata->size += size; +} + + + +// -------------------------------------------------------------- +// +// CRestore +// +// -------------------------------------------------------------- + +int CRestore::ReadField( void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount, int startField, int size, char *pName, void *pData ) +{ + int i, j, stringCount, fieldNumber, entityIndex; + TYPEDESCRIPTION *pTest; + float time, timeData; + Vector position; + edict_t *pent; + char *pString; + + time = 0; + position = Vector(0,0,0); + + if ( m_pdata ) + { + time = m_pdata->time; + if ( m_pdata->fUseLandmark ) + position = m_pdata->vecLandmarkOffset; + } + + for ( i = 0; i < fieldCount; i++ ) + { + fieldNumber = (i+startField)%fieldCount; + pTest = &pFields[ fieldNumber ]; + if ( !stricmp( pTest->fieldName, pName ) ) + { + if ( !m_global || !(pTest->flags & FTYPEDESC_GLOBAL) ) + { + for ( j = 0; j < pTest->fieldSize; j++ ) + { + void *pOutputData = ((char *)pBaseData + pTest->fieldOffset + (j*gSizes[pTest->fieldType]) ); + void *pInputData = (char *)pData + j * gSizes[pTest->fieldType]; + + switch( pTest->fieldType ) + { + case FIELD_TIME: + timeData = *(float *)pInputData; + // Re-base time variables + timeData += time; + *((float *)pOutputData) = timeData; + break; + case FIELD_FLOAT: + *((float *)pOutputData) = *(float *)pInputData; + break; + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + case FIELD_STRING: + // Skip over j strings + pString = (char *)pData; + for ( stringCount = 0; stringCount < j; stringCount++ ) + { + while (*pString) + pString++; + pString++; + } + pInputData = pString; + if ( strlen( (char *)pInputData ) == 0 ) + *((int *)pOutputData) = 0; + else + { + int string; + + string = ALLOC_STRING( (char *)pInputData ); + + *((int *)pOutputData) = string; + + if ( !FStringNull( string ) && m_precache ) + { + if ( pTest->fieldType == FIELD_MODELNAME ) + PRECACHE_MODEL( (char *)STRING( string ) ); + else if ( pTest->fieldType == FIELD_SOUNDNAME ) + PRECACHE_SOUND( (char *)STRING( string ) ); + } + } + break; + case FIELD_EVARS: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((entvars_t **)pOutputData) = VARS(pent); + else + *((entvars_t **)pOutputData) = NULL; + break; + case FIELD_CLASSPTR: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((CBaseEntity **)pOutputData) = CBaseEntity::Instance(pent); + else + *((CBaseEntity **)pOutputData) = NULL; + break; + case FIELD_EDICT: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + *((edict_t **)pOutputData) = pent; + break; + case FIELD_EHANDLE: + // Input and Output sizes are different! + pOutputData = (char *)pOutputData + j*(sizeof(EHANDLE) - gSizes[pTest->fieldType]); + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((EHANDLE *)pOutputData) = CBaseEntity::Instance(pent); + else + *((EHANDLE *)pOutputData) = NULL; + break; + case FIELD_ENTITY: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((EOFFSET *)pOutputData) = OFFSET(pent); + else + *((EOFFSET *)pOutputData) = 0; + break; + case FIELD_VECTOR: + ((float *)pOutputData)[0] = ((float *)pInputData)[0]; + ((float *)pOutputData)[1] = ((float *)pInputData)[1]; + ((float *)pOutputData)[2] = ((float *)pInputData)[2]; + break; + case FIELD_POSITION_VECTOR: + ((float *)pOutputData)[0] = ((float *)pInputData)[0] + position.x; + ((float *)pOutputData)[1] = ((float *)pInputData)[1] + position.y; + ((float *)pOutputData)[2] = ((float *)pInputData)[2] + position.z; + break; + + case FIELD_BOOLEAN: + case FIELD_INTEGER: + *((int *)pOutputData) = *( int *)pInputData; + break; + + case FIELD_SHORT: + *((short *)pOutputData) = *( short *)pInputData; + break; + + case FIELD_CHARACTER: + *((char *)pOutputData) = *( char *)pInputData; + break; + + case FIELD_POINTER: + *((int *)pOutputData) = *( int *)pInputData; + break; + case FIELD_FUNCTION: + if ( strlen( (char *)pInputData ) == 0 ) + *((int *)pOutputData) = 0; + else + *((int *)pOutputData) = FUNCTION_FROM_NAME( (char *)pInputData ); + break; + + default: + ALERT( at_error, "Bad field type\n" ); + } + } + } +#if 0 + else + { + ALERT( at_console, "Skipping global field %s\n", pName ); + } +#endif + return fieldNumber; + } + } + + return -1; +} + + +int CRestore::ReadEntVars( const char *pname, entvars_t *pev ) +{ + return ReadFields( pname, pev, gEntvarsDescription, ENTVARS_COUNT ); +} + + +int CRestore::ReadFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + unsigned short i, token; + int lastField, fileCount; + HEADER header; + + i = ReadShort(); + ASSERT( i == sizeof(int) ); // First entry should be an int + + token = ReadShort(); + + // Check the struct name + if ( token != TokenHash(pname) ) // Field Set marker + { +// ALERT( at_error, "Expected %s found %s!\n", pname, BufferPointer() ); + BufferRewind( 2*sizeof(short) ); + return 0; + } + + // Skip over the struct name + fileCount = ReadInt(); // Read field count + + lastField = 0; // Make searches faster, most data is read/written in the same order + + // Clear out base data + for ( i = 0; i < fieldCount; i++ ) + { + // Don't clear global fields + if ( !m_global || !(pFields[i].flags & FTYPEDESC_GLOBAL) ) + memset( ((char *)pBaseData + pFields[i].fieldOffset), 0, pFields[i].fieldSize * gSizes[pFields[i].fieldType] ); + } + + for ( i = 0; i < fileCount; i++ ) + { + BufferReadHeader( &header ); + lastField = ReadField( pBaseData, pFields, fieldCount, lastField, header.size, m_pdata->pTokens[header.token], header.pData ); + lastField++; + } + + return 1; +} + + +void CRestore::BufferReadHeader( HEADER *pheader ) +{ + ASSERT( pheader!=NULL ); + pheader->size = ReadShort(); // Read field size + pheader->token = ReadShort(); // Read field name token + pheader->pData = BufferPointer(); // Field Data is next + BufferSkipBytes( pheader->size ); // Advance to next field +} + + +short CRestore::ReadShort( void ) +{ + short tmp = 0; + + BufferReadBytes( (char *)&tmp, sizeof(short) ); + + return tmp; +} + +int CRestore::ReadInt( void ) +{ + int tmp = 0; + + BufferReadBytes( (char *)&tmp, sizeof(int) ); + + return tmp; +} + +int CRestore::ReadNamedInt( const char *pName ) +{ + HEADER header; + + BufferReadHeader( &header ); + return ((int *)header.pData)[0]; +} + +char *CRestore::ReadNamedString( const char *pName ) +{ + HEADER header; + + BufferReadHeader( &header ); +#ifdef TOKENIZE + return (char *)(m_pdata->pTokens[*(short *)header.pData]); +#else + return (char *)header.pData; +#endif +} + + +char *CRestore::BufferPointer( void ) +{ + if ( !m_pdata ) + return NULL; + + return m_pdata->pCurrentData; +} + +void CRestore::BufferReadBytes( char *pOutput, int size ) +{ + ASSERT( m_pdata !=NULL ); + + if ( !m_pdata || Empty() ) + return; + + if ( (m_pdata->size + size) > m_pdata->bufferSize ) + { + ALERT( at_error, "Restore overflow!" ); + m_pdata->size = m_pdata->bufferSize; + return; + } + + if ( pOutput ) + memcpy( pOutput, m_pdata->pCurrentData, size ); + m_pdata->pCurrentData += size; + m_pdata->size += size; +} + + +void CRestore::BufferSkipBytes( int bytes ) +{ + BufferReadBytes( NULL, bytes ); +} + +int CRestore::BufferSkipZString( void ) +{ + char *pszSearch; + int len; + + if ( !m_pdata ) + return 0; + + int maxLen = m_pdata->bufferSize - m_pdata->size; + + len = 0; + pszSearch = m_pdata->pCurrentData; + while ( *pszSearch++ && len < maxLen ) + len++; + + len++; + + BufferSkipBytes( len ); + + return len; +} + +int CRestore::BufferCheckZString( const char *string ) +{ + if ( !m_pdata ) + return 0; + + int maxLen = m_pdata->bufferSize - m_pdata->size; + int len = strlen( string ); + if ( len <= maxLen ) + { + if ( !strncmp( string, m_pdata->pCurrentData, len ) ) + return 1; + } + return 0; +} + diff --git a/hlsdk/dlls/util.h b/hlsdk/dlls/util.h new file mode 100644 index 00000000..bc7e1e74 --- /dev/null +++ b/hlsdk/dlls/util.h @@ -0,0 +1,541 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Misc utility code +// +#include +#ifndef ACTIVITY_H +#include "activity.h" +#endif + +#ifndef ENGINECALLBACK_H +#include "enginecallback.h" +#endif +inline void MESSAGE_BEGIN( int msg_dest, int msg_type, const float *pOrigin, entvars_t *ent ); // implementation later in this file + +extern globalvars_t *gpGlobals; + +// Use this instead of ALLOC_STRING on constant strings +#define STRING(offset) reinterpret_cast(gpGlobals->pStringBase + (int)offset) +#define MAKE_STRING(str) ((int)str - (int)STRING(0)) + +inline edict_t *FIND_ENTITY_BY_CLASSNAME(edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING(entStart, "classname", pszName); +} + +inline edict_t *FIND_ENTITY_BY_TARGETNAME(edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING(entStart, "targetname", pszName); +} + +// for doing a reverse lookup. Say you have a door, and want to find its button. +inline edict_t *FIND_ENTITY_BY_TARGET(edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING(entStart, "target", pszName); +} + +// Keeps clutter down a bit, when writing key-value pairs +#define WRITEKEY_INT(pf, szKeyName, iKeyValue) ENGINE_FPRINTF(pf, "\"%s\" \"%d\"\n", szKeyName, iKeyValue) +#define WRITEKEY_FLOAT(pf, szKeyName, flKeyValue) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%f\"\n", szKeyName, flKeyValue) +#define WRITEKEY_STRING(pf, szKeyName, szKeyValue) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%s\"\n", szKeyName, szKeyValue) +#define WRITEKEY_VECTOR(pf, szKeyName, flX, flY, flZ) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%f %f %f\"\n", szKeyName, flX, flY, flZ) + +// Keeps clutter down a bit, when using a float as a bit-vector +#define SetBits(flBitVector, bits) ((flBitVector) = (int)(flBitVector) | (bits)) +#define ClearBits(flBitVector, bits) ((flBitVector) = (int)(flBitVector) & ~(bits)) +#define FBitSet(flBitVector, bit) ((int)(flBitVector) & (bit)) + +// Makes these more explicit, and easier to find +#define FILE_GLOBAL static +#define DLL_GLOBAL + +// Until we figure out why "const" gives the compiler problems, we'll just have to use +// this bogus "empty" define to mark things as constant. +#define CONSTANT + +// More explicit than "int" +typedef int EOFFSET; + +// In case it's not alread defined +typedef int BOOL; + +// In case this ever changes +#define M_PI 3.14159265358979323846 + +// Keeps clutter down a bit, when declaring external entity/global method prototypes +#define DECLARE_GLOBAL_METHOD(MethodName) extern void DLLEXPORT MethodName( void ) +#define GLOBAL_METHOD(funcname) void DLLEXPORT funcname(void) + +// This is the glue that hooks .MAP entity class names to our CPP classes +// The _declspec forces them to be exported by name so we can do a lookup with GetProcAddress() +// The function is used to intialize / allocate the object for the entity +#ifdef _WIN32 +#define LINK_ENTITY_TO_CLASS(mapClassName,DLLClassName) \ + extern "C" _declspec( dllexport ) void mapClassName( entvars_t *pev ); \ + void mapClassName( entvars_t *pev ) { GetClassPtr( (DLLClassName *)pev ); } +#else +#define LINK_ENTITY_TO_CLASS(mapClassName,DLLClassName) extern "C" void mapClassName( entvars_t *pev ); void mapClassName( entvars_t *pev ) { GetClassPtr( (DLLClassName *)pev ); } +#endif + + +// +// Conversion among the three types of "entity", including identity-conversions. +// +#ifdef DEBUG + extern edict_t *DBG_EntOfVars(const entvars_t *pev); + inline edict_t *ENT(const entvars_t *pev) { return DBG_EntOfVars(pev); } +#else + inline edict_t *ENT(const entvars_t *pev) { return pev->pContainingEntity; } +#endif +inline edict_t *ENT(edict_t *pent) { return pent; } +inline edict_t *ENT(EOFFSET eoffset) { return (*g_engfuncs.pfnPEntityOfEntOffset)(eoffset); } +inline EOFFSET OFFSET(EOFFSET eoffset) { return eoffset; } +inline EOFFSET OFFSET(const edict_t *pent) +{ +#ifdef _DEBUG + if ( !pent ) + ALERT( at_error, "Bad ent in OFFSET()\n" ); +#endif + return (*g_engfuncs.pfnEntOffsetOfPEntity)(pent); +} +inline EOFFSET OFFSET(entvars_t *pev) +{ +#ifdef _DEBUG + if ( !pev ) + ALERT( at_error, "Bad pev in OFFSET()\n" ); +#endif + return OFFSET(ENT(pev)); +} +inline entvars_t *VARS(entvars_t *pev) { return pev; } + +inline entvars_t *VARS(edict_t *pent) +{ + if ( !pent ) + return NULL; + + return &pent->v; +} + +inline entvars_t* VARS(EOFFSET eoffset) { return VARS(ENT(eoffset)); } +inline int ENTINDEX(edict_t *pEdict) { return (*g_engfuncs.pfnIndexOfEdict)(pEdict); } +inline edict_t* INDEXENT( int iEdictNum ) { return (*g_engfuncs.pfnPEntityOfEntIndex)(iEdictNum); } +inline void MESSAGE_BEGIN( int msg_dest, int msg_type, const float *pOrigin, entvars_t *ent ) { + (*g_engfuncs.pfnMessageBegin)(msg_dest, msg_type, pOrigin, ENT(ent)); +} + +// Testing the three types of "entity" for nullity +#define eoNullEntity 0 +inline BOOL FNullEnt(EOFFSET eoffset) { return eoffset == 0; } +inline BOOL FNullEnt(const edict_t* pent) { return pent == NULL || FNullEnt(OFFSET(pent)); } +inline BOOL FNullEnt(entvars_t* pev) { return pev == NULL || FNullEnt(OFFSET(pev)); } + +// Testing strings for nullity +#define iStringNull 0 +inline BOOL FStringNull(int iString) { return iString == iStringNull; } + +#define cchMapNameMost 32 + +// Dot products for view cone checking +#define VIEW_FIELD_FULL (float)-1.0 // +-180 degrees +#define VIEW_FIELD_WIDE (float)-0.7 // +-135 degrees 0.1 // +-85 degrees, used for full FOV checks +#define VIEW_FIELD_NARROW (float)0.7 // +-45 degrees, more narrow check used to set up ranged attacks +#define VIEW_FIELD_ULTRA_NARROW (float)0.9 // +-25 degrees, more narrow check used to set up ranged attacks + +// All monsters need this data +#define DONT_BLEED -1 +#define BLOOD_COLOR_RED (BYTE)247 +#define BLOOD_COLOR_YELLOW (BYTE)195 +#define BLOOD_COLOR_GREEN BLOOD_COLOR_YELLOW + +typedef enum +{ + + MONSTERSTATE_NONE = 0, + MONSTERSTATE_IDLE, + MONSTERSTATE_COMBAT, + MONSTERSTATE_ALERT, + MONSTERSTATE_HUNT, + MONSTERSTATE_PRONE, + MONSTERSTATE_SCRIPT, + MONSTERSTATE_PLAYDEAD, + MONSTERSTATE_DEAD + +} MONSTERSTATE; + + + +// Things that toggle (buttons/triggers/doors) need this +typedef enum + { + TS_AT_TOP, + TS_AT_BOTTOM, + TS_GOING_UP, + TS_GOING_DOWN + } TOGGLE_STATE; + +// Misc useful +inline BOOL FStrEq(const char*sz1, const char*sz2) + { return (strcmp(sz1, sz2) == 0); } +inline BOOL FClassnameIs(edict_t* pent, const char* szClassname) + { return FStrEq(STRING(VARS(pent)->classname), szClassname); } +inline BOOL FClassnameIs(entvars_t* pev, const char* szClassname) + { return FStrEq(STRING(pev->classname), szClassname); } + +class CBaseEntity; + +// Misc. Prototypes +extern void UTIL_SetSize (entvars_t* pev, const Vector &vecMin, const Vector &vecMax); +extern float UTIL_VecToYaw (const Vector &vec); +extern Vector UTIL_VecToAngles (const Vector &vec); +extern float UTIL_AngleMod (float a); +extern float UTIL_AngleDiff ( float destAngle, float srcAngle ); + +extern CBaseEntity *UTIL_FindEntityInSphere(CBaseEntity *pStartEntity, const Vector &vecCenter, float flRadius); +extern CBaseEntity *UTIL_FindEntityByString(CBaseEntity *pStartEntity, const char *szKeyword, const char *szValue ); +extern CBaseEntity *UTIL_FindEntityByClassname(CBaseEntity *pStartEntity, const char *szName ); +extern CBaseEntity *UTIL_FindEntityByTargetname(CBaseEntity *pStartEntity, const char *szName ); +extern CBaseEntity *UTIL_FindEntityGeneric(const char *szName, Vector &vecSrc, float flRadius ); + +// returns a CBaseEntity pointer to a player by index. Only returns if the player is spawned and connected +// otherwise returns NULL +// Index is 1 based +extern CBaseEntity *UTIL_PlayerByIndex( int playerIndex ); + +#define UTIL_EntitiesInPVS(pent) (*g_engfuncs.pfnEntitiesInPVS)(pent) +extern void UTIL_MakeVectors (const Vector &vecAngles); + +// Pass in an array of pointers and an array size, it fills the array and returns the number inserted +extern int UTIL_MonstersInSphere( CBaseEntity **pList, int listMax, const Vector ¢er, float radius ); +extern int UTIL_EntitiesInBox( CBaseEntity **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask ); + +inline void UTIL_MakeVectorsPrivate( const Vector &vecAngles, float *p_vForward, float *p_vRight, float *p_vUp ) +{ + g_engfuncs.pfnAngleVectors( vecAngles, p_vForward, p_vRight, p_vUp ); +} + +extern void UTIL_MakeAimVectors ( const Vector &vecAngles ); // like MakeVectors, but assumes pitch isn't inverted +extern void UTIL_MakeInvVectors ( const Vector &vec, globalvars_t *pgv ); + +extern void UTIL_SetOrigin ( entvars_t* pev, const Vector &vecOrigin ); +extern void UTIL_EmitAmbientSound ( edict_t *entity, const Vector &vecOrigin, const char *samp, float vol, float attenuation, int fFlags, int pitch ); +extern void UTIL_ParticleEffect ( const Vector &vecOrigin, const Vector &vecDirection, ULONG ulColor, ULONG ulCount ); +extern void UTIL_ScreenShake ( const Vector ¢er, float amplitude, float frequency, float duration, float radius ); +extern void UTIL_ScreenShakeAll ( const Vector ¢er, float amplitude, float frequency, float duration ); +extern void UTIL_ShowMessage ( const char *pString, CBaseEntity *pPlayer ); +extern void UTIL_ShowMessageAll ( const char *pString ); +extern void UTIL_ScreenFadeAll ( const Vector &color, float fadeTime, float holdTime, int alpha, int flags ); +extern void UTIL_ScreenFade ( CBaseEntity *pEntity, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ); + +typedef enum { ignore_monsters=1, dont_ignore_monsters=0, missile=2 } IGNORE_MONSTERS; +typedef enum { ignore_glass=1, dont_ignore_glass=0 } IGNORE_GLASS; +extern void UTIL_TraceLine (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, edict_t *pentIgnore, TraceResult *ptr); +extern void UTIL_TraceLine (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, IGNORE_GLASS ignoreGlass, edict_t *pentIgnore, TraceResult *ptr); +typedef enum { point_hull=0, human_hull=1, large_hull=2, head_hull=3 }; +extern void UTIL_TraceHull (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, int hullNumber, edict_t *pentIgnore, TraceResult *ptr); +extern TraceResult UTIL_GetGlobalTrace (void); +extern void UTIL_TraceModel (const Vector &vecStart, const Vector &vecEnd, int hullNumber, edict_t *pentModel, TraceResult *ptr); +extern Vector UTIL_GetAimVector (edict_t* pent, float flSpeed); +extern int UTIL_PointContents (const Vector &vec); + +extern int UTIL_IsMasterTriggered (string_t sMaster, CBaseEntity *pActivator); +extern void UTIL_BloodStream( const Vector &origin, const Vector &direction, int color, int amount ); +extern void UTIL_BloodDrips( const Vector &origin, const Vector &direction, int color, int amount ); +extern Vector UTIL_RandomBloodVector( void ); +extern BOOL UTIL_ShouldShowBlood( int bloodColor ); +extern void UTIL_BloodDecalTrace( TraceResult *pTrace, int bloodColor ); +extern void UTIL_DecalTrace( TraceResult *pTrace, int decalNumber ); +extern void UTIL_PlayerDecalTrace( TraceResult *pTrace, int playernum, int decalNumber, BOOL bIsCustom ); +extern void UTIL_GunshotDecalTrace( TraceResult *pTrace, int decalNumber ); +extern void UTIL_Sparks( const Vector &position ); +extern void UTIL_Ricochet( const Vector &position, float scale ); +extern void UTIL_StringToVector( float *pVector, const char *pString ); +extern void UTIL_StringToIntArray( int *pVector, int count, const char *pString ); +extern Vector UTIL_ClampVectorToBox( const Vector &input, const Vector &clampSize ); +extern float UTIL_Approach( float target, float value, float speed ); +extern float UTIL_ApproachAngle( float target, float value, float speed ); +extern float UTIL_AngleDistance( float next, float cur ); + +extern char *UTIL_VarArgs( char *format, ... ); +extern void UTIL_Remove( CBaseEntity *pEntity ); +extern BOOL UTIL_IsValidEntity( edict_t *pent ); +extern BOOL UTIL_TeamsMatch( const char *pTeamName1, const char *pTeamName2 ); + +// Use for ease-in, ease-out style interpolation (accel/decel) +extern float UTIL_SplineFraction( float value, float scale ); + +// Search for water transition along a vertical line +extern float UTIL_WaterLevel( const Vector &position, float minz, float maxz ); +extern void UTIL_Bubbles( Vector mins, Vector maxs, int count ); +extern void UTIL_BubbleTrail( Vector from, Vector to, int count ); + +// allows precacheing of other entities +extern void UTIL_PrecacheOther( const char *szClassname ); + +// prints a message to each client +extern void UTIL_ClientPrintAll( int msg_dest, const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ); +inline void UTIL_CenterPrintAll( const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ) +{ + UTIL_ClientPrintAll( HUD_PRINTCENTER, msg_name, param1, param2, param3, param4 ); +} + +class CBasePlayerItem; +class CBasePlayer; +extern BOOL UTIL_GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ); + +// prints messages through the HUD +extern void ClientPrint( entvars_t *client, int msg_dest, const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ); + +// prints a message to the HUD say (chat) +extern void UTIL_SayText( const char *pText, CBaseEntity *pEntity ); +extern void UTIL_SayTextAll( const char *pText, CBaseEntity *pEntity ); + + +typedef struct hudtextparms_s +{ + float x; + float y; + int effect; + byte r1, g1, b1, a1; + byte r2, g2, b2, a2; + float fadeinTime; + float fadeoutTime; + float holdTime; + float fxTime; + int channel; +} hudtextparms_t; + +// prints as transparent 'title' to the HUD +extern void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage ); +extern void UTIL_HudMessage( CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage ); + +// for handy use with ClientPrint params +extern char *UTIL_dtos1( int d ); +extern char *UTIL_dtos2( int d ); +extern char *UTIL_dtos3( int d ); +extern char *UTIL_dtos4( int d ); + +// Writes message to console with timestamp and FragLog header. +extern void UTIL_LogPrintf( char *fmt, ... ); + +// Sorta like FInViewCone, but for nonmonsters. +extern float UTIL_DotPoints ( const Vector &vecSrc, const Vector &vecCheck, const Vector &vecDir ); + +extern void UTIL_StripToken( const char *pKey, char *pDest );// for redundant keynames + +// Misc functions +extern void SetMovedir(entvars_t* pev); +extern Vector VecBModelOrigin( entvars_t* pevBModel ); +extern int BuildChangeList( LEVELLIST *pLevelList, int maxList ); + +// +// How did I ever live without ASSERT? +// +#ifdef DEBUG +void DBG_AssertFunction(BOOL fExpr, const char* szExpr, const char* szFile, int szLine, const char* szMessage); +#define ASSERT(f) DBG_AssertFunction(f, #f, __FILE__, __LINE__, NULL) +#define ASSERTSZ(f, sz) DBG_AssertFunction(f, #f, __FILE__, __LINE__, sz) +#else // !DEBUG +#define ASSERT(f) +#define ASSERTSZ(f, sz) +#endif // !DEBUG + + +extern DLL_GLOBAL const Vector g_vecZero; + +// +// Constants that were used only by QC (maybe not used at all now) +// +// Un-comment only as needed +// +#define LANGUAGE_ENGLISH 0 +#define LANGUAGE_GERMAN 1 +#define LANGUAGE_FRENCH 2 +#define LANGUAGE_BRITISH 3 + +extern DLL_GLOBAL int g_Language; + +#define AMBIENT_SOUND_STATIC 0 // medium radius attenuation +#define AMBIENT_SOUND_EVERYWHERE 1 +#define AMBIENT_SOUND_SMALLRADIUS 2 +#define AMBIENT_SOUND_MEDIUMRADIUS 4 +#define AMBIENT_SOUND_LARGERADIUS 8 +#define AMBIENT_SOUND_START_SILENT 16 +#define AMBIENT_SOUND_NOT_LOOPING 32 + +#define SPEAKER_START_SILENT 1 // wait for trigger 'on' to start announcements + +#define SND_SPAWNING (1<<8) // duplicated in protocol.h we're spawing, used in some cases for ambients +#define SND_STOP (1<<5) // duplicated in protocol.h stop sound +#define SND_CHANGE_VOL (1<<6) // duplicated in protocol.h change sound vol +#define SND_CHANGE_PITCH (1<<7) // duplicated in protocol.h change sound pitch + +#define LFO_SQUARE 1 +#define LFO_TRIANGLE 2 +#define LFO_RANDOM 3 + +// func_rotating +#define SF_BRUSH_ROTATE_Y_AXIS 0 +#define SF_BRUSH_ROTATE_INSTANT 1 +#define SF_BRUSH_ROTATE_BACKWARDS 2 +#define SF_BRUSH_ROTATE_Z_AXIS 4 +#define SF_BRUSH_ROTATE_X_AXIS 8 +#define SF_PENDULUM_AUTO_RETURN 16 +#define SF_PENDULUM_PASSABLE 32 + + +#define SF_BRUSH_ROTATE_SMALLRADIUS 128 +#define SF_BRUSH_ROTATE_MEDIUMRADIUS 256 +#define SF_BRUSH_ROTATE_LARGERADIUS 512 + +#define PUSH_BLOCK_ONLY_X 1 +#define PUSH_BLOCK_ONLY_Y 2 + +#define VEC_HULL_MIN Vector(-16, -16, -36) +#define VEC_HULL_MAX Vector( 16, 16, 36) +#define VEC_HUMAN_HULL_MIN Vector( -16, -16, 0 ) +#define VEC_HUMAN_HULL_MAX Vector( 16, 16, 72 ) +#define VEC_HUMAN_HULL_DUCK Vector( 16, 16, 36 ) + +#define VEC_VIEW Vector( 0, 0, 28 ) + +#define VEC_DUCK_HULL_MIN Vector(-16, -16, -18 ) +#define VEC_DUCK_HULL_MAX Vector( 16, 16, 18) +#define VEC_DUCK_VIEW Vector( 0, 0, 12 ) + +#define SVC_TEMPENTITY 23 +#define SVC_INTERMISSION 30 +#define SVC_CDTRACK 32 +#define SVC_WEAPONANIM 35 +#define SVC_ROOMTYPE 37 +#define SVC_DIRECTOR 51 + + + +// triggers +#define SF_TRIGGER_ALLOWMONSTERS 1// monsters allowed to fire this trigger +#define SF_TRIGGER_NOCLIENTS 2// players not allowed to fire this trigger +#define SF_TRIGGER_PUSHABLES 4// only pushables can fire this trigger + +// func breakable +#define SF_BREAK_TRIGGER_ONLY 1// may only be broken by trigger +#define SF_BREAK_TOUCH 2// can be 'crashed through' by running player (plate glass) +#define SF_BREAK_PRESSURE 4// can be broken by a player standing on it +#define SF_BREAK_CROWBAR 256// instant break if hit with crowbar + +// func_pushable (it's also func_breakable, so don't collide with those flags) +#define SF_PUSH_BREAKABLE 128 + +#define SF_LIGHT_START_OFF 1 + +#define SPAWNFLAG_NOMESSAGE 1 +#define SPAWNFLAG_NOTOUCH 1 +#define SPAWNFLAG_DROIDONLY 4 + +#define SPAWNFLAG_USEONLY 1 // can't be touched, must be used (buttons) + +#define TELE_PLAYER_ONLY 1 +#define TELE_SILENT 2 + +#define SF_TRIG_PUSH_ONCE 1 + + +// Sound Utilities + +// sentence groups +#define CBSENTENCENAME_MAX 16 +#define CVOXFILESENTENCEMAX 1536 // max number of sentences in game. NOTE: this must match + // CVOXFILESENTENCEMAX in engine\sound.h!!! + +extern char gszallsentencenames[CVOXFILESENTENCEMAX][CBSENTENCENAME_MAX]; +extern int gcallsentences; + +int USENTENCEG_Pick(int isentenceg, char *szfound); +int USENTENCEG_PickSequential(int isentenceg, char *szfound, int ipick, int freset); +void USENTENCEG_InitLRU(unsigned char *plru, int count); + +void SENTENCEG_Init(); +void SENTENCEG_Stop(edict_t *entity, int isentenceg, int ipick); +int SENTENCEG_PlayRndI(edict_t *entity, int isentenceg, float volume, float attenuation, int flags, int pitch); +int SENTENCEG_PlayRndSz(edict_t *entity, const char *szrootname, float volume, float attenuation, int flags, int pitch); +int SENTENCEG_PlaySequentialSz(edict_t *entity, const char *szrootname, float volume, float attenuation, int flags, int pitch, int ipick, int freset); +int SENTENCEG_GetIndex(const char *szrootname); +int SENTENCEG_Lookup(const char *sample, char *sentencenum); + +void TEXTURETYPE_Init(); +char TEXTURETYPE_Find(char *name); +float TEXTURETYPE_PlaySound(TraceResult *ptr, Vector vecSrc, Vector vecEnd, int iBulletType); + +// NOTE: use EMIT_SOUND_DYN to set the pitch of a sound. Pitch of 100 +// is no pitch shift. Pitch > 100 up to 255 is a higher pitch, pitch < 100 +// down to 1 is a lower pitch. 150 to 70 is the realistic range. +// EMIT_SOUND_DYN with pitch != 100 should be used sparingly, as it's not quite as +// fast as EMIT_SOUND (the pitchshift mixer is not native coded). + +void EMIT_SOUND_DYN(edict_t *entity, int channel, const char *sample, float volume, float attenuation, + int flags, int pitch); + + +inline void EMIT_SOUND(edict_t *entity, int channel, const char *sample, float volume, float attenuation) +{ + EMIT_SOUND_DYN(entity, channel, sample, volume, attenuation, 0, PITCH_NORM); +} + +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 < static_cast(ARRAYSIZE( a )); i++ ) PRECACHE_SOUND((char *) a [i]); } + +#define EMIT_SOUND_ARRAY_DYN( chan, array ) \ + EMIT_SOUND_DYN ( ENT(pev), chan , array [ RANDOM_LONG(0,ARRAYSIZE( array )-1) ], 1.0, ATTN_NORM, 0, RANDOM_LONG(95,105) ); + +#define RANDOM_SOUND_ARRAY( array ) (array) [ RANDOM_LONG(0,ARRAYSIZE( (array) )-1) ] + +#define PLAYBACK_EVENT( flags, who, index ) PLAYBACK_EVENT_FULL( flags, who, index, 0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0 ); +#define PLAYBACK_EVENT_DELAY( flags, who, index, delay ) PLAYBACK_EVENT_FULL( flags, who, index, delay, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0 ); + +#define GROUP_OP_AND 0 +#define GROUP_OP_NAND 1 + +extern int g_groupmask; +extern int g_groupop; + +class UTIL_GroupTrace +{ +public: + UTIL_GroupTrace( int groupmask, int op ); + ~UTIL_GroupTrace( void ); + +private: + int m_oldgroupmask, m_oldgroupop; +}; + +void UTIL_SetGroupTrace( int groupmask, int op ); +void UTIL_UnsetGroupTrace( void ); + +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ); +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ); + +float UTIL_WeaponTimeBase( void ); diff --git a/hlsdk/dlls/vector.h b/hlsdk/dlls/vector.h new file mode 100644 index 00000000..08148de6 --- /dev/null +++ b/hlsdk/dlls/vector.h @@ -0,0 +1,111 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef VECTOR_H +#define VECTOR_H + +//========================================================= +// 2DVector - used for many pathfinding and many other +// operations that are treated as planar rather than 3d. +//========================================================= +class Vector2D +{ +public: + inline Vector2D(void): x(0.0), y(0.0) { } + inline Vector2D(float X, float Y): x(0.0), y(0.0) { x = X; y = Y; } + inline Vector2D operator+(const Vector2D& v) const { return Vector2D(x+v.x, y+v.y); } + inline Vector2D operator-(const Vector2D& v) const { return Vector2D(x-v.x, y-v.y); } + inline Vector2D operator*(float fl) const { return Vector2D(x*fl, y*fl); } + inline Vector2D operator/(float fl) const { return Vector2D(x/fl, y/fl); } + + inline float Length(void) const { return sqrt(x*x + y*y ); } + + inline Vector2D Normalize ( void ) const + { + + float flLen = Length(); + if ( flLen == 0 ) + { + return Vector2D( 0, 0 ); + } + else + { + flLen = 1 / flLen; + return Vector2D( x * flLen, y * flLen ); + } + } + + vec_t x, y; +}; + +inline float DotProduct(const Vector2D& a, const Vector2D& b) { return( a.x*b.x + a.y*b.y ); } +inline Vector2D operator*(float fl, const Vector2D& v) { return v * fl; } + +//========================================================= +// 3D Vector +//========================================================= +class Vector // same data-layout as engine's vec3_t, +{ // which is a vec_t[3] +public: + // Construction/destruction + inline Vector(void): x(0.0), y(0.0), z(0.0) { } + inline Vector(float X, float Y, float Z): x(0.0), y(0.0), z(0.0) { x = X; y = Y; z = Z; } + //inline Vector(double X, double Y, double Z) { x = (float)X; y = (float)Y; z = (float)Z; } + //inline Vector(int X, int Y, int Z) { x = (float)X; y = (float)Y; z = (float)Z; } + inline Vector(const Vector& v): x(0.0), y(0.0), z(0.0) { x = v.x; y = v.y; z = v.z; } + inline Vector(float rgfl[3]): x(0.0), y(0.0), z(0.0) { x = rgfl[0]; y = rgfl[1]; z = rgfl[2]; } + + // Operators + inline Vector operator-(void) const { return Vector(-x,-y,-z); } + inline int operator==(const Vector& v) const { return x==v.x && y==v.y && z==v.z; } + inline int operator!=(const Vector& v) const { return !(*this==v); } + inline Vector operator+(const Vector& v) const { return Vector(x+v.x, y+v.y, z+v.z); } + inline Vector operator-(const Vector& v) const { return Vector(x-v.x, y-v.y, z-v.z); } + inline Vector operator*(float fl) const { return Vector(x*fl, y*fl, z*fl); } + inline Vector operator/(float fl) const { return Vector(x/fl, y/fl, z/fl); } + + // Methods + inline void CopyToArray(float* rgfl) const { rgfl[0] = x, rgfl[1] = y, rgfl[2] = z; } + inline float Length(void) const { return sqrt(x*x + y*y + z*z); } + operator float *() { return &x; } // Vectors will now automatically convert to float * when needed + operator const float *() const { return &x; } // Vectors will now automatically convert to float * when needed + inline Vector Normalize(void) const + { + float flLen = Length(); + if (flLen == 0) return Vector(0,0,1); // ???? + flLen = 1 / flLen; + return Vector(x * flLen, y * flLen, z * flLen); + } + + inline Vector2D Make2D ( void ) const + { + Vector2D Vec2; + + Vec2.x = x; + Vec2.y = y; + + return Vec2; + } + inline float Length2D(void) const { return sqrt(x*x + y*y); } + + // Members + vec_t x, y, z; +}; +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 ); } + + + +#endif diff --git a/hlsdk/dlls/weapons.cpp b/hlsdk/dlls/weapons.cpp new file mode 100644 index 00000000..ae83b9d7 --- /dev/null +++ b/hlsdk/dlls/weapons.cpp @@ -0,0 +1,1579 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== weapons.cpp ======================================================== + + functions governing the selection/use of weapons for players + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "soundent.h" +#include "decals.h" +#include "gamerules.h" + +extern CGraph WorldGraph; +extern int gEvilImpulse101; + + +#define NOT_USED 255 + +DLL_GLOBAL short g_sModelIndexLaser;// holds the index for the laser beam +DLL_GLOBAL const char *g_pModelNameLaser = "sprites/laserbeam.spr"; +DLL_GLOBAL short g_sModelIndexLaserDot;// holds the index for the laser beam dot +DLL_GLOBAL short g_sModelIndexFireball;// holds the index for the fireball +DLL_GLOBAL short g_sModelIndexSmoke;// holds the index for the smoke cloud +DLL_GLOBAL short g_sModelIndexWExplosion;// holds the index for the underwater explosion +DLL_GLOBAL short g_sModelIndexBubbles;// holds the index for the bubbles model +DLL_GLOBAL short g_sModelIndexBloodDrop;// holds the sprite index for the initial blood +DLL_GLOBAL short g_sModelIndexBloodSpray;// holds the sprite index for splattered blood + +ItemInfo CBasePlayerItem::ItemInfoArray[MAX_WEAPONS]; +AmmoInfo CBasePlayerItem::AmmoInfoArray[MAX_AMMO_SLOTS]; + +extern int gmsgCurWeapon; + +MULTIDAMAGE gMultiDamage; + +#define TRACER_FREQ 4 // Tracers fire every fourth bullet + + +//========================================================= +// MaxAmmoCarry - pass in a name and this function will tell +// you the maximum amount of that type of ammunition that a +// player can carry. +//========================================================= +int MaxAmmoCarry( int iszName ) +{ + for ( int i = 0; i < MAX_WEAPONS; i++ ) + { + if ( CBasePlayerItem::ItemInfoArray[i].pszAmmo1 && !strcmp( STRING(iszName), CBasePlayerItem::ItemInfoArray[i].pszAmmo1 ) ) + return CBasePlayerItem::ItemInfoArray[i].iMaxAmmo1; + if ( CBasePlayerItem::ItemInfoArray[i].pszAmmo2 && !strcmp( STRING(iszName), CBasePlayerItem::ItemInfoArray[i].pszAmmo2 ) ) + return CBasePlayerItem::ItemInfoArray[i].iMaxAmmo2; + } + + ALERT( at_console, "MaxAmmoCarry() doesn't recognize '%s'!\n", STRING( iszName ) ); + return -1; +} + + +/* +============================================================================== + +MULTI-DAMAGE + +Collects multiple small damages into a single damage + +============================================================================== +*/ + +// +// ClearMultiDamage - resets the global multi damage accumulator +// +void ClearMultiDamage(void) +{ + gMultiDamage.pEntity = NULL; + gMultiDamage.amount = 0; + gMultiDamage.type = 0; +} + + +// +// ApplyMultiDamage - inflicts contents of global multi damage register on gMultiDamage.pEntity +// +// GLOBALS USED: +// gMultiDamage + +void ApplyMultiDamage(entvars_t *pevInflictor, entvars_t *pevAttacker ) +{ + Vector vecSpot1;//where blood comes from + Vector vecDir;//direction blood should go + TraceResult tr; + + if ( !gMultiDamage.pEntity ) + return; + + gMultiDamage.pEntity->TakeDamage(pevInflictor, pevAttacker, gMultiDamage.amount, gMultiDamage.type ); +} + + +// GLOBALS USED: +// gMultiDamage + +void AddMultiDamage( entvars_t *pevInflictor, CBaseEntity *pEntity, float flDamage, int bitsDamageType) +{ + if ( !pEntity ) + return; + + gMultiDamage.type |= bitsDamageType; + + if ( pEntity != gMultiDamage.pEntity ) + { + ApplyMultiDamage(pevInflictor,pevInflictor); // UNDONE: wrong attacker! + gMultiDamage.pEntity = pEntity; + gMultiDamage.amount = 0; + } + + gMultiDamage.amount += flDamage; +} + +/* +================ +SpawnBlood +================ +*/ +void SpawnBlood(Vector vecSpot, int bloodColor, float flDamage) +{ + UTIL_BloodDrips( vecSpot, g_vecAttackDir, bloodColor, (int)flDamage ); +} + + +int DamageDecal( CBaseEntity *pEntity, int bitsDamageType ) +{ + if ( !pEntity ) + return (DECAL_GUNSHOT1 + RANDOM_LONG(0,4)); + + return pEntity->DamageDecal( bitsDamageType ); +} + +void DecalGunshot( TraceResult *pTrace, int iBulletType ) +{ + // Is the entity valid + if ( !UTIL_IsValidEntity( pTrace->pHit ) ) + return; + + if ( VARS(pTrace->pHit)->solid == SOLID_BSP || VARS(pTrace->pHit)->movetype == MOVETYPE_PUSHSTEP ) + { + CBaseEntity *pEntity = NULL; + // Decal the wall with a gunshot + if ( !FNullEnt(pTrace->pHit) ) + pEntity = CBaseEntity::Instance(pTrace->pHit); + + switch( iBulletType ) + { + 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; + } + } +} + + + +// +// EjectBrass - tosses a brass shell from passed origin at passed velocity +// +void EjectBrass ( const Vector &vecOrigin, const Vector &vecVelocity, float rotation, int model, int soundtype ) +{ + // FIX: when the player shoots, their gun isn't in the same position as it is on the model other players see. + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecOrigin ); + WRITE_BYTE( TE_MODEL); + WRITE_COORD( vecOrigin.x); + WRITE_COORD( vecOrigin.y); + WRITE_COORD( vecOrigin.z); + WRITE_COORD( vecVelocity.x); + WRITE_COORD( vecVelocity.y); + WRITE_COORD( vecVelocity.z); + WRITE_ANGLE( rotation ); + WRITE_SHORT( model ); + WRITE_BYTE ( soundtype); + WRITE_BYTE ( 25 );// 2.5 seconds + MESSAGE_END(); +} + + +#if 0 +// UNDONE: This is no longer used? +void ExplodeModel( const Vector &vecOrigin, float speed, int model, int count ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecOrigin ); + WRITE_BYTE ( TE_EXPLODEMODEL ); + WRITE_COORD( vecOrigin.x ); + WRITE_COORD( vecOrigin.y ); + WRITE_COORD( vecOrigin.z ); + WRITE_COORD( speed ); + WRITE_SHORT( model ); + WRITE_SHORT( count ); + WRITE_BYTE ( 15 );// 1.5 seconds + MESSAGE_END(); +} +#endif + + +int giAmmoIndex = 0; + +// Precaches the ammo and queues the ammo info for sending to clients +void AddAmmoNameToAmmoRegistry( const char *szAmmoname ) +{ + // make sure it's not already in the registry + for ( int i = 0; i < MAX_AMMO_SLOTS; i++ ) + { + if ( !CBasePlayerItem::AmmoInfoArray[i].pszName) + continue; + + if ( stricmp( CBasePlayerItem::AmmoInfoArray[i].pszName, szAmmoname ) == 0 ) + return; // ammo already in registry, just quite + } + + + giAmmoIndex++; + ASSERT( giAmmoIndex < MAX_AMMO_SLOTS ); + if ( giAmmoIndex >= MAX_AMMO_SLOTS ) + giAmmoIndex = 0; + + CBasePlayerItem::AmmoInfoArray[giAmmoIndex].pszName = szAmmoname; + CBasePlayerItem::AmmoInfoArray[giAmmoIndex].iId = giAmmoIndex; // yes, this info is redundant +} + + +// Precaches the weapon and queues the weapon info for sending to clients +void UTIL_PrecacheOtherWeapon( const char *szClassname ) +{ + edict_t *pent; + + pent = CREATE_NAMED_ENTITY( MAKE_STRING( szClassname ) ); + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in UTIL_PrecacheOtherWeapon\n" ); + return; + } + + CBaseEntity *pEntity = CBaseEntity::Instance (VARS( pent )); + + if (pEntity) + { + ItemInfo II; + pEntity->Precache( ); + memset( &II, 0, sizeof II ); + if ( ((CBasePlayerItem*)pEntity)->GetItemInfo( &II ) ) + { + CBasePlayerItem::ItemInfoArray[II.iId] = II; + + if ( II.pszAmmo1 && *II.pszAmmo1 ) + { + AddAmmoNameToAmmoRegistry( II.pszAmmo1 ); + } + + if ( II.pszAmmo2 && *II.pszAmmo2 ) + { + AddAmmoNameToAmmoRegistry( II.pszAmmo2 ); + } + + memset( &II, 0, sizeof II ); + } + } + + REMOVE_ENTITY(pent); +} + +// called by worldspawn +void W_Precache(void) +{ + memset( CBasePlayerItem::ItemInfoArray, 0, sizeof(CBasePlayerItem::ItemInfoArray) ); + memset( CBasePlayerItem::AmmoInfoArray, 0, sizeof(CBasePlayerItem::AmmoInfoArray) ); + giAmmoIndex = 0; + + // custom items... + + // common world objects + UTIL_PrecacheOther( "item_suit" ); + UTIL_PrecacheOther( "item_battery" ); + UTIL_PrecacheOther( "item_antidote" ); + UTIL_PrecacheOther( "item_security" ); + UTIL_PrecacheOther( "item_longjump" ); + + // shotgun + UTIL_PrecacheOtherWeapon( "weapon_shotgun" ); + UTIL_PrecacheOther( "ammo_buckshot" ); + + // 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 + + g_sModelIndexFireball = PRECACHE_MODEL ("sprites/zerogxplode.spr");// fireball + g_sModelIndexWExplosion = PRECACHE_MODEL ("sprites/WXplo1.spr");// underwater fireball + g_sModelIndexSmoke = PRECACHE_MODEL ("sprites/steam1.spr");// smoke + g_sModelIndexBubbles = PRECACHE_MODEL ("sprites/bubble.spr");//bubbles + g_sModelIndexBloodSpray = PRECACHE_MODEL ("sprites/bloodspray.spr"); // initial blood + g_sModelIndexBloodDrop = PRECACHE_MODEL ("sprites/blood.spr"); // splattered blood + + g_sModelIndexLaser = PRECACHE_MODEL( (char *)g_pModelNameLaser ); + g_sModelIndexLaserDot = PRECACHE_MODEL("sprites/laserdot.spr"); + + + // used by explosions + PRECACHE_MODEL ("models/grenade.mdl"); + PRECACHE_MODEL ("sprites/explode1.spr"); + + PRECACHE_SOUND ("weapons/debris1.wav");// explosion aftermaths + PRECACHE_SOUND ("weapons/debris2.wav");// explosion aftermaths + PRECACHE_SOUND ("weapons/debris3.wav");// explosion aftermaths + + PRECACHE_SOUND ("weapons/grenade_hit1.wav");//grenade + PRECACHE_SOUND ("weapons/grenade_hit2.wav");//grenade + PRECACHE_SOUND ("weapons/grenade_hit3.wav");//grenade + + PRECACHE_SOUND ("weapons/bullet_hit1.wav"); // hit by bullet + 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 ), + DEFINE_FIELD( CBasePlayerItem, m_pNext, FIELD_CLASSPTR ), + //DEFINE_FIELD( CBasePlayerItem, m_fKnown, FIELD_INTEGER ),Reset to zero on load + DEFINE_FIELD( CBasePlayerItem, m_iId, FIELD_INTEGER ), + // DEFINE_FIELD( CBasePlayerItem, m_iIdPrimary, FIELD_INTEGER ), + // DEFINE_FIELD( CBasePlayerItem, m_iIdSecondary, FIELD_INTEGER ), +}; +IMPLEMENT_SAVERESTORE( CBasePlayerItem, CBaseAnimating ); + + +TYPEDESCRIPTION CBasePlayerWeapon::m_SaveData[] = +{ +#if defined( CLIENT_WEAPONS ) + DEFINE_FIELD( CBasePlayerWeapon, m_flNextPrimaryAttack, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayerWeapon, m_flNextSecondaryAttack, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayerWeapon, m_flTimeWeaponIdle, FIELD_FLOAT ), +#else // CLIENT_WEAPONS + DEFINE_FIELD( CBasePlayerWeapon, m_flNextPrimaryAttack, FIELD_TIME ), + DEFINE_FIELD( CBasePlayerWeapon, m_flNextSecondaryAttack, FIELD_TIME ), + DEFINE_FIELD( CBasePlayerWeapon, m_flTimeWeaponIdle, FIELD_TIME ), +#endif // CLIENT_WEAPONS + DEFINE_FIELD( CBasePlayerWeapon, m_iPrimaryAmmoType, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerWeapon, m_iSecondaryAmmoType, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerWeapon, m_iClip, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerWeapon, m_iDefaultAmmo, FIELD_INTEGER ), +// DEFINE_FIELD( CBasePlayerWeapon, m_iClientClip, FIELD_INTEGER ) , reset to zero on load so hud gets updated correctly +// DEFINE_FIELD( CBasePlayerWeapon, m_iClientWeaponState, FIELD_INTEGER ), reset to zero on load so hud gets updated correctly +}; + +IMPLEMENT_SAVERESTORE( CBasePlayerWeapon, CBasePlayerItem ); + + +void CBasePlayerItem :: SetObjectCollisionBox( void ) +{ + pev->absmin = pev->origin + Vector(-24, -24, 0); + pev->absmax = pev->origin + Vector(24, 24, 16); +} + + +//========================================================= +// Sets up movetype, size, solidtype for a new weapon. +//========================================================= +void CBasePlayerItem :: FallInit( void ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_BBOX; + + UTIL_SetOrigin( pev, pev->origin ); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0) );//pointsize until it lands on the ground. + + SetTouch( &CBasePlayerItem::DefaultTouch ); + SetThink( &CBasePlayerItem::FallThink ); + + pev->nextthink = gpGlobals->time + 0.1; +} + +//========================================================= +// FallThink - Items that have just spawned run this think +// to catch them when they hit the ground. Once we're sure +// that the object is grounded, we change its solid type +// to trigger and set it in a large box that helps the +// player get it. +//========================================================= +void CBasePlayerItem::FallThink ( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + if ( pev->flags & FL_ONGROUND ) + { + // clatter if we have an owner (i.e., dropped by someone) + // don't clatter if the gun is waiting to respawn (if it's waiting, it is invisible!) + if ( !FNullEnt( pev->owner ) ) + { + int pitch = 95 + RANDOM_LONG(0,29); + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "items/weapondrop1.wav", 1, ATTN_NORM, 0, pitch); + } + + // lie flat + pev->angles.x = 0; + pev->angles.z = 0; + + Materialize(); + } +} + +//========================================================= +// Materialize - make a CBasePlayerItem visible and tangible +//========================================================= +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; + } + + pev->solid = SOLID_TRIGGER; + + UTIL_SetOrigin( pev, pev->origin );// link into world. + SetTouch (&CBasePlayerItem::DefaultTouch); + SetThink (NULL); + +} + +//========================================================= +// AttemptToMaterialize - the item is trying to rematerialize, +// should it do so now or wait longer? +//========================================================= +void CBasePlayerItem::AttemptToMaterialize( void ) +{ + float time = g_pGameRules->FlWeaponTryRespawn( this ); + + if ( time == 0 ) + { + Materialize(); + return; + } + + pev->nextthink = gpGlobals->time + time; +} + +//========================================================= +// CheckRespawn - a player is taking this weapon, should +// it respawn? +//========================================================= +void CBasePlayerItem :: CheckRespawn ( void ) +{ + switch ( g_pGameRules->WeaponShouldRespawn( this ) ) + { + case GR_WEAPON_RESPAWN_YES: + Respawn(); + break; + case GR_WEAPON_RESPAWN_NO: + return; + break; + } +} + +//========================================================= +// Respawn- this item is already in the world, but it is +// invisible and intangible. Make it visible and tangible. +//========================================================= +CBaseEntity* CBasePlayerItem::Respawn( void ) +{ + // make a copy of this weapon that is invisible and inaccessible to players (no touch function). The weapon spawn/respawn code + // will decide when to make the weapon visible and touchable. + CBaseEntity *pNewWeapon = CBaseEntity::Create( (char *)STRING( pev->classname ), g_pGameRules->VecWeaponRespawnSpot( this ), pev->angles, pev->owner ); + + if ( pNewWeapon ) + { + pNewWeapon->pev->effects |= EF_NODRAW;// invisible for now + pNewWeapon->SetTouch( NULL );// no touch + pNewWeapon->SetThink( &CBasePlayerItem::AttemptToMaterialize ); + + DROP_TO_FLOOR ( ENT(pev) ); + + // not a typo! We want to know when the weapon the player just picked up should respawn! This new entity we created is the replacement, + // but when it should respawn is based on conditions belonging to the weapon that was taken. + pNewWeapon->pev->nextthink = g_pGameRules->FlWeaponRespawnTime( this ); + } + else + { + ALERT ( at_console, "Respawn failed to create %s!\n", STRING( pev->classname ) ); + } + + return pNewWeapon; +} + +void CBasePlayerItem::DefaultTouch( CBaseEntity *pOther ) +{ + // if it's not a player, ignore + if ( !pOther->IsPlayer() ) + return; + + CBasePlayer *pPlayer = (CBasePlayer *)pOther; + + // can I have this? + if ( !g_pGameRules->CanHavePlayerItem( pPlayer, this ) ) + { + if ( gEvilImpulse101 ) + { + UTIL_Remove( this ); + } + return; + } + + if (pOther->AddPlayerItem( this )) + { + AttachToPlayer( pPlayer ); + EMIT_SOUND(ENT(pPlayer->pev), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM); + } + + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); // UNDONE: when should this happen? +} + +BOOL CanAttack( float attack_time, float curtime, BOOL isPredicted ) +{ +#if defined( CLIENT_WEAPONS ) + if ( !isPredicted ) +#else + if ( 1 ) +#endif + { + return ( attack_time <= curtime ) ? TRUE : FALSE; + } + else + { + return ( attack_time <= 0.0 ) ? TRUE : FALSE; + } +} + +void CBasePlayerWeapon::ItemPostFrame( void ) +{ + if ((m_fInReload) && ( m_pPlayer->m_flNextAttack <= UTIL_WeaponTimeBase() ) ) + { + // 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; + + m_pPlayer->TabulateAmmo(); + + m_fInReload = FALSE; + } + + if ((m_pPlayer->pev->button & IN_ATTACK2) && CanAttack( m_flNextSecondaryAttack, gpGlobals->time, UseDecrement() ) ) + { + if ( pszAmmo2() && !m_pPlayer->m_rgAmmo[SecondaryAmmoIndex()] ) + { + m_fFireOnEmpty = TRUE; + } + + m_pPlayer->TabulateAmmo(); + SecondaryAttack(); + m_pPlayer->pev->button &= ~IN_ATTACK2; + } + 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()] ) ) + { + m_fFireOnEmpty = TRUE; + } + + m_pPlayer->TabulateAmmo(); + 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; + + 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; + } + + // catch all + if ( ShouldWeaponIdle() ) + { + WeaponIdle(); + } +} + +void CBasePlayerItem::DestroyItem( void ) +{ + if ( m_pPlayer ) + { + // if attached to a player, remove. + m_pPlayer->RemovePlayerItem( this ); + } + + Kill( ); +} + +int CBasePlayerItem::AddToPlayer( CBasePlayer *pPlayer ) +{ + m_pPlayer = pPlayer; + + return TRUE; +} + +void CBasePlayerItem::Drop( void ) +{ + SetTouch( NULL ); + SetThink(&CBaseEntity::SUB_Remove); + pev->nextthink = gpGlobals->time + .1; +} + +void CBasePlayerItem::Kill( void ) +{ + SetTouch( NULL ); + SetThink(&CBaseEntity::SUB_Remove); + pev->nextthink = gpGlobals->time + .1; +} + +void CBasePlayerItem::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->pev->viewmodel = 0; + m_pPlayer->pev->weaponmodel = 0; +} + +void CBasePlayerItem::AttachToPlayer ( CBasePlayer *pPlayer ) +{ + pev->movetype = MOVETYPE_FOLLOW; + pev->solid = SOLID_NOT; + 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->owner = pPlayer->edict(); + pev->nextthink = gpGlobals->time + .1; + SetTouch( NULL ); +} + +// CALLED THROUGH the newly-touched weapon's instance. The existing player weapon is pOriginal +int CBasePlayerWeapon::AddDuplicate( CBasePlayerItem *pOriginal ) +{ + if ( m_iDefaultAmmo ) + { + return ExtractAmmo( (CBasePlayerWeapon *)pOriginal ); + } + else + { + // a dead player dropped this. + return ExtractClipAmmo( (CBasePlayerWeapon *)pOriginal ); + } +} + + +int CBasePlayerWeapon::AddToPlayer( CBasePlayer *pPlayer ) +{ + int bResult = CBasePlayerItem::AddToPlayer( pPlayer ); + + pPlayer->pev->weapons |= (1<GetAmmoIndex( pszAmmo1() ); + m_iSecondaryAmmoType = pPlayer->GetAmmoIndex( pszAmmo2() ); + } + + + if (bResult) + return AddWeapon( ); + return FALSE; +} + +int CBasePlayerWeapon::UpdateClientData( CBasePlayer *pPlayer ) +{ + BOOL bSend = FALSE; + int state = 0; + if ( pPlayer->m_pActiveItem == this ) + { + if ( pPlayer->m_fOnTarget ) + state = WEAPON_IS_ONTARGET; + else + state = 1; + } + + // Forcing send of all data! + if ( !pPlayer->m_fWeapon ) + { + bSend = TRUE; + } + + // This is the current or last weapon, so the state will need to be updated + if ( this == pPlayer->m_pActiveItem || + this == pPlayer->m_pClientActiveItem ) + { + if ( pPlayer->m_pActiveItem != pPlayer->m_pClientActiveItem ) + { + bSend = TRUE; + } + } + + // If the ammo, state, or fov has changed, update the weapon + if ( m_iClip != m_iClientClip || + state != m_iClientWeaponState || + pPlayer->m_iFOV != pPlayer->m_iClientFOV ) + { + bSend = TRUE; + } + + if ( bSend ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pPlayer->pev ); + WRITE_BYTE( state ); + WRITE_BYTE( m_iId ); + WRITE_BYTE( m_iClip ); + MESSAGE_END(); + + m_iClientClip = m_iClip; + m_iClientWeaponState = state; + pPlayer->m_fWeapon = TRUE; + } + + if ( m_pNext ) + m_pNext->UpdateClientData( pPlayer ); + + return 1; +} + + +void CBasePlayerWeapon::SendWeaponAnim( int iAnim, int skiplocal, int body ) +{ + if ( UseDecrement() ) + skiplocal = 1; + else + skiplocal = 0; + + m_pPlayer->pev->weaponanim = iAnim; + +#if defined( CLIENT_WEAPONS ) + if ( skiplocal && ENGINE_CANSKIP( m_pPlayer->edict() ) ) + return; +#endif + + MESSAGE_BEGIN( MSG_ONE, SVC_WEAPONANIM, NULL, m_pPlayer->pev ); + WRITE_BYTE( iAnim ); // sequence number + WRITE_BYTE( pev->body ); // weaponmodel bodygroup. + MESSAGE_END(); +} + +BOOL CBasePlayerWeapon :: AddPrimaryAmmo( int iCount, char *szName, int iMaxClip, int iMaxCarry ) +{ + int iIdAmmo; + + if (iMaxClip < 1) + { + m_iClip = -1; + iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName, iMaxCarry ); + } + else if (m_iClip == 0) + { + int i; + i = min( m_iClip + iCount, iMaxClip ) - m_iClip; + m_iClip += i; + iIdAmmo = m_pPlayer->GiveAmmo( iCount - i, szName, iMaxCarry ); + } + else + { + iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName, iMaxCarry ); + } + + // m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] = iMaxCarry; // hack for testing + + if (iIdAmmo > 0) + { + m_iPrimaryAmmoType = iIdAmmo; + if (m_pPlayer->HasPlayerItem( this ) ) + { + // 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); + } + } + + return iIdAmmo > 0 ? TRUE : FALSE; +} + + +BOOL CBasePlayerWeapon :: AddSecondaryAmmo( int iCount, char *szName, int iMax ) +{ + int iIdAmmo; + + iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName, iMax ); + + //m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] = iMax; // hack for testing + + if (iIdAmmo > 0) + { + m_iSecondaryAmmoType = iIdAmmo; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + } + return iIdAmmo > 0 ? TRUE : FALSE; +} + +//========================================================= +// IsUseable - this function determines whether or not a +// weapon is useable by the player in its current state. +// (does it have ammo loaded? do I have any ammo for the +// weapon?, etc) +//========================================================= +BOOL CBasePlayerWeapon :: IsUseable( void ) +{ + if ( m_iClip <= 0 ) + { + if ( m_pPlayer->m_rgAmmo[ PrimaryAmmoIndex() ] <= 0 && iMaxAmmo1() != -1 ) + { + // clip is empty (or nonexistant) and the player has no more ammo of this type. + return FALSE; + } + } + + return TRUE; +} + +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; +} + +BOOL CBasePlayerWeapon :: DefaultDeploy( char *szViewModel, char *szWeaponModel, int iAnim, char *szAnimExt, int skiplocal /* = 0 */, int body ) +{ + if (!CanDeploy( )) + return FALSE; + + m_pPlayer->TabulateAmmo(); + m_pPlayer->pev->viewmodel = MAKE_STRING(szViewModel); + m_pPlayer->pev->weaponmodel = MAKE_STRING(szWeaponModel); + strcpy( m_pPlayer->m_szAnimExtention, szAnimExt ); + SendWeaponAnim( iAnim, skiplocal, body ); + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.0; + + return TRUE; +} + + +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() ? 1 : 0 ); + + m_fInReload = TRUE; + + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 3; + return TRUE; +} + +BOOL CBasePlayerWeapon :: PlayEmptySound( void ) +{ + if (m_iPlayEmptySound) + { + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/357_cock1.wav", 0.8, ATTN_NORM); + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +void CBasePlayerWeapon :: ResetEmptySound( void ) +{ + m_iPlayEmptySound = 1; +} + +//========================================================= +//========================================================= +int CBasePlayerWeapon::PrimaryAmmoIndex( void ) +{ + return m_iPrimaryAmmoType; +} + +//========================================================= +//========================================================= +int CBasePlayerWeapon::SecondaryAmmoIndex( void ) +{ + return -1; +} + +void CBasePlayerWeapon::Holster( int skiplocal /* = 0 */ ) +{ + m_fInReload = FALSE; // cancel any reload in progress. + m_pPlayer->pev->viewmodel = 0; + m_pPlayer->pev->weaponmodel = 0; +} + +void CBasePlayerAmmo::Spawn( void ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 16)); + UTIL_SetOrigin( pev, pev->origin ); + + SetTouch( &CBasePlayerAmmo::DefaultTouch ); +} + +CBaseEntity* CBasePlayerAmmo::Respawn( void ) +{ + pev->effects |= EF_NODRAW; + SetTouch( NULL ); + + UTIL_SetOrigin( pev, g_pGameRules->VecAmmoRespawnSpot( this ) );// move to wherever I'm supposed to repawn. + + SetThink( &CBasePlayerAmmo::Materialize ); + pev->nextthink = g_pGameRules->FlAmmoRespawnTime( this ); + + return this; +} + +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; + } + + SetTouch( &CBasePlayerAmmo::DefaultTouch ); +} + +void CBasePlayerAmmo :: DefaultTouch( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + { + return; + } + + if (AddAmmo( pOther )) + { + if ( g_pGameRules->AmmoShouldRespawn( this ) == GR_AMMO_RESPAWN_YES ) + { + Respawn(); + } + else + { + SetTouch( NULL ); + SetThink(&CBaseEntity::SUB_Remove); + pev->nextthink = gpGlobals->time + .1; + } + } + else if (gEvilImpulse101) + { + // evil impulse 101 hack, kill always + SetTouch( NULL ); + SetThink(&CBaseEntity::SUB_Remove); + pev->nextthink = gpGlobals->time + .1; + } +} + +//========================================================= +// called by the new item with the existing item as parameter +// +// if we call ExtractAmmo(), it's because the player is picking up this type of weapon for +// the first time. If it is spawned by the world, m_iDefaultAmmo will have a default ammo amount in it. +// if this is a weapon dropped by a dying player, has 0 m_iDefaultAmmo, which means only the ammo in +// the weapon clip comes along. +//========================================================= +int CBasePlayerWeapon::ExtractAmmo( CBasePlayerWeapon *pWeapon ) +{ + int iReturn = 0; + + if ( pszAmmo1() != NULL ) + { + // blindly call with m_iDefaultAmmo. It's either going to be a value or zero. If it is zero, + // we only get the ammo in the weapon's clip, which is what we want. + iReturn = pWeapon->AddPrimaryAmmo( m_iDefaultAmmo, (char *)pszAmmo1(), iMaxClip(), iMaxAmmo1() ); + m_iDefaultAmmo = 0; + } + + if ( pszAmmo2() != NULL ) + { + iReturn = pWeapon->AddSecondaryAmmo( 0, (char *)pszAmmo2(), iMaxAmmo2() ); + } + + return iReturn; +} + +//========================================================= +// called by the new item's class with the existing item as parameter +//========================================================= +int CBasePlayerWeapon::ExtractClipAmmo( CBasePlayerWeapon *pWeapon ) +{ + int iAmmo; + + if ( m_iClip == WEAPON_NOCLIP ) + { + iAmmo = 0;// guns with no clips always come empty if they are second-hand + } + else + { + iAmmo = m_iClip; + } + + return pWeapon->m_pPlayer->GiveAmmo( iAmmo, (char *)pszAmmo1(), iMaxAmmo1() ); // , &m_iPrimaryAmmoType +} + +//========================================================= +// RetireWeapon - no more ammo for this gun, put it away. +//========================================================= +void CBasePlayerWeapon::RetireWeapon( void ) +{ + // first, no viewmodel at all. + m_pPlayer->pev->viewmodel = iStringNull; + m_pPlayer->pev->weaponmodel = iStringNull; + //m_pPlayer->pev->viewmodelindex = NULL; + + g_pGameRules->GetNextBestWeapon( m_pPlayer, this ); +} + +//********************************************************* +// weaponbox code: +//********************************************************* + +LINK_ENTITY_TO_CLASS( weaponbox, CWeaponBox ); + +TYPEDESCRIPTION CWeaponBox::m_SaveData[] = +{ + DEFINE_ARRAY( CWeaponBox, m_rgAmmo, FIELD_INTEGER, MAX_AMMO_SLOTS ), + DEFINE_ARRAY( CWeaponBox, m_rgiszAmmo, FIELD_STRING, MAX_AMMO_SLOTS ), + DEFINE_ARRAY( CWeaponBox, m_rgpPlayerItems, FIELD_CLASSPTR, MAX_ITEM_TYPES ), + DEFINE_FIELD( CWeaponBox, m_cAmmoTypes, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CWeaponBox, CBaseEntity ); + +//========================================================= +// +//========================================================= +void CWeaponBox::Precache( void ) +{ + PRECACHE_MODEL("models/w_weaponbox.mdl"); +} + +//========================================================= +//========================================================= +void CWeaponBox :: KeyValue( KeyValueData *pkvd ) +{ + if ( m_cAmmoTypes < MAX_AMMO_SLOTS ) + { + PackAmmo( ALLOC_STRING(pkvd->szKeyName), atoi(pkvd->szValue) ); + m_cAmmoTypes++;// count this new ammo type. + + pkvd->fHandled = TRUE; + } + else + { + ALERT ( at_console, "WeaponBox too full! only %d ammotypes allowed\n", MAX_AMMO_SLOTS ); + } +} + +//========================================================= +// CWeaponBox - Spawn +//========================================================= +void CWeaponBox::Spawn( void ) +{ + Precache( ); + + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + + UTIL_SetSize( pev, g_vecZero, g_vecZero ); + + SET_MODEL( ENT(pev), "models/w_weaponbox.mdl"); +} + +//========================================================= +// CWeaponBox - Kill - the think function that removes the +// box from the world. +//========================================================= +void CWeaponBox::Kill( void ) +{ + CBasePlayerItem *pWeapon; + int i; + + // destroy the weapons + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + pWeapon = m_rgpPlayerItems[ i ]; + + while ( pWeapon ) + { + pWeapon->SetThink(&CBaseEntity::SUB_Remove); + pWeapon->pev->nextthink = gpGlobals->time + 0.1; + pWeapon = pWeapon->m_pNext; + } + } + + // remove the box + UTIL_Remove( this ); +} + +//========================================================= +// CWeaponBox - Touch: try to add my contents to the toucher +// if the toucher is a player. +//========================================================= +void CWeaponBox::Touch( CBaseEntity *pOther ) +{ + if ( !(pev->flags & FL_ONGROUND ) ) + { + return; + } + + if ( !pOther->IsPlayer() ) + { + // only players may touch a weaponbox. + return; + } + + if ( !pOther->IsAlive() ) + { + // no dead guys. + return; + } + + CBasePlayer *pPlayer = (CBasePlayer *)pOther; + int i; + +// dole out ammo + for ( i = 0 ; i < MAX_AMMO_SLOTS ; i++ ) + { + if ( !FStringNull( m_rgiszAmmo[ i ] ) ) + { + // there's some ammo of this type. + pPlayer->GiveAmmo( m_rgAmmo[ i ], (char *)STRING( m_rgiszAmmo[ i ] ), MaxAmmoCarry( m_rgiszAmmo[ i ] ) ); + + //ALERT ( at_console, "Gave %d rounds of %s\n", m_rgAmmo[i], STRING(m_rgiszAmmo[i]) ); + + // now empty the ammo from the weaponbox since we just gave it to the player + m_rgiszAmmo[ i ] = iStringNull; + m_rgAmmo[ i ] = 0; + } + } + +// go through my weapons and try to give the usable ones to the player. +// it's important the the player be given ammo first, so the weapons code doesn't refuse +// to deploy a better weapon that the player may pick up because he has no ammo for it. + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + CBasePlayerItem *pItem; + + // have at least one weapon in this slot + while ( m_rgpPlayerItems[ i ] ) + { + //ALERT ( at_console, "trying to give %s\n", STRING( m_rgpPlayerItems[ i ]->pev->classname ) ); + + pItem = m_rgpPlayerItems[ i ]; + m_rgpPlayerItems[ i ] = m_rgpPlayerItems[ i ]->m_pNext;// unlink this weapon from the box + + if ( pPlayer->AddPlayerItem( pItem ) ) + { + pItem->AttachToPlayer( pPlayer ); + } + } + } + } + + EMIT_SOUND( pOther->edict(), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM ); + SetTouch(NULL); + UTIL_Remove(this); +} + +//========================================================= +// CWeaponBox - PackWeapon: Add this weapon to the box +//========================================================= +BOOL CWeaponBox::PackWeapon( CBasePlayerItem *pWeapon ) +{ + // is one of these weapons already packed in this box? + if ( HasWeapon( pWeapon ) ) + { + return FALSE;// box can only hold one of each weapon type + } + + if ( pWeapon->m_pPlayer ) + { + if ( !pWeapon->m_pPlayer->RemovePlayerItem( pWeapon ) ) + { + // failed to unhook the weapon from the player! + return FALSE; + } + } + + int iWeaponSlot = pWeapon->iItemSlot(); + + if ( m_rgpPlayerItems[ iWeaponSlot ] ) + { + // there's already one weapon in this slot, so link this into the slot's column + pWeapon->m_pNext = m_rgpPlayerItems[ iWeaponSlot ]; + m_rgpPlayerItems[ iWeaponSlot ] = pWeapon; + } + else + { + // first weapon we have for this slot + m_rgpPlayerItems[ iWeaponSlot ] = pWeapon; + pWeapon->m_pNext = NULL; + } + + pWeapon->pev->spawnflags |= SF_NORESPAWN;// never respawn + pWeapon->pev->movetype = MOVETYPE_NONE; + pWeapon->pev->solid = SOLID_NOT; + pWeapon->pev->effects = EF_NODRAW; + pWeapon->pev->modelindex = 0; + pWeapon->pev->model = iStringNull; + pWeapon->pev->owner = edict(); + pWeapon->SetThink( NULL );// crowbar may be trying to swing again, etc. + pWeapon->SetTouch( NULL ); + pWeapon->m_pPlayer = NULL; + + //ALERT ( at_console, "packed %s\n", STRING(pWeapon->pev->classname) ); + + return TRUE; +} + +//========================================================= +// CWeaponBox - PackAmmo +//========================================================= +BOOL CWeaponBox::PackAmmo( int iszName, int iCount ) +{ + int iMaxCarry; + + if ( FStringNull( iszName ) ) + { + // error here + ALERT ( at_console, "NULL String in PackAmmo!\n" ); + return FALSE; + } + + iMaxCarry = MaxAmmoCarry( iszName ); + + if ( iMaxCarry != -1 && iCount > 0 ) + { + //ALERT ( at_console, "Packed %d rounds of %s\n", iCount, STRING(iszName) ); + GiveAmmo( iCount, (char *)STRING( iszName ), iMaxCarry ); + return TRUE; + } + + return FALSE; +} + +//========================================================= +// CWeaponBox - GiveAmmo +//========================================================= +int CWeaponBox::GiveAmmo( int iCount, char *szName, int iMax, int *pIndex/* = NULL*/ ) +{ + int i; + + for (i = 1; i < MAX_AMMO_SLOTS && !FStringNull( m_rgiszAmmo[i] ); i++) + { + if (stricmp( szName, STRING( m_rgiszAmmo[i])) == 0) + { + if (pIndex) + *pIndex = i; + + int iAdd = min( iCount, iMax - m_rgAmmo[i]); + if (iCount == 0 || iAdd > 0) + { + m_rgAmmo[i] += iAdd; + + return i; + } + return -1; + } + } + if (i < MAX_AMMO_SLOTS) + { + if (pIndex) + *pIndex = i; + + m_rgiszAmmo[i] = MAKE_STRING( szName ); + m_rgAmmo[i] = iCount; + + return i; + } + ALERT( at_console, "out of named ammo slots\n"); + return i; +} + +//========================================================= +// CWeaponBox::HasWeapon - is a weapon of this type already +// packed in this box? +//========================================================= +BOOL CWeaponBox::HasWeapon( CBasePlayerItem *pCheckItem ) +{ + CBasePlayerItem *pItem = m_rgpPlayerItems[pCheckItem->iItemSlot()]; + + while (pItem) + { + if (FClassnameIs( pItem->pev, STRING( pCheckItem->pev->classname) )) + { + return TRUE; + } + pItem = pItem->m_pNext; + } + + return FALSE; +} + +//========================================================= +// CWeaponBox::IsEmpty - is there anything in this box? +//========================================================= +BOOL CWeaponBox::IsEmpty( void ) +{ + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + return FALSE; + } + } + + for ( i = 0 ; i < MAX_AMMO_SLOTS ; i++ ) + { + if ( !FStringNull( m_rgiszAmmo[ i ] ) ) + { + // still have a bit of this type of ammo + return FALSE; + } + } + + return TRUE; +} + +//========================================================= +//========================================================= +void CWeaponBox::SetObjectCollisionBox( void ) +{ + pev->absmin = pev->origin + Vector(-16, -16, 0); + pev->absmax = pev->origin + Vector(16, 16, 16); +} + + +void CBasePlayerWeapon::PrintState( void ) +{ + ALERT( at_console, "primary: %f\n", m_flNextPrimaryAttack ); + ALERT( at_console, "idle : %f\n", m_flTimeWeaponIdle ); + +// ALERT( at_console, "nextrl : %f\n", m_flNextReload ); +// ALERT( at_console, "nextpum: %f\n", m_flPumpTime ); + +// ALERT( at_console, "m_frt : %f\n", m_fReloadTime ); + ALERT( at_console, "m_finre: %i\n", m_fInReload ); +// ALERT( at_console, "m_finsr: %i\n", m_fInSpecialReload ); + + 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/hlsdk/dlls/weapons.h b/hlsdk/dlls/weapons.h new file mode 100644 index 00000000..8194a4d3 --- /dev/null +++ b/hlsdk/dlls/weapons.h @@ -0,0 +1,1015 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef WEAPONS_H +#define WEAPONS_H + +#include "effects.h" + +class CBasePlayer; +extern int gmsgWeapPickup; + +void DeactivateSatchels( CBasePlayer *pOwner ); + +// Contact Grenade / Timed grenade / Satchel Charge +class CGrenade : public CBaseMonster +{ +public: + void Spawn( void ); + + typedef enum { SATCHEL_DETONATE = 0, SATCHEL_RELEASE } SATCHELCODE; + + static CGrenade *ShootTimed( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time ); + static CGrenade *ShootContact( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ); + static CGrenade *ShootSatchelCharge( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ); + static void UseSatchelCharges( entvars_t *pevOwner, SATCHELCODE code ); + + void Explode( Vector vecSrc, Vector vecAim ); + void Explode( TraceResult *pTrace, int bitsDamageType ); + void EXPORT Smoke( void ); + + void EXPORT BounceTouch( CBaseEntity *pOther ); + void EXPORT SlideTouch( CBaseEntity *pOther ); + void EXPORT ExplodeTouch( CBaseEntity *pOther ); + void EXPORT DangerSoundThink( void ); + void EXPORT PreDetonate( void ); + void EXPORT Detonate( void ); + void EXPORT DetonateUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT TumbleThink( void ); + + virtual void BounceSound( void ); + virtual int BloodColor( void ) { return DONT_BLEED; } + virtual void Killed( entvars_t *pevAttacker, int iGib ); + + BOOL m_fRegisteredSound;// whether or not this grenade has issued its DANGER sound to the world sound list yet. +}; + + +// 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/hlsdk/dlls/world.cpp b/hlsdk/dlls/world.cpp new file mode 100644 index 00000000..a6bad770 --- /dev/null +++ b/hlsdk/dlls/world.cpp @@ -0,0 +1,742 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== world.cpp ======================================================== + + precaches and defs for entities and other data that must always be available. + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "nodes.h" +#include "soundent.h" +#include "client.h" +#include "decals.h" +#include "skill.h" +#include "effects.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "teamplay_gamerules.h" + +extern CGraph WorldGraph; +extern CSoundEnt *pSoundEnt; + +extern CBaseEntity *g_pLastSpawn; +DLL_GLOBAL edict_t *g_pBodyQueueHead; +CGlobalState gGlobalState; +extern DLL_GLOBAL int gDisplayTitle; + +extern void W_Precache(void); + +// +// This must match the list in util.h +// +DLL_DECALLIST gDecals[] = { + { "{shot1", 0 }, // DECAL_GUNSHOT1 + { "{shot2", 0 }, // DECAL_GUNSHOT2 + { "{shot3",0 }, // DECAL_GUNSHOT3 + { "{shot4", 0 }, // DECAL_GUNSHOT4 + { "{shot5", 0 }, // DECAL_GUNSHOT5 + { "{lambda01", 0 }, // DECAL_LAMBDA1 + { "{lambda02", 0 }, // DECAL_LAMBDA2 + { "{lambda03", 0 }, // DECAL_LAMBDA3 + { "{lambda04", 0 }, // DECAL_LAMBDA4 + { "{lambda05", 0 }, // DECAL_LAMBDA5 + { "{lambda06", 0 }, // DECAL_LAMBDA6 + { "{scorch1", 0 }, // DECAL_SCORCH1 + { "{scorch2", 0 }, // DECAL_SCORCH2 + { "{blood1", 0 }, // DECAL_BLOOD1 + { "{blood2", 0 }, // DECAL_BLOOD2 + { "{blood3", 0 }, // DECAL_BLOOD3 + { "{blood4", 0 }, // DECAL_BLOOD4 + { "{blood5", 0 }, // DECAL_BLOOD5 + { "{blood6", 0 }, // DECAL_BLOOD6 + { "{yblood1", 0 }, // DECAL_YBLOOD1 + { "{yblood2", 0 }, // DECAL_YBLOOD2 + { "{yblood3", 0 }, // DECAL_YBLOOD3 + { "{yblood4", 0 }, // DECAL_YBLOOD4 + { "{yblood5", 0 }, // DECAL_YBLOOD5 + { "{yblood6", 0 }, // DECAL_YBLOOD6 + { "{break1", 0 }, // DECAL_GLASSBREAK1 + { "{break2", 0 }, // DECAL_GLASSBREAK2 + { "{break3", 0 }, // DECAL_GLASSBREAK3 + { "{bigshot1", 0 }, // DECAL_BIGSHOT1 + { "{bigshot2", 0 }, // DECAL_BIGSHOT2 + { "{bigshot3", 0 }, // DECAL_BIGSHOT3 + { "{bigshot4", 0 }, // DECAL_BIGSHOT4 + { "{bigshot5", 0 }, // DECAL_BIGSHOT5 + { "{spit1", 0 }, // DECAL_SPIT1 + { "{spit2", 0 }, // DECAL_SPIT2 + { "{bproof1", 0 }, // DECAL_BPROOF1 + { "{gargstomp", 0 }, // DECAL_GARGSTOMP1, // Gargantua stomp crack + { "{smscorch1", 0 }, // DECAL_SMALLSCORCH1, // Small scorch mark + { "{smscorch2", 0 }, // DECAL_SMALLSCORCH2, // Small scorch mark + { "{smscorch3", 0 }, // DECAL_SMALLSCORCH3, // Small scorch mark + { "{mommablob", 0 }, // DECAL_MOMMABIRTH // BM Birth spray + { "{mommablob", 0 }, // DECAL_MOMMASPLAT // BM Mortar spray?? need decal +}; + +/* +============================================================================== + +BODY QUE + +============================================================================== +*/ + +#define SF_DECAL_NOTINDEATHMATCH 2048 + +class CDecal : public CBaseEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT StaticDecal( void ); + void EXPORT TriggerDecal( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; + +LINK_ENTITY_TO_CLASS( infodecal, CDecal ); + +// UNDONE: These won't get sent to joining players in multi-player +void CDecal :: Spawn( void ) +{ + if ( pev->skin < 0 || (gpGlobals->deathmatch && FBitSet( pev->spawnflags, SF_DECAL_NOTINDEATHMATCH )) ) + { + REMOVE_ENTITY(ENT(pev)); + return; + } + + if ( FStringNull ( pev->targetname ) ) + { + SetThink( &CDecal::StaticDecal ); + // if there's no targetname, the decal will spray itself on as soon as the world is done spawning. + pev->nextthink = gpGlobals->time; + } + else + { + // if there IS a targetname, the decal sprays itself on when it is triggered. + SetThink ( &CBaseEntity::SUB_DoNothing ); + SetUse(&CDecal::TriggerDecal); + } +} + +void CDecal :: TriggerDecal ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // this is set up as a USE function for infodecals that have targetnames, so that the + // decal doesn't get applied until it is fired. (usually by a scripted sequence) + TraceResult trace; + int entityIndex; + + UTIL_TraceLine( pev->origin - Vector(5,5,5), pev->origin + Vector(5,5,5), ignore_monsters, ENT(pev), &trace ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY); + WRITE_BYTE( TE_BSPDECAL ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( (int)pev->skin ); + entityIndex = (short)ENTINDEX(trace.pHit); + WRITE_SHORT( entityIndex ); + if ( entityIndex ) + WRITE_SHORT( (int)VARS(trace.pHit)->modelindex ); + MESSAGE_END(); + + SetThink( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CDecal :: StaticDecal( void ) +{ + TraceResult trace; + int entityIndex, modelIndex; + + UTIL_TraceLine( pev->origin - Vector(5,5,5), pev->origin + Vector(5,5,5), ignore_monsters, ENT(pev), &trace ); + + entityIndex = (short)ENTINDEX(trace.pHit); + if ( entityIndex ) + modelIndex = (int)VARS(trace.pHit)->modelindex; + else + modelIndex = 0; + + g_engfuncs.pfnStaticDecal( pev->origin, (int)pev->skin, entityIndex, modelIndex ); + + SUB_Remove(); +} + + +void CDecal :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "texture")) + { + pev->skin = DECAL_INDEX( pkvd->szValue ); + + // Found + if ( pev->skin >= 0 ) + return; + ALERT( at_console, "Can't find decal %s\n", pkvd->szValue ); + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +// Body queue class here.... It's really just CBaseEntity +class CCorpse : public CBaseEntity +{ + virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +}; + +LINK_ENTITY_TO_CLASS( bodyque, CCorpse ); + +static void InitBodyQue(void) +{ + string_t istrClassname = MAKE_STRING("bodyque"); + + g_pBodyQueueHead = CREATE_NAMED_ENTITY( istrClassname ); + entvars_t *pev = VARS(g_pBodyQueueHead); + + // Reserve 3 more slots for dead bodies + for ( int i = 0; i < 3; i++ ) + { + pev->owner = CREATE_NAMED_ENTITY( istrClassname ); + pev = VARS(pev->owner); + } + + pev->owner = g_pBodyQueueHead; +} + + +// +// make a body que entry for the given ent so the ent can be respawned elsewhere +// +// GLOBALS ASSUMED SET: g_eoBodyQueueHead +// +void CopyToBodyQue(entvars_t *pev) +{ + if (pev->effects & EF_NODRAW) + return; + + entvars_t *pevHead = VARS(g_pBodyQueueHead); + + pevHead->angles = pev->angles; + pevHead->model = pev->model; + pevHead->modelindex = pev->modelindex; + pevHead->frame = pev->frame; + pevHead->colormap = pev->colormap; + pevHead->movetype = MOVETYPE_TOSS; + pevHead->velocity = pev->velocity; + pevHead->flags = 0; + pevHead->deadflag = pev->deadflag; + pevHead->renderfx = kRenderFxDeadPlayer; + pevHead->renderamt = ENTINDEX( ENT( pev ) ); + + pevHead->effects = pev->effects | EF_NOINTERP; + //pevHead->goalstarttime = pev->goalstarttime; + //pevHead->goalframe = pev->goalframe; + //pevHead->goalendtime = pev->goalendtime ; + + pevHead->sequence = pev->sequence; + pevHead->animtime = pev->animtime; + + UTIL_SetOrigin(pevHead, pev->origin); + UTIL_SetSize(pevHead, pev->mins, pev->maxs); + g_pBodyQueueHead = pevHead->owner; +} + + +CGlobalState::CGlobalState( void ) +{ + Reset(); +} + +void CGlobalState::Reset( void ) +{ + m_pList = NULL; + m_listCount = 0; +} + +globalentity_t *CGlobalState :: Find( string_t globalname ) +{ + if ( !globalname ) + return NULL; + + globalentity_t *pTest; + const char *pEntityName = STRING(globalname); + + + pTest = m_pList; + while ( pTest ) + { + if ( FStrEq( pEntityName, pTest->name ) ) + break; + + pTest = pTest->pNext; + } + + return pTest; +} + + +// This is available all the time now on impulse 104, remove later +//#ifdef _DEBUG +void CGlobalState :: DumpGlobals( void ) +{ + static char *estates[] = { "Off", "On", "Dead" }; + globalentity_t *pTest; + + ALERT( at_console, "-- Globals --\n" ); + pTest = m_pList; + while ( pTest ) + { + ALERT( at_console, "%s: %s (%s)\n", pTest->name, pTest->levelName, estates[pTest->state] ); + pTest = pTest->pNext; + } +} +//#endif + + +void CGlobalState :: EntityAdd( string_t globalname, string_t mapName, GLOBALESTATE state ) +{ + ASSERT( !Find(globalname) ); + + globalentity_t *pNewEntity = (globalentity_t *)calloc( sizeof( globalentity_t ), 1 ); + ASSERT( pNewEntity != NULL ); + pNewEntity->pNext = m_pList; + m_pList = pNewEntity; + strcpy( pNewEntity->name, STRING( globalname ) ); + strcpy( pNewEntity->levelName, STRING(mapName) ); + pNewEntity->state = state; + m_listCount++; +} + + +void CGlobalState :: EntitySetState( string_t globalname, GLOBALESTATE state ) +{ + globalentity_t *pEnt = Find( globalname ); + + if ( pEnt ) + pEnt->state = state; +} + + +const globalentity_t *CGlobalState :: EntityFromTable( string_t globalname ) +{ + globalentity_t *pEnt = Find( globalname ); + + return pEnt; +} + + +GLOBALESTATE CGlobalState :: EntityGetState( string_t globalname ) +{ + globalentity_t *pEnt = Find( globalname ); + if ( pEnt ) + return pEnt->state; + + return GLOBAL_OFF; +} + + +// Global Savedata for Delay +TYPEDESCRIPTION CGlobalState::m_SaveData[] = +{ + DEFINE_FIELD( CGlobalState, m_listCount, FIELD_INTEGER ), +}; + +// Global Savedata for Delay +TYPEDESCRIPTION gGlobalEntitySaveData[] = +{ + DEFINE_ARRAY( globalentity_t, name, FIELD_CHARACTER, 64 ), + DEFINE_ARRAY( globalentity_t, levelName, FIELD_CHARACTER, 32 ), + DEFINE_FIELD( globalentity_t, state, FIELD_INTEGER ), +}; + + +int CGlobalState::Save( CSave &save ) +{ + int i; + globalentity_t *pEntity; + + if ( !save.WriteFields( "GLOBAL", this, m_SaveData, ARRAYSIZE(m_SaveData) ) ) + return 0; + + pEntity = m_pList; + for ( i = 0; i < m_listCount && pEntity; i++ ) + { + if ( !save.WriteFields( "GENT", pEntity, gGlobalEntitySaveData, ARRAYSIZE(gGlobalEntitySaveData) ) ) + return 0; + + pEntity = pEntity->pNext; + } + + return 1; +} + +int CGlobalState::Restore( CRestore &restore ) +{ + int i, listCount; + globalentity_t tmpEntity; + + + ClearStates(); + if ( !restore.ReadFields( "GLOBAL", this, m_SaveData, ARRAYSIZE(m_SaveData) ) ) + return 0; + + listCount = m_listCount; // Get new list count + m_listCount = 0; // Clear loaded data + + for ( i = 0; i < listCount; i++ ) + { + if ( !restore.ReadFields( "GENT", &tmpEntity, gGlobalEntitySaveData, ARRAYSIZE(gGlobalEntitySaveData) ) ) + return 0; + EntityAdd( MAKE_STRING(tmpEntity.name), MAKE_STRING(tmpEntity.levelName), tmpEntity.state ); + } + return 1; +} + +void CGlobalState::EntityUpdate( string_t globalname, string_t mapname ) +{ + globalentity_t *pEnt = Find( globalname ); + + if ( pEnt ) + strcpy( pEnt->levelName, STRING(mapname) ); +} + + +void CGlobalState::ClearStates( void ) +{ + globalentity_t *pFree = m_pList; + while ( pFree ) + { + globalentity_t *pNext = pFree->pNext; + free( pFree ); + pFree = pNext; + } + Reset(); +} + + +void SaveGlobalState( SAVERESTOREDATA *pSaveData ) +{ + CSave saveHelper( pSaveData ); + gGlobalState.Save( saveHelper ); +} + + +void RestoreGlobalState( SAVERESTOREDATA *pSaveData ) +{ + CRestore restoreHelper( pSaveData ); + gGlobalState.Restore( restoreHelper ); +} + + +void ResetGlobalState( void ) +{ + gGlobalState.ClearStates(); + gInitHUD = TRUE; // Init the HUD on a new game / load game +} + +// moved CWorld class definition to cbase.h +//======================= +// CWorld +// +// This spawns first when each level begins. +//======================= + +LINK_ENTITY_TO_CLASS( worldspawn, CWorld ); + +#define SF_WORLD_DARK 0x0001 // Fade from black at startup +#define SF_WORLD_TITLE 0x0002 // Display game title at startup +#define SF_WORLD_FORCETEAM 0x0004 // Force teams + +extern DLL_GLOBAL BOOL g_fGameOver; +float g_flWeaponCheat; + +void CWorld :: Spawn( void ) +{ + g_fGameOver = FALSE; + Precache( ); + g_flWeaponCheat = CVAR_GET_FLOAT( "sv_cheats" ); // Is the impulse 101 command allowed? +} + +void CWorld :: Precache( void ) +{ + g_pLastSpawn = NULL; + +#if 1 + CVAR_SET_STRING("sv_gravity", "800"); // 67ft/sec + CVAR_SET_STRING("sv_stepsize", "18"); +#else + CVAR_SET_STRING("sv_gravity", "384"); // 32ft/sec + CVAR_SET_STRING("sv_stepsize", "24"); +#endif + + CVAR_SET_STRING("room_type", "0");// clear DSP + + // Set up game rules + if (g_pGameRules) + { + delete g_pGameRules; + } + + g_pGameRules = InstallGameRules( ); + + //!!!UNDONE why is there so much Spawn code in the Precache function? I'll just keep it here + + ///!!!LATER - do we want a sound ent in deathmatch? (sjb) + //pSoundEnt = CBaseEntity::Create( "soundent", g_vecZero, g_vecZero, edict() ); + pSoundEnt = GetClassPtr( ( CSoundEnt *)NULL ); + pSoundEnt->Spawn(); + + if ( !pSoundEnt ) + { + ALERT ( at_console, "**COULD NOT CREATE SOUNDENT**\n" ); + } + + InitBodyQue(); + +// init sentence group playback stuff from sentences.txt. +// ok to call this multiple times, calls after first are ignored. + + SENTENCEG_Init(); + +// init texture type array from materials.txt + + TEXTURETYPE_Init(); + + +// the area based ambient sounds MUST be the first precache_sounds + +// player precaches + W_Precache (); // get weapon precaches + + ClientPrecache(); + +// 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) + PRECACHE_SOUND( "common/bodydrop4.wav" ); + + g_Language = (int)CVAR_GET_FLOAT( "sv_language" ); + if ( g_Language == LANGUAGE_GERMAN ) + { + PRECACHE_MODEL( "models/germangibs.mdl" ); + } + else + { + PRECACHE_MODEL( "models/hgibs.mdl" ); + 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"); + + // 1 FLICKER (first variety) + LIGHT_STYLE(1, "mmnmmommommnonmmonqnmmo"); + + // 2 SLOW STRONG PULSE + LIGHT_STYLE(2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"); + + // 3 CANDLE (first variety) + LIGHT_STYLE(3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg"); + + // 4 FAST STROBE + LIGHT_STYLE(4, "mamamamamama"); + + // 5 GENTLE PULSE 1 + LIGHT_STYLE(5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj"); + + // 6 FLICKER (second variety) + LIGHT_STYLE(6, "nmonqnmomnmomomno"); + + // 7 CANDLE (second variety) + LIGHT_STYLE(7, "mmmaaaabcdefgmmmmaaaammmaamm"); + + // 8 CANDLE (third variety) + LIGHT_STYLE(8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa"); + + // 9 SLOW STROBE (fourth variety) + LIGHT_STYLE(9, "aaaaaaaazzzzzzzz"); + + // 10 FLUORESCENT FLICKER + LIGHT_STYLE(10, "mmamammmmammamamaaamammma"); + + // 11 SLOW PULSE NOT FADE TO BLACK + LIGHT_STYLE(11, "abcdefghijklmnopqrrqponmlkjihgfedcba"); + + // 12 UNDERWATER LIGHT MUTATION + // this light only distorts the lightmap - no contribution + // is made to the brightness of affected surfaces + LIGHT_STYLE(12, "mmnnmmnnnmmnn"); + + // styles 32-62 are assigned by the light program for switchable lights + + // 63 testing + LIGHT_STYLE(63, "a"); + + for ( int i = 0; i < static_cast(ARRAYSIZE(gDecals)); i++ ) + gDecals[i].index = DECAL_INDEX( gDecals[i].name ); + +// init the WorldGraph. + WorldGraph.InitGraph(); + +// make sure the .NOD file is newer than the .BSP file. + if ( !WorldGraph.CheckNODFile ( ( char * )STRING( gpGlobals->mapname ) ) ) + {// NOD file is not present, or is older than the BSP file. + WorldGraph.AllocNodes (); + } + else + {// Load the node graph for this level + if ( !WorldGraph.FLoadGraph ( (char *)STRING( gpGlobals->mapname ) ) ) + {// couldn't load, so alloc and prepare to build a graph. + ALERT ( at_console, "*Error opening .NOD file\n" ); + WorldGraph.AllocNodes (); + } + else + { + ALERT ( at_console, "\n*Graph Loaded!\n" ); + } + } + + if ( pev->speed > 0 ) + CVAR_SET_FLOAT( "sv_zmax", pev->speed ); + else + CVAR_SET_FLOAT( "sv_zmax", 4096 ); + + if ( pev->netname ) + { + ALERT( at_aiconsole, "Chapter title: %s\n", STRING(pev->netname) ); + CBaseEntity *pEntity = CBaseEntity::Create( "env_message", g_vecZero, g_vecZero, NULL ); + if ( pEntity ) + { + pEntity->SetThink( &CBaseEntity::SUB_CallUseToggle ); + pEntity->pev->message = pev->netname; + pev->netname = 0; + pEntity->pev->nextthink = gpGlobals->time + 0.3; + pEntity->pev->spawnflags = SF_MESSAGE_ONCE; + } + } + + if ( pev->spawnflags & SF_WORLD_DARK ) + CVAR_SET_FLOAT( "v_dark", 1.0 ); + else + CVAR_SET_FLOAT( "v_dark", 0.0 ); + + if ( pev->spawnflags & SF_WORLD_TITLE ) + gDisplayTitle = TRUE; // display the game title if this key is set + else + gDisplayTitle = FALSE; + + if ( pev->spawnflags & SF_WORLD_FORCETEAM ) + { + CVAR_SET_FLOAT( "mp_defaultteam", 1 ); + } + else + { + CVAR_SET_FLOAT( "mp_defaultteam", 0 ); + } +} + + +// +// Just to ignore the "wad" field. +// +void CWorld :: KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "skyname") ) + { + // Sent over net now. + CVAR_SET_STRING( "sv_skyname", pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "sounds") ) + { + gpGlobals->cdAudioTrack = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "WaveHeight") ) + { + // Sent over net now. + pev->scale = atof(pkvd->szValue) * (1.0/8.0); + pkvd->fHandled = TRUE; + CVAR_SET_FLOAT( "sv_wateramp", pev->scale ); + } + else if ( FStrEq(pkvd->szKeyName, "MaxRange") ) + { + pev->speed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "chaptertitle") ) + { + pev->netname = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "startdark") ) + { + // UNDONE: This is a gross hack!!! The CVAR is NOT sent over the client/sever link + // but it will work for single player + int flag = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + if ( flag ) + pev->spawnflags |= SF_WORLD_DARK; + } + else if ( FStrEq(pkvd->szKeyName, "newunit") ) + { + // Single player only. Clear save directory if set + if ( atoi(pkvd->szValue) ) + CVAR_SET_FLOAT( "sv_newunit", 1 ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "gametitle") ) + { + if ( atoi(pkvd->szValue) ) + pev->spawnflags |= SF_WORLD_TITLE; + + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "mapteams") ) + { + pev->team = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "defaultteam") ) + { + if ( atoi(pkvd->szValue) ) + { + pev->spawnflags |= SF_WORLD_FORCETEAM; + } + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} diff --git a/hlsdk/dlls/wpn_shared/hl_wpn_glock.cpp b/hlsdk/dlls/wpn_shared/hl_wpn_glock.cpp new file mode 100644 index 00000000..ee3fa5da --- /dev/null +++ b/hlsdk/dlls/wpn_shared/hl_wpn_glock.cpp @@ -0,0 +1,274 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" + +enum glock_e { + GLOCK_IDLE1 = 0, + GLOCK_IDLE2, + GLOCK_IDLE3, + GLOCK_SHOOT, + GLOCK_SHOOT_EMPTY, + GLOCK_RELOAD, + GLOCK_RELOAD_NOT_EMPTY, + GLOCK_DRAW, + GLOCK_HOLSTER, + GLOCK_ADD_SILENCER +}; + +LINK_ENTITY_TO_CLASS( weapon_glock, CGlock ); +LINK_ENTITY_TO_CLASS( weapon_9mmhandgun, CGlock ); + + +void CGlock::Spawn( ) +{ + pev->classname = MAKE_STRING("weapon_9mmhandgun"); // hack to allow for old names + Precache( ); + m_iId = WEAPON_GLOCK; + SET_MODEL(ENT(pev), "models/w_9mmhandgun.mdl"); + + m_iDefaultAmmo = GLOCK_DEFAULT_GIVE; + + FallInit();// get ready to fall down. +} + + +void CGlock::Precache( void ) +{ + PRECACHE_MODEL("models/v_9mmhandgun.mdl"); + PRECACHE_MODEL("models/w_9mmhandgun.mdl"); + PRECACHE_MODEL("models/p_9mmhandgun.mdl"); + + m_iShell = PRECACHE_MODEL ("models/shell.mdl");// brass shell + + PRECACHE_SOUND("items/9mmclip1.wav"); + PRECACHE_SOUND("items/9mmclip2.wav"); + + PRECACHE_SOUND ("weapons/pl_gun1.wav");//silenced handgun + PRECACHE_SOUND ("weapons/pl_gun2.wav");//silenced handgun + PRECACHE_SOUND ("weapons/pl_gun3.wav");//handgun + + m_usFireGlock1 = PRECACHE_EVENT( 1, "events/glock1.sc" ); + m_usFireGlock2 = PRECACHE_EVENT( 1, "events/glock2.sc" ); +} + +int CGlock::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "9mm"; + p->iMaxAmmo1 = _9MM_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = GLOCK_MAX_CLIP; + p->iSlot = 1; + p->iPosition = 0; + p->iFlags = 0; + p->iId = m_iId = WEAPON_GLOCK; + p->iWeight = GLOCK_WEIGHT; + + return 1; +} + +BOOL CGlock::Deploy( ) +{ + // pev->body = 1; + return DefaultDeploy( "models/v_9mmhandgun.mdl", "models/p_9mmhandgun.mdl", GLOCK_DRAW, "onehanded", /*UseDecrement() ? 1 : 0*/ 0 ); +} + +void CGlock::SecondaryAttack( void ) +{ + GlockFire( 0.1, 0.2, FALSE ); +} + +void CGlock::PrimaryAttack( void ) +{ + GlockFire( 0.01, 0.3, TRUE ); +} + +void CGlock::GlockFire( float flSpread , float flCycleTime, BOOL fUseAutoAim ) +{ + if (m_iClip <= 0) + { + if (m_fFireOnEmpty) + { + PlayEmptySound(); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.2; + } + + return; + } + + m_iClip--; + + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + int flags; + +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + // silenced + if (pev->body == 1) + { + m_pPlayer->m_iWeaponVolume = QUIET_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = DIM_GUN_FLASH; + } + else + { + // non-silenced + m_pPlayer->m_iWeaponVolume = NORMAL_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; + } + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming; + + if ( fUseAutoAim ) + { + vecAiming = m_pPlayer->GetAutoaimVector( AUTOAIM_10DEGREES ); + } + else + { + vecAiming = gpGlobals->v_forward; + } + + Vector vecDir; + vecDir = m_pPlayer->FireBulletsPlayer( 1, vecSrc, vecAiming, Vector( flSpread, flSpread, flSpread ), 8192, BULLET_PLAYER_9MM, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed ); + + PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), fUseAutoAim ? m_usFireGlock1 : m_usFireGlock2, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, vecDir.x, vecDir.y, 0, 0, ( m_iClip == 0 ) ? 1 : 0, 0 ); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + flCycleTime; + + if (!m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + // HEV suit - indicate out of ammo condition + m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); +} + + +void CGlock::Reload( void ) +{ + if ( m_pPlayer->ammo_9mm <= 0 ) + return; + + int iResult; + + if (m_iClip == 0) + iResult = DefaultReload( 17, GLOCK_RELOAD, 1.5 ); + else + iResult = DefaultReload( 17, GLOCK_RELOAD_NOT_EMPTY, 1.5 ); + + if (iResult) + { + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + } +} + + + +void CGlock::WeaponIdle( void ) +{ + ResetEmptySound( ); + + m_pPlayer->GetAutoaimVector( AUTOAIM_10DEGREES ); + + if ( m_flTimeWeaponIdle > UTIL_WeaponTimeBase() ) + return; + + // only idle if the slid isn't back + if (m_iClip != 0) + { + int iAnim; + float flRand = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 0.0, 1.0 ); + + if (flRand <= 0.3 + 0 * 0.75) + { + iAnim = GLOCK_IDLE3; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 49.0 / 16; + } + else if (flRand <= 0.6 + 0 * 0.875) + { + iAnim = GLOCK_IDLE1; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 60.0 / 16.0; + } + else + { + iAnim = GLOCK_IDLE2; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 40.0 / 16.0; + } + SendWeaponAnim( iAnim, 1 ); + } +} + + + + + + + + +class CGlockAmmo : public CBasePlayerAmmo +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_9mmclip.mdl"); + CBasePlayerAmmo::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_9mmclip.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + } + BOOL AddAmmo( CBaseEntity *pOther ) + { + if (pOther->GiveAmmo( AMMO_GLOCKCLIP_GIVE, "9mm", _9MM_MAX_CARRY ) != -1) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + return TRUE; + } + return FALSE; + } +}; +LINK_ENTITY_TO_CLASS( ammo_glockclip, CGlockAmmo ); +LINK_ENTITY_TO_CLASS( ammo_9mmclip, CGlockAmmo ); + + + + + + + + + + + + + + + diff --git a/hlsdk/dlls/xen.cpp b/hlsdk/dlls/xen.cpp new file mode 100644 index 00000000..ff35cb8d --- /dev/null +++ b/hlsdk/dlls/xen.cpp @@ -0,0 +1,584 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "animation.h" +#include "effects.h" + + +#define XEN_PLANT_GLOW_SPRITE "sprites/flare3.spr" +#define XEN_PLANT_HIDE_TIME 5 + + +class CActAnimating : public CBaseAnimating +{ +public: + void SetActivity( Activity act ); + inline Activity GetActivity( void ) { return m_Activity; } + + virtual int ObjectCaps( void ) { return CBaseAnimating :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + Activity m_Activity; +}; + +TYPEDESCRIPTION CActAnimating::m_SaveData[] = +{ + DEFINE_FIELD( CActAnimating, m_Activity, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CActAnimating, CBaseAnimating ); + +void CActAnimating :: SetActivity( Activity act ) +{ + int sequence = LookupActivity( act ); + if ( sequence != ACTIVITY_NOT_AVAILABLE ) + { + pev->sequence = sequence; + m_Activity = act; + pev->frame = 0; + ResetSequenceInfo( ); + } +} + + + + +class CXenPLight : public CActAnimating +{ +public: + void Spawn( void ); + void Precache( void ); + void Touch( CBaseEntity *pOther ); + void Think( void ); + + void LightOn( void ); + void LightOff( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + CSprite *m_pGlow; +}; + +LINK_ENTITY_TO_CLASS( xen_plantlight, CXenPLight ); + +TYPEDESCRIPTION CXenPLight::m_SaveData[] = +{ + DEFINE_FIELD( CXenPLight, m_pGlow, FIELD_CLASSPTR ), +}; + +IMPLEMENT_SAVERESTORE( CXenPLight, CActAnimating ); + +void CXenPLight :: Spawn( void ) +{ + Precache(); + + SET_MODEL( ENT(pev), "models/light.mdl" ); + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_TRIGGER; + + UTIL_SetSize( pev, Vector(-80,-80,0), Vector(80,80,32)); + SetActivity( ACT_IDLE ); + pev->nextthink = gpGlobals->time + 0.1; + pev->frame = RANDOM_FLOAT(0,255); + + m_pGlow = CSprite::SpriteCreate( XEN_PLANT_GLOW_SPRITE, pev->origin + Vector(0,0,(pev->mins.z+pev->maxs.z)*0.5), FALSE ); + m_pGlow->SetTransparency( kRenderGlow, static_cast(pev->rendercolor.x), static_cast(pev->rendercolor.y), static_cast(pev->rendercolor.z), static_cast(pev->renderamt), static_cast(pev->renderfx) ); + m_pGlow->SetAttachment( edict(), 1 ); +} + + +void CXenPLight :: Precache( void ) +{ + PRECACHE_MODEL( "models/light.mdl" ); + PRECACHE_MODEL( XEN_PLANT_GLOW_SPRITE ); +} + + +void CXenPLight :: Think( void ) +{ + StudioFrameAdvance(); + pev->nextthink = gpGlobals->time + 0.1; + + switch( GetActivity() ) + { + case ACT_CROUCH: + if ( m_fSequenceFinished ) + { + SetActivity( ACT_CROUCHIDLE ); + LightOff(); + } + break; + + case ACT_CROUCHIDLE: + if ( gpGlobals->time > pev->dmgtime ) + { + SetActivity( ACT_STAND ); + LightOn(); + } + break; + + case ACT_STAND: + if ( m_fSequenceFinished ) + SetActivity( ACT_IDLE ); + break; + + case ACT_IDLE: + default: + break; + } +} + + +void CXenPLight :: Touch( CBaseEntity *pOther ) +{ + if ( pOther->IsPlayer() ) + { + pev->dmgtime = gpGlobals->time + XEN_PLANT_HIDE_TIME; + if ( GetActivity() == ACT_IDLE || GetActivity() == ACT_STAND ) + { + SetActivity( ACT_CROUCH ); + } + } +} + + +void CXenPLight :: LightOn( void ) +{ + SUB_UseTargets( this, USE_ON, 0 ); + if ( m_pGlow ) + m_pGlow->pev->effects &= ~EF_NODRAW; +} + + +void CXenPLight :: LightOff( void ) +{ + SUB_UseTargets( this, USE_OFF, 0 ); + if ( m_pGlow ) + m_pGlow->pev->effects |= EF_NODRAW; +} + + + +class CXenHair : public CActAnimating +{ +public: + void Spawn( void ); + void Precache( void ); + void Think( void ); +}; + +LINK_ENTITY_TO_CLASS( xen_hair, CXenHair ); + +#define SF_HAIR_SYNC 0x0001 + +void CXenHair::Spawn( void ) +{ + Precache(); + SET_MODEL( edict(), "models/hair.mdl" ); + UTIL_SetSize( pev, Vector(-4,-4,0), Vector(4,4,32)); + pev->sequence = 0; + + if ( !(pev->spawnflags & SF_HAIR_SYNC) ) + { + pev->frame = RANDOM_FLOAT(0,255); + pev->framerate = RANDOM_FLOAT( 0.7, 1.4 ); + } + ResetSequenceInfo( ); + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->nextthink = gpGlobals->time + RANDOM_FLOAT( 0.1, 0.4 ); // Load balance these a bit +} + + +void CXenHair::Think( void ) +{ + StudioFrameAdvance(); + pev->nextthink = gpGlobals->time + 0.5; +} + + +void CXenHair::Precache( void ) +{ + PRECACHE_MODEL( "models/hair.mdl" ); +} + + +class CXenTreeTrigger : public CBaseEntity +{ +public: + void Touch( CBaseEntity *pOther ); + static CXenTreeTrigger *TriggerCreate( edict_t *pOwner, const Vector &position ); +}; +LINK_ENTITY_TO_CLASS( xen_ttrigger, CXenTreeTrigger ); + +CXenTreeTrigger *CXenTreeTrigger :: TriggerCreate( edict_t *pOwner, const Vector &position ) +{ + CXenTreeTrigger *pTrigger = GetClassPtr( (CXenTreeTrigger *)NULL ); + pTrigger->pev->origin = position; + pTrigger->pev->classname = MAKE_STRING("xen_ttrigger"); + pTrigger->pev->solid = SOLID_TRIGGER; + pTrigger->pev->movetype = MOVETYPE_NONE; + pTrigger->pev->owner = pOwner; + + return pTrigger; +} + + +void CXenTreeTrigger::Touch( CBaseEntity *pOther ) +{ + if ( pev->owner ) + { + CBaseEntity *pEntity = CBaseEntity::Instance(pev->owner); + pEntity->Touch( pOther ); + } +} + + +#define TREE_AE_ATTACK 1 + +class CXenTree : public CActAnimating +{ +public: + void Spawn( void ); + void Precache( void ); + void Touch( CBaseEntity *pOther ); + void Think( void ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { Attack(); return 0; } + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void Attack( void ); + int Classify( void ) { return CLASS_BARNACLE; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + static const char *pAttackHitSounds[]; + static const char *pAttackMissSounds[]; + +private: + CXenTreeTrigger *m_pTrigger; +}; + +LINK_ENTITY_TO_CLASS( xen_tree, CXenTree ); + +TYPEDESCRIPTION CXenTree::m_SaveData[] = +{ + DEFINE_FIELD( CXenTree, m_pTrigger, FIELD_CLASSPTR ), +}; + +IMPLEMENT_SAVERESTORE( CXenTree, CActAnimating ); + +void CXenTree :: Spawn( void ) +{ + Precache(); + + SET_MODEL( ENT(pev), "models/tree.mdl" ); + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_BBOX; + + pev->takedamage = DAMAGE_YES; + + UTIL_SetSize( pev, Vector(-30,-30,0), Vector(30,30,188)); + SetActivity( ACT_IDLE ); + pev->nextthink = gpGlobals->time + 0.1; + pev->frame = RANDOM_FLOAT(0,255); + pev->framerate = RANDOM_FLOAT( 0.7, 1.4 ); + + Vector triggerPosition; + UTIL_MakeVectorsPrivate( pev->angles, triggerPosition, NULL, NULL ); + triggerPosition = pev->origin + (triggerPosition * 64); + // Create the trigger + m_pTrigger = CXenTreeTrigger::TriggerCreate( edict(), triggerPosition ); + UTIL_SetSize( m_pTrigger->pev, Vector( -24, -24, 0 ), Vector( 24, 24, 128 ) ); +} + +const char *CXenTree::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CXenTree::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +void CXenTree :: Precache( void ) +{ + PRECACHE_MODEL( "models/tree.mdl" ); + PRECACHE_MODEL( XEN_PLANT_GLOW_SPRITE ); + PRECACHE_SOUND_ARRAY( pAttackHitSounds ); + PRECACHE_SOUND_ARRAY( pAttackMissSounds ); +} + + +void CXenTree :: Touch( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() && FClassnameIs( pOther->pev, "monster_bigmomma" ) ) + return; + + Attack(); +} + + +void CXenTree :: Attack( void ) +{ + if ( GetActivity() == ACT_IDLE ) + { + SetActivity( ACT_MELEE_ATTACK1 ); + pev->framerate = RANDOM_FLOAT( 1.0, 1.4 ); + EMIT_SOUND_ARRAY_DYN( CHAN_WEAPON, pAttackMissSounds ); + } +} + + +void CXenTree :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case TREE_AE_ATTACK: + { + CBaseEntity *pList[8]; + BOOL sound = FALSE; + int count = UTIL_EntitiesInBox( pList, 8, m_pTrigger->pev->absmin, m_pTrigger->pev->absmax, FL_MONSTER|FL_CLIENT ); + Vector forward; + + UTIL_MakeVectorsPrivate( pev->angles, forward, NULL, NULL ); + + for ( int i = 0; i < count; i++ ) + { + if ( pList[i] != this ) + { + if ( pList[i]->pev->owner != edict() ) + { + sound = TRUE; + pList[i]->TakeDamage( pev, pev, 25, DMG_CRUSH | DMG_SLASH ); + pList[i]->pev->punchangle.x = 15; + pList[i]->pev->velocity = pList[i]->pev->velocity + forward * 100; + } + } + } + + if ( sound ) + { + EMIT_SOUND_ARRAY_DYN( CHAN_WEAPON, pAttackHitSounds ); + } + } + return; + } + + CActAnimating::HandleAnimEvent( pEvent ); +} + +void CXenTree :: Think( void ) +{ + float flInterval = StudioFrameAdvance(); + pev->nextthink = gpGlobals->time + 0.1; + DispatchAnimEvents( flInterval ); + + switch( GetActivity() ) + { + case ACT_MELEE_ATTACK1: + if ( m_fSequenceFinished ) + { + SetActivity( ACT_IDLE ); + pev->framerate = RANDOM_FLOAT( 0.6, 1.4 ); + } + break; + + default: + case ACT_IDLE: + break; + + } +} + + +// UNDONE: These need to smoke somehow when they take damage +// Touch behavior? +// Cause damage in smoke area + +// +// Spores +// +class CXenSpore : public CActAnimating +{ +public: + void Spawn( void ); + void Precache( void ); + void Touch( CBaseEntity *pOther ); + void Think( void ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { Attack(); return 0; } +// void HandleAnimEvent( MonsterEvent_t *pEvent ); + void Attack( void ) {} + + static const char *pModelNames[]; +}; + +class CXenSporeSmall : public CXenSpore +{ + void Spawn( void ); +}; + +class CXenSporeMed : public CXenSpore +{ + void Spawn( void ); +}; + +class CXenSporeLarge : public CXenSpore +{ + void Spawn( void ); + + static const Vector m_hullSizes[]; +}; + +// Fake collision box for big spores +class CXenHull : public CPointEntity +{ +public: + static CXenHull *CreateHull( CBaseEntity *source, const Vector &mins, const Vector &maxs, const Vector &offset ); + int Classify( void ) { return CLASS_BARNACLE; } +}; + +CXenHull *CXenHull :: CreateHull( CBaseEntity *source, const Vector &mins, const Vector &maxs, const Vector &offset ) +{ + CXenHull *pHull = GetClassPtr( (CXenHull *)NULL ); + + UTIL_SetOrigin( pHull->pev, source->pev->origin + offset ); + SET_MODEL( pHull->edict(), STRING(source->pev->model) ); + pHull->pev->solid = SOLID_BBOX; + pHull->pev->classname = MAKE_STRING("xen_hull"); + pHull->pev->movetype = MOVETYPE_NONE; + pHull->pev->owner = source->edict(); + UTIL_SetSize( pHull->pev, mins, maxs ); + pHull->pev->renderamt = 0; + pHull->pev->rendermode = kRenderTransTexture; + // pHull->pev->effects = EF_NODRAW; + + return pHull; +} + + +LINK_ENTITY_TO_CLASS( xen_spore_small, CXenSporeSmall ); +LINK_ENTITY_TO_CLASS( xen_spore_medium, CXenSporeMed ); +LINK_ENTITY_TO_CLASS( xen_spore_large, CXenSporeLarge ); +LINK_ENTITY_TO_CLASS( xen_hull, CXenHull ); + +void CXenSporeSmall::Spawn( void ) +{ + pev->skin = 0; + CXenSpore::Spawn(); + UTIL_SetSize( pev, Vector(-16,-16,0), Vector(16,16,64)); +} +void CXenSporeMed::Spawn( void ) +{ + pev->skin = 1; + CXenSpore::Spawn(); + UTIL_SetSize( pev, Vector(-40,-40,0), Vector(40,40,120)); +} + + +// I just eyeballed these -- fill in hulls for the legs +const Vector CXenSporeLarge::m_hullSizes[] = +{ + Vector( 90, -25, 0 ), + Vector( 25, 75, 0 ), + Vector( -15, -100, 0 ), + Vector( -90, -35, 0 ), + Vector( -90, 60, 0 ), +}; + +void CXenSporeLarge::Spawn( void ) +{ + pev->skin = 2; + CXenSpore::Spawn(); + UTIL_SetSize( pev, Vector(-48,-48,110), Vector(48,48,240)); + + Vector forward, right; + + UTIL_MakeVectorsPrivate( pev->angles, forward, right, NULL ); + + // Rotate the leg hulls into position + for ( int i = 0; i < static_cast(ARRAYSIZE(m_hullSizes)); i++ ) + CXenHull :: CreateHull( this, Vector(-12, -12, 0 ), Vector( 12, 12, 120 ), (m_hullSizes[i].x * forward) + (m_hullSizes[i].y * right) ); +} + +void CXenSpore :: Spawn( void ) +{ + Precache(); + + SET_MODEL( ENT(pev), pModelNames[pev->skin] ); + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_BBOX; + pev->takedamage = DAMAGE_YES; + +// SetActivity( ACT_IDLE ); + pev->sequence = 0; + pev->frame = RANDOM_FLOAT(0,255); + pev->framerate = RANDOM_FLOAT( 0.7, 1.4 ); + ResetSequenceInfo( ); + pev->nextthink = gpGlobals->time + RANDOM_FLOAT( 0.1, 0.4 ); // Load balance these a bit +} + +const char *CXenSpore::pModelNames[] = +{ + "models/fungus(small).mdl", + "models/fungus.mdl", + "models/fungus(large).mdl", +}; + + +void CXenSpore :: Precache( void ) +{ + PRECACHE_MODEL( (char *)pModelNames[pev->skin] ); +} + + +void CXenSpore :: Touch( CBaseEntity *pOther ) +{ +} + + +void CXenSpore :: Think( void ) +{ + StudioFrameAdvance(); + pev->nextthink = gpGlobals->time + 0.1; + +#if 0 + DispatchAnimEvents( flInterval ); + + switch( GetActivity() ) + { + default: + case ACT_IDLE: + break; + + } +#endif +} + + diff --git a/hlsdk/dmc/cl_dll/DMC_BSPFile.h b/hlsdk/dmc/cl_dll/DMC_BSPFile.h new file mode 100644 index 00000000..a6bf4a6d --- /dev/null +++ b/hlsdk/dmc/cl_dll/DMC_BSPFile.h @@ -0,0 +1,48 @@ +//========= Copyright © 1996-2002, 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/hlsdk/dmc/cl_dll/DMC_Teleporters.cpp b/hlsdk/dmc/cl_dll/DMC_Teleporters.cpp new file mode 100644 index 00000000..9ca0e711 --- /dev/null +++ b/hlsdk/dmc/cl_dll/DMC_Teleporters.cpp @@ -0,0 +1,516 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "extdll.h" +#include "entity_state.h" +#include "pm_defs.h" +#include "pm_shared.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" + +extern "C" playermove_t *pmove; +extern int g_runfuncs; + +// Don't support more than MAX_TELE teleporters ( map still can load tho ) +#define MAX_TELES 256 + +extern Vector g_vecTeleMins[ MAX_TELES ]; +extern Vector g_vecTeleMaxs[ MAX_TELES ]; +extern int g_iTeleNum; +extern int g_iUser1; +extern bool g_bLoadedTeles; +vec3_t vecTempAngles; +bool bChangeAngles; + + +// We only care about two kinds of entities for now: Teleporters and their targets +// FIXME: After loading, store a pointer from teleporter to target instead of looking up all the time. +typedef enum +{ + // Entity is a teleporter + DMC_TELE = 0, + // Entity is a target + DMC_TARGET +} dmc_teletype_t; + +typedef struct +{ + // Type of entity + dmc_teletype_t type; + + // Classname + char classname[ 32 ]; + + // What this entity targets + char target[ 32 ]; + + // If entity is a target, the name tag it uses + char targetname[ 32 ]; + + // Orientation of the teleporter + float angles[3]; + + // Target origin + float origin[3]; + + // Bounding box of the teleporter + float absmin[3]; + float absmax[3]; + +} dmc_tele_t; + +// Teleporter/Target entity database +static dmc_tele_t s_teles[ MAX_TELES ]; +static int s_num_teles = 0; + +// We'll use this for playing the teleporting sounds locally. +static unsigned short s_usTeleport; + +/* +============================== +Dmc_SetKeyValue + +Fill in key/values fro the teleporter +============================== +*/ +void Dmc_SetKeyValue( dmc_tele_t *pTele, const char *key, const char *value ) +{ + float x, y, z; + + if ( !stricmp( key, "classname" ) ) + { + strcpy( pTele->classname, value ); + } + else if ( !stricmp( key, "target" ) ) + { + strcpy( pTele->target, value ); + } + else if ( !stricmp( key, "targetname" ) ) + { + strcpy( pTele->targetname, value ); + } + else if ( !stricmp( key, "angles" ) ) + { + if ( sscanf( value, "%f %f %f", &x, &y, &z ) == 3 ) + { + pTele->angles[ 0 ] = x ; + pTele->angles[ 1 ] = y; + pTele->angles[ 2 ] = z; + } + } + else if ( !stricmp( key, "origin" ) ) + { + if ( sscanf( value, "%f %f %f", &x, &y, &z ) == 3 ) + { + pTele->origin[ 0 ] = x; + pTele->origin[ 1 ] = y; + pTele->origin[ 2 ] = z; + } + } +} + +/* +============================== +Dmc_ParseTeleporter + +Evaluate Key/Value pairs for the entity +============================== +*/ +char *Dmc_ParseTeleporter( char *buffer, dmc_tele_t *pTele, int *error ) +{ + char key[256]; + char token[ 1024 ]; + int n; + + memset( pTele, 0, sizeof( *pTele ) ); + + while (1) + { + // Parse key + buffer = gEngfuncs.COM_ParseFile ( buffer, token ); + if ( token[0] == '}' ) + break; + + // Ran out of input buffer? + if ( !buffer ) + { + *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 + buffer = gEngfuncs.COM_ParseFile ( buffer, token ); + + // Ran out of buffer? + if (!buffer) + { + *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 + Dmc_SetKeyValue( pTele, key, token ); + } + + // Return what's left in the stream + return buffer; +} + +/* +============================== +Dmc_ProcessEnts + +Parse through entity lump looking for teleporters or targets +============================== +*/ +void Dmc_ProcessEnts( char *buffer ) +{ + char token[ 1024 ]; + dmc_tele_t *pTele = NULL; + int error = 0; + + // parse entities from entity lump of .bsp file + while ( 1 ) + { + // parse the opening brace + buffer = gEngfuncs.COM_ParseFile ( buffer, token ); + if (!buffer) + break; + + // Didn't find opening brace? + if ( token[0] != '{' ) + { + gEngfuncs.Con_Printf ("Dmc_ProcessEnts: found %s when expecting {\n", token ); + return; + } + + // Assume we're filling in this tele + pTele = &s_teles[ s_num_teles ]; + + // Fill in data + buffer = Dmc_ParseTeleporter( buffer, pTele, &error ); + + // Check for errors and abort if any + if ( error ) + { + gEngfuncs.Con_Printf ("Dmc_ProcessEnts: error parsing entities\n" ); + return; + } + + // Check classname + if ( stricmp( pTele->classname, "trigger_teleport" ) && stricmp( pTele->classname, "info_teleport_destination" ) ) + continue; + + // Set type based on classname + if ( !stricmp( pTele->classname, "trigger_teleport" ) ) + { + pTele->type = DMC_TELE; + } + else + { + pTele->type = DMC_TARGET; + } + + // If we got to here, we're using the entity + s_num_teles++; + + // No more room... + if ( s_num_teles >= MAX_TELES ) + break; + } +} + +/* +============================== +Dmc_LoadEntityLump + +Open the .bsp and read in the entity lump +============================== +*/ +char *Dmc_LoadEntityLump( const char *filename ) +{ + FILE *fp; + int i; + dheader_t header; + int size; + lump_t *curLump; + char *buffer = NULL; + + fp = fopen( filename, "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", filename); + 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", filename, 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 = (char *)malloc( size + 1 ); + if ( !buffer ) + { + 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; +} + +/* +============================== +Dmc_LoadTeleporters + +Load in the .bsp file and process the entities +============================== +*/ +void Dmc_LoadTeleporters( const char *map ) +{ + char *buffer = NULL; + char filename[ 256 ]; + + sprintf( filename, "%s/%s", gEngfuncs.pfnGetGameDirectory(), map ); + + // TODO: Fix Slashes? + + // Reset count + s_num_teles = 0; + + // Load entity lump + buffer = Dmc_LoadEntityLump( filename ); + if ( !buffer ) + return; + + // Process buffer and extract teleporters/targets + Dmc_ProcessEnts( buffer ); + + // Discard buffer + free( buffer ); +} + +/* +============================== +Dmc_FindTarget + +Search entity list for target matching "name" +============================== +*/ +dmc_tele_t *Dmc_FindTarget( const char *name, int numtele, dmc_tele_t *pTeles ) +{ + int i; + dmc_tele_t *target; + + // Find the target + for ( i = 0; i < numtele; i++ ) + { + target = &pTeles[ i ]; + + if ( !target ) + continue; + + if ( stricmp( target->targetname, name ) ) + continue; + + return target; + } + + return NULL; +} + +/* +============================== +Dmc_TeleporterTouched + +Imparts the desired velocity to the player +after touching a teleporter. +============================== +*/ +void Dmc_TeleporterTouched( int numtele, dmc_tele_t *pTeles, dmc_tele_t *pTele, struct local_state_s *player ) +{ + int i; + dmc_tele_t *target; + pmtrace_t tr; + float flGravity = pmove->movevars->gravity; + + vec3_t forward, up, right; + + float zero[ 3 ] = { 0.0, 0.0, 0.0 }; + + // Find the target + target = Dmc_FindTarget( pTele->target, numtele, pTeles ); + + for ( i = 0; i < 3; i++ ) + player->playerstate.origin[ i ] = target->origin[ i ]; + + player->playerstate.origin[ 2 ] += 27; + + AngleVectors( target->angles, forward, right, up ); + player->client.velocity = forward * 300; + + // Play sound if appropriate + if ( s_usTeleport && g_runfuncs ) + { + //Adrian - This is a little hack to make the player face + //the destination angles as soon as we step out. + //Check view.cpp for the rest. + for ( i = 0; i < 3; i++ ) + vecTempAngles[ i ] = target->angles[ i ]; + + bChangeAngles = true; + + gEngfuncs.pfnPlaybackEvent( FEV_NOTHOST, NULL, s_usTeleport, 0.0, target->origin, zero, 0.0, 0.0, 0, 0, 0, 0 ); + } +} + +/* +============================== +Dmc_TouchTeleporters + +See if player is touching a teleporter ( not that kind of touching! ). +============================== +*/ +void Dmc_TouchTeleporters ( struct local_state_s *player, dmc_tele_t *pTeles, int numtele ) +{ + int i, j; + dmc_tele_t *pTele; + float absmin[3], absmax[3]; + float pmins[ 3 ] = { 13, 13, 24 }; + float pmaxs[ 3 ] = { 13, 13, 32 }; + vec3_t LengthVector; + int iTeleNum = 0; + + + // Determine player's bbox + for ( j = 0; j < 3; j++ ) + { + absmin[ j ] = player->playerstate.origin[ j ] - pmins[ j ]; + absmax[ j ] = player->playerstate.origin[ j ] + pmaxs[ j ]; + } + + for ( i = 0; i < numtele; i++ ) + { + pTele = &pTeles[ i ]; + if ( !pTele ) + continue; + + if ( pTele->type != DMC_TELE ) + continue; + + //Adrian - Load all the teleporter Mins and Max size. + //This comes via an event when the player connects. + if ( !g_bLoadedTeles ) + { + for ( int j = 0; j < 3; j++ ) + { + pTele->absmin[ j ] = g_vecTeleMins[ iTeleNum ][ j ] - 1.0; + pTele->absmax[ j ] = g_vecTeleMaxs[ iTeleNum ][ j ] + 1.0; + } + iTeleNum++; + + //Done going thru all the teleporters + if ( iTeleNum == g_iTeleNum ) + g_bLoadedTeles = true; + } + + if ( absmin[0] > pTele->absmax[0] + || absmin[1] > pTele->absmax[1] + || absmin[2] > pTele->absmax[2] + || absmax[0] < pTele->absmin[0] + || absmax[1] < pTele->absmin[1] + || absmax[2] < pTele->absmin[2] ) + continue; + + Dmc_TeleporterTouched( numtele, pTeles, pTele, player ); + + break; + } +} + +/* +============================== +Dmc_CheckTeleporters + +Load data if needed, otherwise just run checks on player's final position to see if teleporter needs +to impart velocity on the player. +============================== +*/ +void Dmc_CheckTeleporters( struct local_state_s *from, struct local_state_s *to ) +{ + static char current_level[ 128 ]; + + // See if we've changed to a new map + if ( stricmp( current_level, gEngfuncs.pfnGetLevelName() ) ) + { + strcpy( current_level, gEngfuncs.pfnGetLevelName() ); + Dmc_LoadTeleporters( current_level ); + + // Grab sound event + s_usTeleport = gEngfuncs.pfnPrecacheEvent( 1, "events/teleport.sc" ); + } + + // Run test + if ( g_iUser1 == OBS_NONE ) + Dmc_TouchTeleporters( to, s_teles, s_num_teles ); +} \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/DMC_Teleporters.h b/hlsdk/dmc/cl_dll/DMC_Teleporters.h new file mode 100644 index 00000000..e14a3c7f --- /dev/null +++ b/hlsdk/dmc/cl_dll/DMC_Teleporters.h @@ -0,0 +1,16 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( DMC_TELEPORTERS_H ) +#define DMC_TELEPORTERS_H +#ifdef _WIN32 +#pragma once +#endif + +void Dmc_CheckTeleporters( struct local_state_s *from, struct local_state_s *to ); + +#endif // DMC_TELEPORTERS_H \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/GameStudioModelRenderer.cpp b/hlsdk/dmc/cl_dll/GameStudioModelRenderer.cpp new file mode 100644 index 00000000..497ceab8 --- /dev/null +++ b/hlsdk/dmc/cl_dll/GameStudioModelRenderer.cpp @@ -0,0 +1,120 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "com_model.h" +#include "studio.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "dlight.h" +#include "triangleapi.h" + +#include +#include +#include +#include + +#include "studio_util.h" +#include "r_studioint.h" + +#include "StudioModelRenderer.h" +#include "GameStudioModelRenderer.h" + +// +// Override the StudioModelRender virtual member functions here to implement custom bone +// setup, blending, etc. +// + +// Global engine <-> studio model rendering code interface +extern engine_studio_api_t IEngineStudio; + +// The renderer object, created on the stack. +CGameStudioModelRenderer g_StudioRenderer; + +/* +==================== +CGameStudioModelRenderer + +==================== +*/ +CGameStudioModelRenderer::CGameStudioModelRenderer( void ) +{ +} + +//////////////////////////////////// +// Hooks to class implementation +//////////////////////////////////// + +/* +==================== +R_StudioDrawPlayer + +==================== +*/ +int R_StudioDrawPlayer( int flags, entity_state_t *pplayer ) +{ + return g_StudioRenderer.StudioDrawPlayer( flags, pplayer ); +} + +/* +==================== +R_StudioDrawModel + +==================== +*/ +int R_StudioDrawModel( int flags ) +{ + return g_StudioRenderer.StudioDrawModel( flags ); +} + +/* +==================== +R_StudioInit + +==================== +*/ +void R_StudioInit( void ) +{ + g_StudioRenderer.Init(); +} + +// The simple drawing interface we'll pass back to the engine +r_studio_interface_t studio = +{ + STUDIO_INTERFACE_VERSION, + R_StudioDrawModel, + R_StudioDrawPlayer, +}; + +/* +==================== +HUD_GetStudioModelInterface + +Export this function for the engine to use the studio renderer class to render objects. +==================== +*/ +#define DLLEXPORT __declspec( dllexport ) +extern "C" int DLLEXPORT HUD_GetStudioModelInterface( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio ) +{ + if ( version != STUDIO_INTERFACE_VERSION ) + return 0; + + // Point the engine to our callbacks + *ppinterface = &studio; + + // Copy in engine helper functions + memcpy( &IEngineStudio, pstudio, sizeof( IEngineStudio ) ); + + // Initialize local variables, etc. + R_StudioInit(); + + // Success + return 1; +} diff --git a/hlsdk/dmc/cl_dll/GameStudioModelRenderer.h b/hlsdk/dmc/cl_dll/GameStudioModelRenderer.h new file mode 100644 index 00000000..7d06f70f --- /dev/null +++ b/hlsdk/dmc/cl_dll/GameStudioModelRenderer.h @@ -0,0 +1,26 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( GAMESTUDIOMODELRENDERER_H ) +#define GAMESTUDIOMODELRENDERER_H +#if defined( _WIN32 ) +#pragma once +#endif + +/* +==================== +CGameStudioModelRenderer + +==================== +*/ +class CGameStudioModelRenderer : public CStudioModelRenderer +{ +public: + CGameStudioModelRenderer( void ); +}; + +#endif // GAMESTUDIOMODELRENDERER_H \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/MOTD.cpp b/hlsdk/dmc/cl_dll/MOTD.cpp new file mode 100644 index 00000000..a06b231a --- /dev/null +++ b/hlsdk/dmc/cl_dll/MOTD.cpp @@ -0,0 +1,155 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// MOTD.cpp +// +// for displaying a server-sent message of the day +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +DECLARE_MESSAGE( m_MOTD, MOTD ); + +int CHudMOTD::MOTD_DISPLAY_TIME; + +int CHudMOTD :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( MOTD ); + + CVAR_CREATE( "motd_display_time", "15", 0 ); + + m_iFlags &= ~HUD_ACTIVE; // start out inactive + m_szMOTD[0] = 0; + + return 1; +} + +int CHudMOTD :: VidInit( void ) +{ + // Load sprites here + + return 1; +} + +void CHudMOTD :: Reset( void ) +{ + m_iFlags &= ~HUD_ACTIVE; // start out inactive + m_szMOTD[0] = 0; + m_iLines = 0; + m_flActiveRemaining = 0; +} + +#define LINE_HEIGHT 13 + +int CHudMOTD :: Draw( float fTime ) +{ + static float sfLastTime; + float fElapsed; + + // Draw MOTD line-by-line + if ( m_flActiveRemaining <= 0.0 ) + { + // finished with MOTD, disable it + m_szMOTD[0] = 0; + m_iLines = 0; + m_iFlags &= ~HUD_ACTIVE; + m_flActiveRemaining = 0.0; + return 1; + } + + fElapsed = gHUD.m_flTime - sfLastTime; + + // Don't let time go negative ( level transition? ) + fElapsed = max( 0.0, fElapsed ); + // Don't let time go hugely positive ( first connection to active server ? ) + fElapsed = min( 1.0, fElapsed ); + + // Remember last timestamp + sfLastTime = gHUD.m_flTime; + + // Remove a bit of time + m_flActiveRemaining -= fElapsed; + + // find the top of where the MOTD should be drawn, so the whole thing is centered in the screen + int ypos = max(((ScreenHeight - (m_iLines * LINE_HEIGHT)) / 2) - 40, 30 ); // shift it up slightly + char *ch = m_szMOTD; + while ( *ch ) + { + int line_length = 0; // count the length of the current line + for ( char *next_line = ch; *next_line != '\n' && *next_line != 0; next_line++ ) + line_length += gHUD.m_scrinfo.charWidths[ *next_line ]; + char *top = next_line; + if ( *top == '\n' ) + *top = 0; + else + top = NULL; + + // find where to start drawing the line + int xpos = (ScreenWidth - line_length) / 2; + + gHUD.DrawHudString( xpos, ypos, ScreenWidth, ch, 255, 180, 0 ); + + ypos += LINE_HEIGHT; + + if ( top ) // restore + *top = '\n'; + ch = next_line; + if ( *ch == '\n' ) + ch++; + + if ( ypos > (ScreenHeight - 20) ) + break; // don't let it draw too low + } + + return 1; +} + +int CHudMOTD :: MsgFunc_MOTD( const char *pszName, int iSize, void *pbuf ) +{ + if ( m_iFlags & HUD_ACTIVE ) + { + Reset(); // clear the current MOTD in prep for this one + } + + BEGIN_READ( pbuf, iSize ); + + int is_finished = READ_BYTE(); + strcat( m_szMOTD, READ_STRING() ); + + if ( is_finished ) + { + m_iFlags |= HUD_ACTIVE; + + MOTD_DISPLAY_TIME = max( 10, CVAR_GET_FLOAT( "motd_display_time" ) ); + + m_flActiveRemaining = MOTD_DISPLAY_TIME; + + for ( char *sz = m_szMOTD; *sz != 0; sz++ ) // count the number of lines in the MOTD + { + if ( *sz == '\n' ) + m_iLines++; + } + } + + return 1; +} + diff --git a/hlsdk/dmc/cl_dll/StudioModelRenderer.cpp b/hlsdk/dmc/cl_dll/StudioModelRenderer.cpp new file mode 100644 index 00000000..c49c369a --- /dev/null +++ b/hlsdk/dmc/cl_dll/StudioModelRenderer.cpp @@ -0,0 +1,1723 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// studio_model.cpp +// routines for setting up to draw 3DStudio models + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "com_model.h" +#include "studio.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "dlight.h" +#include "triangleapi.h" + +#include +#include +#include +#include + +#include "studio_util.h" +#include "r_studioint.h" + +#include "StudioModelRenderer.h" +#include "GameStudioModelRenderer.h" + +// Global engine <-> studio model rendering code interface +engine_studio_api_t IEngineStudio; + +///////////////////// +// Implementation of CStudioModelRenderer.h + +/* +==================== +Init + +==================== +*/ +void CStudioModelRenderer::Init( void ) +{ + // Set up some variables shared with engine + m_pCvarHiModels = IEngineStudio.GetCvar( "cl_himodels" ); + m_pCvarDeveloper = IEngineStudio.GetCvar( "developer" ); + m_pCvarDrawEntities = IEngineStudio.GetCvar( "r_drawentities" ); + + m_pChromeSprite = IEngineStudio.GetChromeSprite(); + + IEngineStudio.GetModelCounters( &m_pStudioModelCount, &m_pModelsDrawn ); + + // Get pointers to engine data structures + m_pbonetransform = (float (*)[MAXSTUDIOBONES][3][4])IEngineStudio.StudioGetBoneTransform(); + m_plighttransform = (float (*)[MAXSTUDIOBONES][3][4])IEngineStudio.StudioGetLightTransform(); + m_paliastransform = (float (*)[3][4])IEngineStudio.StudioGetAliasTransform(); + m_protationmatrix = (float (*)[3][4])IEngineStudio.StudioGetRotationMatrix(); +} + +/* +==================== +CStudioModelRenderer + +==================== +*/ +CStudioModelRenderer::CStudioModelRenderer( void ) +{ + m_fDoInterp = 1; + m_fGaitEstimation = 1; + m_pCurrentEntity = NULL; + m_pCvarHiModels = NULL; + m_pCvarDeveloper = NULL; + m_pCvarDrawEntities = NULL; + m_pChromeSprite = NULL; + m_pStudioModelCount = NULL; + m_pModelsDrawn = NULL; + m_protationmatrix = NULL; + m_paliastransform = NULL; + m_pbonetransform = NULL; + m_plighttransform = NULL; + m_pStudioHeader = NULL; + m_pBodyPart = NULL; + m_pSubModel = NULL; + m_pPlayerInfo = NULL; + m_pRenderModel = NULL; +} + +/* +==================== +~CStudioModelRenderer + +==================== +*/ +CStudioModelRenderer::~CStudioModelRenderer( void ) +{ +} + +/* +==================== +StudioCalcBoneAdj + +==================== +*/ +void CStudioModelRenderer::StudioCalcBoneAdj( float dadt, float *adj, const byte *pcontroller1, const byte *pcontroller2, byte mouthopen ) +{ + int i, j; + float value; + mstudiobonecontroller_t *pbonecontroller; + + pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bonecontrollerindex); + + for (j = 0; j < m_pStudioHeader->numbonecontrollers; j++) + { + i = pbonecontroller[j].index; + if (i <= 3) + { + // check for 360% wrapping + if (pbonecontroller[j].type & STUDIO_RLOOP) + { + if (abs(pcontroller1[i] - pcontroller2[i]) > 128) + { + int a, b; + a = (pcontroller1[j] + 128) % 256; + b = (pcontroller2[j] + 128) % 256; + value = ((a * dadt) + (b * (1 - dadt)) - 128) * (360.0/256.0) + pbonecontroller[j].start; + } + else + { + value = ((pcontroller1[i] * dadt + (pcontroller2[i]) * (1.0 - dadt))) * (360.0/256.0) + pbonecontroller[j].start; + } + } + else + { + value = (pcontroller1[i] * dadt + pcontroller2[i] * (1.0 - dadt)) / 255.0; + if (value < 0) value = 0; + if (value > 1.0) value = 1.0; + value = (1.0 - value) * pbonecontroller[j].start + value * pbonecontroller[j].end; + } + // Con_DPrintf( "%d %d %f : %f\n", m_pCurrentEntity->curstate.controller[j], m_pCurrentEntity->latched.prevcontroller[j], value, dadt ); + } + else + { + value = mouthopen / 64.0; + if (value > 1.0) value = 1.0; + value = (1.0 - value) * pbonecontroller[j].start + value * pbonecontroller[j].end; + // Con_DPrintf("%d %f\n", mouthopen, value ); + } + switch(pbonecontroller[j].type & STUDIO_TYPES) + { + case STUDIO_XR: + case STUDIO_YR: + case STUDIO_ZR: + adj[j] = value * (M_PI / 180.0); + break; + case STUDIO_X: + case STUDIO_Y: + case STUDIO_Z: + adj[j] = value; + break; + } + } +} + + +/* +==================== +StudioCalcBoneQuaterion + +==================== +*/ +void CStudioModelRenderer::StudioCalcBoneQuaterion( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *q ) +{ + int j, k; + vec4_t q1, q2; + vec3_t angle1, angle2; + mstudioanimvalue_t *panimvalue; + + for (j = 0; j < 3; j++) + { + if (panim->offset[j+3] == 0) + { + angle2[j] = angle1[j] = pbone->value[j+3]; // default; + } + else + { + panimvalue = (mstudioanimvalue_t *)((byte *)panim + panim->offset[j+3]); + k = frame; + // DEBUG + if (panimvalue->num.total < panimvalue->num.valid) + k = 0; + while (panimvalue->num.total <= k) + { + k -= panimvalue->num.total; + panimvalue += panimvalue->num.valid + 1; + // DEBUG + if (panimvalue->num.total < panimvalue->num.valid) + k = 0; + } + // Bah, missing blend! + if (panimvalue->num.valid > k) + { + angle1[j] = panimvalue[k+1].value; + + if (panimvalue->num.valid > k + 1) + { + angle2[j] = panimvalue[k+2].value; + } + else + { + if (panimvalue->num.total > k + 1) + angle2[j] = angle1[j]; + else + angle2[j] = panimvalue[panimvalue->num.valid+2].value; + } + } + else + { + angle1[j] = panimvalue[panimvalue->num.valid].value; + if (panimvalue->num.total > k + 1) + { + angle2[j] = angle1[j]; + } + else + { + angle2[j] = panimvalue[panimvalue->num.valid + 2].value; + } + } + angle1[j] = pbone->value[j+3] + angle1[j] * pbone->scale[j+3]; + angle2[j] = pbone->value[j+3] + angle2[j] * pbone->scale[j+3]; + } + + if (pbone->bonecontroller[j+3] != -1) + { + angle1[j] += adj[pbone->bonecontroller[j+3]]; + angle2[j] += adj[pbone->bonecontroller[j+3]]; + } + } + + if (!VectorCompare( angle1, angle2 )) + { + AngleQuaternion( angle1, q1 ); + AngleQuaternion( angle2, q2 ); + QuaternionSlerp( q1, q2, s, q ); + } + else + { + AngleQuaternion( angle1, q ); + } +} + +/* +==================== +StudioCalcBonePosition + +==================== +*/ +void CStudioModelRenderer::StudioCalcBonePosition( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *pos ) +{ + int j, k; + mstudioanimvalue_t *panimvalue; + + for (j = 0; j < 3; j++) + { + pos[j] = pbone->value[j]; // default; + if (panim->offset[j] != 0) + { + panimvalue = (mstudioanimvalue_t *)((byte *)panim + panim->offset[j]); + /* + if (i == 0 && j == 0) + Con_DPrintf("%d %d:%d %f\n", frame, panimvalue->num.valid, panimvalue->num.total, s ); + */ + + k = frame; + // DEBUG + if (panimvalue->num.total < panimvalue->num.valid) + k = 0; + // find span of values that includes the frame we want + while (panimvalue->num.total <= k) + { + k -= panimvalue->num.total; + panimvalue += panimvalue->num.valid + 1; + // DEBUG + if (panimvalue->num.total < panimvalue->num.valid) + k = 0; + } + // if we're inside the span + if (panimvalue->num.valid > k) + { + // and there's more data in the span + if (panimvalue->num.valid > k + 1) + { + pos[j] += (panimvalue[k+1].value * (1.0 - s) + s * panimvalue[k+2].value) * pbone->scale[j]; + } + else + { + pos[j] += panimvalue[k+1].value * pbone->scale[j]; + } + } + else + { + // are we at the end of the repeating values section and there's another section with data? + if (panimvalue->num.total <= k + 1) + { + pos[j] += (panimvalue[panimvalue->num.valid].value * (1.0 - s) + s * panimvalue[panimvalue->num.valid + 2].value) * pbone->scale[j]; + } + else + { + pos[j] += panimvalue[panimvalue->num.valid].value * pbone->scale[j]; + } + } + } + if ( pbone->bonecontroller[j] != -1 && adj ) + { + pos[j] += adj[pbone->bonecontroller[j]]; + } + } +} + +/* +==================== +StudioSlerpBones + +==================== +*/ +void CStudioModelRenderer::StudioSlerpBones( vec4_t q1[], float pos1[][3], vec4_t q2[], float pos2[][3], float s ) +{ + int i; + vec4_t q3; + float s1; + + if (s < 0) s = 0; + else if (s > 1.0) s = 1.0; + + s1 = 1.0 - s; + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + QuaternionSlerp( q1[i], q2[i], s, q3 ); + q1[i][0] = q3[0]; + q1[i][1] = q3[1]; + q1[i][2] = q3[2]; + q1[i][3] = q3[3]; + pos1[i][0] = pos1[i][0] * s1 + pos2[i][0] * s; + pos1[i][1] = pos1[i][1] * s1 + pos2[i][1] * s; + pos1[i][2] = pos1[i][2] * s1 + pos2[i][2] * s; + } +} + +/* +==================== +StudioGetAnim + +==================== +*/ +mstudioanim_t *CStudioModelRenderer::StudioGetAnim( model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc ) +{ + mstudioseqgroup_t *pseqgroup; + cache_user_t *paSequences; + + pseqgroup = (mstudioseqgroup_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqgroupindex) + pseqdesc->seqgroup; + + if (pseqdesc->seqgroup == 0) + { + return (mstudioanim_t *)((byte *)m_pStudioHeader + pseqgroup->data + pseqdesc->animindex); + } + + paSequences = (cache_user_t *)m_pSubModel->submodels; + + if (paSequences == NULL) + { + paSequences = (cache_user_t *)IEngineStudio.Mem_Calloc( 16, sizeof( cache_user_t ) ); // UNDONE: leak! + m_pSubModel->submodels = (dmodel_t *)paSequences; + } + + if (!IEngineStudio.Cache_Check( (struct cache_user_s *)&(paSequences[pseqdesc->seqgroup]))) + { + gEngfuncs.Con_DPrintf("loading %s\n", pseqgroup->name ); + IEngineStudio.LoadCacheFile( pseqgroup->name, (struct cache_user_s *)&paSequences[pseqdesc->seqgroup] ); + } + return (mstudioanim_t *)((byte *)paSequences[pseqdesc->seqgroup].data + pseqdesc->animindex); +} + +/* +==================== +StudioPlayerBlend + +==================== +*/ +void CStudioModelRenderer::StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch ) +{ + // calc up/down pointing + *pBlend = (*pPitch * 3); + if (*pBlend < pseqdesc->blendstart[0]) + { + *pPitch -= pseqdesc->blendstart[0] / 3.0; + *pBlend = 0; + } + else if (*pBlend > pseqdesc->blendend[0]) + { + *pPitch -= pseqdesc->blendend[0] / 3.0; + *pBlend = 255; + } + else + { + if (pseqdesc->blendend[0] - pseqdesc->blendstart[0] < 0.1) // catch qc error + *pBlend = 127; + else + *pBlend = 255 * (*pBlend - pseqdesc->blendstart[0]) / (pseqdesc->blendend[0] - pseqdesc->blendstart[0]); + *pPitch = 0; + } +} + +/* +==================== +StudioSetUpTransform + +==================== +*/ +void CStudioModelRenderer::StudioSetUpTransform (int trivial_accept) +{ + int i; + vec3_t angles; + vec3_t modelpos; + + + VectorCopy( m_pCurrentEntity->origin, modelpos ); + +// TODO: should really be stored with the entity instead of being reconstructed +// TODO: should use a look-up table +// TODO: could cache lazily, stored in the entity + angles[ROLL] = m_pCurrentEntity->curstate.angles[ROLL]; + angles[PITCH] = m_pCurrentEntity->curstate.angles[PITCH]; + angles[YAW] = m_pCurrentEntity->curstate.angles[YAW]; + + //Adrian - Have the model rotate ( weapon world models, powerups and armor. ) + //Yeah, we're too lazy to animate them!. + if ( strstr( m_pCurrentEntity->model->name, "g_" ) || strstr( m_pCurrentEntity->model->name, "pow_" ) || strstr( m_pCurrentEntity->model->name, "armour" ) ) + { + float timemod; + + timemod = fmod( gEngfuncs.GetClientTime(), 2.0f ); + + m_pCurrentEntity->angles[0] = 0; + m_pCurrentEntity->angles[YAW] = timemod * 180.0 - 90.0; + m_pCurrentEntity->angles[2] = 0; + + VectorCopy( m_pCurrentEntity->angles, m_pCurrentEntity->curstate.angles ); + } + + //Con_DPrintf("Angles %4.2f prev %4.2f for %i\n", angles[PITCH], m_pCurrentEntity->index); + //Con_DPrintf("movetype %d %d\n", m_pCurrentEntity->movetype, m_pCurrentEntity->aiment ); + if (m_pCurrentEntity->curstate.movetype == MOVETYPE_STEP) + { + float f = 0; + float d; + + // don't do it if the goalstarttime hasn't updated in a while. + + // NOTE: Because we need to interpolate multiplayer characters, the interpolation time limit + // was increased to 1.0 s., which is 2x the max lag we are accounting for. + + if ( ( m_clTime < m_pCurrentEntity->curstate.animtime + 1.0f ) && + ( m_pCurrentEntity->curstate.animtime != m_pCurrentEntity->latched.prevanimtime ) ) + { + f = (m_clTime - m_pCurrentEntity->curstate.animtime) / (m_pCurrentEntity->curstate.animtime - m_pCurrentEntity->latched.prevanimtime); + //Con_DPrintf("%4.2f %.2f %.2f\n", f, m_pCurrentEntity->curstate.animtime, m_clTime); + } + + if (m_fDoInterp) + { + // ugly hack to interpolate angle, position. current is reached 0.1 seconds after being set + f = f - 1.0; + } + else + { + f = 0; + } + + for (i = 0; i < 3; i++) + { + modelpos[i] += (m_pCurrentEntity->origin[i] - m_pCurrentEntity->latched.prevorigin[i]) * f; + } + + // NOTE: Because multiplayer lag can be relatively large, we don't want to cap + // f at 1.5 anymore. + //if (f > -1.0 && f < 1.5) {} + +// gEngfuncs.Con_DPrintf("%.0f %.0f\n",m_pCurrentEntity->angles[0][YAW], m_pCurrentEntity->angles[1][YAW] ); + for (i = 0; i < 3; i++) + { + float ang1, ang2; + + ang1 = m_pCurrentEntity->angles[i]; + ang2 = m_pCurrentEntity->latched.prevangles[i]; + + d = ang1 - ang2; + if (d > 180) + { + d -= 360; + } + else if (d < -180) + { + d += 360; + } + + angles[i] += d * f; + } + //Con_DPrintf("%.3f \n", f ); + } + else if ( m_pCurrentEntity->curstate.movetype != MOVETYPE_NONE ) + { + VectorCopy( m_pCurrentEntity->angles, angles ); + } + + //Con_DPrintf("%.0f %0.f %0.f\n", modelpos[0], modelpos[1], modelpos[2] ); +// gEngfuncs.Con_DPrintf("%.0f %0.f %0.f\n", angles[0], angles[1], angles[2] ); + + + angles[PITCH] = -angles[PITCH]; + AngleMatrix (angles, (*m_protationmatrix)); + + if ( !IEngineStudio.IsHardware() ) + { + static float viewmatrix[3][4]; + + VectorCopy (m_vRight, viewmatrix[0]); + VectorCopy (m_vUp, viewmatrix[1]); + VectorInverse (viewmatrix[1]); + VectorCopy (m_vNormal, viewmatrix[2]); + + (*m_protationmatrix)[0][3] = modelpos[0] - m_vRenderOrigin[0]; + (*m_protationmatrix)[1][3] = modelpos[1] - m_vRenderOrigin[1]; + (*m_protationmatrix)[2][3] = modelpos[2] - m_vRenderOrigin[2]; + + ConcatTransforms (viewmatrix, (*m_protationmatrix), (*m_paliastransform)); + + // do the scaling up of x and y to screen coordinates as part of the transform + // for the unclipped case (it would mess up clipping in the clipped case). + // Also scale down z, so 1/z is scaled 31 bits for free, and scale down x and y + // correspondingly so the projected x and y come out right + // FIXME: make this work for clipped case too? + if (trivial_accept) + { + for (i=0 ; i<4 ; i++) + { + (*m_paliastransform)[0][i] *= m_fSoftwareXScale * + (1.0 / (ZISCALE * 0x10000)); + (*m_paliastransform)[1][i] *= m_fSoftwareYScale * + (1.0 / (ZISCALE * 0x10000)); + (*m_paliastransform)[2][i] *= 1.0 / (ZISCALE * 0x10000); + + } + } + } + + (*m_protationmatrix)[0][3] = modelpos[0]; + (*m_protationmatrix)[1][3] = modelpos[1]; + (*m_protationmatrix)[2][3] = modelpos[2]; +} + + +/* +==================== +StudioEstimateInterpolant + +==================== +*/ +float CStudioModelRenderer::StudioEstimateInterpolant( void ) +{ + float dadt = 1.0; + + if ( m_fDoInterp && ( m_pCurrentEntity->curstate.animtime >= m_pCurrentEntity->latched.prevanimtime + 0.01 ) ) + { + dadt = (m_clTime - m_pCurrentEntity->curstate.animtime) / 0.1; + if (dadt > 2.0) + { + dadt = 2.0; + } + } + return dadt; +} + +/* +==================== +StudioCalcRotations + +==================== +*/ +void CStudioModelRenderer::StudioCalcRotations ( float pos[][3], vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f ) +{ + int i; + int frame; + mstudiobone_t *pbone; + + float s; + float adj[MAXSTUDIOCONTROLLERS]; + float dadt; + + if (f > pseqdesc->numframes - 1) + { + f = 0; // bah, fix this bug with changing sequences too fast + } + // BUG ( somewhere else ) but this code should validate this data. + // This could cause a crash if the frame # is negative, so we'll go ahead + // and clamp it here + else if ( f < -0.01 ) + { + f = -0.01; + } + + frame = (int)f; + + // Con_DPrintf("%d %.4f %.4f %.4f %.4f %d\n", m_pCurrentEntity->curstate.sequence, m_clTime, m_pCurrentEntity->animtime, m_pCurrentEntity->frame, f, frame ); + + // Con_DPrintf( "%f %f %f\n", m_pCurrentEntity->angles[ROLL], m_pCurrentEntity->angles[PITCH], m_pCurrentEntity->angles[YAW] ); + + // Con_DPrintf("frame %d %d\n", frame1, frame2 ); + + + dadt = StudioEstimateInterpolant( ); + s = (f - frame); + + // add in programtic controllers + pbone = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + StudioCalcBoneAdj( dadt, adj, m_pCurrentEntity->curstate.controller, m_pCurrentEntity->latched.prevcontroller, m_pCurrentEntity->mouth.mouthopen ); + + for (i = 0; i < m_pStudioHeader->numbones; i++, pbone++, panim++) + { + StudioCalcBoneQuaterion( frame, s, pbone, panim, adj, q[i] ); + + StudioCalcBonePosition( frame, s, pbone, panim, adj, pos[i] ); + // if (0 && i == 0) + // Con_DPrintf("%d %d %d %d\n", m_pCurrentEntity->curstate.sequence, frame, j, k ); + } + + if (pseqdesc->motiontype & STUDIO_X) + { + pos[pseqdesc->motionbone][0] = 0.0; + } + if (pseqdesc->motiontype & STUDIO_Y) + { + pos[pseqdesc->motionbone][1] = 0.0; + } + if (pseqdesc->motiontype & STUDIO_Z) + { + pos[pseqdesc->motionbone][2] = 0.0; + } + + s = 0 * ((1.0 - (f - (int)(f))) / (pseqdesc->numframes)) * m_pCurrentEntity->curstate.framerate; + + if (pseqdesc->motiontype & STUDIO_LX) + { + pos[pseqdesc->motionbone][0] += s * pseqdesc->linearmovement[0]; + } + if (pseqdesc->motiontype & STUDIO_LY) + { + pos[pseqdesc->motionbone][1] += s * pseqdesc->linearmovement[1]; + } + if (pseqdesc->motiontype & STUDIO_LZ) + { + pos[pseqdesc->motionbone][2] += s * pseqdesc->linearmovement[2]; + } +} + +/* +==================== +Studio_FxTransform + +==================== +*/ +void CStudioModelRenderer::StudioFxTransform( cl_entity_t *ent, float transform[3][4] ) +{ + + switch( ent->curstate.renderfx ) + { + case kRenderFxDistort: + case kRenderFxHologram: + if ( gEngfuncs.pfnRandomLong(0,49) == 0 ) + { + int axis = gEngfuncs.pfnRandomLong(0,1); + if ( axis == 1 ) // Choose between x & z + axis = 2; + VectorScale( transform[axis], gEngfuncs.pfnRandomFloat(1,1.484), transform[axis] ); + } + else if ( gEngfuncs.pfnRandomLong(0,49) == 0 ) + { + float offset; + int axis = gEngfuncs.pfnRandomLong(0,1); + if ( axis == 1 ) // Choose between x & z + axis = 2; + offset = gEngfuncs.pfnRandomFloat(-10,10); + transform[gEngfuncs.pfnRandomLong(0,2)][3] += offset; + } + break; + case kRenderFxExplode: + { + float scale; + + scale = 1.0 + ( m_clTime - ent->curstate.animtime) * 10.0; + if ( scale > 2 ) // Don't blow up more than 200% + scale = 2; + transform[0][1] *= scale; + transform[1][1] *= scale; + transform[2][1] *= scale; + } + break; + + } +} + +/* +==================== +StudioEstimateFrame + +==================== +*/ +float CStudioModelRenderer::StudioEstimateFrame( mstudioseqdesc_t *pseqdesc ) +{ + double dfdt, f; + + if ( m_fDoInterp ) + { + if ( m_clTime < m_pCurrentEntity->curstate.animtime ) + { + dfdt = 0; + } + else + { + dfdt = (m_clTime - m_pCurrentEntity->curstate.animtime) * m_pCurrentEntity->curstate.framerate * pseqdesc->fps; + + } + } + else + { + dfdt = 0; + } + + if (pseqdesc->numframes <= 1) + { + f = 0; + } + else + { + f = (m_pCurrentEntity->curstate.frame * (pseqdesc->numframes - 1)) / 256.0; + } + + f += dfdt; + + if (pseqdesc->flags & STUDIO_LOOPING) + { + if (pseqdesc->numframes > 1) + { + f -= (int)(f / (pseqdesc->numframes - 1)) * (pseqdesc->numframes - 1); + } + if (f < 0) + { + f += (pseqdesc->numframes - 1); + } + } + else + { + if (f >= pseqdesc->numframes - 1.001) + { + f = pseqdesc->numframes - 1.001; + } + if (f < 0.0) + { + f = 0.0; + } + } + return f; +} + +/* +==================== +StudioSetupBones + +==================== +*/ +void CStudioModelRenderer::StudioSetupBones ( void ) +{ + int i; + double f; + + mstudiobone_t *pbones; + mstudioseqdesc_t *pseqdesc; + mstudioanim_t *panim; + + static float pos[MAXSTUDIOBONES][3]; + static vec4_t q[MAXSTUDIOBONES]; + float bonematrix[3][4]; + + static float pos2[MAXSTUDIOBONES][3]; + static vec4_t q2[MAXSTUDIOBONES]; + static float pos3[MAXSTUDIOBONES][3]; + static vec4_t q3[MAXSTUDIOBONES]; + static float pos4[MAXSTUDIOBONES][3]; + static vec4_t q4[MAXSTUDIOBONES]; + + if (m_pCurrentEntity->curstate.sequence >= m_pStudioHeader->numseq) + { + m_pCurrentEntity->curstate.sequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + f = StudioEstimateFrame( pseqdesc ); + + if (m_pCurrentEntity->latched.prevframe > f) + { + //Con_DPrintf("%f %f\n", m_pCurrentEntity->prevframe, f ); + } + + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + StudioCalcRotations( pos, q, pseqdesc, panim, f ); + + if (pseqdesc->numblends > 1) + { + float s; + float dadt; + + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos2, q2, pseqdesc, panim, f ); + + dadt = StudioEstimateInterpolant(); + s = (m_pCurrentEntity->curstate.blending[0] * dadt + m_pCurrentEntity->latched.prevblending[0] * (1.0 - dadt)) / 255.0; + + StudioSlerpBones( q, pos, q2, pos2, s ); + + if (pseqdesc->numblends == 4) + { + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos3, q3, pseqdesc, panim, f ); + + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos4, q4, pseqdesc, panim, f ); + + s = (m_pCurrentEntity->curstate.blending[0] * dadt + m_pCurrentEntity->latched.prevblending[0] * (1.0 - dadt)) / 255.0; + StudioSlerpBones( q3, pos3, q4, pos4, s ); + + s = (m_pCurrentEntity->curstate.blending[1] * dadt + m_pCurrentEntity->latched.prevblending[1] * (1.0 - dadt)) / 255.0; + StudioSlerpBones( q, pos, q3, pos3, s ); + } + } + + if (m_fDoInterp && + m_pCurrentEntity->latched.sequencetime && + ( m_pCurrentEntity->latched.sequencetime + 0.2 > m_clTime ) && + ( m_pCurrentEntity->latched.prevsequence < m_pStudioHeader->numseq )) + { + // blend from last sequence + static float pos1b[MAXSTUDIOBONES][3]; + static vec4_t q1b[MAXSTUDIOBONES]; + float s; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->latched.prevsequence; + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + // clip prevframe + StudioCalcRotations( pos1b, q1b, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + if (pseqdesc->numblends > 1) + { + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos2, q2, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + s = (m_pCurrentEntity->latched.prevseqblending[0]) / 255.0; + StudioSlerpBones( q1b, pos1b, q2, pos2, s ); + + if (pseqdesc->numblends == 4) + { + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos3, q3, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos4, q4, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + s = (m_pCurrentEntity->latched.prevseqblending[0]) / 255.0; + StudioSlerpBones( q3, pos3, q4, pos4, s ); + + s = (m_pCurrentEntity->latched.prevseqblending[1]) / 255.0; + StudioSlerpBones( q1b, pos1b, q3, pos3, s ); + } + } + + s = 1.0 - (m_clTime - m_pCurrentEntity->latched.sequencetime) / 0.2; + StudioSlerpBones( q, pos, q1b, pos1b, s ); + } + else + { + //Con_DPrintf("prevframe = %4.2f\n", f); + m_pCurrentEntity->latched.prevframe = f; + } + + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + // calc gait animation + if (m_pPlayerInfo && m_pPlayerInfo->gaitsequence != 0) + { + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pPlayerInfo->gaitsequence; + + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + StudioCalcRotations( pos2, q2, pseqdesc, panim, m_pPlayerInfo->gaitframe ); + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + if (strcmp( pbones[i].name, "Bip01 Spine") == 0) + break; + memcpy( pos[i], pos2[i], sizeof( pos[i] )); + memcpy( q[i], q2[i], sizeof( q[i] )); + } + } + + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + QuaternionMatrix( q[i], bonematrix ); + + bonematrix[0][3] = pos[i][0]; + bonematrix[1][3] = pos[i][1]; + bonematrix[2][3] = pos[i][2]; + + if (pbones[i].parent == -1) + { + + //Adrian - Scale the player's model height down + //NOTE: This is only relative for a few models, + //some other models might need a different number. + if ( m_pCurrentEntity->player ) + bonematrix[2][2] *= 0.89; // (2,2) is Z component + + if ( IEngineStudio.IsHardware() ) + { + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + } + else + { + ConcatTransforms ((*m_paliastransform), bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + } + + // Apply client-side effects to the transformation matrix + StudioFxTransform( m_pCurrentEntity, (*m_pbonetransform)[i] ); + } + else + { + ConcatTransforms ((*m_pbonetransform)[pbones[i].parent], bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_plighttransform)[pbones[i].parent], bonematrix, (*m_plighttransform)[i]); + } + } + +} + + +/* +==================== +StudioSaveBones + +==================== +*/ +void CStudioModelRenderer::StudioSaveBones( void ) +{ + int i; + + mstudiobone_t *pbones; + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + m_nCachedBones = m_pStudioHeader->numbones; + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + strcpy( m_nCachedBoneNames[i], pbones[i].name ); + MatrixCopy( (*m_pbonetransform)[i], m_rgCachedBoneTransform[i] ); + MatrixCopy( (*m_plighttransform)[i], m_rgCachedLightTransform[i] ); + } +} + + +/* +==================== +StudioMergeBones + +==================== +*/ +void CStudioModelRenderer::StudioMergeBones ( model_t *m_pSubModel ) +{ + int i, j; + double f; + int do_hunt = true; + + mstudiobone_t *pbones; + mstudioseqdesc_t *pseqdesc; + mstudioanim_t *panim; + + static float pos[MAXSTUDIOBONES][3]; + float bonematrix[3][4]; + static vec4_t q[MAXSTUDIOBONES]; + + if (m_pCurrentEntity->curstate.sequence >= m_pStudioHeader->numseq) + { + m_pCurrentEntity->curstate.sequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + f = StudioEstimateFrame( pseqdesc ); + + if (m_pCurrentEntity->latched.prevframe > f) + { + //Con_DPrintf("%f %f\n", m_pCurrentEntity->prevframe, f ); + } + + panim = StudioGetAnim( m_pSubModel, pseqdesc ); + StudioCalcRotations( pos, q, pseqdesc, panim, f ); + + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + for (j = 0; j < m_nCachedBones; j++) + { + if (stricmp(pbones[i].name, m_nCachedBoneNames[j]) == 0) + { + MatrixCopy( m_rgCachedBoneTransform[j], (*m_pbonetransform)[i] ); + MatrixCopy( m_rgCachedLightTransform[j], (*m_plighttransform)[i] ); + break; + } + } + if (j >= m_nCachedBones) + { + QuaternionMatrix( q[i], bonematrix ); + + bonematrix[0][3] = pos[i][0]; + bonematrix[1][3] = pos[i][1]; + bonematrix[2][3] = pos[i][2]; + + if (pbones[i].parent == -1) + { + if ( IEngineStudio.IsHardware() ) + { + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + //MatrixCopy( (*m_pbonetransform)[i], (*m_plighttransform)[i] ); + } + else + { + ConcatTransforms ((*m_paliastransform), bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + } + + // Apply client-side effects to the transformation matrix + StudioFxTransform( m_pCurrentEntity, (*m_pbonetransform)[i] ); + } + else + { + ConcatTransforms ((*m_pbonetransform)[pbones[i].parent], bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_plighttransform)[pbones[i].parent], bonematrix, (*m_plighttransform)[i]); + } + } + } +} + +/* +==================== +StudioDrawModel + +==================== +*/ +int CStudioModelRenderer::StudioDrawModel( int flags ) +{ + alight_t lighting; + vec3_t dir; + + m_pCurrentEntity = IEngineStudio.GetCurrentEntity(); + IEngineStudio.GetTimes( &m_nFrameCount, &m_clTime, &m_clOldTime ); + IEngineStudio.GetViewInfo( m_vRenderOrigin, m_vUp, m_vRight, m_vNormal ); + IEngineStudio.GetAliasScale( &m_fSoftwareXScale, &m_fSoftwareYScale ); + + if (m_pCurrentEntity->curstate.renderfx == kRenderFxDeadPlayer) + { + entity_state_t deadplayer; + + int result; + int save_interp; + + if (m_pCurrentEntity->curstate.renderamt <= 0 || m_pCurrentEntity->curstate.renderamt > gEngfuncs.GetMaxClients() ) + return 0; + + // get copy of player + deadplayer = *(IEngineStudio.GetPlayerState( m_pCurrentEntity->curstate.renderamt - 1 )); //cl.frames[cl.parsecount & CL_UPDATE_MASK].playerstate[m_pCurrentEntity->curstate.renderamt-1]; + + // clear weapon, movement state + deadplayer.number = m_pCurrentEntity->curstate.renderamt; + deadplayer.weaponmodel = 0; + deadplayer.gaitsequence = 0; + + deadplayer.movetype = MOVETYPE_NONE; + VectorCopy( m_pCurrentEntity->curstate.angles, deadplayer.angles ); + VectorCopy( m_pCurrentEntity->curstate.origin, deadplayer.origin ); + + save_interp = m_fDoInterp; + m_fDoInterp = 0; + + // draw as though it were a player + result = StudioDrawPlayer( flags, &deadplayer ); + + m_fDoInterp = save_interp; + return result; + } + + m_pRenderModel = m_pCurrentEntity->model; + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (m_pRenderModel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + IEngineStudio.SetRenderModel( m_pRenderModel ); + + StudioSetUpTransform( 0 ); + + if (flags & STUDIO_RENDER) + { + // see if the bounding box lets us trivially reject, also sets + if (!IEngineStudio.StudioCheckBBox ()) + return 0; + + (*m_pModelsDrawn)++; + (*m_pStudioModelCount)++; // render data cache cookie + + if (m_pStudioHeader->numbodyparts == 0) + return 1; + } + + if (m_pCurrentEntity->curstate.movetype == MOVETYPE_FOLLOW) + { + StudioMergeBones( m_pRenderModel ); + } + else + { + StudioSetupBones( ); + } + + StudioSaveBones( ); + + if (flags & STUDIO_EVENTS) + { + StudioCalcAttachments( ); + IEngineStudio.StudioClientEvents( ); + // copy attachments into global entity array + if ( m_pCurrentEntity->index > 0 ) + { + cl_entity_t *ent = gEngfuncs.GetEntityByIndex( m_pCurrentEntity->index ); + + memcpy( ent->attachment, m_pCurrentEntity->attachment, sizeof( vec3_t ) * 4 ); + } + } + + if (flags & STUDIO_RENDER) + { + lighting.plightvec = dir; + IEngineStudio.StudioDynamicLight(m_pCurrentEntity, &lighting ); + + IEngineStudio.StudioEntityLight( &lighting ); + + //Scale the spike model lighting by a factor of 30 ( H4X!! ) + if ( strstr ( m_pCurrentEntity->model->name, "spike.mdl" ) ) + lighting.ambientlight *= 30; + + // model and frame independant + IEngineStudio.StudioSetupLighting (&lighting); + + // get remap colors + m_nTopColor = m_pCurrentEntity->curstate.colormap & 0xFF; + m_nBottomColor = (m_pCurrentEntity->curstate.colormap & 0xFF00) >> 8; + + IEngineStudio.StudioSetRemapColors( m_nTopColor, m_nBottomColor ); + + StudioRenderModel( ); + } + + return 1; +} + +/* +==================== +StudioEstimateGait + +==================== +*/ +void CStudioModelRenderer::StudioEstimateGait( entity_state_t *pplayer ) +{ + float dt; + vec3_t est_velocity; + + dt = (m_clTime - m_clOldTime); + if (dt < 0) + dt = 0; + else if (dt > 1.0) + dt = 1; + + if (dt == 0 || m_pPlayerInfo->renderframe == m_nFrameCount) + { + m_flGaitMovement = 0; + return; + } + + // VectorAdd( pplayer->velocity, pplayer->prediction_error, est_velocity ); + if ( m_fGaitEstimation ) + { + VectorSubtract( m_pCurrentEntity->origin, m_pPlayerInfo->prevgaitorigin, est_velocity ); + VectorCopy( m_pCurrentEntity->origin, m_pPlayerInfo->prevgaitorigin ); + m_flGaitMovement = Length( est_velocity ); + if (dt <= 0 || m_flGaitMovement / dt < 5) + { + m_flGaitMovement = 0; + est_velocity[0] = 0; + est_velocity[1] = 0; + } + } + else + { + VectorCopy( pplayer->velocity, est_velocity ); + m_flGaitMovement = Length( est_velocity ) * dt; + } + + if (est_velocity[1] == 0 && est_velocity[0] == 0) + { + float flYawDiff = m_pCurrentEntity->angles[YAW] - m_pPlayerInfo->gaityaw; + flYawDiff = flYawDiff - (int)(flYawDiff / 360) * 360; + if (flYawDiff > 180) + flYawDiff -= 360; + if (flYawDiff < -180) + flYawDiff += 360; + + if (dt < 0.25) + flYawDiff *= dt * 4; + else + flYawDiff *= dt; + + m_pPlayerInfo->gaityaw += flYawDiff; + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - (int)(m_pPlayerInfo->gaityaw / 360) * 360; + + m_flGaitMovement = 0; + } + else + { + m_pPlayerInfo->gaityaw = (atan2(est_velocity[1], est_velocity[0]) * 180 / M_PI); + if (m_pPlayerInfo->gaityaw > 180) + m_pPlayerInfo->gaityaw = 180; + if (m_pPlayerInfo->gaityaw < -180) + m_pPlayerInfo->gaityaw = -180; + } + +} + +/* +==================== +StudioProcessGait + +==================== +*/ +void CStudioModelRenderer::StudioProcessGait( entity_state_t *pplayer ) +{ + mstudioseqdesc_t *pseqdesc; + float dt; + int iBlend; + float flYaw; // view direction relative to movement + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + StudioPlayerBlend( pseqdesc, &iBlend, &m_pCurrentEntity->angles[PITCH] ); + + m_pCurrentEntity->latched.prevangles[PITCH] = m_pCurrentEntity->angles[PITCH]; + m_pCurrentEntity->curstate.blending[0] = iBlend; + m_pCurrentEntity->latched.prevblending[0] = m_pCurrentEntity->curstate.blending[0]; + m_pCurrentEntity->latched.prevseqblending[0] = m_pCurrentEntity->curstate.blending[0]; + + // Con_DPrintf("%f %d\n", m_pCurrentEntity->angles[PITCH], m_pCurrentEntity->blending[0] ); + + dt = (m_clTime - m_clOldTime); + if (dt < 0) + dt = 0; + else if (dt > 1.0) + dt = 1; + + StudioEstimateGait( pplayer ); + + // Con_DPrintf("%f %f\n", m_pCurrentEntity->angles[YAW], m_pPlayerInfo->gaityaw ); + + // calc side to side turning + flYaw = m_pCurrentEntity->angles[YAW] - m_pPlayerInfo->gaityaw; + flYaw = flYaw - (int)(flYaw / 360) * 360; + if (flYaw < -180) + flYaw = flYaw + 360; + if (flYaw > 180) + flYaw = flYaw - 360; + + if (flYaw > 120) + { + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - 180; + m_flGaitMovement = -m_flGaitMovement; + flYaw = flYaw - 180; + } + else if (flYaw < -120) + { + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw + 180; + m_flGaitMovement = -m_flGaitMovement; + flYaw = flYaw + 180; + } + + // adjust torso + m_pCurrentEntity->curstate.controller[0] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); + m_pCurrentEntity->curstate.controller[1] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); + m_pCurrentEntity->curstate.controller[2] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); + m_pCurrentEntity->curstate.controller[3] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); + m_pCurrentEntity->latched.prevcontroller[0] = m_pCurrentEntity->curstate.controller[0]; + m_pCurrentEntity->latched.prevcontroller[1] = m_pCurrentEntity->curstate.controller[1]; + m_pCurrentEntity->latched.prevcontroller[2] = m_pCurrentEntity->curstate.controller[2]; + m_pCurrentEntity->latched.prevcontroller[3] = m_pCurrentEntity->curstate.controller[3]; + + m_pCurrentEntity->angles[YAW] = m_pPlayerInfo->gaityaw; + if (m_pCurrentEntity->angles[YAW] < -0) + m_pCurrentEntity->angles[YAW] += 360; + m_pCurrentEntity->latched.prevangles[YAW] = m_pCurrentEntity->angles[YAW]; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + pplayer->gaitsequence; + + // calc gait frame + if (pseqdesc->linearmovement[0] > 0) + { + m_pPlayerInfo->gaitframe += (m_flGaitMovement / pseqdesc->linearmovement[0]) * pseqdesc->numframes; + } + else + { + m_pPlayerInfo->gaitframe += pseqdesc->fps * dt; + } + + // do modulo + m_pPlayerInfo->gaitframe = m_pPlayerInfo->gaitframe - (int)(m_pPlayerInfo->gaitframe / pseqdesc->numframes) * pseqdesc->numframes; + if (m_pPlayerInfo->gaitframe < 0) + m_pPlayerInfo->gaitframe += pseqdesc->numframes; +} + +/* +==================== +StudioDrawPlayer + +==================== +*/ +int CStudioModelRenderer::StudioDrawPlayer( int flags, entity_state_t *pplayer ) +{ + alight_t lighting; + vec3_t dir; + + m_pCurrentEntity = IEngineStudio.GetCurrentEntity(); + IEngineStudio.GetTimes( &m_nFrameCount, &m_clTime, &m_clOldTime ); + IEngineStudio.GetViewInfo( m_vRenderOrigin, m_vUp, m_vRight, m_vNormal ); + IEngineStudio.GetAliasScale( &m_fSoftwareXScale, &m_fSoftwareYScale ); + + // Con_DPrintf("DrawPlayer %d\n", m_pCurrentEntity->blending[0] ); + + // Con_DPrintf("DrawPlayer %d %d (%d)\n", m_nFrameCount, pplayer->player_index, m_pCurrentEntity->curstate.sequence ); + + // Con_DPrintf("Player %.2f %.2f %.2f\n", pplayer->velocity[0], pplayer->velocity[1], pplayer->velocity[2] ); + + m_nPlayerIndex = pplayer->number - 1; + + if (m_nPlayerIndex < 0 || m_nPlayerIndex >= gEngfuncs.GetMaxClients()) + return 0; + + m_pRenderModel = IEngineStudio.SetupPlayerModel( m_nPlayerIndex ); + if (m_pRenderModel == NULL) + return 0; + + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (m_pRenderModel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + IEngineStudio.SetRenderModel( m_pRenderModel ); + + if (pplayer->gaitsequence) + { + vec3_t orig_angles; + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + + VectorCopy( m_pCurrentEntity->angles, orig_angles ); + + StudioProcessGait( pplayer ); + + m_pPlayerInfo->gaitsequence = pplayer->gaitsequence; + m_pPlayerInfo = NULL; + + StudioSetUpTransform( 0 ); + VectorCopy( orig_angles, m_pCurrentEntity->angles ); + } + else + { + m_pCurrentEntity->curstate.controller[0] = 127; + m_pCurrentEntity->curstate.controller[1] = 127; + m_pCurrentEntity->curstate.controller[2] = 127; + m_pCurrentEntity->curstate.controller[3] = 127; + m_pCurrentEntity->latched.prevcontroller[0] = m_pCurrentEntity->curstate.controller[0]; + m_pCurrentEntity->latched.prevcontroller[1] = m_pCurrentEntity->curstate.controller[1]; + m_pCurrentEntity->latched.prevcontroller[2] = m_pCurrentEntity->curstate.controller[2]; + m_pCurrentEntity->latched.prevcontroller[3] = m_pCurrentEntity->curstate.controller[3]; + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + m_pPlayerInfo->gaitsequence = 0; + + StudioSetUpTransform( 0 ); + } + + if (flags & STUDIO_RENDER) + { + // see if the bounding box lets us trivially reject, also sets + if (!IEngineStudio.StudioCheckBBox ()) + return 0; + + (*m_pModelsDrawn)++; + (*m_pStudioModelCount)++; // render data cache cookie + + if (m_pStudioHeader->numbodyparts == 0) + return 1; + } + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + StudioSetupBones( ); + + StudioSaveBones( ); + m_pPlayerInfo->renderframe = m_nFrameCount; + + m_pPlayerInfo = NULL; + + if (flags & STUDIO_EVENTS) + { + StudioCalcAttachments( ); + IEngineStudio.StudioClientEvents( ); + // copy attachments into global entity array + if ( m_pCurrentEntity->index > 0 ) + { + cl_entity_t *ent = gEngfuncs.GetEntityByIndex( m_pCurrentEntity->index ); + + memcpy( ent->attachment, m_pCurrentEntity->attachment, sizeof( vec3_t ) * 4 ); + } + } + + if (flags & STUDIO_RENDER) + { + if (m_pCvarHiModels->value && m_pRenderModel != m_pCurrentEntity->model ) + { + // show highest resolution multiplayer model + m_pCurrentEntity->curstate.body = 255; + } + + if (!(m_pCvarDeveloper->value == 0 && gEngfuncs.GetMaxClients() == 1 ) && ( m_pRenderModel == m_pCurrentEntity->model ) ) + { + m_pCurrentEntity->curstate.body = 1; // force helmet + } + + lighting.plightvec = dir; + IEngineStudio.StudioDynamicLight(m_pCurrentEntity, &lighting ); + + IEngineStudio.StudioEntityLight( &lighting ); + + //Scale player model lighting by a factor of 100 ( H4X!! ) + lighting.ambientlight *= 100; + + // model and frame independant + IEngineStudio.StudioSetupLighting (&lighting); + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + + // get remap colors + m_nTopColor = m_pPlayerInfo->topcolor; + if (m_nTopColor < 0) + m_nTopColor = 0; + if (m_nTopColor > 360) + m_nTopColor = 360; + m_nBottomColor = m_pPlayerInfo->bottomcolor; + if (m_nBottomColor < 0) + m_nBottomColor = 0; + if (m_nBottomColor > 360) + m_nBottomColor = 360; + + IEngineStudio.StudioSetRemapColors( m_nTopColor, m_nBottomColor ); + + StudioRenderModel( ); + m_pPlayerInfo = NULL; + + if ( pplayer->weaponmodel ) + { + cl_entity_t saveent = *m_pCurrentEntity; + + model_t *pweaponmodel = IEngineStudio.GetModelByIndex( pplayer->weaponmodel ); + + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (pweaponmodel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + + //Animate p_ model. + if ( strstr ( pweaponmodel->name, "p_nail2.mdl" ) ) + { + if ( m_pCurrentEntity->curstate.sequence == 50 ) + m_pCurrentEntity->curstate.sequence = 1; + else + m_pCurrentEntity->curstate.sequence = 0; + + StudioSetupBones( ); + } + else if ( strstr ( pweaponmodel->name, "p_rock.mdl" ) ) + { + if ( m_pCurrentEntity->curstate.sequence == 46 ) + m_pCurrentEntity->curstate.sequence = 1; + else + m_pCurrentEntity->curstate.sequence = 0; + + StudioSetupBones( ); + } + + StudioMergeBones( pweaponmodel); + + IEngineStudio.StudioSetupLighting (&lighting); + + StudioRenderModel( ); + + StudioCalcAttachments( ); + + *m_pCurrentEntity = saveent; + } + } + + return 1; +} + +/* +==================== +StudioCalcAttachments + +==================== +*/ +void CStudioModelRenderer::StudioCalcAttachments( void ) +{ + int i; + mstudioattachment_t *pattachment; + + if ( m_pStudioHeader->numattachments > 4 ) + { + gEngfuncs.Con_DPrintf( "Too many attachments on %s\n", m_pCurrentEntity->model->name ); + exit( -1 ); + } + + // calculate attachment points + pattachment = (mstudioattachment_t *)((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex); + for (i = 0; i < m_pStudioHeader->numattachments; i++) + { + VectorTransform( pattachment[i].org, (*m_plighttransform)[pattachment[i].bone], m_pCurrentEntity->attachment[i] ); + } +} + +/* +==================== +StudioRenderModel + +==================== +*/ +void CStudioModelRenderer::StudioRenderModel( void ) +{ + IEngineStudio.SetChromeOrigin(); + IEngineStudio.SetForceFaceFlags( 0 ); + + if ( m_pCurrentEntity->curstate.renderfx == kRenderFxGlowShell ) + { + if ( strstr ( m_pCurrentEntity->model->name, "v_" ) ) + { + if ( m_pCurrentEntity->curstate.renderamt != 5 ) + { + m_pCurrentEntity->curstate.renderfx = kRenderFxNone; + StudioRenderFinal( ); + } + } + else + { + if ( m_pCurrentEntity->curstate.renderamt != 5 && m_pCurrentEntity->curstate.rendermode != kRenderTransColor ) + { + m_pCurrentEntity->curstate.renderfx = kRenderFxNone; + StudioRenderFinal( ); + } + } + + if ( !IEngineStudio.IsHardware() ) + { + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + } + + IEngineStudio.SetForceFaceFlags( STUDIO_NF_CHROME ); + + gEngfuncs.pTriAPI->SpriteTexture( m_pChromeSprite, 0 ); + m_pCurrentEntity->curstate.renderfx = kRenderFxGlowShell; + + StudioRenderFinal( ); + if ( !IEngineStudio.IsHardware() ) + { + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); + } + + } + else + { + StudioRenderFinal( ); + } +} + +/* +==================== +StudioRenderFinal_Software + +==================== +*/ +void CStudioModelRenderer::StudioRenderFinal_Software( void ) +{ + int i; + + // Note, rendermode set here has effect in SW + IEngineStudio.SetupRenderer( 0 ); + + if (m_pCvarDrawEntities->value == 2) + { + IEngineStudio.StudioDrawBones( ); + } + else if (m_pCvarDrawEntities->value == 3) + { + IEngineStudio.StudioDrawHulls( ); + } + else + { + for (i=0 ; i < m_pStudioHeader->numbodyparts ; i++) + { + IEngineStudio.StudioSetupModel( i, (void **)&m_pBodyPart, (void **)&m_pSubModel ); + IEngineStudio.StudioDrawPoints( ); + } + } + + if (m_pCvarDrawEntities->value == 4) + { + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + IEngineStudio.StudioDrawHulls( ); + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); + } + + if (m_pCvarDrawEntities->value == 5) + { + IEngineStudio.StudioDrawAbsBBox( ); + } + + IEngineStudio.RestoreRenderer(); +} + +/* +==================== +StudioRenderFinal_Hardware + +==================== +*/ +void CStudioModelRenderer::StudioRenderFinal_Hardware( void ) +{ + int i; + int rendermode; + + rendermode = IEngineStudio.GetForceFaceFlags() ? kRenderTransAdd : m_pCurrentEntity->curstate.rendermode; + IEngineStudio.SetupRenderer( rendermode ); + + if (m_pCvarDrawEntities->value == 2) + { + IEngineStudio.StudioDrawBones(); + } + else if (m_pCvarDrawEntities->value == 3) + { + IEngineStudio.StudioDrawHulls(); + } + else + { + for (i=0 ; i < m_pStudioHeader->numbodyparts ; i++) + { + IEngineStudio.StudioSetupModel( i, (void **)&m_pBodyPart, (void **)&m_pSubModel ); + + if (m_fDoInterp) + { + // interpolation messes up bounding boxes. + m_pCurrentEntity->trivial_accept = 0; + } + + IEngineStudio.GL_SetRenderMode( rendermode ); + IEngineStudio.StudioDrawPoints(); + IEngineStudio.GL_StudioDrawShadow(); + } + } + + if ( m_pCvarDrawEntities->value == 4 ) + { + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + IEngineStudio.StudioDrawHulls( ); + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); + } + + IEngineStudio.RestoreRenderer(); +} + +/* +==================== +StudioRenderFinal + +==================== +*/ +void CStudioModelRenderer::StudioRenderFinal(void) +{ + if ( IEngineStudio.IsHardware() ) + { + StudioRenderFinal_Hardware(); + } + else + { + StudioRenderFinal_Software(); + } +} + diff --git a/hlsdk/dmc/cl_dll/StudioModelRenderer.h b/hlsdk/dmc/cl_dll/StudioModelRenderer.h new file mode 100644 index 00000000..8f1427ff --- /dev/null +++ b/hlsdk/dmc/cl_dll/StudioModelRenderer.h @@ -0,0 +1,189 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined ( STUDIOMODELRENDERER_H ) +#define STUDIOMODELRENDERER_H +#if defined( _WIN32 ) +#pragma once +#endif + +/* +==================== +CStudioModelRenderer + +==================== +*/ +class CStudioModelRenderer +{ +public: + // Construction/Destruction + CStudioModelRenderer( void ); + virtual ~CStudioModelRenderer( void ); + + // Initialization + virtual void Init( void ); + +public: + // Public Interfaces + virtual int StudioDrawModel ( int flags ); + virtual int StudioDrawPlayer ( int flags, struct entity_state_s *pplayer ); + +public: + // Local interfaces + // + + // Look up animation data for sequence + virtual mstudioanim_t *StudioGetAnim ( model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc ); + + // Interpolate model position and angles and set up matrices + virtual void StudioSetUpTransform (int trivial_accept); + + // Set up model bone positions + virtual void StudioSetupBones ( void ); + + // Find final attachment points + virtual void StudioCalcAttachments ( void ); + + // Save bone matrices and names + virtual void StudioSaveBones( void ); + + // Merge cached bones with current bones for model + virtual void StudioMergeBones ( model_t *m_pSubModel ); + + // Determine interpolation fraction + virtual float StudioEstimateInterpolant( void ); + + // Determine current frame for rendering + virtual float StudioEstimateFrame ( mstudioseqdesc_t *pseqdesc ); + + // Apply special effects to transform matrix + virtual void StudioFxTransform( cl_entity_t *ent, float transform[3][4] ); + + // Spherical interpolation of bones + virtual void StudioSlerpBones ( vec4_t q1[], float pos1[][3], vec4_t q2[], float pos2[][3], float s ); + + // Compute bone adjustments ( bone controllers ) + virtual void StudioCalcBoneAdj ( float dadt, float *adj, const byte *pcontroller1, const byte *pcontroller2, byte mouthopen ); + + // Get bone quaternions + virtual void StudioCalcBoneQuaterion ( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *q ); + + // Get bone positions + virtual void StudioCalcBonePosition ( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *pos ); + + // Compute rotations + virtual void StudioCalcRotations ( float pos[][3], vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f ); + + // Send bones and verts to renderer + virtual void StudioRenderModel ( void ); + + // Finalize rendering + virtual void StudioRenderFinal (void); + + // GL&D3D vs. Software renderer finishing functions + virtual void StudioRenderFinal_Software ( void ); + virtual void StudioRenderFinal_Hardware ( void ); + + // Player specific data + // Determine pitch and blending amounts for players + virtual void StudioPlayerBlend ( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch ); + + // Estimate gait frame for player + virtual void StudioEstimateGait ( entity_state_t *pplayer ); + + // Process movement of player + virtual void StudioProcessGait ( entity_state_t *pplayer ); + +public: + + // Client clock + double m_clTime; + // Old Client clock + double m_clOldTime; + + // Do interpolation? + int m_fDoInterp; + // Do gait estimation? + int m_fGaitEstimation; + + // Current render frame # + int m_nFrameCount; + + // Cvars that studio model code needs to reference + // + // Use high quality models? + cvar_t *m_pCvarHiModels; + // Developer debug output desired? + cvar_t *m_pCvarDeveloper; + // Draw entities bone hit boxes, etc? + cvar_t *m_pCvarDrawEntities; + + // The entity which we are currently rendering. + cl_entity_t *m_pCurrentEntity; + + // The model for the entity being rendered + model_t *m_pRenderModel; + + // Player info for current player, if drawing a player + player_info_t *m_pPlayerInfo; + + // The index of the player being drawn + int m_nPlayerIndex; + + // The player's gait movement + float m_flGaitMovement; + + // Pointer to header block for studio model data + studiohdr_t *m_pStudioHeader; + + // Pointers to current body part and submodel + mstudiobodyparts_t *m_pBodyPart; + mstudiomodel_t *m_pSubModel; + + // Palette substition for top and bottom of model + int m_nTopColor; + int m_nBottomColor; + + // + // Sprite model used for drawing studio model chrome + model_t *m_pChromeSprite; + + // Caching + // Number of bones in bone cache + int m_nCachedBones; + // Names of cached bones + char m_nCachedBoneNames[ MAXSTUDIOBONES ][ 32 ]; + // Cached bone & light transformation matrices + float m_rgCachedBoneTransform [ MAXSTUDIOBONES ][ 3 ][ 4 ]; + float m_rgCachedLightTransform[ MAXSTUDIOBONES ][ 3 ][ 4 ]; + + // Software renderer scale factors + float m_fSoftwareXScale, m_fSoftwareYScale; + + // Current view vectors and render origin + float m_vUp[ 3 ]; + float m_vRight[ 3 ]; + float m_vNormal[ 3 ]; + + float m_vRenderOrigin[ 3 ]; + + // Model render counters ( from engine ) + int *m_pStudioModelCount; + int *m_pModelsDrawn; + + // Matrices + // Model to world transformation + float (*m_protationmatrix)[ 3 ][ 4 ]; + // Model to view transformation + float (*m_paliastransform)[ 3 ][ 4 ]; + + // Concatenated bone and light transforms + float (*m_pbonetransform) [ MAXSTUDIOBONES ][ 3 ][ 4 ]; + float (*m_plighttransform)[ MAXSTUDIOBONES ][ 3 ][ 4 ]; +}; + +#endif // STUDIOMODELRENDERER_H \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/ammo.cpp b/hlsdk/dmc/cl_dll/ammo.cpp new file mode 100644 index 00000000..a6077394 --- /dev/null +++ b/hlsdk/dmc/cl_dll/ammo.cpp @@ -0,0 +1,1131 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Ammo.cpp +// +// implementation of CHudAmmo class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "ammohistory.h" + +WEAPON *gpActiveSel; // NULL means off, 1 means just the menu bar, otherwise + // this points to the active weapon menu item +WEAPON *gpLastSel; // Last weapon menu selection + +client_sprite_t *GetSpriteList(client_sprite_t *pList, const char *psz, int iRes, int iCount); + +WeaponsResource gWR; + +int g_weaponselect = 0; + +void WeaponsResource :: LoadAllWeaponSprites( void ) +{ + for (int i = 0; i < MAX_WEAPONS; i++) + { + if ( rgWeapons[i].iId ) + LoadWeaponSprites( &rgWeapons[i] ); + } +} + +int WeaponsResource :: CountAmmo( int iId ) +{ + if ( iId < 0 ) + return 0; + + //Fixes some crashes. + if ( iId > MAX_AMMO_TYPES ) + return 0; + + return riAmmo[iId]; +} + +int WeaponsResource :: HasAmmo( WEAPON *p ) +{ + if ( !p ) + return FALSE; + + // weapons with no max ammo can always be selected + if ( p->iMax1 == -1 ) + return TRUE; + + return (p->iAmmoType == -1) || p->iClip > 0 || CountAmmo(p->iAmmoType) + || CountAmmo(p->iAmmo2Type) || ( p->iFlags & WEAPON_FLAGS_SELECTONEMPTY ); +} + + +void WeaponsResource :: LoadWeaponSprites( WEAPON *pWeapon ) +{ + int i, iRes; + + if (ScreenWidth < 640) + iRes = 320; + else + iRes = 640; + + char sz[128]; + + if ( !pWeapon ) + return; + + memset( &pWeapon->rcActive, 0, sizeof(wrect_t) ); + memset( &pWeapon->rcInactive, 0, sizeof(wrect_t) ); + memset( &pWeapon->rcAmmo, 0, sizeof(wrect_t) ); + memset( &pWeapon->rcAmmo2, 0, sizeof(wrect_t) ); + pWeapon->hInactive = 0; + pWeapon->hActive = 0; + pWeapon->hAmmo = 0; + pWeapon->hAmmo2 = 0; + + sprintf(sz, "sprites/%s.txt", pWeapon->szName); + client_sprite_t *pList = SPR_GetList(sz, &i); + + if (!pList) + return; + + client_sprite_t *p; + + p = GetSpriteList( pList, "crosshair", iRes, i ); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hCrosshair = SPR_Load(sz); + pWeapon->rcCrosshair = p->rc; + } + else + pWeapon->hCrosshair = NULL; + + p = GetSpriteList(pList, "autoaim", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hAutoaim = SPR_Load(sz); + pWeapon->rcAutoaim = p->rc; + } + else + pWeapon->hAutoaim = 0; + + p = GetSpriteList( pList, "zoom", iRes, i ); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hZoomedCrosshair = SPR_Load(sz); + pWeapon->rcZoomedCrosshair = p->rc; + } + else + { + pWeapon->hZoomedCrosshair = pWeapon->hCrosshair; //default to non-zoomed crosshair + pWeapon->rcZoomedCrosshair = pWeapon->rcCrosshair; + } + + p = GetSpriteList(pList, "zoom_autoaim", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hZoomedAutoaim = SPR_Load(sz); + pWeapon->rcZoomedAutoaim = p->rc; + } + else + { + pWeapon->hZoomedAutoaim = pWeapon->hZoomedCrosshair; //default to zoomed crosshair + pWeapon->rcZoomedAutoaim = pWeapon->rcZoomedCrosshair; + } + + p = GetSpriteList(pList, "weapon", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hInactive = SPR_Load(sz); + pWeapon->rcInactive = p->rc; + + gHR.iHistoryGap = max( gHR.iHistoryGap, pWeapon->rcActive.bottom - pWeapon->rcActive.top ); + } + else + pWeapon->hInactive = 0; + + p = GetSpriteList(pList, "weapon_s", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hActive = SPR_Load(sz); + pWeapon->rcActive = p->rc; + } + else + pWeapon->hActive = 0; + + p = GetSpriteList(pList, "ammo", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hAmmo = SPR_Load(sz); + pWeapon->rcAmmo = p->rc; + + gHR.iHistoryGap = max( gHR.iHistoryGap, pWeapon->rcActive.bottom - pWeapon->rcActive.top ); + } + else + pWeapon->hAmmo = 0; + + p = GetSpriteList(pList, "ammo2", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hAmmo2 = SPR_Load(sz); + pWeapon->rcAmmo2 = p->rc; + + gHR.iHistoryGap = max( gHR.iHistoryGap, pWeapon->rcActive.bottom - pWeapon->rcActive.top ); + } + else + pWeapon->hAmmo2 = 0; + +} + +// Returns the first weapon for a given slot. +WEAPON *WeaponsResource :: GetFirstPos( int iSlot ) +{ + WEAPON *pret = NULL; + + for (int i = 0; i < MAX_WEAPON_POSITIONS; i++) + { + if ( rgSlots[iSlot][i] && HasAmmo( rgSlots[iSlot][i] ) ) + { + pret = rgSlots[iSlot][i]; + break; + } + } + + return pret; +} + + +WEAPON* WeaponsResource :: GetNextActivePos( int iSlot, int iSlotPos ) +{ + if ( iSlotPos >= MAX_WEAPON_POSITIONS || iSlot >= MAX_WEAPON_SLOTS ) + return NULL; + + WEAPON *p = gWR.rgSlots[ iSlot ][ iSlotPos+1 ]; + + if ( !p || !gWR.HasAmmo(p) ) + return GetNextActivePos( iSlot, iSlotPos + 1 ); + + return p; +} + + +int giBucketHeight, giBucketWidth, giABHeight, giABWidth; // Ammo Bar width and height + +HSPRITE ghsprBuckets; // Sprite for top row of weapons menu + +DECLARE_MESSAGE(m_Ammo, CurWeapon ); // Current weapon and clip +DECLARE_MESSAGE(m_Ammo, WeaponList); // new weapon type +DECLARE_MESSAGE(m_Ammo, AmmoX); // update known ammo type's count +DECLARE_MESSAGE(m_Ammo, AmmoPickup); // flashes an ammo pickup record +DECLARE_MESSAGE(m_Ammo, WeapPickup); // flashes a weapon pickup record +DECLARE_MESSAGE(m_Ammo, HideWeapon); // hides the weapon, ammo, and crosshair displays temporarily +DECLARE_MESSAGE(m_Ammo, ItemPickup); + +DECLARE_COMMAND(m_Ammo, Slot1); +DECLARE_COMMAND(m_Ammo, Slot2); +DECLARE_COMMAND(m_Ammo, Slot3); +DECLARE_COMMAND(m_Ammo, Slot4); +DECLARE_COMMAND(m_Ammo, Slot5); +DECLARE_COMMAND(m_Ammo, Slot6); +DECLARE_COMMAND(m_Ammo, Slot7); +DECLARE_COMMAND(m_Ammo, Slot8); +DECLARE_COMMAND(m_Ammo, Slot9); +DECLARE_COMMAND(m_Ammo, Slot10); +DECLARE_COMMAND(m_Ammo, Close); +DECLARE_COMMAND(m_Ammo, NextWeapon); +DECLARE_COMMAND(m_Ammo, PrevWeapon); + +// width of ammo fonts +#define AMMO_SMALL_WIDTH 10 +#define AMMO_LARGE_WIDTH 20 + +#define HISTORY_DRAW_TIME "5" + +int CHudAmmo::Init(void) +{ + gHUD.AddHudElem(this); + + HOOK_MESSAGE(CurWeapon); + HOOK_MESSAGE(WeaponList); + HOOK_MESSAGE(AmmoPickup); + HOOK_MESSAGE(WeapPickup); + HOOK_MESSAGE(ItemPickup); + HOOK_MESSAGE(HideWeapon); + HOOK_MESSAGE(AmmoX); + + HOOK_COMMAND("slot1", Slot1); + HOOK_COMMAND("slot2", Slot2); + HOOK_COMMAND("slot3", Slot3); + HOOK_COMMAND("slot4", Slot4); + HOOK_COMMAND("slot5", Slot5); + HOOK_COMMAND("slot6", Slot6); + HOOK_COMMAND("slot7", Slot7); + HOOK_COMMAND("slot8", Slot8); + HOOK_COMMAND("slot9", Slot9); + HOOK_COMMAND("slot10", Slot10); + HOOK_COMMAND("cancelselect", Close); + HOOK_COMMAND("invnext", NextWeapon); + HOOK_COMMAND("invprev", PrevWeapon); + + Reset(); + + CVAR_CREATE( "hud_drawhistory_time", HISTORY_DRAW_TIME, 0 ); + CVAR_CREATE( "hud_fastswitch", "0", FCVAR_ARCHIVE ); // controls whether or not weapons can be selected in one keypress + + m_iFlags |= HUD_ACTIVE; //!!! + + gWR.Init(); + gHR.Init(); + + return 1; +}; + +void CHudAmmo::Reset(void) +{ + m_fFade = 0; + m_iFlags |= HUD_ACTIVE; //!!! + + gpActiveSel = NULL; + gHUD.m_iHideHUDDisplay = 0; + + gWR.Reset(); + gHR.Reset(); + + // VidInit(); + +} + +int CHudAmmo::VidInit(void) +{ + // Load sprites for buckets (top row of weapon menu) + m_HUD_bucket0 = gHUD.GetSpriteIndex( "bucket1" ); + m_HUD_selection = gHUD.GetSpriteIndex( "selection" ); + + ghsprBuckets = gHUD.GetSprite(m_HUD_bucket0); + giBucketWidth = gHUD.GetSpriteRect(m_HUD_bucket0).right - gHUD.GetSpriteRect(m_HUD_bucket0).left; + giBucketHeight = gHUD.GetSpriteRect(m_HUD_bucket0).bottom - gHUD.GetSpriteRect(m_HUD_bucket0).top; + + gHR.iHistoryGap = max( gHR.iHistoryGap, gHUD.GetSpriteRect(m_HUD_bucket0).bottom - gHUD.GetSpriteRect(m_HUD_bucket0).top); + + // If we've already loaded weapons, let's get new sprites + gWR.LoadAllWeaponSprites(); + + if (ScreenWidth >= 640) + { + giABWidth = 20; + giABHeight = 4; + } + else + { + giABWidth = 10; + giABHeight = 2; + } + + return 1; +} + +// +// Think: +// Used for selection of weapon menu item. +// +void CHudAmmo::Think(void) +{ + if ( gHUD.m_fPlayerDead ) + return; + + if ( gHUD.m_iWeaponBits != gWR.iOldWeaponBits ) + { + gWR.iOldWeaponBits = gHUD.m_iWeaponBits; + + for (int i = MAX_WEAPONS-1; i > 0; i-- ) + { + WEAPON *p = gWR.GetWeapon(i); + + if ( p ) + { + if ( gHUD.m_iWeaponBits & p->iId ) + gWR.PickupWeapon( p ); + else + gWR.DropWeapon( p ); + } + } + } + + if (!gpActiveSel) + return; + + // has the player selected one? + if (gHUD.m_iKeyBits & IN_ATTACK) + { + if (gpActiveSel != (WEAPON *)1) + { + //ServerCmd(gpActiveSel->szName); + g_weaponselect = gpActiveSel->iSlot; + } + + gpLastSel = gpActiveSel; + gpActiveSel = NULL; + gHUD.m_iKeyBits &= ~IN_ATTACK; + + PlaySound("common/wpn_select.wav", 1); + } + +} + +// +// Helper function to return a Ammo pointer from id +// + +HSPRITE* WeaponsResource :: GetAmmoPicFromWeapon( int iAmmoId, wrect_t& rect ) +{ + for ( int i = 0; i < MAX_WEAPONS; i++ ) + { + if ( rgWeapons[i].iAmmoType == iAmmoId ) + { + rect = rgWeapons[i].rcAmmo; + return &rgWeapons[i].hAmmo; + } + else if ( rgWeapons[i].iAmmo2Type == iAmmoId ) + { + rect = rgWeapons[i].rcAmmo2; + return &rgWeapons[i].hAmmo2; + } + } + + return NULL; +} + +void WeaponsResource :: SelectSlot( int iSlot, int fAdvance, int iDirection ) +{ + if ( gHUD.m_Menu.m_fMenuDisplayed && (fAdvance == FALSE) && (iDirection == 1) ) + { // menu is overriding slot use commands + gHUD.m_Menu.SelectMenuItem( iSlot ); + return; + } + + if ( iSlot > MAX_WEAPON_SLOTS ) + return; + + if ( gHUD.m_fPlayerDead || gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL ) ) + return; + + WEAPON *p = NULL; + bool fastSwitch = CVAR_GET_FLOAT( "hud_fastswitch" ) != 0; + + if ( (gpActiveSel == NULL) || (gpActiveSel == (WEAPON *)1) || (iSlot != gpActiveSel->iSlot) ) + { + PlaySound( "common/wpn_hudon.wav", 1 ); + p = GetFirstPos( iSlot ); + + if ( p && fastSwitch ) // check for fast weapon switch mode + { + // if fast weapon switch is on, then weapons can be selected in a single keypress + // but only if there is only one item in the bucket + WEAPON *p2 = GetNextActivePos( p->iSlot, p->iSlotPos ); + if ( !p2 ) + { // only one active item in bucket, so change directly to weapon + //ServerCmd( p->szName ); + g_weaponselect = iSlot; + return; + } + } + } + else + { + PlaySound("common/wpn_moveselect.wav", 1); + if ( gpActiveSel ) + p = GetNextActivePos( gpActiveSel->iSlot, gpActiveSel->iSlotPos ); + if ( !p ) + p = GetFirstPos( iSlot ); + } + + + if ( !p ) // no selection found + { + // just display the weapon list, unless fastswitch is on just ignore it + if ( !fastSwitch ) + gpActiveSel = (WEAPON *)1; + else + gpActiveSel = NULL; + } + else + gpActiveSel = p; +} + +//------------------------------------------------------------------------ +// Message Handlers +//------------------------------------------------------------------------ + +// +// AmmoX -- Update the count of a known type of ammo +// +int CHudAmmo::MsgFunc_AmmoX(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + int iIndex = READ_BYTE(); + int iCount = READ_BYTE(); + + gWR.SetAmmo( iIndex, abs(iCount) ); + + return 1; +} + +int CHudAmmo::MsgFunc_AmmoPickup( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + int iIndex = READ_BYTE(); + int iCount = READ_BYTE(); + + // Add ammo to the history + gHR.AddToHistory( HISTSLOT_AMMO, iIndex, abs(iCount) ); + + return 1; +} + +int CHudAmmo::MsgFunc_WeapPickup( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + int iIndex = READ_BYTE(); + + // Add the weapon to the history + gHR.AddToHistory( HISTSLOT_WEAP, iIndex ); + + return 1; +} + +int CHudAmmo::MsgFunc_ItemPickup( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + const char *szName = READ_STRING(); + + // Add the weapon to the history + gHR.AddToHistory( HISTSLOT_ITEM, szName ); + + return 1; +} + + +int CHudAmmo::MsgFunc_HideWeapon( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + gHUD.m_iHideHUDDisplay = READ_BYTE(); + + if ( gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL ) ) + { + static wrect_t nullrc; + gpActiveSel = NULL; + SetCrosshair( 0, nullrc, 0, 0, 0 ); + } + + return 1; +} + +// +// CurWeapon: Update hud state with the current weapon and clip count. Ammo +// counts are updated with AmmoX. Server assures that the Weapon ammo type +// numbers match a real ammo type. +// +int CHudAmmo::MsgFunc_CurWeapon(const char *pszName, int iSize, void *pbuf ) +{ + static wrect_t nullrc; + int fOnTarget = FALSE; + + + BEGIN_READ( pbuf, iSize ); + + int iState = READ_BYTE(); + int iId = READ_BYTE(); + int iClip = READ_CHAR(); + + // detect if we're also on target + if ( iState > 1 ) + { + fOnTarget = TRUE; + } + + if ( iId < 1 ) + { + SetCrosshair(0, nullrc, 0, 0, 0); + return 0; + } + + // Is player dead??? + if ((iId == -1) && (iClip == -1)) + { + gHUD.m_fPlayerDead = TRUE; + gpActiveSel = NULL; + return 1; + } + gHUD.m_fPlayerDead = FALSE; + + WEAPON *pWeapon = gWR.GetWeapon( iId ); + + if ( !pWeapon ) + return 0; + + if ( iClip < -1 ) + pWeapon->iClip = abs(iClip); + else + pWeapon->iClip = iClip; + + + if ( iState == 0 ) // we're not the current weapon, so update no more + return 1; + + m_pWeapon = pWeapon; + + m_fFade = 200.0f; //!!! + m_iFlags |= HUD_ACTIVE; + + return 1; +} + +// +// WeaponList -- Tells the hud about a new weapon type. +// +int CHudAmmo::MsgFunc_WeaponList(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + WEAPON Weapon; + + strcpy( Weapon.szName, READ_STRING() ); + Weapon.iAmmoType = (int)READ_CHAR(); + + Weapon.iMax1 = READ_BYTE(); + if (Weapon.iMax1 == 255) + Weapon.iMax1 = -1; + + Weapon.iAmmo2Type = READ_BYTE(); + Weapon.iMax2 = READ_BYTE(); + if (Weapon.iMax2 == 255) + Weapon.iMax2 = -1; + + Weapon.iSlot = READ_BYTE(); + Weapon.iSlotPos = READ_BYTE(); + Weapon.iId = READ_LONG(); + + Weapon.iFlags = READ_BYTE(); + + Weapon.iClip = 0; + + gWR.AddWeapon( &Weapon ); + + return 1; + +} + +//------------------------------------------------------------------------ +// Command Handlers +//------------------------------------------------------------------------ + +void CHudAmmo::UserCmd_Slot1(void) +{ + gWR.SelectSlot(1, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot2(void) +{ + gWR.SelectSlot(2, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot3(void) +{ + gWR.SelectSlot(3, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot4(void) +{ + gWR.SelectSlot(4, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot5(void) +{ + gWR.SelectSlot(5, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot6(void) +{ + gWR.SelectSlot(6, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot7(void) +{ + gWR.SelectSlot(7, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot8(void) +{ + gWR.SelectSlot(8, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot9(void) +{ + gWR.SelectSlot(9, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot10(void) +{ + gWR.SelectSlot(10, FALSE, 1); +} + +void CHudAmmo::UserCmd_Close(void) +{ + if (gpActiveSel) + { + gpLastSel = gpActiveSel; + gpActiveSel = NULL; + PlaySound("common/wpn_hudoff.wav", 1); + } + else + ClientCmd("escape"); +} + + +// Selects the next item in the weapon menu +void CHudAmmo::UserCmd_NextWeapon(void) +{ + if ( gHUD.m_fPlayerDead || (gHUD.m_iHideHUDDisplay & (HIDEHUD_WEAPONS | HIDEHUD_ALL)) ) + return; + + if ( !gpActiveSel || gpActiveSel == (WEAPON*)1 ) + gpActiveSel = m_pWeapon; + + int pos = 0; + int slot = 0; + if ( gpActiveSel ) + { + pos = gpActiveSel->iSlotPos + 1; + slot = gpActiveSel->iSlot; + } + + for ( int loop = 0; loop <= 1; loop++ ) + { + for ( ; slot < MAX_WEAPON_SLOTS + 1; slot++ ) + { + for ( ; pos < MAX_WEAPON_POSITIONS; pos++ ) + { + WEAPON *wsp = gWR.GetWeaponSlot( slot, pos ); + + if ( wsp && gWR.HasAmmo(wsp) ) + { + gpActiveSel = wsp; + return; + } + } + + pos = 0; + } + + slot = 0; // start looking from the first slot again + } + + gpActiveSel = NULL; +} + +// Selects the previous item in the menu +void CHudAmmo::UserCmd_PrevWeapon(void) +{ + if ( gHUD.m_fPlayerDead || (gHUD.m_iHideHUDDisplay & (HIDEHUD_WEAPONS | HIDEHUD_ALL)) ) + return; + + if ( !gpActiveSel || gpActiveSel == (WEAPON*)1 ) + gpActiveSel = m_pWeapon; + + int pos = MAX_WEAPON_POSITIONS-1; + int slot = MAX_WEAPON_SLOTS; + if ( gpActiveSel ) + { + pos = gpActiveSel->iSlotPos - 1; + slot = gpActiveSel->iSlot; + } + + for ( int loop = 0; loop <= 1; loop++ ) + { + for ( ; slot >= 0; slot-- ) + { + for ( ; pos >= 0; pos-- ) + { + WEAPON *wsp = gWR.GetWeaponSlot( slot, pos ); + + if ( wsp && gWR.HasAmmo(wsp) ) + { + gpActiveSel = wsp; + return; + } + } + + pos = MAX_WEAPON_POSITIONS-1; + } + + slot = MAX_WEAPON_SLOTS; + } + + gpActiveSel = NULL; +} + + + +//------------------------------------------------------------------------- +// Drawing code +//------------------------------------------------------------------------- + +int CHudAmmo::Draw(float flTime) +{ + int a, x, y, r, g, b; + int AmmoWidth; + + int iCrossX; + int iCrossY; + int iCrossLength; + char *chCrossHair = "+"; // Heh + + /*if (!(gHUD.m_iWeaponBits & (1<<(WEAPON_SUIT)) )) + return 1;*/ + + if ( (gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL )) ) + return 1; + + // Draw Weapon Menu + DrawWList(flTime); + + // Draw ammo pickup history + gHR.DrawAmmoHistory( flTime ); + + if (!(m_iFlags & HUD_ACTIVE)) + return 0; + + if (!m_pWeapon) + return 0; + + WEAPON *pw = m_pWeapon; // shorthand + + // SPR_Draw Ammo + 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; + + a = (int) max( MIN_ALPHA, m_fFade ); + + if (m_fFade > 0) + m_fFade -= (gHUD.m_flTimeDelta * 20); + + UnpackRGB(r,g,b, RGB_YELLOWISH); + + ScaleColors(r, g, b, a ); + + // Does this weapon have a clip? + y = ScreenHeight - gHUD.m_iFontHeight - gHUD.m_iFontHeight/2; + + /******************* DRAW CROSSHAIR *********************/ + iCrossLength = gHUD.m_scrinfo.charWidths[ *chCrossHair ]; + iCrossY = ScreenHeight / 2 - gHUD.m_scrinfo.iCharHeight / 2; + iCrossX = ScreenWidth / 2 - iCrossLength / 2; + + gHUD.DrawHudString( iCrossX, iCrossY, iCrossX + 50, chCrossHair, 170, 170, 170 ); + /******************* DRAW CROSSHAIR *********************/ + + // Does weapon have any ammo at all? + if (m_pWeapon->iAmmoType > 0) + { + int iIconWidth = m_pWeapon->rcAmmo.right - m_pWeapon->rcAmmo.left; + + if (pw->iClip >= 0) + { + x = ScreenWidth - (8 * AmmoWidth) - iIconWidth; + + UnpackRGB(r,g,b, RGB_YELLOWISH); + // GL Seems to need this + ScaleColors( r, g, b, a ); + m_iNumberXPosition = x = gHUD.DrawHudNumber(x, y, iFlags | DHN_3DIGITS, gWR.CountAmmo(pw->iAmmoType), r, g, b); + } + + // 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); + + m_iXPosition = x; + } + + return 1; +} + + +// +// Draws the ammo bar on the hud +// +int DrawBar(int x, int y, int width, int height, float f) +{ + int r, g, b; + + if (f < 0) + f = 0; + if (f > 1) + f = 1; + + if (f) + { + int w = f * width; + + // Always show at least one pixel if we have ammo. + if (w <= 0) + w = 1; + UnpackRGB(r, g, b, RGB_GREENISH); + FillRGBA(x, y, w, height, r, g, b, 255); + x += w; + width -= w; + } + + UnpackRGB(r, g, b, RGB_YELLOWISH); + + FillRGBA(x, y, width, height, r, g, b, 128); + + return (x + width); +} + + + +void DrawAmmoBar(WEAPON *p, int x, int y, int width, int height) +{ + if ( !p ) + return; + + if (p->iAmmoType != -1) + { + if (!gWR.CountAmmo(p->iAmmoType)) + return; + + float f = (float)gWR.CountAmmo(p->iAmmoType)/(float)p->iMax1; + + x = DrawBar(x, y, width, height, f); + } +} + + + + +// +// Draw Weapon Menu +// +int CHudAmmo::DrawWList(float flTime) +{ + int r,g,b,x,y,a,i; + + if ( !gpActiveSel ) + return 0; + + int iActiveSlot; + + if ( gpActiveSel == (WEAPON *)1 ) + iActiveSlot = -1; // current slot has no weapons + else + iActiveSlot = gpActiveSel->iSlot; + + x = 10; //!!! + y = 10; //!!! + + + // Ensure that there are available choices in the active slot + if ( iActiveSlot > 0 ) + { + if ( !gWR.GetFirstPos( iActiveSlot ) ) + { + gpActiveSel = (WEAPON *)1; + iActiveSlot = -1; + } + } + + // Draw top line + for ( i = 0; i < MAX_WEAPON_SLOTS; i++ ) + { + int iWidth; + + UnpackRGB(r,g,b, RGB_YELLOWISH); + + if ( iActiveSlot == i ) + a = 255; + else + a = 192; + + ScaleColors(r, g, b, 255); + SPR_Set(gHUD.GetSprite(m_HUD_bucket0 + i), r, g, b ); + + // make active slot wide enough to accomodate gun pictures + if ( i == iActiveSlot ) + { + WEAPON *p = gWR.GetFirstPos(iActiveSlot); + + if ( p ) + iWidth = p->rcActive.right - p->rcActive.left; + } + else + iWidth = giBucketWidth; + + if ( i == iActiveSlot ) + SPR_DrawAdditive(0, x + 104, y, &gHUD.GetSpriteRect(m_HUD_bucket0 + i)); + else + SPR_DrawAdditive(0, x, y, &gHUD.GetSpriteRect(m_HUD_bucket0 + i)); + + x += iWidth + 5; + } + + + a = 128; //!!! + x = 10; + + // Draw all of the buckets + for (i = 1; i < MAX_WEAPON_SLOTS + 1; i++) + { + y = giBucketHeight + 10; + + // If this is the active slot, draw the bigger pictures, + // otherwise just draw boxes + if ( i == iActiveSlot ) + { + WEAPON *p = gWR.GetFirstPos( i ); + int iWidth = giBucketWidth; + if ( p ) + iWidth = p->rcActive.right - p->rcActive.left; + + for ( int iPos = 0; iPos < MAX_WEAPON_POSITIONS; iPos++ ) + { + p = gWR.GetWeaponSlot( i, iPos ); + + if ( !p || !p->iId ) + continue; + + UnpackRGB( r,g,b, RGB_YELLOWISH ); + + // if active, then we must have ammo. + + if ( gpActiveSel == p ) + { + if ( gWR.HasAmmo(p) ) + ScaleColors(r, g, b, 192); + else + { + 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); + } + else + { + // Draw Weapon if Red if no ammo + + if ( gWR.HasAmmo(p) ) + ScaleColors(r, g, b, 192); + else + { + UnpackRGB(r,g,b, RGB_REDISH); + ScaleColors(r, g, b, 128); + } + + SPR_Set( p->hInactive, r, g, b ); + SPR_DrawAdditive( 0, x, y, &p->rcInactive ); + } + + // Draw Ammo Bar + + DrawAmmoBar(p, x + giABWidth/2, y, giABWidth, giABHeight); + + y += p->rcActive.bottom - p->rcActive.top + 5; + } + + x += iWidth + 5; + + } + else + { + // Draw Row of weapons. + + UnpackRGB(r,g,b, RGB_YELLOWISH); + + for ( int iPos = 0; iPos < MAX_WEAPON_POSITIONS; iPos++ ) + { + WEAPON *p = gWR.GetWeaponSlot( i, iPos ); + + if ( !p || !p->iId ) + continue; + + if ( gWR.HasAmmo(p) ) + { + UnpackRGB(r,g,b, RGB_YELLOWISH); + a = 128; + } + else + { + UnpackRGB(r,g,b, RGB_REDISH); + a = 96; + } + + FillRGBA( x, y, giBucketWidth, giBucketHeight, r, g, b, a ); + + y += giBucketHeight + 5; + } + + x += giBucketWidth + 5; + } + } + + return 1; + +} + + +/* ================================= + GetSpriteList + +Finds and returns the matching +sprite name 'psz' and resolution 'iRes' +in the given sprite list 'pList' +iCount is the number of items in the pList +================================= */ +client_sprite_t *GetSpriteList(client_sprite_t *pList, const char *psz, int iRes, int iCount) +{ + if (!pList) + return NULL; + + int i = iCount; + client_sprite_t *p = pList; + + while(i--) + { + if ((!strcmp(psz, p->szName)) && (p->iRes == iRes)) + return p; + p++; + } + + return NULL; +} + + diff --git a/hlsdk/dmc/cl_dll/ammo.h b/hlsdk/dmc/cl_dll/ammo.h new file mode 100644 index 00000000..5e44065a --- /dev/null +++ b/hlsdk/dmc/cl_dll/ammo.h @@ -0,0 +1,62 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef __AMMO_H__ +#define __AMMO_H__ + +#define MAX_WEAPON_NAME 128 + + +#define WEAPON_FLAGS_SELECTONEMPTY 1 + +#define WEAPON_IS_ONTARGET 0x40 + +struct WEAPON +{ + char szName[MAX_WEAPON_NAME]; + int iAmmoType; + int iAmmo2Type; + int iMax1; + int iMax2; + int iSlot; + int iSlotPos; + int iFlags; + int iId; + int iClip; + + int iCount; // # of itesm in plist + + HSPRITE hActive; + wrect_t rcActive; + HSPRITE hInactive; + wrect_t rcInactive; + HSPRITE hAmmo; + wrect_t rcAmmo; + HSPRITE hAmmo2; + wrect_t rcAmmo2; + HSPRITE hCrosshair; + wrect_t rcCrosshair; + HSPRITE hAutoaim; + wrect_t rcAutoaim; + HSPRITE hZoomedCrosshair; + wrect_t rcZoomedCrosshair; + HSPRITE hZoomedAutoaim; + wrect_t rcZoomedAutoaim; +}; + +typedef int AMMO; + + +#endif \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/ammo_secondary.cpp b/hlsdk/dmc/cl_dll/ammo_secondary.cpp new file mode 100644 index 00000000..e2a2f591 --- /dev/null +++ b/hlsdk/dmc/cl_dll/ammo_secondary.cpp @@ -0,0 +1,159 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// ammo_secondary.cpp +// +// implementation of CHudAmmoSecondary class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE( m_AmmoSecondary, SecAmmoVal ); +DECLARE_MESSAGE( m_AmmoSecondary, SecAmmoIcon ); + +int CHudAmmoSecondary :: Init( void ) +{ + HOOK_MESSAGE( SecAmmoVal ); + HOOK_MESSAGE( SecAmmoIcon ); + + gHUD.AddHudElem(this); + m_HUD_ammoicon = 0; + + for ( int i = 0; i < MAX_SEC_AMMO_VALUES; i++ ) + m_iAmmoAmounts[i] = -1; // -1 means don't draw this value + + Reset(); + + return 1; +} + +void CHudAmmoSecondary :: Reset( void ) +{ + m_fFade = 0; +} + +int CHudAmmoSecondary :: VidInit( void ) +{ + return 1; +} + +int CHudAmmoSecondary :: Draw(float flTime) +{ + if ( (gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL )) ) + return 1; + + // draw secondary ammo icons above normal ammo readout + int a, x, y, r, g, b, AmmoWidth; + UnpackRGB( r, g, b, RGB_YELLOWISH ); + a = (int) max( MIN_ALPHA, m_fFade ); + if (m_fFade > 0) + m_fFade -= (gHUD.m_flTimeDelta * 20); // slowly lower alpha to fade out icons + ScaleColors( r, g, b, a ); + + AmmoWidth = gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left; + + y = ScreenHeight - (gHUD.m_iFontHeight*4); // this is one font height higher than the weapon ammo values + x = ScreenWidth - AmmoWidth; + + if ( m_HUD_ammoicon ) + { + // Draw the ammo icon + x -= (gHUD.GetSpriteRect(m_HUD_ammoicon).right - gHUD.GetSpriteRect(m_HUD_ammoicon).left); + y -= (gHUD.GetSpriteRect(m_HUD_ammoicon).top - gHUD.GetSpriteRect(m_HUD_ammoicon).bottom); + + SPR_Set( gHUD.GetSprite(m_HUD_ammoicon), r, g, b ); + SPR_DrawAdditive( 0, x, y, &gHUD.GetSpriteRect(m_HUD_ammoicon) ); + } + else + { // move the cursor by the '0' char instead, since we don't have an icon to work with + x -= AmmoWidth; + y -= (gHUD.GetSpriteRect(gHUD.m_HUD_number_0).top - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).bottom); + } + + // draw the ammo counts, in reverse order, from right to left + for ( int i = MAX_SEC_AMMO_VALUES-1; i >= 0; i-- ) + { + if ( m_iAmmoAmounts[i] < 0 ) + continue; // negative ammo amounts imply that they shouldn't be drawn + + // half a char gap between the ammo number and the previous pic + x -= (AmmoWidth / 2); + + // draw the number, right-aligned + x -= (gHUD.GetNumWidth( m_iAmmoAmounts[i], DHN_DRAWZERO ) * AmmoWidth); + gHUD.DrawHudNumber( x, y, DHN_DRAWZERO, m_iAmmoAmounts[i], r, g, b ); + + if ( i != 0 ) + { + // draw the divider bar + x -= (AmmoWidth / 2); + FillRGBA(x, y, (AmmoWidth/10), gHUD.m_iFontHeight, r, g, b, a); + } + } + + return 1; +} + +// Message handler for Secondary Ammo Value +// accepts one value: +// string: sprite name +int CHudAmmoSecondary :: MsgFunc_SecAmmoIcon( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + m_HUD_ammoicon = gHUD.GetSpriteIndex( READ_STRING() ); + + return 1; +} + +// Message handler for Secondary Ammo Icon +// Sets an ammo value +// takes two values: +// byte: ammo index +// byte: ammo value +int CHudAmmoSecondary :: MsgFunc_SecAmmoVal( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int index = READ_BYTE(); + if ( index < 0 || index >= MAX_SEC_AMMO_VALUES ) + return 1; + + m_iAmmoAmounts[index] = READ_BYTE(); + m_iFlags |= HUD_ACTIVE; + + // check to see if there is anything left to draw + int count = 0; + for ( int i = 0; i < MAX_SEC_AMMO_VALUES; i++ ) + { + count += max( 0, m_iAmmoAmounts[i] ); + } + + if ( count == 0 ) + { // the ammo fields are all empty, so turn off this hud area + m_iFlags &= ~HUD_ACTIVE; + return 1; + } + + // make the icons light up + m_fFade = 200.0f; + + return 1; +} + + diff --git a/hlsdk/dmc/cl_dll/ammohistory.cpp b/hlsdk/dmc/cl_dll/ammohistory.cpp new file mode 100644 index 00000000..bc78dad5 --- /dev/null +++ b/hlsdk/dmc/cl_dll/ammohistory.cpp @@ -0,0 +1,190 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// ammohistory.cpp +// + + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "ammohistory.h" + +HistoryResource gHR; + +#define AMMO_PICKUP_GAP (gHR.iHistoryGap+5) +#define AMMO_PICKUP_PICK_HEIGHT (32 + (gHR.iHistoryGap * 2)) +#define AMMO_PICKUP_HEIGHT_MAX (ScreenHeight - 100) + +#define MAX_ITEM_NAME 32 +int HISTORY_DRAW_TIME = 5; + +// keep a list of items +struct ITEM_INFO +{ + char szName[MAX_ITEM_NAME]; + HSPRITE spr; + wrect_t rect; +}; + +void HistoryResource :: AddToHistory( int iType, int iId, int iCount ) +{ + if ( iType == HISTSLOT_AMMO && !iCount ) + return; // no amount, so don't add + + if ( (((AMMO_PICKUP_GAP * iCurrentHistorySlot) + AMMO_PICKUP_PICK_HEIGHT) > AMMO_PICKUP_HEIGHT_MAX) || (iCurrentHistorySlot >= MAX_HISTORY) ) + { // the pic would have to be drawn too high + // so start from the bottom + iCurrentHistorySlot = 0; + } + + HIST_ITEM *freeslot = &rgAmmoHistory[iCurrentHistorySlot++]; // default to just writing to the first slot + HISTORY_DRAW_TIME = CVAR_GET_FLOAT( "hud_drawhistory_time" ); + + freeslot->type = iType; + freeslot->iId = iId; + freeslot->iCount = iCount; + freeslot->DisplayTime = gHUD.m_flTime + HISTORY_DRAW_TIME; +} + +void HistoryResource :: AddToHistory( int iType, const char *szName, int iCount ) +{ + if ( iType != HISTSLOT_ITEM ) + return; + + if ( (((AMMO_PICKUP_GAP * iCurrentHistorySlot) + AMMO_PICKUP_PICK_HEIGHT) > AMMO_PICKUP_HEIGHT_MAX) || (iCurrentHistorySlot >= MAX_HISTORY) ) + { // the pic would have to be drawn too high + // so start from the bottom + iCurrentHistorySlot = 0; + } + + HIST_ITEM *freeslot = &rgAmmoHistory[iCurrentHistorySlot++]; // default to just writing to the first slot + + // I am really unhappy with all the code in this file + + int i = gHUD.GetSpriteIndex( szName ); + if ( i == -1 ) + return; // unknown sprite name, don't add it to history + + freeslot->iId = i; + freeslot->type = iType; + freeslot->iCount = iCount; + + HISTORY_DRAW_TIME = CVAR_GET_FLOAT( "hud_drawhistory_time" ); + freeslot->DisplayTime = gHUD.m_flTime + HISTORY_DRAW_TIME; +} + + +void HistoryResource :: CheckClearHistory( void ) +{ + for ( int i = 0; i < MAX_HISTORY; i++ ) + { + if ( rgAmmoHistory[i].type ) + return; + } + + iCurrentHistorySlot = 0; +} + +// +// Draw Ammo pickup history +// +int HistoryResource :: DrawAmmoHistory( float flTime ) +{ + for ( int i = 0; i < MAX_HISTORY; i++ ) + { + if ( rgAmmoHistory[i].type ) + { + rgAmmoHistory[i].DisplayTime = min( rgAmmoHistory[i].DisplayTime, gHUD.m_flTime + HISTORY_DRAW_TIME ); + + if ( rgAmmoHistory[i].DisplayTime <= flTime ) + { // pic drawing time has expired + memset( &rgAmmoHistory[i], 0, sizeof(HIST_ITEM) ); + CheckClearHistory(); + } + else if ( rgAmmoHistory[i].type == HISTSLOT_AMMO ) + { + wrect_t rcPic; + HSPRITE *spr = gWR.GetAmmoPicFromWeapon( rgAmmoHistory[i].iId, rcPic ); + + int r, g, b; + UnpackRGB(r,g,b, RGB_YELLOWISH); + float scale = (rgAmmoHistory[i].DisplayTime - flTime) * 80; + ScaleColors(r, g, b, min(scale, 255) ); + + // Draw the pic + int ypos = ScreenHeight - (AMMO_PICKUP_PICK_HEIGHT + (AMMO_PICKUP_GAP * i)); + int xpos = ScreenWidth - 24; + if ( spr && *spr ) // weapon isn't loaded yet so just don't draw the pic + { // the dll has to make sure it has sent info the weapons you need + SPR_Set( *spr, r, g, b ); + SPR_DrawAdditive( 0, xpos, ypos, &rcPic ); + } + + // Draw the number + gHUD.DrawHudNumberString( xpos - 10, ypos, xpos - 100, rgAmmoHistory[i].iCount, r, g, b ); + } + else if ( rgAmmoHistory[i].type == HISTSLOT_WEAP ) + { + WEAPON *weap = gWR.GetWeapon( rgAmmoHistory[i].iId ); + + if ( !weap ) + 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); + + if ( !gWR.HasAmmo( weap ) ) + UnpackRGB(r,g,b, RGB_REDISH); // if the weapon doesn't have ammo, display it as red + + float scale = (rgAmmoHistory[i].DisplayTime - flTime) * 80; + ScaleColors(r, g, b, min(scale, 255) ); + + int ypos = ScreenHeight - (AMMO_PICKUP_PICK_HEIGHT + (AMMO_PICKUP_GAP * i)); + int xpos = ScreenWidth - (weap->rcInactive.right - weap->rcInactive.left); + SPR_Set( weap->hInactive, r, g, b ); + SPR_DrawAdditive( 0, xpos, ypos, &weap->rcInactive ); + } + else if ( rgAmmoHistory[i].type == HISTSLOT_ITEM ) + { + int r, g, b; + + if ( !rgAmmoHistory[i].iId ) + continue; // sprite not loaded + + wrect_t rect = gHUD.GetSpriteRect( rgAmmoHistory[i].iId ); + + UnpackRGB(r,g,b, RGB_YELLOWISH); + float scale = (rgAmmoHistory[i].DisplayTime - flTime) * 80; + ScaleColors(r, g, b, min(scale, 255) ); + + int ypos = ScreenHeight - (AMMO_PICKUP_PICK_HEIGHT + (AMMO_PICKUP_GAP * i)); + int xpos = ScreenWidth - (rect.right - rect.left) - 10; + + SPR_Set( gHUD.GetSprite( rgAmmoHistory[i].iId ), r, g, b ); + SPR_DrawAdditive( 0, xpos, ypos, &rect ); + } + } + } + + + return 1; +} + + diff --git a/hlsdk/dmc/cl_dll/ammohistory.h b/hlsdk/dmc/cl_dll/ammohistory.h new file mode 100644 index 00000000..5da8921d --- /dev/null +++ b/hlsdk/dmc/cl_dll/ammohistory.h @@ -0,0 +1,144 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// ammohistory.h +// + +// this is the max number of items in each bucket +#define MAX_WEAPON_POSITIONS 1 + +class WeaponsResource +{ +private: + // Information about weapons & ammo + WEAPON rgWeapons[MAX_WEAPONS]; // Weapons Array + + // 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 ) + { + memset( rgWeapons, 0, sizeof rgWeapons ); + Reset(); + } + + void Reset( void ) + { + iOldWeaponBits = 0; + memset( rgSlots, 0, sizeof rgSlots ); + memset( riAmmo, 0, sizeof riAmmo ); + } + +///// WEAPON ///// + int iOldWeaponBits; + + WEAPON *GetWeapon( int iId ) { return &rgWeapons[iId]; } + void AddWeapon( WEAPON *wp ) + { + rgWeapons[ wp->iId ] = *wp; + LoadWeaponSprites( &rgWeapons[ wp->iId ] ); + } + + void PickupWeapon( WEAPON *wp ) + { + rgSlots[ wp->iSlot ][ 0 ] = wp; + } + + void DropWeapon( WEAPON *wp ) + { + rgSlots[ wp->iSlot ][ 0 ] = NULL; + } + + void DropAllWeapons( void ) + { + for ( int i = 0; i < MAX_WEAPONS; i++ ) + { + if ( rgWeapons[i].iId ) + DropWeapon( &rgWeapons[i] ); + } + } + + WEAPON* GetWeaponSlot( int slot, int pos ) { return rgSlots[slot][pos]; } + + void LoadWeaponSprites( WEAPON* wp ); + void LoadAllWeaponSprites( void ); + WEAPON* GetFirstPos( int iSlot ); + void SelectSlot( int iSlot, int fAdvance, int iDirection ); + WEAPON* GetNextActivePos( int iSlot, int iSlotPos ); + + int HasAmmo( WEAPON *p ); + +///// AMMO ///// + AMMO GetAmmo( int iId ) { return iId; } + + void SetAmmo( int iId, int iCount ) { riAmmo[ iId ] = iCount; } + + int CountAmmo( int iId ); + + HSPRITE* GetAmmoPicFromWeapon( int iAmmoId, wrect_t& rect ); + +}; + +extern WeaponsResource gWR; + + +#define MAX_HISTORY 12 +enum { + HISTSLOT_EMPTY, + HISTSLOT_AMMO, + HISTSLOT_WEAP, + HISTSLOT_ITEM, +}; + +class HistoryResource +{ +private: + struct HIST_ITEM { + int type; + float DisplayTime; // the time at which this item should be removed from the history + int iCount; + int iId; + }; + + HIST_ITEM rgAmmoHistory[MAX_HISTORY]; + +public: + + void Init( void ) + { + Reset(); + } + + void Reset( void ) + { + memset( rgAmmoHistory, 0, sizeof rgAmmoHistory ); + } + + int iHistoryGap; + int iCurrentHistorySlot; + + void AddToHistory( int iType, int iId, int iCount = 0 ); + void AddToHistory( int iType, const char *szName, int iCount = 0 ); + + void CheckClearHistory( void ); + int DrawAmmoHistory( float flTime ); +}; + +extern HistoryResource gHR; + + + diff --git a/hlsdk/dmc/cl_dll/battery.cpp b/hlsdk/dmc/cl_dll/battery.cpp new file mode 100644 index 00000000..dc9a3fbe --- /dev/null +++ b/hlsdk/dmc/cl_dll/battery.cpp @@ -0,0 +1,138 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// battery.cpp +// +// implementation of CHudBattery class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +DECLARE_MESSAGE(m_Battery, Battery) + +int CHudBattery::Init(void) +{ + m_iBat = 0; + m_fFade = 0; + m_iFlags = 0; + + HOOK_MESSAGE(Battery); + + gHUD.AddHudElem(this); + + return 1; +}; + + +int CHudBattery::VidInit(void) +{ + int HUD_suit_empty = gHUD.GetSpriteIndex( "suit_empty" ); + int HUD_suit_full = gHUD.GetSpriteIndex( "suit_full" ); + + m_hSprite1 = m_hSprite2 = 0; // delaying get sprite handles until we know the sprites are loaded + m_prc1 = &gHUD.GetSpriteRect( HUD_suit_empty ); + m_prc2 = &gHUD.GetSpriteRect( HUD_suit_full ); + m_iHeight = m_prc2->bottom - m_prc1->top; + m_fFade = 0; + return 1; +}; + +int CHudBattery:: MsgFunc_Battery(const char *pszName, int iSize, void *pbuf ) +{ + m_iFlags |= HUD_ACTIVE; + + + BEGIN_READ( pbuf, iSize ); + int x = READ_SHORT(); + + if (x != m_iBat) + { + m_fFade = FADE_TIME; + m_iBat = x; + } + + return 1; +} + + +int CHudBattery::Draw(float flTime) +{ + if ( gHUD.m_iHideHUDDisplay & HIDEHUD_HEALTH ) + return 1; + + int r, g, b, x, y, a; + wrect_t rc; + + rc = *m_prc2; + rc.top += m_iHeight * ((float)(100-(min(100,m_iBat))) * 0.01); // battery can go from 0 to 100 so * 0.01 goes from 0 to 1 + + UnpackRGB(r,g,b, RGB_YELLOWISH); + + if (!(gHUD.m_iWeaponBits & (1<<(WEAPON_SUIT)) )) + return 1; + + // Has health changed? Flash the health # + if (m_fFade) + { + if (m_fFade > FADE_TIME) + m_fFade = FADE_TIME; + + m_fFade -= (gHUD.m_flTimeDelta * 20); + if (m_fFade <= 0) + { + a = 128; + m_fFade = 0; + } + + // Fade the health number back to dim + + a = MIN_ALPHA + (m_fFade/FADE_TIME) * 128; + + } + else + a = MIN_ALPHA; + + ScaleColors(r, g, b, a ); + + int iOffset = (m_prc1->bottom - m_prc1->top)/6; + + y = ScreenHeight - gHUD.m_iFontHeight - gHUD.m_iFontHeight / 2; + x = ScreenWidth/5; + + // make sure we have the right sprite handles + if ( !m_hSprite1 ) + m_hSprite1 = gHUD.GetSprite( gHUD.GetSpriteIndex( "suit_empty" ) ); + if ( !m_hSprite2 ) + m_hSprite2 = gHUD.GetSprite( gHUD.GetSpriteIndex( "suit_full" ) ); + + SPR_Set(m_hSprite1, r, g, b ); + SPR_DrawAdditive( 0, x, y - iOffset, m_prc1); + + if (rc.bottom > rc.top) + { + SPR_Set(m_hSprite2, r, g, b ); + SPR_DrawAdditive( 0, x, y - iOffset + (rc.top - m_prc2->top), &rc); + } + + x += (m_prc1->right - m_prc1->left); + x = gHUD.DrawHudNumber(x, y, DHN_3DIGITS | DHN_DRAWZERO, m_iBat, r, g, b); + + return 1; +} \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/camera.h b/hlsdk/dmc/cl_dll/camera.h new file mode 100644 index 00000000..08c87920 --- /dev/null +++ b/hlsdk/dmc/cl_dll/camera.h @@ -0,0 +1,24 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// Camera.h -- defines and such for a 3rd person camera +// NOTE: must include quakedef.h first + +#ifndef _CAMERA_H_ +#define _CAMERA_H_ + +// pitch, yaw, dist +extern vec3_t cam_ofs; +// Using third person camera +extern int cam_thirdperson; + +void CAM_Init( void ); +void CAM_ClearStates( void ); +void CAM_StartMouseMove(void); +void CAM_EndMouseMove(void); + +#endif // _CAMERA_H_ diff --git a/hlsdk/dmc/cl_dll/cdll_int.cpp b/hlsdk/dmc/cl_dll/cdll_int.cpp new file mode 100644 index 00000000..5d2bad3c --- /dev/null +++ b/hlsdk/dmc/cl_dll/cdll_int.cpp @@ -0,0 +1,291 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cdll_int.c +// +// this implementation handles the linking of the engine to the DLL +// + +#include "hud.h" +#include "cl_util.h" +#include +#include "netadr.h" +#include "vgui_int.h" +#include "voice_status.h" + +#include "interface.h" + +#define DLLEXPORT __declspec( dllexport ) + +cl_enginefunc_t gEngfuncs; +CHud gHUD; +TeamFortressViewport *gViewPort = NULL; + +extern "C" +{ +#include "pm_shared.h" +} + +#include "hud_servers.h" + +void InitInput (void); +void EV_HookEvents( void ); +void IN_Commands( void ); + +/* +========================== + Initialize + +Called when the DLL is first loaded. +========================== +*/ +extern "C" +{ +int DLLEXPORT Initialize( cl_enginefunc_t *pEnginefuncs, int iVersion ); +int DLLEXPORT HUD_VidInit( void ); +int DLLEXPORT HUD_Init( void ); +int DLLEXPORT HUD_Redraw( float flTime, int intermission ); +int DLLEXPORT HUD_UpdateClientData( client_data_t *cdata, float flTime ); +int DLLEXPORT HUD_Reset ( void ); +void DLLEXPORT HUD_PlayerMove( struct playermove_s *ppmove, int server ); +void DLLEXPORT HUD_PlayerMoveInit( struct playermove_s *ppmove ); +char DLLEXPORT HUD_PlayerMoveTexture( char *name ); +int DLLEXPORT HUD_ConnectionlessPacket( struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); +int DLLEXPORT HUD_GetHullBounds( int hullnumber, float *mins, float *maxs ); +void DLLEXPORT HUD_Frame( double time ); +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 ); +void DLLEXPORT HUD_VoiceStatus(int entindex, qboolean bTalking); +void DLLEXPORT HUD_DirectorMessage( int iSize, void *pbuf ); +} + +/* +================================ +HUD_GetHullBounds + + Engine calls this to enumerate player collision hulls, for prediction. Return 0 if the hullnumber doesn't exist. +================================ +*/ +int DLLEXPORT HUD_GetHullBounds( int hullnumber, float *mins, float *maxs ) +{ + int iret = 0; + + switch ( hullnumber ) + { + case 0: // Normal player + mins = Vector(-16, -16, -32); + maxs = Vector(16, 16, 32); + iret = 1; + break; + case 1: // Crouched player + mins = Vector(-16, -16, -32); + maxs = Vector(16, 16, 32); + iret = 1; + break; + case 2: // Point based hull + mins = Vector( 0, 0, 0 ); + maxs = Vector( 0, 0, 0 ); + iret = 1; + break; + } + + return iret; +} + +/* +================================ +HUD_ConnectionlessPacket + + Return 1 if the packet is valid. Set response_buffer_size if you want to send a response packet. Incoming, it holds the max + size of the response_buffer, so you must zero it out if you choose not to respond. +================================ +*/ +int DLLEXPORT HUD_ConnectionlessPacket( struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ) +{ + // Parse stuff from args + int max_buffer_size = *response_buffer_size; + + // Zero it out since we aren't going to respond. + // If we wanted to response, we'd write data into response_buffer + *response_buffer_size = 0; + + // Since we don't listen for anything here, just respond that it's a bogus message + // If we didn't reject the message, we'd return 1 for success instead. + return 0; +} + +void DLLEXPORT HUD_PlayerMoveInit( struct playermove_s *ppmove ) +{ + PM_Init( ppmove ); +} + +char DLLEXPORT HUD_PlayerMoveTexture( char *name ) +{ + return PM_FindTextureType( name ); +} + +void DLLEXPORT HUD_PlayerMove( struct playermove_s *ppmove, int server ) +{ + PM_Move( ppmove, server ); +} + +int DLLEXPORT Initialize( cl_enginefunc_t *pEnginefuncs, int iVersion ) +{ + gEngfuncs = *pEnginefuncs; + + //!!! mwh UNDONE We need to think about our versioning strategy. Do we want to try to be compatible + // with previous versions, especially when we're only 'bonus' functionality? Should it be the engine + // that decides if the DLL is compliant? + + if (iVersion != CLDLL_INTERFACE_VERSION) + return 0; + + memcpy(&gEngfuncs, pEnginefuncs, sizeof(cl_enginefunc_t)); + + EV_HookEvents(); + + return 1; +} + + +/* +========================== + HUD_VidInit + +Called when the game initializes +and whenever the vid_mode is changed +so the HUD can reinitialize itself. +========================== +*/ + +int DLLEXPORT HUD_VidInit( void ) +{ + gHUD.VidInit(); + + VGui_Startup(); + + return 1; +} + +/* +========================== + HUD_Init + +Called whenever the client connects +to a server. Reinitializes all +the hud variables. +========================== +*/ + +int DLLEXPORT HUD_Init( void ) +{ + InitInput(); + gHUD.Init(); + + return 1; +} + + +/* +========================== + HUD_Redraw + +called every screen frame to +redraw the HUD. +=========================== +*/ + +int DLLEXPORT HUD_Redraw( float time, int intermission ) +{ + gHUD.Redraw( time, intermission ); + + return 1; +} + + +/* +========================== + HUD_UpdateClientData + +called every time shared client +dll/engine data gets changed, +and gives the cdll a chance +to modify the data. + +returns 1 if anything has been changed, 0 otherwise. +========================== +*/ + +int DLLEXPORT HUD_UpdateClientData(client_data_t *pcldata, float flTime ) +{ + return gHUD.UpdateClientData(pcldata, flTime ); +} + +/* +========================== + HUD_Reset + +Called at start and end of demos to restore to "non"HUD state. +========================== +*/ + +int DLLEXPORT HUD_Reset( void ) +{ + gHUD.VidInit(); + return 1; +} + +/* +========================== +HUD_Frame + +Called by engine every frame that client .dll is loaded +========================== +*/ + +void DLLEXPORT HUD_Frame( double time ) +{ + IN_Commands(); + + ServersThink( time ); + + GetClientVoiceMgr()->Frame(time); +} + + +/* +========================== +HUD_VoiceStatus + +Called when a player starts or stops talking. +========================== +*/ + +void DLLEXPORT HUD_VoiceStatus(int entindex, qboolean bTalking) +{ + GetClientVoiceMgr()->UpdateSpeakerStatus(entindex, bTalking); +} + +/* +========================== +HUD_DirectorEvent + +Called when a director event message was received +========================== +*/ + +void DLLEXPORT HUD_DirectorMessage( int iSize, void *pbuf ) +{ + gHUD.m_Spectator.DirectorMessage( iSize, pbuf ); +} diff --git a/hlsdk/dmc/cl_dll/cl_dll.h b/hlsdk/dmc/cl_dll/cl_dll.h new file mode 100644 index 00000000..cb241e3b --- /dev/null +++ b/hlsdk/dmc/cl_dll/cl_dll.h @@ -0,0 +1,47 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cl_dll.h +// + +// 4-23-98 JOHN + +// +// This DLL is linked by the client when they first initialize. +// This DLL is responsible for the following tasks: +// - Loading the HUD graphics upon initialization +// - Drawing the HUD graphics every frame +// - Handling the custum HUD-update packets +// +typedef unsigned char byte; +typedef unsigned short word; +typedef float vec_t; +typedef int (*pfnUserMsgHook)(const char *pszName, int iSize, void *pbuf); + +#if defined _MSC_VER && _MSC_VER >= 1400 + #ifndef _CRT_SECURE_NO_DEPRECATE + #define _CRT_SECURE_NO_DEPRECATE + #endif + + #pragma warning(disable: 4996) // deprecated functions +#endif + +#include "util_vector.h" +#define EXPORT _declspec( dllexport ) + +#include "../engine/cdll_int.h" +#include "../dlls/cdll_dll.h" + +extern cl_enginefunc_t gEngfuncs; diff --git a/hlsdk/dmc/cl_dll/cl_util.h b/hlsdk/dmc/cl_dll/cl_util.h new file mode 100644 index 00000000..38c7bdbe --- /dev/null +++ b/hlsdk/dmc/cl_dll/cl_util.h @@ -0,0 +1,159 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cl_util.h +// + +#include "cvardef.h" + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +// Macros to hook function calls into the HUD object +#define HOOK_MESSAGE(x) gEngfuncs.pfnHookUserMsg(#x, __MsgFunc_##x ); + +#define DECLARE_MESSAGE(y, x) int __MsgFunc_##x(const char *pszName, int iSize, void *pbuf) \ + { \ + return gHUD.##y.MsgFunc_##x(pszName, iSize, pbuf ); \ + } + + +#define HOOK_COMMAND(x, y) gEngfuncs.pfnAddCommand( x, __CmdFunc_##y ); +#define DECLARE_COMMAND(y, x) void __CmdFunc_##x( void ) \ + { \ + gHUD.##y.UserCmd_##x( ); \ + } + +inline float CVAR_GET_FLOAT( const char *x ) { return gEngfuncs.pfnGetCvarFloat( (char*)x ); } +inline char* CVAR_GET_STRING( const char *x ) { return gEngfuncs.pfnGetCvarString( (char*)x ); } +inline struct cvar_s *CVAR_CREATE( const char *cv, const char *val, const int flags ) { return gEngfuncs.pfnRegisterVariable( (char*)cv, (char*)val, flags ); } + +#define SPR_Load (*gEngfuncs.pfnSPR_Load) +#define SPR_Set (*gEngfuncs.pfnSPR_Set) +#define SPR_Frames (*gEngfuncs.pfnSPR_Frames) +#define SPR_GetList (*gEngfuncs.pfnSPR_GetList) + +// SPR_Draw draws a the current sprite as solid +#define SPR_Draw (*gEngfuncs.pfnSPR_Draw) +// SPR_DrawHoles draws the current sprites, with color index255 not drawn (transparent) +#define SPR_DrawHoles (*gEngfuncs.pfnSPR_DrawHoles) +// SPR_DrawAdditive adds the sprites RGB values to the background (additive transulency) +#define SPR_DrawAdditive (*gEngfuncs.pfnSPR_DrawAdditive) + +// SPR_EnableScissor sets a clipping rect for HUD sprites. (0,0) is the top-left hand corner of the screen. +#define SPR_EnableScissor (*gEngfuncs.pfnSPR_EnableScissor) +// SPR_DisableScissor disables the clipping rect +#define SPR_DisableScissor (*gEngfuncs.pfnSPR_DisableScissor) +// +#define FillRGBA (*gEngfuncs.pfnFillRGBA) + + +// ScreenHeight returns the height of the screen, in pixels +#define ScreenHeight (gHUD.m_scrinfo.iHeight) +// ScreenWidth returns the width of the screen, in pixels +#define ScreenWidth (gHUD.m_scrinfo.iWidth) + +// Use this to set any co-ords in 640x480 space +#define XRES(x) ((int)(float(x) * ((float)ScreenWidth / 640.0f) + 0.5f)) +#define YRES(y) ((int)(float(y) * ((float)ScreenHeight / 480.0f) + 0.5f)) + +// use this to project world coordinates to screen coordinates +#define XPROJECT(x) ( (1.0f+(x))*ScreenWidth*0.5f ) +#define YPROJECT(y) ( (1.0f-(y))*ScreenHeight*0.5f ) +#define GetScreenInfo (*gEngfuncs.pfnGetScreenInfo) +#define ServerCmd (*gEngfuncs.pfnServerCmd) +#define ClientCmd (*gEngfuncs.pfnClientCmd) +#define SetCrosshair (*gEngfuncs.pfnSetCrosshair) +#define AngleVectors (*gEngfuncs.pfnAngleVectors) + + +// Gets the height & width of a sprite, at the specified frame +inline int SPR_Height( HSPRITE x, int f ) { return gEngfuncs.pfnSPR_Height(x, f); } +inline int SPR_Width( HSPRITE x, int f ) { return gEngfuncs.pfnSPR_Width(x, f); } + +inline client_textmessage_t *TextMessageGet( const char *pName ) { return gEngfuncs.pfnTextMessageGet( pName ); } +inline int TextMessageDrawChar( int x, int y, int number, int r, int g, int b ) +{ + return gEngfuncs.pfnDrawCharacter( x, y, number, r, g, b ); +} + +inline int DrawConsoleString( int x, int y, const char *string ) +{ + return gEngfuncs.pfnDrawConsoleString( x, y, (char*) string ); +} + +inline void GetConsoleStringSize( const char *string, int *width, int *height ) +{ + gEngfuncs.pfnDrawConsoleStringLen( string, width, height ); +} + +inline int ConsoleStringLen( const char *string ) +{ + int _width, _height; + GetConsoleStringSize( string, &_width, &_height ); + return _width; +} + +inline void ConsolePrint( const char *string ) +{ + gEngfuncs.pfnConsolePrint( string ); +} + +inline void CenterPrint( const char *string ) +{ + gEngfuncs.pfnCenterPrint( string ); +} + +// returns the players name of entity no. +#define GetPlayerInfo (*gEngfuncs.pfnGetPlayerInfo) + +// sound functions +inline void PlaySound( char *szSound, float vol ) { gEngfuncs.pfnPlaySoundByName( szSound, vol ); } +inline void PlaySound( int iSound, float vol ) { gEngfuncs.pfnPlaySoundByIndex( iSound, vol ); } + +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#define fabs(x) ((x) > 0 ? (x) : 0 - (x)) + +void ScaleColors( int &r, int &g, int &b, int a ); + +#define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]) +#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 VectorClear(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); +void VectorScale (const float *in, float scale, float *out); +float VectorNormalize (float *v); +void VectorInverse ( float *v ); + +extern vec3_t vec3_origin; + +// disable 'possible loss of data converting float to int' warning message +#pragma warning( disable: 4244 ) +// disable 'truncation from 'const double' to 'float' warning message +#pragma warning( disable: 4305 ) + +inline void UnpackRGB(int &r, int &g, int &b, unsigned long ulRGB)\ +{\ + r = (ulRGB & 0xFF0000) >>16;\ + g = (ulRGB & 0xFF00) >> 8;\ + b = ulRGB & 0xFF;\ +} + +HSPRITE LoadSprite(const char *pszName); diff --git a/hlsdk/dmc/cl_dll/com_model.h b/hlsdk/dmc/cl_dll/com_model.h new file mode 100644 index 00000000..a6b85b25 --- /dev/null +++ b/hlsdk/dmc/cl_dll/com_model.h @@ -0,0 +1,369 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +// com_model.h +#if !defined( COM_MODEL_H ) +#define COM_MODEL_H +#if defined( _WIN32 ) +#pragma once +#endif + +#define STUDIO_RENDER 1 +#define STUDIO_EVENTS 2 + +#define MAX_CLIENTS 32 +#define MAX_EDICTS 900 + +#define MAX_MODEL_NAME 64 +#define MAX_MAP_HULLS 4 +#define MIPLEVELS 4 +#define NUM_AMBIENTS 4 // automatic ambient sounds +#define MAXLIGHTMAPS 4 +#define PLANE_ANYZ 5 + +#define ALIAS_Z_CLIP_PLANE 5 + +// flags in finalvert_t.flags +#define ALIAS_LEFT_CLIP 0x0001 +#define ALIAS_TOP_CLIP 0x0002 +#define ALIAS_RIGHT_CLIP 0x0004 +#define ALIAS_BOTTOM_CLIP 0x0008 +#define ALIAS_Z_CLIP 0x0010 +#define ALIAS_ONSEAM 0x0020 +#define ALIAS_XY_CLIP_MASK 0x000F + +#define ZISCALE ((float)0x8000) + +#define CACHE_SIZE 32 // used to align key data structures + +typedef enum +{ + mod_brush, + mod_sprite, + mod_alias, + mod_studio +} modtype_t; + +// must match definition in modelgen.h +#ifndef SYNCTYPE_T +#define SYNCTYPE_T + +typedef enum +{ + ST_SYNC=0, + ST_RAND +} synctype_t; + +#endif + +typedef struct +{ + float mins[3], maxs[3]; + float origin[3]; + int headnode[MAX_MAP_HULLS]; + int visleafs; // not including the solid leaf 0 + int firstface, numfaces; +} dmodel_t; + +// plane_t structure +// plane_t structure +#ifndef _MPLANE_DEFINED_ +#define _MPLANE_DEFINED_ +typedef struct mplane_s +{ + vec3_t normal; // surface normal + float dist; // closest appoach to origin + byte type; // for texture axis selection and fast side tests + byte signbits; // signx + signy<<1 + signz<<1 + byte pad[2]; +} mplane_t; +#endif + +typedef struct +{ + vec3_t position; +} mvertex_t; + +typedef struct +{ + unsigned short v[2]; + unsigned int cachededgeoffset; +} medge_t; + +typedef struct texture_s +{ + char name[16]; + unsigned width, height; + int anim_total; // total tenths in sequence ( 0 = no) + int anim_min, anim_max; // time for this frame min <=time< max + struct texture_s *anim_next; // in the animation sequence + struct texture_s *alternate_anims; // bmodels in frame 1 use these + unsigned offsets[MIPLEVELS]; // four mip maps stored + unsigned paloffset; +} texture_t; + +typedef struct +{ + float vecs[2][4]; // [s/t] unit vectors in world space. + // [i][3] is the s/t offset relative to the origin. + // s or t = dot(3Dpoint,vecs[i])+vecs[i][3] + float mipadjust; // ?? mipmap limits for very small surfaces + texture_t *texture; + int flags; // sky or slime, no lightmap or 256 subdivision +} mtexinfo_t; + +typedef struct mnode_s +{ +// common with leaf + int contents; // 0, to differentiate from leafs + int visframe; // node needs to be traversed if current + + short minmaxs[6]; // for bounding box culling + + struct mnode_s *parent; + +// node specific + mplane_t *plane; + struct mnode_s *children[2]; + + unsigned short firstsurface; + unsigned short numsurfaces; +} mnode_t; + +typedef struct msurface_s msurface_t; +typedef struct decal_s decal_t; + +// JAY: Compress this as much as possible +struct decal_s +{ + decal_t *pnext; // linked list for each surface + msurface_t *psurface; // Surface id for persistence / unlinking + short dx; // Offsets into surface texture (in texture coordinates, so we don't need floats) + short dy; + short texture; // Decal texture + byte scale; // Pixel scale + byte flags; // Decal flags + + short entityIndex; // Entity this is attached to +}; + +typedef struct mleaf_s +{ +// common with node + int contents; // wil be a negative contents number + int visframe; // node needs to be traversed if current + + short minmaxs[6]; // for bounding box culling + + struct mnode_s *parent; + +// leaf specific + byte *compressed_vis; + struct efrag_s *efrags; + + msurface_t **firstmarksurface; + int nummarksurfaces; + int key; // BSP sequence number for leaf's contents + byte ambient_sound_level[NUM_AMBIENTS]; +} mleaf_t; + +struct msurface_s +{ + int visframe; // should be drawn when node is crossed + + int dlightframe; // last frame the surface was checked by an animated light + int dlightbits; // dynamically generated. Indicates if the surface illumination + // is modified by an animated light. + + mplane_t *plane; // pointer to shared plane + int flags; // see SURF_ #defines + + int firstedge; // look up in model->surfedges[], negative numbers + int numedges; // are backwards edges + +// surface generation data + struct surfcache_s *cachespots[MIPLEVELS]; + + short texturemins[2]; // smallest s/t position on the surface. + short extents[2]; // ?? s/t texture size, 1..256 for all non-sky surfaces + + mtexinfo_t *texinfo; + +// lighting info + byte styles[MAXLIGHTMAPS]; // index into d_lightstylevalue[] for animated lights + // no one surface can be effected by more than 4 + // animated lights. + color24 *samples; + + decal_t *pdecals; +}; + +#ifndef _DCLIPNODE_DEFINED_ +#define _DCLIPNODE_DEFINED_ +typedef struct +{ + int planenum; + short children[2]; // negative numbers are contents +} dclipnode_t; +#endif + +#ifndef _HULL_DEFINED_ +#define _HULL_DEFINED_ +typedef struct hull_s +{ + dclipnode_t *clipnodes; + mplane_t *planes; + int firstclipnode; + int lastclipnode; + vec3_t clip_mins; + vec3_t clip_maxs; +} hull_t; +#endif + +#if !defined( CACHE_USER ) && !defined( QUAKEDEF_H ) +#define CACHE_USER +typedef struct cache_user_s +{ + void *data; +} cache_user_t; +#endif + +typedef struct model_s +{ + char name[ MAX_MODEL_NAME ]; + qboolean needload; // bmodels and sprites don't cache normally + + modtype_t type; + int numframes; + synctype_t synctype; + + int flags; + +// +// volume occupied by the model +// + vec3_t mins, maxs; + float radius; + +// +// brush model +// + int firstmodelsurface, nummodelsurfaces; + + int numsubmodels; + dmodel_t *submodels; + + int numplanes; + mplane_t *planes; + + int numleafs; // number of visible leafs, not counting 0 + struct mleaf_s *leafs; + + int numvertexes; + mvertex_t *vertexes; + + int numedges; + medge_t *edges; + + int numnodes; + mnode_t *nodes; + + int numtexinfo; + mtexinfo_t *texinfo; + + int numsurfaces; + msurface_t *surfaces; + + int numsurfedges; + int *surfedges; + + int numclipnodes; + dclipnode_t *clipnodes; + + int nummarksurfaces; + msurface_t **marksurfaces; + + hull_t hulls[MAX_MAP_HULLS]; + + int numtextures; + texture_t **textures; + + byte *visdata; + + color24 *lightdata; + + char *entities; + +// +// additional model data +// + cache_user_t cache; // only access through Mod_Extradata + +} model_t; + +typedef vec_t vec4_t[4]; + +typedef struct alight_s +{ + int ambientlight; // clip at 128 + int shadelight; // clip at 192 - ambientlight + vec3_t color; + float *plightvec; +} alight_t; + +typedef struct auxvert_s +{ + float fv[3]; // viewspace x, y +} auxvert_t; + +#include "custom.h" + +#define MAX_INFO_STRING 256 +#define MAX_SCOREBOARDNAME 32 +typedef struct player_info_s +{ + // User id on server + int userid; + + // User info string + char userinfo[ MAX_INFO_STRING ]; + + // Name + char name[ MAX_SCOREBOARDNAME ]; + + // Spectator or not, unused + int spectator; + + int ping; + int packet_loss; + + // skin information + char model[MAX_QPATH]; + int topcolor; + int bottomcolor; + + // last frame rendered + int renderframe; + + // Gait frame estimation + int gaitsequence; + float gaitframe; + float gaityaw; + vec3_t prevgaitorigin; + + customization_t customdata; +} player_info_t; + +#endif // #define COM_MODEL_H diff --git a/hlsdk/dmc/cl_dll/com_weapons.cpp b/hlsdk/dmc/cl_dll/com_weapons.cpp new file mode 100644 index 00000000..ee51d5bd --- /dev/null +++ b/hlsdk/dmc/cl_dll/com_weapons.cpp @@ -0,0 +1,278 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +// Com_Weapons.cpp +// Shared weapons common/shared functions +#include +#include "hud.h" +#include "cl_util.h" +#include "com_weapons.h" + +#include "const.h" +#include "entity_state.h" +#include "r_efx.h" + +// g_runfuncs is true if this is the first time we've "predicated" a particular movement/firing +// command. If it is 1, then we should play events/sounds etc., otherwise, we just will be +// updating state info, but not firing events +int g_runfuncs = 0; + +// During our weapon prediction processing, we'll need to reference some data that is part of +// the final state passed into the postthink functionality. We'll set this pointer and then +// reset it to NULL as appropriate +struct local_state_s *g_finalstate = NULL; + +/* +==================== +COM_Log + +Log debug messages to file ( appends ) +==================== +*/ +void COM_Log( char *pszFile, char *fmt, ...) +{ + va_list argptr; + char string[1024]; + FILE *fp; + char *pfilename; + + if ( !pszFile ) + { + pfilename = "c:\\hllog.txt"; + } + else + { + pfilename = pszFile; + } + + va_start (argptr,fmt); + vsprintf (string, fmt,argptr); + va_end (argptr); + + fp = fopen( pfilename, "a+t"); + if (fp) + { + fprintf(fp, "%s", string); + fclose(fp); + } +} + +// remember the current animation for the view model, in case we get out of sync with +// server. +static int g_currentanim; + +/* +===================== +HUD_SendWeaponAnim + +Change weapon model animation +===================== +*/ +void HUD_SendWeaponAnim( int iAnim, int body, int force ) +{ + // Don't actually change it. + if ( !g_runfuncs && !force ) + return; + + g_currentanim = iAnim; + + // Tell animation system new info + gEngfuncs.pfnWeaponAnim( iAnim, body ); +} + +/* +===================== +HUD_GetWeaponAnim + +Retrieve current predicted weapon animation +===================== +*/ +int HUD_GetWeaponAnim( void ) +{ + return g_currentanim; +} + +/* +===================== +HUD_PlaySound + +Play a sound, if we are seeing this command for the first time +===================== +*/ +void HUD_PlaySound( char *sound, float volume ) +{ + if ( !g_runfuncs || !g_finalstate ) + return; + + gEngfuncs.pfnPlaySoundByNameAtLocation( sound, volume, (float *)&g_finalstate->playerstate.origin ); +} + +/* +===================== +HUD_PlaybackEvent + +Directly queue up an event on the client +===================== +*/ +void HUD_PlaybackEvent( int flags, const edict_t *pInvoker, unsigned short eventindex, float delay, + float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ) +{ + vec3_t org; + vec3_t ang; + + if ( !g_runfuncs || !g_finalstate ) + return; + + // Weapon prediction events are assumed to occur at the player's origin + org = g_finalstate->playerstate.origin; + ang = v_angles; + gEngfuncs.pfnPlaybackEvent( flags, pInvoker, eventindex, delay, (float *)&org, (float *)&ang, fparam1, fparam2, iparam1, iparam2, bparam1, bparam2 ); +} + +/* +===================== +HUD_SetMaxSpeed + +===================== +*/ +void HUD_SetMaxSpeed( const edict_t *ed, float speed ) +{ +} + + +/* +===================== +UTIL_WeaponTimeBase + +Always 0.0 on client, even if not predicting weapons ( won't get called + in that case ) +===================== +*/ +float UTIL_WeaponTimeBase( void ) +{ + return 0.0; +} + +static unsigned int glSeed = 0; + +unsigned int seed_table[ 256 ] = +{ + 28985, 27138, 26457, 9451, 17764, 10909, 28790, 8716, 6361, 4853, 17798, 21977, 19643, 20662, 10834, 20103, + 27067, 28634, 18623, 25849, 8576, 26234, 23887, 18228, 32587, 4836, 3306, 1811, 3035, 24559, 18399, 315, + 26766, 907, 24102, 12370, 9674, 2972, 10472, 16492, 22683, 11529, 27968, 30406, 13213, 2319, 23620, 16823, + 10013, 23772, 21567, 1251, 19579, 20313, 18241, 30130, 8402, 20807, 27354, 7169, 21211, 17293, 5410, 19223, + 10255, 22480, 27388, 9946, 15628, 24389, 17308, 2370, 9530, 31683, 25927, 23567, 11694, 26397, 32602, 15031, + 18255, 17582, 1422, 28835, 23607, 12597, 20602, 10138, 5212, 1252, 10074, 23166, 19823, 31667, 5902, 24630, + 18948, 14330, 14950, 8939, 23540, 21311, 22428, 22391, 3583, 29004, 30498, 18714, 4278, 2437, 22430, 3439, + 28313, 23161, 25396, 13471, 19324, 15287, 2563, 18901, 13103, 16867, 9714, 14322, 15197, 26889, 19372, 26241, + 31925, 14640, 11497, 8941, 10056, 6451, 28656, 10737, 13874, 17356, 8281, 25937, 1661, 4850, 7448, 12744, + 21826, 5477, 10167, 16705, 26897, 8839, 30947, 27978, 27283, 24685, 32298, 3525, 12398, 28726, 9475, 10208, + 617, 13467, 22287, 2376, 6097, 26312, 2974, 9114, 21787, 28010, 4725, 15387, 3274, 10762, 31695, 17320, + 18324, 12441, 16801, 27376, 22464, 7500, 5666, 18144, 15314, 31914, 31627, 6495, 5226, 31203, 2331, 4668, + 12650, 18275, 351, 7268, 31319, 30119, 7600, 2905, 13826, 11343, 13053, 15583, 30055, 31093, 5067, 761, + 9685, 11070, 21369, 27155, 3663, 26542, 20169, 12161, 15411, 30401, 7580, 31784, 8985, 29367, 20989, 14203, + 29694, 21167, 10337, 1706, 28578, 887, 3373, 19477, 14382, 675, 7033, 15111, 26138, 12252, 30996, 21409, + 25678, 18555, 13256, 23316, 22407, 16727, 991, 9236, 5373, 29402, 6117, 15241, 27715, 19291, 19888, 19847 +}; + +unsigned int U_Random( void ) +{ + glSeed *= 69069; + glSeed += seed_table[ glSeed & 0xff ]; + + return ( ++glSeed & 0x0fffffff ); +} + +void U_Srand( unsigned int seed ) +{ + glSeed = seed_table[ seed & 0xff ]; +} + +/* +===================== +UTIL_SharedRandomLong +===================== +*/ +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ) +{ + + unsigned int range; + + U_Srand( (int)seed + low + high ); + + range = high - low + 1; + if ( !(range - 1) ) + { + return low; + } + else + { + int offset; + int rnum; + + rnum = U_Random(); + + offset = rnum % range; + + return (low + offset); + } +} + +/* +===================== +UTIL_SharedRandomFloat +===================== +*/ +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ) +{ + // + unsigned int range; + + U_Srand( (int)seed + *(int *)&low + *(int *)&high ); + + U_Random(); + U_Random(); + + range = high - low; + if ( !range ) + { + return low; + } + else + { + int tensixrand; + float offset; + + tensixrand = U_Random() & 65535; + + offset = (float)tensixrand / 65536.0; + + return (low + offset * range ); + } +} + +/* +====================== +stub_* + +stub functions for such things as precaching. So we don't have to modify weapons code that + is compiled into both game and client .dlls. +====================== +*/ +int stub_PrecacheModel ( char* s ) { return 0; } +int stub_PrecacheSound ( char* s ) { return 0; } +unsigned short stub_PrecacheEvent ( int type, const char *s ) { return 0; } +const char *stub_NameForFunction ( uint32 function ) { return "func"; } +void stub_SetModel ( edict_t *e, const char *m ) {} diff --git a/hlsdk/dmc/cl_dll/com_weapons.h b/hlsdk/dmc/cl_dll/com_weapons.h new file mode 100644 index 00000000..2edd6750 --- /dev/null +++ b/hlsdk/dmc/cl_dll/com_weapons.h @@ -0,0 +1,48 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// com_weapons.h +// Shared weapons common function prototypes +#if !defined( COM_WEAPONSH ) +#define COM_WEAPONSH +#ifdef _WIN32 +#pragma once +#endif + +#include "hud_iface.h" + +extern "C" +{ + 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 ); +} + +void COM_Log( char *pszFile, char *fmt, ...); +int CL_IsDead( void ); + +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ); +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ); + +int HUD_GetWeaponAnim( void ); +void HUD_SendWeaponAnim( int iAnim, int body, int force ); +void HUD_PlaySound( char *sound, float volume ); +void HUD_PlaybackEvent( int flags, const struct edict_s *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); +void HUD_SetMaxSpeed( const struct edict_s *ed, float speed ); +int stub_PrecacheModel( char* s ); +int stub_PrecacheSound( char* s ); +unsigned short stub_PrecacheEvent( int type, const char *s ); +const char *stub_NameForFunction ( uint32 function ); +void stub_SetModel ( struct edict_s *e, const char *m ); + + +extern cvar_t *cl_lw; + +extern int g_runfuncs; +extern vec3_t v_angles; +extern float g_lastFOV; +extern struct local_state_s *g_finalstate; + +#endif \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/death.cpp b/hlsdk/dmc/cl_dll/death.cpp new file mode 100644 index 00000000..98a9e84c --- /dev/null +++ b/hlsdk/dmc/cl_dll/death.cpp @@ -0,0 +1,293 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// death notice +// +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "vgui_viewport.h" + +DECLARE_MESSAGE( m_DeathNotice, DeathMsg ); + +struct DeathNoticeItem { + char szKiller[MAX_PLAYER_NAME_LENGTH*2]; + char szVictim[MAX_PLAYER_NAME_LENGTH*2]; + int iId; // the index number of the associated sprite + int iSuicide; + int iTeamKill; + float flDisplayTime; + float *KillerColor; + float *VictimColor; +}; + +#define MAX_DEATHNOTICES 4 +static int DEATHNOTICE_DISPLAY_TIME = 6; + +#define DEATHNOTICE_TOP 32 + +DeathNoticeItem rgDeathNoticeList[ MAX_DEATHNOTICES + 1 ]; + +extern extra_player_info_t g_PlayerExtraInfo[MAX_PLAYERS+1]; +extern hud_player_info_t g_PlayerInfoList[MAX_PLAYERS+1]; + +float g_ColorBlue[3] = { 0.6, 0.8, 1.0 }; +float g_ColorRed[3] = { 1.0, 0.25, 0.25 }; +float g_ColorGreen[3] = { 0.6, 1.0, 0.6 }; +float g_ColorYellow[3] = { 1.0, 0.7, 0.0 }; +float g_ColorYellowish[3] = { 1.0, 0.625, 0.0 }; + +float *GetClientColor( int clientIndex ) +{ + const char *teamName = g_PlayerExtraInfo[ clientIndex].teamname; + + if ( !teamName || *teamName == 0 ) + return g_ColorYellowish; + + if ( !stricmp( "blue", teamName ) ) + return g_ColorBlue; + else if ( !stricmp( "red", teamName ) ) + return g_ColorRed; + else if ( !stricmp( "green", teamName ) ) + return g_ColorGreen; + else if ( !stricmp( "yellow", teamName ) ) + return g_ColorYellow; + + return g_ColorYellowish; +} + +int GetTeamIndex( int clientIndex ) +{ + const char *teamName = g_PlayerExtraInfo[ clientIndex].teamname; + + if ( !teamName || *teamName == 0 ) + return NULL; + + if ( !stricmp( "red", teamName ) ) + return 1; + else if ( !stricmp( "blue", teamName ) ) + return 2; + + return 0; +} + +int CHudDeathNotice :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( DeathMsg ); + + CVAR_CREATE( "hud_deathnotice_time", "6", 0 ); + + return 1; +} + + +void CHudDeathNotice :: InitHUDData( void ) +{ + memset( rgDeathNoticeList, 0, sizeof(rgDeathNoticeList) ); +} + + +int CHudDeathNotice :: VidInit( void ) +{ + m_HUD_d_skull = gHUD.GetSpriteIndex( "d_skull" ); + + return 1; +} + +int CHudDeathNotice :: Draw( float flTime ) +{ + int x, y, r, g, b; + + for ( int i = 0; i < MAX_DEATHNOTICES; i++ ) + { + if ( rgDeathNoticeList[i].iId == 0 ) + break; // we've gone through them all + + if ( rgDeathNoticeList[i].flDisplayTime < flTime ) + { // display time has expired + // remove the current item from the list + memmove( &rgDeathNoticeList[i], &rgDeathNoticeList[i+1], sizeof(DeathNoticeItem) * (MAX_DEATHNOTICES - i) ); + i--; // continue on the next item; stop the counter getting incremented + continue; + } + + rgDeathNoticeList[i].flDisplayTime = min( rgDeathNoticeList[i].flDisplayTime, gHUD.m_flTime + DEATHNOTICE_DISPLAY_TIME ); + + // Draw the death notice + y = YRES(DEATHNOTICE_TOP) + 2 + (20 * i); //!!! + + 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); + + if ( !rgDeathNoticeList[i].iSuicide ) + { + x -= (5 + ConsoleStringLen( rgDeathNoticeList[i].szKiller ) ); + + // Draw killers name + if ( rgDeathNoticeList[i].KillerColor ) + gEngfuncs.pfnDrawSetTextColor( rgDeathNoticeList[i].KillerColor[0], rgDeathNoticeList[i].KillerColor[1], rgDeathNoticeList[i].KillerColor[2] ); + x = 5 + DrawConsoleString( x, y, rgDeathNoticeList[i].szKiller ); + } + + r = 255; g = 80; b = 0; + 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) ); + + x += (gHUD.GetSpriteRect(id).right - gHUD.GetSpriteRect(id).left); + + // Draw victims name + if ( rgDeathNoticeList[i].VictimColor ) + gEngfuncs.pfnDrawSetTextColor( rgDeathNoticeList[i].VictimColor[0], rgDeathNoticeList[i].VictimColor[1], rgDeathNoticeList[i].VictimColor[2] ); + x = DrawConsoleString( x, y, rgDeathNoticeList[i].szVictim ); + } + + return 1; +} + + +// This message handler may be better off elsewhere +int CHudDeathNotice :: MsgFunc_DeathMsg( const char *pszName, int iSize, void *pbuf ) +{ + m_iFlags |= HUD_ACTIVE; + + BEGIN_READ( pbuf, iSize ); + + int killer = READ_BYTE(); + int victim = READ_BYTE(); + + char killedwith[32]; + strcpy( killedwith, "d_" ); + strncat( killedwith, READ_STRING(), 32 ); + + if (gViewPort) + gViewPort->DeathMsg( killer, victim ); + + gHUD.m_Spectator.DeathMessage(victim); + int i; + for ( i = 0; i < MAX_DEATHNOTICES; i++ ) + { + if ( rgDeathNoticeList[i].iId == 0 ) + break; + } + if ( i == MAX_DEATHNOTICES ) + { // move the rest of the list forward to make room for this item + memmove( rgDeathNoticeList, rgDeathNoticeList+1, sizeof(DeathNoticeItem) * MAX_DEATHNOTICES ); + i = MAX_DEATHNOTICES - 1; + } + + //gHUD.m_Scoreboard.GetAllPlayersInfo(); + + if ( gViewPort ) + gViewPort->GetAllPlayersInfo(); + + char *killer_name = g_PlayerInfoList[ killer ].name; + char *victim_name = g_PlayerInfoList[ victim ].name; + if ( !killer_name ) + { + killer_name = ""; + rgDeathNoticeList[i].szKiller[0] = 0; + } + else + { + rgDeathNoticeList[i].KillerColor = GetClientColor( killer ); + strncpy( rgDeathNoticeList[i].szKiller, killer_name, MAX_PLAYER_NAME_LENGTH ); + rgDeathNoticeList[i].szKiller[MAX_PLAYER_NAME_LENGTH-1] = 0; + } + + if ( !victim_name ) + { + victim_name = ""; + rgDeathNoticeList[i].szVictim[0] = 0; + } + else + { + rgDeathNoticeList[i].VictimColor = GetClientColor( victim ); + strncpy( rgDeathNoticeList[i].szVictim, victim_name, MAX_PLAYER_NAME_LENGTH ); + rgDeathNoticeList[i].szVictim[MAX_PLAYER_NAME_LENGTH-1] = 0; + } + + if ( killer == victim || killer == 0 ) + rgDeathNoticeList[i].iSuicide = TRUE; + + if ( !strcmp( killedwith, "d_teammate" ) ) + rgDeathNoticeList[i].iTeamKill = TRUE; + + // Find the sprite in the list + int spr = gHUD.GetSpriteIndex( killedwith ); + + rgDeathNoticeList[i].iId = spr; + + DEATHNOTICE_DISPLAY_TIME = CVAR_GET_FLOAT( "hud_deathnotice_time" ); + rgDeathNoticeList[i].flDisplayTime = gHUD.m_flTime + DEATHNOTICE_DISPLAY_TIME; + + // 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" ); + + return 1; +} + + + diff --git a/hlsdk/dmc/cl_dll/demo.cpp b/hlsdk/dmc/cl_dll/demo.cpp new file mode 100644 index 00000000..67e51cfa --- /dev/null +++ b/hlsdk/dmc/cl_dll/demo.cpp @@ -0,0 +1,63 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "hud.h" +#include "cl_util.h" +#include "demo.h" +#include "demo_api.h" +#include + +#define DLLEXPORT __declspec( dllexport ) + +extern "C" +{ + void DLLEXPORT Demo_ReadBuffer( int size, unsigned char *buffer ); +} + +/* +===================== +Demo_WriteBuffer + +Write some data to the demo stream +===================== +*/ +void Demo_WriteBuffer( int type, int size, unsigned char *buffer ) +{ + int pos = 0; + unsigned char buf[ 32 * 1024 ]; + *( int * )&buf[pos] = type; + pos+=sizeof( int ); + + memcpy( &buf[pos], buffer, size ); + + // Write full buffer out + gEngfuncs.pDemoAPI->WriteBuffer( size + sizeof( int ), buf ); +} + +/* +===================== +Demo_ReadBuffer + +Engine wants us to parse some data from the demo stream +===================== +*/ +void DLLEXPORT Demo_ReadBuffer( int size, unsigned char *buffer ) +{ + int type; + int i = 0; + + type = *( int * )buffer; + i += sizeof( int ); + switch ( type ) + { + case TYPE_USER: + break; + default: + gEngfuncs.Con_DPrintf( "Unknown demo buffer type, skipping.\n" ); + break; + } +} \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/demo.h b/hlsdk/dmc/cl_dll/demo.h new file mode 100644 index 00000000..a220327b --- /dev/null +++ b/hlsdk/dmc/cl_dll/demo.h @@ -0,0 +1,20 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( DEMOH ) +#define DEMOH +#pragma once + +// Types of demo messages we can write/parse +enum +{ + TYPE_USER = 0, +}; + +void Demo_WriteBuffer( int type, int size, unsigned char *buffer ); + +#endif \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/entity.cpp b/hlsdk/dmc/cl_dll/entity.cpp new file mode 100644 index 00000000..3717f091 --- /dev/null +++ b/hlsdk/dmc/cl_dll/entity.cpp @@ -0,0 +1,861 @@ +/**** +* +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Client side entity management functions +#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 "usercmd.h" +#include "event_api.h" +#include "pm_defs.h" +#include "pm_shared.h" +#include "pmtrace.h" +#include "voice_status.h" + +void Game_AddObjects( void ); + +#define DLLEXPORT __declspec( dllexport ) + +extern vec3_t v_origin; + +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 ); + void DLLEXPORT HUD_TempEntUpdate( double frametime, double client_time, double cl_gravity, struct tempent_s **ppTempEntFree, struct tempent_s **ppTempEntActive, int ( *Callback_AddVisibleEntity )( struct cl_entity_s *pEntity ), void ( *Callback_TempEntPlaySound )( struct tempent_s *pTemp, float damp ) ); + struct cl_entity_s DLLEXPORT *HUD_GetUserEntity( int index ); +} + +/* +======================== +HUD_AddEntity + Return 0 to filter entity from visible list for rendering +======================== +*/ +int DLLEXPORT HUD_AddEntity( int type, struct cl_entity_s *ent, const char *modelname ) +{ + switch ( type ) + { + case ET_NORMAL: + case ET_PLAYER: + case ET_BEAM: + case ET_TEMPENTITY: + case ET_FRAGMENTED: + default: + break; + } + + // each frame every entity passes this function, so the overview hooks it to filter the overview entities + // in spectator mode: + // each frame every entity passes this function, so the overview hooks + // it to filter the overview entities + + if ( g_iUser1 ) + { + gHUD.m_Spectator.AddOverviewEntity( type, ent, modelname ); + + if ( ( g_iUser1 == OBS_IN_EYE || gHUD.m_Spectator.m_pip->value == INSET_IN_EYE ) && + ent->index == g_iUser2 ) + return 0; // don't draw the player we are following in eye + + } + return 1; +} + +/* +========================= +HUD_TxferLocalOverrides + +The server sends us our origin with extra precision as part of the clientdata structure, not during the normal +playerstate update in entity_state_t. In order for these overrides to eventually get to the appropriate playerstate +structure, we need to copy them into the state structure at this point. +========================= +*/ +void DLLEXPORT HUD_TxferLocalOverrides( struct entity_state_s *state, const struct clientdata_s *client ) +{ + VectorCopy( client->origin, state->origin ); + + // Observer + state->iuser1 = client->iuser1; + state->iuser2 = client->iuser2; +} + +/* +========================= +HUD_ProcessPlayerState + +We have received entity_state_t for this player over the network. We need to copy appropriate fields to the +playerstate structure +========================= +*/ +void DLLEXPORT HUD_ProcessPlayerState( struct entity_state_s *dst, const struct entity_state_s *src ) +{ + // Copy in network data + VectorCopy( src->origin, dst->origin ); + VectorCopy( src->angles, dst->angles ); + + VectorCopy( src->velocity, dst->velocity ); + + dst->frame = src->frame; + dst->modelindex = src->modelindex; + dst->skin = src->skin; + dst->effects = src->effects; + dst->weaponmodel = src->weaponmodel; + dst->movetype = src->movetype; + dst->sequence = src->sequence; + dst->animtime = src->animtime; + + dst->solid = src->solid; + + dst->rendermode = src->rendermode; + dst->renderamt = src->renderamt; + dst->rendercolor.r = src->rendercolor.r; + dst->rendercolor.g = src->rendercolor.g; + dst->rendercolor.b = src->rendercolor.b; + dst->renderfx = src->renderfx; + + dst->framerate = src->framerate; + dst->body = src->body; + + memcpy( &dst->controller[0], &src->controller[0], 4 * sizeof( byte ) ); + memcpy( &dst->blending[0], &src->blending[0], 2 * sizeof( byte ) ); + + VectorCopy( src->basevelocity, dst->basevelocity ); + + dst->friction = src->friction; + dst->gravity = src->gravity; + dst->gaitsequence = src->gaitsequence; + dst->spectator = src->spectator; + dst->usehull = src->usehull; + dst->playerclass = src->playerclass; + dst->team = src->team; + dst->colormap = src->colormap; + // Save off some data so other areas of the Client DLL can get to it + cl_entity_t *player = gEngfuncs.GetLocalPlayer(); // Get the local player's index + if ( dst->number == player->index ) + { + g_iUser1 = src->iuser1; + g_iUser2 = src->iuser2; + g_iUser3 = src->iuser3; + } +} + +/* +========================= +HUD_TxferPredictionData + +Because we can predict an arbitrary number of frames before the server responds with an update, we need to be able to copy client side prediction data in + from the state that the server ack'd receiving, which can be anywhere along the predicted frame path ( i.e., we could predict 20 frames into the future and the server ack's + up through 10 of those frames, so we need to copy persistent client-side only state from the 10th predicted frame to the slot the server + update is occupying. +========================= +*/ +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 ) +{ + ps->oldbuttons = pps->oldbuttons; + ps->flFallVelocity = pps->flFallVelocity; + ps->iStepLeft = pps->iStepLeft; + ps->playerclass = pps->playerclass; + + pcd->viewmodel = ppcd->viewmodel; + pcd->m_iId = ppcd->m_iId; + pcd->ammo_shells = ppcd->ammo_shells; + pcd->ammo_nails = ppcd->ammo_nails; + pcd->ammo_cells = ppcd->ammo_cells; + pcd->ammo_rockets = ppcd->ammo_rockets; + pcd->m_flNextAttack = ppcd->m_flNextAttack; + pcd->fov = ppcd->fov; + pcd->weaponanim = ppcd->weaponanim; + pcd->tfstate = ppcd->tfstate; + pcd->maxspeed = ppcd->maxspeed; + + pcd->deadflag = ppcd->deadflag; + + // Observer + pcd->iuser1 = ppcd->iuser1; + pcd->iuser2 = ppcd->iuser2; + + if ( gEngfuncs.IsSpectateOnly() ) + { + // in specator mode we tell the engine who we want to spectate and how + // iuser3 is not used for duck prevention (since the spectator can't duck at all) + pcd->iuser1 = g_iUser1; // observer mode + pcd->iuser2 = g_iUser2; // first target + pcd->iuser3 = g_iUser3; // second target + } + // m_iQuakeItems + pcd->iuser3 = ppcd->iuser3; + // m_iQuakeWeapon # + pcd->fuser1 = ppcd->fuser1; + // m_iNailIndex + pcd->fuser2 = ppcd->fuser2; + // m_iRuneStatus + pcd->fuser3 = ppcd->fuser3; + + memcpy( wd, pwd, 32 * sizeof( weapon_data_t ) ); +} + +/* +//#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 +*/ + +/* +void ParticleCallback( struct particle_s *particle, float frametime ) +{ + int i; + + for ( i = 0; i < 3; i++ ) + { + particle->org[ i ] += particle->vel[ i ] * frametime; + } +} + +void Particles( void ) +{ + static float lasttime; + float curtime; + + curtime = gEngfuncs.GetClientTime(); + + if ( ( curtime - lasttime ) < 10.0 ) + return; + + lasttime = curtime; + + // Create a few particles + particle_t *p; + int i, j; + + for ( i = 0; i < 100; i++ ) + { + p = gEngfuncs.pEfxAPI->R_AllocParticle( ParticleCallback ); + if ( !p ) + break; + + for ( j = 0; j < 3; j++ ) + { + p->org[ j ] = v_origin[ j ]; + p->vel[ j ] = gEngfuncs.pfnRandomFloat( -100.0, 100.0 ); + } + + p->color = gEngfuncs.pfnRandomLong( 0, 255 ); + gEngfuncs.pEfxAPI->R_GetPackedColor( &p->packedColor, p->color ); + + // p->die is set to current time so all you have to do is add an additional time to it + p->die += 5.0; + } +} +*/ + +/* +void TempEntCallback ( struct tempent_s *ent, float frametime, float currenttime ) +{ + int i; + + for ( i = 0; i < 3; i++ ) + { + ent->entity.curstate.origin[ i ] += ent->entity.baseline.origin[ i ] * frametime; + } +} + +void TempEnts( void ) +{ + static float lasttime; + float curtime; + + curtime = gEngfuncs.GetClientTime(); + + if ( ( curtime - lasttime ) < 10.0 ) + return; + + lasttime = curtime; + + TEMPENTITY *p; + int i, j; + struct model_s *mod; + vec3_t origin; + int index; + + mod = gEngfuncs.CL_LoadModel( "sprites/laserdot.spr", &index ); + + for ( i = 0; i < 100; i++ ) + { + for ( j = 0; j < 3; j++ ) + { + origin[ j ] = v_origin[ j ]; + if ( j != 2 ) + { + origin[ j ] += 75; + } + } + + p = gEngfuncs.pEfxAPI->CL_TentEntAllocCustom( (float *)&origin, mod, 0, TempEntCallback ); + if ( !p ) + break; + + for ( j = 0; j < 3; j++ ) + { + p->entity.curstate.origin[ j ] = origin[ j ]; + + // Store velocity in baseline origin + p->entity.baseline.origin[ j ] = gEngfuncs.pfnRandomFloat( -100, 100 ); + } + + // p->die is set to current time so all you have to do is add an additional time to it + p->die += 10.0; + } +} +*/ + +/* +========================= +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(); +*/ + // 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 + +Simulation and cleanup of temporary entities +================= +*/ +void DLLEXPORT HUD_TempEntUpdate ( + double frametime, // Simulation time + double client_time, // Absolute time on client + double cl_gravity, // True gravity on client + TEMPENTITY **ppTempEntFree, // List of freed temporary ents + TEMPENTITY **ppTempEntActive, // List + int ( *Callback_AddVisibleEntity )( cl_entity_t *pEntity ), + void ( *Callback_TempEntPlaySound )( TEMPENTITY *pTemp, float damp ) ) +{ + static int gTempEntFrame = 0; + int i; + TEMPENTITY *pTemp, *pnext, *pprev; + float freq, gravity, gravitySlow, life, fastFreq; + + // Nothing to simulate + if ( !*ppTempEntActive ) + return; + + // in order to have tents collide with players, we have to run the player prediction code so + // that the client has the player list. We run this code once when we detect any COLLIDEALL + // tent, then set this BOOL to true so the code doesn't get run again if there's more than + // one COLLIDEALL ent for this update. (often are). + 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 ( -1 ); + + // !!!BUGBUG -- This needs to be time based + gTempEntFrame = (gTempEntFrame+1) & 31; + + pTemp = *ppTempEntActive; + + // !!! Don't simulate while paused.... This is sort of a hack, revisit. + if ( frametime <= 0 ) + { + while ( pTemp ) + { + if ( !(pTemp->flags & FTENT_NOMODEL ) ) + { + Callback_AddVisibleEntity( &pTemp->entity ); + } + pTemp = pTemp->next; + } + goto finish; + } + + pprev = NULL; + freq = client_time * 0.01; + fastFreq = client_time * 5.5; + gravity = -frametime * cl_gravity; + gravitySlow = gravity * 0.5; + + while ( pTemp ) + { + int active; + + active = 1; + + life = pTemp->die - client_time; + pnext = pTemp->next; + if ( life < 0 ) + { + if ( pTemp->flags & FTENT_FADEOUT ) + { + if (pTemp->entity.curstate.rendermode == kRenderNormal) + pTemp->entity.curstate.rendermode = kRenderTransTexture; + pTemp->entity.curstate.renderamt = pTemp->entity.baseline.renderamt * ( 1 + life * pTemp->fadeSpeed ); + if ( pTemp->entity.curstate.renderamt <= 0 ) + active = 0; + + } + else + active = 0; + } + if ( !active ) // Kill it + { + pTemp->next = *ppTempEntFree; + *ppTempEntFree = pTemp; + if ( !pprev ) // Deleting at head of list + *ppTempEntActive = pnext; + else + pprev->next = pnext; + } + else + { + pprev = pTemp; + + VectorCopy( pTemp->entity.origin, pTemp->entity.prevstate.origin ); + + if ( pTemp->flags & FTENT_SPARKSHOWER ) + { + // Adjust speed if it's time + // Scale is next think time + if ( client_time > pTemp->entity.baseline.scale ) + { + // Show Sparks + gEngfuncs.pEfxAPI->R_SparkEffect( pTemp->entity.origin, 8, -200, 200 ); + + // Reduce life + pTemp->entity.baseline.framerate -= 0.1; + + if ( pTemp->entity.baseline.framerate <= 0.0 ) + { + pTemp->die = client_time; + } + else + { + // So it will die no matter what + pTemp->die = client_time + 0.5; + + // Next think + pTemp->entity.baseline.scale = client_time + 0.1; + } + } + } + else if ( pTemp->flags & FTENT_PLYRATTACHMENT ) + { + cl_entity_t *pClient; + + pClient = gEngfuncs.GetEntityByIndex( pTemp->clientIndex ); + + VectorAdd( pClient->origin, pTemp->tentOffset, pTemp->entity.origin ); + } + else if ( pTemp->flags & FTENT_SINEWAVE ) + { + pTemp->x += pTemp->entity.baseline.origin[0] * frametime; + pTemp->y += pTemp->entity.baseline.origin[1] * frametime; + + pTemp->entity.origin[0] = pTemp->x + sin( pTemp->entity.baseline.origin[2] + client_time * pTemp->entity.prevstate.frame ) * (10*pTemp->entity.curstate.framerate); + pTemp->entity.origin[1] = pTemp->y + sin( pTemp->entity.baseline.origin[2] + fastFreq + 0.7 ) * (8*pTemp->entity.curstate.framerate); + pTemp->entity.origin[2] += pTemp->entity.baseline.origin[2] * frametime; + } + else if ( pTemp->flags & FTENT_SPIRAL ) + { + float s, c; + s = sin( pTemp->entity.baseline.origin[2] + fastFreq ); + c = cos( pTemp->entity.baseline.origin[2] + fastFreq ); + + pTemp->entity.origin[0] += pTemp->entity.baseline.origin[0] * frametime + 8 * sin( client_time * 20 + (int)pTemp ); + 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++ ) + pTemp->entity.origin[i] += pTemp->entity.baseline.origin[i] * frametime; + } + + if ( pTemp->flags & FTENT_SPRANIMATE ) + { + pTemp->entity.curstate.frame += frametime * pTemp->entity.curstate.framerate; + if ( pTemp->entity.curstate.frame >= pTemp->frameMax ) + { + pTemp->entity.curstate.frame = pTemp->entity.curstate.frame - (int)(pTemp->entity.curstate.frame); + + if ( !(pTemp->flags & FTENT_SPRANIMATELOOP) ) + { + // this animating sprite isn't set to loop, so destroy it. + pTemp->die = client_time; + pTemp = pnext; + continue; + } + } + } + else if ( pTemp->flags & FTENT_SPRCYCLE ) + { + pTemp->entity.curstate.frame += frametime * 10; + if ( pTemp->entity.curstate.frame >= pTemp->frameMax ) + { + pTemp->entity.curstate.frame = pTemp->entity.curstate.frame - (int)(pTemp->entity.curstate.frame); + } + } +// Experiment +#if 0 + if ( pTemp->flags & FTENT_SCALE ) + pTemp->entity.curstate.framerate += 20.0 * (frametime / pTemp->entity.curstate.framerate); +#endif + + if ( pTemp->flags & FTENT_ROTATE ) + { + pTemp->entity.angles[0] += pTemp->entity.baseline.angles[0] * frametime; + pTemp->entity.angles[1] += pTemp->entity.baseline.angles[1] * frametime; + pTemp->entity.angles[2] += pTemp->entity.baseline.angles[2] * frametime; + + VectorCopy( pTemp->entity.angles, pTemp->entity.latched.prevangles ); + } + + if ( pTemp->flags & (FTENT_COLLIDEALL | FTENT_COLLIDEWORLD) ) + { + vec3_t traceNormal; + float traceFraction = 1; + + if ( pTemp->flags & FTENT_COLLIDEALL ) + { + pmtrace_t pmtrace; + physent_t *pe; + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + + gEngfuncs.pEventAPI->EV_PlayerTrace( pTemp->entity.prevstate.origin, pTemp->entity.origin, PM_STUDIO_BOX, -1, &pmtrace ); + + + if ( pmtrace.fraction != 1 ) + { + pe = gEngfuncs.pEventAPI->EV_GetPhysent( pmtrace.ent ); + + if ( !pmtrace.ent || ( pe->info != pTemp->clientIndex ) ) + { + traceFraction = pmtrace.fraction; + VectorCopy( pmtrace.plane.normal, traceNormal ); + + if ( pTemp->hitcallback ) + { + (*pTemp->hitcallback)( pTemp, &pmtrace ); + } + } + } + } + else if ( pTemp->flags & FTENT_COLLIDEWORLD ) + { + pmtrace_t pmtrace; + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + + gEngfuncs.pEventAPI->EV_PlayerTrace( pTemp->entity.prevstate.origin, pTemp->entity.origin, PM_STUDIO_BOX | PM_WORLD_ONLY, -1, &pmtrace ); + + if ( pmtrace.fraction != 1 ) + { + traceFraction = pmtrace.fraction; + VectorCopy( pmtrace.plane.normal, traceNormal ); + + if ( pTemp->flags & FTENT_SPARKSHOWER ) + { + // Chop spark speeds a bit more + // + VectorScale( pTemp->entity.baseline.origin, 0.6, pTemp->entity.baseline.origin ); + + if ( Length( pTemp->entity.baseline.origin ) < 10 ) + { + pTemp->entity.baseline.framerate = 0.0; + } + } + + if ( pTemp->hitcallback ) + { + (*pTemp->hitcallback)( pTemp, &pmtrace ); + } + } + } + + if ( traceFraction != 1 ) // Decent collision now, and damping works + { + float proj, damp; + + // Place at contact point + VectorMA( pTemp->entity.prevstate.origin, traceFraction*frametime, pTemp->entity.baseline.origin, pTemp->entity.origin ); + // Damp velocity + damp = pTemp->bounceFactor; + if ( pTemp->flags & (FTENT_GRAVITY|FTENT_SLOWGRAVITY) ) + { + damp *= 0.5; + if ( traceNormal[2] > 0.9 ) // Hit floor? + { + if ( pTemp->entity.baseline.origin[2] <= 0 && pTemp->entity.baseline.origin[2] >= gravity*3 ) + { + damp = 0; // Stop + pTemp->flags &= ~(FTENT_ROTATE|FTENT_GRAVITY|FTENT_SLOWGRAVITY|FTENT_COLLIDEWORLD|FTENT_SMOKETRAIL); + pTemp->entity.angles[0] = 0; + pTemp->entity.angles[2] = 0; + } + } + } + + if (pTemp->hitSound) + { + Callback_TempEntPlaySound(pTemp, damp); + } + + if (pTemp->flags & FTENT_COLLIDEKILL) + { + // die on impact + pTemp->flags &= ~FTENT_FADEOUT; + pTemp->die = client_time; + } + else + { + // Reflect velocity + if ( damp != 0 ) + { + proj = DotProduct( pTemp->entity.baseline.origin, traceNormal ); + VectorMA( pTemp->entity.baseline.origin, -proj*2, traceNormal, pTemp->entity.baseline.origin ); + // Reflect rotation (fake) + + pTemp->entity.angles[1] = -pTemp->entity.angles[1]; + } + + if ( damp != 1 ) + { + + VectorScale( pTemp->entity.baseline.origin, damp, pTemp->entity.baseline.origin ); + VectorScale( pTemp->entity.angles, 0.9, pTemp->entity.angles ); + } + } + } + } + + + if ( (pTemp->flags & FTENT_FLICKER) && gTempEntFrame == pTemp->entity.curstate.effects ) + { + dlight_t *dl = gEngfuncs.pEfxAPI->CL_AllocDlight (0); + VectorCopy (pTemp->entity.origin, dl->origin); + dl->radius = 60; + dl->color.r = 255; + dl->color.g = 120; + dl->color.b = 0; + dl->die = client_time + 0.01; + } + + if ( pTemp->flags & FTENT_SMOKETRAIL ) + { + if ( pTemp->entity.baseline.sequence == 69 ) // Little smoke + gEngfuncs.pEfxAPI->R_RocketTrail ( pTemp->entity.prevstate.origin, pTemp->entity.origin, 1 ); + else if ( pTemp->entity.baseline.sequence == 70 ) // Rocket Powered smoke ( heh? ) + gEngfuncs.pEfxAPI->R_RocketTrail ( pTemp->entity.prevstate.origin, pTemp->entity.origin, 0 ); + else + gEngfuncs.pEfxAPI->R_RocketTrail ( pTemp->entity.prevstate.origin, pTemp->entity.origin, 2 ); + } + + + 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 ) + { + if ( pTemp->callback ) + { + ( *pTemp->callback )( pTemp, frametime, client_time ); + } + } + + // Cull to PVS (not frustum cull, just PVS) + if ( !(pTemp->flags & FTENT_NOMODEL ) ) + { + if ( !Callback_AddVisibleEntity( &pTemp->entity ) ) + { + if ( !(pTemp->flags & FTENT_PERSIST) ) + { + pTemp->die = client_time; // If we can't draw it this frame, just dump it. + pTemp->flags &= ~FTENT_FADEOUT; // Don't fade out, just die + } + } + } + } + pTemp = pnext; + } + +finish: + // Restore state info + gEngfuncs.pEventAPI->EV_PopPMStates(); +} + +/* +================= +HUD_GetUserEntity + +If you specify negative numbers for beam start and end point entities, then + the engine will call back into this function requesting a pointer to a cl_entity_t + object that describes the entity to attach the beam onto. + +Indices must start at 1, not zero. +================= +*/ +cl_entity_t DLLEXPORT *HUD_GetUserEntity( int index ) +{ +return NULL; +} diff --git a/hlsdk/dmc/cl_dll/ev_common.cpp b/hlsdk/dmc/cl_dll/ev_common.cpp new file mode 100644 index 00000000..91384095 --- /dev/null +++ b/hlsdk/dmc/cl_dll/ev_common.cpp @@ -0,0 +1,198 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// shared event functions +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" + +#include "r_efx.h" + +#include "eventscripts.h" +#include "event_api.h" + +/* +================= +GetEntity + +Return's the requested cl_entity_t +================= +*/ +struct cl_entity_s *GetEntity( int idx ) +{ + return gEngfuncs.GetEntityByIndex( idx ); +} + +/* +================= +GetViewEntity + +Return's the current weapon/view model +================= +*/ +struct cl_entity_s *GetViewEntity( void ) +{ + return gEngfuncs.GetViewModel(); +} + +/* +================= +EV_CreateTracer + +Creates a tracer effect +================= +*/ +void EV_CreateTracer( float *start, float *end ) +{ + gEngfuncs.pEfxAPI->R_TracerEffect( start, end ); +} + +/* +================= +EV_IsPlayer + +Is the entity's index in the player range? +================= +*/ +qboolean EV_IsPlayer( int idx ) +{ + if ( idx >= 1 && idx <= gEngfuncs.GetMaxClients() ) + return true; + + return false; +} + +/* +================= +EV_IsLocal + +Is the entity == the local player +================= +*/ +qboolean EV_IsLocal( int idx ) +{ + return gEngfuncs.pEventAPI->EV_IsLocal( idx - 1 ) ? true : false; +} + +/* +================= +EV_GetGunPosition + +Figure out the height of the gun +================= +*/ +void EV_GetGunPosition( event_args_t *args, float *pos, float *origin ) +{ + int idx; + vec3_t view_ofs; + + idx = args->entindex; + + VectorClear( view_ofs ); + view_ofs[2] = DEFAULT_VIEWHEIGHT; + + if ( EV_IsPlayer( idx ) ) + { + if ( EV_IsLocal( idx ) ) + { + // Grab predicted result for local player + gEngfuncs.pEventAPI->EV_LocalPlayerViewheight( view_ofs ); + } + else if ( args->ducking == 1 ) + { + view_ofs[2] = VEC_DUCK_VIEW; + } + } + + VectorAdd( origin, view_ofs, pos ); +} + +/* +================= +EV_EjectBrass + +Bullet shell casings +================= +*/ +void EV_EjectBrass( float *origin, float *velocity, float rotation, int model, int soundtype ) +{ + vec3_t endpos; + VectorClear( endpos ); + endpos[1] = rotation; + gEngfuncs.pEfxAPI->R_TempModel( origin, velocity, endpos, 2.5, model, soundtype ); +} + +/* +================= +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 ) +{ + int i; + vec3_t view_ofs; + float fR, fU; + + int idx; + + idx = args->entindex; + + VectorClear( view_ofs ); + view_ofs[2] = DEFAULT_VIEWHEIGHT; + + if ( EV_IsPlayer( idx ) ) + { + if ( EV_IsLocal( idx ) ) + { + gEngfuncs.pEventAPI->EV_LocalPlayerViewheight( view_ofs ); + } + else if ( args->ducking == 1 ) + { + view_ofs[2] = VEC_DUCK_VIEW; + } + } + + fR = gEngfuncs.pfnRandomFloat( 50, 70 ); + fU = gEngfuncs.pfnRandomFloat( 100, 150 ); + + 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; + } +} + +/* +================= +EV_MuzzleFlash + +Flag weapon/view model for muzzle flash +================= +*/ +void EV_MuzzleFlash( void ) +{ + // Add muzzle flash to current weapon model + cl_entity_t *ent = GetViewEntity(); + if ( !ent ) + { + return; + } + + // Or in the muzzle flash + ent->curstate.effects |= EF_MUZZLEFLASH; +} \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/ev_hldm.cpp b/hlsdk/dmc/cl_dll/ev_hldm.cpp new file mode 100644 index 00000000..d234e2cd --- /dev/null +++ b/hlsdk/dmc/cl_dll/ev_hldm.cpp @@ -0,0 +1,1340 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#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_hldm.h" + +#include "r_efx.h" +#include "event_api.h" +#include "event_args.h" +#include "in_defs.h" + +#include + +// QUAKECLASSIC +#define Q_SMALL_PUNCHANGLE_KICK -2 +#define Q_BIG_PUNCHANGLE_KICK -4 + +extern "C" char PM_FindTextureType( char *name ); + +void V_PunchAxis( int axis, float punch ); +extern vec3_t v_origin; + +extern "C" +{ + +// HLDM +void EV_FireShotGunSingle( struct event_args_s *args ); +void EV_FireShotGunDouble( struct event_args_s *args ); +void EV_FireAxe( struct event_args_s *args ); +void EV_FireAxeSwing( struct event_args_s *args ); +void EV_FireRocket( struct event_args_s *args ); +void EV_FireLightning( struct event_args_s *args ); +void EV_FireSpike( struct event_args_s *args ); +void EV_FireSuperSpike( struct event_args_s *args ); +void EV_FireGrenade( struct event_args_s *args ); +void EV_Gibbed( event_args_t *args ); +void EV_Teleport( event_args_t *args ); +void EV_Trail( event_args_t *args ); +void EV_Explosion( event_args_t *args ); + +void EV_PlayerPowerup( struct event_args_s *args ); + +void EV_DMC_DoorGoUp( struct event_args_s *args ); +void EV_DMC_DoorGoDown( struct event_args_s *args ); +void EV_DMC_DoorHitTop( struct event_args_s *args ); +void EV_DMC_DoorHitBottom( struct event_args_s *args ); + +void EV_TrainPitchAdjust( 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 +float EV_HLDM_PlayTextureSound( int idx, pmtrace_t *ptr, float *vecSrc, float *vecEnd, int iBulletType ) +{ + // hit the world, try to play sound based on texture material type + char chTextureType = CHAR_TEX_CONCRETE; + float fvol; + float fvolbar; + char *rgsz[4]; + int cnt; + float fattn = ATTN_NORM; + int entity; + char *pTextureName; + char texname[ 64 ]; + char szbuffer[ 64 ]; + + entity = gEngfuncs.pEventAPI->EV_IndexFromTrace( ptr ); + + // FIXME check if playtexture sounds movevar is set + // + + chTextureType = 0; + + // Player + if ( entity >= 1 && entity <= gEngfuncs.GetMaxClients() ) + { + // hit body + chTextureType = CHAR_TEX_FLESH; + } + else if ( entity == 0 ) + { + // get texture from entity or world (world is ent(0)) + pTextureName = (char *)gEngfuncs.pEventAPI->EV_TraceTexture( ptr->ent, vecSrc, vecEnd ); + + if ( pTextureName ) + { + strcpy( texname, pTextureName ); + pTextureName = texname; + + // strip leading '-0' or '+0~' or '{' or '!' + if (*pTextureName == '-' || *pTextureName == '+') + { + pTextureName += 2; + } + + if (*pTextureName == '{' || *pTextureName == '!' || *pTextureName == '~' || *pTextureName == ' ') + { + pTextureName++; + } + + // '}}' + strcpy( szbuffer, pTextureName ); + szbuffer[ CBTEXTURENAMEMAX - 1 ] = 0; + + // get texture type + chTextureType = PM_FindTextureType( szbuffer ); + } + } + + switch (chTextureType) + { + default: + case CHAR_TEX_CONCRETE: fvol = 0.9; fvolbar = 0.6; + rgsz[0] = "player/pl_step1.wav"; + rgsz[1] = "player/pl_step2.wav"; + cnt = 2; + break; + case CHAR_TEX_METAL: fvol = 0.9; fvolbar = 0.3; + rgsz[0] = "player/pl_metal1.wav"; + rgsz[1] = "player/pl_metal2.wav"; + cnt = 2; + break; + case CHAR_TEX_DIRT: fvol = 0.9; fvolbar = 0.1; + rgsz[0] = "player/pl_dirt1.wav"; + rgsz[1] = "player/pl_dirt2.wav"; + rgsz[2] = "player/pl_dirt3.wav"; + cnt = 3; + break; + case CHAR_TEX_VENT: fvol = 0.5; fvolbar = 0.3; + rgsz[0] = "player/pl_duct1.wav"; + rgsz[1] = "player/pl_duct1.wav"; + cnt = 2; + break; + case CHAR_TEX_GRATE: fvol = 0.9; fvolbar = 0.5; + rgsz[0] = "player/pl_grate1.wav"; + rgsz[1] = "player/pl_grate4.wav"; + cnt = 2; + break; + case CHAR_TEX_TILE: fvol = 0.8; fvolbar = 0.2; + rgsz[0] = "player/pl_tile1.wav"; + rgsz[1] = "player/pl_tile3.wav"; + rgsz[2] = "player/pl_tile2.wav"; + rgsz[3] = "player/pl_tile4.wav"; + cnt = 4; + break; + case CHAR_TEX_SLOSH: fvol = 0.9; fvolbar = 0.0; + rgsz[0] = "player/pl_slosh1.wav"; + rgsz[1] = "player/pl_slosh3.wav"; + rgsz[2] = "player/pl_slosh2.wav"; + rgsz[3] = "player/pl_slosh4.wav"; + cnt = 4; + break; + case CHAR_TEX_WOOD: fvol = 0.9; fvolbar = 0.2; + rgsz[0] = "debris/wood1.wav"; + rgsz[1] = "debris/wood2.wav"; + rgsz[2] = "debris/wood3.wav"; + cnt = 3; + break; + case CHAR_TEX_GLASS: + case CHAR_TEX_COMPUTER: + fvol = 0.8; fvolbar = 0.2; + rgsz[0] = "debris/glass1.wav"; + rgsz[1] = "debris/glass2.wav"; + rgsz[2] = "debris/glass3.wav"; + 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"; + fattn = 1.0; + cnt = 2; + break; + } + + // play material hit sound + gEngfuncs.pEventAPI->EV_PlaySound( 0, ptr->endpos, CHAN_STATIC, rgsz[gEngfuncs.pfnRandomLong(0,cnt-1)], fvol, fattn, 0, 96 + gEngfuncs.pfnRandomLong(0,0xf) ); + return fvolbar; +} + +//CheckPVS see if playerIndex is in same PVS as localplayer +bool CheckPVS( int playerIndex ) +{ + //returns true if the player is in the same PVS + cl_entity_t *localPlayer = gEngfuncs.GetLocalPlayer(); + cl_entity_t *player; + + player = gEngfuncs.GetEntityByIndex( playerIndex ); + + if( !player || !localPlayer ) + return false; + + if ( player == localPlayer ) + return true; + + if( player->curstate.messagenum < localPlayer->curstate.messagenum ) + return false; + + return true; +} + + +char *EV_HLDM_RocketDamageDecal( void ) +{ + static char decalname[ 32 ]; + int idx; + + idx = gEngfuncs.pfnRandomLong( 1, 3 ); + sprintf( decalname, "{scorch%i", idx ); + return decalname; +} + +char *EV_HLDM_DamageDecal( void ) +{ + static char decalname[ 32 ]; + int idx; + + idx = gEngfuncs.pfnRandomLong( 0, 4 ); + sprintf( decalname, "{shot%i", idx + 1 ); + return decalname; +} + + +char *EV_Lightning_DamageDecal( void ) +{ + int idx; + static char decalname[ 32 ]; + //sprintf( decalname, "{smscorch1"); + + idx = gEngfuncs.pfnRandomLong( 0, 2 ); + sprintf( decalname, "{smscorch%i", idx + 1 ); + + return decalname; +} + +char *EV_Quake_DamageDecalClub( void ) +{ + static char decalname[ 32 ]; + int idx; + + idx = gEngfuncs.pfnRandomLong( 0, 4 ); + sprintf( decalname, "{shot%i", idx + 1 ); + return decalname; +} + +void EV_Quake_GunshotDecalTrace( pmtrace_t *pTrace, char *decalName ) +{ + int iRand; + physent_t *pe; + + gEngfuncs.pEfxAPI->R_BulletImpactParticles( pTrace->endpos ); + + iRand = gEngfuncs.pfnRandomLong(0,0x7FFF); + if ( iRand < (0x7fff/2) )// not every bullet makes a sound. + { + switch( iRand % 5) + { + case 0: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "weapons/ric1.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "weapons/ric2.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 2: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "weapons/ric3.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "weapons/ric4.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 4: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "weapons/ric5.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + } + } + + pe = gEngfuncs.pEventAPI->EV_GetPhysent( pTrace->ent ); + + // 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 ); + } + } +} + + +void EV_Quake_DecalTrace( pmtrace_t *pTrace, char *decalName ) +{ + physent_t *pe; + + pe = gEngfuncs.pEventAPI->EV_GetPhysent( pTrace->ent ); + + // 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 ); + } + } +} + +void EV_HLDM_DecalGunshot( pmtrace_t *pTrace, int iBulletType ) +{ + physent_t *pe; + + pe = gEngfuncs.pEventAPI->EV_GetPhysent( pTrace->ent ); + + if ( pe && pe->solid == SOLID_BSP ) + { + switch( iBulletType ) + { + case BULLET_PLAYER_CROWBAR: + EV_Quake_DecalTrace( pTrace, EV_Quake_DamageDecalClub() ); + break; + + 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 + EV_Quake_GunshotDecalTrace( pTrace, EV_HLDM_DamageDecal() ); + break; + } + } +} + +void EV_Quake_PlayQuadSound ( int idx, float *origin, int iFlag ) +{ + if ( iFlag == 1 ) + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_ITEM, "items/damage3.wav", 1, ATTN_NORM, 0, PITCH_NORM); +} + +/* +================ +FireBullets + +Go to the trouble of combining multiple pellets into a single damage call. +================ +*/ +void EV_Quake_FireBullets( int idx, float *forward, float *right, float *up, int cShots, float *vecSrc, float *vecDirShooting, float *vecSpread ) +{ + int i; + pmtrace_t tr; + int iShot; + vec3_t vecRight, vecUp; + + VectorCopy( right, vecRight ); + VectorCopy( up, vecUp ); + + for ( iShot = 1; iShot <= cShots; iShot++ ) + { + vec3_t vecDir, vecEnd; + + // get circular gaussian spread + vec3_t spread; + do { + spread[0] = gEngfuncs.pfnRandomFloat(-1.0,1.0) + gEngfuncs.pfnRandomFloat(-1.0,1.0); + spread[1] = gEngfuncs.pfnRandomFloat(-1.0,1.0) + gEngfuncs.pfnRandomFloat(-1.0,1.0); + spread[2] = spread[0] * spread[0] + spread[1] *spread[1]; + } while (spread[2] > 1); + + for ( i = 0 ; i < 3; i++ ) + { + vecDir[i] = vecDirShooting[i] + spread[ 0 ] * vecSpread[ 0 ] * vecRight[ i ] + spread[ 1 ] * vecSpread[ 1 ] * up [ i ]; + vecEnd[i] = vecSrc[ i ] + 2048.0 * 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 ); + + int iBulletType = BULLET_PLAYER_BUCKSHOT; + + // do damage, paint decals + if ( tr.fraction != 1.0 ) + { + switch(iBulletType) + { + default: + case BULLET_PLAYER_9MM: + EV_HLDM_PlayTextureSound( idx, &tr, vecSrc, vecEnd, iBulletType ); + EV_HLDM_DecalGunshot( &tr, iBulletType ); + break; + case BULLET_PLAYER_MP5: + EV_HLDM_PlayTextureSound( idx, &tr, vecSrc, vecEnd, iBulletType ); + EV_HLDM_DecalGunshot( &tr, iBulletType ); + break; + case BULLET_PLAYER_BUCKSHOT: + EV_HLDM_DecalGunshot( &tr, iBulletType ); + break; + case BULLET_PLAYER_357: + EV_HLDM_PlayTextureSound( idx, &tr, vecSrc, vecEnd, iBulletType ); + EV_HLDM_DecalGunshot( &tr, iBulletType ); + break; + } + } + + gEngfuncs.pEventAPI->EV_PopPMStates(); + } +} + +void EV_FireShotGunDouble( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + + int i; + vec3_t vecSrc, vecAiming; + vec3_t vecSpread; + vec3_t up, right, forward; + float flSpread = 0.01; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + + AngleVectors( angles, forward, right, up ); + + EV_Quake_PlayQuadSound( idx, origin, args->iparam1 ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/shotgn2.wav", 1.0, ATTN_NORM, 0, 100 ); + + EV_GetGunPosition( args, vecSrc, origin ); + VectorCopy( forward, vecAiming ); + + for ( i = 0; i < 3; i++ ) + { + vecSpread[0] = 0.04; + vecSpread[1] = 0.04; + vecSpread[2] = 0.00; + } + + EV_Quake_FireBullets( idx, forward, right, up, 14, vecSrc, vecAiming, vecSpread ); + + if ( EV_IsLocal( idx ) ) + { + V_PunchAxis( 0, Q_BIG_PUNCHANGLE_KICK ); + } +} + +void EV_FireShotGunSingle( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + + int i; + vec3_t vecSrc, vecAiming; + vec3_t vecSpread; + vec3_t up, right, forward; + float flSpread = 0.01; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + + AngleVectors( angles, forward, right, up ); + + EV_Quake_PlayQuadSound( idx, origin, args->iparam1 ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/guncock.wav", 1.0, ATTN_NORM, 0, 100 ); + + EV_GetGunPosition( args, vecSrc, origin ); + VectorCopy( forward, vecAiming ); + + for ( i = 0; i < 3; i++ ) + { + vecSpread[0] = 0.04; + vecSpread[1] = 0.04; + vecSpread[2] = 0.00; + } + + EV_Quake_FireBullets( idx, forward, right, up, 6, vecSrc, vecAiming, vecSpread ); + + if ( EV_IsLocal( idx ) ) + { + V_PunchAxis( 0, Q_SMALL_PUNCHANGLE_KICK ); + } +} + +enum soundtypes_e +{ + SOUND_MISS, + SOUND_HIT_BODY, + SOUND_HIT_WALL, +}; + +void EV_Quake_PlayAxeSound( int idx, float *origin, int iSoundType ) +{ + switch ( iSoundType ) + { + case SOUND_HIT_BODY: + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "player/axhitbod.wav", 1, ATTN_NORM, 0, PITCH_NORM); + break; + + case SOUND_HIT_WALL: + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM, 0, PITCH_NORM); + break; + + default: + break; + } +} + + +void EV_FireAxe( event_args_t *args ) +{ + int ent; + + int fDidHit = 0; + int m_bHullHit = 1; + + vec3_t vecSrc, vecEnd; + vec3_t up, right, forward; + pmtrace_t tr; + + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + + AngleVectors( angles, forward, right, up ); + + EV_GetGunPosition( args, vecSrc, origin ); + + VectorMA( vecSrc, 64, 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_NORMAL, -1, &tr ); + + if ( tr.fraction < 1.0 ) + { + ent = gEngfuncs.pEventAPI->EV_IndexFromTrace( &tr ); + + if ( !EV_IsPlayer( ent ) ) + { + EV_Quake_PlayAxeSound( idx, origin, SOUND_HIT_WALL ); + EV_HLDM_DecalGunshot( &tr, BULLET_PLAYER_CROWBAR ); + } + + EV_Quake_PlayQuadSound( idx, origin, args->iparam1 ); + } + + gEngfuncs.pEventAPI->EV_PopPMStates(); + +} + +void EV_FireAxeSwing( event_args_t *args ) +{ + int idx; + vec3_t origin; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + + EV_Quake_PlayQuadSound( idx, origin, args->iparam1 ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/ax1.wav", 1.0, ATTN_NORM, 0, 100 ); +} + +void EV_PowerupCallback ( struct tempent_s *ent, float frametime, float currenttime ) +{ + //If the Player is not on our PVS, then go back + if ( !CheckPVS( ent->clientIndex ) ) + return; + + dlight_t *dl = gEngfuncs.pEfxAPI->CL_AllocDlight ( 0 ); + + cl_entity_t *player = gEngfuncs.GetEntityByIndex( ent->clientIndex ); + + if ( !player ) + return; + + VectorCopy ( player->origin, dl->origin ); + + dl->radius = 270; + dl->dark = true; + dl->die = gEngfuncs.GetClientTime() + 0.001; //Kill it right away + + if ( ent->entity.baseline.iuser2 == 1 ) + { + if ( ent->entity.baseline.iuser1 == 1 ) + { + dl->color.r = 255; + dl->color.g = 128; + dl->color.b = 128; + } + else + { + dl->color.r = 0; + dl->color.g = 75; + dl->color.b = 255; + } + } + else if ( ent->entity.baseline.iuser2 == 2 ) + { + if ( ent->entity.baseline.iuser1 == 1 ) + { + dl->color.r = 255; + dl->color.g = 128; + dl->color.b = 0; + } + else if ( ent->entity.baseline.iuser1 == 2 ) + { + dl->color.r = 0; + dl->color.g = 128; + dl->color.b = 250; + } + else + { + dl->color.r = 255; + dl->color.g = 75; + dl->color.b = 0; + } + } + else if ( ent->entity.baseline.iuser2 == 3 ) + { + dl->color.r = 255; + dl->color.g = 125; + dl->color.b = 255; + } +} + + +void EV_PlayerPowerup (event_args_t *args) +{ + int iEntIndex = args->iparam1; + int iTeam = args->iparam2; + int iPowerUp = (int)args->fparam1; + + int modelIndex; + char *model = "sprites/smoke.spr"; + + modelIndex = gEngfuncs.pEventAPI->EV_FindModelIndex ( model ); + + if ( args->bparam1 == 1) + gEngfuncs.pEfxAPI->R_KillAttachedTents ( iEntIndex ); + + if ( iPowerUp ) + { + TEMPENTITY *pTrailSpawner = NULL; + pTrailSpawner = gEngfuncs.pEfxAPI->R_TempModel ( args->origin, args->velocity, args->angles, 9999, modelIndex, TE_BOUNCE_NULL ); + + if ( pTrailSpawner != NULL) + { + pTrailSpawner->flags |= ( FTENT_PLYRATTACHMENT | FTENT_PERSIST | FTENT_NOMODEL | FTENT_CLIENTCUSTOM ); + pTrailSpawner->clientIndex = iEntIndex; + + pTrailSpawner->entity.baseline.iuser1 = iTeam; + pTrailSpawner->entity.baseline.iuser2 = iPowerUp; + + pTrailSpawner->callback = EV_PowerupCallback; + } + } +} + +float g_flLightTime; +BEAM *pBeam; + +void EV_FireLightning( event_args_t *args ) +{ + int idx; + vec3_t origin, endorigin; + vec3_t angles; + vec3_t vecEnd; + vec3_t up, right, forward; + int iShutDown; + + cl_entity_t *player; + + pmtrace_t tr; + int modelIndex; + + bool bSound = false; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + bSound = args->bparam1 ? true : false; + iShutDown = args->iparam2; + + modelIndex = gEngfuncs.pEventAPI->EV_FindModelIndex( "sprites/laserbeam.spr" ); + + // Load it up with some bogus data + player = gEngfuncs.GetLocalPlayer(); + + AngleVectors( angles, forward, right, up ); + + if ( EV_IsLocal( idx ) ) + { + if ( g_flLightTime <= gEngfuncs.GetClientTime() ) + { + gEngfuncs.pfnWeaponAnim( 1, 0 ); + g_flLightTime = gEngfuncs.GetClientTime() + 0.5; + } + + V_PunchAxis( 0, Q_SMALL_PUNCHANGLE_KICK ); + + cl_entity_t *player = gEngfuncs.GetViewModel(); + origin = player->attachment[0]; + } + else + { + origin = origin + Vector( 0, 0, 16 ); + } + + + if ( bSound ) + { + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/lhit.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); + EV_Quake_PlayQuadSound( idx, origin, args->iparam1 ); + } + + if ( iShutDown == 0 && EV_IsLocal( idx ) && pBeam == NULL ) + { + vec3_t vecSrc, vecEnd, origin, angles, forward, right, up; + pmtrace_t tr; + + cl_entity_t *pl = gEngfuncs.GetEntityByIndex( idx ); + + if ( pl ) + { + VectorCopy( gHUD.m_vecAngles, angles ); + + AngleVectors( angles, forward, right, up ); + + EV_GetGunPosition( args, vecSrc, pl->origin ); + + 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(); + + pBeam = gEngfuncs.pEfxAPI->R_BeamEntPoint ( idx | 0x1000, tr.endpos, modelIndex, 99999, 5.0, 0.15, 2.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0 ); + } + } + else if ( iShutDown == 1 ) + { + if ( EV_IsLocal( idx ) ) + { + if ( pBeam ) + { + pBeam->die = 0.0; + pBeam = NULL; + } + } + } +} + + +void EV_FireRocket( event_args_t *args ) +{ + int idx; + vec3_t origin; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/sgun1.wav", 1.0, ATTN_NORM, 0, 100 ); + + EV_Quake_PlayQuadSound( idx, origin, args->iparam1 ); + + if ( EV_IsLocal( idx ) ) + { + V_PunchAxis( 0, Q_SMALL_PUNCHANGLE_KICK ); + } +} + +void EV_FireGrenade( event_args_t *args ) +{ + int idx; + vec3_t origin; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + + EV_Quake_PlayQuadSound( idx, origin, args->iparam1 ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/grenade.wav", 1.0, ATTN_NORM, 0, 100 ); + + if ( EV_IsLocal( idx ) ) + { + V_PunchAxis( 0, Q_SMALL_PUNCHANGLE_KICK ); + } +} + +void EV_Quake_NailTouch( struct tempent_s *ent, pmtrace_t *ptr ) +{ + char decalname[ 32 ]; + int idx; + physent_t *pe; + + pe = gEngfuncs.pEventAPI->EV_GetPhysent( ptr->ent ); + if ( pe && ( pe->solid == SOLID_BSP || pe->movetype == MOVETYPE_PUSHSTEP ) ) + { + decalname[ 0 ] = '\0'; + idx = gEngfuncs.pfnRandomLong( 0, 4 ); + sprintf( decalname, "{shot%i", idx + 1 ); + EV_Quake_GunshotDecalTrace( ptr, decalname ); + } +} + +void EV_Quake_ClientProjectile( float *vecOrigin, float *vecVelocity, short sModelIndex, int iOwnerIndex, int iLife, void (*hitcallback)( struct tempent_s *ent, pmtrace_t *ptr ) ) +{ + gEngfuncs.pEfxAPI->R_Projectile( vecOrigin, vecVelocity, sModelIndex, iLife, iOwnerIndex, hitcallback ); +} + +void EV_FireSpike( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t up, right, forward; + vec3_t vecVelocity; + float offset = args->bparam1 ? 2.0 : -2.0; + + int shell; + + // gEngfuncs.Con_NPrintf( 22, "offset %f", offset ); + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + + shell = gEngfuncs.pEventAPI->EV_FindModelIndex( "models/spike.mdl" ); + AngleVectors( angles, forward, right, up ); + + EV_Quake_PlayQuadSound( idx, origin, args->iparam1 ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/rocket1i.wav", 1.0, ATTN_NORM, 0, 100 ); + + // make nails + VectorScale( forward, 1000, vecVelocity ); + EV_Quake_ClientProjectile( origin + Vector(0,0,10) + (right * offset), vecVelocity, (short)shell, idx, 6, EV_Quake_NailTouch ); + + if ( EV_IsLocal( idx ) ) + { + V_PunchAxis( 0, Q_SMALL_PUNCHANGLE_KICK ); + } +} + +void EV_FireSuperSpike( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t up, right, forward; + vec3_t vecVelocity; + + int shell; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + + shell = gEngfuncs.pEventAPI->EV_FindModelIndex( "models/spike.mdl" ); + AngleVectors( angles, forward, right, up ); + + EV_Quake_PlayQuadSound( idx, origin, args->iparam1 ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/spike2.wav", 1.0, ATTN_NORM, 0, 100 ); + + // make nails + VectorScale( forward, 1000, vecVelocity ); + EV_Quake_ClientProjectile( origin + Vector(0,0,16), vecVelocity, (short)shell, idx, 6, EV_Quake_NailTouch ); + + if ( EV_IsLocal( idx ) ) + { + V_PunchAxis( 0, Q_SMALL_PUNCHANGLE_KICK ); + } +} + +#define SND_CHANGE_PITCH (1<<7) + +void EV_TrainPitchAdjust( event_args_t *args ) +{ + int idx; + vec3_t origin; + + unsigned short us_params; + int noise; + float m_flVolume; + int pitch; + int stop; + + char sz[ 256 ]; + + idx = args->entindex; + + VectorCopy( args->origin, origin ); + + us_params = (unsigned short)args->iparam1; + stop = args->bparam1; + + m_flVolume = (float)(us_params & 0x003f)/40.0; + noise = (int)(((us_params) >> 12 ) & 0x0007); + pitch = (int)( 10.0 * (float)( ( us_params >> 6 ) & 0x003f ) ); + + switch ( noise ) + { + case 1: strcpy( sz, "plats/ttrain1.wav"); break; + case 2: strcpy( sz, "plats/ttrain2.wav"); break; + case 3: strcpy( sz, "plats/ttrain3.wav"); break; + case 4: strcpy( sz, "plats/ttrain4.wav"); break; + case 5: strcpy( sz, "plats/ttrain6.wav"); break; + case 6: strcpy( sz, "plats/ttrain7.wav"); break; + default: + // no sound + strcpy( sz, "" ); + return; + } + + if ( stop ) + { + gEngfuncs.pEventAPI->EV_StopSound( idx, CHAN_STATIC, sz ); + } + else + { + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_STATIC, sz, m_flVolume, ATTN_NORM, SND_CHANGE_PITCH, pitch ); + } +} + +char *DMC_BloodDecal (void) +{ + static char blooddecal[ 32 ]; + int idx; + + idx = gEngfuncs.pfnRandomLong( 0, 5 ); + + sprintf( blooddecal, "{blood%i", idx + 1 ); + + return blooddecal; +} + +void DMC_DecalTrace( pmtrace_t *pTrace, char *decalName ) +{ + physent_t *pe; + + pe = gEngfuncs.pEventAPI->EV_GetPhysent( pTrace->ent ); + + // 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 ); + } + } +} + +void EV_GibTouch ( struct tempent_s *ent, struct pmtrace_s *ptr ) +{ + DMC_DecalTrace (ptr, DMC_BloodDecal()); + + // 1 in 5 chance of squishy sound + if (gEngfuncs.pfnRandomLong(0, 4) > 0) + return; + + switch (gEngfuncs.pfnRandomLong(0, 5)) + { + case 0 : gEngfuncs.pEventAPI->EV_PlaySound( 0, ptr->endpos, CHAN_STATIC, "debris/flesh1.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 1 : gEngfuncs.pEventAPI->EV_PlaySound( 0, ptr->endpos, CHAN_STATIC, "debris/flesh2.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 2 : gEngfuncs.pEventAPI->EV_PlaySound( 0, ptr->endpos, CHAN_STATIC, "debris/flesh3.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 3 : gEngfuncs.pEventAPI->EV_PlaySound( 0, ptr->endpos, CHAN_STATIC, "debris/flesh5.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 4 : gEngfuncs.pEventAPI->EV_PlaySound( 0, ptr->endpos, CHAN_STATIC, "debris/flesh6.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 5 : gEngfuncs.pEventAPI->EV_PlaySound( 0, ptr->endpos, CHAN_STATIC, "debris/flesh7.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + } +} + +void EV_GibParticleCallback( struct particle_s *particle, float frametime ) +{ + int i; + + for ( i = 0; i < 3; i++ ) + { + particle->org[ i ] += particle->vel[ i ] * frametime; + } +} + +void EV_Gibbed (event_args_t *args) +{ + + vec3_t origin, velocity, angles, rotate; + int modelindex, i; + TEMPENTITY *pGib = NULL; + int gibs = 5; + char *model1 = "models/gib_1.mdl"; + char *model2 = "models/gib_2.mdl"; + char *model3 = "models/gib_3.mdl"; + + VectorCopy( args->origin, origin ); + + gEngfuncs.pEventAPI->EV_PlaySound( 0, origin, CHAN_STATIC, "player/gib.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); + + rotate[0] = gEngfuncs.pfnRandomLong (-100, 100); + rotate[1] = gEngfuncs.pfnRandomLong (-100, 100); + rotate[2] = gEngfuncs.pfnRandomLong (-100, 100); + + for (i = 0; i < gibs; i++) + { + switch ( gEngfuncs.pfnRandomLong( 1, 3 ) ) + { + case 1: modelindex = gEngfuncs.pEventAPI->EV_FindModelIndex ( model1 ); break; + case 2: modelindex = gEngfuncs.pEventAPI->EV_FindModelIndex ( model2 ); break; + case 3: modelindex = gEngfuncs.pEventAPI->EV_FindModelIndex ( model3 ); break; + //Just in case + default: modelindex = gEngfuncs.pEventAPI->EV_FindModelIndex ( model1 ); break; + } + + if (!modelindex) + return; + + VectorCopy( args->angles, angles ); + + VectorScale( angles, -1.0, angles ); + + angles[0] += gEngfuncs.pfnRandomFloat ( -0.30, 0.30 ); + angles[1] += gEngfuncs.pfnRandomFloat ( -0.30, 0.30 ); + angles[2] += gEngfuncs.pfnRandomFloat ( -0.30, 0.30 ); + + VectorScale ( angles, gEngfuncs.pfnRandomFloat( 500, 1200 ), velocity ); + velocity[2] += 600; + + pGib = gEngfuncs.pEfxAPI->R_TempModel (origin, velocity, rotate, 15, modelindex, TE_BOUNCE_NULL); + + if (pGib != NULL) + { + pGib->flags |= (FTENT_COLLIDEWORLD | FTENT_ROTATE | FTENT_FADEOUT | FTENT_CLIENTCUSTOM | FTENT_SMOKETRAIL); + pGib->hitcallback = EV_GibTouch; + + } + } +} + +//Spawns the teleport effect. +void EV_Teleport ( event_args_t *args ) +{ + vec3_t vecOrg; + + VectorCopy( args->origin, vecOrg ); + + switch (gEngfuncs.pfnRandomLong(0, 4)) + { + case 0 : gEngfuncs.pEventAPI->EV_PlaySound( 0, vecOrg, CHAN_STATIC, "misc/r_tele1.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 1 : gEngfuncs.pEventAPI->EV_PlaySound( 0, vecOrg, CHAN_STATIC, "misc/r_tele2.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 2 : gEngfuncs.pEventAPI->EV_PlaySound( 0, vecOrg, CHAN_STATIC, "misc/r_tele3.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 3 : gEngfuncs.pEventAPI->EV_PlaySound( 0, vecOrg, CHAN_STATIC, "misc/r_tele4.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 4 : gEngfuncs.pEventAPI->EV_PlaySound( 0, vecOrg, CHAN_STATIC, "misc/r_tele5.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + } + + gEngfuncs.pEfxAPI->R_TeleportSplash( vecOrg ); +} + +int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3}; + +void EV_RocketTrailCallback ( struct tempent_s *ent, float frametime, float currenttime ) +{ + if ( currenttime < ent->entity.baseline.fuser1 ) + return; + + if ( ent->entity.origin == ent->entity.attachment[0] ) + ent->die = gEngfuncs.GetClientTime(); + else + VectorCopy ( ent->entity.origin, ent->entity.attachment[0] ); + + //Make the Rocket light up. ( And only rockets, no Grenades ). + if ( ent->entity.baseline.sequence == 70 ) + { + dlight_t *dl = gEngfuncs.pEfxAPI->CL_AllocDlight ( 0 ); + VectorCopy ( ent->entity.origin, dl->origin ); + + dl->radius = 160; + dl->dark = true; + dl->die = gEngfuncs.GetClientTime() + 0.001; //Kill it right away + + dl->color.r = 255; + dl->color.g = 255; + dl->color.b = 255; + } +} + +#define GRENADE_TRAIL 1 +#define ROCKET_TRAIL 2 + +void EV_Trail (event_args_t *args) +{ + int iEntIndex = args->iparam1; + TEMPENTITY *pTrailSpawner = NULL; + + pTrailSpawner = gEngfuncs.pEfxAPI->CL_TempEntAllocNoModel ( args->origin ); + + if ( pTrailSpawner != NULL) + { + pTrailSpawner->flags |= ( FTENT_PLYRATTACHMENT | FTENT_COLLIDEKILL | FTENT_CLIENTCUSTOM | FTENT_SMOKETRAIL | FTENT_COLLIDEWORLD ); + pTrailSpawner->callback = EV_RocketTrailCallback; + pTrailSpawner->clientIndex = iEntIndex; + + if ( args->iparam2 == GRENADE_TRAIL ) + pTrailSpawner->entity.baseline.sequence = 69; + else if ( args->iparam2 == ROCKET_TRAIL ) + pTrailSpawner->entity.baseline.sequence = 70; + + pTrailSpawner->die = gEngfuncs.GetClientTime() + 10; // Just in case + pTrailSpawner->entity.baseline.fuser1 = gEngfuncs.GetClientTime() + 0.5; // Don't try to die till 500ms ahead + } +} + +void EV_Explosion (event_args_t *args) +{ + vec3_t origin, scorch_origin, velocity, forward, right, up; + int modelIndex; + char *model = "sprites/zerogxplode.spr"; + modelIndex = gEngfuncs.pEventAPI->EV_FindModelIndex (model); + pmtrace_t tr; + + //Make decals and Explosions + //Might not work for grenades. + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, velocity ); //Velocity + + scorch_origin = origin - velocity.Normalize() * 32; + + gEngfuncs.pEfxAPI->R_Explosion( origin, modelIndex, 2.5, 15, TE_EXPLFLAG_NONE ); + gEngfuncs.pEfxAPI->R_ParticleExplosion2( origin , 111, 8 ); + + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); + + gEngfuncs.pEventAPI->EV_PushPMStates(); + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( -1 ); + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( scorch_origin, scorch_origin + velocity.Normalize() * 64, PM_STUDIO_BOX | PM_WORLD_ONLY , -1, &tr ); + + gEngfuncs.pEventAPI->EV_PopPMStates(); + + + DMC_DecalTrace( &tr, EV_HLDM_RocketDamageDecal() ); + +} + +#define EV_DMC_MOVE_SOUND 0 +#define EV_DMC_STOP_SOUND 1 +char *EV_DMC_LookupDoorSound( int type, int index ) +{ + static char sound[ 128 ]; + int idx; + + // Assume the worst + strcpy( sound, "common/null.wav"); + + if ( type == EV_DMC_MOVE_SOUND ) + { + idx = ( index >> 8 ) & 0xff; + + switch (idx) + { + case 0: + strcpy( sound, "common/null.wav"); + break; + case 1: + strcpy( sound, "doors/doormove1.wav"); + break; + case 2: + strcpy( sound, "doors/doormove2.wav"); + break; + case 3: + strcpy( sound, "doors/doormove3.wav"); + break; + case 4: + strcpy( sound, "doors/doormove4.wav"); + break; + case 5: + strcpy( sound, "doors/doormove5.wav"); + break; + case 6: + strcpy( sound, "doors/doormove6.wav"); + break; + case 7: + strcpy( sound, "doors/doormove7.wav"); + break; + case 8: + strcpy( sound, "doors/doormove8.wav"); + break; + case 9: + strcpy( sound, "doors/doormove9.wav"); + break; + case 10: + strcpy( sound, "doors/doormove10.wav"); + break; + default: + strcpy( sound, "common/null.wav"); + break; + } + } + else if ( type == EV_DMC_STOP_SOUND ) + { + idx = ( index & 0xff ); + + // set the door's 'reached destination' stop sound + switch ( idx ) + { + case 0: + strcpy( sound, "common/null.wav"); + break; + case 1: + strcpy( sound, "doors/doorstop1.wav"); + break; + case 2: + strcpy( sound, "doors/doorstop2.wav"); + break; + case 3: + strcpy( sound, "doors/doorstop3.wav"); + break; + case 4: + strcpy( sound, "doors/doorstop4.wav"); + break; + case 5: + strcpy( sound, "doors/doorstop5.wav"); + break; + case 6: + strcpy( sound, "doors/doorstop6.wav"); + break; + case 7: + strcpy( sound, "doors/doorstop7.wav"); + break; + case 8: + strcpy( sound, "doors/doorstop8.wav"); + break; + default: + strcpy( sound, "common/null.wav"); + break; + } + } + return sound; +} + +void EV_DMC_DoorGoUp( event_args_t *args ) +{ + int idx = -1; + int soundindex = args->iparam1; + + gEngfuncs.pEventAPI->EV_StopSound( idx, CHAN_STATIC, EV_DMC_LookupDoorSound( EV_DMC_MOVE_SOUND, soundindex )); + gEngfuncs.pEventAPI->EV_PlaySound( idx, args->origin, CHAN_STATIC, EV_DMC_LookupDoorSound( EV_DMC_MOVE_SOUND, soundindex ), 1.0, ATTN_NORM, 0, PITCH_NORM ); +} + +void EV_DMC_DoorGoDown( event_args_t *args ) +{ + int idx = -1; + int soundindex = args->iparam1; + + gEngfuncs.pEventAPI->EV_StopSound( idx, CHAN_STATIC, EV_DMC_LookupDoorSound( EV_DMC_MOVE_SOUND, soundindex )); + gEngfuncs.pEventAPI->EV_PlaySound( idx, args->origin, CHAN_STATIC, EV_DMC_LookupDoorSound( EV_DMC_MOVE_SOUND, soundindex ), 1.0, ATTN_NORM, 0, PITCH_NORM ); +} + +void EV_DMC_DoorHitTop( event_args_t *args ) +{ + int idx = -1; + int soundindex = args->iparam1; + + gEngfuncs.pEventAPI->EV_StopSound( idx, CHAN_STATIC, EV_DMC_LookupDoorSound( EV_DMC_MOVE_SOUND, soundindex )); + gEngfuncs.pEventAPI->EV_PlaySound( idx, args->origin, CHAN_STATIC, EV_DMC_LookupDoorSound( EV_DMC_STOP_SOUND, soundindex ), 1.0, ATTN_NORM, 0, PITCH_NORM ); +} + +void EV_DMC_DoorHitBottom( event_args_t *args ) +{ + int idx = -1; + int soundindex = args->iparam1; + + gEngfuncs.pEventAPI->EV_StopSound( idx, CHAN_STATIC, EV_DMC_LookupDoorSound( EV_DMC_MOVE_SOUND, soundindex )); + gEngfuncs.pEventAPI->EV_PlaySound( idx, args->origin, CHAN_STATIC, EV_DMC_LookupDoorSound( EV_DMC_STOP_SOUND, soundindex ), 1.0, ATTN_NORM, 0, PITCH_NORM ); +} + + diff --git a/hlsdk/dmc/cl_dll/ev_hldm.h b/hlsdk/dmc/cl_dll/ev_hldm.h new file mode 100644 index 00000000..2c5e4ce8 --- /dev/null +++ b/hlsdk/dmc/cl_dll/ev_hldm.h @@ -0,0 +1,96 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined ( EV_HLDMH ) +#define EV_HLDMH + +// bullet types +typedef enum +{ + BULLET_NONE = 0, + BULLET_PLAYER_9MM, // glock + BULLET_PLAYER_MP5, // mp5 + BULLET_PLAYER_357, // python + BULLET_PLAYER_BUCKSHOT, // shotgun + BULLET_PLAYER_CROWBAR, // crowbar swipe + + BULLET_MONSTER_9MM, + BULLET_MONSTER_MP5, + BULLET_MONSTER_12MM, +} Bullet; + +enum glock_e { + GLOCK_IDLE1 = 0, + GLOCK_IDLE2, + GLOCK_IDLE3, + GLOCK_SHOOT, + GLOCK_SHOOT_EMPTY, + GLOCK_RELOAD, + GLOCK_RELOAD_NOT_EMPTY, + GLOCK_DRAW, + GLOCK_HOLSTER, + GLOCK_ADD_SILENCER +}; + +enum shotgun_e { + SHOTGUN_IDLE = 0, + SHOTGUN_FIRE, + SHOTGUN_FIRE2, + SHOTGUN_RELOAD, + SHOTGUN_PUMP, + SHOTGUN_START_RELOAD, + SHOTGUN_DRAW, + SHOTGUN_HOLSTER, + SHOTGUN_IDLE4, + SHOTGUN_IDLE_DEEP +}; + +enum mp5_e +{ + MP5_LONGIDLE = 0, + MP5_IDLE1, + MP5_LAUNCH, + MP5_RELOAD, + MP5_DEPLOY, + MP5_FIRE1, + MP5_FIRE2, + MP5_FIRE3, +}; + +enum python_e { + PYTHON_IDLE1 = 0, + PYTHON_FIDGET, + PYTHON_FIRE1, + PYTHON_RELOAD, + PYTHON_HOLSTER, + PYTHON_DRAW, + PYTHON_IDLE2, + PYTHON_IDLE3 +}; + +#define GAUSS_PRIMARY_CHARGE_VOLUME 256// how loud gauss is while charging +#define GAUSS_PRIMARY_FIRE_VOLUME 450// how loud gauss is when discharged + +enum gauss_e { + GAUSS_IDLE = 0, + GAUSS_IDLE2, + GAUSS_FIDGET, + GAUSS_SPINUP, + GAUSS_SPIN, + GAUSS_FIRE, + GAUSS_FIRE2, + GAUSS_HOLSTER, + GAUSS_DRAW +}; + +int EV_HLDM_DamageDecal( int entity, int bitsDamageType ); +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 *vecSpread, float flDistance, int iBulletType, int iTracerFreq, int *tracerCount ); + +#endif // EV_HLDMH \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/events.cpp b/hlsdk/dmc/cl_dll/events.cpp new file mode 100644 index 00000000..a1708881 --- /dev/null +++ b/hlsdk/dmc/cl_dll/events.cpp @@ -0,0 +1,30 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "hud.h" +#include "cl_util.h" + +void Game_HookEvents( void ); + +/* +=================== +EV_HookEvents + +See if game specific code wants to hook any events. +=================== +*/ +void EV_HookEvents( void ) +{ + Game_HookEvents(); +} diff --git a/hlsdk/dmc/cl_dll/eventscripts.h b/hlsdk/dmc/cl_dll/eventscripts.h new file mode 100644 index 00000000..5611729a --- /dev/null +++ b/hlsdk/dmc/cl_dll/eventscripts.h @@ -0,0 +1,73 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// eventscripts.h +#if !defined ( EVENTSCRIPTSH ) +#define EVENTSCRIPTSH + +// defaults for clientinfo messages +#define DEFAULT_VIEWHEIGHT 22 +#define VEC_DUCK_VIEW 12 + +#define FTENT_FADEOUT 0x00000080 + +#define DMG_GENERIC 0 // generic damage was done +#define DMG_CRUSH (1 << 0) // crushed by falling or moving object +#define DMG_BULLET (1 << 1) // shot +#define DMG_SLASH (1 << 2) // cut, clawed, stabbed +#define DMG_BURN (1 << 3) // heat burned +#define DMG_FREEZE (1 << 4) // frozen +#define DMG_FALL (1 << 5) // fell too far +#define DMG_BLAST (1 << 6) // explosive blast damage +#define DMG_CLUB (1 << 7) // crowbar, punch, headbutt +#define DMG_SHOCK (1 << 8) // electric shock +#define DMG_SONIC (1 << 9) // sound pulse shockwave +#define DMG_ENERGYBEAM (1 << 10) // laser or other high energy beam +#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 + +#define DMG_DROWN (1 << 14) // Drowning +#define DMG_FIRSTTIMEBASED DMG_DROWN + +#define DMG_PARALYZE (1 << 15) // slows affected creature down +#define DMG_NERVEGAS (1 << 16) // nerve toxins, very bad +#define DMG_POISON (1 << 17) // blood poisioning +#define DMG_RADIATION (1 << 18) // radiation exposure +#define DMG_DROWNRECOVER (1 << 19) // drowning recovery +#define DMG_ACID (1 << 20) // toxic chemicals or acid burns +#define DMG_SLOWBURN (1 << 21) // in an oven +#define DMG_SLOWFREEZE (1 << 22) // in a subzero freezer +#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_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 ); +qboolean EV_IsLocal( int idx ); +qboolean EV_IsPlayer( int idx ); +void EV_CreateTracer( float *start, float *end ); + +struct cl_entity_s *GetEntity( int idx ); +struct cl_entity_s *GetViewEntity( void ); +void EV_MuzzleFlash( void ); + +#endif // EVENTSCRIPTSH diff --git a/hlsdk/dmc/cl_dll/flashlight.cpp b/hlsdk/dmc/cl_dll/flashlight.cpp new file mode 100644 index 00000000..620ac2f8 --- /dev/null +++ b/hlsdk/dmc/cl_dll/flashlight.cpp @@ -0,0 +1,149 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// flashlight.cpp +// +// implementation of CHudFlashlight class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + + + +DECLARE_MESSAGE(m_Flash, FlashBat) +DECLARE_MESSAGE(m_Flash, Flashlight) + +#define BAT_NAME "sprites/%d_Flashlight.spr" + +int CHudFlashlight::Init(void) +{ + m_fFade = 0; + m_fOn = 0; + + HOOK_MESSAGE(Flashlight); + HOOK_MESSAGE(FlashBat); + + m_iFlags |= HUD_ACTIVE; + + gHUD.AddHudElem(this); + + return 1; +}; + +void CHudFlashlight::Reset(void) +{ + m_fFade = 0; + m_fOn = 0; +} + +int CHudFlashlight::VidInit(void) +{ + int HUD_flash_empty = gHUD.GetSpriteIndex( "flash_empty" ); + int HUD_flash_full = gHUD.GetSpriteIndex( "flash_full" ); + int HUD_flash_beam = gHUD.GetSpriteIndex( "flash_beam" ); + + m_hSprite1 = gHUD.GetSprite(HUD_flash_empty); + m_hSprite2 = gHUD.GetSprite(HUD_flash_full); + m_hBeam = gHUD.GetSprite(HUD_flash_beam); + m_prc1 = &gHUD.GetSpriteRect(HUD_flash_empty); + m_prc2 = &gHUD.GetSpriteRect(HUD_flash_full); + m_prcBeam = &gHUD.GetSpriteRect(HUD_flash_beam); + m_iWidth = m_prc2->right - m_prc2->left; + + return 1; +}; + +int CHudFlashlight:: MsgFunc_FlashBat(const char *pszName, int iSize, void *pbuf ) +{ + + + BEGIN_READ( pbuf, iSize ); + int x = READ_BYTE(); + m_iBat = x; + m_flBat = ((float)x)/100.0; + + return 1; +} + +int CHudFlashlight:: MsgFunc_Flashlight(const char *pszName, int iSize, void *pbuf ) +{ + + BEGIN_READ( pbuf, iSize ); + m_fOn = READ_BYTE(); + int x = READ_BYTE(); + m_iBat = x; + m_flBat = ((float)x)/100.0; + + return 1; +} + +int CHudFlashlight::Draw(float flTime) +{ + if ( gHUD.m_iHideHUDDisplay & ( HIDEHUD_FLASHLIGHT | HIDEHUD_ALL ) ) + return 1; + + int r, g, b, x, y, a; + wrect_t rc; + + if (!(gHUD.m_iWeaponBits & (1<<(4096)) )) + return 1; + + if (m_fOn) + a = 225; + else + a = MIN_ALPHA; + + if (m_flBat < 0.20) + UnpackRGB(r,g,b, RGB_REDISH); + else + UnpackRGB(r,g,b, RGB_YELLOWISH); + + ScaleColors(r, g, b, a); + + y = (m_prc1->bottom - m_prc2->top)/2; + x = ScreenWidth - m_iWidth - m_iWidth/2 ; + + // Draw the flashlight casing + SPR_Set(m_hSprite1, r, g, b ); + SPR_DrawAdditive( 0, x, y, m_prc1); + + if ( m_fOn ) + { // draw the flashlight beam + x = ScreenWidth - m_iWidth/2; + + SPR_Set( m_hBeam, r, g, b ); + SPR_DrawAdditive( 0, x, y, m_prcBeam ); + } + + // draw the flashlight energy level + x = ScreenWidth - m_iWidth - m_iWidth/2 ; + int iOffset = m_iWidth * (1.0 - m_flBat); + if (iOffset < m_iWidth) + { + rc = *m_prc2; + rc.left += iOffset; + + SPR_Set(m_hSprite2, r, g, b ); + SPR_DrawAdditive( 0, x + iOffset, y, &rc); + } + + + return 1; +} \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/geiger.cpp b/hlsdk/dmc/cl_dll/geiger.cpp new file mode 100644 index 00000000..8fa14070 --- /dev/null +++ b/hlsdk/dmc/cl_dll/geiger.cpp @@ -0,0 +1,184 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Geiger.cpp +// +// implementation of CHudAmmo class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include + +#include "parsemsg.h" + +DECLARE_MESSAGE(m_Geiger, Geiger ) + +int CHudGeiger::Init(void) +{ + HOOK_MESSAGE( Geiger ); + + m_iGeigerRange = 0; + m_iFlags = 0; + + gHUD.AddHudElem(this); + + srand( (unsigned)time( NULL ) ); + + return 1; +}; + +int CHudGeiger::VidInit(void) +{ + return 1; +}; + +int CHudGeiger::MsgFunc_Geiger(const char *pszName, int iSize, void *pbuf) +{ + + BEGIN_READ( pbuf, iSize ); + + // update geiger data + m_iGeigerRange = READ_BYTE(); + m_iGeigerRange = m_iGeigerRange << 2; + + m_iFlags |= HUD_ACTIVE; + + return 1; +} + +int CHudGeiger::Draw (float flTime) +{ + int pct; + float flvol; + int rg[3]; + int i; + + if (m_iGeigerRange < 1000 && m_iGeigerRange > 0) + { + // peicewise linear is better than continuous formula for this + if (m_iGeigerRange > 800) + { + pct = 0; //Con_Printf ( "range > 800\n"); + } + else if (m_iGeigerRange > 600) + { + pct = 2; + flvol = 0.4; //Con_Printf ( "range > 600\n"); + rg[0] = 1; + rg[1] = 1; + i = 2; + } + else if (m_iGeigerRange > 500) + { + pct = 4; + flvol = 0.5; //Con_Printf ( "range > 500\n"); + rg[0] = 1; + rg[1] = 2; + i = 2; + } + else if (m_iGeigerRange > 400) + { + pct = 8; + flvol = 0.6; //Con_Printf ( "range > 400\n"); + rg[0] = 1; + rg[1] = 2; + rg[2] = 3; + i = 3; + } + else if (m_iGeigerRange > 300) + { + pct = 8; + flvol = 0.7; //Con_Printf ( "range > 300\n"); + rg[0] = 2; + rg[1] = 3; + rg[2] = 4; + i = 3; + } + else if (m_iGeigerRange > 200) + { + pct = 28; + flvol = 0.78; //Con_Printf ( "range > 200\n"); + rg[0] = 2; + rg[1] = 3; + rg[2] = 4; + i = 3; + } + else if (m_iGeigerRange > 150) + { + pct = 40; + flvol = 0.80; //Con_Printf ( "range > 150\n"); + rg[0] = 3; + rg[1] = 4; + rg[2] = 5; + i = 3; + } + else if (m_iGeigerRange > 100) + { + pct = 60; + flvol = 0.85; //Con_Printf ( "range > 100\n"); + rg[0] = 3; + rg[1] = 4; + rg[2] = 5; + i = 3; + } + else if (m_iGeigerRange > 75) + { + pct = 80; + flvol = 0.9; //Con_Printf ( "range > 75\n"); + //gflGeigerDelay = cl.time + GEIGERDELAY * 0.75; + rg[0] = 4; + rg[1] = 5; + rg[2] = 6; + i = 3; + } + else if (m_iGeigerRange > 50) + { + pct = 90; + flvol = 0.95; //Con_Printf ( "range > 50\n"); + rg[0] = 5; + rg[1] = 6; + i = 2; + } + else + { + pct = 95; + flvol = 1.0; //Con_Printf ( "range < 50\n"); + rg[0] = 5; + rg[1] = 6; + i = 2; + } + + flvol = (flvol * ((rand() & 127)) / 255) + 0.25; // UTIL_RandomFloat(0.25, 0.5); + + if ((rand() & 127) < pct || (rand() & 127) < pct) + { + //S_StartDynamicSound (-1, 0, rgsfx[rand() % i], r_origin, flvol, 1.0, 0, 100); + char sz[256]; + + int j = rand() & 1; + if (i > 2) + j += rand() & 1; + + sprintf(sz, "player/geiger%d.wav", j + 1); + PlaySound(sz, flvol); + + } + } + + return 1; +} diff --git a/hlsdk/dmc/cl_dll/health.cpp b/hlsdk/dmc/cl_dll/health.cpp new file mode 100644 index 00000000..287074b4 --- /dev/null +++ b/hlsdk/dmc/cl_dll/health.cpp @@ -0,0 +1,476 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Health.cpp +// +// implementation of CHudHealth class +// + +#include "STDIO.H" +#include "STDLIB.H" +#include "MATH.H" + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" +#include + + +DECLARE_MESSAGE(m_Health, Health ) +DECLARE_MESSAGE(m_Health, Damage ) + +#define PAIN_NAME "sprites/%d_pain.spr" +#define DAMAGE_NAME "sprites/%d_dmg.spr" + +int giDmgHeight, giDmgWidth; + +int giDmgFlags[NUM_DMG_TYPES] = +{ + DMG_POISON, + DMG_ACID, + DMG_FREEZE|DMG_SLOWFREEZE, + DMG_DROWN, + DMG_BURN|DMG_SLOWBURN, + 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_bitsDamage = 0; + m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 0; + giDmgHeight = 0; + giDmgWidth = 0; + + memset(m_dmg, 0, sizeof(DAMAGE_IMAGE) * NUM_DMG_TYPES); + + + gHUD.AddHudElem(this); + return 1; +} + +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++ ) + { + m_dmg[i].fExpire = 0; + } +} + +int CHudHealth::VidInit(void) +{ + m_hSprite = 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; + 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; + } + + return 1; +} + + +int CHudHealth:: MsgFunc_Damage(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int armor = READ_BYTE(); // armor + int damageTaken = READ_BYTE(); // health + long bitsDamage = READ_LONG(); // damage bits + + vec3_t vecFrom; + + for ( int i = 0 ; i < 3 ; i++) + vecFrom[i] = READ_COORD(); + + UpdateTiles(gHUD.m_flTime, bitsDamage); + + // 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 ) +{ + int iHealth = m_iHealth; + + if (iHealth > 25) + iHealth -= 25; + else if ( iHealth < 0 ) + iHealth = 0; +#if 0 + g = iHealth * 255 / 100; + r = 255 - g; + b = 0; +#else + if (m_iHealth > 25) + { + UnpackRGB(r,g,b, RGB_YELLOWISH); + } + else + { + r = 250; + g = 0; + b = 0; + } +#endif +} + +int CHudHealth::Draw(float flTime) +{ + int r, g, b; + int a = 0, x, y; + int HealthWidth; + +// if (m_iHealth <= 0) +// return 1; + + if ( gHUD.m_iHideHUDDisplay & HIDEHUD_HEALTH ) + return 1; + + if ( !m_hSprite ) + m_hSprite = LoadSprite(PAIN_NAME); + + // Has health changed? Flash the health # + if (m_fFade) + { + m_fFade -= (gHUD.m_flTimeDelta * 20); + if (m_fFade <= 0) + { + a = MIN_ALPHA; + m_fFade = 0; + } + + // Fade the health number back to dim + + a = MIN_ALPHA + (m_fFade/FADE_TIME) * 128; + + } + else + a = MIN_ALPHA; + + // If health is getting low, make it bright red + if (m_iHealth <= 15) + a = 255; + + GetPainColor( r, g, b ); + ScaleColors(r, g, b, a ); + + // 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; + + 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); + } + + DrawDamage(flTime); + return DrawPain(flTime); +} + +void CHudHealth::CalcDamageDirection(vec3_t vecFrom) +{ + vec3_t forward, right, up; + float side, front; + vec3_t vecOrigin, vecAngles; + + if (!vecFrom[0] && !vecFrom[1] && !vecFrom[2]) + { + m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 0; + 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(); + + vecFrom = vecFrom.Normalize(); + AngleVectors (vecAngles, forward, right, up); + + front = DotProduct (vecFrom, right); + side = DotProduct (vecFrom, forward); + + if (flDistToTarget <= 50) + { + m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 1; + } + else + { + if (side > 0) + { + if (side > 0.3) + m_fAttackFront = max(m_fAttackFront, side); + } + else + { + float f = fabs(side); + if (f > 0.3) + m_fAttackRear = max(m_fAttackRear, f); + } + + if (front > 0) + { + if (front > 0.3) + m_fAttackRight = max(m_fAttackRight, front); + } + else + { + float f = fabs(front); + if (f > 0.3) + m_fAttackLeft = max(m_fAttackLeft, f); + } + } +} + +int CHudHealth::DrawPain(float flTime) +{ + if (!(m_fAttackFront || m_fAttackRear || m_fAttackLeft || m_fAttackRight)) + return 1; + + int r, g, b; + int x, y, a, shade; + + // TODO: get the shift value of the health + a = 255; // max brightness until then + + float fFade = gHUD.m_flTimeDelta * 2; + + // SPR_Draw top + if (m_fAttackFront > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackFront, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 - SPR_Width(m_hSprite, 0)/2; + y = ScreenHeight/2 - SPR_Height(m_hSprite,0) * 3; + SPR_DrawAdditive(0, x, y, NULL); + m_fAttackFront = max( 0, m_fAttackFront - fFade ); + } else + m_fAttackFront = 0; + + if (m_fAttackRight > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackRight, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 + SPR_Width(m_hSprite, 1) * 2; + y = ScreenHeight/2 - SPR_Height(m_hSprite,1)/2; + SPR_DrawAdditive(1, x, y, NULL); + m_fAttackRight = max( 0, m_fAttackRight - fFade ); + } else + m_fAttackRight = 0; + + if (m_fAttackRear > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackRear, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 - SPR_Width(m_hSprite, 2)/2; + y = ScreenHeight/2 + SPR_Height(m_hSprite,2) * 2; + SPR_DrawAdditive(2, x, y, NULL); + m_fAttackRear = max( 0, m_fAttackRear - fFade ); + } else + m_fAttackRear = 0; + + if (m_fAttackLeft > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackLeft, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 - SPR_Width(m_hSprite, 3) * 3; + y = ScreenHeight/2 - SPR_Height(m_hSprite,3)/2; + SPR_DrawAdditive(3, x, y, NULL); + + m_fAttackLeft = max( 0, m_fAttackLeft - fFade ); + } else + m_fAttackLeft = 0; + + return 1; +} + +int CHudHealth::DrawDamage(float flTime) +{ + int r, g, b, a; + DAMAGE_IMAGE *pdmg; + + if (!m_bitsDamage) + return 1; + + UnpackRGB(r,g,b, RGB_YELLOWISH); + + a = (int)( fabs(sin(flTime*2)) * 256.0); + + ScaleColors(r, g, b, a); + + // Draw all the items + for (int i = 0; i < NUM_DMG_TYPES; i++) + { + if (m_bitsDamage & giDmgFlags[i]) + { + pdmg = &m_dmg[i]; + SPR_Set(gHUD.GetSprite(m_HUD_dmg_bio + i), r, g, b ); + SPR_DrawAdditive(0, pdmg->x, pdmg->y, &gHUD.GetSpriteRect(m_HUD_dmg_bio + i)); + } + } + + + // check for bits that should be expired + for ( int i = 0; i < NUM_DMG_TYPES; i++ ) + { + DAMAGE_IMAGE *pdmg = &m_dmg[i]; + + if ( m_bitsDamage & giDmgFlags[i] ) + { + pdmg->fExpire = min( flTime + DMG_IMAGE_LIFE, pdmg->fExpire ); + + if ( pdmg->fExpire <= flTime // when the time has expired + && a < 40 ) // and the flash is at the low point of the cycle + { + pdmg->fExpire = 0; + + int y = pdmg->y; + pdmg->x = pdmg->y = 0; + + // move everyone above down + for (int j = 0; j < NUM_DMG_TYPES; j++) + { + pdmg = &m_dmg[j]; + if ((pdmg->y) && (pdmg->y < y)) + pdmg->y += giDmgHeight; + + } + + m_bitsDamage &= ~giDmgFlags[i]; // clear the bits + } + } + } + + return 1; +} + + +void CHudHealth::UpdateTiles(float flTime, long bitsDamage) +{ + DAMAGE_IMAGE *pdmg; + + // Which types are new? + long bitsOn = ~m_bitsDamage & bitsDamage; + + for (int i = 0; i < NUM_DMG_TYPES; i++) + { + pdmg = &m_dmg[i]; + + // Is this one already on? + if (m_bitsDamage & giDmgFlags[i]) + { + pdmg->fExpire = flTime + DMG_IMAGE_LIFE; // extend the duration + if (!pdmg->fBaseline) + pdmg->fBaseline = flTime; + } + + // Are we just turning it on? + if (bitsOn & giDmgFlags[i]) + { + // put this one at the bottom + pdmg->x = giDmgWidth/8; + pdmg->y = ScreenHeight - giDmgHeight * 2; + pdmg->fExpire=flTime + DMG_IMAGE_LIFE; + + // move everyone else up + for (int j = 0; j < NUM_DMG_TYPES; j++) + { + if (j == i) + continue; + + pdmg = &m_dmg[j]; + if (pdmg->y) + pdmg->y -= giDmgHeight; + + } + pdmg = &m_dmg[i]; + } + } + + // damage bits are only turned on here; they are turned off when the draw time has expired (in DrawDamage()) + m_bitsDamage |= bitsDamage; +} + diff --git a/hlsdk/dmc/cl_dll/health.h b/hlsdk/dmc/cl_dll/health.h new file mode 100644 index 00000000..51201038 --- /dev/null +++ b/hlsdk/dmc/cl_dll/health.h @@ -0,0 +1,130 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#define DMG_IMAGE_LIFE 2 // seconds that image is up + +#define DMG_IMAGE_POISON 0 +#define DMG_IMAGE_ACID 1 +#define DMG_IMAGE_COLD 2 +#define DMG_IMAGE_DROWN 3 +#define DMG_IMAGE_BURN 4 +#define DMG_IMAGE_NERVE 5 +#define DMG_IMAGE_RAD 6 +#define DMG_IMAGE_SHOCK 7 +//tf defines +#define DMG_IMAGE_CALTROP 8 +#define DMG_IMAGE_TRANQ 9 +#define DMG_IMAGE_CONCUSS 10 +#define DMG_IMAGE_HALLUC 11 +#define NUM_DMG_TYPES 12 +// instant damage + +#define DMG_GENERIC 0 // generic damage was done +#define DMG_CRUSH (1 << 0) // crushed by falling or moving object +#define DMG_BULLET (1 << 1) // shot +#define DMG_SLASH (1 << 2) // cut, clawed, stabbed +#define DMG_BURN (1 << 3) // heat burned +#define DMG_FREEZE (1 << 4) // frozen +#define DMG_FALL (1 << 5) // fell too far +#define DMG_BLAST (1 << 6) // explosive blast damage +#define DMG_CLUB (1 << 7) // crowbar, punch, headbutt +#define DMG_SHOCK (1 << 8) // electric shock +#define DMG_SONIC (1 << 9) // sound pulse shockwave +#define DMG_ENERGYBEAM (1 << 10) // laser or other high energy beam +#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 + + +#define DMG_DROWN (1 << 14) // Drowning +#define DMG_FIRSTTIMEBASED DMG_DROWN + +#define DMG_PARALYZE (1 << 15) // slows affected creature down +#define DMG_NERVEGAS (1 << 16) // nerve toxins, very bad +#define DMG_POISON (1 << 17) // blood poisioning +#define DMG_RADIATION (1 << 18) // radiation exposure +#define DMG_DROWNRECOVER (1 << 19) // drowning recovery +#define DMG_ACID (1 << 20) // toxic chemicals or acid burns +#define DMG_SLOWBURN (1 << 21) // in an oven +#define DMG_SLOWFREEZE (1 << 22) // in a subzero freezer +#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_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) + +// 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; + float fBaseline; + int x, y; +} DAMAGE_IMAGE; + +// +//----------------------------------------------------- +// +class CHudHealth: public CHudBase +{ +public: + virtual int Init( void ); + 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; + + void GetPainColor( int &r, int &g, int &b ); + float m_fFade; + +private: + HSPRITE m_hSprite; + HSPRITE m_hDamage; + + DAMAGE_IMAGE m_dmg[NUM_DMG_TYPES]; + int m_bitsDamage; + + + int DrawPain(float fTime); + int DrawDamage(float fTime); + float m_fAttackFront, m_fAttackRear, m_fAttackLeft, m_fAttackRight; + void CalcDamageDirection(vec3_t vecFrom); + void UpdateTiles(float fTime, long bits); +}; diff --git a/hlsdk/dmc/cl_dll/hud.cpp b/hlsdk/dmc/cl_dll/hud.cpp new file mode 100644 index 00000000..875ccaf1 --- /dev/null +++ b/hlsdk/dmc/cl_dll/hud.cpp @@ -0,0 +1,567 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud.cpp +// +// implementation of CHud class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" +#include "hud_servers.h" +#include "vgui_viewport.h" +#include "demo.h" +#include "demo_api.h" +#include "voice_status.h" +#include "vgui_scorepanel.h" + + +class CDMCVoiceStatusHelper : public IVoiceStatusHelper +{ +public: + virtual void GetPlayerTextColor(int entindex, int color[3]) + { + color[0] = color[1] = color[2] = 255; + + if( entindex >= 0 && entindex < sizeof(g_PlayerExtraInfo)/sizeof(g_PlayerExtraInfo[0]) ) + { + int iTeam = g_PlayerExtraInfo[entindex].teamnumber; + + if( iTeam >= 0 && iTeam < sizeof(iTeamColors)/sizeof(iTeamColors[0]) ) + { + color[0] = iTeamColors[iTeam][0]; + color[1] = iTeamColors[iTeam][1]; + color[2] = iTeamColors[iTeam][2]; + } + } + } + + virtual void UpdateCursorState() + { + gViewPort->UpdateCursorState(); + } + + virtual int GetAckIconHeight() + { + return ScreenHeight - gHUD.m_iFontHeight*2 - 6; + } + + virtual bool CanShowSpeakerLabels() + { + if( gViewPort && gViewPort->m_pScoreBoard ) + return !gViewPort->m_pScoreBoard->isVisible(); + else + return false; + } +}; +static CDMCVoiceStatusHelper g_VoiceStatusHelper; + + + +extern client_sprite_t *GetSpriteList(client_sprite_t *pList, const char *psz, int iRes, int iCount); + +extern cvar_t *sensitivity; +cvar_t *cl_lw = NULL; +cvar_t *cl_autowepswitch; +cvar_t *cl_rollspeed; +cvar_t *cl_rollangle; +cvar_t *cl_fov; + +void ShutdownInput (void); + +void __CmdFunc_ToggleServerBrowser( void ) +{ + if ( gViewPort ) + { + gViewPort->ToggleServerBrowser(); + } +} + +//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) +{ + return gHUD.MsgFunc_ResetHUD(pszName, iSize, pbuf ); +} + +int __MsgFunc_InitHUD(const char *pszName, int iSize, void *pbuf) +{ + gHUD.MsgFunc_InitHUD( pszName, iSize, pbuf ); + return 1; +} + +int __MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_SetFOV( pszName, iSize, pbuf ); +} + +int __MsgFunc_Concuss(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_Concuss( pszName, iSize, pbuf ); +} + +int __MsgFunc_GameMode(const char *pszName, int iSize, void *pbuf ) +{ + return gHUD.MsgFunc_GameMode( pszName, iSize, pbuf ); +} + +int __MsgFunc_MOTD(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_MOTD( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_ServerName(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_ServerName( pszName, iSize, pbuf ); + return 0; +} + +// QUAKECLASSIC +int __MsgFunc_QItems(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_QItems( pszName, iSize, pbuf ); +} + +int __MsgFunc_ScoreInfo(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_ScoreInfo( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_TeamInfo(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_TeamInfo( 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 ); + HOOK_MESSAGE( SetFOV ); + HOOK_MESSAGE( Concuss ); + + HOOK_MESSAGE( MOTD ); + HOOK_MESSAGE( ServerName ); + + HOOK_COMMAND( "togglebrowser", ToggleServerBrowser ); + + // QUAKECLASSIC + HOOK_MESSAGE( QItems ); + HOOK_MESSAGE( ScoreInfo ); + //HOOK_MESSAGE( TeamScore ); + HOOK_MESSAGE( TeamInfo ); + + m_iLogo = 0; + m_iFOV = 0; + + CVAR_CREATE( "zoom_sensitivity_ratio", "1.2", 0 ); + default_fov = CVAR_CREATE( "default_fov", "90", 0 ); + cl_lw = gEngfuncs.pfnGetCvarPointer( "cl_lw" ); + m_pCvarStealMouse = CVAR_CREATE( "hud_capturemouse", "1", FCVAR_ARCHIVE ); + m_pCvarDraw = CVAR_CREATE( "hud_draw", "1", FCVAR_ARCHIVE ); + /************************ CLIENT CVAR DEFINITIONS ************************/ + cl_autowepswitch = gEngfuncs.pfnRegisterVariable ( "cl_autowepswitch", "2", FCVAR_USERINFO|FCVAR_ARCHIVE ); + cl_rollangle = gEngfuncs.pfnRegisterVariable ( "cl_rollangle", "0.65", FCVAR_CLIENTDLL|FCVAR_ARCHIVE ); + cl_rollspeed = gEngfuncs.pfnRegisterVariable ( "cl_rollspeed", "300", FCVAR_CLIENTDLL|FCVAR_ARCHIVE ); + cl_fov = gEngfuncs.pfnRegisterVariable ( "cl_fov", "90", FCVAR_USERINFO|FCVAR_ARCHIVE ); + /************************ CLIENT CVAR DEFINITIONS ************************/ + + m_pSpriteList = NULL; + + // Clear any old HUD list + if ( m_pHudList ) + { + HUDLIST *pList; + while ( m_pHudList ) + { + pList = m_pHudList; + m_pHudList = m_pHudList->pNext; + free( pList ); + } + m_pHudList = NULL; + } + + // In case we get messages before the first update -- time will be valid + m_flTime = 1.0; + + m_Ammo.Init(); + m_Health.Init(); + m_SayText.Init(); + m_Spectator.Init(); + m_Geiger.Init(); + m_Train.Init(); + m_Battery.Init(); + m_Message.Init(); +// m_Scoreboard.Init(); +// m_MOTD.Init(); + m_StatusBar.Init(); + m_DeathNotice.Init(); + m_AmmoSecondary.Init(); + m_TextMessage.Init(); + m_StatusIcons.Init(); + + GetClientVoiceMgr()->Init(&g_VoiceStatusHelper, (vgui::Panel**)&gViewPort); + + + m_Menu.Init(); + + ServersInit(); + + MsgFunc_ResetHUD(0, 0, NULL ); +} + +CHud::CHud() : m_iSpriteCount(0), m_pHudList(NULL) +{ +} + +// CHud destructor +// cleans up memory allocated for m_rg* arrays +CHud :: ~CHud() +{ + delete [] m_rghSprites; + delete [] m_rgrcRects; + delete [] m_rgszSpriteNames; + + if ( m_pHudList ) + { + HUDLIST *pList; + while ( m_pHudList ) + { + pList = m_pHudList; + m_pHudList = m_pHudList->pNext; + free( pList ); + } + m_pHudList = NULL; + } + + ServersShutdown(); +} + +// GetSpriteIndex() +// searches through the sprite list loaded from hud.txt for a name matching SpriteName +// returns an index into the gHUD.m_rghSprites[] array +// returns 0 if sprite not found +int CHud :: GetSpriteIndex( const char *SpriteName ) +{ + // look through the loaded sprite name list for SpriteName + for ( int i = 0; i < m_iSpriteCount; i++ ) + { + if ( strncmp( SpriteName, m_rgszSpriteNames + (i * MAX_SPRITE_NAME_LENGTH), MAX_SPRITE_NAME_LENGTH ) == 0 ) + return i; + } + + return -1; // invalid sprite +} + +void CHud :: VidInit( void ) +{ + m_scrinfo.iSize = sizeof(m_scrinfo); + GetScreenInfo(&m_scrinfo); + + // ---------- + // Load Sprites + // --------- +// m_hsprFont = LoadSprite("sprites/%d_font.spr"); + + m_hsprLogo = 0; + m_hsprCursor = 0; + + if (ScreenWidth < 640) + m_iRes = 320; + else + m_iRes = 640; + + // Only load this once + if ( !m_pSpriteList ) + { + // we need to load the hud.txt, and all sprites within + m_pSpriteList = SPR_GetList("sprites/hud.txt", &m_iSpriteCountAllRes); + + if (m_pSpriteList) + { + // count the number of sprites of the appropriate res + m_iSpriteCount = 0; + client_sprite_t *p = m_pSpriteList; + for ( int j = 0; j < m_iSpriteCountAllRes; j++ ) + { + if ( p->iRes == m_iRes ) + m_iSpriteCount++; + p++; + } + + // allocated memory for sprite handle arrays + m_rghSprites = new HSPRITE[m_iSpriteCount]; + m_rgrcRects = new wrect_t[m_iSpriteCount]; + m_rgszSpriteNames = new char[m_iSpriteCount * MAX_SPRITE_NAME_LENGTH]; + + p = m_pSpriteList; + int index = 0; + for ( int j = 0; j < m_iSpriteCountAllRes; j++ ) + { + if ( p->iRes == m_iRes ) + { + char sz[256]; + sprintf(sz, "sprites/%s.spr", p->szSprite); + m_rghSprites[index] = SPR_Load(sz); + m_rgrcRects[index] = p->rc; + strncpy( &m_rgszSpriteNames[index * MAX_SPRITE_NAME_LENGTH], p->szName, MAX_SPRITE_NAME_LENGTH ); + + index++; + } + + p++; + } + } + } + else + { + // we have already have loaded the sprite reference from hud.txt, but + // we need to make sure all the sprites have been loaded (we've gone through a transition, or loaded a save game) + client_sprite_t *p = m_pSpriteList; + int index = 0; + for ( int j = 0; j < m_iSpriteCountAllRes; j++ ) + { + if ( p->iRes == m_iRes ) + { + char sz[256]; + sprintf( sz, "sprites/%s.spr", p->szSprite ); + m_rghSprites[index] = SPR_Load(sz); + index++; + } + + p++; + } + } + + // assumption: number_1, number_2, etc, are all listed and loaded sequentially + m_HUD_number_0 = GetSpriteIndex( "number_0" ); + + m_iFontHeight = m_rgrcRects[m_HUD_number_0].bottom - m_rgrcRects[m_HUD_number_0].top; + + m_Ammo.VidInit(); + m_Health.VidInit(); + m_Spectator.VidInit(); + m_Geiger.VidInit(); + m_Train.VidInit(); + m_Battery.VidInit(); + m_Message.VidInit(); +// m_Scoreboard.VidInit(); +// m_MOTD.VidInit(); + m_StatusBar.VidInit(); + m_DeathNotice.VidInit(); + m_SayText.VidInit(); + m_Menu.VidInit(); + m_AmmoSecondary.VidInit(); + m_TextMessage.VidInit(); + m_StatusIcons.VidInit(); + + m_Spectator.VidInit(); + + GetClientVoiceMgr()->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; +} + +float g_lastFOV = 0.0; + +/* +============ +COM_FileBase +============ +*/ +// Extracts the base name of a file (no path, no extension, assumes '/' as path separator) +void COM_FileBase ( const char *in, char *out) +{ + int len, start, end; + + len = strlen( in ); + + // scan backward for '.' + end = len - 1; + while ( end && in[end] != '.' && in[end] != '/' && in[end] != '\\' ) + end--; + + if ( in[end] != '.' ) // no '.', copy to end + end = len-1; + else + end--; // Found ',', copy to left of '.' + + + // Scan backward for '/' + start = len-1; + while ( start >= 0 && in[start] != '/' && in[start] != '\\' ) + start--; + + if ( in[start] != '/' && in[start] != '\\' ) + start = 0; + else + start++; + + // Length of new sting + len = end - start + 1; + + // Copy partial string + strncpy( out, &in[start], len ); + // Terminate it + out[len] = 0; +} + +/* +================= +HUD_IsGame + +================= +*/ +int HUD_IsGame( const char *game ) +{ + const char *gamedir; + char gd[ 1024 ]; + + gamedir = gEngfuncs.pfnGetGameDirectory(); + if ( gamedir && gamedir[0] ) + { + COM_FileBase( gamedir, gd ); + if ( !stricmp( gd, game ) ) + return 1; + } + return 0; +} + +/* +===================== +HUD_GetFOV + +Returns last FOV +===================== +*/ +float HUD_GetFOV( void ) +{ + /* + if ( gEngfuncs.pDemoAPI->IsRecording() ) + { + // Write it + int i = 0; + unsigned char buf[ 100 ]; + + // Active + *( float * )&buf[ i ] = g_lastFOV; + i += sizeof( float ); + + Demo_WriteBuffer( TYPE_ZOOM, i, buf ); + } + + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + { + g_lastFOV = g_demozoom; + } + */ + return g_lastFOV; +} + +int CHud::MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + int newfov = READ_BYTE(); + int def_fov = CVAR_GET_FLOAT( "default_fov" ); + + if ( newfov == 0 ) + { + m_iFOV = def_fov; + } + else + { + m_iFOV = newfov; + } + + // the clients fov is actually set in the client data update section of the hud + + // Set a new sensitivity + if ( m_iFOV == def_fov ) + { + // reset to saved sensitivity + m_flMouseSensitivity = 0; + } + else + { + // set a new sensitivity that is proportional to the change from the FOV default + m_flMouseSensitivity = sensitivity->value * ((float)newfov / (float)def_fov) * CVAR_GET_FLOAT("zoom_sensitivity_ratio"); + } + + return 1; +} + + +void CHud::AddHudElem(CHudBase *phudelem) +{ + HUDLIST *pdl, *ptemp; + +//phudelem->Think(); + + if (!phudelem) + return; + + pdl = (HUDLIST *)malloc(sizeof(HUDLIST)); + if (!pdl) + return; + + memset(pdl, 0, sizeof(HUDLIST)); + pdl->p = phudelem; + + if (!m_pHudList) + { + m_pHudList = pdl; + return; + } + + ptemp = m_pHudList; + + while (ptemp->pNext) + ptemp = ptemp->pNext; + + ptemp->pNext = pdl; +} + +float CHud::GetSensitivity( void ) +{ + return m_flMouseSensitivity; +} + + diff --git a/hlsdk/dmc/cl_dll/hud.h b/hlsdk/dmc/cl_dll/hud.h new file mode 100644 index 00000000..bff1c959 --- /dev/null +++ b/hlsdk/dmc/cl_dll/hud.h @@ -0,0 +1,656 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud.h +// +// class CHud declaration +// +// CHud handles the message, calculation, and drawing the HUD +// + + +#define RGB_YELLOWISH 0x00FFA000 //255,160,0 +#define RGB_REDISH 0x00FF1010 //255,160,0 +#define RGB_GREENISH 0x0000A000 //0,160,0 +#define RGB_NORMAL 0x00FFFFFF // 255,255,255 + +#include "wrect.h" + +#include "cl_dll.h" +#include "ammo.h" + +#define DHN_DRAWZERO 1 +#define DHN_2DIGITS 2 +#define DHN_3DIGITS 4 +#define MIN_ALPHA 100 + +#define HUDELEM_ACTIVE 1 + +typedef struct { + int x, y; +} POSITION; + +typedef struct { + unsigned char r,g,b,a; +} RGBA; +typedef struct cvar_s cvar_t; + + +#define HUD_ACTIVE 1 +#define HUD_INTERMISSION 2 + +#define MAX_PLAYER_NAME_LENGTH 32 + +#define MAX_MOTD_LENGTH 1024 + + +enum +{ + MAX_PLAYERS = 64, + MAX_TEAMS = 64, + MAX_TEAM_NAME = 16, +}; + +struct extra_player_info_t +{ + short frags; + short deaths; + short teamnumber; + char teamname[MAX_TEAM_NAME]; +}; + +struct team_info_t +{ + char name[MAX_TEAM_NAME]; + short frags; + short deaths; + short ping; + short packetloss; + short ownteam; + short players; + int already_drawn; + int scores_overriden; + int teamnumber; +}; + +extern hud_player_info_t g_PlayerInfoList[MAX_PLAYERS+1]; // player info from the engine +extern extra_player_info_t g_PlayerExtraInfo[MAX_PLAYERS+1]; // additional player info sent directly to the client dll +extern team_info_t g_TeamInfo[MAX_TEAMS+1]; + +// +//----------------------------------------------------- +// +class CHudBase +{ +public: + POSITION m_pos; + int m_type; + int m_iFlags; // active, moving, + virtual int Init( void ) {return 0;} + virtual int VidInit( void ) {return 0;} + virtual int Draw(float flTime) {return 0;} + virtual void Think(void) {return;} + virtual void Reset(void) {return;} + virtual void InitHUDData( void ) {} // called every time a server is connected to + +}; + +struct HUDLIST { + CHudBase *p; + HUDLIST *pNext; +}; + + +#include "hud_spectator.h" +// +//----------------------------------------------------- +// +class CHudAmmo: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + void Think(void); + void Reset(void); + int DrawWList(float flTime); + int DrawFastList(float flTime); + float m_flFastListTime; + int MsgFunc_CurWeapon(const char *pszName, int iSize, void *pbuf); + int MsgFunc_WeaponList(const char *pszName, int iSize, void *pbuf); + int MsgFunc_AmmoX(const char *pszName, int iSize, void *pbuf); + int MsgFunc_AmmoPickup( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_WeapPickup( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_ItemPickup( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_HideWeapon( const char *pszName, int iSize, void *pbuf ); + + void _cdecl UserCmd_Slot1( void ); + void _cdecl UserCmd_Slot2( void ); + void _cdecl UserCmd_Slot3( void ); + void _cdecl UserCmd_Slot4( void ); + void _cdecl UserCmd_Slot5( void ); + void _cdecl UserCmd_Slot6( void ); + void _cdecl UserCmd_Slot7( void ); + void _cdecl UserCmd_Slot8( void ); + void _cdecl UserCmd_Slot9( void ); + void _cdecl UserCmd_Slot10( void ); + void _cdecl UserCmd_Close( void ); + void _cdecl UserCmd_NextWeapon( void ); + void _cdecl UserCmd_PrevWeapon( void ); + + int m_iXPosition; + int m_iNumberXPosition; + HSPRITE m_sprAmmoSprite; + int m_HUD_Ammo; + wrect_t *m_prc1; + +private: + float m_fFade; + RGBA m_rgba; + WEAPON *m_pWeapon; + int m_HUD_bucket0; + int m_HUD_selection; + + +}; + +// +//----------------------------------------------------- +// + +class CHudAmmoSecondary: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + void Reset( void ); + int Draw(float flTime); + + int MsgFunc_SecAmmoVal( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_SecAmmoIcon( const char *pszName, int iSize, void *pbuf ); + +private: + enum { + MAX_SEC_AMMO_VALUES = 4 + }; + + int m_HUD_ammoicon; // sprite indices + int m_iAmmoAmounts[MAX_SEC_AMMO_VALUES]; + float m_fFade; +}; + + +#include "health.h" + + +#define FADE_TIME 100 + +// +//----------------------------------------------------- +// +class CHudGeiger: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_Geiger(const char *pszName, int iSize, void *pbuf); + +private: + int m_iGeigerRange; + +}; + +// +//----------------------------------------------------- +// +class CHudTrain: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_Train(const char *pszName, int iSize, void *pbuf); + +private: + HSPRITE m_hSprite; + int m_iPos; + +}; +// +//----------------------------------------------------- +// +// REMOVED: Vgui has replaced this. +// +// +//----------------------------------------------------- +/* +class CHudMOTD : public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw( float flTime ); + void Reset( void ); + + int MsgFunc_MOTD( const char *pszName, int iSize, void *pbuf ); + +protected: + static int MOTD_DISPLAY_TIME; + char m_szMOTD[ MAX_MOTD_LENGTH ]; + float m_flActiveRemaining; + int m_iLines; +}; +*/ +// +//----------------------------------------------------- +// +class CHudStatusBar : public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw( float flTime ); + void Reset( void ); + void ParseStatusString( int line_num ); + + int MsgFunc_StatusText( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_StatusValue( const char *pszName, int iSize, void *pbuf ); + +protected: + enum { + MAX_STATUSTEXT_LENGTH = 128, + MAX_STATUSBAR_VALUES = 8, + MAX_STATUSBAR_LINES = 2, + }; + + HSPRITE m_hArmor; + HSPRITE m_hHealth; + int m_iArmorSpriteIndex; + int m_iHealthSpriteIndex; + + + char m_szStatusText[MAX_STATUSBAR_LINES][MAX_STATUSTEXT_LENGTH]; // a text string describing how the status bar is to be drawn + char m_szStatusBar[MAX_STATUSBAR_LINES][MAX_STATUSTEXT_LENGTH]; // the constructed bar that is drawn + int m_iStatusValues[MAX_STATUSBAR_VALUES]; // an array of values for use in the status bar + + char m_szName[MAX_STATUSBAR_LINES][MAX_STATUSTEXT_LENGTH]; + char m_szHealth[MAX_STATUSBAR_LINES][MAX_STATUSTEXT_LENGTH]; + char m_szArmor[MAX_STATUSBAR_LINES][MAX_STATUSTEXT_LENGTH]; + + int m_iTeamMate[MAX_STATUSBAR_LINES]; + + int m_bReparseString; // set to TRUE whenever the m_szStatusBar needs to be recalculated +}; + +// +//----------------------------------------------------- +// + +/*class CHudScoreboard: public CHudBase +{ +public: + int Init( void ); + void InitHUDData( void ); + int VidInit( void ); + int Draw( float flTime ); + int DrawPlayers( int xoffset, float listslot, int nameoffset = 0, char *team = NULL ); // returns the ypos where it finishes drawing + void UserCmd_ShowScores( void ); + void UserCmd_HideScores( void ); + int MsgFunc_ScoreInfo( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_TeamInfo( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_TeamScore( const char *pszName, int iSize, void *pbuf ); + void DeathMsg( int killer, int victim ); + + + + int m_iNumTeams; + + int m_iLastKilledBy; + int m_fLastKillTime; + int m_iPlayerNum; + int m_iShowscoresHeld; + + void GetAllPlayersInfo( void ); + + + +}; + +*/ + +// +//----------------------------------------------------- +// +class CHudDeathNotice : public CHudBase +{ +public: + int Init( void ); + void InitHUDData( void ); + int VidInit( void ); + int Draw( float flTime ); + int MsgFunc_DeathMsg( const char *pszName, int iSize, void *pbuf ); + +private: + int m_HUD_d_skull; // sprite index of skull icon + + char szText[256][4]; +}; + +// +//----------------------------------------------------- +// +class CHudMenu : public CHudBase +{ +public: + int Init( void ); + void InitHUDData( void ); + int VidInit( void ); + void Reset( void ); + int Draw( float flTime ); + int MsgFunc_ShowMenu( const char *pszName, int iSize, void *pbuf ); + + void SelectMenuItem( int menu_item ); + + int m_fMenuDisplayed; + int m_bitsValidSlots; + float m_flShutoffTime; + int m_fWaitingForMore; +}; + +// +//----------------------------------------------------- +// +class CHudSayText : public CHudBase +{ +public: + int Init( void ); + void InitHUDData( void ); + int VidInit( void ); + int Draw( float flTime ); + int MsgFunc_SayText( const char *pszName, int iSize, void *pbuf ); + void SayTextPrint( const char *pszBuf, int iBufSize, int clientIndex = -1 ); + void EnsureTextFitsInOneLineAndWrapIfHaveTo( int line ); + + struct cvar_s * m_HUD_saytext; + struct cvar_s * m_HUD_saytext_time; +}; + +// +//----------------------------------------------------- +// +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 +}; + + +// +//----------------------------------------------------- +// +const int maxHUDMessages = 16; +struct message_parms_t +{ + client_textmessage_t *pMessage; + float time; + int x, y; + int totalWidth, totalHeight; + int width; + int lines; + int lineLength; + int length; + int r, g, b; + int text; + int fadeBlend; + float charTime; + float fadeTime; +}; + +// +//----------------------------------------------------- +// + +class CHudTextMessage: public CHudBase +{ +public: + int Init( void ); + static char *LocaliseTextString( const char *msg, char *dst_buffer, int buffer_size ); + static char *BufferedLocaliseTextString( const char *msg ); + char *LookupString( const char *msg_name, int *msg_dest = NULL ); + int MsgFunc_TextMsg(const char *pszName, int iSize, void *pbuf); +}; + +// +//----------------------------------------------------- +// + +class CHudMessage: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_HudText(const char *pszName, int iSize, void *pbuf); + int MsgFunc_GameTitle(const char *pszName, int iSize, void *pbuf); + + float FadeBlend( float fadein, float fadeout, float hold, float localTime ); + int XPosition( float x, int width, int lineWidth ); + int YPosition( float y, int height ); + + void MessageAdd( const char *pName, float time ); + void MessageAdd(client_textmessage_t * newMessage ); + void MessageDrawScan( client_textmessage_t *pMessage, float time ); + void MessageScanStart( void ); + void MessageScanNextChar( void ); + void Reset( void ); + +private: + client_textmessage_t *m_pMessages[maxHUDMessages]; + float m_startTime[maxHUDMessages]; + message_parms_t m_parms; + float m_gameTitleTime; + client_textmessage_t *m_pGameTitle; + + int m_HUD_title_life; + int m_HUD_title_half; +}; + +// +//----------------------------------------------------- +// +#define MAX_SPRITE_NAME_LENGTH 24 + +class CHudStatusIcons: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + void Reset( void ); + int Draw(float flTime); + int MsgFunc_StatusIcon(const char *pszName, int iSize, void *pbuf); + + enum { + MAX_ICONSPRITENAME_LENGTH = MAX_SPRITE_NAME_LENGTH, + MAX_ICONSPRITES = 4, + }; + + + //had to make these public so CHud could access them (to enable concussion icon) + //could use a friend declaration instead... + void EnableIcon( char *pszIconName, unsigned char red, unsigned char green, unsigned char blue ); + void DisableIcon( char *pszIconName ); + +private: + + typedef struct + { + char szSpriteName[MAX_ICONSPRITENAME_LENGTH]; + HSPRITE spr; + wrect_t rc; + unsigned char r, g, b; + } icon_sprite_t; + + icon_sprite_t m_IconList[MAX_ICONSPRITES]; + +}; + + +// +//----------------------------------------------------- +// +class CVoiceStatus; +typedef struct cvar_s cvar_t; + +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; + float m_flTime; // the current client time + float m_fOldTime; // the time at which the HUD was last redrawn + double m_flTimeDelta; // the difference between flTime and fOldTime + Vector m_vecOrigin; + Vector m_vecAngles; + int m_iKeyBits; + int m_iHideHUDDisplay; + int m_iFOV; + int m_Teamplay; + int m_iRes; + cvar_t *m_pCvarStealMouse; + cvar_t *m_pCvarDraw; + + // QUAKECLASSIC + int m_iQuakeItems; + + int m_iFontHeight; + int DrawHudNumber(int x, int y, int iFlags, int iNumber, int r, int g, int b ); + int DrawHudString(int x, int y, int iMaxX, char *szString, int r, int g, int b ); + + int DrawHudStringCTF(int x, int y, int iMaxX, char *szString, int r, int g, int b ); + + int ReturnStringPixelLength ( char *Hihi ); + + int DrawHudStringReverse( int xpos, int ypos, int iMinX, char *szString, int r, int g, int b ); + int DrawHudNumberString( int xpos, int ypos, int iMinX, int iNumber, int r, int g, int b ); + int GetNumWidth(int iNumber, int iFlags); + +private: + // the memory for these arrays are allocated in the first call to CHud::VidInit(), when the hud.txt and associated sprites are loaded. + // freed in ~CHud() + HSPRITE *m_rghSprites; /*[HUD_SPRITE_COUNT]*/ // the sprites loaded from hud.txt + wrect_t *m_rgrcRects; /*[HUD_SPRITE_COUNT]*/ + char *m_rgszSpriteNames; /*[HUD_SPRITE_COUNT][MAX_SPRITE_NAME_LENGTH]*/ + + struct cvar_s *default_fov; +public: + HSPRITE GetSprite( int index ) + { + return (index < 0) ? 0 : m_rghSprites[index]; + } + + wrect_t& GetSpriteRect( int index ) + { + return m_rgrcRects[index]; + } + + + int GetSpriteIndex( const char *SpriteName ); // gets a sprite index, for use in the m_rghSprites[] array + + CHudAmmo m_Ammo; + CHudHealth m_Health; + CHudSpectator m_Spectator; + CHudGeiger m_Geiger; + CHudBattery m_Battery; + CHudTrain m_Train; + CHudMessage m_Message; +// CHudScoreboard m_Scoreboard; +// CHudMOTD m_MOTD; + CHudStatusBar m_StatusBar; + CHudDeathNotice m_DeathNotice; + CHudSayText m_SayText; + CHudMenu m_Menu; + CHudAmmoSecondary m_AmmoSecondary; + CHudTextMessage m_TextMessage; + CHudStatusIcons m_StatusIcons; + + void Init( void ); + void VidInit( void ); + void Think(void); + int Redraw( float flTime, int intermission ); + int UpdateClientData( client_data_t *cdata, float time ); + + CHud(); + ~CHud(); // destructor, frees allocated memory + + // 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); + int _cdecl MsgFunc_Concuss( const char *pszName, int iSize, void *pbuf ); + + // QUAKECLASSIC + int _cdecl MsgFunc_QItems( const char *pszName, int iSize, void *pbuf ); + + // Screen information + SCREENINFO m_scrinfo; + + int m_iWeaponBits; + int m_fPlayerDead; + int m_iIntermission; + + // sprite indexes + int m_HUD_number_0; + + + void AddHudElem(CHudBase *p); + + float GetSensitivity(); +}; + +class TeamFortressViewport; + +extern CHud gHUD; +extern TeamFortressViewport *gViewPort; + +extern int g_iUser1; +extern int g_iUser2; +extern int g_iUser3; + diff --git a/hlsdk/dmc/cl_dll/hud_iface.h b/hlsdk/dmc/cl_dll/hud_iface.h new file mode 100644 index 00000000..a513e775 --- /dev/null +++ b/hlsdk/dmc/cl_dll/hud_iface.h @@ -0,0 +1,20 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( HUD_IFACEH ) +#define HUD_IFACEH +#pragma once + +#define EXPORT _declspec( dllexport ) +#define _DLLEXPORT __declspec( dllexport ) + +typedef int (*pfnUserMsgHook)(const char *pszName, int iSize, void *pbuf); +#include "wrect.h" +#include "../engine/cdll_int.h" +extern cl_enginefunc_t gEngfuncs; + +#endif \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/hud_msg.cpp b/hlsdk/dmc/cl_dll/hud_msg.cpp new file mode 100644 index 00000000..dd2231d4 --- /dev/null +++ b/hlsdk/dmc/cl_dll/hud_msg.cpp @@ -0,0 +1,171 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud_msg.cpp +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +extern float g_flLightTime; + +#define MAX_TELES 256 +Vector g_vecTeleMins[ MAX_TELES ]; +Vector g_vecTeleMaxs[ MAX_TELES ]; +int g_iTeleNum; +bool g_bLoadedTeles; + +float g_iFogColor[3]; +float g_iStartDist; +float g_iEndDist; + +/// USER-DEFINED SERVER MESSAGE HANDLERS + +int CHud :: MsgFunc_ResetHUD(const char *pszName, int iSize, void *pbuf ) +{ + ASSERT( iSize == 0 ); + + // clear all hud data + HUDLIST *pList = m_pHudList; + + while ( pList ) + { + if ( pList->p ) + pList->p->Reset(); + pList = pList->pNext; + } + + // reset sensitivity + m_flMouseSensitivity = 0; + + // reset concussion effect + m_iConcussionEffect = 0; + + g_flLightTime = 0.0; + + return 1; +} + +void CHud :: MsgFunc_InitHUD( const char *pszName, int iSize, void *pbuf ) +{ + g_iTeleNum = 0; + g_bLoadedTeles = false; + int i; + + //Clear all the teleporters + for ( i = 0; i < MAX_TELES; i++ ) + { + g_vecTeleMins[ i ].x = 0.0; + g_vecTeleMins[ i ].y = 0.0; + g_vecTeleMins[ i ].z = 0.0; + + g_vecTeleMaxs[ i ].x = 0.0; + g_vecTeleMaxs[ i ].y = 0.0; + g_vecTeleMaxs[ i ].z = 0.0; + } + + /***** FOG CLEARING JIBBA JABBA *****/ + for ( i = 0; i < 4; i++ ) + g_iFogColor[ i ] = 0.0; + + g_iStartDist = 0.0; + g_iEndDist = 0.0; + /***** FOG CLEARING JIBBA JABBA *****/ + + // prepare all hud data + HUDLIST *pList = m_pHudList; + + while (pList) + { + if ( pList->p ) + pList->p->InitHUDData(); + pList = pList->pNext; + } + + BEGIN_READ( pbuf, iSize ); + g_iTeleNum = READ_BYTE(); + + for ( i = 0; i < g_iTeleNum; i++ ) + { + g_vecTeleMins[ i ].x = READ_COORD(); + g_vecTeleMins[ i ].y = READ_COORD(); + g_vecTeleMins[ i ].z = READ_COORD(); + g_vecTeleMaxs[ i ].x = READ_COORD(); + g_vecTeleMaxs[ i ].y = READ_COORD(); + g_vecTeleMaxs[ i ].z = READ_COORD(); + } + + for ( i = 0; i < 3; i++ ) + g_iFogColor[ i ] = READ_SHORT(); // Should just get a byte. + + //If they both are 0, it means no fog for this level. + g_iStartDist = READ_SHORT(); + g_iEndDist = READ_SHORT(); +} + + +int CHud :: MsgFunc_GameMode(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + m_Teamplay = READ_BYTE(); + + return 1; +} + + +int CHud :: MsgFunc_Damage(const char *pszName, int iSize, void *pbuf ) +{ + int armor, blood; + Vector from; + int i; + float count; + + BEGIN_READ( pbuf, iSize ); + armor = READ_BYTE(); + blood = READ_BYTE(); + + for (i=0 ; i<3 ; i++) + from[i] = READ_COORD(); + + count = (blood * 0.5) + (armor * 0.5); + + if (count < 10) + count = 10; + + // TODO: kick viewangles, show damage visually + + return 1; +} + +int CHud :: MsgFunc_Concuss( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + m_iConcussionEffect = READ_BYTE(); + if (m_iConcussionEffect) + this->m_StatusIcons.EnableIcon("dmg_concuss",255,160,0); + else + this->m_StatusIcons.DisableIcon("dmg_concuss"); + return 1; +} + +// QUAKECLASSIC +int CHud :: MsgFunc_QItems(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + m_iQuakeItems = READ_LONG(); + + return 1; +} \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/hud_redraw.cpp b/hlsdk/dmc/cl_dll/hud_redraw.cpp new file mode 100644 index 00000000..eed945ee --- /dev/null +++ b/hlsdk/dmc/cl_dll/hud_redraw.cpp @@ -0,0 +1,444 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud_redraw.cpp +// +#include +#include "hud.h" +#include "cl_util.h" +#include +#include "vgui_viewport.h" + +extern int g_iVisibleMouse; + +#define MAX_LOGO_FRAMES 56 + +int grgLogoFrame[MAX_LOGO_FRAMES] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 13, 13, 13, 13, 13, 12, 11, 10, 9, 8, 14, 15, + 16, 17, 18, 19, 20, 20, 20, 20, 20, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 29, 29, 29, 29, 29, 28, 27, 26, 25, 24, 30, 31 +}; + + +// Think +void CHud::Think(void) +{ + HUDLIST *pList = m_pHudList; + while (pList) + { + if (pList->p->m_iFlags & HUD_ACTIVE) + pList->p->Think(); + pList = pList->pNext; + } + + // think about default fov + if ( m_iFOV == 0 ) + { // only let players adjust up in fov, and only if they are not overriden by something else + m_iFOV = max( default_fov->value, 90 ); + } + + +} + +// Redraw +// step through the local data, placing the appropriate graphics & text as appropriate +// returns 1 if they've changed, 0 otherwise +int CHud :: Redraw( float flTime, int intermission ) +{ + m_fOldTime = m_flTime; // save time of previous redraw + m_flTime = flTime; + m_flTimeDelta = (double)m_flTime - m_fOldTime; + static int m_flShotTime = 0; + + // Clock was reset, reset delta + if ( m_flTimeDelta < 0 ) + m_flTimeDelta = 0; + + // Bring up the scoreboard during intermission + if (gViewPort) + { + if ( m_iIntermission && !intermission ) + { + // Have to do this here so the scoreboard goes away + m_iIntermission = intermission; + gViewPort->HideCommandMenu(); + gViewPort->HideScoreBoard(); + gViewPort->UpdateSpectatorPanel(); + } + else if ( !m_iIntermission && intermission ) + { + m_iIntermission = intermission; + gViewPort->HideCommandMenu(); + gViewPort->HideVGUIMenu(); + gViewPort->ShowScoreBoard(); + gViewPort->UpdateSpectatorPanel(); + + // Take a screenshot if the client's got the cvar set + if ( CVAR_GET_FLOAT( "hud_takesshots" ) != 0 ) + m_flShotTime = flTime + 1.0; // Take a screenshot in a second + } + } + + if (m_flShotTime && m_flShotTime < flTime) + { + gEngfuncs.pfnClientCmd("snapshot\n"); + m_flShotTime = 0; + } + // if no redrawing is necessary + // return 0; + + // draw all registered HUD elements + if ( m_pCvarDraw->value ) + { + HUDLIST *pList = m_pHudList; + + while (pList) + { + if ( !intermission ) + { + if ((pList->p->m_iFlags & HUD_ACTIVE) && !(m_iHideHUDDisplay & HIDEHUD_ALL)) + pList->p->Draw(flTime); + } + else + { // it's an intermission, so only draw hud elements that are set to draw during intermissions + if ( pList->p->m_iFlags & HUD_INTERMISSION ) + pList->p->Draw( flTime ); + } + + 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); + } + + + return 1; +} + +void ScaleColors( int &r, int &g, int &b, int a ) +{ + float x = (float)a / 255; + r = (int)(r * x); + g = (int)(g * x); + b = (int)(b * x); +} + + + +/* +=========================== +int ReturnStringPixelLength ( char *Hihi ) + +Returns a integer representing the length of the string passed +=========================== +*/ +int CHud :: ReturnStringPixelLength ( char *Hihi ) +{ + int iNameLength = 0; + + int strleng = ( strlen( Hihi ) ); + + for ( int har = 0; har < strleng; har++) + iNameLength += gHUD.m_scrinfo.charWidths[ Hihi[har] ]; + + return iNameLength; +} + +int LastColor; + +int CHud :: DrawHudStringCTF(int xpos, int ypos, int iMaxX, char *szIt, int r, int g, int b ) +{ + int WantColor = 0; + int Color = 0; + + // draw the string until we hit the null character or a newline character + for ( ; *szIt != 0 && *szIt != '\n'; szIt++ ) + { + int next;// = xpos + gHUD.m_scrinfo.charWidths[ *szIt ]; // variable-width fonts look cool + + /* if ( next > iMaxX ) + return xpos;*/ + + + if (*szIt == '\\') + { + if (Color > 0) + Color = 0; + + WantColor = 1; + + } + + if (WantColor == 1 && *szIt == 'w') + { + Color = 1; + LastColor = Color; + } + + + if (WantColor == 1 && *szIt == 'g') + { + Color = 2; + LastColor = Color; + } + + + + if (WantColor == 1 && *szIt == 'b') + { + Color = 3; + LastColor = Color; + } + + + if (WantColor == 1 && *szIt == 'r') + { + Color = 4; + LastColor = Color; + } + + + + if (WantColor == 1 && *szIt == 'y') + { + Color = 5; + LastColor = Color; + } + + + + if (WantColor == 1 && *szIt == 'q') + { + Color = 6; + LastColor = Color; + } + + + + if (Color == 0 && WantColor == 0) + { + if (LastColor == 1) + TextMessageDrawChar( xpos, ypos, *szIt, 255, 255, 255 ); + if (LastColor == 2) + TextMessageDrawChar( xpos, ypos, *szIt, 0, 79, 0); + if (LastColor == 3) + TextMessageDrawChar( xpos, ypos, *szIt, 0, 0, 200); + if (LastColor == 4) + TextMessageDrawChar( xpos, ypos, *szIt, 200, 0, 0 ); + if (LastColor == 5) + TextMessageDrawChar( xpos, ypos, *szIt, 198, 221, 66 ); + if (LastColor == 6) + TextMessageDrawChar( xpos, ypos, *szIt, 136, 136, 136 ); + + else if (LastColor == 0) + TextMessageDrawChar( xpos, ypos, *szIt, r, g, b ); + + next = xpos + gHUD.m_scrinfo.charWidths[ *szIt ]; + } + + else if (Color > 0 && WantColor == 0 ) + { + if (Color == 1) + TextMessageDrawChar( xpos, ypos, *szIt, 255, 255, 255 ); + if (Color == 2) + TextMessageDrawChar( xpos, ypos, *szIt, 0, 79, 0); + if (Color == 3) + TextMessageDrawChar( xpos, ypos, *szIt, 0, 0, 200); + if (Color == 4) + TextMessageDrawChar( xpos, ypos, *szIt, 200, 0, 0 ); + if (Color == 5) + TextMessageDrawChar( xpos, ypos, *szIt, 198, 221, 66 ); + if (Color == 6) + TextMessageDrawChar( xpos, ypos, *szIt, 136, 136, 136 ); + + next = xpos + gHUD.m_scrinfo.charWidths[ *szIt ]; + } + + else if (Color > 0 && WantColor == 1) + { + //next = xpos + (gHUD.m_scrinfo.charWidths[ *szIt ] * 2); // variable-width fonts look cool + WantColor = 0; + } + + + xpos = next; + } + + return xpos; +} + + +int CHud :: DrawHudString(int xpos, int ypos, int iMaxX, char *szIt, int r, int g, int b ) +{ + // draw the string until we hit the null character or a newline character + for ( ; *szIt != 0 && *szIt != '\n'; szIt++ ) + { + int next = xpos + gHUD.m_scrinfo.charWidths[ *szIt ]; // variable-width fonts look cool + if ( next > iMaxX ) + return xpos; + + TextMessageDrawChar( xpos, ypos, *szIt, r, g, b ); + xpos = next; + } + + return xpos; +} + +int CHud :: DrawHudNumberString( int xpos, int ypos, int iMinX, int iNumber, int r, int g, int b ) +{ + char szString[32]; + sprintf( szString, "%d", iNumber ); + return DrawHudStringReverse( xpos, ypos, iMinX, szString, r, g, b ); + +} + +// draws a string from right to left (right-aligned) +int CHud :: DrawHudStringReverse( int xpos, int ypos, int iMinX, char *szString, int r, int g, int b ) +{ + // find the end of the string + char *szIt = NULL; + for ( szIt = szString; *szIt != 0; szIt++ ) + { // we should count the length? + } + + // iterate throug the string in reverse + for ( szIt--; szIt != (szString-1); szIt-- ) + { + int next = xpos - gHUD.m_scrinfo.charWidths[ *szIt ]; // variable-width fonts look cool + if ( next < iMinX ) + return xpos; + xpos = next; + + TextMessageDrawChar( xpos, ypos, *szIt, r, g, b ); + } + + return xpos; +} + +int CHud :: DrawHudNumber( int x, int y, int iFlags, int iNumber, int r, int g, int b) +{ + int iWidth = GetSpriteRect(m_HUD_number_0).right - GetSpriteRect(m_HUD_number_0).left; + int k; + + if (iNumber > 0) + { + // SPR_Draw 100's + if (iNumber >= 100) + { + k = iNumber/100; + SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); + SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); + x += iWidth; + } + else if (iFlags & (DHN_3DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + // SPR_Draw 10's + if (iNumber >= 10) + { + k = (iNumber % 100)/10; + SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); + SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); + x += iWidth; + } + else if (iFlags & (DHN_3DIGITS | DHN_2DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + // SPR_Draw ones + k = iNumber % 10; + SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); + SPR_DrawAdditive(0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); + x += iWidth; + } + else if (iFlags & DHN_DRAWZERO) + { + SPR_Set(GetSprite(m_HUD_number_0), r, g, b ); + + // SPR_Draw 100's + if (iFlags & (DHN_3DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + if (iFlags & (DHN_3DIGITS | DHN_2DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + // SPR_Draw ones + + SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0)); + x += iWidth; + } + + return x; +} + + +int CHud::GetNumWidth( int iNumber, int iFlags ) +{ + if (iFlags & (DHN_3DIGITS)) + return 3; + + if (iFlags & (DHN_2DIGITS)) + return 2; + + if (iNumber <= 0) + { + if (iFlags & (DHN_DRAWZERO)) + return 1; + else + return 0; + } + + if (iNumber < 10) + return 1; + + if (iNumber < 100) + return 2; + + return 3; + +} + + diff --git a/hlsdk/dmc/cl_dll/hud_servers.cpp b/hlsdk/dmc/cl_dll/hud_servers.cpp new file mode 100644 index 00000000..8ce5c32f --- /dev/null +++ b/hlsdk/dmc/cl_dll/hud_servers.cpp @@ -0,0 +1,1249 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// hud_servers.cpp +#include "hud.h" +#include "cl_util.h" +#include "hud_servers_priv.h" +#include "hud_servers.h" +#include "net_api.h" +#include +#include + +static int context_id; + +// Default master server address in case we can't read any from woncomm.lst file +#define VALVE_MASTER_ADDRESS "half-life.east.won.net" +#define PORT_MASTER 27010 +#define PORT_SERVER 27015 + +// File where we really should look for master servers +#define MASTER_PARSE_FILE "woncomm.lst" + +#define MAX_QUERIES 20 + +#define NET_API gEngfuncs.pNetAPI + +static CHudServers *g_pServers = NULL; + +/* +=================== +ListResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK ListResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->ListResponse( response ); + } +} + +/* +=================== +ServerResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK ServerResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->ServerResponse( response ); + } +} + +/* +=================== +PingResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK PingResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->PingResponse( response ); + } +} + +/* +=================== +RulesResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK RulesResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->RulesResponse( response ); + } +} +/* +=================== +PlayersResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK PlayersResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->PlayersResponse( response ); + } +} +/* +=================== +ListResponse + +=================== +*/ +void CHudServers::ListResponse( struct net_response_s *response ) +{ + request_t *list; + request_t *p; + int c = 0; + + if ( !( response->error == NET_SUCCESS ) ) + return; + + if ( response->type != NETAPI_REQUEST_SERVERLIST ) + return; + + if ( response->response ) + { + list = ( request_t * ) response->response; + while ( list ) + { + c++; + + //if ( c < 40 ) + { + // Copy from parsed stuff + p = new request_t; + p->context = -1; + p->remote_address = list->remote_address; + p->next = m_pServerList; + m_pServerList = p; + } + + // Move on + list = list->next; + } + } + + gEngfuncs.Con_Printf( "got list\n" ); + + m_nQuerying = 1; + m_nActiveQueries = 0; +} + +/* +=================== +ServerResponse + +=================== +*/ +void CHudServers::ServerResponse( struct net_response_s *response ) +{ + char *szresponse; + request_t *p; + server_t *browser; + int len; + char sz[ 32 ]; + + // Remove from active list + p = FindRequest( response->context, m_pActiveList ); + if ( p ) + { + static int first = 0; + + RemoveServerFromList( &m_pActiveList, p ); + m_nActiveQueries--; + + if ( !first ) + { + gEngfuncs.Con_Printf( "recv first %f\n", gEngfuncs.GetClientTime() ); + first = 1; + } + } + + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_DETAILS: + if ( response->response ) + { + szresponse = (char *)response->response; + len = strlen( szresponse ) + 100 + 1; + sprintf( sz, "%i", (int)( 1000.0 * response->ping ) ); + + browser = new server_t; + browser->remote_address = response->remote_address; + browser->info = new char[ len ]; + browser->ping = (int)( 1000.0 * response->ping ); + strcpy( browser->info, szresponse ); + + NET_API->SetValueForKey( browser->info, "address", gEngfuncs.pNetAPI->AdrToString( &response->remote_address ), len ); + NET_API->SetValueForKey( browser->info, "ping", sz, len ); + + AddServer( &m_pServers, browser ); + } + break; + default: + break; + } +} + +/* +=================== +PingResponse + +=================== +*/ +void CHudServers::PingResponse( struct net_response_s *response ) +{ + char sz[ 32 ]; + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_PING: + sprintf( sz, "%.2f", 1000.0 * response->ping ); + + gEngfuncs.Con_Printf( "ping == %s\n", sz ); + break; + default: + break; + } +} + +/* +=================== +RulesResponse + +=================== +*/ +void CHudServers::RulesResponse( struct net_response_s *response ) +{ + char *szresponse; + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_RULES: + if ( response->response ) + { + szresponse = (char *)response->response; + + gEngfuncs.Con_Printf( "rules %s\n", szresponse ); + } + break; + default: + break; + } +} + +/* +=================== +PlayersResponse + +=================== +*/ +void CHudServers::PlayersResponse( struct net_response_s *response ) +{ + char *szresponse; + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_PLAYERS: + if ( response->response ) + { + szresponse = (char *)response->response; + + gEngfuncs.Con_Printf( "players %s\n", szresponse ); + } + break; + default: + break; + } +} + +/* +=================== +CompareServers + +Return 1 if p1 is "less than" p2, 0 otherwise +=================== +*/ +int CHudServers::CompareServers( server_t *p1, server_t *p2 ) +{ + const char *n1, *n2; + + if ( p1->ping < p2->ping ) + return 1; + + if ( p1->ping == p2->ping ) + { + // Pings equal, sort by second key: hostname + if ( p1->info && p2->info ) + { + n1 = NET_API->ValueForKey( p1->info, "hostname" ); + n2 = NET_API->ValueForKey( p2->info, "hostname" ); + + if ( n1 && n2 ) + { + if ( stricmp( n1, n2 ) < 0 ) + return 1; + } + } + } + + return 0; +} + +/* +=================== +AddServer + +=================== +*/ +void CHudServers::AddServer( server_t **ppList, server_t *p ) +{ +server_t *list; + + if ( !ppList || ! p ) + return; + + m_nServerCount++; + + // What sort key? Ping? + list = *ppList; + + // Head of list? + if ( !list ) + { + p->next = NULL; + *ppList = p; + return; + } + + // Put on head of list + if ( CompareServers( p, list ) ) + { + p->next = *ppList; + *ppList = p; + } + else + { + while ( list->next ) + { + // Insert before list next + if ( CompareServers( p, list->next ) ) + { + p->next = list->next->next; + list->next = p; + return; + } + + list = list->next; + } + + // Just add at end + p->next = NULL; + list->next = p; + } +} + +/* +=================== +Think + +=================== +*/ +void CHudServers::Think( double time ) +{ + m_fElapsed += time; + + if ( !m_nRequesting ) + return; + + if ( !m_nQuerying ) + return; + + QueryThink(); + + if ( ServerListSize() > 0 ) + return; + + m_dStarted = 0.0; + m_nRequesting = 0; + m_nDone = 0; + m_nQuerying = 0; + m_nActiveQueries = 0; +} + +/* +=================== +QueryThink + +=================== +*/ +void CHudServers::QueryThink( void ) +{ + request_t *p; + + if ( !m_nRequesting || m_nDone ) + return; + + if ( !m_nQuerying ) + return; + + if ( m_nActiveQueries > MAX_QUERIES ) + return; + + // Nothing left + if ( !m_pServerList ) + return; + + while ( 1 ) + { + static int first = 0; + p = m_pServerList; + + // No more in list? + if ( !p ) + break; + + // Move to next + m_pServerList = m_pServerList->next; + + // Setup context_id + p->context = context_id; + + // Make sure networking system has started. + // NET_API->InitNetworking(); + + if ( !first ) + { + gEngfuncs.Con_Printf( "send first %f\n", gEngfuncs.GetClientTime() ); + first = 1; + } + + // Start up query on this one + NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, 0, 2.0, &p->remote_address, ::ServerResponse ); + + // Increment active list + m_nActiveQueries++; + + // Add to active list + p->next = m_pActiveList; + m_pActiveList = p; + + // Too many active? + if ( m_nActiveQueries > MAX_QUERIES ) + break; + } +} + +/* +================== +ServerListSize + +# of servers in active query and in pending to be queried lists +================== +*/ +int CHudServers::ServerListSize( void ) +{ + int c = 0; + request_t *p; + + p = m_pServerList; + while ( p ) + { + c++; + p = p->next; + } + + p = m_pActiveList; + while ( p ) + { + c++; + p = p->next; + } + + return c; +} + +/* +=================== +FindRequest + +Look up a request by context id +=================== +*/ +CHudServers::request_t *CHudServers::FindRequest( int context, request_t *pList ) +{ + request_t *p; + p = pList; + while ( p ) + { + if ( context == p->context ) + return p; + + p = p->next; + } + return NULL; +} + +/* +=================== +RemoveServerFromList + +Remote, but don't delete, item from *ppList +=================== +*/ +void CHudServers::RemoveServerFromList( request_t **ppList, request_t *item ) +{ + request_t *p, *n; + request_t *newlist = NULL; + + if ( !ppList ) + return; + + p = *ppList; + while ( p ) + { + n = p->next; + if ( p != item ) + { + p->next = newlist; + newlist = p; + } + p = n; + } + *ppList = newlist; +} + +/* +=================== +ClearRequestList + +=================== +*/ +void CHudServers::ClearRequestList( request_t **ppList ) +{ + request_t *p, *n; + + if ( !ppList ) + return; + + p = *ppList; + while ( p ) + { + n = p->next; + delete p; + p = n; + } + *ppList = NULL; +} + +/* +=================== +ClearServerList + +=================== +*/ +void CHudServers::ClearServerList( server_t **ppList ) +{ + server_t *p, *n; + + if ( !ppList ) + return; + + p = *ppList; + while ( p ) + { + n = p->next; + delete[] p->info; + delete p; + p = n; + } + *ppList = NULL; +} + +int CompareField( CHudServers::server_t *p1, CHudServers::server_t *p2, const char *fieldname, int iSortOrder ) +{ + const char *sz1, *sz2; + float fv1, fv2; + + sz1 = NET_API->ValueForKey( p1->info, fieldname ); + sz2 = NET_API->ValueForKey( p2->info, fieldname ); + + fv1 = atof( sz1 ); + fv2 = atof( sz2 ); + + if ( fv1 && fv2 ) + { + if ( fv1 > fv2 ) + return iSortOrder; + else if ( fv1 < fv2 ) + return -iSortOrder; + else + return 0; + } + + // String compare + return stricmp( sz1, sz2 ); +} + +int CALLBACK ServerListCompareFunc( CHudServers::server_t *p1, CHudServers::server_t *p2, const char *fieldname ) +{ + if (!p1 || !p2) // No meaningful comparison + return 0; + + int iSortOrder = 1; + + int retval = 0; + + retval = CompareField( p1, p2, fieldname, iSortOrder ); + + return retval; +} + +static char g_fieldname[ 256 ]; +int __cdecl FnServerCompare(const void *elem1, const void *elem2 ) +{ + CHudServers::server_t *list1, *list2; + + list1 = *(CHudServers::server_t **)elem1; + list2 = *(CHudServers::server_t **)elem2; + + return ServerListCompareFunc( list1, list2, g_fieldname ); +} + +void CHudServers::SortServers( const char *fieldname ) +{ + server_t *p; + // Create a list + if ( !m_pServers ) + return; + + strcpy( g_fieldname, fieldname ); + + int i; + int c = 0; + + p = m_pServers; + while ( p ) + { + c++; + p = p->next; + } + + server_t **pSortArray; + + pSortArray = new server_t *[ c ]; + memset( pSortArray, 0, c * sizeof( server_t * ) ); + + // Now copy the list into the pSortArray: + p = m_pServers; + i = 0; + while ( p ) + { + pSortArray[ i++ ] = p; + p = p->next; + } + + // Now do that actual sorting. + size_t nCount = c; + size_t nSize = sizeof( server_t * ); + + qsort( + pSortArray, + (size_t)nCount, + (size_t)nSize, + FnServerCompare + ); + + // Now rebuild the list. + m_pServers = pSortArray[0]; + for ( i = 0; i < c - 1; i++ ) + { + pSortArray[ i ]->next = pSortArray[ i + 1 ]; + } + pSortArray[ c - 1 ]->next = NULL; + + // Clean Up. + delete[] pSortArray; +} + +/* +=================== +GetServer + +Return particular server +=================== +*/ +CHudServers::server_t *CHudServers::GetServer( int server ) +{ + int c = 0; + server_t *p; + + p = m_pServers; + while ( p ) + { + if ( c == server ) + return p; + + c++; + p = p->next; + } + return NULL; +} + +/* +=================== +GetServerInfo + +Return info ( key/value ) string for particular server +=================== +*/ +char *CHudServers::GetServerInfo( int server ) +{ + server_t *p = GetServer( server ); + if ( p ) + { + return p->info; + } + return NULL; +} + +/* +=================== +CancelRequest + +Kill all pending requests in engine +=================== +*/ +void CHudServers::CancelRequest( void ) +{ + m_nRequesting = 0; + m_nQuerying = 0; + m_nDone = 1; + + NET_API->CancelAllRequests(); +} + +/* +================== +LoadMasterAddresses + +Loads the master server addresses from file and into the passed in array +================== +*/ +int CHudServers::LoadMasterAddresses( int maxservers, int *count, netadr_t *padr ) +{ + int i; + char szMaster[ 256 ]; + char szMasterFile[256]; + char *pbuffer = NULL; + char *pstart = NULL ; + netadr_t adr; + char szAdr[64]; + int nPort; + int nCount = 0; + bool bIgnore; + int nDefaultPort; + + // Assume default master and master file + strcpy( szMaster, VALVE_MASTER_ADDRESS ); // IP:PORT string + strcpy( szMasterFile, MASTER_PARSE_FILE ); + + // See if there is a command line override + i = gEngfuncs.CheckParm( "-comm", &pstart ); + if ( i && pstart ) + { + strcpy (szMasterFile, pstart ); + } + + // Read them in from proper file + pbuffer = (char *)gEngfuncs.COM_LoadFile( szMasterFile, 5, NULL ); // Use malloc + if ( !pbuffer ) + { + goto finish_master; + } + + pstart = pbuffer; + + while ( nCount < maxservers ) + { + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if ( strlen(m_szToken) <= 0) + break; + + bIgnore = true; + + if ( !stricmp( m_szToken, "Master" ) ) + { + nDefaultPort = PORT_MASTER; + bIgnore = FALSE; + } + + // Now parse all addresses between { } + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + if ( strlen(m_szToken) <= 0 ) + break; + + if ( stricmp ( m_szToken, "{" ) ) + break; + + // Parse addresses until we get to "}" + while ( nCount < maxservers ) + { + char base[256]; + + // Now parse all addresses between { } + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if (strlen(m_szToken) <= 0) + break; + + if ( !stricmp ( m_szToken, "}" ) ) + break; + + sprintf( base, "%s", m_szToken ); + + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if (strlen(m_szToken) <= 0) + break; + + if ( stricmp( m_szToken, ":" ) ) + break; + + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if (strlen(m_szToken) <= 0) + break; + + nPort = atoi ( m_szToken ); + if ( !nPort ) + nPort = nDefaultPort; + + sprintf( szAdr, "%s:%i", base, nPort ); + + // Can we resolve it any better + if ( !NET_API->StringToAdr( szAdr, &adr ) ) + bIgnore = true; + + if ( !bIgnore ) + { + padr[ nCount++ ] = adr; + } + } + } + +finish_master: + if ( !nCount ) + { + sprintf( szMaster, VALVE_MASTER_ADDRESS ); // IP:PORT string + + // Convert to netadr_t + if ( NET_API->StringToAdr ( szMaster, &adr ) ) + { + + padr[ nCount++ ] = adr; + } + } + + *count = nCount; + + if ( pbuffer ) + { + gEngfuncs.COM_FreeFile( pbuffer ); + } + + return ( nCount > 0 ) ? 1 : 0; +} + +/* +=================== +RequestList + +Request list of game servers from master +=================== +*/ +void CHudServers::RequestList( void ) +{ + m_nRequesting = 1; + m_nDone = 0; + m_dStarted = m_fElapsed; + + int count = 0; + netadr_t adr; + + if ( !LoadMasterAddresses( 1, &count, &adr ) ) + { + gEngfuncs.Con_DPrintf( "SendRequest: Unable to read master server addresses\n" ); + return; + } + + ClearRequestList( &m_pActiveList ); + ClearRequestList( &m_pServerList ); + ClearServerList( &m_pServers ); + + m_nServerCount = 0; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Kill off left overs if any + NET_API->CancelAllRequests(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_SERVERLIST, 0, 5.0, &adr, ::ListResponse ); +} + +void CHudServers::RequestBroadcastList( int clearpending ) +{ + m_nRequesting = 1; + m_nDone = 0; + m_dStarted = m_fElapsed; + + netadr_t adr; + memset( &adr, 0, sizeof( adr ) ); + + if ( clearpending ) + { + ClearRequestList( &m_pActiveList ); + ClearRequestList( &m_pServerList ); + ClearServerList( &m_pServers ); + + m_nServerCount = 0; + } + + // Make sure to byte swap server if necessary ( using "host" to "net" conversion + adr.port = htons( PORT_SERVER ); + + // Make sure networking system has started. + NET_API->InitNetworking(); + + if ( clearpending ) + { + // Kill off left overs if any + NET_API->CancelAllRequests(); + } + + adr.type = NA_BROADCAST; + + // Request Servers from LAN via IP + NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, FNETAPI_MULTIPLE_RESPONSE, 5.0, &adr, ::ServerResponse ); + + adr.type = NA_BROADCAST_IPX; + + // Request Servers from LAN via IPX ( if supported ) + NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, FNETAPI_MULTIPLE_RESPONSE, 5.0, &adr, ::ServerResponse ); +} + +void CHudServers::ServerPing( int server ) +{ + server_t *p; + + p = GetServer( server ); + if ( !p ) + return; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_PING, 0, 5.0, &p->remote_address, ::PingResponse ); +} + +void CHudServers::ServerRules( int server ) +{ + server_t *p; + + p = GetServer( server ); + if ( !p ) + return; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_RULES, 0, 5.0, &p->remote_address, ::RulesResponse ); +} + +void CHudServers::ServerPlayers( int server ) +{ + server_t *p; + + p = GetServer( server ); + if ( !p ) + return; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_PLAYERS, 0, 5.0, &p->remote_address, ::PlayersResponse ); +} + +int CHudServers::isQuerying() +{ + return m_nRequesting ? 1 : 0; +} + + +/* +=================== +GetServerCount + +Return number of servers in browser list +=================== +*/ +int CHudServers::GetServerCount( void ) +{ + return m_nServerCount; +} + +/* +=================== +CHudServers + +=================== +*/ +CHudServers::CHudServers( void ) +{ + m_nRequesting = 0; + m_dStarted = 0.0; + m_nDone = 0; + m_pServerList = NULL; + m_pServers = NULL; + m_pActiveList = NULL; + m_nQuerying = 0; + m_nActiveQueries = 0; + + m_fElapsed = 0.0; + + + m_pPingRequest = NULL; + m_pRulesRequest = NULL; + m_pPlayersRequest = NULL; +} + +/* +=================== +~CHudServers + +=================== +*/ +CHudServers::~CHudServers( void ) +{ + ClearRequestList( &m_pActiveList ); + ClearRequestList( &m_pServerList ); + ClearServerList( &m_pServers ); + + m_nServerCount = 0; + + if ( m_pPingRequest ) + { + delete m_pPingRequest; + m_pPingRequest = NULL; + + } + + if ( m_pRulesRequest ) + { + delete m_pRulesRequest; + m_pRulesRequest = NULL; + } + + if ( m_pPlayersRequest ) + { + delete m_pPlayersRequest; + m_pPlayersRequest = NULL; + } +} + +/////////////////////////////// +// +// PUBLIC APIs +// +/////////////////////////////// + +/* +=================== +ServersGetCount + +=================== +*/ +int ServersGetCount( void ) +{ + if ( g_pServers ) + { + return g_pServers->GetServerCount(); + } + return 0; +} + +int ServersIsQuerying( void ) +{ + if ( g_pServers ) + { + return g_pServers->isQuerying(); + } + return 0; +} + +/* +=================== +ServersGetInfo + +=================== +*/ +const char *ServersGetInfo( int server ) +{ + if ( g_pServers ) + { + return g_pServers->GetServerInfo( server ); + } + + return NULL; +} + +void SortServers( const char *fieldname ) +{ + if ( g_pServers ) + { + g_pServers->SortServers( fieldname ); + } +} + +/* +=================== +ServersShutdown + +=================== +*/ +void ServersShutdown( void ) +{ + if ( g_pServers ) + { + delete g_pServers; + g_pServers = NULL; + } +} + +/* +=================== +ServersInit + +=================== +*/ +void ServersInit( void ) +{ + // Kill any previous instance + ServersShutdown(); + + g_pServers = new CHudServers(); +} + +/* +=================== +ServersThink + +=================== +*/ +void ServersThink( double time ) +{ + if ( g_pServers ) + { + g_pServers->Think( time ); + } +} + +/* +=================== +ServersCancel + +=================== +*/ +void ServersCancel( void ) +{ + if ( g_pServers ) + { + g_pServers->CancelRequest(); + } +} + +// Requests +/* +=================== +ServersList + +=================== +*/ +void ServersList( void ) +{ + if ( g_pServers ) + { + g_pServers->RequestList(); + } +} + +void BroadcastServersList( int clearpending ) +{ + if ( g_pServers ) + { + g_pServers->RequestBroadcastList( clearpending ); + } +} + +void ServerPing( int server ) +{ + if ( g_pServers ) + { + g_pServers->ServerPing( server ); + } +} + +void ServerRules( int server ) +{ + if ( g_pServers ) + { + g_pServers->ServerRules( server ); + } +} + +void ServerPlayers( int server ) +{ + if ( g_pServers ) + { + g_pServers->ServerPlayers( server ); + } +} \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/hud_servers.h b/hlsdk/dmc/cl_dll/hud_servers.h new file mode 100644 index 00000000..01e94425 --- /dev/null +++ b/hlsdk/dmc/cl_dll/hud_servers.h @@ -0,0 +1,41 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( HUD_SERVERSH ) +#define HUD_SERVERSH +#pragma once + +#define NET_CALLBACK /* */ + +// Dispatchers +void NET_CALLBACK ListResponse( struct net_response_s *response ); +void NET_CALLBACK ServerResponse( struct net_response_s *response ); +void NET_CALLBACK PingResponse( struct net_response_s *response ); +void NET_CALLBACK RulesResponse( struct net_response_s *response ); +void NET_CALLBACK PlayersResponse( struct net_response_s *response ); + +void ServersInit( void ); +void ServersShutdown( void ); +void ServersThink( double time ); +void ServersCancel( void ); + +// Get list and get server info from each +void ServersList( void ); + +// Query for IP / IPX LAN servers +void BroadcastServersList( int clearpending ); + +void ServerPing( int server ); +void ServerRules( int server ); +void ServerPlayers( int server ); + +int ServersGetCount( void ); +const char *ServersGetInfo( int server ); +int ServersIsQuerying( void ); +void SortServers( const char *fieldname ); + +#endif // HUD_SERVERSH \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/hud_servers_priv.h b/hlsdk/dmc/cl_dll/hud_servers_priv.h new file mode 100644 index 00000000..73692f46 --- /dev/null +++ b/hlsdk/dmc/cl_dll/hud_servers_priv.h @@ -0,0 +1,98 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( HUD_SERVERS_PRIVH ) +#define HUD_SERVERS_PRIVH +#pragma once + +#include "netadr.h" + +class CHudServers +{ +public: + typedef struct request_s + { + struct request_s *next; + netadr_t remote_address; + int context; + } request_t; + + typedef struct server_s + { + struct server_s *next; + netadr_t remote_address; + char *info; + int ping; + } server_t; + + CHudServers(); + ~CHudServers(); + + void Think( double time ); + void QueryThink( void ); + int isQuerying( void ); + + int LoadMasterAddresses( int maxservers, int *count, netadr_t *padr ); + + void RequestList( void ); + void RequestBroadcastList( int clearpending ); + + void ServerPing( int server ); + void ServerRules( int server ); + void ServerPlayers( int server ); + + void CancelRequest( void ); + + int CompareServers( server_t *p1, server_t *p2 ); + + void ClearServerList( server_t **ppList ); + void ClearRequestList( request_t **ppList ); + + void AddServer( server_t **ppList, server_t *p ); + + void RemoveServerFromList( request_t **ppList, request_t *item ); + + request_t *FindRequest( int context, request_t *pList ); + + int ServerListSize( void ); + char *GetServerInfo( int server ); + int GetServerCount( void ); + void SortServers( const char *fieldname ); + + void ListResponse( struct net_response_s *response ); + void ServerResponse( struct net_response_s *response ); + void PingResponse( struct net_response_s *response ); + void RulesResponse( struct net_response_s *response ); + void PlayersResponse( struct net_response_s *response ); +private: + + server_t *GetServer( int server ); + + // + char m_szToken[ 1024 ]; + int m_nRequesting; + int m_nDone; + + double m_dStarted; + + request_t *m_pServerList; + request_t *m_pActiveList; + + server_t *m_pServers; + + int m_nServerCount; + + int m_nActiveQueries; + int m_nQuerying; + double m_fElapsed; + + request_t *m_pPingRequest; + request_t *m_pRulesRequest; + request_t *m_pPlayersRequest; +}; + +#endif // HUD_SERVERS_PRIVH \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/hud_spectator.cpp b/hlsdk/dmc/cl_dll/hud_spectator.cpp new file mode 100644 index 00000000..a5dfc40f --- /dev/null +++ b/hlsdk/dmc/cl_dll/hud_spectator.cpp @@ -0,0 +1,1576 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "hud.h" +#include "cl_util.h" +#include "cl_entity.h" +#include "triangleapi.h" +#include "vgui_viewport.h" +#include "vgui_SpectatorPanel.h" +#include "hltv.h" + +#include "pm_shared.h" +#include "pm_defs.h" +#include "pmtrace.h" +#include "parsemsg.h" +#include "entity_types.h" + +// these are included for the math functions +#include "com_model.h" +#include "demo_api.h" +#include "studio_util.h" + +#pragma warning(disable: 4244) + +extern "C" int iJumpSpectator; +extern "C" float vJumpOrigin[3]; +extern "C" float vJumpAngles[3]; + + +extern void V_GetInEyePos(int entity, float * origin, float * angles ); +extern void V_ResetChaseCam(); +extern void V_GetChasePos(int target, float * cl_angles, float * origin, float * angles); +extern void VectorAngles( const float *forward, float *angles ); +extern "C" void NormalizeAngles( float *angles ); +extern float * GetClientColor( int clientIndex ); + +extern vec3_t v_origin; // last view origin +extern vec3_t v_angles; // last view angle +extern vec3_t v_cl_angles; // last client/mouse angle +extern vec3_t v_sim_org; // last sim origin + +void SpectatorMode(void) +{ + + + if ( gEngfuncs.Cmd_Argc() <= 1 ) + { + gEngfuncs.Con_Printf( "usage: spec_mode
[]\n" ); + return; + } + + // SetModes() will decide if command is executed on server or local + if ( gEngfuncs.Cmd_Argc() == 2 ) + gHUD.m_Spectator.SetModes( atoi( gEngfuncs.Cmd_Argv(1) ), -1 ); + else if ( gEngfuncs.Cmd_Argc() == 3 ) + gHUD.m_Spectator.SetModes( atoi( gEngfuncs.Cmd_Argv(1) ), atoi( gEngfuncs.Cmd_Argv(2) ) ); +} + +void SpectatorSpray(void) +{ + vec3_t forward; + char string[128]; + + if ( !gEngfuncs.IsSpectateOnly() ) + return; + + AngleVectors(v_angles,forward,NULL,NULL); + VectorScale(forward, 128, forward); + VectorAdd(forward, v_origin, forward); + pmtrace_t * trace = gEngfuncs.PM_TraceLine( v_origin, forward, PM_TRACELINE_PHYSENTSONLY, 2, -1 ); + if ( trace->fraction != 1.0 ) + { + sprintf(string, "drc_spray %.2f %.2f %.2f %i", + trace->endpos[0], trace->endpos[1], trace->endpos[2], trace->ent ); + gEngfuncs.pfnServerCmd(string); + } + +} +void SpectatorHelp(void) +{ + if ( gViewPort ) + { + gViewPort->ShowVGUIMenu( MENU_SPECHELP ); + } + else + { + char *text = CHudTextMessage::BufferedLocaliseTextString( "#Spec_Help_Text" ); + + if ( text ) + { + while ( *text ) + { + if ( *text != 13 ) + gEngfuncs.Con_Printf( "%c", *text ); + text++; + } + } + } +} + +void SpectatorMenu( void ) +{ + if ( gEngfuncs.Cmd_Argc() <= 1 ) + { + gEngfuncs.Con_Printf( "usage: spec_menu <0|1>\n" ); + return; + } + + gViewPort->m_pSpectatorPanel->ShowMenu( atoi( gEngfuncs.Cmd_Argv(1))!=0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CHudSpectator::Init() +{ + gHUD.AddHudElem(this); + + m_iFlags |= HUD_ACTIVE; + m_flNextObserverInput = 0.0f; + m_zoomDelta = 0.0f; + m_moveDelta = 0.0f; + m_chatEnabled = (gHUD.m_SayText.m_HUD_saytext->value!=0); + iJumpSpectator = 0; + + memset( &m_OverviewData, 0, sizeof(m_OverviewData)); + memset( &m_OverviewEntities, 0, sizeof(m_OverviewEntities)); + m_lastPrimaryObject = m_lastSecondaryObject = 0; + + gEngfuncs.pfnAddCommand ("spec_mode", SpectatorMode ); + gEngfuncs.pfnAddCommand ("spec_decal", SpectatorSpray ); + gEngfuncs.pfnAddCommand ("spec_help", SpectatorHelp ); + gEngfuncs.pfnAddCommand ("spec_menu", SpectatorMenu ); + + m_drawnames = gEngfuncs.pfnRegisterVariable("spec_drawnames","1",0); + m_drawcone = gEngfuncs.pfnRegisterVariable("spec_drawcone","1",0); + m_drawstatus = gEngfuncs.pfnRegisterVariable("spec_drawstatus","1",0); + m_autoDirector = gEngfuncs.pfnRegisterVariable("spec_autodirector","1",0); + m_pip = gEngfuncs.pfnRegisterVariable("spec_pip","1",0); + + + if ( !m_drawnames || !m_drawcone || !m_drawstatus || !m_autoDirector || !m_pip) + { + gEngfuncs.Con_Printf("ERROR! Couldn't register all spectator variables.\n"); + return 0; + } + + return 1; +} + + +//----------------------------------------------------------------------------- +// UTIL_StringToVector originally from ..\dlls\util.cpp, slightly changed +//----------------------------------------------------------------------------- + +void UTIL_StringToVector( float * pVector, const char *pString ) +{ + char *pstr, *pfront, tempString[128]; + int j; + + strcpy( tempString, pString ); + pstr = pfront = tempString; + + for ( j = 0; j < 3; j++ ) + { + pVector[j] = atof( pfront ); + + while ( *pstr && *pstr != ' ' ) + pstr++; + if (!*pstr) + break; + pstr++; + pfront = pstr; + } + + if (j < 2) + { + for (j = j+1;j < 3; j++) + pVector[j] = 0; + } +} + +int UTIL_FindEntityInMap(char * name, float * origin, float * angle) +{ + int n,found = 0; + char keyname[256]; + char token[1024]; + + cl_entity_t * pEnt = gEngfuncs.GetEntityByIndex( 0 ); // get world model + + if ( !pEnt ) return 0; + + if ( !pEnt->model ) return 0; + + char * data = pEnt->model->entities; + + while (data) + { + data = gEngfuncs.COM_ParseFile(data, token); + + if ( (token[0] == '}') || (token[0]==0) ) + break; + + if (!data) + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: EOF without closing brace\n"); + return 0; + } + + if (token[0] != '{') + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: expected {\n"); + return 0; + } + + // we parse the first { now parse entities properties + + while ( 1 ) + { + // parse key + data = gEngfuncs.COM_ParseFile(data, token); + if (token[0] == '}') + break; // finish parsing this entity + + if (!data) + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: EOF without closing brace\n"); + return 0; + }; + + strcpy (keyname, token); + + // another hack to fix keynames with trailing spaces + n = strlen(keyname); + while (n && keyname[n-1] == ' ') + { + keyname[n-1] = 0; + n--; + } + + // parse value + data = gEngfuncs.COM_ParseFile(data, token); + if (!data) + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: EOF without closing brace\n"); + return 0; + }; + + if (token[0] == '}') + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: closing brace without data"); + return 0; + } + + if (!strcmp(keyname,"classname")) + { + if (!strcmp(token, name )) + { + found = 1; // thats our entity + } + }; + + if( !strcmp( keyname, "angle" ) ) + { + float y = atof( token ); + + if (y >= 0) + { + angle[0] = 0.0f; + angle[1] = y; + } + else if ((int)y == -1) + { + angle[0] = -90.0f; + angle[1] = 0.0f;; + } + else + { + angle[0] = 90.0f; + angle[1] = 0.0f; + } + + angle[2] = 0.0f; + } + + if( !strcmp( keyname, "angles" ) ) + { + UTIL_StringToVector(angle, token); + } + + if (!strcmp(keyname,"origin")) + { + UTIL_StringToVector(origin, token); + + }; + + } // while (1) + + if (found) + return 1; + + } + + return 0; // we search all entities, but didn't found the correct + +} + +//----------------------------------------------------------------------------- +// SetSpectatorStartPosition(): +// Get valid map position and 'beam' spectator to this position +//----------------------------------------------------------------------------- + +void CHudSpectator::SetSpectatorStartPosition() +{ + VectorCopy(vec3_origin, m_cameraOrigin); + VectorCopy(vec3_origin, m_cameraAngles); + + + // search for info_player start + if (!UTIL_FindEntityInMap( "trigger_camera", m_cameraOrigin, m_cameraAngles ) ) + { + if (!UTIL_FindEntityInMap( "info_player_start", m_cameraOrigin, m_cameraAngles ) ) + gEngfuncs.Con_Printf("Couldn't find spectator spawn point.\n"); + // uh, we didn't find anything + } + + VectorCopy(m_cameraOrigin, vJumpOrigin); + VectorCopy(m_cameraAngles, vJumpAngles); + + iJumpSpectator = 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Loads new icons +//----------------------------------------------------------------------------- +int CHudSpectator::VidInit() +{ + m_hsprPlayer = SPR_Load("sprites/iplayer.spr"); + m_hsprPlayerBlue = SPR_Load("sprites/iplayerblue.spr"); + m_hsprPlayerRed = SPR_Load("sprites/iplayerred.spr"); + m_hsprPlayerDead = SPR_Load("sprites/iplayerdead.spr"); + m_hsprUnkownMap = SPR_Load("sprites/tile.spr"); + m_hsprBeam = SPR_Load("sprites/laserbeam.spr"); + m_hsprCamera = SPR_Load("sprites/camera.spr"); + m_hCrosshair = SPR_Load("sprites/crosshairs.spr"); + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flTime - +// intermission - +//----------------------------------------------------------------------------- +int CHudSpectator::Draw(float flTime) +{ + int lx; + + char string[256]; + float * color; + + // draw only in spectator mode + if ( !g_iUser1 ) + return 1; + + // if user pressed zoom, aplly changes + if ( (m_zoomDelta != 0.0f) && (g_iUser1 != OBS_ROAMING) ) + { + m_mapZoom += m_zoomDelta; + + if ( m_mapZoom > 3.0f ) + m_mapZoom = 3.0f; + + if ( m_mapZoom < 0.5f ) + m_mapZoom = 0.5f; + } + + // if user moves in map mode, change map origin + if ( (m_moveDelta != 0.0f) && (g_iUser1 != OBS_ROAMING) ) + { + vec3_t right; + AngleVectors(v_angles, NULL, right, NULL); + VectorNormalize(right); + VectorScale(right, m_moveDelta, right ); + + VectorAdd( m_mapOrigin, right, m_mapOrigin ) + + } + + // Only draw the icon names only if map mode is in Main Mode + if ( g_iUser1 < OBS_MAP_FREE ) + return 1; + + if ( !m_drawnames->value ) + return 1; + + // 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++) + { + + if ( m_vPlayerPos[i][2]<0 ) // marked as invisible ? + continue; + + // check if name would be in inset window + if ( m_pip->value != INSET_OFF ) + { + if ( m_vPlayerPos[i][0] > XRES( m_OverviewData.insetWindowX ) && + m_vPlayerPos[i][1] > YRES( m_OverviewData.insetWindowY ) && + m_vPlayerPos[i][0] < XRES( m_OverviewData.insetWindowX + m_OverviewData.insetWindowWidth ) && + m_vPlayerPos[i][1] < YRES( m_OverviewData.insetWindowY + m_OverviewData.insetWindowHeight) + ) continue; + } + + color = GetClientColor( i+1 ); + + // draw the players name and health underneath + sprintf(string, "%s", g_PlayerInfoList[i+1].name ); + + lx = strlen(string)*3; // 3 is avg. character length :) + + gEngfuncs.pfnDrawSetTextColor( color[0], color[1], color[2] ); + DrawConsoleString( m_vPlayerPos[i][0]-lx,m_vPlayerPos[i][1], string); + + } + + return 1; +} + + +void CHudSpectator::DirectorMessage( int iSize, void *pbuf ) +{ + float value; + char * string; + + BEGIN_READ( pbuf, iSize ); + + int cmd = READ_BYTE(); + + switch ( cmd ) // director command byte + { + case DRC_CMD_START : + // now we have to do some things clientside, since the proxy doesn't know our mod + + // fake a InitHUD message + gHUD.MsgFunc_InitHUD(NULL,0, NULL); + + break; + + case DRC_CMD_EVENT : + m_lastPrimaryObject = READ_WORD(); + m_lastSecondaryObject = READ_WORD(); + m_iObserverFlags = READ_LONG(); + + if ( m_autoDirector->value ) + { + if ( (g_iUser2 != m_lastPrimaryObject) || (g_iUser3 != m_lastSecondaryObject) ) + V_ResetChaseCam(); + + g_iUser2 = m_lastPrimaryObject; + g_iUser3 = m_lastSecondaryObject; + } + + // gEngfuncs.Con_Printf("Director Camera: %i %i\n", firstObject, secondObject); + break; + + case DRC_CMD_MODE : + if ( m_autoDirector->value ) + { + SetModes( READ_BYTE(), -1 ); + } + break; + + case DRC_CMD_CAMERA : + if ( m_autoDirector->value ) + { + vJumpOrigin[0] = READ_COORD(); // position + vJumpOrigin[1] = READ_COORD(); + vJumpOrigin[2] = READ_COORD(); + + vJumpAngles[0] = READ_COORD(); // view angle + vJumpAngles[1] = READ_COORD(); + vJumpAngles[0] = READ_COORD(); + + iJumpSpectator = 1; + } + break; + + case DRC_CMD_MESSAGE: + { + client_textmessage_t * msg = &m_HUDMessages[m_lastHudMessage]; + + msg->effect = READ_BYTE(); // effect + + UnpackRGB( (int&)msg->r1, (int&)msg->g1, (int&)msg->b2, READ_LONG() ); // color + msg->r2 = msg->r1; + msg->g2 = msg->g1; + msg->b2 = msg->b1; + msg->a2 = msg->a1 = 0xFF; // not transparent + + msg->x = READ_FLOAT(); // x pos + msg->y = READ_FLOAT(); // y pos + + msg->fadein = READ_FLOAT(); // fadein + msg->fadeout = READ_FLOAT(); // fadeout + msg->holdtime = READ_FLOAT(); // holdtime + msg->fxtime = READ_FLOAT(); // fxtime; + + strncpy( m_HUDMessageText[m_lastHudMessage], READ_STRING(), 128 ); + m_HUDMessageText[m_lastHudMessage][127]=0; // text + + msg->pMessage = m_HUDMessageText[m_lastHudMessage]; + msg->pName = "HUD_MESSAGE"; + + gHUD.m_Message.MessageAdd( msg ); + + m_lastHudMessage++; + m_lastHudMessage %= MAX_SPEC_HUD_MESSAGES; + + } + + break; + + case DRC_CMD_SOUND : + string = READ_STRING(); + value = READ_FLOAT(); + + // gEngfuncs.Con_Printf("DRC_CMD_FX_SOUND: %s %.2f\n", string, value ); + PlaySound( string, value ); + + break; + case DRC_CMD_TIMESCALE : + value = READ_FLOAT(); + break; + + + +/* case DRC_CMD_STATUS: + READ_LONG(); // total number of spectator slots + m_iSpectatorNumber = READ_LONG(); // total number of spectator + READ_WORD(); // total number of relay proxies + + gViewPort->UpdateSpectatorPanel(); + break; + + case DRC_CMD_BANNER: + // gEngfuncs.Con_DPrintf("GUI: Banner %s\n",READ_STRING() ); // name of banner tga eg gfx/temp/7454562234563475.tga + gViewPort->m_pSpectatorPanel->m_TopBanner->LoadImage( READ_STRING() ); + gViewPort->UpdateSpectatorPanel(); + break; */ + + case DRC_CMD_FADE: + /*{ + screenfade_t sf; + + sf.fader = 255; + sf.fadeg = 0; + sf.fadeb = 0; + sf.fadealpha = 128; + sf.fadeFlags = FFADE_STAYOUT | FFADE_OUT; + + // gHUD.m_flTime = cl.time + + stream->ReadFloat(); // duration + sf.stream->ReadFloat(); // holdTime + sf.fadeFlags = READ_SHORT(); // flags + stream->ReadLong(); // color RGB + + CallEnghudSetScreenFade( &sf ); + }*/ + break; + + case DRC_CMD_STUFFTEXT: + ClientCmd( READ_STRING() ); + break; + + default : gEngfuncs.Con_DPrintf("CHudSpectator::DirectorMessage: unknown command %i.\n", cmd ); + } +} + +void CHudSpectator::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; + cl_entity_t * pEnt = NULL; + + // if we are NOT in HLTV mode, spectator targets are set on server + if ( !gEngfuncs.IsSpectateOnly() ) + { + char cmdstring[32]; + // forward command to server + sprintf(cmdstring,"follownext %i",bReverse?1:0); + gEngfuncs.pfnServerCmd(cmdstring); + return; + } + + if ( g_iUser2 ) + iStart = g_iUser2; + else + iStart = 1; + + g_iUser2 = 0; + + int iCurrent = iStart; + + int iDir = bReverse ? -1 : 1; + + // make sure we have player info + gViewPort->GetAllPlayersInfo(); + + + do + { + iCurrent += iDir; + + // Loop through the clients + if (iCurrent > MAX_PLAYERS) + iCurrent = 1; + if (iCurrent < 1) + iCurrent = MAX_PLAYERS; + + pEnt = gEngfuncs.GetEntityByIndex( iCurrent ); + + if ( !IsActivePlayer( pEnt ) ) + continue; + + // MOD AUTHORS: Add checks on target here. + + g_iUser2 = iCurrent; + break; + + } while ( iCurrent != iStart ); + + // Did we find a target? + if ( !g_iUser2 ) + { + gEngfuncs.Con_DPrintf( "No observer targets.\n" ); + // take save camera position + VectorCopy(m_cameraOrigin, vJumpOrigin); + VectorCopy(m_cameraAngles, vJumpAngles); + } + else + { + // use new entity position for roaming + VectorCopy ( pEnt->origin, vJumpOrigin ); + VectorCopy ( pEnt->angles, vJumpAngles ); + } + iJumpSpectator = 1; +} + +void CHudSpectator::HandleButtonsDown( int ButtonPressed ) +{ + double time = gEngfuncs.GetClientTime(); + + int newMainMode = -1; + int newInsetMode = m_pip->value; + + // gEngfuncs.Con_Printf(" HandleButtons:%i\n", ButtonPressed ); + if ( !gViewPort ) + return; + + if ( !g_iUser1 ) + return; // dont do anything if not in spectator mode + + // don't handle buttons during normal demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() && !gEngfuncs.IsSpectateOnly() ) + return; + // Slow down mouse clicks. + if ( m_flNextObserverInput > time ) + return; + + // enable spectator screen + if ( ButtonPressed & IN_DUCK ) + gViewPort->m_pSpectatorPanel->ShowMenu(!gViewPort->m_pSpectatorPanel->m_menuVisible); + + // 'Use' changes inset window mode + if ( ButtonPressed & IN_USE ) + { + newInsetMode = ToggleInset(true); + } + + // if not in HLTV mode, buttons are handled server side + if ( gEngfuncs.IsSpectateOnly() ) + { + // changing target or chase mode not in overviewmode without inset window + + // Jump changes main window modes + if ( ButtonPressed & IN_JUMP ) + { + if ( g_iUser1 == OBS_CHASE_LOCKED ) + newMainMode = OBS_CHASE_FREE; + + else if ( g_iUser1 == OBS_CHASE_FREE ) + newMainMode = OBS_IN_EYE; + + else if ( g_iUser1 == OBS_IN_EYE ) + newMainMode = OBS_ROAMING; + + else if ( g_iUser1 == OBS_ROAMING ) + newMainMode = OBS_MAP_FREE; + + else if ( g_iUser1 == OBS_MAP_FREE ) + newMainMode = OBS_MAP_CHASE; + + else + newMainMode = OBS_CHASE_FREE; // don't use OBS_CHASE_LOCKED anymore + } + + // Attack moves to the next player + if ( ButtonPressed & (IN_ATTACK | IN_ATTACK2) ) + { + FindNextPlayer( (ButtonPressed & IN_ATTACK2) ? true:false ); + + if ( g_iUser1 == OBS_ROAMING ) + { + gEngfuncs.SetViewAngles( vJumpAngles ); + iJumpSpectator = 1; + + } + // lease directed mode if player want to see another player + m_autoDirector->value = 0.0f; + } + } + + SetModes(newMainMode, newInsetMode); + + if ( ButtonPressed & IN_FORWARD ) + m_zoomDelta = 0.01f; + + if ( ButtonPressed & IN_BACK ) + m_zoomDelta = -0.01f; + + if ( ButtonPressed & IN_MOVELEFT ) + m_moveDelta = -12.0f; + + if ( ButtonPressed & IN_MOVERIGHT ) + m_moveDelta = 12.0f; + + m_flNextObserverInput = time + 0.2; +} + +void CHudSpectator::HandleButtonsUp( int ButtonPressed ) +{ + if ( !gViewPort ) + return; + + if ( !gViewPort->m_pSpectatorPanel->isVisible() ) + return; // dont do anything if not in spectator mode + + if ( ButtonPressed & (IN_FORWARD | IN_BACK) ) + m_zoomDelta = 0.0f; + + if ( ButtonPressed & (IN_MOVELEFT | IN_MOVERIGHT) ) + m_moveDelta = 0.0f; +} +void CHudSpectator::SetModes(int iNewMainMode, int iNewInsetMode) +{ + static wrect_t crosshairRect; + + // if value == -1 keep old value + if ( iNewMainMode == -1 ) + iNewMainMode = g_iUser1; + + if ( iNewInsetMode == -1 ) + iNewInsetMode = m_pip->value; + + // inset mode is handled only clients side + m_pip->value = iNewInsetMode; + + if ( iNewMainMode < OBS_CHASE_LOCKED || iNewMainMode > OBS_MAP_CHASE ) + { + gEngfuncs.Con_Printf("Invalid spectator mode.\n"); + return; + } + + // main modes ettings will override inset window settings + if ( iNewMainMode != g_iUser1 ) + { + // if we are NOT in HLTV mode, main spectator mode is set on server + if ( !gEngfuncs.IsSpectateOnly() ) + { + char cmdstring[32]; + // forward command to server + sprintf(cmdstring,"specmode %i",iNewMainMode ); + gEngfuncs.pfnServerCmd(cmdstring); + return; + } + + if ( !g_iUser2 ) // make sure we have a target + { + // choose last Director object if still available + if ( IsActivePlayer( gEngfuncs.GetEntityByIndex( m_lastPrimaryObject ) ) ) + { + g_iUser2 = m_lastPrimaryObject; + g_iUser3 = m_lastSecondaryObject; + } + else + FindNextPlayer(false); // find any target + } + + switch ( iNewMainMode ) + { + case OBS_CHASE_LOCKED: g_iUser1 = OBS_CHASE_LOCKED; + break; + + case OBS_CHASE_FREE : g_iUser1 = OBS_CHASE_FREE; + break; + + case OBS_ROAMING : // jump to current vJumpOrigin/angle + g_iUser1 = OBS_ROAMING; + V_GetChasePos( g_iUser2, v_cl_angles, vJumpOrigin, vJumpAngles ); + gEngfuncs.SetViewAngles( vJumpAngles ); + iJumpSpectator = 1; + break; + + case OBS_IN_EYE : g_iUser1 = OBS_IN_EYE; + break; + + case OBS_MAP_FREE : g_iUser1 = OBS_MAP_FREE; + // reset user values + m_mapZoom = m_OverviewData.zoom; + m_mapOrigin = m_OverviewData.origin; + break; + + case OBS_MAP_CHASE : g_iUser1 = OBS_MAP_CHASE; + // reset user values + m_mapZoom = m_OverviewData.zoom; + m_mapOrigin = m_OverviewData.origin; + break; + } + + // enable or disable crosshair + if ( (g_iUser1 == OBS_IN_EYE) || (g_iUser1 == OBS_ROAMING) ) + { + crosshairRect.left = 24; + crosshairRect.top = 0; + crosshairRect.right = 48; + crosshairRect.bottom = 24; + + SetCrosshair( m_hCrosshair, crosshairRect, 255, 255, 255 ); + } + else + { + memset( &crosshairRect,0,sizeof(crosshairRect) ); + SetCrosshair( 0, crosshairRect, 0, 0, 0 ); + } + + char string[128]; + sprintf(string, "#Spec_Mode%d", g_iUser1 ); + sprintf(string, "%c%s", HUD_PRINTCENTER, CHudTextMessage::BufferedLocaliseTextString( string )); + gHUD.m_TextMessage.MsgFunc_TextMsg(NULL, strlen(string)+1, string ); + } + + gViewPort->UpdateSpectatorPanel(); + +} + +bool CHudSpectator::IsActivePlayer(cl_entity_t * ent) +{ + return ( ent && + ent->player && + ent->curstate.solid != SOLID_NOT && + ent != gEngfuncs.GetLocalPlayer() && + g_PlayerInfoList[ent->index].name != NULL + ); +} + + +bool CHudSpectator::ParseOverviewFile( ) +{ + char filename[255]; + char levelname[255]; + char token[1024]; + float height; + + char *pfile = NULL; + + memset( &m_OverviewData, 0, sizeof(m_OverviewData)); + + // fill in standrd values + m_OverviewData.insetWindowX = 4; // upper left corner + m_OverviewData.insetWindowY = 4; + m_OverviewData.insetWindowHeight = 180; + m_OverviewData.insetWindowWidth = 240; + m_OverviewData.origin[0] = 0.0f; + m_OverviewData.origin[1] = 0.0f; + m_OverviewData.origin[2] = 0.0f; + m_OverviewData.zoom = 1.0f; + m_OverviewData.layers = 0; + m_OverviewData.layersHeights[0] = 0.0f; + strcpy( m_OverviewData.map, gEngfuncs.pfnGetLevelName() ); + + if ( strlen( m_OverviewData.map ) == 0 ) + return false; // not active yet + + strcpy(levelname, m_OverviewData.map + 5); + levelname[strlen(levelname)-4] = 0; + + sprintf(filename, "overviews/%s.txt", levelname ); + + pfile = (char *)gEngfuncs.COM_LoadFile( filename, 5, NULL); + + if (!pfile) + { + gEngfuncs.Con_Printf("Couldn't open file %s. Using default values for overiew mode.\n", filename ); + return false; + } + + + while (true) + { + pfile = gEngfuncs.COM_ParseFile(pfile, token); + + if (!pfile) + break; + + if ( !stricmp( token, "global" ) ) + { + // parse the global data + pfile = gEngfuncs.COM_ParseFile(pfile, token); + if ( stricmp( token, "{" ) ) + { + gEngfuncs.Con_Printf("Error parsing overview file %s. (expected { )\n", filename ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); + + while (stricmp( token, "}") ) + { + if ( !stricmp( token, "zoom" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.zoom = atof( token ); + } + else if ( !stricmp( token, "origin" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile, token); + m_OverviewData.origin[0] = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.origin[1] = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile, token); + m_OverviewData.origin[2] = atof( token ); + } + else if ( !stricmp( token, "rotated" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.rotated = atoi( token ); + } + else if ( !stricmp( token, "inset" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.insetWindowX = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.insetWindowY = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.insetWindowWidth = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.insetWindowHeight = atof( token ); + + } + else + { + gEngfuncs.Con_Printf("Error parsing overview file %s. (%s unkown)\n", filename, token ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); // parse next token + + } + } + else if ( !stricmp( token, "layer" ) ) + { + // parse a layer data + + if ( m_OverviewData.layers == OVERVIEW_MAX_LAYERS ) + { + gEngfuncs.Con_Printf("Error parsing overview file %s. ( too many layers )\n", filename ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); + + + if ( stricmp( token, "{" ) ) + { + gEngfuncs.Con_Printf("Error parsing overview file %s. (expected { )\n", filename ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); + + while (stricmp( token, "}") ) + { + if ( !stricmp( token, "image" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + strcpy(m_OverviewData.layersImages[ m_OverviewData.layers ], token); + + + } + else if ( !stricmp( token, "height" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + height = atof(token); + m_OverviewData.layersHeights[ m_OverviewData.layers ] = height; + } + else + { + gEngfuncs.Con_Printf("Error parsing overview file %s. (%s unkown)\n", filename, token ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); // parse next token + } + + m_OverviewData.layers++; + + } + } + + gEngfuncs.COM_FreeFile( pfile ); + + m_mapZoom = m_OverviewData.zoom; + m_mapOrigin = m_OverviewData.origin; + + return true; + +} + +void CHudSpectator::LoadMapSprites() +{ + // right now only support for one map layer + if (m_OverviewData.layers > 0 ) + { + m_MapSprite = gEngfuncs.LoadMapSprite( m_OverviewData.layersImages[0] ); + } + else + m_MapSprite = NULL; // the standard "unkown map" sprite will be used instead +} + +void CHudSpectator::DrawOverviewLayer() +{ + float screenaspect, xs, ys, xStep, yStep, x,y,z; + int ix,iy,i,xTiles,yTiles,frame; + + qboolean hasMapImage = m_MapSprite?TRUE:FALSE; + model_t * dummySprite = (struct model_s *)gEngfuncs.GetSpritePointer( m_hsprUnkownMap); + + if ( hasMapImage) + { + i = m_MapSprite->numframes / (4*3); + i = static_cast(sqrt(static_cast(i))); + xTiles = i*4; + yTiles = i*3; + } + else + { + xTiles = 8; + yTiles = 6; + } + + + screenaspect = 4.0f/3.0f; + + + xs = m_OverviewData.origin[0]; + ys = m_OverviewData.origin[1]; + z = ( 90.0f - v_angles[0] ) / 90.0f; + z *= m_OverviewData.layersHeights[0]; // gOverviewData.z_min - 32; + + // i = r_overviewTexture + ( layer*OVERVIEW_X_TILES*OVERVIEW_Y_TILES ); + + gEngfuncs.pTriAPI->RenderMode( kRenderTransTexture ); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); + gEngfuncs.pTriAPI->Color4f( 1.0, 1.0, 1.0, 1.0 ); + + frame = 0; + + + // rotated view ? + if ( m_OverviewData.rotated ) + { + xStep = (2*4096.0f / m_OverviewData.zoom ) / xTiles; + yStep = -(2*4096.0f / (m_OverviewData.zoom* screenaspect) ) / yTiles; + + y = ys + (4096.0f / (m_OverviewData.zoom * screenaspect)); + + for (iy = 0; iy < yTiles; iy++) + { + x = xs - (4096.0f / (m_OverviewData.zoom)); + + for (ix = 0; ix < xTiles; ix++) + { + if (hasMapImage) + gEngfuncs.pTriAPI->SpriteTexture( m_MapSprite, frame ); + else + gEngfuncs.pTriAPI->SpriteTexture( dummySprite, 0 ); + + gEngfuncs.pTriAPI->Begin( TRI_QUADS ); + gEngfuncs.pTriAPI->TexCoord2f( 0, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x, y, z); + + gEngfuncs.pTriAPI->TexCoord2f( 1, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x+xStep ,y, z); + + gEngfuncs.pTriAPI->TexCoord2f( 1, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x+xStep, y+yStep, z); + + gEngfuncs.pTriAPI->TexCoord2f( 0, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x, y+yStep, z); + gEngfuncs.pTriAPI->End(); + + frame++; + x+= xStep; + } + + y+=yStep; + } + } + else + { + xStep = -(2*4096.0f / m_OverviewData.zoom ) / xTiles; + yStep = -(2*4096.0f / (m_OverviewData.zoom* screenaspect) ) / yTiles; + + + x = xs + (4096.0f / (m_OverviewData.zoom * screenaspect )); + + + + for (ix = 0; ix < yTiles; ix++) + { + + y = ys + (4096.0f / (m_OverviewData.zoom)); + + for (iy = 0; iy < xTiles; iy++) + { + if (hasMapImage) + gEngfuncs.pTriAPI->SpriteTexture( m_MapSprite, frame ); + else + gEngfuncs.pTriAPI->SpriteTexture( dummySprite, 0 ); + + gEngfuncs.pTriAPI->Begin( TRI_QUADS ); + gEngfuncs.pTriAPI->TexCoord2f( 0, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x, y, z); + + gEngfuncs.pTriAPI->TexCoord2f( 0, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x+xStep ,y, z); + + gEngfuncs.pTriAPI->TexCoord2f( 1, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x+xStep, y+yStep, z); + + gEngfuncs.pTriAPI->TexCoord2f( 1, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x, y+yStep, z); + gEngfuncs.pTriAPI->End(); + + frame++; + + y+=yStep; + } + + x+= xStep; + + } + } +} + +void CHudSpectator::DrawOverviewEntities() +{ + int i,ir,ig,ib; + struct model_s *hSpriteModel; + vec3_t origin, angles, point, forward, right, left, up, world, screen, offset; + float x,y,z, r,g,b, sizeScale = 4.0f; + cl_entity_t * ent; + float rmatrix[3][4]; // transformation matrix + + float zScale = (90.0f - v_angles[0] ) / 90.0f; + + + z = m_OverviewData.layersHeights[0] * zScale; + // get yellow/brown HUD color + UnpackRGB(ir,ig,ib, RGB_YELLOWISH); + r = (float)ir/255.0f; + g = (float)ig/255.0f; + b = (float)ib/255.0f; + + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); + + for (i=0; i < MAX_PLAYERS; i++ ) + m_vPlayerPos[i][2] = -1; // mark as invisible + + // draw all players + for (i=0 ; i < MAX_OVERVIEW_ENTITIES ; i++) + { + if ( !m_OverviewEntities[i].hSprite ) + continue; + + hSpriteModel = (struct model_s *)gEngfuncs.GetSpritePointer( m_OverviewEntities[i].hSprite ); + ent = m_OverviewEntities[i].entity; + + gEngfuncs.pTriAPI->SpriteTexture( hSpriteModel, 0 ); + gEngfuncs.pTriAPI->RenderMode( kRenderTransTexture ); + + // see R_DrawSpriteModel + // draws players sprite + + AngleVectors(ent->angles, right, up, NULL ); + + VectorCopy(ent->origin,origin); + + gEngfuncs.pTriAPI->Begin( TRI_QUADS ); + + gEngfuncs.pTriAPI->Color4f( 1.0, 1.0, 1.0, 1.0 ); + + gEngfuncs.pTriAPI->TexCoord2f (1, 0); + VectorMA (origin, 16.0f * sizeScale, up, point); + VectorMA (point, 16.0f * sizeScale, right, point); + point[2] *= zScale; + gEngfuncs.pTriAPI->Vertex3fv (point); + + gEngfuncs.pTriAPI->TexCoord2f (0, 0); + + VectorMA (origin, 16.0f * sizeScale, up, point); + VectorMA (point, -16.0f * sizeScale, right, point); + point[2] *= zScale; + gEngfuncs.pTriAPI->Vertex3fv (point); + + gEngfuncs.pTriAPI->TexCoord2f (0,1); + VectorMA (origin, -16.0f * sizeScale, up, point); + VectorMA (point, -16.0f * sizeScale, right, point); + point[2] *= zScale; + gEngfuncs.pTriAPI->Vertex3fv (point); + + gEngfuncs.pTriAPI->TexCoord2f (1,1); + VectorMA (origin, -16.0f * sizeScale, up, point); + VectorMA (point, 16.0f * sizeScale, right, point); + point[2] *= zScale; + gEngfuncs.pTriAPI->Vertex3fv (point); + + gEngfuncs.pTriAPI->End (); + + + if ( !ent->player) + continue; + // draw line under player icons + origin[2] *= zScale; + + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + + hSpriteModel = (struct model_s *)gEngfuncs.GetSpritePointer( m_hsprBeam ); + gEngfuncs.pTriAPI->SpriteTexture( hSpriteModel, 0 ); + + gEngfuncs.pTriAPI->Color4f(r, g, b, 0.3); + + gEngfuncs.pTriAPI->Begin ( TRI_QUADS ); + gEngfuncs.pTriAPI->TexCoord2f (1, 0); + gEngfuncs.pTriAPI->Vertex3f (origin[0]+4, origin[1]+4, origin[2]-zScale); + gEngfuncs.pTriAPI->TexCoord2f (0, 0); + gEngfuncs.pTriAPI->Vertex3f (origin[0]-4, origin[1]-4, origin[2]-zScale); + gEngfuncs.pTriAPI->TexCoord2f (0, 1); + gEngfuncs.pTriAPI->Vertex3f (origin[0]-4, origin[1]-4,z); + gEngfuncs.pTriAPI->TexCoord2f (1, 1); + gEngfuncs.pTriAPI->Vertex3f (origin[0]+4, origin[1]+4,z); + gEngfuncs.pTriAPI->End (); + + gEngfuncs.pTriAPI->Begin ( TRI_QUADS ); + gEngfuncs.pTriAPI->TexCoord2f (1, 0); + gEngfuncs.pTriAPI->Vertex3f (origin[0]-4, origin[1]+4, origin[2]-zScale); + gEngfuncs.pTriAPI->TexCoord2f (0, 0); + gEngfuncs.pTriAPI->Vertex3f (origin[0]+4, origin[1]-4, origin[2]-zScale); + gEngfuncs.pTriAPI->TexCoord2f (0, 1); + gEngfuncs.pTriAPI->Vertex3f (origin[0]+4, origin[1]-4,z); + gEngfuncs.pTriAPI->TexCoord2f (1, 1); + gEngfuncs.pTriAPI->Vertex3f (origin[0]-4, origin[1]+4,z); + gEngfuncs.pTriAPI->End (); + + // calculate screen position for name and infromation in hud::draw() + if ( gEngfuncs.pTriAPI->WorldToScreen(origin,screen) ) + continue; // object is behind viewer + + screen[0] = XPROJECT(screen[0]); + screen[1] = YPROJECT(screen[1]); + screen[2] = 0.0f; + + // calculate some offset under the icon + origin[0]+=32.0f; + origin[1]+=32.0f; + + gEngfuncs.pTriAPI->WorldToScreen(origin,offset); + + offset[0] = XPROJECT(offset[0]); + offset[1] = YPROJECT(offset[1]); + offset[2] = 0.0f; + + VectorSubtract(offset, screen, offset ); + + int playerNum = ent->index - 1; + + m_vPlayerPos[playerNum][0] = screen[0]; + m_vPlayerPos[playerNum][1] = screen[1] + Length(offset); + m_vPlayerPos[playerNum][2] = 1; // mark player as visible + } + + if ( !m_pip->value || !m_drawcone->value ) + return; + + // get current camera position and angle + + if ( m_pip->value == INSET_IN_EYE || g_iUser1 == OBS_IN_EYE ) + { + V_GetInEyePos( g_iUser2, origin, angles ); + } + else if ( m_pip->value == INSET_CHASE_FREE || g_iUser1 == OBS_CHASE_FREE ) + { + V_GetChasePos( g_iUser2, v_cl_angles, origin, angles ); + } + else if ( g_iUser1 == OBS_ROAMING ) + { + VectorCopy( v_sim_org, origin ); + VectorCopy( v_cl_angles, angles ); + } + else + V_GetChasePos( g_iUser2, NULL, origin, angles ); + + // draw camera sprite + + x = origin[0]; + y = origin[1]; + z = origin[2]; + + angles[0] = 0; // always show horizontal camera sprite + + hSpriteModel = (struct model_s *)gEngfuncs.GetSpritePointer( m_hsprCamera ); + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + gEngfuncs.pTriAPI->SpriteTexture( hSpriteModel, 0 ); + + gEngfuncs.pTriAPI->Color4f( r, g, b, 1.0 ); + + AngleVectors(angles, forward, NULL, NULL ); + VectorScale (forward, 512.0f, forward); + + offset[0] = 0.0f; + offset[1] = 45.0f; + offset[2] = 0.0f; + + AngleMatrix(offset, rmatrix ); + VectorTransform(forward, rmatrix , right ); + + offset[1]= -45.0f; + AngleMatrix(offset, rmatrix ); + VectorTransform(forward, rmatrix , left ); + + gEngfuncs.pTriAPI->Begin (TRI_TRIANGLES); + gEngfuncs.pTriAPI->TexCoord2f( 0, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x+right[0], y+right[1], (z+right[2]) * zScale); + + gEngfuncs.pTriAPI->TexCoord2f( 0, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x, y, z * zScale); + + gEngfuncs.pTriAPI->TexCoord2f( 1, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x+left[0], y+left[1], (z+left[2]) * zScale); + gEngfuncs.pTriAPI->End (); + +} + + +void CHudSpectator::DrawOverview() +{ + // draw only in sepctator mode + if ( !g_iUser1 ) + return; + + // Only draw the overview if Map Mode is selected for this view + if ( m_iDrawCycle == 0 && ( (g_iUser1 != OBS_MAP_FREE) && (g_iUser1 != OBS_MAP_CHASE) ) ) + return; + + if ( m_iDrawCycle == 1 && m_pip->value < INSET_MAP_FREE ) + return; + + DrawOverviewLayer(); + DrawOverviewEntities(); + CheckOverviewEntities(); +} +void CHudSpectator::CheckOverviewEntities() +{ + double time = gEngfuncs.GetClientTime(); + + // removes old entities from list + for ( int i = 0; i< MAX_OVERVIEW_ENTITIES; i++ ) + { + // remove entity from list if it is too old + if ( m_OverviewEntities[i].killTime < time ) + { + memset( &m_OverviewEntities[i], 0, sizeof (overviewEntity_t) ); + } + } +} + +bool CHudSpectator::AddOverviewEntity( int type, struct cl_entity_s *ent, const char *modelname) +{ + HSPRITE hSprite = 0; + double duration = -1.0f; // duration -1 means show it only this frame; + + if ( !ent ) + return false; + + if ( type == ET_PLAYER ) + { + if ( ent->curstate.solid != SOLID_NOT) + { + switch ( g_PlayerExtraInfo[ent->index].teamnumber ) + { + // blue and red teams are swapped in CS and TFC + case 1 : hSprite = m_hsprPlayerBlue; break; + case 2 : hSprite = m_hsprPlayerRed; break; + default : hSprite = m_hsprPlayer; break; + } + } + else + return false; // it's an spectator + } + else if (type == ET_NORMAL) + { + return false; + } + else + return false; + + return AddOverviewEntityToList(hSprite, ent, gEngfuncs.GetClientTime() + duration ); +} + +void CHudSpectator::DeathMessage(int victim) +{ + // find out where the victim is + cl_entity_t *pl = gEngfuncs.GetEntityByIndex(victim); + + if (pl && pl->player) + AddOverviewEntityToList(m_hsprPlayerDead, pl, gEngfuncs.GetClientTime() + 2.0f ); +} + +bool CHudSpectator::AddOverviewEntityToList(HSPRITE sprite, cl_entity_t *ent, double killTime) +{ + for ( int i = 0; i< MAX_OVERVIEW_ENTITIES; i++ ) + { + // find empty entity slot + if ( m_OverviewEntities[i].entity == NULL) + { + m_OverviewEntities[i].entity = ent; + m_OverviewEntities[i].hSprite = sprite; + m_OverviewEntities[i].killTime = killTime; + return true; + } + } + + return false; // maximum overview entities reached +} +void CHudSpectator::CheckSettings() +{ + // disallow same inset mode as main mode: + + if ( ( g_iUser1 < OBS_MAP_FREE ) && ( m_pip->value == INSET_CHASE_FREE || m_pip->value == INSET_IN_EYE ) ) + { + // otherwise both would show in World picures + m_pip->value = INSET_MAP_FREE; + } + + if ( ( g_iUser1 >= OBS_MAP_FREE ) && ( m_pip->value >= INSET_MAP_FREE ) ) + { + // both would show map views + m_pip->value = INSET_CHASE_FREE; + } + + // disble in intermission screen + if ( gHUD.m_iIntermission ) + m_pip->value = INSET_OFF; + + // check chat mode + if ( m_chatEnabled != (gHUD.m_SayText.m_HUD_saytext->value!=0) ) + { + // hud_saytext changed + m_chatEnabled = (gHUD.m_SayText.m_HUD_saytext->value!=0); + + if ( gEngfuncs.IsSpectateOnly() ) + { + // tell proxy our new chat mode + char chatcmd[32]; + sprintf(chatcmd, "ignoremsg %i", m_chatEnabled?0:1 ); + gEngfuncs.pfnServerCmd(chatcmd); + } + } + + + // draw small border around inset view, adjust upper black bar + gViewPort->m_pSpectatorPanel->EnableInsetView( m_pip->value != INSET_OFF ); +} + +int CHudSpectator::ToggleInset(bool allowOff) +{ + int newInsetMode = m_pip->value + 1; + + if ( g_iUser1 < OBS_MAP_FREE ) + { + if ( newInsetMode > INSET_MAP_CHASE ) + { + if (allowOff) + newInsetMode = INSET_OFF; + else + newInsetMode = INSET_MAP_FREE; + } + + if ( newInsetMode == INSET_CHASE_FREE ) + newInsetMode = INSET_MAP_FREE; + } + else + { + if ( newInsetMode > INSET_IN_EYE ) + { + if (allowOff) + newInsetMode = INSET_OFF; + else + newInsetMode = INSET_CHASE_FREE; + } + } + + return newInsetMode; +} +void CHudSpectator::Reset() +{ + // Reset HUD + if ( strcmp( m_OverviewData.map, gEngfuncs.pfnGetLevelName() ) ) + { + // update level overview if level changed + ParseOverviewFile(); + LoadMapSprites(); + SetSpectatorStartPosition(); + } + + memset( &m_OverviewEntities, 0, sizeof(m_OverviewEntities)); +} + +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)); + + if ( gEngfuncs.IsSpectateOnly() || gEngfuncs.pDemoAPI->IsPlayingback() ) + m_autoDirector->value = 1.0f; + else + m_autoDirector->value = 0.0f; + + SetModes( OBS_CHASE_FREE, INSET_OFF ); + + g_iUser2 = 0; // fake not target until first camera command + + // reset HUD FOV + gHUD.m_iFOV = CVAR_GET_FLOAT("default_fov"); + Reset(); + SetSpectatorStartPosition(); +} + diff --git a/hlsdk/dmc/cl_dll/hud_spectator.h b/hlsdk/dmc/cl_dll/hud_spectator.h new file mode 100644 index 00000000..ca57ea85 --- /dev/null +++ b/hlsdk/dmc/cl_dll/hud_spectator.h @@ -0,0 +1,129 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef SPECTATOR_H +#define SPECTATOR_H +#pragma once + +#include "cl_entity.h" + + + +#define INSET_OFF 0 +#define INSET_CHASE_FREE 1 +#define INSET_IN_EYE 2 +#define INSET_MAP_FREE 3 +#define INSET_MAP_CHASE 4 + +#define MAX_SPEC_HUD_MESSAGES 8 + + + +#define OVERVIEW_TILE_SIZE 128 // don't change this +#define OVERVIEW_MAX_LAYERS 1 + +//----------------------------------------------------------------------------- +// Purpose: Handles the drawing of the spectator stuff (camera & top-down map and all the things on it ) +//----------------------------------------------------------------------------- + +typedef struct overviewInfo_s { + char map[64]; // cl.levelname or empty + vec3_t origin; // center of map + float zoom; // zoom of map images + int layers; // how may layers do we have + float layersHeights[OVERVIEW_MAX_LAYERS]; + char layersImages[OVERVIEW_MAX_LAYERS][255]; + qboolean rotated; // are map images rotated (90 degrees) ? + + int insetWindowX; + int insetWindowY; + int insetWindowHeight; + int insetWindowWidth; +} overviewInfo_t; + +typedef struct overviewEntity_s { + + HSPRITE hSprite; + struct cl_entity_s * entity; + double killTime; +} overviewEntity_t; + +#define MAX_OVERVIEW_ENTITIES 128 + +class CHudSpectator : public CHudBase +{ +public: + void Reset(); + int ToggleInset(bool allowOff); + void CheckSettings(); + void InitHUDData( void ); + bool AddOverviewEntityToList( HSPRITE sprite, cl_entity_t * ent, double killTime); + void DeathMessage(int victim); + bool AddOverviewEntity( int type, struct cl_entity_s *ent, const char *modelname ); + void CheckOverviewEntities(); + void DrawOverview(); + void DrawOverviewEntities(); + void GetMapPosition( float * returnvec ); + void DrawOverviewLayer(); + void LoadMapSprites(); + bool ParseOverviewFile(); + bool IsActivePlayer(cl_entity_t * ent); + void SetModes(int iMainMode, int iInsetMode); + void HandleButtonsDown(int ButtonPressed); + void HandleButtonsUp(int ButtonPressed); + void FindNextPlayer( bool bReverse ); + void DirectorMessage( int iSize, void *pbuf ); + void SetSpectatorStartPosition(); + int Init(); + int VidInit(); + + int Draw(float flTime); + + int m_iDrawCycle; + client_textmessage_t m_HUDMessages[MAX_SPEC_HUD_MESSAGES]; + char m_HUDMessageText[MAX_SPEC_HUD_MESSAGES][128]; + int m_lastHudMessage; + overviewInfo_t m_OverviewData; + overviewEntity_t m_OverviewEntities[MAX_OVERVIEW_ENTITIES]; + int m_iObserverFlags; + int m_iSpectatorNumber; + + float m_mapZoom; // zoom the user currently uses + vec3_t m_mapOrigin; // origin where user rotates around + cvar_t * m_drawnames; + cvar_t * m_drawcone; + cvar_t * m_drawstatus; + cvar_t * m_autoDirector; + cvar_t * m_pip; + + + qboolean m_chatEnabled; + + vec3_t m_cameraOrigin; // a help camera + vec3_t m_cameraAngles; // and it's angles + + +private: + vec3_t m_vPlayerPos[MAX_PLAYERS]; + HSPRITE m_hsprPlayerBlue; + HSPRITE m_hsprPlayerRed; + HSPRITE m_hsprPlayer; + HSPRITE m_hsprCamera; + HSPRITE m_hsprPlayerDead; + HSPRITE m_hsprViewcone; + HSPRITE m_hsprUnkownMap; + HSPRITE m_hsprBeam; + HSPRITE m_hCrosshair; + struct model_s * m_MapSprite; // each layer image is saved in one sprite, where each tile is a sprite frame + float m_flNextObserverInput; + float m_zoomDelta; + float m_moveDelta; + int m_lastPrimaryObject; + int m_lastSecondaryObject; +}; + +#endif // SPECTATOR_H diff --git a/hlsdk/dmc/cl_dll/hud_update.cpp b/hlsdk/dmc/cl_dll/hud_update.cpp new file mode 100644 index 00000000..7f2b2b8b --- /dev/null +++ b/hlsdk/dmc/cl_dll/hud_update.cpp @@ -0,0 +1,56 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud_update.cpp +// + +#include +#include "hud.h" +#include "cl_util.h" +#include +#include + +int CL_ButtonBits( int ); +void CL_ResetButtonBits( int bits ); + +extern float v_idlescale; +float in_fov; +extern void HUD_SetCmdBits( int bits ); +int iCarriedWeapons; + +int CHud::UpdateClientData(client_data_t *cdata, float time) +{ + memcpy(m_vecOrigin, cdata->origin, sizeof(vec3_t)); + memcpy(m_vecAngles, cdata->viewangles, sizeof(vec3_t)); + + m_iKeyBits = CL_ButtonBits( 0 ); + m_iWeaponBits = cdata->iWeaponBits; + + in_fov = cdata->fov; + + Think(); + + cdata->fov = m_iFOV; + + v_idlescale = m_iConcussionEffect; + + CL_ResetButtonBits( m_iKeyBits ); + + // return 1 if in anything in the client_data struct has been changed, 0 otherwise + return 1; +} + + + diff --git a/hlsdk/dmc/cl_dll/in_camera.cpp b/hlsdk/dmc/cl_dll/in_camera.cpp new file mode 100644 index 00000000..2c44bf6f --- /dev/null +++ b/hlsdk/dmc/cl_dll/in_camera.cpp @@ -0,0 +1,618 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#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 "windows.h" + +float CL_KeyState (kbutton_t *key); + +extern "C" +{ + void DLLEXPORT CAM_Think( void ); + int DLLEXPORT CL_IsThirdPerson( void ); + void DLLEXPORT CL_CameraOffset( float *ofs ); +} + +extern cl_enginefunc_t gEngfuncs; + +//-------------------------------------------------- Constants + +#define CAM_DIST_DELTA 1.0 +#define CAM_ANGLE_DELTA 2.5 +#define CAM_ANGLE_SPEED 2.5 +#define CAM_MIN_DIST 30.0 +#define CAM_ANGLE_MOVE .5 +#define MAX_ANGLE_DIFF 10.0 +#define PITCH_MAX 90.0 +#define PITCH_MIN 0 +#define YAW_MAX 135.0 +#define YAW_MIN -135.0 + +enum ECAM_Command +{ + CAM_COMMAND_NONE = 0, + CAM_COMMAND_TOTHIRDPERSON = 1, + CAM_COMMAND_TOFIRSTPERSON = 2 +}; + +//-------------------------------------------------- Global Variables + +cvar_t *cam_command; +cvar_t *cam_snapto; +cvar_t *cam_idealyaw; +cvar_t *cam_idealpitch; +cvar_t *cam_idealdist; +cvar_t *cam_contain; + +cvar_t *c_maxpitch; +cvar_t *c_minpitch; +cvar_t *c_maxyaw; +cvar_t *c_minyaw; +cvar_t *c_maxdistance; +cvar_t *c_mindistance; + +// pitch, yaw, dist +vec3_t cam_ofs; + + +// In third person +int cam_thirdperson; +int cam_mousemove; //true if we are moving the cam with the mouse, False if not +int iMouseInUse=0; +int cam_distancemove; +extern int mouse_x, mouse_y; //used to determine what the current x and y values are +int cam_old_mouse_x, cam_old_mouse_y; //holds the last ticks mouse movement +POINT cam_mouse; +//-------------------------------------------------- Local Variables + +static kbutton_t cam_pitchup, cam_pitchdown, cam_yawleft, cam_yawright; +static kbutton_t cam_in, cam_out, cam_move; + +//-------------------------------------------------- Prototypes + +void CAM_ToThirdPerson(void); +void CAM_ToFirstPerson(void); +void CAM_StartDistance(void); +void CAM_EndDistance(void); + + +//-------------------------------------------------- Local Functions + +float MoveToward( float cur, float goal, float maxspeed ) +{ + if( cur != goal ) + { + if( abs( cur - goal ) > 180.0 ) + { + if( cur < goal ) + cur += 360.0; + else + cur -= 360.0; + } + + if( cur < goal ) + { + if( cur < goal - 1.0 ) + cur += ( goal - cur ) / 4.0; + else + cur = goal; + } + else + { + if( cur > goal + 1.0 ) + cur -= ( cur - goal ) / 4.0; + else + cur = goal; + } + } + + + // bring cur back into range + if( cur < 0 ) + cur += 360.0; + else if( cur >= 360 ) + cur -= 360; + + return cur; +} + + +//-------------------------------------------------- Gobal Functions + +typedef struct +{ + vec3_t boxmins, boxmaxs;// enclose the test object along entire move + float *mins, *maxs; // size of the moving object + vec3_t mins2, maxs2; // size when clipping against mosnters + float *start, *end; + trace_t trace; + int type; + edict_t *passedict; + qboolean monsterclip; +} moveclip_t; + +extern trace_t SV_ClipMoveToEntity (edict_t *ent, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end); + +void DLLEXPORT CAM_Think( void ) +{ + vec3_t origin; + vec3_t ext, pnt, camForward, camRight, camUp; + moveclip_t clip; + float dist; + vec3_t camAngles; + float flSensitivity; +#ifdef LATER + int i; +#endif + vec3_t viewangles; + + switch( (int) cam_command->value ) + { + case CAM_COMMAND_TOTHIRDPERSON: + CAM_ToThirdPerson(); + break; + + case CAM_COMMAND_TOFIRSTPERSON: + CAM_ToFirstPerson(); + break; + + case CAM_COMMAND_NONE: + default: + break; + } + + if( !cam_thirdperson ) + return; + +#ifdef LATER + if ( cam_contain->value ) + { + gEngfuncs.GetClientOrigin( origin ); + ext[0] = ext[1] = ext[2] = 0.0; + } +#endif + + camAngles[ PITCH ] = cam_idealpitch->value; + camAngles[ YAW ] = cam_idealyaw->value; + dist = cam_idealdist->value; + // + //movement of the camera with the mouse + // + if (cam_mousemove) + { + //get windows cursor position + GetCursorPos (&cam_mouse); + //check for X delta values and adjust accordingly + //eventually adjust YAW based on amount of movement + //don't do any movement of the cam using YAW/PITCH if we are zooming in/out the camera + if (!cam_distancemove) + { + + //keep the camera within certain limits around the player (ie avoid certain bad viewing angles) + if (cam_mouse.x>gEngfuncs.GetWindowCenterX()) + { + //if ((camAngles[YAW]>=225.0)||(camAngles[YAW]<135.0)) + if (camAngles[YAW]value) + { + camAngles[ YAW ] += (CAM_ANGLE_MOVE)*((cam_mouse.x-gEngfuncs.GetWindowCenterX())/2); + } + if (camAngles[YAW]>c_maxyaw->value) + { + + camAngles[YAW]=c_maxyaw->value; + } + } + else if (cam_mouse.x225.0)) + if (camAngles[YAW]>c_minyaw->value) + { + camAngles[ YAW ] -= (CAM_ANGLE_MOVE)* ((gEngfuncs.GetWindowCenterX()-cam_mouse.x)/2); + + } + if (camAngles[YAW]value) + { + camAngles[YAW]=c_minyaw->value; + + } + } + + //check for y delta values and adjust accordingly + //eventually adjust PITCH based on amount of movement + //also make sure camera is within bounds + if (cam_mouse.y>gEngfuncs.GetWindowCenterY()) + { + if(camAngles[PITCH]value) + { + camAngles[PITCH] +=(CAM_ANGLE_MOVE)* ((cam_mouse.y-gEngfuncs.GetWindowCenterY())/2); + } + if (camAngles[PITCH]>c_maxpitch->value) + { + camAngles[PITCH]=c_maxpitch->value; + } + } + else if (cam_mouse.yc_minpitch->value) + { + camAngles[PITCH] -= (CAM_ANGLE_MOVE)*((gEngfuncs.GetWindowCenterY()-cam_mouse.y)/2); + } + if (camAngles[PITCH]value) + { + camAngles[PITCH]=c_minpitch->value; + } + } + + //set old mouse coordinates to current mouse coordinates + //since we are done with the mouse + + if ( ( flSensitivity = gHUD.GetSensitivity() ) != 0 ) + { + cam_old_mouse_x=cam_mouse.x*flSensitivity; + cam_old_mouse_y=cam_mouse.y*flSensitivity; + } + else + { + cam_old_mouse_x=cam_mouse.x; + cam_old_mouse_y=cam_mouse.y; + } + SetCursorPos (gEngfuncs.GetWindowCenterX(), gEngfuncs.GetWindowCenterY()); + } + } + + //Nathan code here + if( CL_KeyState( &cam_pitchup ) ) + camAngles[ PITCH ] += CAM_ANGLE_DELTA; + else if( CL_KeyState( &cam_pitchdown ) ) + camAngles[ PITCH ] -= CAM_ANGLE_DELTA; + + if( CL_KeyState( &cam_yawleft ) ) + camAngles[ YAW ] -= CAM_ANGLE_DELTA; + else if( CL_KeyState( &cam_yawright ) ) + camAngles[ YAW ] += CAM_ANGLE_DELTA; + + if( CL_KeyState( &cam_in ) ) + { + dist -= CAM_DIST_DELTA; + if( dist < CAM_MIN_DIST ) + { + // If we go back into first person, reset the angle + camAngles[ PITCH ] = 0; + camAngles[ YAW ] = 0; + dist = CAM_MIN_DIST; + } + + } + else if( CL_KeyState( &cam_out ) ) + dist += CAM_DIST_DELTA; + + if (cam_distancemove) + { + if (cam_mouse.y>gEngfuncs.GetWindowCenterY()) + { + if(distvalue) + { + dist +=CAM_DIST_DELTA * ((cam_mouse.y-gEngfuncs.GetWindowCenterY())/2); + } + if (dist>c_maxdistance->value) + { + dist=c_maxdistance->value; + } + } + else if (cam_mouse.yc_mindistance->value) + { + dist -= (CAM_DIST_DELTA)*((gEngfuncs.GetWindowCenterY()-cam_mouse.y)/2); + } + if (distvalue) + { + dist=c_mindistance->value; + } + } + //set old mouse coordinates to current mouse coordinates + //since we are done with the mouse + cam_old_mouse_x=cam_mouse.x*gHUD.GetSensitivity(); + cam_old_mouse_y=cam_mouse.y*gHUD.GetSensitivity(); + SetCursorPos (gEngfuncs.GetWindowCenterX(), gEngfuncs.GetWindowCenterY()); + } +#ifdef LATER + if( cam_contain->value ) + { + // check new ideal + VectorCopy( origin, pnt ); + AngleVectors( camAngles, camForward, camRight, camUp ); + for (i=0 ; i<3 ; i++) + pnt[i] += -dist*camForward[i]; + + // check line from r_refdef.vieworg to pnt + memset ( &clip, 0, sizeof ( moveclip_t ) ); + clip.trace = SV_ClipMoveToEntity( sv.edicts, r_refdef.vieworg, ext, ext, pnt ); + if( clip.trace.fraction == 1.0 ) + { + // update ideal + cam_idealpitch->value = camAngles[ PITCH ]; + cam_idealyaw->value = camAngles[ YAW ]; + cam_idealdist->value = dist; + } + } + else +#endif + { + // update ideal + cam_idealpitch->value = camAngles[ PITCH ]; + cam_idealyaw->value = camAngles[ YAW ]; + cam_idealdist->value = dist; + } + + // Move towards ideal + VectorCopy( cam_ofs, camAngles ); + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + if( cam_snapto->value ) + { + camAngles[ YAW ] = cam_idealyaw->value + viewangles[ YAW ]; + camAngles[ PITCH ] = cam_idealpitch->value + viewangles[ PITCH ]; + camAngles[ 2 ] = cam_idealdist->value; + } + else + { + if( camAngles[ YAW ] - viewangles[ YAW ] != cam_idealyaw->value ) + camAngles[ YAW ] = MoveToward( camAngles[ YAW ], cam_idealyaw->value + viewangles[ YAW ], CAM_ANGLE_SPEED ); + + if( camAngles[ PITCH ] - viewangles[ PITCH ] != cam_idealpitch->value ) + camAngles[ PITCH ] = MoveToward( camAngles[ PITCH ], cam_idealpitch->value + viewangles[ PITCH ], CAM_ANGLE_SPEED ); + + if( abs( camAngles[ 2 ] - cam_idealdist->value ) < 2.0 ) + camAngles[ 2 ] = cam_idealdist->value; + else + camAngles[ 2 ] += ( cam_idealdist->value - camAngles[ 2 ] ) / 4.0; + } +#ifdef LATER + if( cam_contain->value ) + { + // Test new position + dist = camAngles[ ROLL ]; + camAngles[ ROLL ] = 0; + + VectorCopy( origin, pnt ); + AngleVectors( camAngles, camForward, camRight, camUp ); + for (i=0 ; i<3 ; i++) + pnt[i] += -dist*camForward[i]; + + // check line from r_refdef.vieworg to pnt + memset ( &clip, 0, sizeof ( moveclip_t ) ); + ext[0] = ext[1] = ext[2] = 0.0; + clip.trace = SV_ClipMoveToEntity( sv.edicts, r_refdef.vieworg, ext, ext, pnt ); + if( clip.trace.fraction != 1.0 ) + return; + } +#endif + cam_ofs[ 0 ] = camAngles[ 0 ]; + cam_ofs[ 1 ] = camAngles[ 1 ]; + cam_ofs[ 2 ] = dist; +} + +extern void KeyDown (kbutton_t *b); // HACK +extern void KeyUp (kbutton_t *b); // HACK + +void CAM_PitchUpDown(void) { KeyDown( &cam_pitchup ); } +void CAM_PitchUpUp(void) { KeyUp( &cam_pitchup ); } +void CAM_PitchDownDown(void) { KeyDown( &cam_pitchdown ); } +void CAM_PitchDownUp(void) { KeyUp( &cam_pitchdown ); } +void CAM_YawLeftDown(void) { KeyDown( &cam_yawleft ); } +void CAM_YawLeftUp(void) { KeyUp( &cam_yawleft ); } +void CAM_YawRightDown(void) { KeyDown( &cam_yawright ); } +void CAM_YawRightUp(void) { KeyUp( &cam_yawright ); } +void CAM_InDown(void) { KeyDown( &cam_in ); } +void CAM_InUp(void) { KeyUp( &cam_in ); } +void CAM_OutDown(void) { KeyDown( &cam_out ); } +void CAM_OutUp(void) { KeyUp( &cam_out ); } + +void CAM_ToThirdPerson(void) +{ + //Only allow cam_command if we are running a debug version of the client.dll +#ifndef _DEBUG + return; +#endif + + vec3_t viewangles; + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + if( !cam_thirdperson ) + { + cam_thirdperson = 1; + + cam_ofs[ YAW ] = viewangles[ YAW ]; + cam_ofs[ PITCH ] = viewangles[ PITCH ]; + cam_ofs[ 2 ] = CAM_MIN_DIST; + } + + gEngfuncs.Cvar_SetValue( "cam_command", 0 ); +} + +void CAM_ToFirstPerson(void) +{ + cam_thirdperson = 0; + + gEngfuncs.Cvar_SetValue( "cam_command", 0 ); +} + +void CAM_ToggleSnapto( void ) +{ + cam_snapto->value = !cam_snapto->value; +} + +void CAM_Init( void ) +{ + gEngfuncs.pfnAddCommand( "+campitchup", CAM_PitchUpDown ); + gEngfuncs.pfnAddCommand( "-campitchup", CAM_PitchUpUp ); + gEngfuncs.pfnAddCommand( "+campitchdown", CAM_PitchDownDown ); + gEngfuncs.pfnAddCommand( "-campitchdown", CAM_PitchDownUp ); + gEngfuncs.pfnAddCommand( "+camyawleft", CAM_YawLeftDown ); + gEngfuncs.pfnAddCommand( "-camyawleft", CAM_YawLeftUp ); + gEngfuncs.pfnAddCommand( "+camyawright", CAM_YawRightDown ); + gEngfuncs.pfnAddCommand( "-camyawright", CAM_YawRightUp ); + gEngfuncs.pfnAddCommand( "+camin", CAM_InDown ); + gEngfuncs.pfnAddCommand( "-camin", CAM_InUp ); + gEngfuncs.pfnAddCommand( "+camout", CAM_OutDown ); + gEngfuncs.pfnAddCommand( "-camout", CAM_OutUp ); + gEngfuncs.pfnAddCommand( "thirdperson", CAM_ToThirdPerson ); + gEngfuncs.pfnAddCommand( "firstperson", CAM_ToFirstPerson ); + gEngfuncs.pfnAddCommand( "+cammousemove",CAM_StartMouseMove); + gEngfuncs.pfnAddCommand( "-cammousemove",CAM_EndMouseMove); + gEngfuncs.pfnAddCommand( "+camdistance", CAM_StartDistance ); + gEngfuncs.pfnAddCommand( "-camdistance", CAM_EndDistance ); + gEngfuncs.pfnAddCommand( "snapto", CAM_ToggleSnapto ); + + cam_command = gEngfuncs.pfnRegisterVariable ( "cam_command", "0", 0 ); // tells camera to go to thirdperson + cam_snapto = gEngfuncs.pfnRegisterVariable ( "cam_snapto", "0", 0 ); // snap to thirdperson view + cam_idealyaw = gEngfuncs.pfnRegisterVariable ( "cam_idealyaw", "90", 0 ); // thirdperson yaw + cam_idealpitch = gEngfuncs.pfnRegisterVariable ( "cam_idealpitch", "0", 0 ); // thirperson pitch + cam_idealdist = gEngfuncs.pfnRegisterVariable ( "cam_idealdist", "64", 0 ); // thirdperson distance + cam_contain = gEngfuncs.pfnRegisterVariable ( "cam_contain", "0", 0 ); // contain camera to world + + c_maxpitch = gEngfuncs.pfnRegisterVariable ( "c_maxpitch", "90.0", 0 ); + c_minpitch = gEngfuncs.pfnRegisterVariable ( "c_minpitch", "0.0", 0 ); + c_maxyaw = gEngfuncs.pfnRegisterVariable ( "c_maxyaw", "135.0", 0 ); + c_minyaw = gEngfuncs.pfnRegisterVariable ( "c_minyaw", "-135.0", 0 ); + c_maxdistance = gEngfuncs.pfnRegisterVariable ( "c_maxdistance", "200.0", 0 ); + c_mindistance = gEngfuncs.pfnRegisterVariable ( "c_mindistance", "30.0", 0 ); +} + +void CAM_ClearStates( void ) +{ + vec3_t viewangles; + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + cam_pitchup.state = 0; + cam_pitchdown.state = 0; + cam_yawleft.state = 0; + cam_yawright.state = 0; + cam_in.state = 0; + cam_out.state = 0; + + cam_thirdperson = 0; + cam_command->value = 0; + cam_mousemove=0; + + cam_snapto->value = 0; + cam_distancemove = 0; + + cam_ofs[ 0 ] = 0.0; + cam_ofs[ 1 ] = 0.0; + cam_ofs[ 2 ] = CAM_MIN_DIST; + + cam_idealpitch->value = viewangles[ PITCH ]; + cam_idealyaw->value = viewangles[ YAW ]; + cam_idealdist->value = CAM_MIN_DIST; +} + +void CAM_StartMouseMove(void) +{ + float flSensitivity; + + //only move the cam with mouse if we are in third person. + if (cam_thirdperson) + { + //set appropriate flags and initialize the old mouse position + //variables for mouse camera movement + if (!cam_mousemove) + { + cam_mousemove=1; + iMouseInUse=1; + GetCursorPos (&cam_mouse); + + if ( ( flSensitivity = gHUD.GetSensitivity() ) != 0 ) + { + cam_old_mouse_x=cam_mouse.x*flSensitivity; + cam_old_mouse_y=cam_mouse.y*flSensitivity; + } + else + { + cam_old_mouse_x=cam_mouse.x; + cam_old_mouse_y=cam_mouse.y; + } + } + } + //we are not in 3rd person view..therefore do not allow camera movement + else + { + cam_mousemove=0; + iMouseInUse=0; + } +} + +//the key has been released for camera movement +//tell the engine that mouse camera movement is off +void CAM_EndMouseMove(void) +{ + cam_mousemove=0; + iMouseInUse=0; +} + + +//---------------------------------------------------------- +//routines to start the process of moving the cam in or out +//using the mouse +//---------------------------------------------------------- +void CAM_StartDistance(void) +{ + //only move the cam with mouse if we are in third person. + if (cam_thirdperson) + { + //set appropriate flags and initialize the old mouse position + //variables for mouse camera movement + if (!cam_distancemove) + { + cam_distancemove=1; + cam_mousemove=1; + iMouseInUse=1; + GetCursorPos (&cam_mouse); + cam_old_mouse_x=cam_mouse.x*gHUD.GetSensitivity(); + cam_old_mouse_y=cam_mouse.y*gHUD.GetSensitivity(); + } + } + //we are not in 3rd person view..therefore do not allow camera movement + else + { + cam_distancemove=0; + cam_mousemove=0; + iMouseInUse=0; + } +} + +//the key has been released for camera movement +//tell the engine that mouse camera movement is off +void CAM_EndDistance(void) +{ + cam_distancemove=0; + cam_mousemove=0; + iMouseInUse=0; +} + +int DLLEXPORT CL_IsThirdPerson( void ) +{ + return cam_thirdperson ? 1 : 0; +} + +void DLLEXPORT CL_CameraOffset( float *ofs ) +{ + VectorCopy( cam_ofs, ofs ); +} \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/in_defs.h b/hlsdk/dmc/cl_dll/in_defs.h new file mode 100644 index 00000000..19ae0045 --- /dev/null +++ b/hlsdk/dmc/cl_dll/in_defs.h @@ -0,0 +1,21 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( IN_DEFSH ) +#define IN_DEFSH +#pragma once + +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 + +#define DLLEXPORT __declspec( dllexport ) + +#endif \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/input.cpp b/hlsdk/dmc/cl_dll/input.cpp new file mode 100644 index 00000000..1922d3d1 --- /dev/null +++ b/hlsdk/dmc/cl_dll/input.cpp @@ -0,0 +1,1026 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// cl.input.c -- builds an intended movement command to send to the server + +//xxxxxx Move bob and pitch drifting code here and other stuff from view if needed + +// Quake is a trademark of Id Software, Inc., (c) 1996 Id Software, Inc. All +// rights reserved. +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +extern "C" +{ +#include "kbutton.h" +} +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "view.h" +#include +#include +#include "vgui_viewport.h" +#include "voice_status.h" + +extern "C" +{ + struct kbutton_s DLLEXPORT *KB_Find( const char *name ); + void DLLEXPORT CL_CreateMove ( float frametime, struct usercmd_s *cmd, int active ); + void DLLEXPORT HUD_Shutdown( void ); + int DLLEXPORT HUD_Key_Event( int eventcode, int keynum, const char *pszCurrentBinding ); +} + +extern int g_weaponselect; +extern cl_enginefunc_t gEngfuncs; + +// Defined in pm_math.c +extern "C" float anglemod( float a ); + +void IN_Init (void); +void IN_Move ( float frametime, usercmd_t *cmd); +void IN_Shutdown( void ); +void V_Init( void ); +int CL_ButtonBits( int ); + +// xxx need client dll function to get and clear impuse +extern cvar_t *in_joystick; + +int in_impulse = 0; +int in_cancel = 0; + +cvar_t *m_pitch; +cvar_t *m_yaw; +cvar_t *m_forward; +cvar_t *m_side; + +cvar_t *lookstrafe; +cvar_t *lookspring; +cvar_t *cl_pitchup; +cvar_t *cl_pitchdown; +cvar_t *cl_upspeed; +cvar_t *cl_forwardspeed; +cvar_t *cl_backspeed; +cvar_t *cl_sidespeed; +cvar_t *cl_movespeedkey; +cvar_t *cl_yawspeed; +cvar_t *cl_pitchspeed; +cvar_t *cl_anglespeedkey; +cvar_t *cl_vsmoothing; +/* +=============================================================================== + +KEY BUTTONS + +Continuous button event tracking is complicated by the fact that two different +input sources (say, mouse button 1 and the control key) can both press the +same button, but the button should only be released when both of the +pressing key have been released. + +When a key event issues a button command (+forward, +attack, etc), it appends +its key number as a parameter to the command so it can be matched up with +the release. + +state bit 0 is the current state of the key +state bit 1 is edge triggered on the up to down transition +state bit 2 is edge triggered on the down to up transition + +=============================================================================== +*/ + + +kbutton_t in_mlook; +kbutton_t in_klook; +kbutton_t in_jlook; +kbutton_t in_left; +kbutton_t in_right; +kbutton_t in_forward; +kbutton_t in_back; +kbutton_t in_lookup; +kbutton_t in_lookdown; +kbutton_t in_moveleft; +kbutton_t in_moveright; +kbutton_t in_strafe; +kbutton_t in_speed; +kbutton_t in_use; +kbutton_t in_jump; +kbutton_t in_attack; +kbutton_t in_attack2; +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 + +typedef struct kblist_s +{ + struct kblist_s *next; + kbutton_t *pkey; + char name[32]; +} kblist_t; + +kblist_t *g_kbkeys = NULL; + +/* +============ +KB_ConvertString + +Removes references to +use and replaces them with the keyname in the output string. If + a binding is unfound, then the original text is retained. +NOTE: Only works for text with +word in it. +============ +*/ +int KB_ConvertString( char *in, char **ppout ) +{ + char sz[ 4096 ]; + char binding[ 64 ]; + char *p; + char *pOut; + char *pEnd; + const char *pBinding; + + if ( !ppout ) + return 0; + + *ppout = NULL; + p = in; + pOut = sz; + while ( *p ) + { + if ( *p == '+' ) + { + pEnd = binding; + while ( *p && ( isalnum( *p ) || ( pEnd == binding ) ) && ( ( pEnd - binding ) < 63 ) ) + { + *pEnd++ = *p++; + } + + *pEnd = '\0'; + + pBinding = NULL; + if ( strlen( binding + 1 ) > 0 ) + { + // See if there is a binding for binding? + pBinding = gEngfuncs.Key_LookupBinding( binding + 1 ); + } + + if ( pBinding ) + { + *pOut++ = '['; + pEnd = (char *)pBinding; + } + else + { + pEnd = binding; + } + + while ( *pEnd ) + { + *pOut++ = *pEnd++; + } + + if ( pBinding ) + { + *pOut++ = ']'; + } + } + else + { + *pOut++ = *p++; + } + } + + *pOut = '\0'; + + pOut = ( char * )malloc( strlen( sz ) + 1 ); + strcpy( pOut, sz ); + *ppout = pOut; + + return 1; +} + +/* +============ +KB_Find + +Allows the engine to get a kbutton_t directly ( so it can check +mlook state, etc ) for saving out to .cfg files +============ +*/ +struct kbutton_s DLLEXPORT *KB_Find( const char *name ) +{ + kblist_t *p; + p = g_kbkeys; + while ( p ) + { + if ( !stricmp( name, p->name ) ) + return p->pkey; + + p = p->next; + } + return NULL; +} + +/* +============ +KB_Add + +Add a kbutton_t * to the list of pointers the engine can retrieve via KB_Find +============ +*/ +void KB_Add( const char *name, kbutton_t *pkb ) +{ + kblist_t *p; + kbutton_t *kb; + + kb = KB_Find( name ); + + if ( kb ) + return; + + p = ( kblist_t * )malloc( sizeof( kblist_t ) ); + memset( p, 0, sizeof( *p ) ); + + strcpy( p->name, name ); + p->pkey = pkb; + + p->next = g_kbkeys; + g_kbkeys = p; +} + +/* +============ +KB_Init + +Add kbutton_t definitions that the engine can query if needed +============ +*/ +void KB_Init( void ) +{ + g_kbkeys = NULL; + + KB_Add( "in_graph", &in_graph ); + KB_Add( "in_mlook", &in_mlook ); + KB_Add( "in_jlook", &in_jlook ); +} + +/* +============ +KB_Shutdown + +Clear kblist +============ +*/ +void KB_Shutdown( void ) +{ + kblist_t *p, *n; + p = g_kbkeys; + while ( p ) + { + n = p->next; + free( p ); + p = n; + } + g_kbkeys = NULL; +} + +/* +============ +KeyDown +============ +*/ +void KeyDown (kbutton_t *b) +{ + int k; + char *c; + + c = gEngfuncs.Cmd_Argv(1); + if (c[0]) + k = atoi(c); + else + k = -1; // typed manually at the console for continuous down + + if (k == b->down[0] || k == b->down[1]) + return; // repeating key + + if (!b->down[0]) + b->down[0] = k; + else if (!b->down[1]) + b->down[1] = k; + else + { + gEngfuncs.Con_DPrintf ("Three keys down for a button '%c' '%c' '%c'!\n", b->down[0], b->down[1], c); + return; + } + + if (b->state & 1) + return; // still down + b->state |= 1 + 2; // down + impulse down +} + +/* +============ +KeyUp +============ +*/ +void KeyUp (kbutton_t *b) +{ + int k; + char *c; + + c = gEngfuncs.Cmd_Argv(1); + if (c[0]) + k = atoi(c); + else + { // typed manually at the console, assume for unsticking, so clear all + b->down[0] = b->down[1] = 0; + b->state = 4; // impulse up + return; + } + + if (b->down[0] == k) + b->down[0] = 0; + else if (b->down[1] == k) + b->down[1] = 0; + else + return; // key up without coresponding down (menu pass through) + if (b->down[0] || b->down[1]) + { + //Con_Printf ("Keys down for button: '%c' '%c' '%c' (%d,%d,%d)!\n", b->down[0], b->down[1], c, b->down[0], b->down[1], c); + return; // some other key is still holding it down + } + + if (!(b->state & 1)) + return; // still up (this should not happen) + + b->state &= ~1; // now up + b->state |= 4; // impulse up +} + +/* +============ +HUD_Key_Event + +Return 1 to allow engine to process the key, otherwise, act on it as needed +============ +*/ +int DLLEXPORT HUD_Key_Event( int down, int keynum, const char *pszCurrentBinding ) +{ + /* + if ( keynum == K_ESCAPE ) + { + // Trap escape key and do something + return 0; + } + */ + return 1; +} + +void IN_BreakDown( void ) { KeyDown( &in_break );}; +void IN_BreakUp( void ) { KeyUp( &in_break ); }; +void IN_KLookDown (void) {KeyDown(&in_klook);} +void IN_KLookUp (void) {KeyUp(&in_klook);} +void IN_JLookDown (void) {KeyDown(&in_jlook);} +void IN_JLookUp (void) {KeyUp(&in_jlook);} +void IN_MLookDown (void) {KeyDown(&in_mlook);} +void IN_UpDown(void) {KeyDown(&in_up);} +void IN_UpUp(void) {KeyUp(&in_up);} +void IN_DownDown(void) {KeyDown(&in_down);} +void IN_DownUp(void) {KeyUp(&in_down);} +void IN_LeftDown(void) {KeyDown(&in_left);} +void IN_LeftUp(void) {KeyUp(&in_left);} +void IN_RightDown(void) {KeyDown(&in_right);} +void IN_RightUp(void) {KeyUp(&in_right);} + +void IN_ForwardDown(void) +{ + KeyDown(&in_forward); + gHUD.m_Spectator.HandleButtonsDown( IN_FORWARD ); +} + +void IN_ForwardUp(void) +{ + KeyUp(&in_forward); + gHUD.m_Spectator.HandleButtonsUp( IN_FORWARD ); +} + +void IN_BackDown(void) +{ + KeyDown(&in_back); + gHUD.m_Spectator.HandleButtonsDown( IN_BACK ); +} + +void IN_BackUp(void) +{ + KeyUp(&in_back); + gHUD.m_Spectator.HandleButtonsUp( IN_BACK ); +} +void IN_LookupDown(void) {KeyDown(&in_lookup);} +void IN_LookupUp(void) {KeyUp(&in_lookup);} +void IN_LookdownDown(void) {KeyDown(&in_lookdown);} +void IN_LookdownUp(void) {KeyUp(&in_lookdown);} +void IN_MoveleftDown(void) +{ + KeyDown(&in_moveleft); + gHUD.m_Spectator.HandleButtonsDown( IN_MOVELEFT ); +} + +void IN_MoveleftUp(void) +{ + KeyUp(&in_moveleft); + gHUD.m_Spectator.HandleButtonsUp( IN_MOVELEFT ); +} + +void IN_MoverightDown(void) +{ + KeyDown(&in_moveright); + gHUD.m_Spectator.HandleButtonsDown( IN_MOVERIGHT ); +} + +void IN_MoverightUp(void) +{ + KeyUp(&in_moveright); + gHUD.m_Spectator.HandleButtonsUp( IN_MOVERIGHT ); +} +void IN_SpeedDown(void) {KeyDown(&in_speed);} +void IN_SpeedUp(void) {KeyUp(&in_speed);} +void IN_StrafeDown(void) {KeyDown(&in_strafe);} +void IN_StrafeUp(void) {KeyUp(&in_strafe);} + +void IN_Attack2Down(void) +{ + KeyDown(&in_attack2); + gHUD.m_Spectator.HandleButtonsDown( IN_ATTACK2 ); +} + +void IN_Attack2Up(void) {KeyUp(&in_attack2);} +void IN_UseDown (void) +{ + KeyDown(&in_use); + gHUD.m_Spectator.HandleButtonsDown( IN_USE ); +} +void IN_UseUp (void) {KeyUp(&in_use);} + +void IN_JumpDown (void) +{ + KeyDown(&in_jump); + gHUD.m_Spectator.HandleButtonsDown( IN_JUMP ); +} +void IN_JumpUp (void) {KeyUp(&in_jump);} + +void IN_DuckDown(void) +{ + KeyDown(&in_duck); + gHUD.m_Spectator.HandleButtonsDown( IN_DUCK ); +} +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);} + +void IN_AttackDown(void) +{ + KeyDown( &in_attack ); + gHUD.m_Spectator.HandleButtonsDown( IN_ATTACK ); +} + +void IN_AttackUp(void) +{ + KeyUp( &in_attack ); + in_cancel = 0; +} + +// Special handling +void IN_Cancel(void) +{ + in_cancel = 1; +} + +void IN_Impulse (void) +{ + in_impulse = atoi( gEngfuncs.Cmd_Argv(1) ); +} + +void IN_ScoreDown(void) +{ + KeyDown(&in_score); + + if ( gViewPort ) + { + gViewPort->ShowScoreBoard(); + } +} + +void IN_ScoreUp(void) +{ + KeyUp(&in_score); + + if ( gViewPort ) + { + gViewPort->HideScoreBoard(); + } +} + +void IN_MLookUp (void) +{ + KeyUp( &in_mlook ); + if ( !( in_mlook.state & 1 ) && lookspring->value ) + { + V_StartPitchDrift(); + } +} + +/* +=============== +CL_KeyState + +Returns 0.25 if a key was pressed and released during the frame, +0.5 if it was pressed and held +0 if held then released, and +1.0 if held for the entire time +=============== +*/ +float CL_KeyState (kbutton_t *key) +{ + float val = 0.0; + int impulsedown, impulseup, down; + + impulsedown = key->state & 2; + impulseup = key->state & 4; + down = key->state & 1; + + if ( impulsedown && !impulseup ) + { + // pressed and held this frame? + val = down ? 0.5 : 0.0; + } + + if ( impulseup && !impulsedown ) + { + // released this frame? + val = down ? 0.0 : 0.0; + } + + if ( !impulsedown && !impulseup ) + { + // held the entire frame? + val = down ? 1.0 : 0.0; + } + + if ( impulsedown && impulseup ) + { + if ( down ) + { + // released and re-pressed this frame + val = 0.75; + } + else + { + // pressed and released this frame + val = 0.25; + } + } + + // clear impulses + key->state &= 1; + return val; +} + +/* +================ +CL_AdjustAngles + +Moves the local angle positions +================ +*/ +void CL_AdjustAngles ( float frametime, float *viewangles ) +{ + float speed; + float up, down; + + if (in_speed.state & 1) + { + speed = frametime * cl_anglespeedkey->value; + } + else + { + speed = frametime; + } + + if (!(in_strafe.state & 1)) + { + viewangles[YAW] -= speed*cl_yawspeed->value*CL_KeyState (&in_right); + viewangles[YAW] += speed*cl_yawspeed->value*CL_KeyState (&in_left); + viewangles[YAW] = anglemod(viewangles[YAW]); + } + if (in_klook.state & 1) + { + V_StopPitchDrift (); + viewangles[PITCH] -= speed*cl_pitchspeed->value * CL_KeyState (&in_forward); + viewangles[PITCH] += speed*cl_pitchspeed->value * CL_KeyState (&in_back); + } + + up = CL_KeyState (&in_lookup); + down = CL_KeyState(&in_lookdown); + + viewangles[PITCH] -= speed*cl_pitchspeed->value * up; + viewangles[PITCH] += speed*cl_pitchspeed->value * down; + + if (up || down) + V_StopPitchDrift (); + + if (viewangles[PITCH] > cl_pitchdown->value) + viewangles[PITCH] = cl_pitchdown->value; + if (viewangles[PITCH] < -cl_pitchup->value) + viewangles[PITCH] = -cl_pitchup->value; + + if (viewangles[ROLL] > 50) + viewangles[ROLL] = 50; + if (viewangles[ROLL] < -50) + viewangles[ROLL] = -50; +} + +/* +================ +CL_CreateMove + +Send the intended movement message to the server +if active == 1 then we are 1) not playing back demos ( where our commands are ignored ) and +2 ) we have finished signing on to server +================ +*/ +void DLLEXPORT CL_CreateMove ( float frametime, struct usercmd_s *cmd, int active ) +{ + float spd; + vec3_t viewangles; + static vec3_t oldangles; + + if ( active ) + { + //memset( viewangles, 0, sizeof( vec3_t ) ); + //viewangles[ 0 ] = viewangles[ 1 ] = viewangles[ 2 ] = 0.0; + gEngfuncs.GetViewAngles( (float *)viewangles ); + + CL_AdjustAngles ( frametime, viewangles ); + + memset (cmd, 0, sizeof(*cmd)); + + gEngfuncs.SetViewAngles( (float *)viewangles ); + + if ( in_strafe.state & 1 ) + { + cmd->sidemove += cl_sidespeed->value * CL_KeyState (&in_right); + cmd->sidemove -= cl_sidespeed->value * CL_KeyState (&in_left); + } + + cmd->sidemove += cl_sidespeed->value * CL_KeyState (&in_moveright); + cmd->sidemove -= cl_sidespeed->value * CL_KeyState (&in_moveleft); + + cmd->upmove += cl_upspeed->value * CL_KeyState (&in_up); + cmd->upmove -= cl_upspeed->value * CL_KeyState (&in_down); + + if ( !(in_klook.state & 1 ) ) + { + cmd->forwardmove += cl_forwardspeed->value * CL_KeyState (&in_forward); + cmd->forwardmove -= cl_backspeed->value * CL_KeyState (&in_back); + } + + // adjust for speed key + if ( in_speed.state & 1 ) + { + cmd->forwardmove *= cl_movespeedkey->value; + cmd->sidemove *= cl_movespeedkey->value; + cmd->upmove *= cl_movespeedkey->value; + } + + // clip to maxspeed + spd = gEngfuncs.GetClientMaxspeed(); + if ( spd != 0.0 ) + { + // scale the 3 speeds so that the total velocity is not > cl.maxspeed + float fmov = sqrt( (cmd->forwardmove*cmd->forwardmove) + (cmd->sidemove*cmd->sidemove) + (cmd->upmove*cmd->upmove) ); + + if ( fmov > spd ) + { + float fratio = spd / fmov; + cmd->forwardmove *= fratio; + cmd->sidemove *= fratio; + cmd->upmove *= fratio; + } + } + + // Allow mice and other controllers to add their inputs + IN_Move ( frametime, cmd ); + } + + cmd->impulse = in_impulse; + in_impulse = 0; + + cmd->weaponselect = g_weaponselect; + g_weaponselect = 0; + + // + // set button and flag bits + // + cmd->buttons = CL_ButtonBits( 1 ); + + // If they're in a modal dialog, ignore the attack button. + if(GetClientVoiceMgr()->IsInSquelchMode()) + cmd->buttons &= ~IN_ATTACK; + + // Using joystick? + if ( in_joystick->value ) + { + if ( cmd->forwardmove > 0 ) + { + cmd->buttons |= IN_FORWARD; + } + else if ( cmd->forwardmove < 0 ) + { + cmd->buttons |= IN_BACK; + } + } + + gEngfuncs.GetViewAngles( (float *)viewangles ); + // Set current view angles. + + if ( gHUD.m_Health.m_iHealth > 0 ) + { + VectorCopy( viewangles, cmd->viewangles ); + VectorCopy( viewangles, oldangles ); + } + else + { + VectorCopy( oldangles, cmd->viewangles ); + } +} + +/* +============ +CL_IsDead + +Returns 1 if health is <= 0 +============ +*/ +int CL_IsDead( void ) +{ + return ( gHUD.m_Health.m_iHealth <= 0 ) ? 1 : 0; +} + +/* +============ +CL_ButtonBits + +Returns appropriate button info for keyboard and mouse state +Set bResetState to 1 to clear old state info +============ +*/ +int CL_ButtonBits( int bResetState ) +{ + int bits = 0; + + if ( in_attack.state & 3 ) + { + bits |= IN_ATTACK; + } + + if (in_duck.state & 3) + { + bits |= IN_DUCK; + } + + if (in_jump.state & 3) + { + bits |= IN_JUMP; + } + + if ( in_forward.state & 3 ) + { + bits |= IN_FORWARD; + } + + if (in_back.state & 3) + { + bits |= IN_BACK; + } + + if (in_use.state & 3) + { + bits |= IN_USE; + } + + if (in_cancel) + { + bits |= IN_CANCEL; + } + + if ( in_left.state & 3 ) + { + bits |= IN_LEFT; + } + + if (in_right.state & 3) + { + bits |= IN_RIGHT; + } + + if ( in_moveleft.state & 3 ) + { + bits |= IN_MOVELEFT; + } + + if (in_moveright.state & 3) + { + bits |= IN_MOVERIGHT; + } + + if (in_attack2.state & 3) + { + bits |= IN_ATTACK2; + } + + if (in_reload.state & 3) + { + bits |= IN_RELOAD; + } + + if (in_alt1.state & 3) + { + bits |= IN_ALT1; + } + + if ( in_score.state & 3 ) + { + bits |= IN_SCORE; + } + + // Dead or in intermission? Shore scoreboard, too + if ( CL_IsDead() || gHUD.m_iIntermission ) + { + bits |= IN_SCORE; + } + + if ( bResetState ) + { + in_attack.state &= ~2; + in_duck.state &= ~2; + in_jump.state &= ~2; + in_forward.state &= ~2; + in_back.state &= ~2; + in_use.state &= ~2; + in_left.state &= ~2; + in_right.state &= ~2; + in_moveleft.state &= ~2; + in_moveright.state &= ~2; + in_attack2.state &= ~2; + in_reload.state &= ~2; + in_alt1.state &= ~2; + in_score.state &= ~2; + } + + return bits; +} + +/* +============ +CL_ResetButtonBits + +============ +*/ +void CL_ResetButtonBits( int bits ) +{ + int bitsNew = CL_ButtonBits( 0 ) ^ bits; + + // Has the attack button been changed + if ( bitsNew & IN_ATTACK ) + { + // Was it pressed? or let go? + if ( bits & IN_ATTACK ) + { + KeyDown( &in_attack ); + } + else + { + // totally clear state + in_attack.state &= ~7; + } + } +} + +/* +============ +InitInput +============ +*/ +void InitInput (void) +{ + gEngfuncs.pfnAddCommand ("+moveup",IN_UpDown); + gEngfuncs.pfnAddCommand ("-moveup",IN_UpUp); + gEngfuncs.pfnAddCommand ("+movedown",IN_DownDown); + gEngfuncs.pfnAddCommand ("-movedown",IN_DownUp); + gEngfuncs.pfnAddCommand ("+left",IN_LeftDown); + gEngfuncs.pfnAddCommand ("-left",IN_LeftUp); + gEngfuncs.pfnAddCommand ("+right",IN_RightDown); + gEngfuncs.pfnAddCommand ("-right",IN_RightUp); + gEngfuncs.pfnAddCommand ("+forward",IN_ForwardDown); + gEngfuncs.pfnAddCommand ("-forward",IN_ForwardUp); + gEngfuncs.pfnAddCommand ("+back",IN_BackDown); + gEngfuncs.pfnAddCommand ("-back",IN_BackUp); + gEngfuncs.pfnAddCommand ("+lookup", IN_LookupDown); + gEngfuncs.pfnAddCommand ("-lookup", IN_LookupUp); + gEngfuncs.pfnAddCommand ("+lookdown", IN_LookdownDown); + gEngfuncs.pfnAddCommand ("-lookdown", IN_LookdownUp); + gEngfuncs.pfnAddCommand ("+strafe", IN_StrafeDown); + gEngfuncs.pfnAddCommand ("-strafe", IN_StrafeUp); + gEngfuncs.pfnAddCommand ("+moveleft", IN_MoveleftDown); + gEngfuncs.pfnAddCommand ("-moveleft", IN_MoveleftUp); + gEngfuncs.pfnAddCommand ("+moveright", IN_MoverightDown); + gEngfuncs.pfnAddCommand ("-moveright", IN_MoverightUp); + gEngfuncs.pfnAddCommand ("+speed", IN_SpeedDown); + gEngfuncs.pfnAddCommand ("-speed", IN_SpeedUp); + gEngfuncs.pfnAddCommand ("+attack", IN_AttackDown); + gEngfuncs.pfnAddCommand ("-attack", IN_AttackUp); + gEngfuncs.pfnAddCommand ("+attack2", IN_Attack2Down); + gEngfuncs.pfnAddCommand ("-attack2", IN_Attack2Up); + gEngfuncs.pfnAddCommand ("+use", IN_UseDown); + gEngfuncs.pfnAddCommand ("-use", IN_UseUp); + gEngfuncs.pfnAddCommand ("+jump", IN_JumpDown); + gEngfuncs.pfnAddCommand ("-jump", IN_JumpUp); + gEngfuncs.pfnAddCommand ("impulse", IN_Impulse); + gEngfuncs.pfnAddCommand ("+klook", IN_KLookDown); + gEngfuncs.pfnAddCommand ("-klook", IN_KLookUp); + gEngfuncs.pfnAddCommand ("+mlook", IN_MLookDown); + gEngfuncs.pfnAddCommand ("-mlook", IN_MLookUp); + gEngfuncs.pfnAddCommand ("+jlook", IN_JLookDown); + gEngfuncs.pfnAddCommand ("-jlook", IN_JLookUp); + gEngfuncs.pfnAddCommand ("+duck", IN_DuckDown); + 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); + gEngfuncs.pfnAddCommand ("-showscores", IN_ScoreUp); + gEngfuncs.pfnAddCommand ("+graph", IN_GraphDown); + gEngfuncs.pfnAddCommand ("-graph", IN_GraphUp); + gEngfuncs.pfnAddCommand ("+break",IN_BreakDown); + gEngfuncs.pfnAddCommand ("-break",IN_BreakUp); + + lookstrafe = gEngfuncs.pfnRegisterVariable ( "lookstrafe", "0", FCVAR_ARCHIVE ); + lookspring = gEngfuncs.pfnRegisterVariable ( "lookspring", "0", FCVAR_ARCHIVE ); + cl_anglespeedkey = gEngfuncs.pfnRegisterVariable ( "cl_anglespeedkey", "0.67", 0 ); + cl_yawspeed = gEngfuncs.pfnRegisterVariable ( "cl_yawspeed", "210", 0 ); + cl_pitchspeed = gEngfuncs.pfnRegisterVariable ( "cl_pitchspeed", "225", 0 ); + cl_upspeed = gEngfuncs.pfnRegisterVariable ( "cl_upspeed", "320", 0 ); + cl_forwardspeed = gEngfuncs.pfnRegisterVariable ( "cl_forwardspeed", "400", FCVAR_ARCHIVE ); + cl_backspeed = gEngfuncs.pfnRegisterVariable ( "cl_backspeed", "400", FCVAR_ARCHIVE ); + cl_sidespeed = gEngfuncs.pfnRegisterVariable ( "cl_sidespeed", "400", 0 ); + 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 ); + m_yaw = gEngfuncs.pfnRegisterVariable ( "m_yaw","0.022", FCVAR_ARCHIVE ); + m_forward = gEngfuncs.pfnRegisterVariable ( "m_forward","1", FCVAR_ARCHIVE ); + m_side = gEngfuncs.pfnRegisterVariable ( "m_side","0.8", FCVAR_ARCHIVE ); + + // Initialize third person camera controls. + CAM_Init(); + // Initialize inputs + IN_Init(); + // Initialize keyboard + KB_Init(); + // Initialize view system + V_Init(); +} + +/* +============ +ShutdownInput +============ +*/ +void ShutdownInput (void) +{ + IN_Shutdown(); + KB_Shutdown(); +} + +void DLLEXPORT HUD_Shutdown( void ) +{ + ShutdownInput(); +} diff --git a/hlsdk/dmc/cl_dll/inputw32.cpp b/hlsdk/dmc/cl_dll/inputw32.cpp new file mode 100644 index 00000000..b4b6ddd6 --- /dev/null +++ b/hlsdk/dmc/cl_dll/inputw32.cpp @@ -0,0 +1,947 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// in_win.c -- windows 95 mouse and joystick code +// 02/21/97 JCB Added extended DirectInput code to support external controllers. + +#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 "../engine/keydefs.h" +#include "view.h" +#include "windows.h" + +#define MOUSE_BUTTON_COUNT 5 + +// Set this to 1 to show mouse cursor. Experimental +int g_iVisibleMouse = 0; + +extern "C" +{ + void DLLEXPORT IN_ActivateMouse( void ); + void DLLEXPORT IN_DeactivateMouse( void ); + void DLLEXPORT IN_MouseEvent (int mstate); + void DLLEXPORT IN_Accumulate (void); + void DLLEXPORT IN_ClearStates (void); +} + +extern cl_enginefunc_t gEngfuncs; + +extern int iMouseInUse; + +extern kbutton_t in_strafe; +extern kbutton_t in_mlook; +extern kbutton_t in_speed; +extern kbutton_t in_jlook; + +extern cvar_t *m_pitch; +extern cvar_t *m_yaw; +extern cvar_t *m_forward; +extern cvar_t *m_side; + +extern cvar_t *lookstrafe; +extern cvar_t *lookspring; +extern cvar_t *cl_pitchdown; +extern cvar_t *cl_pitchup; +extern cvar_t *cl_yawspeed; +extern cvar_t *cl_sidespeed; +extern cvar_t *cl_forwardspeed; +extern cvar_t *cl_pitchspeed; +extern cvar_t *cl_movespeedkey; + +// mouse variables +cvar_t *m_filter; +cvar_t *sensitivity; + +int mouse_buttons; +int mouse_oldbuttonstate; +POINT current_pos; +int mouse_x, mouse_y, old_mouse_x, old_mouse_y, mx_accum, my_accum; + +static int restore_spi; +static int originalmouseparms[3], newmouseparms[3] = {0, 0, 1}; +static int mouseactive; +int mouseinitialized; +static int mouseparmsvalid; +static int mouseshowtoggle = 1; + +// joystick defines and variables +// where should defines be moved? +#define JOY_ABSOLUTE_AXIS 0x00000000 // control like a joystick +#define JOY_RELATIVE_AXIS 0x00000010 // control like a mouse, spinner, trackball +#define JOY_MAX_AXES 6 // X, Y, Z, R, U, V +#define JOY_AXIS_X 0 +#define JOY_AXIS_Y 1 +#define JOY_AXIS_Z 2 +#define JOY_AXIS_R 3 +#define JOY_AXIS_U 4 +#define JOY_AXIS_V 5 + +enum _ControlList +{ + AxisNada = 0, + AxisForward, + AxisLook, + AxisSide, + AxisTurn +}; + +DWORD dwAxisFlags[JOY_MAX_AXES] = +{ + JOY_RETURNX, + JOY_RETURNY, + JOY_RETURNZ, + JOY_RETURNR, + JOY_RETURNU, + JOY_RETURNV +}; + +DWORD dwAxisMap[ JOY_MAX_AXES ]; +DWORD dwControlMap[ JOY_MAX_AXES ]; +PDWORD pdwRawValue[ JOY_MAX_AXES ]; + +// none of these cvars are saved over a session +// this means that advanced controller configuration needs to be executed +// each time. this avoids any problems with getting back to a default usage +// or when changing from one controller to another. this way at least something +// works. +cvar_t *in_joystick; +cvar_t *joy_name; +cvar_t *joy_advanced; +cvar_t *joy_advaxisx; +cvar_t *joy_advaxisy; +cvar_t *joy_advaxisz; +cvar_t *joy_advaxisr; +cvar_t *joy_advaxisu; +cvar_t *joy_advaxisv; +cvar_t *joy_forwardthreshold; +cvar_t *joy_sidethreshold; +cvar_t *joy_pitchthreshold; +cvar_t *joy_yawthreshold; +cvar_t *joy_forwardsensitivity; +cvar_t *joy_sidesensitivity; +cvar_t *joy_pitchsensitivity; +cvar_t *joy_yawsensitivity; +cvar_t *joy_wwhack1; +cvar_t *joy_wwhack2; + +int joy_avail, joy_advancedinit, joy_haspov; +DWORD joy_oldbuttonstate, joy_oldpovstate; + +int joy_id; +DWORD joy_flags; +DWORD joy_numbuttons; + +static JOYINFOEX ji; + +/* +=========== +Force_CenterView_f +=========== +*/ +void Force_CenterView_f (void) +{ + vec3_t viewangles; + + if (!iMouseInUse) + { + gEngfuncs.GetViewAngles( (float *)viewangles ); + viewangles[PITCH] = 0; + gEngfuncs.SetViewAngles( (float *)viewangles ); + } +} + +/* +=========== +IN_ActivateMouse +=========== +*/ +void DLLEXPORT IN_ActivateMouse (void) +{ + if (mouseinitialized) + { + if (mouseparmsvalid) + restore_spi = SystemParametersInfo (SPI_SETMOUSE, 0, newmouseparms, 0); + mouseactive = 1; + } +} + +/* +=========== +IN_DeactivateMouse +=========== +*/ +void DLLEXPORT IN_DeactivateMouse (void) +{ + if (mouseinitialized) + { + if (restore_spi) + SystemParametersInfo (SPI_SETMOUSE, 0, originalmouseparms, 0); + + mouseactive = 0; + } +} + +/* +=========== +IN_StartupMouse +=========== +*/ +void IN_StartupMouse (void) +{ + if ( gEngfuncs.CheckParm ("-nomouse", NULL ) ) + return; + + mouseinitialized = 1; + mouseparmsvalid = SystemParametersInfo (SPI_GETMOUSE, 0, originalmouseparms, 0); + + if (mouseparmsvalid) + { + if ( gEngfuncs.CheckParm ("-noforcemspd", NULL ) ) + newmouseparms[2] = originalmouseparms[2]; + + if ( gEngfuncs.CheckParm ("-noforcemaccel", NULL ) ) + { + newmouseparms[0] = originalmouseparms[0]; + newmouseparms[1] = originalmouseparms[1]; + } + + if ( gEngfuncs.CheckParm ("-noforcemparms", NULL ) ) + { + newmouseparms[0] = originalmouseparms[0]; + newmouseparms[1] = originalmouseparms[1]; + newmouseparms[2] = originalmouseparms[2]; + } + } + + mouse_buttons = MOUSE_BUTTON_COUNT; +} + +/* +=========== +IN_Shutdown +=========== +*/ +void IN_Shutdown (void) +{ + IN_DeactivateMouse (); +} + +/* +=========== +IN_GetMousePos + +Ask for mouse position from engine +=========== +*/ +void IN_GetMousePos( int *mx, int *my ) +{ + gEngfuncs.GetMousePosition( mx, my ); +} + +/* +=========== +IN_ResetMouse + +FIXME: Call through to engine? +=========== +*/ +void IN_ResetMouse( void ) +{ + SetCursorPos ( gEngfuncs.GetWindowCenterX(), gEngfuncs.GetWindowCenterY() ); +} + +/* +=========== +IN_MouseEvent +=========== +*/ +void DLLEXPORT IN_MouseEvent (int mstate) +{ + int i; + + if ( !mouseactive ) + return; + + // perform button actions + for (i=0 ; ivalue) + { + 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() ) + { + cmd->upmove -= m_forward->value * mouse_y; + } + else + { + cmd->forwardmove -= m_forward->value * mouse_y; + } + } + + // if the mouse has moved, force it to the center, so there's room to move + if ( mx || my ) + { + IN_ResetMouse(); + } + } + + gEngfuncs.SetViewAngles( (float *)viewangles ); + +/* +//#define TRACE_TEST +#if defined( TRACE_TEST ) + { + int mx, my; + void V_Move( int mx, int my ); + IN_GetMousePos( &mx, &my ); + V_Move( mx, my ); + } +#endif +*/ +} + +/* +=========== +IN_Accumulate +=========== +*/ +void DLLEXPORT IN_Accumulate (void) +{ + //only accumulate mouse if we are not moving the camera with the mouse + if ( !iMouseInUse && !g_iVisibleMouse ) + { + if (mouseactive) + { + GetCursorPos (¤t_pos); + + mx_accum += current_pos.x - gEngfuncs.GetWindowCenterX(); + my_accum += current_pos.y - gEngfuncs.GetWindowCenterY(); + + // force the mouse to the center, so there's room to move + IN_ResetMouse(); + } + } + +} + +/* +=================== +IN_ClearStates +=================== +*/ +void DLLEXPORT IN_ClearStates (void) +{ + if ( !mouseactive ) + return; + + mx_accum = 0; + my_accum = 0; + mouse_oldbuttonstate = 0; +} + +/* +=============== +IN_StartupJoystick +=============== +*/ +void IN_StartupJoystick (void) +{ + int numdevs; + JOYCAPS jc; + MMRESULT mmr; + + // assume no joystick + joy_avail = 0; + + // abort startup if user requests no joystick + if ( gEngfuncs.CheckParm ("-nojoy", NULL ) ) + return; + + // verify joystick driver is present + if ((numdevs = joyGetNumDevs ()) == 0) + { + gEngfuncs.Con_DPrintf ("joystick not found -- driver not present\n\n"); + return; + } + + // cycle through the joystick ids for the first valid one + for (joy_id=0 ; joy_idvalue == 0.0) + { + // default joystick initialization + // 2 axes only with joystick control + dwAxisMap[JOY_AXIS_X] = AxisTurn; + // dwControlMap[JOY_AXIS_X] = JOY_ABSOLUTE_AXIS; + dwAxisMap[JOY_AXIS_Y] = AxisForward; + // dwControlMap[JOY_AXIS_Y] = JOY_ABSOLUTE_AXIS; + } + else + { + if ( strcmp ( joy_name->string, "joystick") != 0 ) + { + // notify user of advanced controller + gEngfuncs.Con_Printf ("\n%s configured\n\n", joy_name->string); + } + + // advanced initialization here + // data supplied by user via joy_axisn cvars + dwTemp = (DWORD) joy_advaxisx->value; + dwAxisMap[JOY_AXIS_X] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_X] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisy->value; + dwAxisMap[JOY_AXIS_Y] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_Y] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisz->value; + dwAxisMap[JOY_AXIS_Z] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_Z] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisr->value; + dwAxisMap[JOY_AXIS_R] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_R] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisu->value; + dwAxisMap[JOY_AXIS_U] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_U] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisv->value; + dwAxisMap[JOY_AXIS_V] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_V] = dwTemp & JOY_RELATIVE_AXIS; + } + + // compute the axes to collect from DirectInput + joy_flags = JOY_RETURNCENTERED | JOY_RETURNBUTTONS | JOY_RETURNPOV; + for (i = 0; i < JOY_MAX_AXES; i++) + { + if (dwAxisMap[i] != AxisNada) + { + joy_flags |= dwAxisFlags[i]; + } + } +} + + +/* +=========== +IN_Commands +=========== +*/ +void IN_Commands (void) +{ + int i, key_index; + DWORD buttonstate, povstate; + + if (!joy_avail) + { + return; + } + + + // loop through the joystick buttons + // key a joystick event or auxillary event for higher number buttons for each state change + buttonstate = ji.dwButtons; + for (i=0 ; i < (int)joy_numbuttons ; i++) + { + if ( (buttonstate & (1<value != 0.0) + { + ji.dwUpos += 100; + } + return 1; + } + else + { + // read error occurred + // turning off the joystick seems too harsh for 1 read error,\ + // but what should be done? + // Con_Printf ("IN_ReadJoystick: no response\n"); + // joy_avail = 0; + return 0; + } +} + + +/* +=========== +IN_JoyMove +=========== +*/ +void IN_JoyMove ( float frametime, usercmd_t *cmd ) +{ + float speed, aspeed; + float fAxisValue, fTemp; + int i; + vec3_t viewangles; + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + + // complete initialization if first time in + // this is needed as cvars are not available at initialization time + if( joy_advancedinit != 1 ) + { + Joy_AdvancedUpdate_f(); + joy_advancedinit = 1; + } + + // verify joystick is available and that the user wants to use it + if (!joy_avail || !in_joystick->value) + { + return; + } + + // collect the joystick data, if possible + if (IN_ReadJoystick () != 1) + { + return; + } + + if (in_speed.state & 1) + speed = cl_movespeedkey->value; + else + speed = 1; + + aspeed = speed * frametime; + + // loop through the axes + for (i = 0; i < JOY_MAX_AXES; i++) + { + // get the floating point zero-centered, potentially-inverted data for the current axis + fAxisValue = (float) *pdwRawValue[i]; + // move centerpoint to zero + fAxisValue -= 32768.0; + + if (joy_wwhack2->value != 0.0) + { + if (dwAxisMap[i] == AxisTurn) + { + // this is a special formula for the Logitech WingMan Warrior + // y=ax^b; where a = 300 and b = 1.3 + // also x values are in increments of 800 (so this is factored out) + // then bounds check result to level out excessively high spin rates + fTemp = 300.0 * pow(abs(fAxisValue) / 800.0, 1.3); + if (fTemp > 14000.0) + fTemp = 14000.0; + // restore direction information + fAxisValue = (fAxisValue > 0.0) ? fTemp : -fTemp; + } + } + + // convert range from -32768..32767 to -1..1 + fAxisValue /= 32768.0; + + switch (dwAxisMap[i]) + { + case AxisForward: + if ((joy_advanced->value == 0.0) && (in_jlook.state & 1)) + { + // user wants forward control to become look control + if (fabs(fAxisValue) > joy_pitchthreshold->value) + { + // if mouse invert is on, invert the joystick pitch value + // only absolute control support here (joy_advanced is 0) + if (m_pitch->value < 0.0) + { + viewangles[PITCH] -= (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value; + } + else + { + viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value; + } + V_StopPitchDrift(); + } + else + { + // no pitch movement + // disable pitch return-to-center unless requested by user + // *** this code can be removed when the lookspring bug is fixed + // *** the bug always has the lookspring feature on + if(lookspring->value == 0.0) + { + V_StopPitchDrift(); + } + } + } + else + { + // user wants forward control to be forward control + if (fabs(fAxisValue) > joy_forwardthreshold->value) + { + cmd->forwardmove += (fAxisValue * joy_forwardsensitivity->value) * speed * cl_forwardspeed->value; + } + } + break; + + case AxisSide: + if (fabs(fAxisValue) > joy_sidethreshold->value) + { + cmd->sidemove += (fAxisValue * joy_sidesensitivity->value) * speed * cl_sidespeed->value; + } + break; + + case AxisTurn: + if ((in_strafe.state & 1) || (lookstrafe->value && (in_jlook.state & 1))) + { + // user wants turn control to become side control + if (fabs(fAxisValue) > joy_sidethreshold->value) + { + cmd->sidemove -= (fAxisValue * joy_sidesensitivity->value) * speed * cl_sidespeed->value; + } + } + else + { + // user wants turn control to be turn control + if (fabs(fAxisValue) > joy_yawthreshold->value) + { + if(dwControlMap[i] == JOY_ABSOLUTE_AXIS) + { + viewangles[YAW] += (fAxisValue * joy_yawsensitivity->value) * aspeed * cl_yawspeed->value; + } + else + { + viewangles[YAW] += (fAxisValue * joy_yawsensitivity->value) * speed * 180.0; + } + + } + } + break; + + case AxisLook: + if (in_jlook.state & 1) + { + if (fabs(fAxisValue) > joy_pitchthreshold->value) + { + // pitch movement detected and pitch movement desired by user + if(dwControlMap[i] == JOY_ABSOLUTE_AXIS) + { + viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value; + } + else + { + viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * speed * 180.0; + } + V_StopPitchDrift(); + } + else + { + // no pitch movement + // disable pitch return-to-center unless requested by user + // *** this code can be removed when the lookspring bug is fixed + // *** the bug always has the lookspring feature on + if( lookspring->value == 0.0 ) + { + V_StopPitchDrift(); + } + } + } + break; + + default: + break; + } + } + + // bounds check pitch + if (viewangles[PITCH] > cl_pitchdown->value) + viewangles[PITCH] = cl_pitchdown->value; + if (viewangles[PITCH] < -cl_pitchup->value) + viewangles[PITCH] = -cl_pitchup->value; + + gEngfuncs.SetViewAngles( (float *)viewangles ); + +} + +/* +=========== +IN_Move +=========== +*/ +void IN_Move ( float frametime, usercmd_t *cmd) +{ + if ( !iMouseInUse && mouseactive ) + { + IN_MouseMove ( frametime, cmd); + } + + IN_JoyMove ( frametime, cmd); +} + +/* +=========== +IN_Init +=========== +*/ +void IN_Init (void) +{ + m_filter = gEngfuncs.pfnRegisterVariable ( "m_filter","0", FCVAR_ARCHIVE ); + sensitivity = gEngfuncs.pfnRegisterVariable ( "sensitivity","3", FCVAR_ARCHIVE ); // user mouse sensitivity setting. + + in_joystick = gEngfuncs.pfnRegisterVariable ( "joystick","0", FCVAR_ARCHIVE ); + joy_name = gEngfuncs.pfnRegisterVariable ( "joyname", "joystick", 0 ); + joy_advanced = gEngfuncs.pfnRegisterVariable ( "joyadvanced", "0", 0 ); + joy_advaxisx = gEngfuncs.pfnRegisterVariable ( "joyadvaxisx", "0", 0 ); + joy_advaxisy = gEngfuncs.pfnRegisterVariable ( "joyadvaxisy", "0", 0 ); + joy_advaxisz = gEngfuncs.pfnRegisterVariable ( "joyadvaxisz", "0", 0 ); + joy_advaxisr = gEngfuncs.pfnRegisterVariable ( "joyadvaxisr", "0", 0 ); + joy_advaxisu = gEngfuncs.pfnRegisterVariable ( "joyadvaxisu", "0", 0 ); + joy_advaxisv = gEngfuncs.pfnRegisterVariable ( "joyadvaxisv", "0", 0 ); + joy_forwardthreshold = gEngfuncs.pfnRegisterVariable ( "joyforwardthreshold", "0.15", 0 ); + joy_sidethreshold = gEngfuncs.pfnRegisterVariable ( "joysidethreshold", "0.15", 0 ); + joy_pitchthreshold = gEngfuncs.pfnRegisterVariable ( "joypitchthreshold", "0.15", 0 ); + joy_yawthreshold = gEngfuncs.pfnRegisterVariable ( "joyyawthreshold", "0.15", 0 ); + joy_forwardsensitivity = gEngfuncs.pfnRegisterVariable ( "joyforwardsensitivity", "-1.0", 0 ); + joy_sidesensitivity = gEngfuncs.pfnRegisterVariable ( "joysidesensitivity", "-1.0", 0 ); + joy_pitchsensitivity = gEngfuncs.pfnRegisterVariable ( "joypitchsensitivity", "1.0", 0 ); + joy_yawsensitivity = gEngfuncs.pfnRegisterVariable ( "joyyawsensitivity", "-1.0", 0 ); + joy_wwhack1 = gEngfuncs.pfnRegisterVariable ( "joywwhack1", "0.0", 0 ); + joy_wwhack2 = gEngfuncs.pfnRegisterVariable ( "joywwhack2", "0.0", 0 ); + + gEngfuncs.pfnAddCommand ("force_centerview", Force_CenterView_f); + gEngfuncs.pfnAddCommand ("joyadvancedupdate", Joy_AdvancedUpdate_f); + + IN_StartupMouse (); + IN_StartupJoystick (); +} \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/kbutton.h b/hlsdk/dmc/cl_dll/kbutton.h new file mode 100644 index 00000000..23cd8acc --- /dev/null +++ b/hlsdk/dmc/cl_dll/kbutton.h @@ -0,0 +1,18 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( KBUTTONH ) +#define KBUTTONH +#pragma once + +typedef struct kbutton_s +{ + int down[2]; // key nums holding it down + int state; // low bit is down state +} kbutton_t; + +#endif // !KBUTTONH \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/menu.cpp b/hlsdk/dmc/cl_dll/menu.cpp new file mode 100644 index 00000000..6510fbe2 --- /dev/null +++ b/hlsdk/dmc/cl_dll/menu.cpp @@ -0,0 +1,186 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// menu.cpp +// +// generic menu handler +// +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include +#include "vgui_viewport.h" + +#define MAX_MENU_STRING 512 +char g_szMenuString[MAX_MENU_STRING]; +char g_szPrelocalisedMenuString[MAX_MENU_STRING]; + +int KB_ConvertString( char *in, char **ppout ); + +DECLARE_MESSAGE( m_Menu, ShowMenu ); + +int CHudMenu :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( ShowMenu ); + + InitHUDData(); + + return 1; +} + +void CHudMenu :: InitHUDData( void ) +{ + m_fMenuDisplayed = 0; + m_bitsValidSlots = 0; + Reset(); +} + +void CHudMenu :: Reset( void ) +{ + g_szPrelocalisedMenuString[0] = 0; + m_fWaitingForMore = FALSE; +} + +int CHudMenu :: VidInit( void ) +{ + return 1; +} + +int CHudMenu :: Draw( float flTime ) +{ + // check for if menu is set to disappear + if ( m_flShutoffTime > 0 ) + { + if ( m_flShutoffTime <= gHUD.m_flTime ) + { // times up, shutoff + m_fMenuDisplayed = 0; + m_iFlags &= ~HUD_ACTIVE; + return 1; + } + } + + // don't draw the menu if the scoreboard is being shown + if ( gViewPort->IsScoreBoardVisible() ) + return 1; + + // draw the menu, along the left-hand side of the screen + + // count the number of newlines + int nlc = 0, i = 0; + for ( i = 0; i < MAX_MENU_STRING && g_szMenuString[i] != '\0'; i++ ) + { + if ( g_szMenuString[i] == '\n' ) + nlc++; + } + + // center it + int y = (ScreenHeight/2) - ((nlc/2)*12) - 40; // make sure it is above the say text + int x = 20; + + i = 0; + while ( i < MAX_MENU_STRING && g_szMenuString[i] != '\0' ) + { + gHUD.DrawHudString( x, y, 320, g_szMenuString + i, 255, 255, 255 ); + y += 12; + + while ( i < MAX_MENU_STRING && g_szMenuString[i] != '\0' && g_szMenuString[i] != '\n' ) + i++; + if ( g_szMenuString[i] == '\n' ) + i++; + } + + return 1; +} + +// selects an item from the menu +void CHudMenu :: SelectMenuItem( int menu_item ) +{ + // if menu_item is in a valid slot, send a menuselect command to the server + if ( (menu_item > 0) && (m_bitsValidSlots & (1 << (menu_item-1))) ) + { + char szbuf[32]; + sprintf( szbuf, "menuselect %d\n", menu_item ); + ClientCmd( szbuf ); + + // remove the menu + m_fMenuDisplayed = 0; + m_iFlags &= ~HUD_ACTIVE; + } +} + + +// Message handler for ShowMenu message +// takes four values: +// short: a bitfield of keys that are valid input +// char : the duration, in seconds, the menu should stay up. -1 means is stays until something is chosen. +// byte : a boolean, TRUE if there is more string yet to be received before displaying the menu, FALSE if it's the last string +// string: menu string to display +// if this message is never received, then scores will simply be the combined totals of the players. +int CHudMenu :: MsgFunc_ShowMenu( const char *pszName, int iSize, void *pbuf ) +{ + char *temp = NULL; + + BEGIN_READ( pbuf, iSize ); + + m_bitsValidSlots = READ_SHORT(); + int DisplayTime = READ_CHAR(); + int NeedMore = READ_BYTE(); + + if ( DisplayTime > 0 ) + m_flShutoffTime = DisplayTime + gHUD.m_flTime; + else + m_flShutoffTime = -1; + + if ( m_bitsValidSlots ) + { + if ( !m_fWaitingForMore ) // this is the start of a new menu + { + strncpy( g_szPrelocalisedMenuString, READ_STRING(), MAX_MENU_STRING ); + } + else + { // append to the current menu string + strncat( g_szPrelocalisedMenuString, READ_STRING(), MAX_MENU_STRING - strlen(g_szPrelocalisedMenuString) ); + } + g_szPrelocalisedMenuString[MAX_MENU_STRING-1] = 0; // ensure null termination (strncat/strncpy does not) + + if ( !NeedMore ) + { // we have the whole string, so we can localise it now + strcpy( g_szMenuString, gHUD.m_TextMessage.BufferedLocaliseTextString( g_szPrelocalisedMenuString ) ); + + // Swap in characters + if ( KB_ConvertString( g_szMenuString, &temp ) ) + { + strcpy( g_szMenuString, temp ); + free( temp ); + } + } + + m_fMenuDisplayed = 1; + m_iFlags |= HUD_ACTIVE; + } + else + { + m_fMenuDisplayed = 0; // no valid slots means that the menu should be turned off + m_iFlags &= ~HUD_ACTIVE; + } + + m_fWaitingForMore = NeedMore; + + return 1; +} diff --git a/hlsdk/dmc/cl_dll/message.cpp b/hlsdk/dmc/cl_dll/message.cpp new file mode 100644 index 00000000..723a6dcf --- /dev/null +++ b/hlsdk/dmc/cl_dll/message.cpp @@ -0,0 +1,482 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Message.cpp +// +// implementation of CHudMessage class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE( m_Message, HudText ) +DECLARE_MESSAGE( m_Message, GameTitle ) + + +int CHudMessage::Init(void) +{ + HOOK_MESSAGE( HudText ); + HOOK_MESSAGE( GameTitle ); + + gHUD.AddHudElem(this); + + Reset(); + + return 1; +}; + +int CHudMessage::VidInit( void ) +{ + m_HUD_title_half = gHUD.GetSpriteIndex( "title_half" ); + m_HUD_title_life = gHUD.GetSpriteIndex( "title_life" ); + + return 1; +}; + + +void CHudMessage::Reset( void ) +{ + memset( m_pMessages, 0, sizeof( m_pMessages[0] ) * maxHUDMessages ); + memset( m_startTime, 0, sizeof( m_startTime[0] ) * maxHUDMessages ); + + m_gameTitleTime = 0; + m_pGameTitle = NULL; +} + + +float CHudMessage::FadeBlend( float fadein, float fadeout, float hold, float localTime ) +{ + float fadeTime = fadein + hold; + float fadeBlend; + + if ( localTime < 0 ) + return 0; + + if ( localTime < fadein ) + { + fadeBlend = 1 - ((fadein - localTime) / fadein); + } + else if ( localTime > fadeTime ) + { + if ( fadeout > 0 ) + fadeBlend = 1 - ((localTime - fadeTime) / fadeout); + else + fadeBlend = 0; + } + else + fadeBlend = 1; + + return fadeBlend; +} + + +int CHudMessage::XPosition( float x, int width, int totalWidth ) +{ + int xPos; + + if ( x == -1 ) + { + xPos = (ScreenWidth - width) / 2; + } + else + { + if ( x < 0 ) + xPos = (1.0 + x) * ScreenWidth - totalWidth; // Alight right + else + xPos = x * ScreenWidth; + } + + if ( xPos + width > ScreenWidth ) + xPos = ScreenWidth - width; + else if ( xPos < 0 ) + xPos = 0; + + return xPos; +} + + +int CHudMessage::YPosition( float y, int height ) +{ + int yPos; + + if ( y == -1 ) // Centered? + yPos = (ScreenHeight - height) * 0.5; + else + { + // Alight bottom? + if ( y < 0 ) + yPos = (1.0 + y) * ScreenHeight - height; // Alight bottom + else // align top + yPos = y * ScreenHeight; + } + + if ( yPos + height > ScreenHeight ) + yPos = ScreenHeight - height; + else if ( yPos < 0 ) + yPos = 0; + + return yPos; +} + + +void CHudMessage::MessageScanNextChar( void ) +{ + int srcRed, srcGreen, srcBlue, destRed, destGreen, destBlue; + int blend; + + srcRed = m_parms.pMessage->r1; + srcGreen = m_parms.pMessage->g1; + srcBlue = m_parms.pMessage->b1; + blend = 0; // Pure source + + switch( m_parms.pMessage->effect ) + { + // Fade-in / Fade-out + case 0: + case 1: + destRed = destGreen = destBlue = 0; + blend = m_parms.fadeBlend; + break; + + case 2: + m_parms.charTime += m_parms.pMessage->fadein; + if ( m_parms.charTime > m_parms.time ) + { + srcRed = srcGreen = srcBlue = 0; + blend = 0; // pure source + } + else + { + float deltaTime = m_parms.time - m_parms.charTime; + + destRed = destGreen = destBlue = 0; + if ( m_parms.time > m_parms.fadeTime ) + { + blend = m_parms.fadeBlend; + } + else if ( deltaTime > m_parms.pMessage->fxtime ) + blend = 0; // pure dest + else + { + destRed = m_parms.pMessage->r2; + destGreen = m_parms.pMessage->g2; + destBlue = m_parms.pMessage->b2; + blend = 255 - (deltaTime * (1.0/m_parms.pMessage->fxtime) * 255.0 + 0.5); + } + } + break; + } + if ( blend > 255 ) + blend = 255; + else if ( blend < 0 ) + blend = 0; + + m_parms.r = ((srcRed * (255-blend)) + (destRed * blend)) >> 8; + m_parms.g = ((srcGreen * (255-blend)) + (destGreen * blend)) >> 8; + m_parms.b = ((srcBlue * (255-blend)) + (destBlue * blend)) >> 8; + + if ( m_parms.pMessage->effect == 1 && m_parms.charTime != 0 ) + { + if ( m_parms.x >= 0 && m_parms.y >= 0 && (m_parms.x + gHUD.m_scrinfo.charWidths[ m_parms.text ]) <= ScreenWidth ) + TextMessageDrawChar( m_parms.x, m_parms.y, m_parms.text, m_parms.pMessage->r2, m_parms.pMessage->g2, m_parms.pMessage->b2 ); + } +} + + +void CHudMessage::MessageScanStart( void ) +{ + switch( m_parms.pMessage->effect ) + { + // Fade-in / out with flicker + case 1: + case 0: + m_parms.fadeTime = m_parms.pMessage->fadein + m_parms.pMessage->holdtime; + + + if ( m_parms.time < m_parms.pMessage->fadein ) + { + m_parms.fadeBlend = ((m_parms.pMessage->fadein - m_parms.time) * (1.0/m_parms.pMessage->fadein) * 255); + } + else if ( m_parms.time > m_parms.fadeTime ) + { + if ( m_parms.pMessage->fadeout > 0 ) + m_parms.fadeBlend = (((m_parms.time - m_parms.fadeTime) / m_parms.pMessage->fadeout) * 255); + else + m_parms.fadeBlend = 255; // Pure dest (off) + } + else + m_parms.fadeBlend = 0; // Pure source (on) + m_parms.charTime = 0; + + if ( m_parms.pMessage->effect == 1 && (rand()%100) < 10 ) + m_parms.charTime = 1; + break; + + case 2: + m_parms.fadeTime = (m_parms.pMessage->fadein * m_parms.length) + m_parms.pMessage->holdtime; + + if ( m_parms.time > m_parms.fadeTime && m_parms.pMessage->fadeout > 0 ) + m_parms.fadeBlend = (((m_parms.time - m_parms.fadeTime) / m_parms.pMessage->fadeout) * 255); + else + m_parms.fadeBlend = 0; + break; + } +} + + +void CHudMessage::MessageDrawScan( client_textmessage_t *pMessage, float time ) +{ + int i, j, length, width; + const char *pText; + unsigned char line[80]; + + pText = pMessage->pMessage; + // Count lines + m_parms.lines = 1; + m_parms.time = time; + m_parms.pMessage = pMessage; + length = 0; + width = 0; + m_parms.totalWidth = 0; + while ( *pText ) + { + if ( *pText == '\n' ) + { + m_parms.lines++; + if ( width > m_parms.totalWidth ) + m_parms.totalWidth = width; + width = 0; + } + else + width += gHUD.m_scrinfo.charWidths[*pText]; + pText++; + length++; + } + m_parms.length = length; + m_parms.totalHeight = (m_parms.lines * gHUD.m_scrinfo.iCharHeight); + + + m_parms.y = YPosition( pMessage->y, m_parms.totalHeight ); + pText = pMessage->pMessage; + + m_parms.charTime = 0; + + MessageScanStart(); + + for ( i = 0; i < m_parms.lines; i++ ) + { + m_parms.lineLength = 0; + m_parms.width = 0; + while ( *pText && *pText != '\n' ) + { + unsigned char c = *pText; + line[m_parms.lineLength] = c; + m_parms.width += gHUD.m_scrinfo.charWidths[c]; + m_parms.lineLength++; + pText++; + } + pText++; // Skip LF + line[m_parms.lineLength] = 0; + + m_parms.x = XPosition( pMessage->x, m_parms.width, m_parms.totalWidth ); + + for ( j = 0; j < m_parms.lineLength; j++ ) + { + m_parms.text = line[j]; + int next = m_parms.x + gHUD.m_scrinfo.charWidths[ m_parms.text ]; + MessageScanNextChar(); + + if ( m_parms.x >= 0 && m_parms.y >= 0 && next <= ScreenWidth ) + TextMessageDrawChar( m_parms.x, m_parms.y, m_parms.text, m_parms.r, m_parms.g, m_parms.b ); + m_parms.x = next; + } + + m_parms.y += gHUD.m_scrinfo.iCharHeight; + } +} + + +int CHudMessage::Draw( float fTime ) +{ + int i, drawn; + client_textmessage_t *pMessage; + float endTime; + + drawn = 0; + + if ( m_gameTitleTime > 0 ) + { + float localTime = gHUD.m_flTime - m_gameTitleTime; + float brightness; + + // Maybe timer isn't set yet + if ( m_gameTitleTime > gHUD.m_flTime ) + m_gameTitleTime = gHUD.m_flTime; + + if ( localTime > (m_pGameTitle->fadein + m_pGameTitle->holdtime + m_pGameTitle->fadeout) ) + m_gameTitleTime = 0; + else + { + brightness = FadeBlend( m_pGameTitle->fadein, m_pGameTitle->fadeout, m_pGameTitle->holdtime, localTime ); + + int halfWidth = gHUD.GetSpriteRect(m_HUD_title_half).right - gHUD.GetSpriteRect(m_HUD_title_half).left; + int fullWidth = halfWidth + gHUD.GetSpriteRect(m_HUD_title_life).right - gHUD.GetSpriteRect(m_HUD_title_life).left; + int fullHeight = gHUD.GetSpriteRect(m_HUD_title_half).bottom - gHUD.GetSpriteRect(m_HUD_title_half).top; + + int x = XPosition( m_pGameTitle->x, fullWidth, fullWidth ); + int y = YPosition( m_pGameTitle->y, fullHeight ); + + + SPR_Set( gHUD.GetSprite(m_HUD_title_half), brightness * m_pGameTitle->r1, brightness * m_pGameTitle->g1, brightness * m_pGameTitle->b1 ); + SPR_DrawAdditive( 0, x, y, &gHUD.GetSpriteRect(m_HUD_title_half) ); + + SPR_Set( gHUD.GetSprite(m_HUD_title_life), brightness * m_pGameTitle->r1, brightness * m_pGameTitle->g1, brightness * m_pGameTitle->b1 ); + SPR_DrawAdditive( 0, x + halfWidth, y, &gHUD.GetSpriteRect(m_HUD_title_life) ); + + drawn = 1; + } + } + // Fixup level transitions + for ( i = 0; i < maxHUDMessages; i++ ) + { + // Assume m_parms.time contains last time + if ( m_pMessages[i] ) + { + pMessage = m_pMessages[i]; + if ( m_startTime[i] > gHUD.m_flTime ) + m_startTime[i] = gHUD.m_flTime + m_parms.time - m_startTime[i] + 0.2; // Server takes 0.2 seconds to spawn, adjust for this + } + } + + for ( i = 0; i < maxHUDMessages; i++ ) + { + if ( m_pMessages[i] ) + { + pMessage = m_pMessages[i]; + + // This is when the message is over + switch( pMessage->effect ) + { + case 0: + case 1: + endTime = m_startTime[i] + pMessage->fadein + pMessage->fadeout + pMessage->holdtime; + break; + + // Fade in is per character in scanning messages + case 2: + endTime = m_startTime[i] + (pMessage->fadein * strlen( pMessage->pMessage )) + pMessage->fadeout + pMessage->holdtime; + break; + } + + if ( fTime <= endTime ) + { + float messageTime = fTime - m_startTime[i]; + + // Draw the message + // effect 0 is fade in/fade out + // effect 1 is flickery credits + // effect 2 is write out (training room) + MessageDrawScan( pMessage, messageTime ); + + drawn++; + } + else + { + // The message is over + m_pMessages[i] = NULL; + } + } + } + + // Remember the time -- to fix up level transitions + m_parms.time = gHUD.m_flTime; + // Don't call until we get another message + if ( !drawn ) + m_iFlags &= ~HUD_ACTIVE; + + return 1; +} + + +void CHudMessage::MessageAdd( const char *pName, float time ) +{ + int i; + + for ( i = 0; i < maxHUDMessages; i++ ) + { + if ( !m_pMessages[i] ) + { + m_pMessages[i] = TextMessageGet( pName ); + m_startTime[i] = time; + return; + } + } +} + + +int CHudMessage::MsgFunc_HudText( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + char *pString = READ_STRING(); + + MessageAdd( pString, gHUD.m_flTime ); + // Remember the time -- to fix up level transitions + m_parms.time = gHUD.m_flTime; + + // Turn on drawing + if ( !(m_iFlags & HUD_ACTIVE) ) + m_iFlags |= HUD_ACTIVE; + + return 1; +} + + +int CHudMessage::MsgFunc_GameTitle( const char *pszName, int iSize, void *pbuf ) +{ + m_pGameTitle = TextMessageGet( "GAMETITLE" ); + if ( m_pGameTitle != NULL ) + { + m_gameTitleTime = gHUD.m_flTime; + + // Turn on drawing + if ( !(m_iFlags & HUD_ACTIVE) ) + m_iFlags |= HUD_ACTIVE; + } + + return 1; +} + +void CHudMessage::MessageAdd(client_textmessage_t * newMessage ) +{ + m_parms.time = gHUD.m_flTime; + + // Turn on drawing + if ( !(m_iFlags & HUD_ACTIVE) ) + m_iFlags |= HUD_ACTIVE; + + for ( int i = 0; i < maxHUDMessages; i++ ) + { + if ( !m_pMessages[i] ) + { + m_pMessages[i] = newMessage; + m_startTime[i] = gHUD.m_flTime; + return; + } + } +} diff --git a/hlsdk/dmc/cl_dll/msvc6/cl_dll.dsp b/hlsdk/dmc/cl_dll/msvc6/cl_dll.dsp new file mode 100644 index 00000000..692c9999 --- /dev/null +++ b/hlsdk/dmc/cl_dll/msvc6/cl_dll.dsp @@ -0,0 +1,552 @@ +# Microsoft Developer Studio Project File - Name="cl_dll" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=cl_dll - 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 "cl_dll.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 "cl_dll.mak" CFG="cl_dll - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "cl_dll - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "cl_dll - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "cl_dll - 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 ".\Release" +# PROP Intermediate_Dir ".\Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /MT /W3 /GX /Zi /O2 /I "..\..\utils\common" /I "..\..\engine" /I "..\..\common" /I "..\..\utils\vgui\include" /I "..\..\dlls" /I "..\..\..\engine" /I "..\..\..\common" /I "..\..\pm_shared" /I "..\..\..\utils\vgui\include" /I "." /I "..\..\..\game_shared" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "CLIENT_DLL" /D "DMC_BUILD" /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 winmm.lib ..\..\..\utils\vgui\lib\win32_vc6\vgui.lib wsock32.lib /nologo /subsystem:windows /dll /map /machine:I386 /out:".\Release\client.dll" + +!ELSEIF "$(CFG)" == "cl_dll - 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 ".\Debug" +# PROP Intermediate_Dir ".\Debug" +# 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 /MT /W3 /GX /ZI /Od /I "..\..\engine" /I "..\..\common" /I "..\..\utils\vgui\include" /I "..\..\dlls" /I "..\..\..\engine" /I "..\..\..\common" /I "..\..\pm_shared" /I "..\..\..\utils\vgui\include" /I "." /I "..\..\..\game_shared" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "CLIENT_DLL" /D "DMC_BUILD" /FR /Fp".\Release/cl_dll.pch" /YX /Fo".\Release/" /Fd".\Release/" /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 /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 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 /profile /map /debug /machine:I386 /out:".\Debug\client.dll" + +!ENDIF + +# Begin Target + +# Name "cl_dll - Win32 Release" +# Name "cl_dll - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Group "quake" + +# PROP Default_Filter "*.cpp" +# Begin Source File + +SOURCE=..\DMC_Teleporters.cpp +# End Source File +# Begin Source File + +SOURCE=..\ev_hldm.cpp +# End Source File +# Begin Source File + +SOURCE=..\hud_spectator.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\interface.cpp +# End Source File +# Begin Source File + +SOURCE=..\quake\quake_baseentity.cpp +# End Source File +# Begin Source File + +SOURCE=..\quake\quake_events.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\dlls\quake_gun.cpp +# End Source File +# Begin Source File + +SOURCE=..\quake\quake_objects.cpp +# End Source File +# Begin Source File + +SOURCE=..\quake\quake_weapons.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\dlls\quake_weapons_all.cpp +# End Source File +# Begin Source File + +SOURCE=..\studio_util.cpp +# End Source File +# End Group +# Begin Source File + +SOURCE=..\ammo.cpp +# End Source File +# Begin Source File + +SOURCE=..\ammo_secondary.cpp +# End Source File +# Begin Source File + +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 + +SOURCE=..\com_weapons.cpp +# End Source File +# Begin Source File + +SOURCE=..\death.cpp +# End Source File +# Begin Source File + +SOURCE=..\demo.cpp +# End Source File +# Begin Source File + +SOURCE=..\entity.cpp +# End Source File +# Begin Source File + +SOURCE=..\ev_common.cpp +# End Source File +# Begin Source File + +SOURCE=..\events.cpp +# End Source File +# Begin Source File + +SOURCE=..\GameStudioModelRenderer.cpp +# End Source File +# Begin Source File + +SOURCE=..\geiger.cpp +# End Source File +# Begin Source File + +SOURCE=..\health.cpp +# End Source File +# Begin Source File + +SOURCE=..\hud.cpp +# End Source File +# Begin Source File + +SOURCE=..\hud_msg.cpp +# End Source File +# Begin Source File + +SOURCE=..\hud_redraw.cpp +# End Source File +# Begin Source File + +SOURCE=..\hud_servers.cpp +# End Source File +# Begin Source File + +SOURCE=..\hud_update.cpp +# End Source File +# Begin Source File + +SOURCE=..\in_camera.cpp +# End Source File +# Begin Source File + +SOURCE=..\input.cpp +# End Source File +# Begin Source File + +SOURCE=..\inputw32.cpp +# End Source File +# Begin Source File + +SOURCE=..\menu.cpp +# End Source File +# Begin Source File + +SOURCE=..\message.cpp +# End Source File +# Begin Source File + +SOURCE=..\parsemsg.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=..\saytext.cpp +# End Source File +# Begin Source File + +SOURCE=..\scoreboard.cpp +# PROP Exclude_From_Build 1 +# End Source File +# Begin Source File + +SOURCE=..\status_icons.cpp +# End Source File +# Begin Source File + +SOURCE=..\statusbar.cpp +# End Source File +# Begin Source File + +SOURCE=..\StudioModelRenderer.cpp +# End Source File +# Begin Source File + +SOURCE=..\text_message.cpp +# End Source File +# Begin Source File + +SOURCE=..\train.cpp +# End Source File +# Begin Source File + +SOURCE=..\tri.cpp +# End Source File +# Begin Source File + +SOURCE=..\util.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\game_shared\vgui_checkbutton2.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_CustomObjects.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\game_shared\vgui_grid.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\game_shared\vgui_helpers.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_int.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\game_shared\vgui_listbox.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\game_shared\vgui_loadtga.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_MOTDWindow.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_SchemeManager.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_ScorePanel.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\game_shared\vgui_scrollbar2.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_ServerBrowser.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\game_shared\vgui_slider2.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_SpectatorPanel.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_viewport.cpp +# End Source File +# Begin Source File + +SOURCE=..\view.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\game_shared\voice_banmgr.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\game_shared\voice_status.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=..\ammo.h +# End Source File +# Begin Source File + +SOURCE=..\ammohistory.h +# End Source File +# Begin Source File + +SOURCE=..\camera.h +# End Source File +# Begin Source File + +SOURCE=..\cl_dll.h +# End Source File +# Begin Source File + +SOURCE=..\com_weapons.h +# End Source File +# Begin Source File + +SOURCE=..\demo.h +# End Source File +# Begin Source File + +SOURCE=..\DMC_BSPFile.h +# End Source File +# Begin Source File + +SOURCE=..\DMC_Teleporters.h +# End Source File +# Begin Source File + +SOURCE=..\ev_hldm.h +# End Source File +# Begin Source File + +SOURCE=..\eventscripts.h +# End Source File +# Begin Source File + +SOURCE=..\GameStudioModelRenderer.h +# End Source File +# Begin Source File + +SOURCE=..\health.h +# End Source File +# Begin Source File + +SOURCE=..\hud.h +# End Source File +# Begin Source File + +SOURCE=..\hud_iface.h +# End Source File +# Begin Source File + +SOURCE=..\hud_servers.h +# End Source File +# Begin Source File + +SOURCE=..\hud_servers_priv.h +# End Source File +# Begin Source File + +SOURCE=..\hud_spectator.h +# End Source File +# Begin Source File + +SOURCE=..\in_defs.h +# End Source File +# Begin Source File + +SOURCE=..\kbutton.h +# End Source File +# Begin Source File + +SOURCE=..\parsemsg.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=..\..\dlls\quake_gun.h +# End Source File +# Begin Source File + +SOURCE=..\StudioModelRenderer.h +# End Source File +# Begin Source File + +SOURCE=..\util.h +# End Source File +# Begin Source File + +SOURCE=..\util_vector.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\game_shared\vgui_checkbutton2.h +# End Source File +# Begin Source File + +SOURCE=..\vgui_ControlConfigPanel.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\game_shared\vgui_grid.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\game_shared\vgui_helpers.h +# End Source File +# Begin Source File + +SOURCE=..\vgui_int.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\game_shared\vgui_listbox.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\game_shared\vgui_loadtga.h +# End Source File +# Begin Source File + +SOURCE=..\vgui_SchemeManager.h +# End Source File +# Begin Source File + +SOURCE=..\vgui_ScorePanel.h +# End Source File +# Begin Source File + +SOURCE=..\vgui_ServerBrowser.h +# End Source File +# Begin Source File + +SOURCE=..\vgui_SpectatorPanel.h +# End Source File +# Begin Source File + +SOURCE=..\vgui_viewport.h +# End Source File +# Begin Source File + +SOURCE=..\view.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\game_shared\voice_status.h +# End Source File +# Begin Source File + +SOURCE=..\wrect.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/hlsdk/dmc/cl_dll/msvc7/cl_dll.vcproj b/hlsdk/dmc/cl_dll/msvc7/cl_dll.vcproj new file mode 100644 index 00000000..83a7aa45 --- /dev/null +++ b/hlsdk/dmc/cl_dll/msvc7/cl_dll.vcproj @@ -0,0 +1,1629 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hlsdk/dmc/cl_dll/msvc8/cl_dll.vcproj b/hlsdk/dmc/cl_dll/msvc8/cl_dll.vcproj new file mode 100644 index 00000000..fb3c36a8 --- /dev/null +++ b/hlsdk/dmc/cl_dll/msvc8/cl_dll.vcproj @@ -0,0 +1,2070 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hlsdk/dmc/cl_dll/parsemsg.cpp b/hlsdk/dmc/cl_dll/parsemsg.cpp new file mode 100644 index 00000000..00494414 --- /dev/null +++ b/hlsdk/dmc/cl_dll/parsemsg.cpp @@ -0,0 +1,166 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// parsemsg.cpp +// +typedef unsigned char byte; +#define true 1 + +static byte *gpBuf; +static int giSize; +static int giRead; +static int giBadRead; + +void BEGIN_READ( void *buf, int size ) +{ + giRead = 0; + giBadRead = 0; + giSize = size; + gpBuf = (byte*)buf; +} + + +int READ_CHAR( void ) +{ + int c; + + if (giRead + 1 > giSize) + { + giBadRead = true; + return -1; + } + + c = (signed char)gpBuf[giRead]; + giRead++; + + return c; +} + +int READ_BYTE( void ) +{ + int c; + + if (giRead+1 > giSize) + { + giBadRead = true; + return -1; + } + + c = (unsigned char)gpBuf[giRead]; + giRead++; + + return c; +} + +int READ_SHORT( void ) +{ + int c; + + if (giRead+2 > giSize) + { + giBadRead = true; + return -1; + } + + c = (short)( gpBuf[giRead] + ( gpBuf[giRead+1] << 8 ) ); + + giRead += 2; + + return c; +} + +int READ_WORD( void ) +{ + return READ_SHORT(); +} + + +int READ_LONG( void ) +{ + int c; + + if (giRead+4 > giSize) + { + giBadRead = true; + return -1; + } + + c = gpBuf[giRead] + (gpBuf[giRead + 1] << 8) + (gpBuf[giRead + 2] << 16) + (gpBuf[giRead + 3] << 24); + + giRead += 4; + + return c; +} + +float READ_FLOAT( void ) +{ + union + { + byte b[4]; + float f; + int l; + } dat; + + dat.b[0] = gpBuf[giRead]; + dat.b[1] = gpBuf[giRead+1]; + dat.b[2] = gpBuf[giRead+2]; + dat.b[3] = gpBuf[giRead+3]; + giRead += 4; + +// dat.l = LittleLong (dat.l); + + return dat.f; +} + +char* READ_STRING( void ) +{ + static char string[2048]; + int l,c; + + string[0] = 0; + + l = 0; + do + { + if ( giRead+1 > giSize ) + break; // no more characters + + c = READ_CHAR(); + if (c == -1 || c == 0) + break; + string[l] = c; + l++; + } while (l < sizeof(string)-1); + + string[l] = 0; + + return string; +} + +float READ_COORD( void ) +{ + return (float)(READ_SHORT() * (1.0/8)); +} + +float READ_ANGLE( void ) +{ + return (float)(READ_CHAR() * (360.0/256)); +} + +float READ_HIRESANGLE( void ) +{ + return (float)(READ_SHORT() * (360.0/65536)); +} + diff --git a/hlsdk/dmc/cl_dll/parsemsg.h b/hlsdk/dmc/cl_dll/parsemsg.h new file mode 100644 index 00000000..0e6bd2a3 --- /dev/null +++ b/hlsdk/dmc/cl_dll/parsemsg.h @@ -0,0 +1,40 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// parsemsg.h +// + +#define ASSERT( x ) + +void BEGIN_READ( void *buf, int size ); +int READ_CHAR( void ); +int READ_BYTE( void ); +int READ_SHORT( void ); +int READ_WORD( void ); +int READ_LONG( void ); +float READ_FLOAT( void ); +char* READ_STRING( void ); +float READ_COORD( void ); +float READ_ANGLE( void ); +float READ_HIRESANGLE( void ); + + + + + + + + + diff --git a/hlsdk/dmc/cl_dll/quake/quake_baseentity.cpp b/hlsdk/dmc/cl_dll/quake/quake_baseentity.cpp new file mode 100644 index 00000000..2893c4da --- /dev/null +++ b/hlsdk/dmc/cl_dll/quake/quake_baseentity.cpp @@ -0,0 +1,330 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +/* +========================== +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" + +// Globals used by game logic +const Vector g_vecZero = Vector( 0, 0, 0 ); +int gmsgWeapPickup = 0; +enginefuncs_t g_engfuncs; +globalvars_t *gpGlobals; + +short g_sModelIndexLaser; + +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 ) { } + +CBaseEntity* CBaseMonster :: CheckTraceHullAttack( float flDist, int iDamage, int iDmgType ) { return NULL; } +void CBaseMonster :: Eat ( float flFullDuration ) { } +BOOL CBaseMonster :: FShouldEat ( void ) { return TRUE; } +void CBaseMonster :: BarnacleVictimBitten ( entvars_t *pevBarnacle ) { } +void CBaseMonster :: BarnacleVictimReleased ( void ) { } +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) { } +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) { } +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; } +MONSTERSTATE CBaseMonster :: GetIdealState ( void ) { return MONSTERSTATE_ALERT; } +Schedule_t* CBaseMonster :: GetScheduleOfType ( int Type ) { return NULL; } +Schedule_t *CBaseMonster :: GetSchedule ( void ) { return NULL; } +void CBaseMonster :: RunTask ( Task_t *pTask ) { } +void CBaseMonster :: StartTask ( Task_t *pTask ) { } +Schedule_t *CBaseMonster::ScheduleFromName( const char *pName ) { return NULL;} +void CBaseMonster::BecomeDead( void ) {} +void CBaseMonster :: RunAI ( 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; } +void CBasePlayer::CheckAmmo(void) { } +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 CBasePlayer :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { return 0; } +void CBasePlayer::RemoveAllItems( BOOL removeSuit ) { } +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 :: PlayStepSound(int step, float fvol) { } +void CBasePlayer :: UpdateStepSound( void ) { } +void CBasePlayer::PreThink(void) { } +void CBasePlayer::CheckTimeBasedDamage() { } +void CBasePlayer :: UpdateGeigerCounter( void ) { } +void CBasePlayer::CheckSuitUpdate() { } +void CBasePlayer::SetSuitUpdate(char *name, int fgroup, int iNoRepeatTime) { } +void CBasePlayer::PostThink() { } +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() { } +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; } +void CBasePlayer :: BarnacleVictimBitten ( entvars_t *pevBarnacle ) { } +void CBasePlayer :: BarnacleVictimReleased ( void ) { } +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 ) { } diff --git a/hlsdk/dmc/cl_dll/quake/quake_events.cpp b/hlsdk/dmc/cl_dll/quake/quake_events.cpp new file mode 100644 index 00000000..6925bbf9 --- /dev/null +++ b/hlsdk/dmc/cl_dll/quake/quake_events.cpp @@ -0,0 +1,83 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "../hud.h" +#include "../cl_util.h" +#include "event_api.h" + +extern "C" +{ +// Deathmatch Classic +void EV_FireShotGunSingle( struct event_args_s *args ); +void EV_FireShotGunDouble( struct event_args_s *args ); +void EV_FireAxe( struct event_args_s *args ); +void EV_FireAxeSwing( struct event_args_s *args ); +void EV_FireRocket( struct event_args_s *args ); +void EV_FireLightning( struct event_args_s *args ); +void EV_FireSpike( struct event_args_s *args ); +void EV_FireSuperSpike( struct event_args_s *args ); +void EV_FireGrenade( struct event_args_s *args ); +void EV_Gibbed (struct event_args_s *args ); +void EV_Teleport (struct event_args_s *args ); +void EV_Trail (struct event_args_s *args ); +void EV_Explosion (struct event_args_s *args ); +void EV_PlayerPowerup ( struct event_args_s *args ); + +void EV_DMC_DoorGoUp( struct event_args_s *args ); +void EV_DMC_DoorGoDown( struct event_args_s *args ); +void EV_DMC_DoorHitTop( struct event_args_s *args ); +void EV_DMC_DoorHitBottom( struct event_args_s *args ); + +// HLDM +void EV_TrainPitchAdjust( 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/shotgun1.sc", EV_FireShotGunSingle ); + gEngfuncs.pfnHookEvent( "events/shotgun2.sc", EV_FireShotGunDouble ); + gEngfuncs.pfnHookEvent( "events/axe.sc", EV_FireAxe ); + gEngfuncs.pfnHookEvent( "events/axeswing.sc", EV_FireAxeSwing ); + gEngfuncs.pfnHookEvent( "events/rocket.sc", EV_FireRocket ); + gEngfuncs.pfnHookEvent( "events/lightning.sc", EV_FireLightning ); + gEngfuncs.pfnHookEvent( "events/grenade.sc", EV_FireGrenade ); + gEngfuncs.pfnHookEvent( "events/spike.sc", EV_FireSpike ); + gEngfuncs.pfnHookEvent( "events/superspike.sc", EV_FireSuperSpike ); + gEngfuncs.pfnHookEvent( "events/gibs.sc", EV_Gibbed ); + gEngfuncs.pfnHookEvent( "events/teleport.sc", EV_Teleport ); + gEngfuncs.pfnHookEvent( "events/trail.sc", EV_Trail ); + gEngfuncs.pfnHookEvent( "events/explosion.sc", EV_Explosion ); + + gEngfuncs.pfnHookEvent( "events/powerup.sc", EV_PlayerPowerup ); + + gEngfuncs.pfnHookEvent( "events/door/doorgoup.sc", EV_DMC_DoorGoUp ); + gEngfuncs.pfnHookEvent( "events/door/doorgodown.sc", EV_DMC_DoorGoDown ); + gEngfuncs.pfnHookEvent( "events/door/doorhittop.sc", EV_DMC_DoorHitTop ); + gEngfuncs.pfnHookEvent( "events/door/doorhitbottom.sc", EV_DMC_DoorHitBottom ); + + gEngfuncs.pfnHookEvent( "events/train.sc", EV_TrainPitchAdjust ); +} diff --git a/hlsdk/dmc/cl_dll/quake/quake_objects.cpp b/hlsdk/dmc/cl_dll/quake/quake_objects.cpp new file mode 100644 index 00000000..1459f009 --- /dev/null +++ b/hlsdk/dmc/cl_dll/quake/quake_objects.cpp @@ -0,0 +1,81 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#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" + + +extern BEAM *pBeam; +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(); + + pBeam->target = tr.endpos; + pBeam->die = gEngfuncs.GetClientTime() + 0.1; // We keep it alive just a little bit forward in the future, just in case. +} + +/* +===================== +Game_AddObjects + +Add game specific, client-side objects here +===================== +*/ +void Game_AddObjects( void ) +{ + if ( pBeam ) + UpdateBeams(); +} diff --git a/hlsdk/dmc/cl_dll/quake/quake_weapons.cpp b/hlsdk/dmc/cl_dll/quake/quake_weapons.cpp new file mode 100644 index 00000000..a42fdfce --- /dev/null +++ b/hlsdk/dmc/cl_dll/quake/quake_weapons.cpp @@ -0,0 +1,988 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.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 "../hud_iface.h" +#include "../com_weapons.h" +#include "../demo.h" + +#include "quake_gun.h" +#include "../DMC_Teleporters.h" + +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 CBasePlayerWeapon *g_pWpns[ 32 ]; +extern int iCarriedWeapons; +int g_iWaterLevel; +// HLDM Weapon placeholder entities. +CQuakeGun g_QuakeGun; + +extern BEAM *pBeam; +/* +====================== +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 ); +} + +/* +===================== +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 ] = (CBasePlayerWeapon *)pEntity; + } +} + +CQuakeRocket *CQuakeRocket::CreateRocket( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner ) +{ + return NULL; +} + +CQuakeRocket *CQuakeRocket::CreateGrenade( Vector vecOrigin, Vector vecVelocity, CBaseEntity *pOwner ) +{ + return NULL; +} + +CQuakeNail *CQuakeNail::CreateSuperNail( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner ) +{ + return NULL; +} + +CQuakeNail *CQuakeNail::CreateNail( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner ) +{ + return NULL; +} + +void CBasePlayer :: Precache( void ) +{ + m_usShotgunSingle = PRECACHE_EVENT( 1, "events/shotgun1.sc" ); + m_usShotgunDouble = PRECACHE_EVENT( 1, "events/shotgun2.sc" ); + m_usAxe = PRECACHE_EVENT( 1, "events/axe.sc" ); + m_usAxeSwing = PRECACHE_EVENT( 1, "events/axeswing.sc" ); + m_usRocket = PRECACHE_EVENT( 1, "events/rocket.sc" ); + m_usGrenade = PRECACHE_EVENT( 1, "events/grenade.sc" ); + m_usLightning = PRECACHE_EVENT( 1, "events/lightning.sc" ); + m_usSpike = PRECACHE_EVENT( 1, "events/spike.sc" ); + m_usSuperSpike = PRECACHE_EVENT( 1, "events/superspike.sc" ); +} + +/* +===================== +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 ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + fDelay; + + //!!UNDONE -- reload sound goes here !!! + SendWeaponAnim( iAnim ); + + 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 ) +{ + if ( !CanDeploy() ) + return FALSE; + + gEngfuncs.CL_LoadModel( szViewModel, &m_pPlayer->pev->viewmodel ); + + SendWeaponAnim( iAnim ); + + m_bPlayedIdleAnim = FALSE; + + 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 ) +{ + m_pPlayer->pev->weaponanim = iAnim; + + int body = 0; + + HUD_SendWeaponAnim( iAnim, body, 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; + + if ( !m_bPlayedIdleAnim ) + { + m_bPlayedIdleAnim = TRUE; + + if ( m_pPlayer->m_iQuakeWeapon == IT_LIGHTNING ) + PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), m_pPlayer->m_usLightning, 0, (float *)&m_pPlayer->pev->origin, (float *)&m_pPlayer->pev->angles, 0.0, 0.0, 0, 1, 0, 0 ); + } + + 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 ) +{ + if ( m_iQuakeWeapon == IT_LIGHTNING ) + { + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usLightning, 0, (float *)&pev->origin, (float *)&pev->angles, 0.0, 0.0, 0, 1, 0, 0 ); + } + + // 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( ); +} + +BOOL CQuakeGun::Deploy( ) +{ + gEngfuncs.CL_LoadModel( "models/v_axe.mdl", &m_pPlayer->pev->viewmodel ); + strcpy( m_pPlayer->m_szAnimExtention, "onehanded" ); + return TRUE; +} + +int HUD_GetModelIndex( char *modelname ) +{ + int retval = 0; + gEngfuncs.CL_LoadModel( modelname, &retval ); + return retval; +} + +/* +===================== +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 ); +} + +vec3_t previousorigin; + +/* +===================== +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_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 ); + + // Allocate slot(s) for each weapon that we are going to be predicting + HUD_PrepEntity( &g_QuakeGun , &player ); +} + +int Quake_NumForWeaponItem( int quakeitem ) +{ + int retval = 1; + switch ( quakeitem ) + { + default: + case IT_AXE: + retval = 1; + break; + case IT_SHOTGUN: + retval = 2; + break; + case IT_SUPER_SHOTGUN: + retval = 3; + break; + case IT_NAILGUN: + retval = 4; + break; + case IT_SUPER_NAILGUN: + retval = 5; + break; + case IT_GRENADE_LAUNCHER: + retval = 6; + break; + case IT_ROCKET_LAUNCHER: + retval = 7; + break; + + case IT_LIGHTNING: + retval = 8; + break; + } + + return retval; +} + +/* +===================== +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; + CBasePlayerWeapon *pWeapon = NULL; + CBasePlayerWeapon *pCurrent; + weapon_data_t nulldata, *pfrom, *pto; + static int lasthealth; + + memset( &nulldata, 0, sizeof( nulldata ) ); + + HUD_InitClientWeapons(); + + // Get current clock + gpGlobals->time = time; + + // 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 ) + { + case WEAPON_GLOCK: + pWeapon = &g_QuakeGun; + break; + } + + // 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; + + gpGlobals->deathmatch = from->client.iuser4; + + 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_iClip = pfrom->m_iClip; + pCurrent->m_flNextPrimaryAttack = pfrom->m_flNextPrimaryAttack; + pCurrent->m_flNextSecondaryAttack = pfrom->m_flNextSecondaryAttack; + pCurrent->m_flTimeWeaponIdle = pfrom->m_flTimeWeaponIdle; + } + + // 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; + g_iWaterLevel = 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; + player.m_iQuakeWeapon = (int)from->client.fuser1; + iCarriedWeapons = player.m_iQuakeItems = from->client.iuser3; + + player.m_iAmmoShells = from->client.ammo_shells; + player.m_iAmmoCells = from->client.ammo_cells; + player.m_iAmmoRockets = from->client.ammo_rockets; + player.m_iAmmoNails = from->client.ammo_nails; + + player.m_iNailOffset = (int)from->client.fuser2 != 0.0 ? 4.0 : -4.0; + + + + // REally useful for debugging prediction +/* if ( player.m_iQuakeWeapon > 0 ) + { + gEngfuncs.Con_NPrintf( 9, "got qw %i", player.m_iQuakeWeapon ); + char items[33]; + for ( int i = 0; i < 32; i++ ) + { + if ( player.m_iQuakeItems & (1<viewmodel ); + gEngfuncs.Con_NPrintf( 16, "dm == %i", gpGlobals->deathmatch ); + }*/ + + + // Point to current weapon object + if ( from->client.m_iId ) + { + player.m_pActiveItem = g_pWpns[ from->client.m_iId ]; + } + + // Set ammo, but don't change anim + player.W_SetCurrentAmmo( 0 ); + + + + // 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 ( Quake_NumForWeaponItem( player.m_iQuakeWeapon ) != cmd->weaponselect ) + { + player.W_ChangeWeapon( cmd->weaponselect ); + } + } + + if ( player.m_iQuakeWeapon != IT_LIGHTNING && pBeam != NULL ) + { + pBeam->die = 0.0; + pBeam = NULL; + } + // Copy in results of predcition 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; + to->client.iuser3 = player.m_iQuakeItems; + to->client.fuser1 = (float)player.m_iQuakeWeapon; + to->client.fuser2 = (float)player.m_iNailOffset > 0.0 ? 1.0 : 0.0; + + to->client.ammo_shells = player.m_iAmmoShells; + to->client.ammo_cells = player.m_iAmmoCells; + to->client.ammo_rockets = player.m_iAmmoRockets; + to->client.ammo_nails = player.m_iAmmoNails; + + // 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; + } + + pto->m_fInReload = pCurrent->m_fInReload; + pto->m_iClip = pCurrent->m_iClip; + pto->m_flNextPrimaryAttack = pCurrent->m_flNextPrimaryAttack; + pto->m_flNextSecondaryAttack = pCurrent->m_flNextSecondaryAttack; + pto->m_flTimeWeaponIdle = pCurrent->m_flTimeWeaponIdle; + + // 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; + + if ( pto->m_flPumpTime != -9999 ) + { + pto->m_flPumpTime -= cmd->msec / 1000.0; + if ( pto->m_flPumpTime < -0.001 ) + pto->m_flPumpTime = -0.001; + } + + 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; + } + } + + // 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; + } + + // 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; + + // Only run post think stuff for glock for the sample + // implementation + if ( cl_lw && cl_lw->value ) + { + HUD_WeaponsPostThink( from, to, cmd, time, random_seed ); + } + else + { + to->client.fov = g_lastFOV; + } + + Dmc_CheckTeleporters( from, to ); // See if we stepped on a jump pad + + // All games can use FOV state + g_lastFOV = to->client.fov; +} diff --git a/hlsdk/dmc/cl_dll/readme.txt b/hlsdk/dmc/cl_dll/readme.txt new file mode 100644 index 00000000..249205ca --- /dev/null +++ b/hlsdk/dmc/cl_dll/readme.txt @@ -0,0 +1,107 @@ + client dll readme.txt +------------------------- + +This file details the structure of the half-life client dll, and +how it communicates with the half-life game engine. + + +Engine callback functions: + +Drawing functions: + HSPRITE SPR_Load( char *picname ); + Loads a sprite into memory, and returns a handle to it. + + int SPR_Frames( HSPRITE sprite ); + Returns the number of frames stored in the specified sprite. + + int SPR_Height( HSPRITE x, int frame ) + Returns the height, in pixels, of a sprite at the specified frame. + Returns 0 is the frame number or the sprite handle is invalid. + + int SPR_Width( HSPRITE x, int f ) + Returns the width, in pixels, of a sprite at the specified frame. + Returns 0 is the frame number or the sprite handle is invalid. + + int SPR_Set( HSPRITE sprite, int r, int g, int b ); + Prepares a sprite about to be drawn. RBG color values are applied to the sprite at this time. + + + void SPR_Draw( int frame, int x, int y ); + Precondition: SPR_Set has already been called for a sprite. + Draws the currently active sprite to the screen, at position (x,y), where (0,0) is + the top left-hand corner of the screen. + + + void SPR_DrawHoles( int frame, int x, int y ); + Precondition: SPR_Set has already been called for a sprite. + Draws the currently active sprite to the screen. Color index #255 is treated as transparent. + + void SPR_DrawAdditive( int frame, int x, int y ); + Precondition: SPR_Set has already been called for a sprite. + Draws the currently active sprite to the screen, adding it's color values to the background. + + void SPR_EnableScissor( int x, int y, int width, int height ); + Creates a clipping rectangle. No pixels will be drawn outside the specified area. Will + stay in effect until either the next frame, or SPR_DisableScissor is called. + + void SPR_DisableScissor( void ); + Disables the effect of an SPR_EnableScissor call. + + int IsHighRes( void ); + returns 1 if the res mode is 640x480 or higher; 0 otherwise. + + int ScreenWidth( void ); + returns the screen width, in pixels. + + int ScreenHeight( void ); + returns the screen height, in pixels. + +// Sound functions + void PlaySound( char *szSound, int volume ) + plays the sound 'szSound' at the specified volume. Loads the sound if it hasn't been cached. + If it can't find the sound, it displays an error message and plays no sound. + + void PlaySound( int iSound, int volume ) + Precondition: iSound has been precached. + Plays the sound, from the precache list. + + +// Communication functions + void SendClientCmd( char *szCmdString ); + sends a command to the server, just as if the client had typed the szCmdString at the console. + + char *GetPlayerName( int entity_number ); + returns a pointer to a string, that contains the name of the specified client. + Returns NULL if the entity_number is not a client. + + + DECLARE_MESSAGE(), HOOK_MESSAGE() + These two macros bind the message sending between the entity DLL and the client DLL to + the CHud object. + + HOOK_MESSAGE( message_name ) + This is used inside CHud::Init(). It calls into the engine to hook that message + from the incoming message stream. + Precondition: There must be a function of name UserMsg_message_name declared + for CHud. Eg, CHud::UserMsg_Health() must be declared if you want to + use HOOK_MESSAGE( Health ); + + DECLARE_MESSAGE( message_name ) + For each HOOK_MESSAGE you must have an equivalent DECLARE_MESSAGE. This creates + a function which passes the hooked messages into the CHud object. + + + HOOK_COMMAND(), DECLARE_COMMAND() + These two functions declare and hook console commands into the client dll. + + HOOK_COMMAND( char *command, command_name ) + Whenever the user types the 'command' at the console, the function 'command_name' + will be called. + Precondition: There must be a function of the name UserCmd_command_name declared + for CHud. Eg, CHud::UserMsg_ShowScores() must be declared if you want to + use HOOK_COMMAND( "+showscores", ShowScores ); + + DECLARE_COMMAND( command_name ) + For each HOOK_COMMAND you must have an equivelant DECLARE_COMMAND. This creates + a function which passes the hooked commands into the CHud object. + diff --git a/hlsdk/dmc/cl_dll/saytext.cpp b/hlsdk/dmc/cl_dll/saytext.cpp new file mode 100644 index 00000000..03e308b5 --- /dev/null +++ b/hlsdk/dmc/cl_dll/saytext.cpp @@ -0,0 +1,312 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// saytext.cpp +// +// implementation of CHudSayText class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +extern float *GetClientColor( int clientIndex ); +extern hud_player_info_t g_PlayerInfoList[MAX_PLAYERS+1]; + +#define MAX_LINES 5 +#define MAX_CHARS_PER_LINE 256 /* it can be less than this, depending on char size */ + +// allow 20 pixels on either side of the text +#define MAX_LINE_WIDTH ( ScreenWidth - 40 ) +#define LINE_START 10 +static float SCROLL_SPEED = 5; + +static char g_szLineBuffer[ MAX_LINES + 1 ][ MAX_CHARS_PER_LINE ]; +static float *g_pflNameColors[ MAX_LINES + 1 ]; +static int g_iNameLengths[ MAX_LINES + 1 ]; +static float flScrollTime = 0; // the time at which the lines next scroll up + +static int Y_START = 0; +static int line_height = 0; + +DECLARE_MESSAGE( m_SayText, SayText ); + +int CHudSayText :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( SayText ); + + InitHUDData(); + + m_HUD_saytext = gEngfuncs.pfnRegisterVariable( "hud_saytext", "1", 0 ); + m_HUD_saytext_time = gEngfuncs.pfnRegisterVariable( "hud_saytext_time", "5", 0 ); + + return 1; +} + + +void CHudSayText :: InitHUDData( void ) +{ + memset( g_szLineBuffer, 0, sizeof g_szLineBuffer ); + memset( g_pflNameColors, 0, sizeof g_pflNameColors ); + memset( g_iNameLengths, 0, sizeof g_iNameLengths ); + + m_iFlags |= HUD_INTERMISSION; // is always drawn during an intermission +} + +int CHudSayText :: VidInit( void ) +{ + return 1; +} + + +int ScrollTextUp( void ) +{ + ConsolePrint( g_szLineBuffer[0] ); // move the first line into the console buffer + g_szLineBuffer[MAX_LINES][0] = 0; + memmove( g_szLineBuffer[0], g_szLineBuffer[1], sizeof(g_szLineBuffer) - sizeof(g_szLineBuffer[0]) ); // overwrite the first line + memmove( &g_pflNameColors[0], &g_pflNameColors[1], sizeof(g_pflNameColors) - sizeof(g_pflNameColors[0]) ); + memmove( &g_iNameLengths[0], &g_iNameLengths[1], sizeof(g_iNameLengths) - sizeof(g_iNameLengths[0]) ); + g_szLineBuffer[MAX_LINES-1][0] = 0; + + if ( g_szLineBuffer[0][0] == ' ' ) // also scroll up following lines + { + g_szLineBuffer[0][0] = 2; + return 1 + ScrollTextUp(); + } + + return 1; +} + +int CHudSayText :: Draw( float flTime ) +{ + int y = Y_START; + + // make sure the scrolltime is within reasonable bounds, to guard against the clock being reset + flScrollTime = min( flScrollTime, flTime + m_HUD_saytext_time->value ); + + // make sure the scrolltime is within reasonable bounds, to guard against the clock being reset + flScrollTime = min( flScrollTime, flTime + m_HUD_saytext_time->value ); + + if ( flScrollTime <= flTime ) + { + if ( *g_szLineBuffer[0] ) + { + flScrollTime = flTime + m_HUD_saytext_time->value; + // push the console up + ScrollTextUp(); + } + else + { // buffer is empty, just disable drawing of this section + m_iFlags &= ~HUD_ACTIVE; + } + } + + for ( int i = 0; i < MAX_LINES; i++ ) + { + if ( *g_szLineBuffer[i] ) + { + if ( *g_szLineBuffer[i] == 2 && g_pflNameColors[i] ) + { + // it's a saytext string + static char buf[MAX_PLAYER_NAME_LENGTH+32]; + + // draw the first x characters in the player color + strncpy( buf, g_szLineBuffer[i], min(g_iNameLengths[i], MAX_PLAYER_NAME_LENGTH+32) ); + buf[ min(g_iNameLengths[i], MAX_PLAYER_NAME_LENGTH+31) ] = 0; + gEngfuncs.pfnDrawSetTextColor( g_pflNameColors[i][0], g_pflNameColors[i][1], g_pflNameColors[i][2] ); + + int x = DrawConsoleString( LINE_START, y, buf ); + + // color is reset after each string draw + DrawConsoleString( x, y, g_szLineBuffer[i] + g_iNameLengths[i] ); + } + else + { + // normal draw + DrawConsoleString( LINE_START, y, g_szLineBuffer[i] ); + } + } + + y += line_height; + } + + + return 1; +} + +int CHudSayText :: MsgFunc_SayText( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int client_index = READ_BYTE(); // the client who spoke the message + SayTextPrint( READ_STRING(), iSize - 1, client_index ); + + return 1; +} + +void CHudSayText :: SayTextPrint( const char *pszBuf, int iBufSize, int clientIndex ) +{ + // find an empty string slot + int i; + for ( i = 0; i < MAX_LINES; i++ ) + { + if ( ! *g_szLineBuffer[i] ) + break; + } + if ( i == MAX_LINES ) + { + // force scroll buffer up + ScrollTextUp(); + i = MAX_LINES - 1; + } + + g_iNameLengths[i] = 0; + g_pflNameColors[i] = NULL; + + // if it's a say message, search for the players name in the string + if ( *pszBuf == 2 && clientIndex > 0 ) + { + GetPlayerInfo( clientIndex, &g_PlayerInfoList[clientIndex] ); + const char *pName = g_PlayerInfoList[clientIndex].name; + + if ( pName ) + { + const char *nameInString = strstr( pszBuf, pName ); + + if ( nameInString ) + { + g_iNameLengths[i] = strlen( pName ) + (nameInString - pszBuf); + g_pflNameColors[i] = GetClientColor( clientIndex ); + } + } + } + + strncpy( g_szLineBuffer[i], pszBuf, max(iBufSize -1, MAX_CHARS_PER_LINE-1) ); + + // make sure the text fits in one line + EnsureTextFitsInOneLineAndWrapIfHaveTo( i ); + + // Set scroll time + if ( i == 0 ) + { + flScrollTime = gHUD.m_flTime + m_HUD_saytext_time->value; + } + + m_iFlags |= HUD_ACTIVE; + PlaySound( "misc/talk.wav", 1 ); + + if ( ScreenHeight >= 480 ) + Y_START = ScreenHeight - 138; + else + Y_START = ScreenHeight - 70; + Y_START -= (line_height * (MAX_LINES+1)); + +} + +void CHudSayText :: EnsureTextFitsInOneLineAndWrapIfHaveTo( int line ) +{ + int line_width = 0; + GetConsoleStringSize( g_szLineBuffer[line], &line_width, &line_height ); + + if ( (line_width + LINE_START) > MAX_LINE_WIDTH ) + { // string is too long to fit on line + // scan the string until we find what word is too long, and wrap the end of the sentence after the word + int length = LINE_START; + int tmp_len = 0; + char *last_break = NULL; + for ( char *x = g_szLineBuffer[line]; *x != 0; x++ ) + { + // check for a color change, if so skip past it + if ( x[0] == '/' && x[1] == '(' ) + { + x += 2; + // skip forward until past mode specifier + while ( *x != 0 && *x != ')' ) + x++; + + if ( *x != 0 ) + x++; + + if ( *x == 0 ) + break; + } + + char buf[2]; + buf[1] = 0; + + if ( *x == ' ' && x != g_szLineBuffer[line] ) // store each line break, except for the very first character + last_break = x; + + buf[0] = *x; // get the length of the current character + GetConsoleStringSize( buf, &tmp_len, &line_height ); + length += tmp_len; + + if ( length > MAX_LINE_WIDTH ) + { // needs to be broken up + if ( !last_break ) + last_break = x-1; + + x = last_break; + + // find an empty string slot + int j; + do + { + for ( j = 0; j < MAX_LINES; j++ ) + { + if ( ! *g_szLineBuffer[j] ) + break; + } + if ( j == MAX_LINES ) + { + // need to make more room to display text, scroll stuff up then fix the pointers + int linesmoved = ScrollTextUp(); + line -= linesmoved; + last_break = last_break - (sizeof(g_szLineBuffer[0]) * linesmoved); + } + } + while ( j == MAX_LINES ); + + // copy remaining string into next buffer, making sure it starts with a space character + if ( (char)*last_break == (char)' ' ) + { + int linelen = strlen(g_szLineBuffer[j]); + int remaininglen = strlen(last_break); + + if ( (linelen - remaininglen) <= MAX_CHARS_PER_LINE ) + strcat( g_szLineBuffer[j], last_break ); + } + else + { + if ( (strlen(g_szLineBuffer[j]) - strlen(last_break) - 2) < MAX_CHARS_PER_LINE ) + { + strcat( g_szLineBuffer[j], " " ); + strcat( g_szLineBuffer[j], last_break ); + } + } + + *last_break = 0; // cut off the last string + + EnsureTextFitsInOneLineAndWrapIfHaveTo( j ); + break; + } + } + } +} \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/scoreboard.cpp b/hlsdk/dmc/cl_dll/scoreboard.cpp new file mode 100644 index 00000000..adfe64d1 --- /dev/null +++ b/hlsdk/dmc/cl_dll/scoreboard.cpp @@ -0,0 +1,527 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Scoreboard.cpp +// +// implementation of CHudScoreboard class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +DECLARE_COMMAND( m_Scoreboard, ShowScores ); +DECLARE_COMMAND( m_Scoreboard, HideScores ); + +DECLARE_MESSAGE( m_Scoreboard, ScoreInfo ); +DECLARE_MESSAGE( m_Scoreboard, TeamInfo ); +DECLARE_MESSAGE( m_Scoreboard, TeamScore ); + + +int CHudScoreboard :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( ScoreInfo ); + HOOK_MESSAGE( TeamScore ); + HOOK_MESSAGE( TeamInfo ); + + InitHUDData(); + + return 1; +} + + +int CHudScoreboard :: VidInit( void ) +{ + + return 1; +} + +void CHudScoreboard :: InitHUDData( void ) +{ + memset( g_PlayerExtraInfo, 0, sizeof g_PlayerExtraInfo ); + m_iLastKilledBy = 0; + m_fLastKillTime = 0; + m_iPlayerNum = 0; + m_iNumTeams = 0; + + memset( m_TeamInfo, 0, sizeof m_TeamInfo ); + + m_iFlags &= ~HUD_ACTIVE; // starts out inactive + + m_iFlags |= HUD_INTERMISSION; // is always drawn during an intermission +} + +/* The scoreboard +We have a minimum width of 1-320 - we could have the field widths scale with it? +*/ + +// X positions +// relative to the side of the scoreboard +#define NAME_RANGE_MIN 20 +#define NAME_RANGE_MAX 145 +#define KILLS_RANGE_MIN 130 +#define KILLS_RANGE_MAX 170 +#define DIVIDER_POS 180 +#define DEATHS_RANGE_MIN 185 +#define DEATHS_RANGE_MAX 210 +#define PING_RANGE_MIN 245 +#define PING_RANGE_MAX 295 + +#define SCOREBOARD_WIDTH 320 + + +// Y positions +#define ROW_GAP 13 +#define ROW_RANGE_MIN 15 +#define ROW_RANGE_MAX ( ScreenHeight - 50 ) + +int CHudScoreboard :: Draw( float fTime ) +{ + if ( !m_iShowscoresHeld && gHUD.m_Health.m_iHealth > 0 && !gHUD.m_iIntermission ) + return 1; + + + GetAllPlayersInfo(); + + // just sort the list on the fly + // list is sorted first by frags, then by deaths + float list_slot = 0; + int xpos_rel = (ScreenWidth - SCOREBOARD_WIDTH) / 2; + + // print the heading line + int ypos = ROW_RANGE_MIN + (list_slot * ROW_GAP); + int xpos = NAME_RANGE_MIN + xpos_rel; + + if ( !gHUD.m_Teamplay ) + gHUD.DrawHudString( xpos, ypos, NAME_RANGE_MAX + xpos_rel, "Player", 255, 140, 0 ); + else + gHUD.DrawHudString( xpos, ypos, NAME_RANGE_MAX + xpos_rel, "Teams", 255, 140, 0 ); + + gHUD.DrawHudStringReverse( KILLS_RANGE_MAX + xpos_rel, ypos, 0, "kills", 255, 140, 0 ); + gHUD.DrawHudString( DIVIDER_POS + xpos_rel, ypos, ScreenWidth, "/", 255, 140, 0 ); + gHUD.DrawHudString( DEATHS_RANGE_MIN + xpos_rel + 5, ypos, ScreenWidth, "deaths", 255, 140, 0 ); + gHUD.DrawHudString( PING_RANGE_MAX + xpos_rel - 35, ypos, ScreenWidth, "latency", 255, 140, 0 ); + + list_slot += 1.2; + ypos = ROW_RANGE_MIN + (list_slot * ROW_GAP); + xpos = NAME_RANGE_MIN + xpos_rel; + FillRGBA( xpos - 5, ypos, PING_RANGE_MAX - 5, 1, 255, 140, 0, 255); // draw the seperator line + + list_slot += 0.8; + + if ( !gHUD.m_Teamplay ) + { + // it's not teamplay, so just draw a simple player list + DrawPlayers( xpos_rel, list_slot ); + return 1; + } + + // clear out team scores + for ( int i = 1; i <= m_iNumTeams; i++ ) + { + if ( !m_TeamInfo[i].scores_overriden ) + m_TeamInfo[i].frags = m_TeamInfo[i].deaths = 0; + m_TeamInfo[i].ping = m_TeamInfo[i].packetloss = 0; + } + + // recalc the team scores, then draw them + for ( i = 1; i < MAX_PLAYERS; i++ ) + { + if ( m_PlayerInfoList[i].name == NULL ) + continue; // empty player slot, skip + + if ( m_PlayerExtraInfo[i].teamname[0] == 0 ) + continue; // skip over players who are not in a team + + // find what team this player is in + for ( int j = 1; j <= m_iNumTeams; j++ ) + { + if ( !stricmp( m_PlayerExtraInfo[i].teamname, m_TeamInfo[j].name ) ) + break; + } + if ( j > m_iNumTeams ) // player is not in a team, skip to the next guy + continue; + + if ( !m_TeamInfo[j].scores_overriden ) + { + m_TeamInfo[j].frags += m_PlayerExtraInfo[i].frags; + m_TeamInfo[j].deaths += m_PlayerExtraInfo[i].deaths; + } + + m_TeamInfo[j].ping += m_PlayerInfoList[i].ping; + m_TeamInfo[j].packetloss += m_PlayerInfoList[i].packetloss; + + if ( m_PlayerInfoList[i].thisplayer ) + m_TeamInfo[j].ownteam = TRUE; + else + m_TeamInfo[j].ownteam = FALSE; + } + + // find team ping/packetloss averages + for ( i = 1; i <= m_iNumTeams; i++ ) + { + m_TeamInfo[i].already_drawn = FALSE; + + if ( m_TeamInfo[i].players > 0 ) + { + m_TeamInfo[i].ping /= m_TeamInfo[i].players; // use the average ping of all the players in the team as the teams ping + m_TeamInfo[i].packetloss /= m_TeamInfo[i].players; // use the average ping of all the players in the team as the teams ping + } + } + + // Draw the teams + while ( 1 ) + { + int highest_frags = -99999; int lowest_deaths = 99999; + int best_team = 0; + + for ( i = 1; i <= m_iNumTeams; i++ ) + { + if ( m_TeamInfo[i].players < 0 ) + continue; + + if ( !m_TeamInfo[i].already_drawn && m_TeamInfo[i].frags >= highest_frags ) + { + if ( m_TeamInfo[i].frags > highest_frags || m_TeamInfo[i].deaths < lowest_deaths ) + { + best_team = i; + lowest_deaths = m_TeamInfo[i].deaths; + highest_frags = m_TeamInfo[i].frags; + } + } + } + + // draw the best team on the scoreboard + if ( !best_team ) + break; + + // draw out the best team + team_info_t *team_info = &m_TeamInfo[best_team]; + + ypos = ROW_RANGE_MIN + (list_slot * ROW_GAP); + + // check we haven't drawn too far down + if ( ypos > ROW_RANGE_MAX ) // don't draw to close to the lower border + break; + + xpos = NAME_RANGE_MIN + xpos_rel; + int r = 255, g = 225, b = 55; // draw the stuff kinda yellowish + + if ( team_info->ownteam ) // if it is their team, draw the background different color + { + // overlay the background in blue, then draw the score text over it + FillRGBA( NAME_RANGE_MIN + xpos_rel - 5, ypos, PING_RANGE_MAX - 5, ROW_GAP, 0, 0, 255, 70 ); + } + + // draw their name (left to right) + gHUD.DrawHudString( xpos, ypos, NAME_RANGE_MAX + xpos_rel, team_info->name, r, g, b ); + + // draw kills (right to left) + xpos = KILLS_RANGE_MAX + xpos_rel; + gHUD.DrawHudNumberString( xpos, ypos, KILLS_RANGE_MIN + xpos_rel, team_info->frags, r, g, b ); + + // draw divider + xpos = DIVIDER_POS + xpos_rel; + gHUD.DrawHudString( xpos, ypos, xpos + 20, "/", r, g, b ); + + // draw deaths + xpos = DEATHS_RANGE_MAX + xpos_rel; + gHUD.DrawHudNumberString( xpos, ypos, DEATHS_RANGE_MIN + xpos_rel, team_info->deaths, r, g, b ); + + // draw ping + // draw ping & packetloss + static char buf[64]; + sprintf( buf, "%d", team_info->ping ); + xpos = ((PING_RANGE_MAX - PING_RANGE_MIN) / 2) + PING_RANGE_MIN + xpos_rel + 25; + UnpackRGB( r, g, b, RGB_YELLOWISH ); + gHUD.DrawHudStringReverse( xpos, ypos, xpos - 50, buf, r, g, b ); + + /* Packetloss removed on Kelly 'shipping nazi' Bailey's orders + sprintf( buf, " %d", team_info->packetloss ); + gHUD.DrawHudString( xpos, ypos, xpos+50, buf, r, g, b ); + */ + + team_info->already_drawn = TRUE; // set the already_drawn to be TRUE, so this team won't get drawn again + list_slot++; + + // draw all the players that belong to this team, indented slightly + list_slot = DrawPlayers( xpos_rel, list_slot, 10, team_info->name ); + } + + // draw all the players who are not in a team + list_slot += 0.5; + DrawPlayers( xpos_rel, list_slot, 0, "" ); + + return 1; +} + +// returns the ypos where it finishes drawing +int CHudScoreboard :: DrawPlayers( int xpos_rel, float list_slot, int nameoffset, char *team ) +{ + // draw the players, in order, and restricted to team if set + while ( 1 ) + { + // Find the top ranking player + int highest_frags = -99999; int lowest_deaths = 99999; + int best_player = 0; + + for ( int i = 1; i < MAX_PLAYERS; i++ ) + { + if ( m_PlayerInfoList[i].name && m_PlayerExtraInfo[i].frags >= highest_frags ) + { + if ( !(team && stricmp(m_PlayerExtraInfo[i].teamname, team)) ) // make sure it is the specified team + { + extra_player_info_t *pl_info = &m_PlayerExtraInfo[i]; + if ( pl_info->frags > highest_frags || pl_info->deaths < lowest_deaths ) + { + best_player = i; + lowest_deaths = pl_info->deaths; + highest_frags = pl_info->frags; + + } + } + } + } + + if ( !best_player ) + break; + + // draw out the best player + hud_player_info_t *pl_info = &m_PlayerInfoList[best_player]; + + int ypos = ROW_RANGE_MIN + (list_slot * ROW_GAP); + + // check we haven't drawn too far down + if ( ypos > ROW_RANGE_MAX ) // don't draw to close to the lower border + break; + + int xpos = NAME_RANGE_MIN + xpos_rel; + int r = 255, g = 255, b = 255; + if ( best_player == m_iLastKilledBy && m_fLastKillTime && m_fLastKillTime > gHUD.m_flTime ) + { + if ( pl_info->thisplayer ) + { // green is the suicide color? i wish this could do grey... + FillRGBA( NAME_RANGE_MIN + xpos_rel - 5, ypos, PING_RANGE_MAX - 5, ROW_GAP, 80, 155, 0, 70 ); + } + else + { // Highlight the killers name - overlay the background in red, then draw the score text over it + FillRGBA( NAME_RANGE_MIN + xpos_rel - 5, ypos, PING_RANGE_MAX - 5, ROW_GAP, 255, 0, 0, ((float)15 * (float)(m_fLastKillTime - gHUD.m_flTime)) ); + } + } + else if ( pl_info->thisplayer ) // if it is their name, draw it a different color + { + // overlay the background in blue, then draw the score text over it + FillRGBA( NAME_RANGE_MIN + xpos_rel - 5, ypos, PING_RANGE_MAX - 5, ROW_GAP, 0, 0, 255, 70 ); + } + + // draw their name (left to right) + gHUD.DrawHudString( xpos + nameoffset, ypos, NAME_RANGE_MAX + xpos_rel, pl_info->name, r, g, b ); + + // draw kills (right to left) + xpos = KILLS_RANGE_MAX + xpos_rel; + gHUD.DrawHudNumberString( xpos, ypos, KILLS_RANGE_MIN + xpos_rel, m_PlayerExtraInfo[best_player].frags, r, g, b ); + + // draw divider + xpos = DIVIDER_POS + xpos_rel; + gHUD.DrawHudString( xpos, ypos, xpos + 20, "/", r, g, b ); + + // draw deaths + xpos = DEATHS_RANGE_MAX + xpos_rel; + gHUD.DrawHudNumberString( xpos, ypos, DEATHS_RANGE_MIN + xpos_rel, m_PlayerExtraInfo[best_player].deaths, r, g, b ); + + // draw ping & packetloss + static char buf[64]; + sprintf( buf, "%d", m_PlayerInfoList[best_player].ping ); + xpos = ((PING_RANGE_MAX - PING_RANGE_MIN) / 2) + PING_RANGE_MIN + xpos_rel + 25; + gHUD.DrawHudStringReverse( xpos, ypos, xpos - 50, buf, r, g, b ); + + /* Packetloss removed on Kelly 'shipping nazi' Bailey's orders + if ( m_PlayerInfoList[best_player].packetloss >= 63 ) + { + UnpackRGB( r, g, b, RGB_REDISH ); + sprintf( buf, " !!!!" ); + } + else + { + sprintf( buf, " %d", m_PlayerInfoList[best_player].packetloss ); + } + + gHUD.DrawHudString( xpos, ypos, xpos+50, buf, r, g, b ); + */ + + pl_info->name = NULL; // set the name to be NULL, so this client won't get drawn again + list_slot++; + } + + return list_slot; +} + + +void CHudScoreboard :: GetAllPlayersInfo( void ) +{ + for ( int i = 1; i < MAX_PLAYERS; i++ ) + { + GetPlayerInfo( i, &m_PlayerInfoList[i] ); + + if ( m_PlayerInfoList[i].thisplayer ) + m_iPlayerNum = i; // !!!HACK: this should be initialized elsewhere... maybe gotten from the engine + } +} + +int CHudScoreboard :: MsgFunc_ScoreInfo( const char *pszName, int iSize, void *pbuf ) +{ + m_iFlags |= HUD_ACTIVE; + + BEGIN_READ( pbuf, iSize ); + short cl = READ_BYTE(); + short frags = READ_SHORT(); + short deaths = READ_SHORT(); + + if ( cl > 0 && cl <= MAX_PLAYERS ) + { + m_PlayerExtraInfo[cl].frags = frags; + m_PlayerExtraInfo[cl].deaths = deaths; + } + + return 1; +} + +// Message handler for TeamInfo message +// accepts two values: +// byte: client number +// string: client team name +int CHudScoreboard :: MsgFunc_TeamInfo( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + short cl = READ_BYTE(); + + if ( cl > 0 && cl <= MAX_PLAYERS ) + { // set the players team + strncpy( m_PlayerExtraInfo[cl].teamname, READ_STRING(), MAX_TEAM_NAME ); + } + + // rebuild the list of teams + + // clear out player counts from teams + for ( int i = 1; i <= m_iNumTeams; i++ ) + { + m_TeamInfo[i].players = 0; + } + + // rebuild the team list + GetAllPlayersInfo(); + m_iNumTeams = 0; + for ( i = 1; i < MAX_PLAYERS; i++ ) + { + if ( m_PlayerInfoList[i].name == NULL ) + continue; + + if ( m_PlayerExtraInfo[i].teamname[0] == 0 ) + continue; // skip over players who are not in a team + + // is this player in an existing team? + for ( int j = 1; j <= m_iNumTeams; j++ ) + { + if ( m_TeamInfo[j].name[0] == '\0' ) + break; + + if ( !stricmp( m_PlayerExtraInfo[i].teamname, m_TeamInfo[j].name ) ) + break; + } + + if ( j > m_iNumTeams ) + { // they aren't in a listed team, so make a new one + // search through for an empty team slot + for ( int j = 1; j <= m_iNumTeams; j++ ) + { + if ( m_TeamInfo[j].name[0] == '\0' ) + break; + } + m_iNumTeams = max( j, m_iNumTeams ); + + strncpy( m_TeamInfo[j].name, m_PlayerExtraInfo[i].teamname, MAX_TEAM_NAME ); + m_TeamInfo[j].players = 0; + } + + m_TeamInfo[j].players++; + } + + // clear out any empty teams + for ( i = 1; i <= m_iNumTeams; i++ ) + { + if ( m_TeamInfo[i].players < 1 ) + memset( &m_TeamInfo[i], 0, sizeof(team_info_t) ); + } + + 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 CHudScoreboard :: 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_iNumTeams; i++ ) + { + if ( !stricmp( TeamName, m_TeamInfo[i].name ) ) + break; + } + if ( i > m_iNumTeams ) + return 1; + + // use this new score data instead of combined player scores + m_TeamInfo[i].scores_overriden = TRUE; + m_TeamInfo[i].frags = READ_SHORT(); + m_TeamInfo[i].deaths = READ_SHORT(); + + return 1; +} + +void CHudScoreboard :: DeathMsg( int killer, int victim ) +{ + // if we were the one killed, or the world killed us, set the scoreboard to indicate suicide + if ( victim == m_iPlayerNum || killer == 0 ) + { + m_iLastKilledBy = killer ? killer : m_iPlayerNum; + m_fLastKillTime = gHUD.m_flTime + 10; // display who we were killed by for 10 seconds + + if ( killer == m_iPlayerNum ) + m_iLastKilledBy = m_iPlayerNum; + } +} + + + +void CHudScoreboard :: UserCmd_ShowScores( void ) +{ + m_iShowscoresHeld = TRUE; +} + +void CHudScoreboard :: UserCmd_HideScores( void ) +{ + m_iShowscoresHeld = FALSE; +} diff --git a/hlsdk/dmc/cl_dll/status_icons.cpp b/hlsdk/dmc/cl_dll/status_icons.cpp new file mode 100644 index 00000000..4c5d2743 --- /dev/null +++ b/hlsdk/dmc/cl_dll/status_icons.cpp @@ -0,0 +1,150 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// status_icons.cpp +// +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE( m_StatusIcons, StatusIcon ); + +int CHudStatusIcons::Init( void ) +{ + HOOK_MESSAGE( StatusIcon ); + + gHUD.AddHudElem( this ); + + Reset(); + + return 1; +} + +int CHudStatusIcons::VidInit( void ) +{ + + return 1; +} + +void CHudStatusIcons::Reset( void ) +{ + memset( m_IconList, 0, sizeof m_IconList ); + m_iFlags &= ~HUD_ACTIVE; +} + +// Draw status icons along the left-hand side of the screen +int CHudStatusIcons::Draw( float flTime ) +{ + // find starting position to draw from, along right-hand side of screen + int x = 5; + int y = ScreenHeight / 2; + + // loop through icon list, and draw any valid icons drawing up from the middle of screen + for ( int i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( m_IconList[i].spr ) + { + y -= ( m_IconList[i].rc.bottom - m_IconList[i].rc.top ) + 5; + + SPR_Set( m_IconList[i].spr, m_IconList[i].r, m_IconList[i].g, m_IconList[i].b ); + SPR_DrawAdditive( 0, x, y, &m_IconList[i].rc ); + } + } + + return 1; +} + +// Message handler for StatusIcon message +// accepts five values: +// byte : TRUE = ENABLE icon, FALSE = DISABLE icon +// string : the sprite name to display +// byte : red +// byte : green +// byte : blue +int CHudStatusIcons::MsgFunc_StatusIcon( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int ShouldEnable = READ_BYTE(); + char *pszIconName = READ_STRING(); + if ( ShouldEnable ) + { + int r = READ_BYTE(); + int g = READ_BYTE(); + int b = READ_BYTE(); + EnableIcon( pszIconName, r, g, b ); + m_iFlags |= HUD_ACTIVE; + } + else + { + DisableIcon( pszIconName ); + } + + return 1; +} + +// add the icon to the icon list, and set it's drawing color +void CHudStatusIcons::EnableIcon( char *pszIconName, unsigned char red, unsigned char green, unsigned char blue ) +{ + // check to see if the sprite is in the current list + int i; + for ( i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( !stricmp( m_IconList[i].szSpriteName, pszIconName ) ) + break; + } + + if ( i == MAX_ICONSPRITES ) + { + // icon not in list, so find an empty slot to add to + for ( i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( !m_IconList[i].spr ) + break; + } + } + + // if we've run out of space in the list, overwrite the first icon + if ( i == MAX_ICONSPRITES ) + { + i = 0; + } + + // Load the sprite and add it to the list + // the sprite must be listed in hud.txt + int spr_index = gHUD.GetSpriteIndex( pszIconName ); + m_IconList[i].spr = gHUD.GetSprite( spr_index ); + m_IconList[i].rc = gHUD.GetSpriteRect( spr_index ); + m_IconList[i].r = red; + m_IconList[i].g = green; + m_IconList[i].b = blue; + strcpy( m_IconList[i].szSpriteName, pszIconName ); +} + +void CHudStatusIcons::DisableIcon( char *pszIconName ) +{ + // find the sprite is in the current list + for ( int i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( !stricmp( m_IconList[i].szSpriteName, pszIconName ) ) + { + // clear the item from the list + memset( &m_IconList[i], 0, sizeof icon_sprite_t ); + return; + } + } +} diff --git a/hlsdk/dmc/cl_dll/statusbar.cpp b/hlsdk/dmc/cl_dll/statusbar.cpp new file mode 100644 index 00000000..43ef0612 --- /dev/null +++ b/hlsdk/dmc/cl_dll/statusbar.cpp @@ -0,0 +1,262 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// statusbar.cpp +// +// generic text status bar, set by game dll +// runs across bottom of screen +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +DECLARE_MESSAGE( m_StatusBar, StatusText ); +DECLARE_MESSAGE( m_StatusBar, StatusValue ); + +#define STATUSBAR_ID_LINE 1 + +extern int GetTeamIndex( int clientIndex ); +int g_iNameColors; + +int CHudStatusBar :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( StatusText ); + HOOK_MESSAGE( StatusValue ); + + Reset(); + + return 1; +} + +int CHudStatusBar :: VidInit( void ) +{ + // Load sprites here + m_iArmorSpriteIndex = gHUD.GetSpriteIndex( "armor_bar" ); + m_hArmor = gHUD.GetSprite( m_iArmorSpriteIndex ); + + m_iHealthSpriteIndex = gHUD.GetSpriteIndex( "health_bar" ); + m_hHealth = gHUD.GetSprite( m_iHealthSpriteIndex ); + + return 1; +} + +void CHudStatusBar :: Reset( void ) +{ + m_iFlags &= ~HUD_ACTIVE; // start out inactive + for ( int i = 0; i < MAX_STATUSBAR_LINES; i++ ) + m_szStatusText[i][0] = 0; + memset( m_iStatusValues, 0, sizeof m_iStatusValues ); + + m_iStatusValues[0] = 1; // 0 is the special index, which always returns true +} + +void CHudStatusBar :: ParseStatusString( int line_num ) +{ + int indexval; + + indexval = m_iStatusValues[ 1 ]; + + GetPlayerInfo( indexval, &g_PlayerInfoList[indexval] ); + + if ( g_PlayerInfoList[indexval].name != NULL ) + { + strncpy( m_szName[line_num], g_PlayerInfoList[indexval].name, MAX_PLAYER_NAME_LENGTH ); + } + else + { + strncpy( m_szName[line_num], "******", MAX_PLAYER_NAME_LENGTH ); + } + + g_iNameColors = GetTeamIndex( indexval ); + + indexval = m_iStatusValues[ 2 ]; + sprintf( m_szHealth[ line_num ], ":%d", indexval ); + + indexval = m_iStatusValues[ 3 ]; + sprintf( m_szArmor[ line_num ], ":%d", indexval ); + + m_iTeamMate[ line_num ] = m_iStatusValues[ 5 ]; +} + +int CHudStatusBar :: Draw( float fTime ) +{ + int r , g, b, a, name_r, name_g, name_b; + + if ( m_bReparseString ) + { + for ( int i = 0; i < MAX_STATUSBAR_LINES; i++ ) + ParseStatusString( i ); + m_bReparseString = FALSE; + } + + //Not Watching anyone + if ( m_iStatusValues[ 1 ] == 0 ) + { + m_iFlags &= ~HUD_ACTIVE; + return 1; + } + + // Draw the status bar lines + for ( int i = 0; i < MAX_STATUSBAR_LINES; i++ ) + { + int TextHeight = 0; + int TotalTextWidth = 0; + + //Ugly way to get + if ( m_iTeamMate[i] ) + { + TotalTextWidth += gHUD.ReturnStringPixelLength ( m_szName[i] ); + TotalTextWidth += gHUD.ReturnStringPixelLength ( m_szHealth[i] ); + TotalTextWidth += gHUD.ReturnStringPixelLength ( m_szArmor[i] ); + TotalTextWidth += 48; + TextHeight = gHUD.m_scrinfo.iCharHeight; + } + else + TotalTextWidth += gHUD.ReturnStringPixelLength ( m_szName[i] ); + + TextHeight = gHUD.m_scrinfo.iCharHeight; + + if ( g_iNameColors == 1 ) + { + name_r = 255; + name_g = 50; + name_b = 50; + } + else if ( g_iNameColors == 2 ) + { + name_r = 50; + name_g = 50; + name_b = 255; + } + else + name_r = name_g = name_b = 255; + + int Y_START; + if ( ScreenHeight >= 480 ) + Y_START = ScreenHeight - 55; + else + Y_START = ScreenHeight - 45; + + int x = gHUD.m_Ammo.m_iNumberXPosition; + int y = Y_START; // = ( ScreenHeight / 2 ) + ( TextHeight * 3 ); + + int x_offset; + a = 200; + + UnpackRGB( r, g, b, RGB_NORMAL); + ScaleColors( r, g, b, a ); + ScaleColors( name_r, name_g, name_b, 125 ); + + //Draw the name First + gHUD.DrawHudString( x, y, 1024, m_szName[i], name_r, name_g, name_b ); + + if ( !m_iTeamMate[i] ) + continue; + + //Get the length in pixels for the name + x_offset = gHUD.ReturnStringPixelLength ( m_szName[i] ); + + //Add the offset + x += ( x_offset + 8 ); + + //Now draw the Sprite for the health + SPR_Set( m_hHealth, r, g, b ); + SPR_DrawHoles( 0, x , y, &gHUD.GetSpriteRect( m_iHealthSpriteIndex ) ); + + //Add the sprite width size + x += 16; + + //Draw the health value ( x + offset for the name lenght + width of the sprite ) + gHUD.DrawHudString( x, y, 1024, m_szHealth[i], name_r, name_g, name_b ); + + //Get the length in pixels for the health + x_offset = gHUD.ReturnStringPixelLength ( m_szHealth[i] ); + + //Add the offset + x += ( x_offset + 8 ); + + //Now draw the Sprite for the Armor + SPR_Set( m_hArmor, r, g, b ); + SPR_DrawHoles( 0, x, y, &gHUD.GetSpriteRect( m_iArmorSpriteIndex ) ); + + x += 16; + + //Draw the armor value ( x + offset for the name lenght + width of the sprite ) + gHUD.DrawHudString( x, y, 1024, m_szArmor[i], name_r, name_g, name_b ); + } + + return 1; +} + +// Message handler for StatusText message +// accepts two values: +// byte: line number of status bar text +// string: status bar text +// this string describes how the status bar should be drawn +// a semi-regular expression: +// ( slotnum ([a..z] [%pX] [%iX])*)* +// where slotnum is an index into the Value table (see below) +// if slotnum is 0, the string is always drawn +// if StatusValue[slotnum] != 0, the following string is drawn, upto the next newline - otherwise the text is skipped upto next newline +// %pX, where X is an integer, will substitute a player name here, getting the player index from StatusValue[X] +// %iX, where X is an integer, will substitute a number here, getting the number from StatusValue[X] +int CHudStatusBar :: MsgFunc_StatusText( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int line = READ_BYTE(); + + if ( line < 0 || line >= MAX_STATUSBAR_LINES ) + return 1; + + strncpy( m_szStatusText[line], READ_STRING(), MAX_STATUSTEXT_LENGTH ); + m_szStatusText[line][MAX_STATUSTEXT_LENGTH-1] = 0; // ensure it's null terminated ( strncpy() won't null terminate if read string too long) + + if ( m_szStatusText[0] == 0 ) + m_iFlags &= ~HUD_ACTIVE; + else + m_iFlags |= HUD_ACTIVE; // we have status text, so turn on the status bar + + m_bReparseString = TRUE; + + return 1; +} + +// Message handler for StatusText message +// accepts two values: +// byte: index into the status value array +// short: value to store +int CHudStatusBar :: MsgFunc_StatusValue( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int index = READ_BYTE(); + if ( index < 1 || index >= MAX_STATUSBAR_VALUES ) + return 1; // index out of range + + m_iStatusValues[index] = READ_SHORT(); + + m_iFlags |= HUD_ACTIVE; + + m_bReparseString = TRUE; + + return 1; +} \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/studio_util.cpp b/hlsdk/dmc/cl_dll/studio_util.cpp new file mode 100644 index 00000000..df5fc4bf --- /dev/null +++ b/hlsdk/dmc/cl_dll/studio_util.cpp @@ -0,0 +1,251 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "com_model.h" +#include "studio_util.h" + +/* +==================== +AngleMatrix + +==================== +*/ +void AngleMatrix (const float *angles, float (*matrix)[4] ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + 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); + + // matrix = (YAW * PITCH) * ROLL + matrix[0][0] = cp*cy; + matrix[1][0] = cp*sy; + matrix[2][0] = -sp; + matrix[0][1] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[2][1] = sr*cp; + matrix[0][2] = (cr*sp*cy+-sr*-sy); + matrix[1][2] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.0; + matrix[1][3] = 0.0; + matrix[2][3] = 0.0; +} + +/* +==================== +VectorCompare + +==================== +*/ +int VectorCompare (const float *v1, const float *v2) +{ + int i; + + for (i=0 ; i<3 ; i++) + if (v1[i] != v2[i]) + return 0; + + return 1; +} + +/* +==================== +CrossProduct + +==================== +*/ +void CrossProduct (const float *v1, const float *v2, float *cross) +{ + cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; + cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; + cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} + +/* +==================== +VectorTransform + +==================== +*/ +void VectorTransform (const float *in1, float in2[3][4], float *out) +{ + out[0] = DotProduct(in1, in2[0]) + in2[0][3]; + out[1] = DotProduct(in1, in2[1]) + in2[1][3]; + out[2] = DotProduct(in1, in2[2]) + in2[2][3]; +} + +/* +================ +ConcatTransforms + +================ +*/ +void ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] + + in1[0][2] * in2[2][3] + in1[0][3]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] + + in1[1][2] * in2[2][3] + in1[1][3]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; + out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + + in1[2][2] * in2[2][3] + in1[2][3]; +} + +// angles index are not the same as ROLL, PITCH, YAW + +/* +==================== +AngleQuaternion + +==================== +*/ +void AngleQuaternion( float *angles, vec4_t quaternion ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + // FIXME: rescale the inputs to 1/2 angle + angle = angles[2] * 0.5; + sy = sin(angle); + cy = cos(angle); + angle = angles[1] * 0.5; + sp = sin(angle); + cp = cos(angle); + angle = angles[0] * 0.5; + sr = sin(angle); + cr = cos(angle); + + quaternion[0] = sr*cp*cy-cr*sp*sy; // X + quaternion[1] = cr*sp*cy+sr*cp*sy; // Y + quaternion[2] = cr*cp*sy-sr*sp*cy; // Z + quaternion[3] = cr*cp*cy+sr*sp*sy; // W +} + +/* +==================== +QuaternionSlerp + +==================== +*/ +void QuaternionSlerp( vec4_t p, vec4_t q, float t, vec4_t qt ) +{ + int i; + float omega, cosom, sinom, sclp, sclq; + + // decide if one of the quaternions is backwards + float a = 0; + float b = 0; + + for (i = 0; i < 4; i++) + { + a += (p[i]-q[i])*(p[i]-q[i]); + b += (p[i]+q[i])*(p[i]+q[i]); + } + if (a > b) + { + for (i = 0; i < 4; i++) + { + q[i] = -q[i]; + } + } + + cosom = p[0]*q[0] + p[1]*q[1] + p[2]*q[2] + p[3]*q[3]; + + if ((1.0 + cosom) > 0.000001) + { + if ((1.0 - cosom) > 0.000001) + { + omega = acos( cosom ); + sinom = sin( omega ); + sclp = sin( (1.0 - t)*omega) / sinom; + sclq = sin( t*omega ) / sinom; + } + else + { + sclp = 1.0 - t; + sclq = t; + } + for (i = 0; i < 4; i++) { + qt[i] = sclp * p[i] + sclq * q[i]; + } + } + else + { + qt[0] = -q[1]; + qt[1] = q[0]; + qt[2] = -q[3]; + qt[3] = q[2]; + sclp = sin( (1.0 - t) * (0.5 * M_PI)); + sclq = sin( t * (0.5 * M_PI)); + for (i = 0; i < 3; i++) + { + qt[i] = sclp * p[i] + sclq * qt[i]; + } + } +} + +/* +==================== +QuaternionMatrix + +==================== +*/ +void QuaternionMatrix( vec4_t quaternion, float (*matrix)[4] ) +{ + matrix[0][0] = 1.0 - 2.0 * quaternion[1] * quaternion[1] - 2.0 * quaternion[2] * quaternion[2]; + matrix[1][0] = 2.0 * quaternion[0] * quaternion[1] + 2.0 * quaternion[3] * quaternion[2]; + matrix[2][0] = 2.0 * quaternion[0] * quaternion[2] - 2.0 * quaternion[3] * quaternion[1]; + + matrix[0][1] = 2.0 * quaternion[0] * quaternion[1] - 2.0 * quaternion[3] * quaternion[2]; + matrix[1][1] = 1.0 - 2.0 * quaternion[0] * quaternion[0] - 2.0 * quaternion[2] * quaternion[2]; + matrix[2][1] = 2.0 * quaternion[1] * quaternion[2] + 2.0 * quaternion[3] * quaternion[0]; + + matrix[0][2] = 2.0 * quaternion[0] * quaternion[2] + 2.0 * quaternion[3] * quaternion[1]; + matrix[1][2] = 2.0 * quaternion[1] * quaternion[2] - 2.0 * quaternion[3] * quaternion[0]; + matrix[2][2] = 1.0 - 2.0 * quaternion[0] * quaternion[0] - 2.0 * quaternion[1] * quaternion[1]; +} + +/* +==================== +MatrixCopy + +==================== +*/ +void MatrixCopy( float in[3][4], float out[3][4] ) +{ + memcpy( out, in, sizeof( float ) * 3 * 4 ); +} \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/studio_util.h b/hlsdk/dmc/cl_dll/studio_util.h new file mode 100644 index 00000000..aa8dcf6b --- /dev/null +++ b/hlsdk/dmc/cl_dll/studio_util.h @@ -0,0 +1,40 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( STUDIO_UTIL_H ) +#define STUDIO_UTIL_H +#if defined( WIN32 ) +#pragma once +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +#ifndef PITCH +// MOVEMENT INFO +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 +#endif + +#define FDotProduct( a, b ) (fabs((a[0])*(b[0])) + fabs((a[1])*(b[1])) + fabs((a[2])*(b[2]))) + +void AngleMatrix (const float *angles, float (*matrix)[4] ); +int VectorCompare (const float *v1, const float *v2); +void CrossProduct (const float *v1, const float *v2, float *cross); +void VectorTransform (const float *in1, float in2[3][4], float *out); +void ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]); +void MatrixCopy( float in[3][4], float out[3][4] ); +void QuaternionMatrix( vec4_t quaternion, float (*matrix)[4] ); +void QuaternionSlerp( vec4_t p, vec4_t q, float t, vec4_t qt ); +void AngleQuaternion( float *angles, vec4_t quaternion ); + +#endif // STUDIO_UTIL_H \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/text_message.cpp b/hlsdk/dmc/cl_dll/text_message.cpp new file mode 100644 index 00000000..fea9791f --- /dev/null +++ b/hlsdk/dmc/cl_dll/text_message.cpp @@ -0,0 +1,207 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// text_message.cpp +// +// implementation of CHudTextMessage class +// +// this class routes messages through titles.txt for localisation +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE( m_TextMessage, TextMsg ); + +int CHudTextMessage::Init(void) +{ + HOOK_MESSAGE( TextMsg ); + + gHUD.AddHudElem( this ); + + Reset(); + + return 1; +}; + +// Searches through the string for any msg names (indicated by a '#') +// any found are looked up in titles.txt and the new message substituted +// the new value is pushed into dst_buffer +char *CHudTextMessage::LocaliseTextString( const char *msg, char *dst_buffer, int buffer_size ) +{ + char *dst = dst_buffer; + for ( char *src = (char*)msg; *src != 0 && buffer_size > 0; buffer_size-- ) + { + if ( *src == '#' ) + { + // cut msg name out of string + static char word_buf[255]; + char *wdst = word_buf, *word_start = src; + for ( ++src ; (*src >= 'A' && *src <= 'z') || (*src >= '0' && *src <= '9'); wdst++, src++ ) + { + *wdst = *src; + } + *wdst = 0; + + // lookup msg name in titles.txt + client_textmessage_t *clmsg = TextMessageGet( word_buf ); + if ( !clmsg || !(clmsg->pMessage) ) + { + src = word_start; + *dst = *src; + dst++, src++; + continue; + } + + // copy string into message over the msg name + for ( char *wsrc = (char*)clmsg->pMessage; *wsrc != 0; wsrc++, dst++ ) + { + *dst = *wsrc; + } + *dst = 0; + } + else + { + *dst = *src; + dst++, src++; + *dst = 0; + } + } + + dst_buffer[buffer_size-1] = 0; // ensure null termination + return dst_buffer; +} + +// As above, but with a local static buffer +char *CHudTextMessage::BufferedLocaliseTextString( const char *msg ) +{ + static char dst_buffer[1024]; + return LocaliseTextString( msg, dst_buffer, 1024 ); +} + +// Simplified version of LocaliseTextString; assumes string is only one word +char *CHudTextMessage::LookupString( const char *msg, int *msg_dest ) +{ + if ( !msg ) + return ""; + + // '#' character indicates this is a reference to a string in titles.txt, and not the string itself + if ( msg[0] == '#' ) + { + // this is a message name, so look up the real message + client_textmessage_t *clmsg = TextMessageGet( msg+1 ); + + if ( !clmsg || !(clmsg->pMessage) ) + return (char*)msg; // lookup failed, so return the original string + + if ( msg_dest ) + { + // check to see if titles.txt info overrides msg destination + // if clmsg->effect is less than 0, then clmsg->effect holds -1 * message_destination + if ( clmsg->effect < 0 ) // + *msg_dest = -clmsg->effect; + } + + return (char*)clmsg->pMessage; + } + else + { // nothing special about this message, so just return the same string + return (char*)msg; + } +} + +void StripEndNewlineFromString( char *str ) +{ + int s = strlen( str ) - 1; + if ( str[s] == '\n' || str[s] == '\r' ) + str[s] = 0; +} + +// converts all '\r' characters to '\n', so that the engine can deal with the properly +// returns a pointer to str +char* ConvertCRtoNL( char *str ) +{ + for ( char *ch = str; *ch != 0; ch++ ) + if ( *ch == '\r' ) + *ch = '\n'; + return str; +} + +// Message handler for text messages +// displays a string, looking them up from the titles.txt file, which can be localised +// parameters: +// byte: message direction ( HUD_PRINTCONSOLE, HUD_PRINTNOTIFY, HUD_PRINTCENTER, HUD_PRINTTALK ) +// string: message +// optional parameters: +// string: message parameter 1 +// string: message parameter 2 +// string: message parameter 3 +// string: message parameter 4 +// any string that starts with the character '#' is a message name, and is used to look up the real message in titles.txt +// the next (optional) one to four strings are parameters for that string (which can also be message names if they begin with '#') +int CHudTextMessage::MsgFunc_TextMsg( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int msg_dest = READ_BYTE(); + + static char szBuf[6][128]; + char *msg_text = LookupString( READ_STRING(), &msg_dest ); + msg_text = strcpy( szBuf[0], msg_text ); + + // keep reading strings and using C format strings for subsituting the strings into the localised text string + char *sstr1 = LookupString( READ_STRING() ); + sstr1 = strcpy( szBuf[1], sstr1 ); + StripEndNewlineFromString( sstr1 ); // these strings are meant for subsitution into the main strings, so cull the automatic end newlines + char *sstr2 = LookupString( READ_STRING() ); + sstr2 = strcpy( szBuf[2], sstr2 ); + StripEndNewlineFromString( sstr2 ); + char *sstr3 = LookupString( READ_STRING() ); + sstr3 = strcpy( szBuf[3], sstr3 ); + StripEndNewlineFromString( sstr3 ); + char *sstr4 = LookupString( READ_STRING() ); + sstr4 = strcpy( szBuf[4], sstr4 ); + StripEndNewlineFromString( sstr4 ); + char *psz = szBuf[5]; + + switch ( msg_dest ) + { + case HUD_PRINTCENTER: + sprintf( psz, msg_text, sstr1, sstr2, sstr3, sstr4 ); + CenterPrint( ConvertCRtoNL( psz ) ); + break; + + case HUD_PRINTNOTIFY: + psz[0] = 1; // mark this message to go into the notify buffer + sprintf( psz+1, msg_text, sstr1, sstr2, sstr3, sstr4 ); + ConsolePrint( ConvertCRtoNL( psz ) ); + break; + + case HUD_PRINTTALK: + sprintf( psz, msg_text, sstr1, sstr2, sstr3, sstr4 ); + gHUD.m_SayText.SayTextPrint( ConvertCRtoNL( psz ), 128 ); + break; + + case HUD_PRINTCONSOLE: + sprintf( psz, msg_text, sstr1, sstr2, sstr3, sstr4 ); + ConsolePrint( ConvertCRtoNL( psz ) ); + break; + } + + return 1; +} diff --git a/hlsdk/dmc/cl_dll/train.cpp b/hlsdk/dmc/cl_dll/train.cpp new file mode 100644 index 00000000..0286c689 --- /dev/null +++ b/hlsdk/dmc/cl_dll/train.cpp @@ -0,0 +1,85 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Train.cpp +// +// implementation of CHudAmmo class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE(m_Train, Train ) + + +int CHudTrain::Init(void) +{ + HOOK_MESSAGE( Train ); + + m_iPos = 0; + m_iFlags = 0; + gHUD.AddHudElem(this); + + return 1; +}; + +int CHudTrain::VidInit(void) +{ + m_hSprite = 0; + + return 1; +}; + +int CHudTrain::Draw(float fTime) +{ + if ( !m_hSprite ) + m_hSprite = LoadSprite("sprites/%d_train.spr"); + + if (m_iPos) + { + int r, g, b, x, y; + + UnpackRGB(r,g,b, RGB_YELLOWISH); + SPR_Set(m_hSprite, r, g, b ); + + // This should show up to the right and part way up the armor number + y = ScreenHeight - SPR_Height(m_hSprite,0) - gHUD.m_iFontHeight; + x = ScreenWidth/3 + SPR_Width(m_hSprite,0)/4; + + SPR_DrawAdditive( m_iPos - 1, x, y, NULL); + + } + + return 1; +} + + +int CHudTrain::MsgFunc_Train(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + // update Train data + m_iPos = READ_BYTE(); + + if (m_iPos) + m_iFlags |= HUD_ACTIVE; + else + m_iFlags &= ~HUD_ACTIVE; + + return 1; +} diff --git a/hlsdk/dmc/cl_dll/tri.cpp b/hlsdk/dmc/cl_dll/tri.cpp new file mode 100644 index 00000000..1f15f93b --- /dev/null +++ b/hlsdk/dmc/cl_dll/tri.cpp @@ -0,0 +1,131 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// Triangle rendering, if any + +#include "hud.h" +#include "cl_util.h" + +// Triangle rendering apis are in gEngfuncs.pTriAPI + +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "triangleapi.h" + +#define DLLEXPORT __declspec( dllexport ) + +extern "C" +{ + void DLLEXPORT HUD_DrawNormalTriangles( void ); + void DLLEXPORT HUD_DrawTransparentTriangles( void ); +}; + +extern float g_iFogColor[4]; +extern float g_iStartDist; +extern float g_iEndDist; +extern int g_iWaterLevel; + +//#define TEST_IT +#if defined( TEST_IT ) + +/* +================= +Draw_Triangles + +Example routine. Draws a sprite offset from the player origin. +================= +*/ +void Draw_Triangles( void ) +{ + cl_entity_t *player; + vec3_t org; + + // Load it up with some bogus data + player = gEngfuncs.GetLocalPlayer(); + if ( !player ) + return; + + org = player->origin; + + org.x += 50; + org.y += 50; + + if (gHUD.m_hsprCursor == 0) + { + char sz[256]; + sprintf( sz, "sprites/cursor.spr" ); + gHUD.m_hsprCursor = SPR_Load( sz ); + } + + 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 ); +} + +#endif + +//Render fog ( duh ). +void RenderFog ( void ) +{ + //Not in water and we want fog. + bool bFog = g_iWaterLevel < 2 && g_iStartDist >= 0 && g_iEndDist >= 0; + gEngfuncs.pTriAPI->Fog ( g_iFogColor, g_iStartDist, g_iEndDist, bFog ); +} + + +/* +================= +HUD_DrawNormalTriangles + +Non-transparent triangles-- add them here +================= +*/ +void DLLEXPORT HUD_DrawNormalTriangles( void ) +{ + gHUD.m_Spectator.DrawOverview(); +} + +/* +================= +HUD_DrawTransparentTriangles + +Render any triangles with transparent rendermode needs here +================= +*/ +void DLLEXPORT HUD_DrawTransparentTriangles( void ) +{ + + RenderFog(); +} diff --git a/hlsdk/dmc/cl_dll/util.cpp b/hlsdk/dmc/cl_dll/util.cpp new file mode 100644 index 00000000..43bfbd4d --- /dev/null +++ b/hlsdk/dmc/cl_dll/util.cpp @@ -0,0 +1,134 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// util.cpp +// +// implementation of class-less helper functions +// + +#include "STDIO.H" +#include "STDLIB.H" +#include "MATH.H" + +#include "hud.h" +#include "cl_util.h" +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +vec3_t vec3_origin( 0, 0, 0 ); + +double sqrt(double x); + +float Length(const float *v) +{ + int i; + float length; + + length = 0; + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = sqrt (length); // FIXME + + return length; +} + +void VectorAngles( 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; +} + +float VectorNormalize (float *v) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; + +} + +void VectorInverse ( float *v ) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void VectorScale (const float *in, float scale, float *out) +{ + out[0] = in[0]*scale; + out[1] = in[1]*scale; + out[2] = in[2]*scale; +} + +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]; +} + + +HSPRITE LoadSprite(const char *pszName) +{ + int i; + char sz[256]; + + if (ScreenWidth < 640) + i = 320; + else + i = 640; + + sprintf(sz, pszName, i); + + return SPR_Load(sz); +} + diff --git a/hlsdk/dmc/cl_dll/util.h b/hlsdk/dmc/cl_dll/util.h new file mode 100644 index 00000000..2a7ff204 --- /dev/null +++ b/hlsdk/dmc/cl_dll/util.h @@ -0,0 +1,152 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// util.h +// + +#include "cvardef.h" + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +// Macros to hook function calls into the HUD object +#define HOOK_MESSAGE(x) gEngfuncs.pfnHookUserMsg(#x, __MsgFunc_##x ); + +#define DECLARE_MESSAGE(y, x) int __MsgFunc_##x(const char *pszName, int iSize, void *pbuf) \ + { \ + return gHUD.##y.MsgFunc_##x(pszName, iSize, pbuf ); \ + } + + +#define HOOK_COMMAND(x, y) gEngfuncs.pfnAddCommand( x, __CmdFunc_##y ); +#define DECLARE_COMMAND(y, x) void __CmdFunc_##x( void ) \ + { \ + gHUD.##y.UserCmd_##x( ); \ + } + +inline float CVAR_GET_FLOAT( const char *x ) { return gEngfuncs.pfnGetCvarFloat( (char*)x ); } +inline char* CVAR_GET_STRING( const char *x ) { return gEngfuncs.pfnGetCvarString( (char*)x ); } +inline struct cvar_s *CVAR_CREATE( const char *cv, const char *val, const int flags ) { return gEngfuncs.pfnRegisterVariable( (char*)cv, (char*)val, flags ); } + +#define SPR_Load (*gEngfuncs.pfnSPR_Load) +#define SPR_Set (*gEngfuncs.pfnSPR_Set) +#define SPR_Frames (*gEngfuncs.pfnSPR_Frames) +#define SPR_GetList (*gEngfuncs.pfnSPR_GetList) + +// SPR_Draw draws a the current sprite as solid +#define SPR_Draw (*gEngfuncs.pfnSPR_Draw) +// SPR_DrawHoles draws the current sprites, with color index255 not drawn (transparent) +#define SPR_DrawHoles (*gEngfuncs.pfnSPR_DrawHoles) +// SPR_DrawAdditive adds the sprites RGB values to the background (additive transulency) +#define SPR_DrawAdditive (*gEngfuncs.pfnSPR_DrawAdditive) + +// SPR_EnableScissor sets a clipping rect for HUD sprites. (0,0) is the top-left hand corner of the screen. +#define SPR_EnableScissor (*gEngfuncs.pfnSPR_EnableScissor) +// SPR_DisableScissor disables the clipping rect +#define SPR_DisableScissor (*gEngfuncs.pfnSPR_DisableScissor) +// +#define FillRGBA (*gEngfuncs.pfnFillRGBA) + + +// ScreenHeight returns the height of the screen, in pixels +#define ScreenHeight (gHUD.m_scrinfo.iHeight) +// ScreenWidth returns the width of the screen, in pixels +#define ScreenWidth (gHUD.m_scrinfo.iWidth) + +#define GetScreenInfo (*gEngfuncs.pfnGetScreenInfo) +#define ServerCmd (*gEngfuncs.pfnServerCmd) +#define ClientCmd (*gEngfuncs.pfnClientCmd) +#define SetCrosshair (*gEngfuncs.pfnSetCrosshair) +#define AngleVectors (*gEngfuncs.pfnAngleVectors) + + +// Gets the height & width of a sprite, at the specified frame +inline int SPR_Height( HSPRITE x, int f ) { return gEngfuncs.pfnSPR_Height(x, f); } +inline int SPR_Width( HSPRITE x, int f ) { return gEngfuncs.pfnSPR_Width(x, f); } + +inline client_textmessage_t *TextMessageGet( const char *pName ) { return gEngfuncs.pfnTextMessageGet( pName ); } +inline int TextMessageDrawChar( int x, int y, int number, int r, int g, int b ) +{ + return gEngfuncs.pfnDrawCharacter( x, y, number, r, g, b ); +} + +inline int DrawConsoleString( int x, int y, const char *string ) +{ + return gEngfuncs.pfnDrawConsoleString( x, y, (char*) string ); +} + +inline void GetConsoleStringSize( const char *string, int *width, int *height ) +{ + gEngfuncs.pfnDrawConsoleStringLen( string, width, height ); +} + +inline int ConsoleStringLen( const char *string ) +{ + int _width, _height; + GetConsoleStringSize( string, &_width, &_height ); + return _width; +} + +inline void ConsolePrint( const char *string ) +{ + gEngfuncs.pfnConsolePrint( string ); +} + +inline void CenterPrint( const char *string ) +{ + gEngfuncs.pfnCenterPrint( string ); +} + +// returns the players name of entity no. +#define GetPlayerInfo (*gEngfuncs.pfnGetPlayerInfo) + +// sound functions +inline void PlaySound( char *szSound, float vol ) { gEngfuncs.pfnPlaySoundByName( szSound, vol ); } +inline void PlaySound( int iSound, float vol ) { gEngfuncs.pfnPlaySoundByIndex( iSound, vol ); } + +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#define fabs(x) ((x) > 0 ? (x) : 0 - (x)) + +void ScaleColors( int &r, int &g, int &b, int a ); + +#define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]) +#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 VectorClear(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); +void VectorScale (const float *in, float scale, float *out); +float VectorNormalize (float *v); +void VectorInverse ( float *v ); + +extern vec3_t vec3_origin; + +// disable 'possible loss of data converting float to int' warning message +#pragma warning( disable: 4244 ) +// disable 'truncation from 'const double' to 'float' warning message +#pragma warning( disable: 4305 ) + +inline void UnpackRGB(int &r, int &g, int &b, unsigned long ulRGB)\ +{\ + r = (ulRGB & 0xFF0000) >>16;\ + g = (ulRGB & 0xFF00) >> 8;\ + b = ulRGB & 0xFF;\ +} + +HSPRITE LoadSprite(const char *pszName); diff --git a/hlsdk/dmc/cl_dll/util_vector.h b/hlsdk/dmc/cl_dll/util_vector.h new file mode 100644 index 00000000..53f65467 --- /dev/null +++ b/hlsdk/dmc/cl_dll/util_vector.h @@ -0,0 +1,121 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Vector.h +// A subset of the extdll.h in the project HL Entity DLL +// + +// Misc C-runtime library headers +#include "STDIO.H" +#include "STDLIB.H" +#include "MATH.H" + +// Header file containing definition of globalvars_t and entvars_t +typedef int func_t; // +typedef int string_t; // from engine's pr_comp.h; +typedef float vec_t; // needed before including progdefs.h + +//========================================================= +// 2DVector - used for many pathfinding and many other +// operations that are treated as planar rather than 3d. +//========================================================= +class Vector2D +{ +public: + inline Vector2D(void) { } + inline Vector2D(float X, float Y) { x = X; y = Y; } + inline Vector2D operator+(const Vector2D& v) const { return Vector2D(x+v.x, y+v.y); } + inline Vector2D operator-(const Vector2D& v) const { return Vector2D(x-v.x, y-v.y); } + inline Vector2D operator*(float fl) const { return Vector2D(x*fl, y*fl); } + inline Vector2D operator/(float fl) const { return Vector2D(x/fl, y/fl); } + + inline float Length(void) const { return (float)sqrt(x*x + y*y ); } + + inline Vector2D Normalize ( void ) const + { + Vector2D vec2; + + float flLen = Length(); + if ( flLen == 0 ) + { + return Vector2D( (float)0, (float)0 ); + } + else + { + flLen = 1 / flLen; + return Vector2D( x * flLen, y * flLen ); + } + } + + vec_t x, y; +}; + +inline float DotProduct(const Vector2D& a, const Vector2D& b) { return( a.x*b.x + a.y*b.y ); } +inline Vector2D operator*(float fl, const Vector2D& v) { return v * fl; } + +//========================================================= +// 3D Vector +//========================================================= +class Vector // same data-layout as engine's vec3_t, +{ // which is a vec_t[3] +public: + // Construction/destruction + inline Vector(void) { } + inline Vector(float X, float Y, float Z) { x = X; y = Y; z = Z; } + inline Vector(double X, double Y, double Z) { x = (float)X; y = (float)Y; z = (float)Z; } + inline Vector(int X, int Y, int Z) { x = (float)X; y = (float)Y; z = (float)Z; } + inline Vector(const Vector& v) { x = v.x; y = v.y; z = v.z; } + inline Vector(float rgfl[3]) { x = rgfl[0]; y = rgfl[1]; z = rgfl[2]; } + + // Operators + inline Vector operator-(void) const { return Vector(-x,-y,-z); } + inline int operator==(const Vector& v) const { return x==v.x && y==v.y && z==v.z; } + inline int operator!=(const Vector& v) const { return !(*this==v); } + inline Vector operator+(const Vector& v) const { return Vector(x+v.x, y+v.y, z+v.z); } + inline Vector operator-(const Vector& v) const { return Vector(x-v.x, y-v.y, z-v.z); } + inline Vector operator*(float fl) const { return Vector(x*fl, y*fl, z*fl); } + inline Vector operator/(float fl) const { return Vector(x/fl, y/fl, z/fl); } + + // Methods + inline void CopyToArray(float* rgfl) const { rgfl[0] = x, rgfl[1] = y, rgfl[2] = z; } + inline float Length(void) const { return (float)sqrt(x*x + y*y + z*z); } + operator float *() { return &x; } // Vectors will now automatically convert to float * when needed + operator const float *() const { return &x; } // Vectors will now automatically convert to float * when needed + inline Vector Normalize(void) const + { + float flLen = Length(); + if (flLen == 0) return Vector(0,0,1); // ???? + flLen = 1 / flLen; + return Vector(x * flLen, y * flLen, z * flLen); + } + + inline Vector2D Make2D ( void ) const + { + Vector2D Vec2; + + Vec2.x = x; + Vec2.y = y; + + return Vec2; + } + inline float Length2D(void) const { return (float)sqrt(x*x + y*y); } + + // Members + vec_t x, y, z; +}; +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 ); } + +#define vec3_t Vector diff --git a/hlsdk/dmc/cl_dll/vgui_ControlConfigPanel.h b/hlsdk/dmc/cl_dll/vgui_ControlConfigPanel.h new file mode 100644 index 00000000..4c413a0c --- /dev/null +++ b/hlsdk/dmc/cl_dll/vgui_ControlConfigPanel.h @@ -0,0 +1,47 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef CONTROLCONFIGPANEL_H +#define CONTROLCONFIGPANEL_H + +#include +#include + +namespace vgui +{ +class HeaderPanel; +class TablePanel; +class ScrollPanel; +class InputStream; +class Label; +} + +class ControlConfigPanel : public vgui::Panel +{ +private: + vgui::HeaderPanel* _headerPanel; + vgui::TablePanel* _tablePanel; + vgui::ScrollPanel* _scrollPanel; + vgui::Dar _cvarDar; + vgui::Dar _descDar; + vgui::Label* _actionLabel; + vgui::Label* _keyButtonLabel; + vgui::Label* _alternateLabel; +public: + ControlConfigPanel(int x,int y,int wide,int tall); +public: + void AddCVar(const char* cvar,const char* desc); + void AddCVarFromInputStream(vgui::InputStream* is); + int GetCVarCount(); + void GetCVar(int index,char* cvar,int cvarLen,char* desc,int descLen); + void GetCVarBind(const char* cvar,char* bind,int bindLen,char* bindAlt,int bindAltLen); + void SetCVarBind(const char* cvar,const char* bind,const char* bindAlt); +}; + + + +#endif \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/vgui_CustomObjects.cpp b/hlsdk/dmc/cl_dll/vgui_CustomObjects.cpp new file mode 100644 index 00000000..9a6beb1e --- /dev/null +++ b/hlsdk/dmc/cl_dll/vgui_CustomObjects.cpp @@ -0,0 +1,428 @@ +//=========== (C) Copyright 1996-2002 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: Contains implementation of various VGUI-derived objects +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "VGUI_Font.h" + +#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 "vgui_int.h" +#include "vgui_viewport.h" +#include "vgui_ServerBrowser.h" +#include "VGUI_BitmapTGA.h" + + +// Arrow filenames +char *sArrowFilenames[] = +{ + "arrowup", + "arrowdn", + "arrowlt", + "arrowrt", +}; + + +//----------------------------------------------------------------------------- +// Purpose: Loads a .tga file and returns a pointer to the VGUI tga object +//----------------------------------------------------------------------------- +BitmapTGA *LoadTGA( const char* pImageName ) +{ + BitmapTGA *pTGA; + + char sz[256]; + sprintf(sz, "%%d_%s", pImageName); + + // Load the Image + FileInputStream* fis = new FileInputStream( GetVGUITGAName(sz), false ); + pTGA = new BitmapTGA(fis,true); + fis->close(); + + return pTGA; +} + +//=========================================================== +// All TFC Hud buttons are derived from this one. +CommandButton::CommandButton( const char* text,int x,int y,int wide,int tall, bool bNoHighlight) : Button("",x,y,wide,tall) +{ + m_iPlayerClass = 0; + m_bNoHighlight = bNoHighlight; + Init(); + setText( text ); +} + +CommandButton::CommandButton( int iPlayerClass, const char* text,int x,int y,int wide,int tall) : Button("",x,y,wide,tall) +{ + m_iPlayerClass = iPlayerClass; + m_bNoHighlight = false; + Init(); + setText( text ); +} + +void CommandButton::Init( void ) +{ + m_pSubMenu = NULL; + m_pSubLabel = NULL; + m_pParentMenu = NULL; + + // Set text color to orange + setFgColor(Scheme::sc_primary1); + + // left align + setContentAlignment( vgui::Label::a_west ); + + // Add the Highlight signal + if (!m_bNoHighlight) + addInputSignal( new CHandler_CommandButtonHighlight(this) ); + + // not bound to any button yet + m_cBoundKey = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Prepends the button text with the current bound key +// if no bound key, then a clear space ' ' instead +//----------------------------------------------------------------------------- +void CommandButton::RecalculateText( void ) +{ + char szBuf[128]; + + if ( m_cBoundKey != 0 ) + { + if ( m_cBoundKey == (char)255 ) + { + strcpy( szBuf, m_sMainText ); + } + else + { + sprintf( szBuf, " %c %s", m_cBoundKey, m_sMainText ); + } + szBuf[MAX_BUTTON_SIZE-1] = 0; + } + else + { + // just draw a space if no key bound + sprintf( szBuf, " %s", m_sMainText ); + szBuf[MAX_BUTTON_SIZE-1] = 0; + } + + Button::setText( szBuf ); +} + +void CommandButton::setText( const char *text ) +{ + strncpy( m_sMainText, text, MAX_BUTTON_SIZE ); + m_sMainText[MAX_BUTTON_SIZE-1] = 0; + + RecalculateText(); +} + +void CommandButton::setBoundKey( char boundKey ) +{ + m_cBoundKey = boundKey; + RecalculateText(); +} + +char CommandButton::getBoundKey( void ) +{ + return m_cBoundKey; +} + +void CommandButton::AddSubMenu( CCommandMenu *pNewMenu ) +{ + m_pSubMenu = pNewMenu; + + // Prevent this button from being pushed + setMouseClickEnabled( MOUSE_LEFT, false ); +} + +void CommandButton::UpdateSubMenus( int iAdjustment ) +{ + if ( m_pSubMenu ) + m_pSubMenu->RecalculatePositions( iAdjustment ); +} + +void CommandButton::paint() +{ + // Make the sub label paint the same as the button + if ( m_pSubLabel ) + { + if ( isSelected() ) + m_pSubLabel->PushDown(); + else + m_pSubLabel->PushUp(); + } + + // draw armed button text in white + if ( isArmed() ) + { + setFgColor( Scheme::sc_secondary2 ); + } + else + { + setFgColor( Scheme::sc_primary1 ); + } + + Button::paint(); +} + +void CommandButton::paintBackground() +{ + if ( isArmed() ) + { + // Orange highlight background + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect(0,0,_size[0],_size[1]); + } + + // Orange Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect(0,0,_size[0],_size[1]); +} + +//----------------------------------------------------------------------------- +// Purpose: Highlights the current button, and all it's parent menus +//----------------------------------------------------------------------------- +void CommandButton::cursorEntered( void ) +{ + // unarm all the other buttons in this menu + CCommandMenu *containingMenu = getParentMenu(); + if ( containingMenu ) + { + containingMenu->ClearButtonsOfArmedState(); + + // make all our higher buttons armed + CCommandMenu *pCParent = containingMenu->GetParentMenu(); + if ( pCParent ) + { + CommandButton *pParentButton = pCParent->FindButtonWithSubmenu( containingMenu ); + + pParentButton->cursorEntered(); + } + } + + // arm ourselves + setArmed( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CommandButton::cursorExited( void ) +{ + // only clear ourselves if we have do not have a containing menu + // only stay armed if we have a sub menu + // the buttons only unarm themselves when another button is armed instead + if ( !getParentMenu() || !GetSubMenu() ) + { + setArmed( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the command menu that the button is part of, if any +// Output : CCommandMenu * +//----------------------------------------------------------------------------- +CCommandMenu *CommandButton::getParentMenu( void ) +{ + return m_pParentMenu; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the menu that contains this button +// Input : *pParentMenu - +//----------------------------------------------------------------------------- +void CommandButton::setParentMenu( CCommandMenu *pParentMenu ) +{ + m_pParentMenu = pParentMenu; +} + + +//=========================================================== +int ClassButton::IsNotValid() +{ + return false; +} + +//=========================================================== +// Button with Class image beneath it +CImageLabel::CImageLabel( const char* pImageName,int x,int y ) : Label( "", x,y ) +{ + setContentFitted(true); + m_pTGA = LoadTGA(pImageName); + setImage( m_pTGA ); +} + +CImageLabel::CImageLabel( const char* pImageName,int x,int y,int wide,int tall ) : Label( "", x,y,wide,tall ) +{ + setContentFitted(true); + m_pTGA = LoadTGA(pImageName); + setImage( m_pTGA ); +} + +//=========================================================== +// Image size +int CImageLabel::getImageWide( void ) +{ + int iXSize, iYSize; + m_pTGA->getSize( iXSize, iYSize ); + return iXSize; +} + +int CImageLabel::getImageTall( void ) +{ + int iXSize, iYSize; + m_pTGA->getSize( iXSize, iYSize ); + return iYSize; +} + +//=========================================================== +// Various overloaded paint functions for Custom VGUI objects +void CCommandMenu::paintBackground() +{ + // Transparent black background + drawSetColor(Scheme::sc_primary3); + drawFilledRect(0,0,_size[0],_size[1]); +} + +//================================================================================= +// CUSTOM SCROLLPANEL +//================================================================================= +CTFScrollButton::CTFScrollButton(int iArrow, const char* text,int x,int y,int wide,int tall) : CommandButton(text,x,y,wide,tall) +{ + // Set text color to orange + setFgColor(Scheme::sc_primary1); + + // Load in the arrow + m_pTGA = LoadTGA( sArrowFilenames[iArrow] ); + setImage( m_pTGA ); + + // Highlight signal + InputSignal *pISignal = new CHandler_CommandButtonHighlight(this); + addInputSignal(pISignal); +} + +void CTFScrollButton::paint( void ) +{ + // draw armed button text in white + if ( isArmed() ) + { + m_pTGA->setColor( Color(255,255,255, 0) ); + } + else + { + m_pTGA->setColor( Color(255,255,255, 128) ); + } + + m_pTGA->doPaint(this); +} + +void CTFScrollButton::paintBackground( void ) +{ +/* + if ( isArmed() ) + { + // Orange highlight background + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect(0,0,_size[0],_size[1]); + } + + // Orange Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect(0,0,_size[0]-1,_size[1]); +*/ +} + +void CTFSlider::paintBackground( void ) +{ + int wide,tall,nobx,noby; + getPaintSize(wide,tall); + getNobPos(nobx,noby); + + // Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect( 0,0,wide,tall ); + + if( isVertical() ) + { + // Nob Fill + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect( 0,nobx,wide,noby ); + + // Nob Outline + drawSetColor( Scheme::sc_primary1 ); + drawOutlinedRect( 0,nobx,wide,noby ); + } + else + { + // Nob Fill + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect( nobx,0,noby,tall ); + + // Nob Outline + drawSetColor( Scheme::sc_primary1 ); + drawOutlinedRect( nobx,0,noby,tall ); + } +} + +CTFScrollPanel::CTFScrollPanel(int x,int y,int wide,int tall) : ScrollPanel(x,y,wide,tall) +{ + ScrollBar *pScrollBar = getVerticalScrollBar(); + pScrollBar->setButton( new CTFScrollButton( ARROW_UP, "", 0,0,16,16 ), 0 ); + pScrollBar->setButton( new CTFScrollButton( ARROW_DOWN, "", 0,0,16,16 ), 1 ); + pScrollBar->setSlider( new CTFSlider(0,wide-1,wide,(tall-(wide*2))+2,true) ); + pScrollBar->setPaintBorderEnabled(false); + pScrollBar->setPaintBackgroundEnabled(false); + pScrollBar->setPaintEnabled(false); + + pScrollBar = getHorizontalScrollBar(); + pScrollBar->setButton( new CTFScrollButton( ARROW_LEFT, "", 0,0,16,16 ), 0 ); + pScrollBar->setButton( new CTFScrollButton( ARROW_RIGHT, "", 0,0,16,16 ), 1 ); + pScrollBar->setSlider( new CTFSlider(tall,0,wide-(tall*2),tall,false) ); + pScrollBar->setPaintBorderEnabled(false); + pScrollBar->setPaintBackgroundEnabled(false); + pScrollBar->setPaintEnabled(false); +} + + +//================================================================================= +// CUSTOM HANDLERS +//================================================================================= +void CHandler_MenuButtonOver::cursorEntered(Panel *panel) +{ + if ( gViewPort && m_pMenuPanel ) + { + m_pMenuPanel->SetActiveInfo( m_iButton ); + } +} + +void CMenuHandler_StringCommandClassSelect::actionPerformed(Panel* panel) +{ + CMenuHandler_StringCommand::actionPerformed( panel ); + + bool bAutoKill = CVAR_GET_FLOAT( "hud_classautokill" ) != 0; + +} + diff --git a/hlsdk/dmc/cl_dll/vgui_MOTDWindow.cpp b/hlsdk/dmc/cl_dll/vgui_MOTDWindow.cpp new file mode 100644 index 00000000..66d09b48 --- /dev/null +++ b/hlsdk/dmc/cl_dll/vgui_MOTDWindow.cpp @@ -0,0 +1,153 @@ +//=========== (C) Copyright 1996-2002 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: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "VGUI_Font.h" +#include "VGUI_ScrollPanel.h" +#include "VGUI_TextImage.h" + +#include + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "const.h" + +#include "vgui_int.h" +#include "vgui_viewport.h" +#include "vgui_ServerBrowser.h" + +#define MOTD_TITLE_X XRES(16) +#define MOTD_TITLE_Y YRES(16) + +#define MOTD_WINDOW_X XRES(112) +#define MOTD_WINDOW_Y YRES(80) +#define MOTD_WINDOW_SIZE_X XRES(424) +#define MOTD_WINDOW_SIZE_Y YRES(312) + +//----------------------------------------------------------------------------- +// Purpose: Displays the MOTD and basic server information +//----------------------------------------------------------------------------- +class CMessageWindowPanel : public CMenuPanel +{ +public: + CMessageWindowPanel( const char *szMOTD, const char *szTitle, int iShadeFullScreen, int iRemoveMe, int x, int y, int wide, int tall ); + +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 ) +{ + return new CMessageWindowPanel( szMOTD, szTitle, iShadeFullscreen, iRemoveMe, 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 ) +{ + // 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; + + // 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->setVisible( true ); + + int iXSize,iYSize,iXPos,iYPos; + m_pBackgroundPanel->getPos( iXPos,iYPos ); + m_pBackgroundPanel->getSize( iXSize,iYSize ); + + // Create the title + Label *pLabel = new Label( "", iXPos + MOTD_TITLE_X, iYPos + MOTD_TITLE_Y ); + pLabel->setParent( this ); + pLabel->setFont( pSchemes->getFont(hTitleScheme) ); + pLabel->setFont( Scheme::sf_primary1 ); + + pSchemes->getFgColor( hTitleScheme, r, g, b, a ); + pLabel->setFgColor( r, g, b, a ); + pLabel->setFgColor( Scheme::sc_primary1 ); + pSchemes->getBgColor( hTitleScheme, r, g, b, a ); + pLabel->setBgColor( r, g, b, a ); + pLabel->setContentAlignment( vgui::Label::a_west ); + pLabel->setText(szTitle); + + // Create the Scroll panel + ScrollPanel *pScrollPanel = new CTFScrollPanel( iXPos + XRES(16), iYPos + MOTD_TITLE_Y*2 + YRES(16), iXSize - XRES(32), iYSize - (YRES(48) + BUTTON_SIZE_Y*2) ); + pScrollPanel->setParent(this); + //force the scrollbars on so clientClip will take them in account after the validate + pScrollPanel->setScrollBarAutoVisible(false, false); + pScrollPanel->setScrollBarVisible(true, true); + pScrollPanel->validate(); + + // Create the text panel + TextPanel *pText = new TextPanel( "", 0,0, 64,64); + pText->setParent( pScrollPanel->getClient() ); + + // get the font and colors from the scheme + pText->setFont( pSchemes->getFont(hMOTDText) ); + pSchemes->getFgColor( hMOTDText, r, g, b, a ); + pText->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hMOTDText, r, g, b, a ); + pText->setBgColor( r, g, b, a ); + pText->setText(szMOTD); + + // 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 wdith 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. + pText->getTextImage()->setSize(pScrollPanel->getClientClip()->getWide(), pScrollPanel->getClientClip()->getTall()); + pText->getTextImage()->getTextSizeWrapped( iScrollSizeX, iScrollSizeY ); + + // Now resize the textpanel to fit the scrolled size + pText->setSize( iScrollSizeX , iScrollSizeY ); + + //turn the scrollbars back into automode + pScrollPanel->setScrollBarAutoVisible(true, true); + pScrollPanel->setScrollBarVisible(false, false); + + 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); + +} + + + + + + diff --git a/hlsdk/dmc/cl_dll/vgui_SchemeManager.cpp b/hlsdk/dmc/cl_dll/vgui_SchemeManager.cpp new file mode 100644 index 00000000..6dbc1b93 --- /dev/null +++ b/hlsdk/dmc/cl_dll/vgui_SchemeManager.cpp @@ -0,0 +1,544 @@ +//=========== (C) Copyright 1996-2002 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: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "hud.h" +#include "vgui_SchemeManager.h" +#include "cvardef.h" + +#include + + +cvar_t *g_CV_BitmapFonts; + + +void Scheme_Init() +{ + g_CV_BitmapFonts = gEngfuncs.pfnRegisterVariable("bitmapfonts", "1", 0); +} + + + +//----------------------------------------------------------------------------- +// Purpose: Scheme managers data container +//----------------------------------------------------------------------------- +class CSchemeManager::CScheme +{ +public: + enum { + SCHEME_NAME_LENGTH = 32, + FONT_NAME_LENGTH = 48, + FONT_FILENAME_LENGTH = 64, + }; + + // name + char schemeName[SCHEME_NAME_LENGTH]; + + // font + char fontName[FONT_NAME_LENGTH]; + + int fontSize; + int fontWeight; + + vgui::Font *font; + int ownFontPointer; // true if the font is ours to delete + + // scheme + byte fgColor[4]; + byte bgColor[4]; + byte armedFgColor[4]; + byte armedBgColor[4]; + byte mousedownFgColor[4]; + byte mousedownBgColor[4]; + byte borderColor[4]; + + // construction/destruction + CScheme(); + ~CScheme(); +}; + +CSchemeManager::CScheme::CScheme() +{ + schemeName[0] = 0; + fontName[0] = 0; + fontSize = 0; + fontWeight = 0; + font = NULL; + ownFontPointer = false; +} + +CSchemeManager::CScheme::~CScheme() +{ + // only delete our font pointer if we own it + if ( ownFontPointer ) + { + delete font; + } +} + +//----------------------------------------------------------------------------- +// Purpose: resolution information +// !! needs to be shared out +//----------------------------------------------------------------------------- +static int g_ResArray[] = +{ + 320, + 400, + 512, + 640, + 800, + 1024, + 1152, + 1280, + 1600 +}; +static int g_NumReses = sizeof(g_ResArray) / sizeof(int); + +static byte *LoadFileByResolution( const char *filePrefix, int xRes, const char *filePostfix ) +{ + // find our resolution in the res array + int resNum = g_NumReses - 1; + while ( g_ResArray[resNum] > xRes ) + { + resNum--; + + if ( resNum < 0 ) + return NULL; + } + + // try open the file + byte *pFile = NULL; + while ( 1 ) + { + + // try load + char fname[256]; + sprintf( fname, "%s%d%s", filePrefix, g_ResArray[resNum], filePostfix ); + pFile = gEngfuncs.COM_LoadFile( fname, 5, NULL ); + + if ( pFile ) + break; + + if ( resNum == 0 ) + return NULL; + + resNum--; + }; + + return pFile; +} + +static void ParseRGBAFromString( byte colorArray[4], const char *colorVector ) +{ + int r, g, b, a; + sscanf( colorVector, "%d %d %d %d", &r, &g, &b, &a ); + colorArray[0] = r; + colorArray[1] = g; + colorArray[2] = b; + colorArray[3] = a; +} + +//----------------------------------------------------------------------------- +// Purpose: initializes the scheme manager +// loading the scheme files for the current resolution +// Input : xRes - +// yRes - dimensions of output window +//----------------------------------------------------------------------------- +CSchemeManager::CSchemeManager( int xRes, int yRes ) +{ + // basic setup + m_pSchemeList = NULL; + m_iNumSchemes = 0; + + // find the closest matching scheme file to our resolution + char token[1024]; + char *pFile = (char*)LoadFileByResolution( "", xRes, "_textscheme.txt" ); + m_xRes = xRes; + + char *pFileStart = pFile; + + byte *pFontData; + int fontFileLength; + char fontFilename[512]; + + // + // Read the scheme descriptions from the text file, into a temporary array + // format is simply: + // = + // + // a of "SchemeName" signals a new scheme is being described + // + + const static int numTmpSchemes = 64; + static CScheme tmpSchemes[numTmpSchemes]; + memset( tmpSchemes, 0, sizeof(tmpSchemes) ); + int currentScheme = -1; + CScheme *pScheme = NULL; + + if ( !pFile ) + { + gEngfuncs.Con_DPrintf( "Unable to find *_textscheme.txt\n"); + goto buildDefaultFont; + } + + // record what has been entered so we can create defaults from the different values + bool hasFgColor, hasBgColor, hasArmedFgColor, hasArmedBgColor, hasMouseDownFgColor, hasMouseDownBgColor; + + pFile = gEngfuncs.COM_ParseFile( pFile, token ); + while ( strlen(token) > 0 && (currentScheme < numTmpSchemes) ) + { + // get the paramName name + static const int tokenSize = 64; + char paramName[tokenSize], paramValue[tokenSize]; + + strncpy( paramName, token, tokenSize ); + paramName[tokenSize-1] = 0; // ensure null termination + + // get the '=' character + pFile = gEngfuncs.COM_ParseFile( pFile, token ); + if ( stricmp( token, "=" ) ) + { + if ( currentScheme < 0 ) + { + gEngfuncs.Con_Printf( "error parsing font scheme text file at file start - expected '=', found '%s''\n", token ); + } + else + { + gEngfuncs.Con_Printf( "error parsing font scheme text file at scheme '%s' - expected '=', found '%s''\n", tmpSchemes[currentScheme].schemeName, token ); + } + break; + } + + // get paramValue + pFile = gEngfuncs.COM_ParseFile( pFile, token ); + strncpy( paramValue, token, tokenSize ); + paramValue[tokenSize-1] = 0; // ensure null termination + + // is this a new scheme? + if ( !stricmp(paramName, "SchemeName") ) + { + // setup the defaults for the current scheme + if ( pScheme ) + { + // foreground color defaults (normal -> armed -> mouse down) + if ( !hasFgColor ) + { + pScheme->fgColor[0] = pScheme->fgColor[1] = pScheme->fgColor[2] = pScheme->fgColor[3] = 255; + } + if ( !hasArmedFgColor ) + { + memcpy( pScheme->armedFgColor, pScheme->fgColor, sizeof(pScheme->armedFgColor) ); + } + if ( !hasMouseDownFgColor ) + { + memcpy( pScheme->mousedownFgColor, pScheme->armedFgColor, sizeof(pScheme->mousedownFgColor) ); + } + + // background color (normal -> armed -> mouse down) + if ( !hasBgColor ) + { + pScheme->bgColor[0] = pScheme->bgColor[1] = pScheme->bgColor[2] = pScheme->bgColor[3] = 0; + } + if ( !hasArmedBgColor ) + { + memcpy( pScheme->armedBgColor, pScheme->bgColor, sizeof(pScheme->armedBgColor) ); + } + if ( !hasMouseDownBgColor ) + { + memcpy( pScheme->mousedownBgColor, pScheme->armedBgColor, sizeof(pScheme->mousedownBgColor) ); + } + + // font size + if ( !pScheme->fontSize ) + { + pScheme->fontSize = 17; + } + if ( !pScheme->fontName[0] ) + { + strcpy( pScheme->fontName, "Arial" ); + } + } + + // create the new scheme + currentScheme++; + pScheme = &tmpSchemes[currentScheme]; + hasFgColor = hasBgColor = hasArmedFgColor = hasArmedBgColor = hasMouseDownFgColor = hasMouseDownBgColor = false; + + strncpy( pScheme->schemeName, paramValue, CScheme::SCHEME_NAME_LENGTH ); + pScheme->schemeName[CScheme::SCHEME_NAME_LENGTH-1] = '\0'; // ensure null termination of string + } + + if ( !pScheme ) + { + gEngfuncs.Con_Printf( "font scheme text file MUST start with a 'SchemeName'\n"); + break; + } + + // pull the data out into the scheme + if ( !stricmp(paramName, "FontName") ) + { + strncpy( pScheme->fontName, paramValue, CScheme::FONT_NAME_LENGTH ); + pScheme->fontName[CScheme::FONT_NAME_LENGTH-1] = 0; + } + else if ( !stricmp(paramName, "FontSize") ) + { + pScheme->fontSize = atoi( paramValue ); + } + else if ( !stricmp(paramName, "FontWeight") ) + { + pScheme->fontWeight = atoi( paramValue ); + } + else if ( !stricmp(paramName, "FgColor") ) + { + ParseRGBAFromString( pScheme->fgColor, paramValue ); + hasFgColor = true; + } + else if ( !stricmp(paramName, "BgColor") ) + { + ParseRGBAFromString( pScheme->bgColor, paramValue ); + hasBgColor = true; + } + else if ( !stricmp(paramName, "FgColorArmed") ) + { + ParseRGBAFromString( pScheme->armedFgColor, paramValue ); + hasArmedFgColor = true; + } + else if ( !stricmp(paramName, "BgColorArmed") ) + { + ParseRGBAFromString( pScheme->armedBgColor, paramValue ); + hasArmedBgColor = true; + } + else if ( !stricmp(paramName, "FgColorMousedown") ) + { + ParseRGBAFromString( pScheme->mousedownFgColor, paramValue ); + hasMouseDownFgColor = true; + } + else if ( !stricmp(paramName, "BgColorMousedown") ) + { + ParseRGBAFromString( pScheme->mousedownBgColor, paramValue ); + hasMouseDownBgColor = true; + } + else if ( !stricmp(paramName, "BorderColor") ) + { + ParseRGBAFromString( pScheme->borderColor, paramValue ); + hasMouseDownBgColor = true; + } + + // get the new token last, so we now if the loop needs to be continued or not + pFile = gEngfuncs.COM_ParseFile( pFile, token ); + } + + // free the file + gEngfuncs.COM_FreeFile( pFileStart ); + + +buildDefaultFont: + + // make sure we have at least 1 valid font + if ( currentScheme < 0 ) + { + currentScheme = 0; + strcpy( tmpSchemes[0].schemeName, "Default Scheme" ); + strcpy( tmpSchemes[0].fontName, "Arial" ); + tmpSchemes[0].fontSize = 0; + tmpSchemes[0].fgColor[0] = tmpSchemes[0].fgColor[1] = tmpSchemes[0].fgColor[2] = tmpSchemes[0].fgColor[3] = 255; + tmpSchemes[0].armedFgColor[0] = tmpSchemes[0].armedFgColor[1] = tmpSchemes[0].armedFgColor[2] = tmpSchemes[0].armedFgColor[3] = 255; + tmpSchemes[0].mousedownFgColor[0] = tmpSchemes[0].mousedownFgColor[1] = tmpSchemes[0].mousedownFgColor[2] = tmpSchemes[0].mousedownFgColor[3] = 255; + } + + // we have the full list of schemes in the tmpSchemes array + // now allocate the correct sized list + m_iNumSchemes = currentScheme + 1; // 0-based index + m_pSchemeList = new CScheme[ m_iNumSchemes ]; + + // copy in the data + memcpy( m_pSchemeList, tmpSchemes, sizeof(CScheme) * m_iNumSchemes ); + + // create the fonts + for ( int i = 0; i < m_iNumSchemes; i++ ) + { + m_pSchemeList[i].font = NULL; + + // see if the current font values exist in a previously loaded font + for ( int j = 0; j < i; j++ ) + { + // check if the font name, size, and weight are the same + if ( !stricmp(m_pSchemeList[i].fontName, m_pSchemeList[j].fontName) + && m_pSchemeList[i].fontSize == m_pSchemeList[j].fontSize + && m_pSchemeList[i].fontWeight == m_pSchemeList[j].fontWeight ) + { + // copy the pointer, but mark i as not owning it + m_pSchemeList[i].font = m_pSchemeList[j].font; + m_pSchemeList[i].ownFontPointer = false; + } + } + + // if we haven't found the font already, load it ourselves + if ( !m_pSchemeList[i].font ) + { + fontFileLength = -1; + pFontData = NULL; + + if(g_CV_BitmapFonts && g_CV_BitmapFonts->value) + { + sprintf(fontFilename, "gfx\\vgui\\fonts\\%d_%s.tga", m_xRes, m_pSchemeList[i].schemeName); + pFontData = gEngfuncs.COM_LoadFile( fontFilename, 5, &fontFileLength ); + if(!pFontData) + gEngfuncs.Con_Printf("Missing bitmap font: %s\n", fontFilename); + } + + m_pSchemeList[i].font = new vgui::Font( + m_pSchemeList[i].fontName, + pFontData, + fontFileLength, + m_pSchemeList[i].fontSize, + 0, + 0, + m_pSchemeList[i].fontWeight, + false, + false, + false, + false); + + m_pSchemeList[i].ownFontPointer = true; + } + + // fix up alpha values; VGUI uses 1-A (A=0 being solid, A=255 transparent) + m_pSchemeList[i].fgColor[3] = 255 - m_pSchemeList[i].fgColor[3]; + m_pSchemeList[i].bgColor[3] = 255 - m_pSchemeList[i].bgColor[3]; + m_pSchemeList[i].armedFgColor[3] = 255 - m_pSchemeList[i].armedFgColor[3]; + m_pSchemeList[i].armedBgColor[3] = 255 - m_pSchemeList[i].armedBgColor[3]; + m_pSchemeList[i].mousedownFgColor[3] = 255 - m_pSchemeList[i].mousedownFgColor[3]; + m_pSchemeList[i].mousedownBgColor[3] = 255 - m_pSchemeList[i].mousedownBgColor[3]; + } +} + +//----------------------------------------------------------------------------- +// Purpose: frees all the memory used by the scheme manager +//----------------------------------------------------------------------------- +CSchemeManager::~CSchemeManager() +{ + delete [] m_pSchemeList; + m_iNumSchemes = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Finds a scheme in the list, by name +// Input : char *schemeName - string name of the scheme +// Output : SchemeHandle_t handle to the scheme +//----------------------------------------------------------------------------- +SchemeHandle_t CSchemeManager::getSchemeHandle( const char *schemeName ) +{ + // iterate through the list + for ( int i = 0; i < m_iNumSchemes; i++ ) + { + if ( !stricmp(schemeName, m_pSchemeList[i].schemeName) ) + return i; + } + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: always returns a valid scheme handle +// Input : schemeHandle - +// Output : CScheme +//----------------------------------------------------------------------------- +CSchemeManager::CScheme *CSchemeManager::getSafeScheme( SchemeHandle_t schemeHandle ) +{ + if ( schemeHandle < m_iNumSchemes ) + return m_pSchemeList + schemeHandle; + + return m_pSchemeList; +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns the schemes pointer to a font +// Input : schemeHandle - +// Output : vgui::Font +//----------------------------------------------------------------------------- +vgui::Font *CSchemeManager::getFont( SchemeHandle_t schemeHandle ) +{ + return getSafeScheme( schemeHandle )->font; +} + +void CSchemeManager::getFgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->fgColor[0]; + g = pScheme->fgColor[1]; + b = pScheme->fgColor[2]; + a = pScheme->fgColor[3]; +} + +void CSchemeManager::getBgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->bgColor[0]; + g = pScheme->bgColor[1]; + b = pScheme->bgColor[2]; + a = pScheme->bgColor[3]; +} + +void CSchemeManager::getFgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->armedFgColor[0]; + g = pScheme->armedFgColor[1]; + b = pScheme->armedFgColor[2]; + a = pScheme->armedFgColor[3]; +} + +void CSchemeManager::getBgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->armedBgColor[0]; + g = pScheme->armedBgColor[1]; + b = pScheme->armedBgColor[2]; + a = pScheme->armedBgColor[3]; +} + +void CSchemeManager::getFgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->mousedownFgColor[0]; + g = pScheme->mousedownFgColor[1]; + b = pScheme->mousedownFgColor[2]; + a = pScheme->mousedownFgColor[3]; +} + +void CSchemeManager::getBgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->mousedownBgColor[0]; + g = pScheme->mousedownBgColor[1]; + b = pScheme->mousedownBgColor[2]; + a = pScheme->mousedownBgColor[3]; +} + +void CSchemeManager::getBorderColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->borderColor[0]; + g = pScheme->borderColor[1]; + b = pScheme->borderColor[2]; + a = pScheme->borderColor[3]; +} + + + diff --git a/hlsdk/dmc/cl_dll/vgui_SchemeManager.h b/hlsdk/dmc/cl_dll/vgui_SchemeManager.h new file mode 100644 index 00000000..0e7d99a3 --- /dev/null +++ b/hlsdk/dmc/cl_dll/vgui_SchemeManager.h @@ -0,0 +1,52 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include + +// handle to an individual scheme +typedef int SchemeHandle_t; + +// Register console variables, etc.. +void Scheme_Init(); + + +//----------------------------------------------------------------------------- +// Purpose: Handles the loading of text scheme description from disk +// supports different font/color/size schemes at different resolutions +//----------------------------------------------------------------------------- +class CSchemeManager +{ +public: + // initialization + CSchemeManager( int xRes, int yRes ); + virtual ~CSchemeManager(); + + // scheme handling + SchemeHandle_t getSchemeHandle( const char *schemeName ); + + // getting info from schemes + vgui::Font *getFont( SchemeHandle_t schemeHandle ); + void getFgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getFgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getFgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBorderColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + +private: + class CScheme; + CScheme *m_pSchemeList; + int m_iNumSchemes; + + // Resolution we were initted at. + int m_xRes; + + CScheme *getSafeScheme( SchemeHandle_t schemeHandle ); +}; + + diff --git a/hlsdk/dmc/cl_dll/vgui_ScorePanel.cpp b/hlsdk/dmc/cl_dll/vgui_ScorePanel.cpp new file mode 100644 index 00000000..f9b848ab --- /dev/null +++ b/hlsdk/dmc/cl_dll/vgui_ScorePanel.cpp @@ -0,0 +1,1027 @@ +//=========== (C) Copyright 1996-2002, 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: VGUI scoreboard +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + + +#include + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "vgui_viewport.h" +#include "vgui_ScorePanel.h" +#include "voice_status.h" +#include "vgui_helpers.h" +#include "vgui_loadtga.h" + +extern int g_iTeamNumber; + +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 +team_info_t g_TeamInfo[MAX_TEAMS+1]; +int g_IsSpectator[MAX_PLAYERS+1]; + +// Scoreboard dimensions +#define SBOARD_TITLE_SIZE_Y YRES(22) + +#define X_BORDER XRES(4) + +// Column sizes +class SBColumnInfo +{ +public: + char *m_pTitle; // If null, ignore, if starts with #, it's localized, otherwise use the string directly. + int m_Width; // Based on 640 width. Scaled to fit other resolutions. + Label::Alignment m_Alignment; +}; + +// grid size is marked out for 640x480 screen + +SBColumnInfo g_ColumnInfo[NUM_COLUMNS] = +{ + {NULL, 24, Label::a_east}, // blank column + {NULL, 140, Label::a_west}, // name + {"#SCORE", 80, Label::a_east}, + {"#DEATHS", 46, Label::a_east}, + {"#LATENCY", 46, Label::a_east}, + {"#VOICE", 40, Label::a_east}, + {NULL, 2, Label::a_east}, // blank column to take up the slack +}; + + +#define TEAM_NO 0 +#define TEAM_YES 1 +#define TEAM_SPECTATORS 2 +#define TEAM_BLANK 3 + + +//----------------------------------------------------------------------------- +// ScorePanel::HitTestPanel. +//----------------------------------------------------------------------------- + +void ScorePanel::HitTestPanel::internalMousePressed(MouseCode code) +{ + for(int i=0;i<_inputSignalDar.getCount();i++) + { + _inputSignalDar[i]->mousePressed(code,this); + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: Create the ScoreBoard panel +//----------------------------------------------------------------------------- +ScorePanel::ScorePanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) +{ + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle("Scoreboard Title Text"); + SchemeHandle_t hSmallScheme = pSchemes->getSchemeHandle("Scoreboard Small Text"); + Font *tfont = pSchemes->getFont(hTitleScheme); + Font *smallfont = pSchemes->getFont(hSmallScheme); + + setBgColor(0, 0, 0, 96); + m_pCurrentHighlightLabel = NULL; + m_iHighlightRow = -1; + + // Initialize the top title. + m_TitleLabel.setFont(tfont); + m_TitleLabel.setText(""); + m_TitleLabel.setBgColor( 0, 0, 0, 255 ); + 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) + { + // only expand column size for res greater than 640 + xpos = XRES(xpos); + } + m_TitleLabel.setBounds(xpos, 4, wide, SBOARD_TITLE_SIZE_Y); + m_TitleLabel.setContentFitted(false); + m_TitleLabel.setParent(this); + + // Setup the header (labels like "name", "class", etc..). + m_HeaderGrid.SetDimensions(NUM_COLUMNS, 1); + m_HeaderGrid.SetSpacing(0, 0); + + for(int i=0; i < NUM_COLUMNS; i++) + { + if (g_ColumnInfo[i].m_pTitle && g_ColumnInfo[i].m_pTitle[0] == '#') + m_HeaderLabels[i].setText(CHudTextMessage::BufferedLocaliseTextString(g_ColumnInfo[i].m_pTitle)); + else if(g_ColumnInfo[i].m_pTitle) + m_HeaderLabels[i].setText(g_ColumnInfo[i].m_pTitle); + + int xwide = g_ColumnInfo[i].m_Width; + if (ScreenWidth >= 640) + { + xwide = XRES(xwide); + } + else if (ScreenWidth == 400) + { + // hack to make 400x300 resolution scoreboard fit + if (i == 1) + { + // reduces size of player name cell + xwide -= 28; + } + else if (i == 0) + { + xwide -= 8; + } + } + + m_HeaderGrid.SetColumnWidth(i, xwide); + m_HeaderGrid.SetEntry(i, 0, &m_HeaderLabels[i]); + + m_HeaderLabels[i].setBgColor(0,0,0,255); + m_HeaderLabels[i].setFgColor(Scheme::sc_primary1); + m_HeaderLabels[i].setFont(smallfont); + m_HeaderLabels[i].setContentAlignment(g_ColumnInfo[i].m_Alignment); + + int yres = 12; + if (ScreenHeight >= 480) + { + yres = YRES(yres); + } + m_HeaderLabels[i].setSize(50, yres); + } + + // Set the width of the last column to be the remaining space. + int ex, ey, ew, eh; + m_HeaderGrid.GetEntryBox(NUM_COLUMNS - 2, 0, ex, ey, ew, eh); + m_HeaderGrid.SetColumnWidth(NUM_COLUMNS - 1, (wide - X_BORDER) - (ex + ew)); + + m_HeaderGrid.AutoSetRowHeights(); + m_HeaderGrid.setBounds(X_BORDER, SBOARD_TITLE_SIZE_Y, wide - X_BORDER*2, m_HeaderGrid.GetRowHeight(0)); + m_HeaderGrid.setParent(this); + m_HeaderGrid.setBgColor(0,0,0,255); + + + // Now setup the listbox with the actual player data in it. + int headerX, headerY, headerWidth, headerHeight; + m_HeaderGrid.getBounds(headerX, headerY, headerWidth, headerHeight); + m_PlayerList.setBounds(headerX, headerY+headerHeight, headerWidth, tall - headerY - headerHeight - 6); + m_PlayerList.setBgColor(0,0,0,255); + m_PlayerList.setParent(this); + + for(int row=0; row < NUM_ROWS; row++) + { + CGrid *pGridRow = &m_PlayerGrids[row]; + + pGridRow->SetDimensions(NUM_COLUMNS, 1); + + for(int col=0; col < NUM_COLUMNS; col++) + { + m_PlayerEntries[col][row].setContentFitted(false); + m_PlayerEntries[col][row].setRow(row); + m_PlayerEntries[col][row].addInputSignal(this); + pGridRow->SetEntry(col, 0, &m_PlayerEntries[col][row]); + } + + pGridRow->setBgColor(0,0,0,255); + pGridRow->SetSpacing(0, 0); + pGridRow->CopyColumnWidths(&m_HeaderGrid); + pGridRow->AutoSetRowHeights(); + pGridRow->setSize(PanelWidth(pGridRow), pGridRow->CalcDrawHeight()); + pGridRow->RepositionContents(); + + m_PlayerList.AddItem(pGridRow); + } + + + // Add the hit test panel. It is invisible and traps mouse clicks so we can go into squelch mode. + m_HitTestPanel.setBgColor(0,0,0,255); + m_HitTestPanel.setParent(this); + m_HitTestPanel.setBounds(0, 0, wide, tall); + m_HitTestPanel.addInputSignal(this); + + Initialize(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Called each time a new level is started. +//----------------------------------------------------------------------------- +void ScorePanel::Initialize( void ) +{ + // Clear out scoreboard data + m_iLastKilledBy = 0; + m_fLastKillTime = 0; + m_iPlayerNum = 0; + m_iNumTeams = 0; + memset( g_PlayerExtraInfo, 0, sizeof g_PlayerExtraInfo ); + memset( g_TeamInfo, 0, sizeof g_TeamInfo ); +} + + +bool HACK_GetPlayerUniqueID( int iPlayer, char playerID[16] ) +{ + return !!gEngfuncs.GetPlayerUniqueID( iPlayer, playerID ); +} + +//----------------------------------------------------------------------------- +// Purpose: Recalculate the internal scoreboard data +//----------------------------------------------------------------------------- +void ScorePanel::Update() +{ + // Set the title + if (gViewPort->m_szServerName) + { + char sz[MAX_SERVERNAME_LENGTH + 16]; + sprintf(sz, "%s", gViewPort->m_szServerName ); + m_TitleLabel.setText(sz); + } + + m_iRows = 0; + gViewPort->GetAllPlayersInfo(); + + // Clear out sorts + for (int i = 0; i < NUM_ROWS; i++) + { + m_iSortedRows[i] = 0; + m_iIsATeam[i] = TEAM_NO; + m_bHasBeenSorted[i] = false; + } + + // If it's not teamplay, sort all the players. Otherwise, sort the teams. + if ( !gHUD.m_Teamplay ) + SortPlayers( 0, NULL ); + else + SortTeams(); + + // set scrollbar range + m_PlayerList.SetScrollRange(m_iRows); + + FillGrid(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sort all the teams +//----------------------------------------------------------------------------- +void ScorePanel::SortTeams() +{ + // clear out team scores + for ( int i = 1; i <= m_iNumTeams; i++ ) + { + if ( !g_TeamInfo[i].scores_overriden ) + g_TeamInfo[i].frags = g_TeamInfo[i].deaths = 0; + g_TeamInfo[i].ping = g_TeamInfo[i].packetloss = 0; + } + + // recalc the team scores, then draw them + for ( int i = 1; i < MAX_PLAYERS; i++ ) + { + if ( g_PlayerInfoList[i].name == NULL ) + continue; // empty player slot, skip + + if ( g_PlayerExtraInfo[i].teamname[0] == 0 ) + continue; // skip over players who are not in a team + + // find what team this player is in + int j; + for ( j = 1; j <= m_iNumTeams; j++ ) + { + if ( !stricmp( g_PlayerExtraInfo[i].teamname, g_TeamInfo[j].name ) ) + break; + } + if ( j > m_iNumTeams ) // player is not in a team, skip to the next guy + continue; + + if ( !g_TeamInfo[j].scores_overriden ) + { + g_TeamInfo[j].frags += g_PlayerExtraInfo[i].frags; + g_TeamInfo[j].deaths += g_PlayerExtraInfo[i].deaths; + } + + g_TeamInfo[j].ping += g_PlayerInfoList[i].ping; + g_TeamInfo[j].packetloss += g_PlayerInfoList[i].packetloss; + + if ( g_PlayerInfoList[i].thisplayer ) + g_TeamInfo[j].ownteam = TRUE; + else + g_TeamInfo[j].ownteam = FALSE; + + // Set the team's number (used for team colors) + g_TeamInfo[j].teamnumber = g_PlayerExtraInfo[i].teamnumber; + } + + // find team ping/packetloss averages + for ( int i = 1; i <= m_iNumTeams; i++ ) + { + g_TeamInfo[i].already_drawn = FALSE; + + if ( g_TeamInfo[i].players > 0 ) + { + g_TeamInfo[i].ping /= g_TeamInfo[i].players; // use the average ping of all the players in the team as the teams ping + g_TeamInfo[i].packetloss /= g_TeamInfo[i].players; // use the average ping of all the players in the team as the teams ping + } + } + + // Draw the teams + while ( 1 ) + { + int highest_frags = -99999; int lowest_deaths = 99999; + int best_team = 0; + + for ( int i = 1; i <= m_iNumTeams; i++ ) + { + if ( g_TeamInfo[i].players < 1 ) + continue; + + if ( !g_TeamInfo[i].already_drawn && g_TeamInfo[i].frags >= highest_frags ) + { + if ( g_TeamInfo[i].frags > highest_frags || g_TeamInfo[i].deaths < lowest_deaths ) + { + best_team = i; + lowest_deaths = g_TeamInfo[i].deaths; + highest_frags = g_TeamInfo[i].frags; + } + } + } + + // draw the best team on the scoreboard + if ( !best_team ) + break; + + // Put this team in the sorted list + m_iSortedRows[ m_iRows ] = best_team; + m_iIsATeam[ m_iRows ] = TEAM_YES; + g_TeamInfo[best_team].already_drawn = TRUE; // set the already_drawn to be TRUE, so this team won't get sorted again + m_iRows++; + + // Now sort all the players on this team + SortPlayers( 0, g_TeamInfo[best_team].name ); + } + + // Add all the players who aren't in a team yet into spectators + SortPlayers( TEAM_SPECTATORS, NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sort a list of players +//----------------------------------------------------------------------------- +void ScorePanel::SortPlayers( int iTeam, char *team ) +{ + bool bCreatedTeam = false; + + // draw the players, in order, and restricted to team if set + while ( 1 ) + { + // Find the top ranking player + int highest_frags = -99999; int lowest_deaths = 99999; + int best_player; + best_player = 0; + + for ( int i = 1; i < MAX_PLAYERS; i++ ) + { + if ( m_bHasBeenSorted[i] == false && g_PlayerInfoList[i].name && g_PlayerExtraInfo[i].frags >= highest_frags ) + { + cl_entity_t *ent = gEngfuncs.GetEntityByIndex( i ); + + if ( ent && !(team && stricmp(g_PlayerExtraInfo[i].teamname, team)) ) + { + extra_player_info_t *pl_info = &g_PlayerExtraInfo[i]; + if ( pl_info->frags > highest_frags || pl_info->deaths < lowest_deaths ) + { + best_player = i; + lowest_deaths = pl_info->deaths; + highest_frags = pl_info->frags; + } + } + } + } + + if ( !best_player ) + break; + + // If we haven't created the Team yet, do it first + if (!bCreatedTeam && iTeam) + { + m_iIsATeam[ m_iRows ] = iTeam; + m_iRows++; + + bCreatedTeam = true; + } + + // Put this player in the sorted list + m_iSortedRows[ m_iRows ] = best_player; + m_bHasBeenSorted[ best_player ] = true; + m_iRows++; + } + + if (team) + { + m_iIsATeam[m_iRows++] = TEAM_BLANK; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Recalculate the existing teams in the match +//----------------------------------------------------------------------------- +void ScorePanel::RebuildTeams() +{ + // clear out player counts from teams + for ( int i = 1; i <= m_iNumTeams; i++ ) + { + g_TeamInfo[i].players = 0; + } + + // rebuild the team list + gViewPort->GetAllPlayersInfo(); + m_iNumTeams = 0; + for ( int i = 1; i < MAX_PLAYERS; i++ ) + { + if ( g_PlayerInfoList[i].name == NULL ) + continue; + + if ( g_PlayerExtraInfo[i].teamname[0] == 0 ) + continue; // skip over players who are not in a team + + // is this player in an existing team? + int j; + for ( j = 1; j <= m_iNumTeams; j++ ) + { + if ( g_TeamInfo[j].name[0] == '\0' ) + break; + + if ( !stricmp( g_PlayerExtraInfo[i].teamname, g_TeamInfo[j].name ) ) + break; + } + + if ( j > m_iNumTeams ) + { // they aren't in a listed team, so make a new one + // search through for an empty team slot + for ( j = 1; j <= m_iNumTeams; j++ ) + { + if ( g_TeamInfo[j].name[0] == '\0' ) + break; + } + m_iNumTeams = max( j, m_iNumTeams ); + + strncpy( g_TeamInfo[j].name, g_PlayerExtraInfo[i].teamname, MAX_TEAM_NAME ); + g_TeamInfo[j].players = 0; + } + + g_TeamInfo[j].players++; + } + + // clear out any empty teams + for ( int i = 1; i <= m_iNumTeams; i++ ) + { + if ( g_TeamInfo[i].players < 1 ) + memset( &g_TeamInfo[i], 0, sizeof(team_info_t) ); + } + + // Update the scoreboard + Update(); +} + + +void ScorePanel::FillGrid() +{ + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + SchemeHandle_t hScheme = pSchemes->getSchemeHandle("Scoreboard Text"); + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle("Scoreboard Title Text"); + SchemeHandle_t hSmallScheme = pSchemes->getSchemeHandle("Scoreboard Small Text"); + + Font *sfont = pSchemes->getFont(hScheme); + Font *tfont = pSchemes->getFont(hTitleScheme); + Font *smallfont = pSchemes->getFont(hSmallScheme); + + // update highlight position + int x, y; + getApp()->getCursorPos( x, y ); + screenToLocal( x, y ); + cursorMoved( x, y, this ); + + // remove highlight row if we're not in squelch mode + if (!GetClientVoiceMgr()->IsInSquelchMode()) + { + m_iHighlightRow = -1; + } + + bool bNextRowIsGap = false; + + for(int row=0; row < NUM_ROWS; row++) + { + CGrid *pGridRow = &m_PlayerGrids[row]; + pGridRow->SetRowUnderline(0, false, 0, 0, 0, 0, 0); + + if(row >= m_iRows) + { + for(int col=0; col < NUM_COLUMNS; col++) + m_PlayerEntries[col][row].setVisible(false); + + continue; + } + + bool bRowIsGap = false; + if (bNextRowIsGap) + { + bNextRowIsGap = false; + bRowIsGap = true; + } + + for(int col=0; col < NUM_COLUMNS; col++) + { + CLabelHeader *pLabel = &m_PlayerEntries[col][row]; + + pLabel->setVisible(true); + pLabel->setText2(""); + pLabel->setImage(NULL); + pLabel->setFont(sfont); + pLabel->setTextOffset(0, 0); + + int rowheight = 13; + if (ScreenHeight > 480) + { + rowheight = YRES(rowheight); + } + else + { + // more tweaking, make sure icons fit at low res + rowheight = 15; + } + pLabel->setSize(pLabel->getWide(), rowheight); + pLabel->setBgColor(0, 0, 0, 255); + + char sz[128]; + hud_player_info_t *pl_info = NULL; + team_info_t *team_info = NULL; + + if (m_iIsATeam[row] == TEAM_BLANK) + { + pLabel->setText(" "); + continue; + } + else if ( m_iIsATeam[row] == TEAM_YES ) + { + // Get the team's data + team_info = &g_TeamInfo[ m_iSortedRows[row] ]; + + // team color text for team names + pLabel->setFgColor( iTeamColors[team_info->teamnumber][0], iTeamColors[team_info->teamnumber][1], iTeamColors[team_info->teamnumber][2], 0 ); + + // different height for team header rows + rowheight = 20; + if (ScreenHeight >= 480) + { + rowheight = YRES(rowheight); + } + pLabel->setSize(pLabel->getWide(), rowheight); + pLabel->setFont(tfont); + + pGridRow->SetRowUnderline(0, true, YRES(3), iTeamColors[team_info->teamnumber][0], iTeamColors[team_info->teamnumber][1], iTeamColors[team_info->teamnumber][2], 0); + } + else if ( m_iIsATeam[row] == TEAM_SPECTATORS ) + { + // grey text for spectators + pLabel->setFgColor(100, 100, 100, 0); + + // different height for team header rows + rowheight = 20; + if (ScreenHeight >= 480) + { + rowheight = YRES(rowheight); + } + pLabel->setSize(pLabel->getWide(), rowheight); + pLabel->setFont(tfont); + + pGridRow->SetRowUnderline(0, true, YRES(3), 100, 100, 100, 0); + } + else + { + // team color text for player names + pLabel->setFgColor( iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber ][0], iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber ][1], iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber ][2], 0 ); + + // Get the player's data + pl_info = &g_PlayerInfoList[ m_iSortedRows[row] ]; + + // Set background color + if ( pl_info->thisplayer ) // if it is their name, draw it a different color + { + // Highlight this player + pLabel->setFgColor(Scheme::sc_white); + pLabel->setBgColor( iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber ][0], iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber ][1], iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber ][2], 196 ); + } + else if ( m_iSortedRows[row] == m_iLastKilledBy && m_fLastKillTime && m_fLastKillTime > gHUD.m_flTime ) + { + // Killer's name + pLabel->setBgColor( 255,0,0, 255 - ((float)15 * (float)(m_fLastKillTime - gHUD.m_flTime)) ); + } + } + + // Align + if (col == COLUMN_NAME) + { + pLabel->setContentAlignment( vgui::Label::a_west ); + } + else if (col == COLUMN_TRACKER) + { + pLabel->setContentAlignment( vgui::Label::a_center ); + } + else + { + pLabel->setContentAlignment( vgui::Label::a_east ); + } + + // Fill out with the correct data + strcpy(sz, ""); + if ( m_iIsATeam[row] ) + { + char sz2[128]; + + switch (col) + { + case COLUMN_NAME: + if ( m_iIsATeam[row] == TEAM_SPECTATORS ) + { + sprintf( sz2, CHudTextMessage::BufferedLocaliseTextString( "#Spectators" ) ); + } + else + { + sprintf( sz2, CHudTextMessage::BufferedLocaliseTextString( team_info->name ) ); + } + + strcpy(sz, sz2); + + // Append the number of players + if ( m_iIsATeam[row] == TEAM_YES ) + { + if (team_info->players == 1) + { + sprintf(sz2, "(%d %s)", team_info->players, CHudTextMessage::BufferedLocaliseTextString( "#Player" ) ); + } + else + { + sprintf(sz2, "(%d %s)", team_info->players, CHudTextMessage::BufferedLocaliseTextString( "#Player_plural" ) ); + } + + pLabel->setText2(sz2); + pLabel->setFont2(smallfont); + } + break; + case COLUMN_VOICE: + break; + case COLUMN_KILLS: + if ( m_iIsATeam[row] == TEAM_YES ) + sprintf(sz, "%d", team_info->frags ); + break; + case COLUMN_DEATHS: + if ( m_iIsATeam[row] == TEAM_YES ) + sprintf(sz, "%d", team_info->deaths ); + break; + case COLUMN_LATENCY: + if ( m_iIsATeam[row] == TEAM_YES ) + sprintf(sz, "%d", team_info->ping ); + break; + default: + break; + } + } + else + { + bool bShowClass = false; + + switch (col) + { + case COLUMN_NAME: + sprintf(sz, "%s ", pl_info->name); + break; + case COLUMN_VOICE: + sz[0] = 0; + // in HLTV mode allow spectator to turn on/off commentator voice + if (!pl_info->thisplayer || gEngfuncs.IsSpectateOnly() ) + { + GetClientVoiceMgr()->UpdateSpeakerImage(pLabel, m_iSortedRows[row]); + } + break; + case COLUMN_KILLS: + sprintf(sz, "%d", g_PlayerExtraInfo[ m_iSortedRows[row] ].frags ); + break; + case COLUMN_DEATHS: + sprintf(sz, "%d", g_PlayerExtraInfo[ m_iSortedRows[row] ].deaths ); + break; + case COLUMN_LATENCY: + sprintf(sz, "%d", g_PlayerInfoList[ m_iSortedRows[row] ].ping ); + break; + case COLUMN_TRACKER: + default: + break; + } + } + + pLabel->setText(sz); + } + } + + for(int row = 0; row < NUM_ROWS; row++) + { + CGrid *pGridRow = &m_PlayerGrids[row]; + + pGridRow->AutoSetRowHeights(); + pGridRow->setSize(PanelWidth(pGridRow), pGridRow->CalcDrawHeight()); + pGridRow->RepositionContents(); + } + + // hack, for the thing to resize + m_PlayerList.getSize(x, y); + m_PlayerList.setSize(x, y); +} + + +//----------------------------------------------------------------------------- +// Purpose: Setup highlights for player names in scoreboard +//----------------------------------------------------------------------------- +void ScorePanel::DeathMsg( int killer, int victim ) +{ + // if we were the one killed, or the world killed us, set the scoreboard to indicate suicide + if ( victim == m_iPlayerNum || killer == 0 ) + { + m_iLastKilledBy = killer ? killer : m_iPlayerNum; + m_fLastKillTime = gHUD.m_flTime + 10; // display who we were killed by for 10 seconds + + if ( killer == m_iPlayerNum ) + m_iLastKilledBy = m_iPlayerNum; + } +} + + +void ScorePanel::Open( void ) +{ + RebuildTeams(); + setVisible(true); + m_HitTestPanel.setVisible(true); +} + + +void ScorePanel::mousePressed(MouseCode code, Panel* panel) +{ + if(gHUD.m_iIntermission) + return; + + if (!GetClientVoiceMgr()->IsInSquelchMode()) + { + GetClientVoiceMgr()->StartSquelchMode(); + m_HitTestPanel.setVisible(false); + } + else if (m_iHighlightRow >= 0) + { + // mouse has been pressed, toggle mute state + int iPlayer = m_iSortedRows[m_iHighlightRow]; + if (iPlayer > 0) + { + // print text message + hud_player_info_t *pl_info = &g_PlayerInfoList[iPlayer]; + + if (pl_info && pl_info->name && pl_info->name[0]) + { + char string[256]; + if (GetClientVoiceMgr()->IsPlayerBlocked(iPlayer)) + { + char string1[1024]; + + // remove mute + GetClientVoiceMgr()->SetPlayerBlockedState(iPlayer, false); + + sprintf( string1, CHudTextMessage::BufferedLocaliseTextString( "#Unmuted" ), pl_info->name ); + sprintf( string, "%c** %s\n", HUD_PRINTTALK, string1 ); + + gHUD.m_TextMessage.MsgFunc_TextMsg(NULL, strlen(string)+1, string ); + } + else + { + char string1[1024]; + char string2[1024]; + + // mute the player + GetClientVoiceMgr()->SetPlayerBlockedState(iPlayer, true); + + sprintf( string1, CHudTextMessage::BufferedLocaliseTextString( "#Muted" ), pl_info->name ); + sprintf( string2, CHudTextMessage::BufferedLocaliseTextString( "#No_longer_hear_that_player" ) ); + sprintf( string, "%c** %s %s\n", HUD_PRINTTALK, string1, string2 ); + + gHUD.m_TextMessage.MsgFunc_TextMsg(NULL, strlen(string)+1, string ); + } + } + } + } +} + + +void ScorePanel::cursorMoved(int x, int y, Panel *panel) +{ + // Translate from local coordinates to screen coordinates. + panel->localToScreen( x, y ); + + if (GetClientVoiceMgr()->IsInSquelchMode()) + { + // look for which cell the mouse is currently over + for (int i = 0; i < NUM_ROWS; i++) + { + int row, col; + if (m_PlayerGrids[i].getCellAtPoint(x, y, row, col)) + { + MouseOverCell(i, col); + return; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handles mouse movement over a cell +// Input : row - +// col - +//----------------------------------------------------------------------------- +void ScorePanel::MouseOverCell(int row, int col) +{ + CLabelHeader *label = &m_PlayerEntries[col][row]; + + // clear the previously highlighted label + if (m_pCurrentHighlightLabel != label) + { + m_pCurrentHighlightLabel = NULL; + m_iHighlightRow = -1; + } + if (!label) + return; + + // don't act on teams + if (m_iIsATeam[row] != TEAM_NO) + return; + + // don't act on disconnected players or ourselves + hud_player_info_t *pl_info = &g_PlayerInfoList[ m_iSortedRows[row] ]; + if (!pl_info->name || !pl_info->name[0]) + return; + + if (pl_info->thisplayer && !gEngfuncs.IsSpectateOnly() ) + return; + + // setup the new highlight + m_pCurrentHighlightLabel = label; + m_iHighlightRow = row; +} + +//----------------------------------------------------------------------------- +// Purpose: Label paint functions - take into account current highligh status +//----------------------------------------------------------------------------- +void CLabelHeader::paintBackground() +{ + Color oldBg; + getBgColor(oldBg); + + if (gViewPort->m_pScoreBoard->m_iHighlightRow == _row) + { + setBgColor(134, 91, 19, 0); + } + + Panel::paintBackground(); + + setBgColor(oldBg); +} + + +//----------------------------------------------------------------------------- +// Purpose: Label paint functions - take into account current highligh status +//----------------------------------------------------------------------------- +void CLabelHeader::paint() +{ + Color oldFg; + getFgColor(oldFg); + + if (gViewPort->m_pScoreBoard->m_iHighlightRow == _row) + { + setFgColor(255, 255, 255, 0); + } + + // draw text + int x, y, iwide, itall; + getTextSize(iwide, itall); + calcAlignment(iwide, itall, x, y); + _dualImage->setPos(x, y); + + int x1, y1; + _dualImage->GetImage(1)->getPos(x1, y1); + _dualImage->GetImage(1)->setPos(_gap, y1); + + _dualImage->doPaint(this); + + // get size of the panel and the image + if (_image) + { + _image->getSize(iwide, itall); + calcAlignment(iwide, itall, x, y); + _image->setPos(x, y); + _image->doPaint(this); + } + + setFgColor(oldFg[0], oldFg[1], oldFg[2], oldFg[3]); +} + + +void CLabelHeader::calcAlignment(int iwide, int itall, int &x, int &y) +{ + // calculate alignment ourselves, since vgui is so broken + int wide, tall; + getSize(wide, tall); + + x = 0, y = 0; + + // align left/right + switch (_contentAlignment) + { + // left + case Label::a_northwest: + case Label::a_west: + case Label::a_southwest: + { + x = 0; + break; + } + + // center + case Label::a_north: + case Label::a_center: + case Label::a_south: + { + x = (wide - iwide) / 2; + break; + } + + // right + case Label::a_northeast: + case Label::a_east: + case Label::a_southeast: + { + x = wide - iwide; + break; + } + } + + // top/down + switch (_contentAlignment) + { + // top + case Label::a_northwest: + case Label::a_north: + case Label::a_northeast: + { + y = 0; + break; + } + + // center + case Label::a_west: + case Label::a_center: + case Label::a_east: + { + y = (tall - itall) / 2; + break; + } + + // south + case Label::a_southwest: + case Label::a_south: + case Label::a_southeast: + { + y = tall - itall; + break; + } + } + +// don't clip to Y +// if (y < 0) +// { +// y = 0; +// } + if (x < 0) + { + x = 0; + } + + x += _offset[0]; + y += _offset[1]; +} diff --git a/hlsdk/dmc/cl_dll/vgui_ScorePanel.h b/hlsdk/dmc/cl_dll/vgui_ScorePanel.h new file mode 100644 index 00000000..4eb1f1c6 --- /dev/null +++ b/hlsdk/dmc/cl_dll/vgui_ScorePanel.h @@ -0,0 +1,314 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef SCOREPANEL_H +#define SCOREPANEL_H + +#include +#include +#include +#include +#include +#include +#include "vgui_listbox.h" + +#include + +#define MAX_SCORES 10 +#define MAX_SCOREBOARD_TEAMS 5 + +// Scoreboard cells +#define COLUMN_TRACKER 0 +#define COLUMN_NAME 1 +#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; + +class CTextImage2 : public Image +{ +public: + CTextImage2() + { + _image[0] = new TextImage(""); + _image[1] = new TextImage(""); + } + + ~CTextImage2() + { + delete _image[0]; + delete _image[1]; + } + + TextImage *GetImage(int image) + { + return _image[image]; + } + + void getSize(int &wide, int &tall) + { + int w1, w2, t1, t2; + _image[0]->getTextSize(w1, t1); + _image[1]->getTextSize(w2, t2); + + wide = w1 + w2; + tall = max(t1, t2); + setSize(wide, tall); + } + + void doPaint(Panel *panel) + { + _image[0]->doPaint(panel); + _image[1]->doPaint(panel); + } + + void setPos(int x, int y) + { + _image[0]->setPos(x, y); + + int swide, stall; + _image[0]->getSize(swide, stall); + + int wide, tall; + _image[1]->getSize(wide, tall); + _image[1]->setPos(x + wide, y + (stall * 0.9) - tall); + } + + void setColor(Color color) + { + _image[0]->setColor(color); + } + + void setColor2(Color color) + { + _image[1]->setColor(color); + } + +private: + TextImage *_image[2]; + +}; + +//----------------------------------------------------------------------------- +// Purpose: Custom label for cells in the Scoreboard's Table Header +//----------------------------------------------------------------------------- +class CLabelHeader : public Label +{ +public: + CLabelHeader() : Label("") + { + _dualImage = new CTextImage2(); + _dualImage->setColor2(Color(255, 170, 0, 0)); + _row = -2; + _useFgColorAsImageColor = true; + _offset[0] = 0; + _offset[1] = 0; + } + + ~CLabelHeader() + { + delete _dualImage; + } + + void setRow(int row) + { + _row = row; + } + + void setFgColorAsImageColor(bool state) + { + _useFgColorAsImageColor = state; + } + + virtual void setText(int textBufferLen, const char* text) + { + _dualImage->GetImage(0)->setText(text); + + // calculate the text size + Font *font = _dualImage->GetImage(0)->getFont(); + _gap = 0; + for (const char *ch = text; *ch != 0; ch++) + { + int a, b, c; + font->getCharABCwide(*ch, a, b, c); + _gap += (a + b + c); + } + + _gap += XRES(5); + } + + virtual void setText(const char* text) + { + // strip any non-alnum characters from the end + char buf[512]; + strcpy(buf, text); + + int len = strlen(buf); + while (len && isspace(buf[--len])) + { + buf[len] = 0; + } + + CLabelHeader::setText(0, buf); + } + + void setText2(const char *text) + { + _dualImage->GetImage(1)->setText(text); + } + + void getTextSize(int &wide, int &tall) + { + _dualImage->getSize(wide, tall); + } + + void setFgColor(int r,int g,int b,int a) + { + Label::setFgColor(r,g,b,a); + Color color(r,g,b,a); + _dualImage->setColor(color); + _dualImage->setColor2(color); + if (_image && _useFgColorAsImageColor) + { + _image->setColor(color); + } + repaint(); + } + + void setFgColor(Scheme::SchemeColor sc) + { + Label::setFgColor(sc); + _dualImage->setColor(sc); + } + + void setFont(Font *font) + { + _dualImage->GetImage(0)->setFont(font); + } + + void setFont2(Font *font) + { + _dualImage->GetImage(1)->setFont(font); + } + + // this adjust the absolute position of the text after alignment is calculated + void setTextOffset(int x, int y) + { + _offset[0] = x; + _offset[1] = y; + } + + void paint(); + void paintBackground(); + void calcAlignment(int iwide, int itall, int &x, int &y); + +private: + CTextImage2 *_dualImage; + int _row; + int _gap; + int _offset[2]; + bool _useFgColorAsImageColor; +}; + +class ScoreTablePanel; + +#include "vgui_grid.h" +#include "vgui_defaultinputsignal.h" + +//----------------------------------------------------------------------------- +// Purpose: Scoreboard back panel +//----------------------------------------------------------------------------- +class ScorePanel : public Panel, public vgui::CDefaultInputSignal +{ +private: + // Default panel implementation doesn't forward mouse messages when there is no cursor and we need them. + class HitTestPanel : public Panel + { + public: + virtual void internalMousePressed(MouseCode code); + }; + + +private: + + Label m_TitleLabel; + + // Here is how these controls are arranged hierarchically. + // m_HeaderGrid + // m_HeaderLabels + + // m_PlayerGridScroll + // m_PlayerGrid + // m_PlayerEntries + + CGrid m_HeaderGrid; + CLabelHeader m_HeaderLabels[NUM_COLUMNS]; // Labels above the + CLabelHeader *m_pCurrentHighlightLabel; + int m_iHighlightRow; + + 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. + + ScorePanel::HitTestPanel m_HitTestPanel; + + CLabelHeader* GetPlayerEntry(int x, int y) {return &m_PlayerEntries[x][y];} + +public: + + int m_iNumTeams; + int m_iPlayerNum; + int m_iShowscoresHeld; + + int m_iRows; + int m_iSortedRows[NUM_ROWS]; + int m_iIsATeam[NUM_ROWS]; + bool m_bHasBeenSorted[MAX_PLAYERS]; + int m_iLastKilledBy; + int m_fLastKillTime; + + CImageLabel *m_pImages[ 7 ]; + + +public: + + ScorePanel(int x,int y,int wide,int tall); + + void Update( void ); + + void SortTeams( void ); + void SortPlayers( int iTeam, char *team ); + void RebuildTeams( void ); + + void FillGrid(); + + void DeathMsg( int killer, int victim ); + + void Initialize( void ); + + void Open( void ); + + void MouseOverCell(int row, int col); + + +// InputSignal overrides. +public: + + virtual void mousePressed(MouseCode code, Panel* panel); + virtual void cursorMoved(int x, int y, Panel *panel); + + friend CLabelHeader; +}; + +#endif + diff --git a/hlsdk/dmc/cl_dll/vgui_ServerBrowser.cpp b/hlsdk/dmc/cl_dll/vgui_ServerBrowser.cpp new file mode 100644 index 00000000..55b89cd5 --- /dev/null +++ b/hlsdk/dmc/cl_dll/vgui_ServerBrowser.cpp @@ -0,0 +1,623 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include +#include +#include +#include +#include + +#include "hud.h" +#include "cl_util.h" +#include "hud_servers.h" +#include "net_api.h" + +#include "vgui_viewport.h" +#include "vgui_ServerBrowser.h" + +using namespace vgui; + +namespace +{ + +#define MAX_SB_ROWS 24 + +#define NUM_COLUMNS 5 + +#define HEADER_SIZE_Y YRES(18) + +// Column sizes +#define CSIZE_ADDRESS XRES(200) +#define CSIZE_SERVER XRES(400) +#define CSIZE_MAP XRES(500) +#define CSIZE_CURRENT XRES(570) +#define CSIZE_PING XRES(640) + +#define CELL_HEIGHT YRES(15) + +class ServerBrowserTablePanel; + +class CBrowser_InputSignal : public InputSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; +public: + CBrowser_InputSignal( ServerBrowserTablePanel *pBrowser ) + { + m_pBrowser = pBrowser; + } + + 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); + + 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 ServerBrowserTablePanel : public TablePanel +{ +private: + Label *m_pLabel; + int m_nMouseOverRow; + +public: + + ServerBrowserTablePanel( int x,int y,int wide,int tall,int columnCount) : TablePanel( x,y,wide,tall,columnCount) + { + m_pLabel = new Label( "", 0, 0 /*,wide, tall*/ ); + + m_nMouseOverRow = 0; + } + +public: + void setMouseOverRow( int row ) + { + m_nMouseOverRow = row; + } + + void DoSort( char *sortkey ) + { + // Request server list and refresh servers... + SortServers( sortkey ); + } + + void DoRefresh( void ) + { + // Request server list and refresh servers... + ServersList(); + BroadcastServersList( 0 ); + } + + void DoBroadcastRefresh( void ) + { + // Request server list and refresh servers... + BroadcastServersList( 1 ); + } + + void DoStop( void ) + { + // Stop requesting + ServersCancel(); + } + + void DoCancel( void ) + { + ClientCmd( "togglebrowser\n" ); + } + + void DoConnect( void ) + { + const char *info; + const char *address; + char sz[ 256 ]; + + info = ServersGetInfo( m_nMouseOverRow ); + if ( !info ) + return; + + address = gEngfuncs.pNetAPI->ValueForKey( info, "address" ); + //gEngfuncs.Con_Printf( "Connecting to %s\n", address ); + + sprintf( sz, "connect %s\n", address ); + + ClientCmd( sz ); + + DoCancel(); + } + + void DoPing( void ) + { + ServerPing( 0 ); + ServerRules( 0 ); + ServerPlayers( 0 ); + } + + virtual int getRowCount() + { + int rowcount; + int height, width; + + getSize( width, height ); + + // Space for buttons + height -= YRES(20); + height = max( 0, height ); + + rowcount = height / CELL_HEIGHT; + + return rowcount; + } + + virtual int getCellTall(int row) + { + return CELL_HEIGHT - 2; + } + + virtual Panel* getCellRenderer(int column,int row,bool columnSelected,bool rowSelected,bool cellSelected) + { + const char *info; + const char *val, *val2; + char sz[ 32 ]; + + info = ServersGetInfo( row ); + + if ( row == m_nMouseOverRow ) + { + m_pLabel->setFgColor( 200, 240, 63, 100 ); + } + else + { + m_pLabel->setFgColor( 255, 255, 255, 0 ); + } + m_pLabel->setBgColor( 0, 0, 0, 200 ); + m_pLabel->setContentAlignment( vgui::Label::a_west ); + m_pLabel->setFont( Scheme::sf_primary2 ); + + if ( info ) + { + // Fill out with the correct data + switch ( column ) + { + case 0: + val = gEngfuncs.pNetAPI->ValueForKey( info, "address" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Name; + m_pLabel->setText( sz ); + } + break; + case 1: + val = gEngfuncs.pNetAPI->ValueForKey( info, "hostname" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Map; + m_pLabel->setText( sz ); + } + break; + case 2: + val = gEngfuncs.pNetAPI->ValueForKey( info, "map" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Name; + m_pLabel->setText( sz ); + } + break; + case 3: + val = gEngfuncs.pNetAPI->ValueForKey( info, "current" ); + val2 = gEngfuncs.pNetAPI->ValueForKey( info, "max" ); + if ( val && val2 ) + { + sprintf( sz, "%s/%s", val, val2 ); + sz[ 31 ] = '\0'; + // Server Map; + m_pLabel->setText( sz ); + } + break; + case 4: + val = gEngfuncs.pNetAPI->ValueForKey( info, "ping" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Name; + m_pLabel->setText( sz ); + } + break; + default: + break; + } + } + else + { + if ( !row && !column ) + { + if ( ServersIsQuerying() ) + { + m_pLabel->setText( "Waiting for servers to respond..." ); + } + else + { + m_pLabel->setText( "Press 'Refresh' to search for servers..." ); + } + } + else + { + m_pLabel->setText( "" ); + } + } + + return m_pLabel; + } + + virtual Panel* startCellEditing(int column,int row) + { + return null; + } + +}; + +class ConnectHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + ConnectHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoConnect(); + } +}; + +class RefreshHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + RefreshHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoRefresh(); + } +}; + +class BroadcastRefreshHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + BroadcastRefreshHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoBroadcastRefresh(); + } +}; + +class StopHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + StopHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoStop(); + } +}; + +class CancelHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + CancelHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoCancel(); + } +}; + +class PingHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + PingHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoPing(); + } +}; + +class SortHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + SortHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoSort( "map" ); + } +}; + +} + +class LabelSortInputHandler : public InputSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + char m_szSortKey[ 64 ]; + +public: + LabelSortInputHandler( ServerBrowserTablePanel *pBrowser, char *name ) + { + m_pBrowser = pBrowser; + strcpy( m_szSortKey, name ); + } + + 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) + { + m_pBrowser->DoSort( m_szSortKey ); + } + + virtual void mouseDoublePressed(MouseCode code,Panel* panel) + { + m_pBrowser->DoSort( m_szSortKey ); + } + + 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 CSBLabel : public Label +{ + +private: + char m_szSortKey[ 64 ]; + ServerBrowserTablePanel *m_pBrowser; + +public: + CSBLabel( char *name, char *sortkey ) : Label( name ) + { + m_pBrowser = NULL; + + strcpy( m_szSortKey, sortkey ); + + int label_bg_r = 120, + label_bg_g = 75, + label_bg_b = 32, + label_bg_a = 200; + + int label_fg_r = 255, + label_fg_g = 0, + label_fg_b = 0, + label_fg_a = 0; + + setContentAlignment( vgui::Label::a_west ); + setFgColor( label_fg_r, label_fg_g, label_fg_b, label_fg_a ); + setBgColor( label_bg_r, label_bg_g, label_bg_b, label_bg_a ); + setFont( Scheme::sf_primary2 ); + + } + + void setTable( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + + addInputSignal( new LabelSortInputHandler( (ServerBrowserTablePanel * )m_pBrowser, m_szSortKey ) ); + } +}; + +ServerBrowser::ServerBrowser(int x,int y,int wide,int tall) : CTransparentPanel( 100, x,y,wide,tall ) +{ + int i; + + _headerPanel = new HeaderPanel(0,0,wide,HEADER_SIZE_Y); + _headerPanel->setParent(this); + _headerPanel->setFgColor( 100,100,100, 100 ); + _headerPanel->setBgColor( 0, 0, 0, 100 ); + + CSBLabel *pLabel[5]; + + pLabel[0] = new CSBLabel( "Address", "address" ); + pLabel[1] = new CSBLabel( "Server", "hostname" ); + pLabel[2] = new CSBLabel( "Map", "map" ); + pLabel[3] = new CSBLabel( "Current", "current" ); + pLabel[4] = new CSBLabel( "Latency", "ping" ); + + for ( i = 0; i < 5; i++ ) + { + _headerPanel->addSectionPanel( pLabel[i] ); + } + + // _headerPanel->setFont( Scheme::sf_primary1 ); + + _headerPanel->setSliderPos( 0, CSIZE_ADDRESS ); + _headerPanel->setSliderPos( 1, CSIZE_SERVER ); + _headerPanel->setSliderPos( 2, CSIZE_MAP ); + _headerPanel->setSliderPos( 3, CSIZE_CURRENT ); + _headerPanel->setSliderPos( 4, CSIZE_PING ); + + _tablePanel = new ServerBrowserTablePanel( 0, HEADER_SIZE_Y, wide, tall - HEADER_SIZE_Y, NUM_COLUMNS ); + _tablePanel->setParent(this); + _tablePanel->setHeaderPanel(_headerPanel); + _tablePanel->setFgColor( 100,100,100, 100 ); + _tablePanel->setBgColor( 0, 0, 0, 100 ); + + _tablePanel->addInputSignal( new CBrowser_InputSignal( (ServerBrowserTablePanel *)_tablePanel ) ); + + for ( i = 0; i < 5; i++ ) + { + pLabel[i]->setTable( (ServerBrowserTablePanel * )_tablePanel ); + } + + int bw = 80, bh = 15; + int by = tall - HEADER_SIZE_Y; + + int btnx = 10; + + _connectButton = new CommandButton( "Connect", btnx, by, bw, bh ); + _connectButton->setParent( this ); + _connectButton->addActionSignal( new ConnectHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + btnx += bw; + + _refreshButton = new CommandButton( "Refresh", btnx, by, bw, bh ); + _refreshButton->setParent( this ); + _refreshButton->addActionSignal( new RefreshHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + /* + btnx += bw; + + _broadcastRefreshButton = new CommandButton( "LAN", btnx, by, bw, bh ); + _broadcastRefreshButton->setParent( this ); + _broadcastRefreshButton->addActionSignal( new BroadcastRefreshHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + */ + + btnx += bw; + + _stopButton = new CommandButton( "Stop", btnx, by, bw, bh ); + _stopButton->setParent( this ); + _stopButton->addActionSignal( new StopHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + /* + btnx += bw; + + _pingButton = new CommandButton( "Test", btnx, by, bw, bh ); + _pingButton->setParent( this ); + _pingButton->addActionSignal( new PingHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + btnx += bw; + + _sortButton = new CommandButton( "Sort", btnx, by, bw, bh ); + _sortButton->setParent( this ); + _sortButton->addActionSignal( new SortHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + */ + + btnx += bw; + + _cancelButton = new CommandButton( "Close", btnx, by, bw, bh ); + _cancelButton->setParent( this ); + _cancelButton->addActionSignal( new CancelHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + setPaintBorderEnabled(false); + setPaintBackgroundEnabled(false); + setPaintEnabled(false); + +} + +void ServerBrowser::setSize(int wide,int tall) +{ + Panel::setSize(wide,tall); + + _headerPanel->setBounds(0,0,wide,HEADER_SIZE_Y); + _tablePanel->setBounds(0,HEADER_SIZE_Y,wide,tall - HEADER_SIZE_Y); + + _connectButton->setBounds( 5, tall - HEADER_SIZE_Y, 75, 15 ); + _refreshButton->setBounds( 85, tall - HEADER_SIZE_Y, 75, 15 ); + /* + _broadcastRefreshButton->setBounds( 165, tall - HEADER_SIZE_Y, 75, 15 ); + */ + _stopButton->setBounds( 165, tall - HEADER_SIZE_Y, 75, 15 ); + /* + _pingButton->setBounds( 325, tall - HEADER_SIZE_Y, 75, 15 ); + */ + _cancelButton->setBounds( 245, tall - HEADER_SIZE_Y, 75, 15 ); +} + +void CBrowser_InputSignal::mousePressed(MouseCode code,Panel* panel) +{ + int x, y; + int therow = 2; + + if ( code != MOUSE_LEFT ) + return; + + panel->getApp()->getCursorPos(x,y); + panel->screenToLocal( x, y ); + + therow = y / CELL_HEIGHT; + + // Figure out which row it's on + m_pBrowser->setMouseOverRow( therow ); +} + +void CBrowser_InputSignal::mouseDoublePressed(MouseCode code,Panel* panel) +{ + int x, y; + int therow = 2; + + if ( code != MOUSE_LEFT ) + return; + + panel->getApp()->getCursorPos(x,y); + panel->screenToLocal( x, y ); + + therow = y / CELL_HEIGHT; + + // Figure out which row it's on + m_pBrowser->setMouseOverRow( therow ); + m_pBrowser->DoConnect(); +} diff --git a/hlsdk/dmc/cl_dll/vgui_ServerBrowser.h b/hlsdk/dmc/cl_dll/vgui_ServerBrowser.h new file mode 100644 index 00000000..01f56935 --- /dev/null +++ b/hlsdk/dmc/cl_dll/vgui_ServerBrowser.h @@ -0,0 +1,50 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef ServerBrowser_H +#define ServerBrowser_H + +#include + +namespace vgui +{ +class Button; +class TablePanel; +class HeaderPanel; +} + +class CTransparentPanel; +class CommandButton; + +// Scoreboard positions +#define SB_X_INDENT (20 * ((float)ScreenHeight / 640)) +#define SB_Y_INDENT (20 * ((float)ScreenHeight / 480)) + +class ServerBrowser : public CTransparentPanel +{ +private: + HeaderPanel * _headerPanel; + TablePanel* _tablePanel; + + CommandButton* _connectButton; + CommandButton* _refreshButton; + CommandButton* _broadcastRefreshButton; + CommandButton* _stopButton; + CommandButton* _sortButton; + CommandButton* _cancelButton; + + CommandButton* _pingButton; + +public: + ServerBrowser(int x,int y,int wide,int tall); +public: + virtual void setSize(int wide,int tall); +}; + + + +#endif diff --git a/hlsdk/dmc/cl_dll/vgui_SpectatorPanel.cpp b/hlsdk/dmc/cl_dll/vgui_SpectatorPanel.cpp new file mode 100644 index 00000000..e3a85d60 --- /dev/null +++ b/hlsdk/dmc/cl_dll/vgui_SpectatorPanel.cpp @@ -0,0 +1,204 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// vgui_SpectatorPanel.cpp: implementation of the SpectatorPanel class. +// +////////////////////////////////////////////////////////////////////// + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "pm_shared.h" +#include "vgui_viewport.h" +#include "vgui_SpectatorPanel.h" + + + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +SpectatorPanel::SpectatorPanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) +{ +} + +SpectatorPanel::~SpectatorPanel() +{ + +} + +void SpectatorPanel::ActionSignal(int cmd) +{ + switch (cmd) + { + case SPECTATOR_PANEL_CMD_NONE : break; + + case SPECTATOR_PANEL_CMD_OPTIONS : gViewPort->ShowCommandMenu( gViewPort->m_SpectatorMenu ); + break; + + case SPECTATOR_PANEL_CMD_NEXTPLAYER : gHUD.m_Spectator.FindNextPlayer(true); + break; + + case SPECTATOR_PANEL_CMD_PREVPLAYER : gHUD.m_Spectator.FindNextPlayer(false); + break; + + case SPECTATOR_PANEL_CMD_HIDEMENU : ShowMenu(false); + break; + + + + case SPECTATOR_PANEL_CMD_TOGGLE_INSET : gHUD.m_Spectator.SetModes( -1, + gHUD.m_Spectator.ToggleInset(false) ); + break; + + + default : gEngfuncs.Con_DPrintf("Unknown SpectatorPanel ActionSingal %i.\n",cmd); break; + } + +} + + +void SpectatorPanel::Initialize() +{ + int x,y,wide,tall; + + getBounds(x,y,wide,tall); + + CSchemeManager * pSchemes = gViewPort->GetSchemeManager(); + + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle( "Title Font" ); + + m_TopBorder = new CTransparentPanel(64, 0, 0, ScreenWidth, YRES(32)); + m_TopBorder->setParent(this); + + m_BottomBorder = new CTransparentPanel(64, 0, ScreenHeight - YRES(32), ScreenWidth, YRES(32)); + m_BottomBorder->setParent(this); + + setPaintBackgroundEnabled(false); + + // Initialize the bottom title. + m_BottomMainLabel = new Label( "Spectator Bottom", XRES(6+64+6+24+6), YRES(4), XRES(428), YRES(24) ); + m_BottomMainLabel->setParent(m_BottomBorder); + m_BottomMainLabel->setFont( pSchemes->getFont( hTitleScheme ) ); + m_BottomMainLabel->setPaintBackgroundEnabled(false); + m_BottomMainLabel->setFgColor( Scheme::sc_primary1 ); + m_BottomMainLabel->setContentAlignment( vgui::Label::a_center ); + + LineBorder * border = new LineBorder(1, Scheme::sc_secondary1); + m_BottomMainLabel->setBorder(border); + m_BottomMainLabel->setPaintBorderEnabled(true); + + // Initialize the top title. + m_TopMainLabel = new Label( "Spectator Top", 0, 0, wide, YRES(32) ); + m_TopMainLabel->setParent(m_TopBorder); + m_TopMainLabel->setFont( pSchemes->getFont( hTitleScheme ) ); + m_TopMainLabel->setPaintBackgroundEnabled(false); + m_TopMainLabel->setFgColor( Scheme::sc_primary1 ); + m_TopMainLabel->setContentAlignment( vgui::Label::a_center ); + + // Initialize command buttons. + m_OptionButton = new CommandButton("Options", XRES(6), YRES(6), XRES(64), YRES(20) ); + m_OptionButton->setParent( m_BottomBorder ); + m_OptionButton->setContentAlignment( vgui::Label::a_center ); + m_OptionButton->setBoundKey( (char)255 ); // special no bound to avoid leading spaces in name + m_OptionButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_OPTIONS) ); + + m_PrevPlayerButton= new CommandButton("<<", XRES(6+64+6), YRES(6), XRES(24), YRES(20) ); + m_PrevPlayerButton->setParent( m_BottomBorder ); + m_PrevPlayerButton->setContentAlignment( vgui::Label::a_center ); + m_PrevPlayerButton->setBoundKey( (char)255 ); // special no bound to avoid leading spaces in name + m_PrevPlayerButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_PREVPLAYER) ); + + m_NextPlayerButton= new CommandButton(">>", XRES(640-6-64-6-24), YRES(6), XRES(24), YRES(20) ); + m_NextPlayerButton->setParent( m_BottomBorder ); + m_NextPlayerButton->setContentAlignment( vgui::Label::a_center ); + m_NextPlayerButton->setBoundKey( (char)255 ); // special no bound to avoid leading spaces in name + m_NextPlayerButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_NEXTPLAYER) ); + + m_HideButton = new CommandButton("Hide", XRES(640-6-64), YRES(6), XRES(64), YRES(20) ); + m_HideButton->setParent( m_BottomBorder ); + m_HideButton->setContentAlignment( vgui::Label::a_center ); + m_HideButton->setBoundKey( (char)255 ); // special no bound to avoid leading spaces in name + m_HideButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_HIDEMENU) ); + + + m_InsetViewButton = new CommandButton("", XRES(2), YRES(2), XRES(240), YRES(180) ); + m_InsetViewButton->setParent( this ); + m_InsetViewButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_TOGGLE_INSET) ); + + + m_menuVisible = false; + m_HideButton->setVisible(false); + m_OptionButton->setVisible(false); + m_NextPlayerButton->setVisible(false); + m_PrevPlayerButton->setVisible(false); + m_BottomMainLabel->setPaintBorderEnabled(false); + m_TopMainLabel->setVisible(false); + +} + +void SpectatorPanel::ShowMenu(bool isVisible) +{ + m_HideButton->setVisible(isVisible); + m_OptionButton->setVisible(isVisible); + m_NextPlayerButton->setVisible(isVisible); + m_PrevPlayerButton->setVisible(isVisible); + m_BottomMainLabel->setPaintBorderEnabled(isVisible); + m_TopMainLabel->setVisible(isVisible); + + if ( !isVisible ) + { + gViewPort->HideCommandMenu(); + + // if switching from visible menu to invisible menu, show help text + if ( m_menuVisible && this->isVisible() ) + { + char string[ 64 ]; + + _snprintf( string, sizeof( string ) - 1, "%c%s", HUD_PRINTCENTER, CHudTextMessage::BufferedLocaliseTextString( "#Spec_Duck" ) ); + string[ sizeof( string ) - 1 ] = '\0'; + + gHUD.m_TextMessage.MsgFunc_TextMsg( NULL, strlen( string ) + 1, string ); + } + } + + m_menuVisible = isVisible; + + gViewPort->UpdateCursorState(); +} + +void SpectatorPanel::EnableInsetView(bool isEnabled) +{ + int x = gHUD.m_Spectator.m_OverviewData.insetWindowX; + int y = gHUD.m_Spectator.m_OverviewData.insetWindowY; + int wide = gHUD.m_Spectator.m_OverviewData.insetWindowWidth; + int tall = gHUD.m_Spectator.m_OverviewData.insetWindowHeight; + + if ( isEnabled ) + { + // short black bar to see full inset + m_TopBorder->setBounds( XRES(x+wide+2), 0, XRES(640 - (x+wide+2) ), YRES(32) ); + + m_TopMainLabel->setBounds( 0, 0, XRES(640 - (x+wide+2)), YRES(32) ); + + m_InsetViewButton->setBounds( XRES( x ), YRES( y ), + XRES( wide ), YRES( tall ) ); + m_InsetViewButton->setVisible(true); + } + else + { + // full black bar, no inset border + m_TopBorder->setBounds( 0, 0, ScreenWidth, YRES(32) ); + m_TopMainLabel->setBounds( 0, 0, ScreenWidth, YRES(32) ); + m_InsetViewButton->setVisible(false); + } +} + + + diff --git a/hlsdk/dmc/cl_dll/vgui_SpectatorPanel.h b/hlsdk/dmc/cl_dll/vgui_SpectatorPanel.h new file mode 100644 index 00000000..ee1f712c --- /dev/null +++ b/hlsdk/dmc/cl_dll/vgui_SpectatorPanel.h @@ -0,0 +1,93 @@ + +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// vgui_SpectatorPanel.h: interface for the SpectatorPanel class. +// +////////////////////////////////////////////////////////////////////// + +#ifndef SPECTATORPANEL_H +#define SPECTATORPANEL_H + +#include +#include +#include + +using namespace vgui; + +#define SPECTATOR_PANEL_CMD_NONE 0 + +#define SPECTATOR_PANEL_CMD_OPTIONS 1 +#define SPECTATOR_PANEL_CMD_PREVPLAYER 2 +#define SPECTATOR_PANEL_CMD_NEXTPLAYER 3 +#define SPECTATOR_PANEL_CMD_HIDEMENU 4 +#define SPECTATOR_PANEL_CMD_TOGGLE_INSET 5 + + +class SpectatorPanel : public Panel //, public vgui::CDefaultInputSignal +{ + +public: + SpectatorPanel(int x,int y,int wide,int tall); + virtual ~SpectatorPanel(); + + void ActionSignal(int cmd); + + // InputSignal overrides. +public: + void Initialize(); + + + + +public: + + void EnableInsetView(bool isEnabled); + void ShowMenu(bool isVisible); + + + CommandButton * m_OptionButton; + CommandButton * m_HideButton; + CommandButton * m_PrevPlayerButton; + CommandButton * m_NextPlayerButton; + + CTransparentPanel * m_TopBorder; + CTransparentPanel * m_BottomBorder; + + CommandButton * m_InsetViewButton; + + Label * m_TopMainLabel; + Label * m_BottomMainLabel; + + + bool m_menuVisible; +}; + + + +class CSpectatorHandler_Command : public ActionSignal +{ + +private: + SpectatorPanel * m_pFather; + int m_cmd; + +public: + CSpectatorHandler_Command( SpectatorPanel * panel, int cmd ) + { + m_pFather = panel; + m_cmd = cmd; + } + + virtual void actionPerformed( Panel * panel ) + { + m_pFather->ActionSignal(m_cmd); + } +}; + + +#endif // !defined SPECTATORPANEL_H diff --git a/hlsdk/dmc/cl_dll/vgui_int.cpp b/hlsdk/dmc/cl_dll/vgui_int.cpp new file mode 100644 index 00000000..0f4b9e5a --- /dev/null +++ b/hlsdk/dmc/cl_dll/vgui_int.cpp @@ -0,0 +1,128 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include"vgui_int.h" +#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 "vgui_viewport.h" +#include "vgui_ControlConfigPanel.h" + +namespace +{ + +class TexturePanel : public Panel , public ActionSignal +{ +private: + int _bindIndex; + TextEntry* _textEntry; +public: + TexturePanel() : Panel(0,0,256,276) + { + _bindIndex=2700; + _textEntry=new TextEntry("2700",0,0,128,20); + _textEntry->setParent(this); + _textEntry->addActionSignal(this); + } +public: + virtual bool isWithin(int x,int y) + { + return _textEntry->isWithin(x,y); + } +public: + virtual void actionPerformed(Panel* panel) + { + char buf[256]; + _textEntry->getText(0,buf,256); + sscanf(buf,"%d",&_bindIndex); + } +protected: + virtual void paintBackground() + { + Panel::paintBackground(); + + int wide,tall; + getPaintSize(wide,tall); + + drawSetColor(0,0,255,0); + drawSetTexture(_bindIndex); + drawTexturedRect(0,19,257,257); + } + +}; + +} + +using namespace vgui; + +void VGui_ViewportPaintBackground(int extents[4]) +{ + gEngfuncs.VGui_ViewportPaintBackground(extents); +} + +void* VGui_GetPanel() +{ + return (Panel*)gEngfuncs.VGui_GetPanel(); +} + +void VGui_Startup() +{ + Panel* root=(Panel*)VGui_GetPanel(); + root->setBgColor(128,128,0,0); + //root->setNonPainted(false); + //root->setBorder(new LineBorder()); + root->setLayout(new BorderLayout(0)); + + + //root->getSurfaceBase()->setEmulatedCursorVisible(true); + + if (gViewPort != NULL) + { +// root->removeChild(gViewPort); + + // free the memory +// delete gViewPort; +// gViewPort = NULL; + + gViewPort->Initialize(); + } + else + { + gViewPort = new TeamFortressViewport(0,0,root->getWide(),root->getTall()); + gViewPort->setParent(root); + } + + /* + TexturePanel* texturePanel=new TexturePanel(); + texturePanel->setParent(gViewPort); + */ + +} + +void VGui_Shutdown() +{ + delete gViewPort; + gViewPort = NULL; +} + + + + + diff --git a/hlsdk/dmc/cl_dll/vgui_int.h b/hlsdk/dmc/cl_dll/vgui_int.h new file mode 100644 index 00000000..6510853e --- /dev/null +++ b/hlsdk/dmc/cl_dll/vgui_int.h @@ -0,0 +1,21 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_INT_H +#define VGUI_INT_H + +extern "C" +{ +void VGui_Startup(); +void VGui_Shutdown(); + +//Only safe to call from inside subclass of Panel::paintBackground +void VGui_ViewportPaintBackground(int extents[4]); +} + + +#endif \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/vgui_viewport.cpp b/hlsdk/dmc/cl_dll/vgui_viewport.cpp new file mode 100644 index 00000000..1df8331b --- /dev/null +++ b/hlsdk/dmc/cl_dll/vgui_viewport.cpp @@ -0,0 +1,1976 @@ +//=========== (C) Copyright 1996-2002, 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 "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 "pm_shared.h" +#include "parsemsg.h" +#include "../engine/keydefs.h" +#include "demo.h" +#include "demo_api.h" + +#include "vgui_int.h" +#include "vgui_viewport.h" +#include "vgui_ServerBrowser.h" +#include "vgui_ScorePanel.h" +#include "vgui_SpectatorPanel.h" +#include "voice_status.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 x, int y, int wide, int tall ); +extern float * GetClientColor( int clientIndex ); + +using namespace vgui; + +// Team Colors +int iTeamColors[5][3] = +{ + { 255, 170, 0 }, // HL Orange + { 66, 115, 247 }, + { 220, 51, 38 }, + { 240, 135, 0 }, + { 115, 240, 115 }, +}; + +// Used for Class specific buttons +char *sTFClasses[] = +{ + "", + "SCOUT", + "SNIPER", + "SOLDIER", + "DEMOMAN", + "MEDIC", + "HWGUY", + "PYRO", + "SPY", + "ENGINEER", + "CIVILIAN", +}; + +char *sLocalisedClasses[] = +{ + "#Civilian", + "#Scout", + "#Sniper", + "#Soldier", + "#Demoman", + "#Medic", + "#HWGuy", + "#Pyro", + "#Spy", + "#Engineer", + "#Random", + "#Civilian", +}; + +char *sTFClassSelection[] = +{ + "civilian", + "scout", + "sniper", + "soldier", + "demoman", + "medic", + "hwguy", + "pyro", + "spy", + "engineer", + "randompc", + "civilian", +}; + + +// 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 iYOffset, bool bHideAll ) +{ + int i, iCurrentY = 0; + int iVisibleButtons = 0; + + // Cycle through all the buttons in this menu, and see which will be visible + for (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( 0, 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( 0 , false ) ) + { + // The submenu had no visible buttons, so don't display this button + m_aButtons[i]->setVisible( false ); + continue; + } + } + + m_aButtons[i]->setVisible( true ); + iVisibleButtons++; + } + } + + // Set Size + setSize( _size[0], (iVisibleButtons * (BUTTON_SIZE_Y-1)) + 1 ); + + if ( iYOffset ) + { + m_iYOffset = iYOffset; + } + + for (i = 0; i < m_iButtons; i++) + { + if ( m_aButtons[i]->isVisible() ) + { + if ( m_aButtons[i]->GetSubMenu() != NULL ) + (m_aButtons[i]->GetSubMenu())->RecalculateVisibles( iCurrentY + m_iYOffset, false ); + + + // Make sure it's at the right Y position + // m_aButtons[i]->getPos( iXPos, iYPos ); + + if ( m_iDirection ) + { + m_aButtons[i]->setPos( 0, (iVisibleButtons-1) * (BUTTON_SIZE_Y-1) - iCurrentY ); + } + else + { + m_aButtons[i]->setPos( 0, iCurrentY ); + } + + iCurrentY += (BUTTON_SIZE_Y-1); + } + } + + return iVisibleButtons?true:false; +} + +// Make sure all submenus can fit on the screen +void CCommandMenu::RecalculatePositions( int iYOffset ) +{ + int iTop; + int iAdjust = 0; + + m_iYOffset+= iYOffset; + + if ( m_iDirection ) + iTop = ScreenHeight - (m_iYOffset + _size[1] ); + else + iTop = m_iYOffset; + + if ( iTop < 0 ) + iTop = 0; + + // Calculate if this is going to fit onscreen, and shuffle it up if it won't + int iBottom = iTop + _size[1]; + + if ( iBottom > ScreenHeight ) + { + // Move in increments of button sizes + while (iAdjust < (iBottom - ScreenHeight)) + { + iAdjust += BUTTON_SIZE_Y - 1; + } + + iTop -= iAdjust; + + // Make sure it doesn't move off the top of the screen (the menu's too big to fit it all) + if ( iTop < 0 ) + { + iAdjust -= (0 - iTop); + iTop = 0; + } + } + + setPos( _pos[0], iTop ); + + // 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 ); +} + + +// 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 *TeamFortressViewport::CreateSubMenu( CommandButton *pButton, CCommandMenu *pParentMenu, int iOffsetY ) +{ + int iXPos = 0; + int iYPos = 0; + int iWide = CMENU_SIZE_X; + int iTall = 0; + int iDirection = 0; + + if (pParentMenu) + { + iXPos = m_pCurrentCommandMenu->GetXOffset() + (CMENU_SIZE_X - 1); + iYPos = m_pCurrentCommandMenu->GetYOffset() + iOffsetY; + iDirection = pParentMenu->GetDirection(); + } + + CCommandMenu *pMenu = new CCommandMenu(pParentMenu, iDirection, 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 TeamFortressViewport is nulled out +// Input : stAllocateBlock - +// Output : void * +//----------------------------------------------------------------------------- +void *TeamFortressViewport::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) {} +}; + + +//================================================================ +TeamFortressViewport::TeamFortressViewport(int x,int y,int wide,int tall) : Panel(x,y,wide,tall), m_SchemeManager(wide,tall) +{ + gViewPort = this; + m_iInitialized = false; +// m_pTeamMenu = NULL; +// m_pClassMenu = NULL; + m_pScoreBoard = NULL; + m_pSpectatorPanel = NULL; + m_pCurrentMenu = NULL; + m_pCurrentCommandMenu = NULL; + + CVAR_CREATE( "hud_classautokill", "1", FCVAR_ARCHIVE ); // 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 + + 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(); + CreateClassMenu(); + CreateSpectatorMenu(); + CreateScoreBoard(); + // Init command menus + m_iNumMenus = 0; + m_iCurrentTeamNumber = m_iUser1 = m_iUser2 = 0; + m_StandardMenu = CreateCommandMenu("commandmenu.txt", 0, CMENU_TOP); + m_SpectatorMenu = CreateCommandMenu("spectatormenu.txt", 1, YRES(32) ); // above bottom bar + + CreateServerBrowser(); + +} + +//----------------------------------------------------------------------------- +// Purpose: Called everytime a new level is started. Viewport clears out it's data. +//----------------------------------------------------------------------------- +void TeamFortressViewport::Initialize( void ) +{ + // Force each menu to Initialize +/* if (m_pTeamMenu) + { + m_pTeamMenu->Initialize(); + } + if (m_pClassMenu) + { + m_pClassMenu->Initialize(); + }*/ + if (m_pScoreBoard) + { + m_pScoreBoard->Initialize(); + HideScoreBoard(); + } + if (m_pSpectatorPanel) + { + // Spectator menu doesn't need initializing + m_pSpectatorPanel->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::scu_none) ); +} + +class CException; +//----------------------------------------------------------------------------- +// Purpose: Read the Command Menu structure from the txt file and create the menu. +// Returns Index of menu in m_pCommandMenus +//----------------------------------------------------------------------------- +int TeamFortressViewport::CreateCommandMenu( char * menuFile, int direction, int yOffset ) +{ + // COMMAND MENU + // Create the root of this new Command Menu + + int newIndex = m_iNumMenus; + + m_pCommandMenus[newIndex] = new CCommandMenu(NULL, direction, 0, yOffset, CMENU_SIZE_X, 300); // This will be resized once we know how many items are in it + m_pCommandMenus[newIndex]->setParent(this); + m_pCommandMenus[newIndex]->setVisible(false); + + m_iNumMenus++; + + // Read Command Menu from the txt file + char token[1024]; + char *pfile = (char*)gEngfuncs.COM_LoadFile( menuFile, 5, NULL); + if (!pfile) + { + gEngfuncs.Con_DPrintf( "Unable to open %s\n", menuFile); + SetCurrentCommandMenu( NULL ); + return newIndex; + } + +try +{ + // First, read in the localisation strings + + // Detpack strings + gHUD.m_TextMessage.LocaliseTextString( "#DetpackSet_For5Seconds", m_sDetpackStrings[0], MAX_BUTTON_SIZE ); + gHUD.m_TextMessage.LocaliseTextString( "#DetpackSet_For20Seconds", m_sDetpackStrings[1], MAX_BUTTON_SIZE ); + gHUD.m_TextMessage.LocaliseTextString( "#DetpackSet_For50Seconds", m_sDetpackStrings[2], MAX_BUTTON_SIZE ); + + // Now start parsing the menu structure + m_pCurrentCommandMenu = m_pCommandMenus[newIndex]; + 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 = -1; + int iToggle = 0; + int iButtonY; + bool bGetExtraToken = true; + CommandButton *pButton = NULL; + + // We should never be here without a Command Menu + if (!m_pCurrentCommandMenu) + { + gEngfuncs.Con_Printf("Error in %s file after '%s'.\n",menuFile, szLastButtonText ); + m_iInitialized = false; + return newIndex; + } + + // 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 if ( !strncmp(cCustom, "TOGGLE", 6) ) + { + iToggle = true; + // Get the next token + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } + + // 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'; + + iButtonY = (BUTTON_SIZE_Y-1) * m_pCurrentCommandMenu->GetNumButtons(); + + // Custom button handling + if ( iCustom ) + { + pButton = CreateCustomButton( cText, cCommand, iButtonY ); + + // 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, iButtonY, CMENU_SIZE_X, BUTTON_SIZE_Y); + } + else if ( iTeamOnly != -1) + { + // button that only shows up if the player is on team iTeamOnly + pButton = new TeamOnlyCommandButton( iTeamOnly, cText, 0, iButtonY, CMENU_SIZE_X, BUTTON_SIZE_Y ); + } + else if ( iToggle ) + { + pButton = new ToggleCommandButton( cCommand, cText,0, iButtonY, CMENU_SIZE_X, BUTTON_SIZE_Y ); + } + else + { + // normal button + pButton = new CommandButton( iPlayerClass, cText, 0, iButtonY, 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 %s past '%s'\n",menuFile, szLastButtonText ); + } + else + { + // Create the menu + m_pCommandMenus[m_iNumMenus] = CreateSubMenu(pButton, m_pCurrentCommandMenu, iButtonY ); + m_pCurrentCommandMenu = m_pCommandMenus[m_iNumMenus]; + m_iNumMenus++; + } + } + else if ( !iCustom ) + { + // Create the button and attach it to the current menu + if ( iToggle ) + pButton->addActionSignal(new CMenuHandler_ToggleCvar(cCommand)); + else + 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 newIndex; +} + + SetCurrentMenu( NULL ); + SetCurrentCommandMenu( NULL ); + gEngfuncs.COM_FreeFile( pfile ); + + m_iInitialized = true; + return newIndex; +} + +//----------------------------------------------------------------------------- +// Purpose: Creates all the class choices under a spy's disguise menus, and +// maps a command to them +// Output : CCommandMenu +//----------------------------------------------------------------------------- + +CCommandMenu *TeamFortressViewport::CreateDisguiseSubmenu( CommandButton *pButton, CCommandMenu *pParentMenu, const char *commandText, int iYOffset ) +{ + // create the submenu, under which the class choices will be listed + CCommandMenu *pMenu = CreateSubMenu( pButton, pParentMenu, iYOffset ); + m_pCommandMenus[m_iNumMenus] = pMenu; + m_iNumMenus++; + + return pMenu; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pButtonText - +// *pButtonName - +// Output : CommandButton +//----------------------------------------------------------------------------- +CommandButton *TeamFortressViewport::CreateCustomButton( char *pButtonText, char *pButtonName, int iYOffset ) +{ + 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, iYOffset ); + 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] ); + } + + 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 TeamFortressViewport::ToggleServerBrowser() +{ + if (!m_iInitialized) + return; + + if ( !m_pServerBrowser ) + return; + + if ( m_pServerBrowser->isVisible() ) + { + m_pServerBrowser->setVisible( false ); + } + else + { + m_pServerBrowser->setVisible( true ); + } + + UpdateCursorState(); +} + +//======================================================================= +//======================================================================= +void TeamFortressViewport::ShowCommandMenu(int menuIndex) +{ + if (!m_iInitialized) + return; + + //Already have a menu open. + if ( m_pCurrentMenu ) + return; + + // is the command menu open? + if ( m_pCurrentCommandMenu == m_pCommandMenus[menuIndex] ) + { + HideCommandMenu(); + return; + } + + // Not visible while in intermission + if ( gHUD.m_iIntermission ) + return; + + // Recalculate visible menus + UpdateCommandMenu( menuIndex ); + HideVGUIMenu(); + + SetCurrentCommandMenu( m_pCommandMenus[menuIndex] ); + 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 TeamFortressViewport::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 TeamFortressViewport::HideCommandMenu() +{ + if (!m_iInitialized) + return; + + if ( m_pCommandMenus[m_StandardMenu] ) + { + m_pCommandMenus[m_StandardMenu]->ClearButtonsOfArmedState(); + } + + if ( m_pCommandMenus[m_SpectatorMenu] ) + { + m_pCommandMenus[m_SpectatorMenu]->ClearButtonsOfArmedState(); + } + + m_flMenuOpenTime = 0.0f; + SetCurrentCommandMenu( NULL ); + UpdateCursorState(); +} + +//----------------------------------------------------------------------------- +// Purpose: Bring up the scoreboard +//----------------------------------------------------------------------------- +void TeamFortressViewport::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 TeamFortressViewport::IsScoreBoardVisible( void ) +{ + if (m_pScoreBoard) + return m_pScoreBoard->isVisible(); + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Hide the scoreboard +//----------------------------------------------------------------------------- +void TeamFortressViewport::HideScoreBoard( void ) +{ + // Prevent removal of scoreboard during intermission + if ( gHUD.m_iIntermission ) + return; + + if (m_pScoreBoard) + { + m_pScoreBoard->setVisible(false); + + GetClientVoiceMgr()->StopSquelchMode(); + + UpdateCursorState(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Activate's the player special ability +// called when the player hits their "special" key +//----------------------------------------------------------------------------- +void TeamFortressViewport::InputPlayerSpecial( void ) +{ + if (!m_iInitialized) + return; +} + +// Set the submenu of the Command Menu +void TeamFortressViewport::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 TeamFortressViewport::UpdateCommandMenu(int menuIndex) +{ + m_pCommandMenus[menuIndex]->RecalculateVisibles( 0, false ); + m_pCommandMenus[menuIndex]->RecalculatePositions( 0 ); +} + +void TeamFortressViewport::UpdateSpectatorPanel() +{ + m_iUser1 = g_iUser1; + m_iUser2 = g_iUser2; + + if (!m_pSpectatorPanel) + return; + + // check if spectator combinations are still valid + gHUD.m_Spectator.CheckSettings(); + + if ( g_iUser1 && gHUD.m_pCvarDraw->value && !gHUD.m_iIntermission) // don't draw in dev_overview mode + { + char bottomText[128]; + char helpString2[128]; + char tempString[128]; + char * name; + int player = 0; + + if ( !m_pSpectatorPanel->isVisible() ) + { + m_pSpectatorPanel->setVisible( true ); // show spectator panel, but + m_pSpectatorPanel->ShowMenu( false ); // dsiable all menus/buttons + + _snprintf( tempString, sizeof( tempString ) - 1, "%c%s", HUD_PRINTCENTER, CHudTextMessage::BufferedLocaliseTextString( "#Spec_Duck" ) ); + tempString[ sizeof( tempString ) - 1 ] = '\0'; + + gHUD.m_TextMessage.MsgFunc_TextMsg( NULL, strlen( tempString ) + 1, tempString ); + } + + sprintf(bottomText,"#Spec_Mode%d", g_iUser1 ); + sprintf(helpString2,"#Spec_Mode%d", g_iUser1 ); + + if ( gEngfuncs.IsSpectateOnly() ) + strcat(helpString2, " - HLTV"); + + // check if we're locked onto a target, show the player's name + if ( (g_iUser2 > 0) && (g_iUser2 <= gEngfuncs.GetMaxClients()) && (g_iUser1 != OBS_ROAMING) ) + { + player = g_iUser2; + } + + // special case in free map and inset off, don't show names + if ( (g_iUser1 == OBS_MAP_FREE) && !gHUD.m_Spectator.m_pip->value ) + name = NULL; + else + name = g_PlayerInfoList[player].name; + + // create player & health string + if ( player && name ) + { + strcpy( bottomText, name ); + } + + // in first person mode colorize player names + if ( (g_iUser1 == OBS_IN_EYE) && player ) + { + float * color = GetClientColor( player ); + int r = color[0]*255; + int g = color[1]*255; + int b = color[2]*255; + + // set team color, a bit transparent + m_pSpectatorPanel->m_BottomMainLabel->setFgColor(r,g,b,0); + } + else + { // restore GUI color + m_pSpectatorPanel->m_BottomMainLabel->setFgColor( Scheme::sc_primary1 ); + } + + // add sting auto if we are in auto directed mode + if ( gHUD.m_Spectator.m_autoDirector->value ) + { + char tempString[128]; + sprintf(tempString, "#Spec_Auto %s", helpString2); + strcpy( helpString2, tempString ); + } + + m_pSpectatorPanel->m_BottomMainLabel->setText( CHudTextMessage::BufferedLocaliseTextString( bottomText ) ); + m_pSpectatorPanel->m_TopMainLabel->setText( CHudTextMessage::BufferedLocaliseTextString( helpString2 ) ); + + } + else + { + m_pSpectatorPanel->setVisible( false ); + m_pSpectatorPanel->ShowMenu( false ); // dsiable all menus/buttons + } +} + +//====================================================================== +void TeamFortressViewport::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 TeamFortressViewport::CreateServerBrowser( void ) +{ + m_pServerBrowser = new ServerBrowser( 0, 0, ScreenWidth, ScreenHeight ); + m_pServerBrowser->setParent(this); + m_pServerBrowser->setVisible(false); +} + + +//====================================================================== +// Set the VGUI Menu +void TeamFortressViewport::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* TeamFortressViewport::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, "Half-Life" ); + 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 ); + + if (!pfile) + return NULL; + + cText = pfile; + + strncpy( cTitle, m_sMapName, MAX_TITLE_LENGTH ); + cTitle[MAX_TITLE_LENGTH-1] = 0; + } + else if ( iTextToShow == SHOW_SPECHELP ) + { + CHudTextMessage::LocaliseTextString( "#Spec_Help_Title", cTitle, MAX_TITLE_LENGTH ); + cTitle[MAX_TITLE_LENGTH-1] = 0; + + char *pfile = CHudTextMessage::BufferedLocaliseTextString( "#Spec_Help_Text" ); + if ( pfile ) + { + cText = pfile; + } + } + else + return NULL; + + // 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, g_iPlayerClass == 11, false, 0, 0, ScreenWidth, ScreenHeight ); + pMOTDPanel->setParent( this ); + + if ( pfile ) + gEngfuncs.COM_FreeFile( pfile ); + + return pMOTDPanel; +} + +//================================================================ +// VGUI Menus +void TeamFortressViewport::ShowVGUIMenu( int iMenu ) +{ + CMenuPanel *pNewMenu = NULL; + + // Don't open menus in demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + 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_CLASSHELP: + pNewMenu = CreateTextWindow( SHOW_CLASSDESC ); + break; + + case MENU_SPECHELP: + pNewMenu = CreateTextWindow( SHOW_SPECHELP ); + break; + case MENU_CLASS: + pNewMenu = ShowClassMenu(); + 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 TeamFortressViewport::HideVGUIMenu() +{ + while (m_pCurrentMenu) + { + HideTopMenu(); + } +} + +// Remove the top VGUI menu, and bring up the next one +void TeamFortressViewport::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 TeamFortressViewport::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_CLASS || iId == MENU_INTRO || iId == MENU_CLASSHELP ) + return FALSE; + } + + return TRUE; +} + +//====================================================================================== +// TEAM MENU +//====================================================================================== +// Bring up the Team selection Menu +CMenuPanel* TeamFortressViewport::ShowTeamMenu() +{ + // Don't open menus in demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + return NULL; + +// m_pTeamMenu->Reset(); +// return m_pTeamMenu; + + return NULL; +} + +void TeamFortressViewport::CreateTeamMenu() +{ + // Create the panel +/* m_pTeamMenu = new CTeamMenuPanel(100, false, 0, 0, ScreenWidth, ScreenHeight); + m_pTeamMenu->setParent( this ); + m_pTeamMenu->setVisible( false );*/ +} + +//====================================================================================== +// CLASS MENU +//====================================================================================== +// Bring up the Class selection Menu +CMenuPanel* TeamFortressViewport::ShowClassMenu() +{ + // Don't open menus in demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + return NULL; + +// m_pClassMenu->Reset(); +// return m_pClassMenu; + + return NULL; +} + +void TeamFortressViewport::CreateClassMenu() +{ + // Create the panel +/* m_pClassMenu = new CClassMenuPanel(100, false, 0, 0, ScreenWidth, ScreenHeight); + m_pClassMenu->setParent(this); + m_pClassMenu->setVisible( false );*/ +} + +//====================================================================================== +// SPECTATOR MENU +//====================================================================================== +// Spectator "Menu" explaining the Spectator buttons +void TeamFortressViewport::CreateSpectatorMenu() +{ + // Create the Panel + m_pSpectatorPanel = new SpectatorPanel(0, 0, ScreenWidth, ScreenHeight); + m_pSpectatorPanel->setParent(this); + m_pSpectatorPanel->setVisible(false); + m_pSpectatorPanel->Initialize(); +} + +//====================================================================================== +// UPDATE HUD SECTIONS +//====================================================================================== +// We've got an update on player info +// Recalculate any menus that use it. +void TeamFortressViewport::UpdateOnPlayerInfo() +{ +/* if (m_pTeamMenu) + m_pTeamMenu->Update(); + if (m_pClassMenu) + m_pClassMenu->Update();*/ + if (m_pScoreBoard) + m_pScoreBoard->Update(); +} + +void TeamFortressViewport::UpdateCursorState() +{ + // Need cursor if any VGUI window is up + if ( m_pSpectatorPanel->m_menuVisible || m_pCurrentMenu || m_pServerBrowser->isVisible() || GetClientVoiceMgr()->IsInSquelchMode() ) + { + g_iVisibleMouse = true; + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::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::scu_arrow) ); + return; + } + } + + IN_ResetMouse(); + g_iVisibleMouse = false; + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::scu_none) ); +} + +void TeamFortressViewport::UpdateHighlights() +{ + if (m_pCurrentCommandMenu) + m_pCurrentCommandMenu->MakeVisible( NULL ); +} + +void TeamFortressViewport::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 TeamFortressViewport::paintBackground() +{ + // See if the command menu is visible and needs recalculating due to some external change +/* if ( g_iTeamNumber != m_iCurrentTeamNumber ) + { + UpdateCommandMenu(); + + if ( m_pClassMenu ) + { + m_pClassMenu->Update(); + } + + m_iCurrentTeamNumber = g_iTeamNumber; + } + + if ( g_iPlayerClass != m_iCurrentPlayerClass ) + { + UpdateCommandMenu(); + + m_iCurrentPlayerClass = g_iPlayerClass; + } */ + + // See if the Spectator Menu needs to be update + if ( g_iUser1 != m_iUser1 || g_iUser2 != m_iUser2 ) + { + UpdateSpectatorPanel(); + } + + // 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 TeamFortressViewport::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 TeamFortressViewport::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 || iMenuID == MENU_CLASS) + { + // Escape gets you out of Team/Class menus if the Cancel button is visible + if ( keynum == K_ESCAPE ) + { + if ( (iMenuID == MENU_TEAM && g_iTeamNumber) || (iMenuID == MENU_CLASS && g_iPlayerClass) ) + { + 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 || iMenuID == MENU_CLASSHELP ) + { + HideTopMenu(); + 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 TeamFortressViewport::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( m_StandardMenu ); + + return 1; +} + +int TeamFortressViewport::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] ); + + // Set the disguise buttons + if (m_pDisguiseButtons[i]) + m_pDisguiseButtons[i]->setText( m_sTeamNames[teamNum] ); + } + + // Update the Team Menu +// if (m_pTeamMenu) +// m_pTeamMenu->Update(); + + return 1; +} + +int TeamFortressViewport::MsgFunc_Feign(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iIsFeigning = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu( m_StandardMenu ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_Detpack(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iIsSettingDetpack = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu( m_StandardMenu ); + + return 1; +} + +int TeamFortressViewport::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 TeamFortressViewport::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 TeamFortressViewport::MsgFunc_BuildSt( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iBuildState = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu( m_StandardMenu ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_RandomPC( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iRandomPC = READ_BYTE(); + + return 1; +} + +int TeamFortressViewport::MsgFunc_ServerName( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + strncpy( m_szServerName, READ_STRING(), MAX_SERVERNAME_LENGTH ); + + return 1; +} + +int TeamFortressViewport::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 teamnumber = READ_SHORT(); + + if ( cl > 0 && cl <= MAX_PLAYERS ) + { + g_PlayerExtraInfo[cl].frags = frags; + g_PlayerExtraInfo[cl].deaths = deaths; + g_PlayerExtraInfo[cl].teamnumber = teamnumber; + + 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 TeamFortressViewport::MsgFunc_TeamScore( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + char *TeamName = READ_STRING(); + + // find the team matching the name + int i; + for ( 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 TeamFortressViewport::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 TeamFortressViewport::DeathMsg( int killer, int victim ) +{ + m_pScoreBoard->DeathMsg(killer,victim); +} + +int TeamFortressViewport::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 TeamFortressViewport::MsgFunc_AllowSpec( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iAllowSpectators = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu( m_StandardMenu ); + + // If the team menu is up, update it too +// if (m_pTeamMenu) +// m_pTeamMenu->Update(); + + return 1; +} + diff --git a/hlsdk/dmc/cl_dll/vgui_viewport.h b/hlsdk/dmc/cl_dll/vgui_viewport.h new file mode 100644 index 00000000..1fcd1569 --- /dev/null +++ b/hlsdk/dmc/cl_dll/vgui_viewport.h @@ -0,0 +1,1331 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef TEAMFORTRESSVIEWPORT_H +#define TEAMFORTRESSVIEWPORT_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 PC_LASTCLASS 12 + +#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_SPECHELP 9 + + +using namespace vgui; + +class Cursor; +class ScorePanel; +class SpectatorPanel; +class CCommandMenu; +class CommandLabel; +class CommandButton; +class BuildButton; +class ClassButton; +class CMenuPanel; +class ServerBrowser; +class DragNDropPanel; +class CTransparentPanel; +//class CClassMenuPanel; +//class CTeamMenuPanel; + +char* GetVGUITGAName(const char *pszName); +BitmapTGA *LoadTGA( 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]; + +#define MAX_SERVERNAME_LENGTH 32 + + +// Command Menu positions +#define MAX_MENUS 40 +#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 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; + + // opens menu from top to bottom (0 = default), or from bottom to top (1)? + int m_iDirection; +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; + m_iDirection = 0; + } + + CCommandMenu( CCommandMenu *pParentMenu, int direction, 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; + m_iDirection = direction; + } + + 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 GetDirection() { return m_iDirection; }; + int GetNumButtons() { return m_iButtons; }; + CommandButton *FindButtonWithSubmenu( CCommandMenu *pSubMenu ); + + void ClearButtonsOfArmedState( void ); + + + bool KeyInput( int keyNum ); + + virtual void paintBackground(); +}; + +//============================================================================== +class TeamFortressViewport : 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; + + // VGUI Menus + void CreateTeamMenu( void ); + CMenuPanel* ShowTeamMenu( void ); + void CreateClassMenu( void ); + CMenuPanel* ShowClassMenu( void ); + void CreateSpectatorMenu( 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; + + int m_iAllowSpectators; + + // Data for specific sections of the Command Menu + int m_iValidClasses[5]; + int m_iIsFeigning; + int m_iIsSettingDetpack; + int m_iNumberOfTeams; + int m_iBuildState; + int m_iRandomPC; + char m_sTeamNames[5][MAX_TEAMNAME_SIZE]; + + // Localisation strings + char m_sDetpackStrings[3][MAX_BUTTON_SIZE]; + + char m_sMapName[64]; +public: + TeamFortressViewport(int x,int y,int wide,int tall); + void Initialize( void ); + + int CreateCommandMenu( char * menuFile, int direction, int yOffset ); + void CreateScoreBoard( void ); + void CreateServerBrowser( void ); + CommandButton * CreateCustomButton( char *pButtonText, char * pButtonName, int iYOffset ); + CCommandMenu * CreateDisguiseSubmenu( CommandButton *pButton, CCommandMenu *pParentMenu, const char *commandText, int iYOffset ); + + void UpdateCursorState( void ); + void UpdateCommandMenu(int menuIndex); + void UpdateOnPlayerInfo( void ); + void UpdateHighlights( void ); + void UpdateSpectatorPanel( void ); + + int KeyInput( int down, int keynum, const char *pszCurrentBinding ); + void InputPlayerSpecial( void ); + void GetAllPlayersInfo( void ); + void DeathMsg( int killer, int victim ); + + void ShowCommandMenu(int menuIndex); + 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, int iYOffset ); + + // Data Handlers + int GetValidClasses(int iTeam) { return m_iValidClasses[iTeam]; }; + int GetNumberOfTeams() { return m_iNumberOfTeams; }; + int GetIsFeigning() { return m_iIsFeigning; }; + int GetIsSettingDetpack() { return m_iIsSettingDetpack; }; + 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_Feign(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_Detpack(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_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 ); + + // Input + bool SlotInput( int iSlot ); + + virtual void paintBackground(); + + CSchemeManager *GetSchemeManager( void ) { return &m_SchemeManager; } + + void *operator new( size_t stAllocateBlock ); + +public: + // VGUI Menus + CMenuPanel *m_pCurrentMenu; + int m_SpectatorMenu; // indexs in m_pCommandMenus + int m_StandardMenu; +// CTeamMenuPanel *m_pTeamMenu; +// CClassMenuPanel *m_pClassMenu; + ScorePanel *m_pScoreBoard; + SpectatorPanel *m_pSpectatorPanel; + 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_CLASSDESC 2 +#define SHOW_MOTD 3 +#define SHOW_SPECHELP 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 CMenuHandler_ToggleCvar : public ActionSignal +{ +private: + struct cvar_s * m_cvar; + +public: + CMenuHandler_ToggleCvar( char * cvarname ) + { + m_cvar = gEngfuncs.pfnGetCvarPointer( cvarname ); + } + + virtual void actionPerformed(Panel* panel) + { + if ( m_cvar->value ) + m_cvar->value = 0.0f; + else + m_cvar->value = 1.0f; + + gViewPort->UpdateSpectatorPanel(); + } + + +}; +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 FeignButton : public CommandButton +{ +private: + int m_iFeignState; +public: + FeignButton( int iState, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + m_iFeignState = iState; + } + + virtual int IsNotValid() + { + 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() + { + // 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; + } +}; + +class DetpackButton : public CommandButton +{ +private: + int m_iDetpackState; +public: + DetpackButton( int iState, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + m_iDetpackState = iState; + } + + virtual int IsNotValid() + { + 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() + { + return true; + } +}; + +#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(); + } +}; + +//----------------------------------------------------------------------------- +// Purpose: CommandButton which is only displayed if the player is on team X +//----------------------------------------------------------------------------- +class ToggleCommandButton : public CommandButton, public InputSignal +{ +private: + struct cvar_s * m_cvar; + CImageLabel * pLabelOn; + CImageLabel * pLabelOff; + + +public: + ToggleCommandButton( const char* cvarname, const char* text,int x,int y,int wide,int tall ) : + CommandButton( text, x, y, wide, tall ) + { + m_cvar = gEngfuncs.pfnGetCvarPointer( cvarname ); + + // Put a > to show it's a submenu + pLabelOn = new CImageLabel( "checked", 0, 0 ); + pLabelOn->setParent(this); + pLabelOn->addInputSignal(this); + + pLabelOff = new CImageLabel( "unchecked", 0, 0 ); + pLabelOff->setParent(this); + pLabelOff->setEnabled(true); + pLabelOff->addInputSignal(this); + + int textwide, texttall; + getTextSize( textwide, texttall); + + // Reposition + pLabelOn->setPos( textwide, (tall - pLabelOn->getTall()) / 2 ); + + pLabelOff->setPos( textwide, (tall - pLabelOff->getTall()) / 2 ); + + // Set text color to orange + setFgColor(Scheme::sc_primary1); + } + + virtual void cursorEntered(Panel* panel) + { + CommandButton::cursorEntered(); + } + + virtual void cursorExited(Panel* panel) + { + CommandButton::cursorExited(); + } + + virtual void mousePressed(MouseCode code,Panel* panel) + { + doClick(); + }; + + virtual void cursorMoved(int x,int y,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) {}; + + virtual void paint( void ) + { + if ( !m_cvar ) + { + pLabelOff->setVisible(false); + pLabelOn->setVisible(false); + } + else if ( m_cvar->value ) + { + pLabelOff->setVisible(false); + pLabelOn->setVisible(true); + } + else + { + pLabelOff->setVisible(true); + pLabelOn->setVisible(false); + } + + CommandButton::paint(); + + } +}; +//============================================================ +// 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]); + } + } +}; + +//================================================================ +// 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_iMenuID == MENU_INTRO ) + { + gEngfuncs.pfnClientCmd( "_firstspawn\n" ); + } + + 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 CClassMenuPanel : public CMenuPanel +{ +private: + CTransparentPanel *m_pClassInfoPanel[PC_LASTCLASS]; + Label *m_pPlayers[PC_LASTCLASS]; + ClassButton *m_pButtons[PC_LASTCLASS]; + CommandButton *m_pCancelButton; + ScrollPanel *m_pScrollPanel; + + CImageLabel *m_pClassImages[MAX_TEAMS][PC_LASTCLASS]; + + int m_iCurrentInfo; + + enum { STRLENMAX_PLAYERSONTEAM = 128 }; + char m_sPlayersOnTeamString[STRLENMAX_PLAYERSONTEAM]; + +public: + CClassMenuPanel(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 Initialize( void ); + + virtual void Reset( void ) + { + CMenuPanel::Reset(); + m_iCurrentInfo = 0; + } +};*/ +/* +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; + } +}; +*/ +//========================================================= +// Specific Menus to handle old HUD sections +class CHealthPanel : public DragNDropPanel +{ +private: + BitmapTGA *m_pHealthTGA; + Label *m_pHealthLabel; +public: + CHealthPanel(int x,int y,int wide,int tall) : DragNDropPanel(x,y,wide,tall) + { + // Load the Health icon + FileInputStream* fis = new FileInputStream( GetVGUITGAName("%d_hud_health"), false); + m_pHealthTGA = new BitmapTGA(fis,true); + fis->close(); + + // Create the Health Label + int iXSize,iYSize; + m_pHealthTGA->getSize(iXSize,iYSize); + m_pHealthLabel = new Label("",0,0,iXSize,iYSize); + m_pHealthLabel->setImage(m_pHealthTGA); + m_pHealthLabel->setParent(this); + + // Set panel dimension + // Shouldn't be needed once Billy's fized setImage not recalculating the size + //setSize( iXSize + 100, gHUD.m_iFontHeight + 10 ); + //m_pHealthLabel->setPos( 10, (getTall() - iYSize) / 2 ); + } + + virtual void paintBackground() + { + } + + void paint() + { + // Get the paint color + int r,g,b,a; + // Has health changed? Flash the health # + if (gHUD.m_Health.m_fFade) + { + gHUD.m_Health.m_fFade -= (gHUD.m_flTimeDelta * 20); + if (gHUD.m_Health.m_fFade <= 0) + { + a = MIN_ALPHA; + gHUD.m_Health.m_fFade = 0; + } + + // Fade the health number back to dim + a = MIN_ALPHA + (gHUD.m_Health.m_fFade/FADE_TIME) * 128; + } + else + a = MIN_ALPHA; + + gHUD.m_Health.GetPainColor( r, g, b ); + ScaleColors(r, g, b, a ); + + // If health is getting low, make it bright red + if (gHUD.m_Health.m_iHealth <= 15) + a = 255; + + int iXSize,iYSize, iXPos, iYPos; + m_pHealthTGA->getSize(iXSize,iYSize); + m_pHealthTGA->getPos(iXPos, iYPos); + + // Paint the player's health + int x = gHUD.DrawHudNumber( iXPos + iXSize + 5, iYPos + 5, DHN_3DIGITS | DHN_DRAWZERO, gHUD.m_Health.m_iHealth, r, g, b); + + // Draw the vertical line + int HealthWidth = gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left; + x += HealthWidth / 2; + FillRGBA(x, iYPos + 5, HealthWidth / 10, gHUD.m_iFontHeight, 255, 160, 0, a); + } +}; + +#endif diff --git a/hlsdk/dmc/cl_dll/view.cpp b/hlsdk/dmc/cl_dll/view.cpp new file mode 100644 index 00000000..2a6ea1bd --- /dev/null +++ b/hlsdk/dmc/cl_dll/view.cpp @@ -0,0 +1,1677 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// view/refresh setup functions + +#include +#include "hud.h" +#include "cl_util.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" + +#include "entity_state.h" +#include "cl_entity.h" +#include "ref_params.h" +#include "in_defs.h" // PITCH YAW ROLL +#include "pm_movevars.h" +#include "pm_shared.h" +#include "pm_defs.h" +#include "event_api.h" +#include "pmtrace.h" +#include "hltv.h" + + +// QUAKECLASSIC +extern int iMouseInUse; +extern vec3_t vecTempAngles; +extern bool bChangeAngles; + + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +extern "C" +{ + int CL_IsThirdPerson( void ); + void CL_CameraOffset( float *ofs ); + + void DLLEXPORT V_CalcRefdef( struct ref_params_s *pparams ); + + void PM_ParticleLine( float *start, float *end, int pcolor, float life, float vert); + int PM_GetInfo( int ent ); + void InterpolateAngles( float * start, float * end, float * output, float frac ); + void NormalizeAngles( float * angles ); + float Distance(const float * v1, const float * v2); + float AngleBetweenVectors( const float * v1, const float * v2 ); + + float vJumpOrigin[3]; + float vJumpAngles[3]; +} + +#include "r_studioint.h" +#include "com_model.h" + +extern engine_studio_api_t IEngineStudio; + +void V_DropPunchAngle ( float frametime, float *ev_punchangle ); +void VectorAngles( const float *forward, float *angles ); + +/* +The view is allowed to move slightly from it's true position for bobbing, +but if it exceeds 8 pixels linear distance (spherical, not box), the list of +entities sent from the server may not include everything in the pvs, especially +when crossing a water boudnary. +*/ + +extern cvar_t *cl_forwardspeed; +extern cvar_t *chase_active; +extern cvar_t *cl_vsmoothing; + +vec3_t v_origin, v_angles, v_cl_angles, v_sim_org, v_lastAngles; +float v_frametime, v_lastDistance; + +vec3_t ev_punchangle; + +cvar_t *scr_ofsx; +cvar_t *scr_ofsy; +cvar_t *scr_ofsz; + +cvar_t *v_centermove; +cvar_t *v_centerspeed; + +cvar_t *cl_bobcycle; +cvar_t *cl_bob; +cvar_t *cl_bobup; +cvar_t *cl_waterdist; +cvar_t *cl_chasedist; + +// These cvars are not registered (so users can't cheat), so set the ->value field directly +// Register these cvars in V_Init() if needed for easy tweaking +cvar_t v_iyaw_cycle = {"v_iyaw_cycle", "2", 0, 2}; +cvar_t v_iroll_cycle = {"v_iroll_cycle", "0.5", 0, 0.5}; +cvar_t v_ipitch_cycle = {"v_ipitch_cycle", "1", 0, 1}; +cvar_t v_iyaw_level = {"v_iyaw_level", "0.3", 0, 0.3}; +cvar_t v_iroll_level = {"v_iroll_level", "0.1", 0, 0.1}; +cvar_t v_ipitch_level = {"v_ipitch_level", "0.3", 0, 0.3}; + +float v_idlescale; // used by TFC for concussion grenade effect + +/* +//============================================================================= +void V_NormalizeAngles( float *angles ) +{ + int i; + // Normalize angles + for ( i = 0; i < 3; i++ ) + { + if ( angles[i] > 180.0 ) + { + angles[i] -= 360.0; + } + else if ( angles[i] < -180.0 ) + { + angles[i] += 360.0; + } + } +} + +/* +=================== +V_InterpolateAngles + +Interpolate Euler angles. +FIXME: Use Quaternions to avoid discontinuities +Frac is 0.0 to 1.0 ( i.e., should probably be clamped, but doesn't have to be ) +=================== + +void V_InterpolateAngles( float *start, float *end, float *output, float frac ) +{ + int i; + float ang1, ang2; + float d; + + V_NormalizeAngles( start ); + V_NormalizeAngles( end ); + + for ( i = 0 ; i < 3 ; i++ ) + { + ang1 = start[i]; + ang2 = end[i]; + + d = ang2 - ang1; + if ( d > 180 ) + { + d -= 360; + } + else if ( d < -180 ) + { + d += 360; + } + + output[i] = ang1 + d * frac; + } + + V_NormalizeAngles( output ); +} */ + + +// Quakeworld bob code, this fixes jitters in the mutliplayer since the clock (pparams->time) isn't quite linear +float V_CalcBob ( struct ref_params_s *pparams ) +{ + static double bobtime; + static float bob; + float cycle; + static float lasttime; + vec3_t vel; + + + if ( pparams->onground == -1 || + pparams->time == lasttime ) + { + // just use old value + return bob; + } + + lasttime = pparams->time; + + bobtime += pparams->frametime; + cycle = bobtime - (int)( bobtime / cl_bobcycle->value ) * cl_bobcycle->value; + cycle /= cl_bobcycle->value; + + if ( cycle < cl_bobup->value ) + { + cycle = M_PI * cycle / cl_bobup->value; + } + else + { + cycle = M_PI + M_PI * ( cycle - cl_bobup->value )/( 1.0 - cl_bobup->value ); + } + + // bob is proportional to simulated velocity in the xy plane + // (don't count Z, or jumping messes it up) + VectorCopy( pparams->simvel, vel ); + vel[2] = 0; + + bob = sqrt( vel[0] * vel[0] + vel[1] * vel[1] ) * cl_bob->value; + bob = bob * 0.3 + bob * 0.7 * sin(cycle); + bob = min( bob, 4 ); + bob = max( bob, -7 ); + return bob; + +} + +/* +=============== +V_CalcRoll +Used by view and sv_user +=============== +*/ +float V_CalcRoll (vec3_t angles, vec3_t velocity, float rollangle, float rollspeed ) +{ + float sign; + float side; + float value; + vec3_t forward, right, up; + + AngleVectors ( angles, forward, right, up ); + + side = DotProduct (velocity, right); + sign = side < 0 ? -1 : 1; + side = fabs( side ); + + value = rollangle; + if (side < rollspeed) + { + side = side * value / rollspeed; + } + else + { + side = value; + } + return side * sign; +} + + +typedef struct pitchdrift_s +{ + float pitchvel; + int nodrift; + float driftmove; + double laststop; +} pitchdrift_t; + +static pitchdrift_t pd; + +void V_StartPitchDrift( void ) +{ + if ( pd.laststop == gEngfuncs.GetClientTime() ) + { + return; // something else is keeping it from drifting + } + + if ( pd.nodrift || !pd.pitchvel ) + { + pd.pitchvel = v_centerspeed->value; + pd.nodrift = 0; + pd.driftmove = 0; + } +} + +void V_StopPitchDrift ( void ) +{ + pd.laststop = gEngfuncs.GetClientTime(); + pd.nodrift = 1; + pd.pitchvel = 0; +} + +/* +=============== +V_DriftPitch + +Moves the client pitch angle towards idealpitch sent by the server. + +If the user is adjusting pitch manually, either with lookup/lookdown, +mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped. +=============== +*/ +#include "kbutton.h" +extern kbutton_t in_mlook; + +void V_DriftPitch ( struct ref_params_s *pparams ) +{ + float delta, move; + + if ( ( in_mlook.state & 1) || gEngfuncs.IsNoClipping() || !pparams->onground || pparams->demoplayback || pparams->spectator ) + { + pd.driftmove = 0; + pd.pitchvel = 0; + return; + } + + // don't count small mouse motion + if (pd.nodrift) + { + if ( fabs( pparams->cmd->forwardmove ) < cl_forwardspeed->value ) + pd.driftmove = 0; + else + pd.driftmove += pparams->frametime; + + if ( pd.driftmove > v_centermove->value) + { + V_StartPitchDrift (); + } + return; + } + + delta = pparams->idealpitch - pparams->cl_viewangles[PITCH]; + + if (!delta) + { + pd.pitchvel = 0; + return; + } + + move = pparams->frametime * pd.pitchvel; + pd.pitchvel += pparams->frametime * v_centerspeed->value; + +//Con_Printf ("move: %f (%f)\n", move, pparams->frametime); + + if (delta > 0) + { + if (move > delta) + { + pd.pitchvel = 0; + move = delta; + } + pparams->cl_viewangles[PITCH] += move; + } + else if (delta < 0) + { + if (move > -delta) + { + pd.pitchvel = 0; + move = -delta; + } + pparams->cl_viewangles[PITCH] -= move; + } +} + +/* +============================================================================== + VIEW RENDERING +============================================================================== +*/ + +/* +================== +V_CalcGunAngle +================== +*/ +void V_CalcGunAngle ( struct ref_params_s *pparams ) +{ + cl_entity_t *viewent; + + viewent = gEngfuncs.GetViewModel(); + if ( !viewent ) + return; + + viewent->angles[YAW] = pparams->viewangles[YAW] + pparams->crosshairangle[YAW]; + viewent->angles[PITCH] = -pparams->viewangles[PITCH] + pparams->crosshairangle[PITCH] * 0.25; + viewent->angles[ROLL] -= v_idlescale * sin(pparams->time*v_iroll_cycle.value) * v_iroll_level.value; + + // don't apply all of the v_ipitch to prevent normally unseen parts of viewmodel from coming into view. + viewent->angles[PITCH] -= v_idlescale * sin(pparams->time*v_ipitch_cycle.value) * (v_ipitch_level.value * 0.5); + viewent->angles[YAW] -= v_idlescale * sin(pparams->time*v_iyaw_cycle.value) * v_iyaw_level.value; + + VectorCopy( viewent->angles, viewent->curstate.angles ); + VectorCopy( viewent->angles, viewent->latched.prevangles ); +} + +/* +============== +V_AddIdle + +Idle swaying +============== +*/ +void V_AddIdle ( struct ref_params_s *pparams ) +{ + pparams->viewangles[ROLL] += v_idlescale * sin(pparams->time*v_iroll_cycle.value) * v_iroll_level.value; + pparams->viewangles[PITCH] += v_idlescale * sin(pparams->time*v_ipitch_cycle.value) * v_ipitch_level.value; + pparams->viewangles[YAW] += v_idlescale * sin(pparams->time*v_iyaw_cycle.value) * v_iyaw_level.value; +} + + +extern cvar_t *cl_rollspeed; +extern cvar_t *cl_rollangle; +/* +============== +V_CalcViewRoll + +Roll is induced by movement and damage +============== +*/ +void V_CalcViewRoll ( struct ref_params_s *pparams ) +{ + cl_entity_t *viewentity; + + viewentity = gEngfuncs.GetEntityByIndex( pparams->viewentity ); + if ( !viewentity ) + return; + + //Roll the angles when strafing Quake style! + pparams->viewangles[ROLL] = V_CalcRoll (pparams->viewangles, pparams->simvel, cl_rollangle->value, cl_rollspeed->value ) * 4; + + if ( pparams->health <= 0 && ( pparams->viewheight[2] != 0 ) ) + { + // only roll the view if the player is dead and the viewheight[2] is nonzero + // this is so deadcam in multiplayer will work. + pparams->viewangles[ROLL] = 80; // dead view angle + return; + } +} + + +/* +================== +V_CalcIntermissionRefdef + +================== +*/ +void V_CalcIntermissionRefdef ( struct ref_params_s *pparams ) +{ + cl_entity_t *ent, *view; + float old; + +// don't allow cheats in multiplayer + if ( pparams->maxclients > 1 ) + { + scr_ofsx->value = 0.0; + scr_ofsy->value = 0.0; + scr_ofsz->value = 0.0; + } + + // ent is the player model ( visible when out of body ) + ent = gEngfuncs.GetLocalPlayer(); + + // view is the weapon model (only visible from inside body ) + view = gEngfuncs.GetViewModel(); + + VectorCopy ( pparams->simorg, pparams->vieworg ); + VectorCopy ( pparams->cl_viewangles, pparams->viewangles ); + + view->model = NULL; + + // allways idle in intermission + old = v_idlescale; + v_idlescale = 1; + + V_AddIdle ( pparams ); + + if ( gEngfuncs.IsSpectateOnly() ) + { + // in HLTV we must go to 'intermission' position by ourself + VectorCopy( gHUD.m_Spectator.m_cameraOrigin, pparams->vieworg ); + VectorCopy( gHUD.m_Spectator.m_cameraAngles, pparams->viewangles ); + } + v_idlescale = old; + + v_cl_angles = pparams->cl_viewangles; + v_origin = pparams->vieworg; + v_angles = pparams->viewangles; +} + +#define ORIGIN_BACKUP 64 +#define ORIGIN_MASK ( ORIGIN_BACKUP - 1 ) + +typedef struct +{ + float Origins[ ORIGIN_BACKUP ][3]; + float OriginTime[ ORIGIN_BACKUP ]; + + float Angles[ ORIGIN_BACKUP ][3]; + float AngleTime[ ORIGIN_BACKUP ]; + + int CurrentOrigin; + int CurrentAngle; +} viewinterp_t; + +/* +================== +V_CalcRefdef + +================== +*/ +void V_CalcNormalRefdef ( struct ref_params_s *pparams ) +{ + cl_entity_t *ent, *view; + int i; + vec3_t angles; + float bob, waterOffset; + static viewinterp_t ViewInterp; + + static float oldz = 0; + static float lasttime; + + static float lastang[3]; + vec3_t angdelta; + + vec3_t camAngles, camForward, camRight, camUp; + cl_entity_t *pwater; + + //Force angle change + if ( bChangeAngles == true ) + { + pparams->cl_viewangles[PITCH] = vecTempAngles[PITCH]; + pparams->cl_viewangles[YAW] = vecTempAngles[YAW]; + pparams->cl_viewangles[ROLL] = vecTempAngles[ROLL]; + + vecTempAngles = Vector ( 0, 0, 0 ); + bChangeAngles = false; + } + + // don't allow cheats in multiplayer + if ( pparams->maxclients > 1 ) + { + gEngfuncs.Cvar_SetValue ("scr_ofsx", 0); + gEngfuncs.Cvar_SetValue ("scr_ofsy", 0); + gEngfuncs.Cvar_SetValue ("scr_ofsz", 0); + } + + V_DriftPitch ( pparams ); + + // ent is the player model ( visible when out of body ) + ent = gEngfuncs.GetLocalPlayer(); + + // view is the weapon model (only visible from inside body ) + view = gEngfuncs.GetViewModel(); + + // transform the view offset by the model's matrix to get the offset from + // model origin for the view + bob = V_CalcBob ( pparams ); + + // refresh position + VectorCopy ( pparams->simorg, pparams->vieworg ); + pparams->vieworg[2] += ( bob ); + VectorAdd( pparams->vieworg, pparams->viewheight, pparams->vieworg ); + + VectorCopy ( pparams->cl_viewangles, pparams->viewangles ); + + gEngfuncs.V_CalcShake(); + gEngfuncs.V_ApplyShake( pparams->vieworg, pparams->viewangles, 1.0 ); + + // never let view origin sit exactly on a node line, because a water plane can + // dissapear when viewed with the eye exactly on it. + // FIXME, we send origin at 1/128 now, change this? + // the server protocol only specifies to 1/16 pixel, so add 1/32 in each axis + + pparams->vieworg[0] += 1.0/32; + pparams->vieworg[1] += 1.0/32; + pparams->vieworg[2] += 1.0/32; + + // Check for problems around water, move the viewer artificially if necessary + // -- this prevents drawing errors in GL due to waves + + waterOffset = 0; + if ( pparams->waterlevel >= 2 ) + { + int i, contents, waterDist, waterEntity; + vec3_t point; + waterDist = cl_waterdist->value; + + if ( pparams->hardware ) + { + waterEntity = gEngfuncs.PM_WaterEntity( pparams->simorg ); + if ( waterEntity >= 0 && waterEntity < pparams->max_entities ) + { + pwater = gEngfuncs.GetEntityByIndex( waterEntity ); + if ( pwater && ( pwater->model != NULL ) ) + { + waterDist += ( pwater->curstate.scale * 16 ); // Add in wave height + } + } + } + else + { + waterEntity = 0; // Don't need this in software + } + + VectorCopy( pparams->vieworg, point ); + + // Eyes are above water, make sure we're above the waves + if ( pparams->waterlevel == 2 ) + { + point[2] -= waterDist; + for ( i = 0; i < waterDist; i++ ) + { + contents = gEngfuncs.PM_PointContents( point, NULL ); + if ( contents > CONTENTS_WATER ) + break; + point[2] += 1; + } + waterOffset = (point[2] + waterDist) - pparams->vieworg[2]; + } + else + { + // eyes are under water. Make sure we're far enough under + point[2] += waterDist; + + for ( i = 0; i < waterDist; i++ ) + { + contents = gEngfuncs.PM_PointContents( point, NULL ); + if ( contents <= CONTENTS_WATER ) + break; + point[2] -= 1; + } + waterOffset = (point[2] - waterDist) - pparams->vieworg[2]; + } + } + + pparams->vieworg[2] += waterOffset; + + V_CalcViewRoll ( pparams ); + + V_AddIdle ( pparams ); + + // offsets + VectorCopy( pparams->cl_viewangles, angles ); + + AngleVectors ( angles, pparams->forward, pparams->right, pparams->up ); + + for ( i=0 ; i<3 ; i++ ) + { + pparams->vieworg[i] += scr_ofsx->value*pparams->forward[i] + scr_ofsy->value*pparams->right[i] + scr_ofsz->value*pparams->up[i]; + } + + // Treating cam_ofs[2] as the distance + if( CL_IsThirdPerson() ) + { + vec3_t ofs; + + ofs[0] = ofs[1] = ofs[2] = 0.0; + + CL_CameraOffset( (float *)&ofs ); + + VectorCopy( ofs, camAngles ); + camAngles[ ROLL ] = 0; + + AngleVectors( camAngles, camForward, camRight, camUp ); + + for ( i = 0; i < 3; i++ ) + { + pparams->vieworg[ i ] += -ofs[2] * camForward[ i ]; + } + } + + // Give gun our viewangles + VectorCopy ( pparams->cl_viewangles, view->angles ); + + // set up gun position + V_CalcGunAngle ( pparams ); + + // Use predicted origin as view origin. + VectorCopy ( pparams->simorg, view->origin ); + view->origin[2] += ( waterOffset ); + VectorAdd( view->origin, pparams->viewheight, view->origin ); + + // Let the viewmodel shake at about 10% of the amplitude + gEngfuncs.V_ApplyShake( view->origin, view->angles, 0.9 ); + + for ( i = 0; i < 3; i++ ) + { + view->origin[ i ] += bob * 0.4 * pparams->forward[ i ]; + } + view->origin[2] += bob; + + // throw in a little tilt. + view->angles[YAW] -= bob * 0.5; + view->angles[ROLL] -= bob * 1; + view->angles[PITCH] -= bob * 0.3; + + // pushing the view origin down off of the same X/Z plane as the ent's origin will give the + // gun a very nice 'shifting' effect when the player looks up/down. If there is a problem + // with view model distortion, this may be a cause. (SJB). + view->origin[2] -= 1; + + // fudge position around to keep amount of weapon visible + // roughly equal with different FOV + if (pparams->viewsize == 110) + { + view->origin[2] += 1; + } + else if (pparams->viewsize == 100) + { + view->origin[2] += 2; + } + else if (pparams->viewsize == 90) + { + view->origin[2] += 1; + } + else if (pparams->viewsize == 80) + { + view->origin[2] += 0.5; + } + + // Add in the punchangle, if any + VectorAdd ( pparams->viewangles, pparams->punchangle, pparams->viewangles ); + + // Include client side punch, too + VectorAdd ( pparams->viewangles, (float *)&ev_punchangle, pparams->viewangles); + + V_DropPunchAngle ( pparams->frametime, (float *)&ev_punchangle ); + + // smooth out stair step ups +#if 1 + if ( !pparams->smoothing && pparams->onground && pparams->simorg[2] - oldz > 0) + { + float steptime; + + steptime = pparams->time - lasttime; + if (steptime < 0) + //FIXME I_Error ("steptime < 0"); + steptime = 0; + + oldz += steptime * 150; + if (oldz > pparams->simorg[2]) + oldz = pparams->simorg[2]; + if (pparams->simorg[2] - oldz > 18) + oldz = pparams->simorg[2]- 18; + pparams->vieworg[2] += oldz - pparams->simorg[2]; + view->origin[2] += oldz - pparams->simorg[2]; + } + else + { + oldz = pparams->simorg[2]; + } +#endif + + { + static float lastorg[3]; + vec3_t delta; + + VectorSubtract( pparams->simorg, lastorg, delta ); + + if ( Length( delta ) != 0.0 ) + { + VectorCopy( pparams->simorg, ViewInterp.Origins[ ViewInterp.CurrentOrigin & ORIGIN_MASK ] ); + ViewInterp.OriginTime[ ViewInterp.CurrentOrigin & ORIGIN_MASK ] = pparams->time; + ViewInterp.CurrentOrigin++; + + VectorCopy( pparams->simorg, lastorg ); + } + } + + // Smooth out whole view in multiplayer when on trains, lifts + if ( cl_vsmoothing && cl_vsmoothing->value && + ( pparams->smoothing && ( pparams->maxclients > 1 ) ) ) + { + int foundidx; + int i; + float t; + + if ( cl_vsmoothing->value < 0.0 ) + { + gEngfuncs.Cvar_SetValue( "cl_vsmoothing", 0.0 ); + } + + t = pparams->time - cl_vsmoothing->value; + + for ( i = 1; i < ORIGIN_MASK; i++ ) + { + foundidx = ViewInterp.CurrentOrigin - 1 - i; + if ( ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ] <= t ) + break; + } + + if ( i < ORIGIN_MASK && ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ] != 0.0 ) + { + // Interpolate + vec3_t delta; + double frac; + double dt; + vec3_t neworg; + + dt = ViewInterp.OriginTime[ (foundidx + 1) & ORIGIN_MASK ] - ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ]; + if ( dt > 0.0 ) + { + frac = ( t - ViewInterp.OriginTime[ foundidx & ORIGIN_MASK] ) / dt; + frac = min( 1.0, frac ); + VectorSubtract( ViewInterp.Origins[ ( foundidx + 1 ) & ORIGIN_MASK ], ViewInterp.Origins[ foundidx & ORIGIN_MASK ], delta ); + VectorMA( ViewInterp.Origins[ foundidx & ORIGIN_MASK ], frac, delta, neworg ); + + VectorSubtract( neworg, pparams->simorg, delta ); + + VectorAdd( pparams->simorg, delta, pparams->simorg ); + VectorAdd( pparams->vieworg, delta, pparams->vieworg ); + VectorAdd( view->origin, delta, view->origin ); + } + } + } + + // Store off v_angles before munging for third person + v_angles = pparams->viewangles; + v_cl_angles = pparams->cl_viewangles; + + if ( CL_IsThirdPerson() ) + { + VectorCopy( camAngles, pparams->viewangles); + } + + // override all previous settings if the viewent isn't the client + if ( pparams->viewentity > pparams->maxclients ) + { + cl_entity_t *viewentity; + viewentity = gEngfuncs.GetEntityByIndex( pparams->viewentity ); + if ( viewentity ) + { + VectorCopy( viewentity->origin, pparams->vieworg ); + VectorCopy( viewentity->angles, pparams->viewangles ); + + // Store off overridden viewangles + v_angles = pparams->viewangles; + } + } + + lasttime = pparams->time; + + v_origin = pparams->vieworg; +} +void V_SmoothInterpolateAngles( float * startAngle, float * endAngle, float * finalAngle, float degreesPerSec ) +{ + float frac,d; + + NormalizeAngles( startAngle ); + NormalizeAngles( endAngle ); + + for ( int i = 0 ; i < 3 ; i++ ) + { + d = endAngle[i] - startAngle[i]; + + if ( d > 180.0f ) + { + d -= 360.0f; + } + else if ( d < -180.0f ) + { + d += 360.0f; + } + + float absd = fabs(d); + + if ( absd > 0.1f ) + { + frac = (degreesPerSec * v_frametime ) / absd; + + if ( absd < 45.0f ) + frac*= absd / 45.0f; // slow down last 45 degrees + + if ( frac > 1.0f ) + { + finalAngle[i] = endAngle[i]; + } + else + { + finalAngle[i] = startAngle[i] + d * frac; + } + } + else + { + finalAngle[i] = endAngle[i]; + } + + } + + NormalizeAngles( finalAngle ); +} + +// Get the origin of the Observer based around the target's position and angles +void V_GetChaseOrigin( float * angles, float * origin, float distance, qboolean worldOnly, float * returnvec ) +{ + int tracefinished = false; + vec3_t vecEnd; + vec3_t forward; + vec3_t vecStart; + pmtrace_t * trace; + + // Trace back from the target using the player's view angles + AngleVectors(angles, forward, NULL, NULL); + + VectorScale(forward,-1,forward); + + VectorCopy( origin, vecStart ); + + VectorMA(vecStart, distance , forward, vecEnd); + + while (!tracefinished) + { + + trace = gEngfuncs.PM_TraceLine( vecStart, vecEnd, PM_TRACELINE_PHYSENTSONLY, 2, -1 ); + + if ( trace->ent <= 0 || !worldOnly) + { + tracefinished = true; + } + else + { + if( Distance(trace->endpos, vecEnd ) > 1.0f ) + { + VectorAdd( trace->endpos, forward, vecStart); + } + else + { + tracefinished = true; + } + } + } + + // gEngfuncs.Con_Printf("Trace loop %i\n", trace->ent ); + + VectorMA( trace->endpos, 4, trace->plane.normal, returnvec ); + + v_lastDistance = Distance(trace->endpos, origin); // real distance without offset +} + +void V_GetDirectedChasePosition(cl_entity_t * ent1, cl_entity_t * ent2,float * angle, float * origin) +{ + float newAngle[3]; float newOrigin[3]; float tempVec[3]; + + int flags = gHUD.m_Spectator.m_iObserverFlags; + + qboolean deadPlayer = ent1->player && (ent1->curstate.solid == SOLID_NOT); + + float dfactor = ( flags & DRC_FLAG_DRAMATIC )? -1.0f : 1.0f; + + if ( ent1->player && (ent1->curstate.solid == SOLID_NOT) ) + dfactor = 1.5f; // zoom away if player dies + + float distance = 112.0f + ( 16.0f * dfactor ); // get close if dramatic; + + // go away in final scenes + if (flags & DRC_FLAG_FINAL ) + distance*=2.0f; + + // let v_lastDistance float smoothly away + v_lastDistance+= v_frametime * 24.0f; // move unit per seconds back + + if ( distance > v_lastDistance ) + distance = v_lastDistance; + + + VectorCopy(ent1->origin, newOrigin); + + if ( ent1->player ) + { + if ( deadPlayer ) + newOrigin[2]+= 2; //laying on ground + else + newOrigin[2]+= 28; // head level of living player + + } + else + newOrigin[2]+= 8; // object, tricky, must be above bomb in CS + + if ( ( ent2 == (cl_entity_t*)0xFFFFFFFF ) || deadPlayer ) // we have no second target or player just died + { + // we have no second target, choose view direction based on + // show front of primary target + VectorCopy(ent1->angles, newAngle); + newAngle[1]+= 180.0f; + + newAngle[0]+= 12.5f * dfactor; // lower angle if dramatic + + // if final scene (bomb), show from real high pos + if ( flags & DRC_FLAG_FINAL ) + newAngle[0] = 22.5f; + + // choose side of object/player + if ( flags & DRC_FLAG_SIDE ) + newAngle[1]+=22.5f; + else + newAngle[1]-=22.5f; + + // if ( AngleBetweenVectors( tempVec, newAngle ) > 1.0f ) + V_SmoothInterpolateAngles( v_lastAngles, newAngle, angle, 120.0f ); + + // HACK, if player is dead don't clip against his dead body, can't check this + V_GetChaseOrigin( angle, newOrigin, distance, deadPlayer, origin ); + + } + else if ( ent2 ) + { + // get new angle towards second target + VectorSubtract( ent2->origin, ent1->origin, newAngle ); + + VectorAngles( newAngle, newAngle ); + newAngle[0] = -newAngle[0]; + + // set angle diffrent in Dramtaic scenes + newAngle[0]+= 12.5f * dfactor; // lower angle if dramatic + + if ( flags & DRC_FLAG_SIDE ) + newAngle[1]+=22.5f; + else + newAngle[1]-=22.5f; + + V_GetChaseOrigin( newAngle, newOrigin, distance, false, origin ); + + origin[2]+= 16.0f*( 1.0f - (v_lastDistance / distance) ); + + // calculate angle to second target + VectorSubtract( ent2->origin, origin, tempVec ); + VectorAngles( tempVec, tempVec ); + tempVec[0] = -tempVec[0]; + + // take middle between two viewangles + InterpolateAngles( newAngle, tempVec, angle, 0.5f); + + } + else + { + // second target disappeard somehow (dead) + // keep last good viewangle + V_GetChaseOrigin( angle, newOrigin, distance, false, origin ); + } + + VectorCopy(angle, v_lastAngles); +} + +void V_GetChasePos(int target, float * cl_angles, float * origin, float * angles) +{ + if ( !target) + { + // just copy a save in-map position + VectorCopy ( vJumpAngles, angles ); + VectorCopy ( vJumpOrigin, origin ); + return; + }; + + cl_entity_t * ent = gEngfuncs.GetEntityByIndex( target ); + + if (!ent) return; + + + if ( gHUD.m_Spectator.m_autoDirector->value ) + { + if ( g_iUser3 ) + V_GetDirectedChasePosition( ent, gEngfuncs.GetEntityByIndex( g_iUser3 ), + angles, origin ); + else + V_GetDirectedChasePosition( ent, ( cl_entity_t*)0xFFFFFFFF, + angles, origin ); + } + else + { + if ( cl_angles == NULL ) // no mouse angles given, use entity angles ( locked mode ) + { + VectorCopy ( ent->angles, angles); + angles[0]*=-1; + } + else + VectorCopy ( cl_angles, angles); + + + VectorCopy ( ent->origin, origin); + + origin[2]+= 28; // DEFAULT_VIEWHEIGHT - some offset + + V_GetChaseOrigin( angles, origin, cl_chasedist->value, false, origin ); + } +} + +void V_ResetChaseCam() +{ + v_lastDistance = 4096.0f; +} + + +void V_GetInEyePos(int target, float * origin, float * angles ) +{ + if ( !target) + { + // just copy a save in-map position + VectorCopy ( vJumpAngles, angles ); + VectorCopy ( vJumpOrigin, origin ); + return; + }; + + + cl_entity_t * ent = gEngfuncs.GetEntityByIndex( target ); + + if ( !ent ) + return; + + VectorCopy ( ent->origin, origin ); + VectorCopy ( ent->angles, angles ); + + angles[0]*=-M_PI; + + if ( ent->curstate.solid == SOLID_NOT ) + { + angles[ROLL] = 80; // dead view angle + origin[2]+= -8 ; // PM_DEAD_VIEWHEIGHT + } + else if (ent->curstate.usehull == 1 ) + origin[2]+= 12; // VEC_DUCK_VIEW; + else + // exacty eye position can't be caluculated since it depends on + // client values like cl_bobcycle, this offset matches the default values + origin[2]+= 28; // DEFAULT_VIEWHEIGHT +} + +void V_GetMapFreePosition( float * cl_angles, float * origin, float * angles ) +{ + vec3_t forward; + vec3_t zScaledTarget; + + VectorCopy(cl_angles, angles); + + // modify angles since we don't wanna see map's bottom + angles[0] = 51.25f + 38.75f*(angles[0]/90.0f); + + zScaledTarget[0] = gHUD.m_Spectator.m_mapOrigin[0]; + zScaledTarget[1] = gHUD.m_Spectator.m_mapOrigin[1]; + zScaledTarget[2] = gHUD.m_Spectator.m_mapOrigin[2] * (( 90.0f - angles[0] ) / 90.0f ); + + + AngleVectors(angles, forward, NULL, NULL); + + VectorNormalize(forward); + + VectorMA(zScaledTarget, -( 4096.0f / gHUD.m_Spectator.m_mapZoom ), forward , origin); +} + +void V_GetMapChasePosition(int target, float * cl_angles, float * origin, float * angles) +{ + vec3_t forward; + + if ( target ) + { + cl_entity_t * ent = gEngfuncs.GetEntityByIndex( target ); + + if ( gHUD.m_Spectator.m_autoDirector->value ) + { + // this is done to get the angles made by director mode + V_GetChasePos(target, cl_angles, origin, angles); + VectorCopy(ent->origin, origin); + + // keep fix chase angle horizontal + angles[0] = 45.0f; + } + else + { + VectorCopy(cl_angles, angles); + VectorCopy(ent->origin, origin); + + // modify angles since we don't wanna see map's bottom + angles[0] = 51.25f + 38.75f*(angles[0]/90.0f); + } + } + else + { + // keep out roaming position, but modify angles + VectorCopy(cl_angles, angles); + angles[0] = 51.25f + 38.75f*(angles[0]/90.0f); + } + + origin[2] *= (( 90.0f - angles[0] ) / 90.0f ); + angles[2] = 0.0f; // don't roll angle (if chased player is dead) + + AngleVectors(angles, forward, NULL, NULL); + + VectorNormalize(forward); + + VectorMA(origin, -1536, forward, origin); +} + +int V_FindViewModelByWeaponModel(int weaponindex) +{ + + static char * modelmap[][2] = { + +#ifdef THREEWAVE + // { "models/p_grapple.mdl", "models/v_grapple.mdl" }, +#endif + + { "models/p_crowbar.mdl", "models/v_crowbar.mdl" }, + { "models/p_shot.mdl", "models/v_shot.mdl" }, + { "models/p_shot2.mdl", "models/v_shot2.mdl" }, + { "models/p_nail.mdl", "models/v_nail.mdl" }, + { "models/p_nail2.mdl", "models/v_nail2.mdl" }, + { "models/p_rock.mdl", "models/v_rock.mdl" }, + { "models/p_rock2.mdl", "models/v_rock2.mdl" }, + { "models/p_light.mdl", "models/v_light.mdl" }, + { NULL, NULL } }; + + struct model_s * weaponModel = IEngineStudio.GetModelByIndex( weaponindex ); + + if ( weaponModel ) + { + int len = strlen( weaponModel->name ); + int i = 0; + + while ( *modelmap[i] != NULL ) + { + if ( !strnicmp( weaponModel->name, modelmap[i][0], len ) ) + { + return gEngfuncs.pEventAPI->EV_FindModelIndex( modelmap[i][1] ); + } + i++; + } + + return 0; + } + else + return 0; + +} + + +/* +================== +V_CalcSpectatorRefdef + +================== +*/ +void V_CalcSpectatorRefdef ( struct ref_params_s * pparams ) +{ + + vec3_t angles; + static viewinterp_t ViewInterp; + static float bob = 0.0f; + static vec3_t velocity ( 0.0f, 0.0f, 0.0f); + + static int lastWeaponModelIndex = 0; + static int lastViewModelIndex = 0; + + cl_entity_t * ent = gEngfuncs.GetEntityByIndex( g_iUser2 ); + cl_entity_t * gunModel = gEngfuncs.GetViewModel(); + static float lasttime; + + static float lastang[3]; + static float lastorg[3]; + + vec3_t delta; + pparams->onlyClientDraw = false; + + // refresh position + VectorCopy ( pparams->simorg, v_sim_org ); + + // get old values + VectorCopy ( pparams->cl_viewangles, v_cl_angles ); + VectorCopy ( pparams->viewangles, v_angles ); + VectorCopy ( pparams->vieworg, v_origin ); + v_frametime = pparams->frametime; + + if ( pparams->nextView == 0 ) + { + // first renderer cycle, full screen + + switch ( g_iUser1 ) + { + case OBS_CHASE_LOCKED: V_GetChasePos( g_iUser2, NULL, v_origin, v_angles ); + break; + + case OBS_CHASE_FREE: V_GetChasePos( g_iUser2, v_cl_angles, v_origin, v_angles ); + break; + + case OBS_ROAMING : VectorCopy (v_cl_angles, v_angles); + VectorCopy (v_sim_org, v_origin); + break; + + case OBS_IN_EYE : V_GetInEyePos( g_iUser2, v_origin, v_angles ); + break; + + case OBS_MAP_FREE : pparams->onlyClientDraw = true; + V_GetMapFreePosition( v_cl_angles, v_origin, v_angles ); + break; + + case OBS_MAP_CHASE : pparams->onlyClientDraw = true; + V_GetMapChasePosition( g_iUser2, v_cl_angles, v_origin, v_angles ); + break; + } + + if ( gHUD.m_Spectator.m_pip->value ) + pparams->nextView = 1; // force a second renderer view + + gHUD.m_Spectator.m_iDrawCycle = 0; + + } + else + { + // second renderer cycle, inset window + + // set inset parameters + pparams->viewport[0] = XRES(gHUD.m_Spectator.m_OverviewData.insetWindowX); // change viewport to inset window + pparams->viewport[1] = YRES(gHUD.m_Spectator.m_OverviewData.insetWindowY); + pparams->viewport[2] = XRES(gHUD.m_Spectator.m_OverviewData.insetWindowWidth); + pparams->viewport[3] = YRES(gHUD.m_Spectator.m_OverviewData.insetWindowHeight); + pparams->nextView = 0; // on further view + pparams->onlyClientDraw = false; + + // override some settings in certain modes + switch ( (int)gHUD.m_Spectator.m_pip->value ) + { + case INSET_CHASE_FREE : V_GetChasePos( g_iUser2, v_cl_angles, v_origin, v_angles ); + break; + + case INSET_IN_EYE : V_GetInEyePos( g_iUser2, v_origin, v_angles ); + break; + + case INSET_MAP_FREE : pparams->onlyClientDraw = true; + V_GetMapFreePosition( v_cl_angles, v_origin, v_angles ); + break; + + case INSET_MAP_CHASE : pparams->onlyClientDraw = true; + + if ( g_iUser1 == OBS_ROAMING ) + V_GetMapChasePosition( 0, v_cl_angles, v_origin, v_angles ); + else + V_GetMapChasePosition( g_iUser2, v_cl_angles, v_origin, v_angles ); + + break; + } + + gHUD.m_Spectator.m_iDrawCycle = 1; + } + + + // do the smoothing only once per frame, not in roaming or map mode + if ( (gHUD.m_Spectator.m_iDrawCycle == 0) && (g_iUser1 == OBS_IN_EYE) ) + { + // smooth angles + + VectorSubtract( v_angles, lastang, delta ); + if ( Length( delta ) != 0.0f ) + { + VectorCopy( v_angles, ViewInterp.Angles[ ViewInterp.CurrentAngle & ORIGIN_MASK ] ); + ViewInterp.AngleTime[ ViewInterp.CurrentAngle & ORIGIN_MASK ] = pparams->time; + ViewInterp.CurrentAngle++; + VectorCopy( v_angles, lastang ); + } + + if ( cl_vsmoothing && cl_vsmoothing->value ) + { + int foundidx; + int i; + float t; + + t = pparams->time - cl_vsmoothing->value; + + for ( i = 1; i < ORIGIN_MASK; i++ ) + { + foundidx = ViewInterp.CurrentAngle - 1 - i; + if ( ViewInterp.AngleTime[ foundidx & ORIGIN_MASK ] <= t ) + break; + } + + if ( i < ORIGIN_MASK && ViewInterp.AngleTime[ foundidx & ORIGIN_MASK ] != 0.0 ) + { + // Interpolate + double dt; + float da; + vec3_t v1,v2; + + AngleVectors( ViewInterp.Angles[ foundidx & ORIGIN_MASK ], v1, NULL, NULL ); + AngleVectors( ViewInterp.Angles[ (foundidx + 1) & ORIGIN_MASK ], v2, NULL, NULL ); + da = AngleBetweenVectors( v1, v2 ); + + dt = ViewInterp.AngleTime[ (foundidx + 1) & ORIGIN_MASK ] - ViewInterp.AngleTime[ foundidx & ORIGIN_MASK ]; + + if ( dt > 0.0 && ( da < 22.5f) ) + { + double frac; + + frac = ( t - ViewInterp.AngleTime[ foundidx & ORIGIN_MASK] ) / dt; + frac = min( 1.0, frac ); + + // interpolate angles + InterpolateAngles( ViewInterp.Angles[ foundidx & ORIGIN_MASK ], ViewInterp.Angles[ (foundidx + 1) & ORIGIN_MASK ], v_angles, frac ); + } + } + } + + // smooth origin + + VectorSubtract( v_origin, lastorg, delta ); + + if ( Length( delta ) != 0.0 ) + { + VectorCopy( v_origin, ViewInterp.Origins[ ViewInterp.CurrentOrigin & ORIGIN_MASK ] ); + ViewInterp.OriginTime[ ViewInterp.CurrentOrigin & ORIGIN_MASK ] = pparams->time; + ViewInterp.CurrentOrigin++; + + VectorCopy( v_origin, lastorg ); + } + + // don't smooth in roaming (already smoothd), + if ( cl_vsmoothing && cl_vsmoothing->value ) + { + int foundidx; + int i; + float t; + + t = pparams->time - cl_vsmoothing->value; + + for ( i = 1; i < ORIGIN_MASK; i++ ) + { + foundidx = ViewInterp.CurrentOrigin - 1 - i; + if ( ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ] <= t ) + break; + } + + if ( i < ORIGIN_MASK && ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ] != 0.0 ) + { + // Interpolate + vec3_t delta; + double frac; + double dt; + vec3_t neworg; + + dt = ViewInterp.OriginTime[ (foundidx + 1) & ORIGIN_MASK ] - ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ]; + if ( dt > 0.0 ) + { + frac = ( t - ViewInterp.OriginTime[ foundidx & ORIGIN_MASK] ) / dt; + frac = min( 1.0, frac ); + VectorSubtract( ViewInterp.Origins[ ( foundidx + 1 ) & ORIGIN_MASK ], ViewInterp.Origins[ foundidx & ORIGIN_MASK ], delta ); + VectorMA( ViewInterp.Origins[ foundidx & ORIGIN_MASK ], frac, delta, neworg ); + + // Dont interpolate large changes + if ( Length( delta ) < 64 ) + { + VectorCopy( neworg, v_origin ); + } + } + } + } + } + + // Hack in weapon model: + + + if ( (g_iUser1 == OBS_IN_EYE || gHUD.m_Spectator.m_pip->value == INSET_IN_EYE) + && ent && g_iUser2 ) + { + // get position for weapon model + VectorCopy( v_origin, gunModel->origin); + VectorCopy( v_angles, gunModel->angles); + + // add idle tremble + gunModel->angles[PITCH]*=-1; + + // calculate player velocity + float timeDiff = ent->curstate.msg_time - ent->prevstate.msg_time; + + if ( timeDiff > 0 ) + { + vec3_t distance; + VectorSubtract(ent->prevstate.origin, ent->curstate.origin, distance); + VectorScale(distance, 1/timeDiff, distance ); + + velocity[0] = velocity[0]*0.66f + distance[0]*0.33f; + velocity[1] = velocity[1]*0.66f + distance[1]*0.33f; + velocity[2] = velocity[2]*0.66f + distance[2]*0.33f; + + VectorCopy(velocity, pparams->simvel); + pparams->onground = 1; + + bob = V_CalcBob( pparams ); + } + + vec3_t forward; + AngleVectors(v_angles, forward, NULL, NULL ); + + for ( int i = 0; i < 3; i++ ) + { + gunModel->origin[ i ] += bob * 0.4 * forward[ i ]; + } + + // throw in a little tilt. + gunModel->angles[YAW] -= bob * 0.5; + gunModel->angles[ROLL] -= bob * 1; + gunModel->angles[PITCH] -= bob * 0.3; + + VectorCopy( gunModel->angles, gunModel->curstate.angles ); + VectorCopy( gunModel->angles, gunModel->latched.prevangles ); + + if ( lastWeaponModelIndex != ent->curstate.weaponmodel ) + { + // weapon model changed + + lastWeaponModelIndex = ent->curstate.weaponmodel; + lastViewModelIndex = V_FindViewModelByWeaponModel( lastWeaponModelIndex ); + if ( lastViewModelIndex ) + { + gEngfuncs.pfnWeaponAnim(0,0); // reset weapon animation + } + else + { + // model not found + gunModel->model = NULL; // disable weaopn model + lastWeaponModelIndex = lastViewModelIndex = 0; + } + } + + if ( lastViewModelIndex ) + { + gunModel->model = IEngineStudio.GetModelByIndex( lastViewModelIndex ); + gunModel->curstate.modelindex = lastViewModelIndex; + gunModel->curstate.frame = 0; + gunModel->curstate.colormap = 0; + gunModel->index = g_iUser2; + } + else + { + gunModel->model = NULL; // disable weaopn model + } + } + else + { + gunModel->model = NULL; // disable weaopn model + lastWeaponModelIndex = lastViewModelIndex = 0; + } + + lasttime = pparams->time; + + // write back new values into pparams + + VectorCopy ( v_angles, pparams->viewangles ) + VectorCopy ( v_origin, pparams->vieworg ); + +} + +void DLLEXPORT V_CalcRefdef( struct ref_params_s *pparams ) +{ + // intermission / finale rendering + if ( pparams->intermission ) + { + V_CalcIntermissionRefdef ( pparams ); + } + else if ( pparams->spectator || g_iUser1 ) // g_iUser true if in spectator mode + { + V_CalcSpectatorRefdef ( pparams ); + } + else if ( !pparams->paused ) + { + V_CalcNormalRefdef ( pparams ); + } +} + +/* +============= +V_DropPunchAngle + +============= +*/ +void V_DropPunchAngle ( float frametime, float *ev_punchangle ) +{ + float len; + + len = VectorNormalize ( ev_punchangle ); + len -= (10.0 + len * 0.5) * frametime; + len = max( len, 0.0 ); + VectorScale ( ev_punchangle, len, ev_punchangle ); +} + +/* +============= +V_PunchAxis + +Client side punch effect +============= +*/ +void V_PunchAxis( int axis, float punch ) +{ + ev_punchangle[ axis ] = punch; +} + +/* +============= +V_Init +============= +*/ +void V_Init (void) +{ + gEngfuncs.pfnAddCommand ("centerview", V_StartPitchDrift ); + + scr_ofsx = gEngfuncs.pfnRegisterVariable( "scr_ofsx","0", 0 ); + scr_ofsy = gEngfuncs.pfnRegisterVariable( "scr_ofsy","0", 0 ); + scr_ofsz = gEngfuncs.pfnRegisterVariable( "scr_ofsz","0", 0 ); + + v_centermove = gEngfuncs.pfnRegisterVariable( "v_centermove", "0.15", 0 ); + v_centerspeed = gEngfuncs.pfnRegisterVariable( "v_centerspeed","500", 0 ); + + cl_bobcycle = gEngfuncs.pfnRegisterVariable( "cl_bobcycle","0.8", 0 );// best default for my experimental gun wag (sjb) + cl_bob = gEngfuncs.pfnRegisterVariable( "cl_bob","0.01", 0 );// best default for my experimental gun wag (sjb) + cl_bobup = gEngfuncs.pfnRegisterVariable( "cl_bobup","0.5", 0 ); + cl_waterdist = gEngfuncs.pfnRegisterVariable( "cl_waterdist","4", 0 ); + cl_chasedist = gEngfuncs.pfnRegisterVariable( "cl_chasedist","112", 0 ); +} + + +//#define TRACE_TEST +#if defined( TRACE_TEST ) + +extern float in_fov; +/* +==================== +CalcFov +==================== +*/ +float CalcFov (float fov_x, float width, float height) +{ + float a; + float x; + + if (fov_x < 1 || fov_x > 179) + fov_x = 90; // error, set to 90 + + x = width/tan(fov_x/360*M_PI); + + a = atan (height/x); + + a = a*360/M_PI; + + return a; +} + +int hitent = -1; + +void V_Move( int mx, int my ) +{ + float fov; + float fx, fy; + float dx, dy; + float c_x, c_y; + float dX, dY; + vec3_t forward, up, right; + vec3_t newangles; + + vec3_t farpoint; + pmtrace_t tr; + + fov = CalcFov( in_fov, (float)ScreenWidth, (float)ScreenHeight ); + + c_x = (float)ScreenWidth / 2.0; + c_y = (float)ScreenHeight / 2.0; + + dx = (float)mx - c_x; + dy = (float)my - c_y; + + // Proportion we moved in each direction + fx = dx / c_x; + fy = dy / c_y; + + dX = fx * in_fov / 2.0 ; + dY = fy * fov / 2.0; + + newangles = v_angles; + + newangles[ YAW ] -= dX; + newangles[ PITCH ] += dY; + + // Now rotate v_forward around that point + AngleVectors ( newangles, forward, right, up ); + + farpoint = v_origin + 8192 * forward; + + // Trace + tr = *(gEngfuncs.PM_TraceLine( (float *)&v_origin, (float *)&farpoint, PM_TRACELINE_PHYSENTSONLY, 2 /*point sized hull*/, -1 )); + + if ( tr.fraction != 1.0 && tr.ent != 0 ) + { + hitent = PM_GetInfo( tr.ent ); + PM_ParticleLine( (float *)&v_origin, (float *)&tr.endpos, 5, 1.0, 0.0 ); + } + else + { + hitent = -1; + } +} + +#endif diff --git a/hlsdk/dmc/cl_dll/view.h b/hlsdk/dmc/cl_dll/view.h new file mode 100644 index 00000000..1afbe38e --- /dev/null +++ b/hlsdk/dmc/cl_dll/view.h @@ -0,0 +1,15 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined ( VIEWH ) +#define VIEWH +#pragma once + +void V_StartPitchDrift( void ); +void V_StopPitchDrift( void ); + +#endif // !VIEWH \ No newline at end of file diff --git a/hlsdk/dmc/cl_dll/wrect.h b/hlsdk/dmc/cl_dll/wrect.h new file mode 100644 index 00000000..a3494aed --- /dev/null +++ b/hlsdk/dmc/cl_dll/wrect.h @@ -0,0 +1,16 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( WRECTH ) +#define WRECTH + +typedef struct rect_s +{ + int left, right, top, bottom; +} wrect_t; + +#endif \ No newline at end of file diff --git a/hlsdk/dmc/dlls/Makefile b/hlsdk/dmc/dlls/Makefile new file mode 100644 index 00000000..710d64e9 --- /dev/null +++ b/hlsdk/dmc/dlls/Makefile @@ -0,0 +1,127 @@ +# +# Half-Life DMC SDK 2.3 dmc_i386.so Makefile for x86 Linux +# +# October 2002 by Leon Hartwig (hartwig@valvesoftware.com) +# + +DLLNAME=dmc + +ARCH=i386 + +#make sure this is the correct compiler for your system +CC=gcc + +DLL_SRCDIR=. +ENGINE_SRCDIR=../../engine +COMMON_SRCDIR=../../common +PM_SHARED_SRCDIR=../pm_shared +GAME_SHARED_SRCDIR=../../game_shared + +DLL_OBJDIR=$(DLL_SRCDIR)/obj +PM_SHARED_OBJDIR=$(DLL_OBJDIR)/pm_shared +GAME_SHARED_OBJDIR=$(DLL_OBJDIR)/game_shared + +BASE_CFLAGS= -Dstricmp=strcasecmp -D_strnicmp=strncasecmp -Dstrnicmp=strncasecmp -D_vsnprintf=vsnprintf\ + -DCLIENT_WEAPONS + +#safe optimization +CFLAGS=$(BASE_CFLAGS) -Wall -Wno-non-virtual-dtor -Wno-invalid-offsetof -Werror -m486 -O1 +C_FLAGS=$(BASE_CFLAGS) -Wall -Werror -m486 -O1 + +#full optimization +#CFLAGS=$(BASE_CFLAGS) -Wall -Wno-non-virtual-dtor -Wno-invalid-offsetof -Werror -O1 -m486 -ffast-math -funroll-loops \ + -fomit-frame-pointer -fexpensive-optimizations \ + -malign-loops=2 -malign-jumps=2 -malign-functions=2 + +#use these when debugging +#CFLAGS=$(BASE_CFLAGS) -g + +INCLUDEDIRS=-I. -I$(ENGINE_SRCDIR) -I$(COMMON_SRCDIR) -I$(PM_SHARED_SRCDIR) -I$(GAME_SHARED_SRCDIR) + +LDFLAGS= + +SHLIBEXT=so +SHLIBCFLAGS=-fPIC +SHLIBLDFLAGS=-shared + +DO_CC=$(CC) $(CFLAGS) $(SHLIBCFLAGS) $(INCLUDEDIRS) -o $@ -c $< +DO_C=$(CC) $(C_FLAGS) $(SHLIBCFLAGS) $(INCLUDEDIRS) -o $@ -c $< + +############################################################################# +# SETUP AND BUILD +# GAME +############################################################################# + +$(DLL_OBJDIR)/%.o: $(DLL_SRCDIR)/%.cpp + $(DO_CC) + +$(GAME_SHARED_OBJDIR)/%.o: $(GAME_SHARED_SRCDIR)/%.cpp + $(DO_CC) + +$(PM_SHARED_OBJDIR)/%.o: $(PM_SHARED_SRCDIR)/%.c + $(DO_C) + +OBJ = \ + $(DLL_OBJDIR)/animating.o \ + $(DLL_OBJDIR)/animation.o \ + $(DLL_OBJDIR)/bmodels.o \ + $(DLL_OBJDIR)/buttons.o \ + $(DLL_OBJDIR)/cbase.o \ + $(DLL_OBJDIR)/client.o \ + $(DLL_OBJDIR)/combat.o \ + $(DLL_OBJDIR)/doors.o \ + $(DLL_OBJDIR)/effects.o \ + $(DLL_OBJDIR)/explode.o \ + $(DLL_OBJDIR)/func_break.o \ + $(DLL_OBJDIR)/game.o \ + $(DLL_OBJDIR)/gamerules.o \ + $(DLL_OBJDIR)/globals.o \ + $(DLL_OBJDIR)/h_ai.o \ + $(DLL_OBJDIR)/h_export.o \ + $(DLL_OBJDIR)/lights.o \ + $(DLL_OBJDIR)/maprules.o \ + $(DLL_OBJDIR)/monsters.o \ + $(DLL_OBJDIR)/monsterstate.o \ + $(DLL_OBJDIR)/multiplay_gamerules.o \ + $(DLL_OBJDIR)/nodes.o \ + $(DLL_OBJDIR)/observer.o \ + $(DLL_OBJDIR)/pathcorner.o \ + $(DLL_OBJDIR)/plane.o \ + $(DLL_OBJDIR)/plats.o \ + $(DLL_OBJDIR)/player.o \ + $(DLL_OBJDIR)/quake_gun.o \ + $(DLL_OBJDIR)/quake_items.o \ + $(DLL_OBJDIR)/quake_nail.o \ + $(DLL_OBJDIR)/quake_player.o \ + $(DLL_OBJDIR)/quake_rocket.o \ + $(DLL_OBJDIR)/quake_weapons_all.o \ + $(DLL_OBJDIR)/schedule.o \ + $(DLL_OBJDIR)/singleplay_gamerules.o \ + $(DLL_OBJDIR)/skill.o \ + $(DLL_OBJDIR)/sound.o \ + $(DLL_OBJDIR)/spectator.o \ + $(DLL_OBJDIR)/subs.o \ + $(DLL_OBJDIR)/teamplay_gamerules.o \ + $(DLL_OBJDIR)/triggers.o \ + $(DLL_OBJDIR)/util.o \ + $(DLL_OBJDIR)/weapons.o \ + $(DLL_OBJDIR)/world.o \ + $(PM_SHARED_OBJDIR)/pm_shared.o \ + $(PM_SHARED_OBJDIR)/pm_math.o \ + $(PM_SHARED_OBJDIR)/pm_debug.o \ + $(GAME_SHARED_OBJDIR)/voice_gamemgr.o + +$(DLLNAME)_$(ARCH).$(SHLIBEXT) : neat $(OBJ) + $(CC) $(CFLAGS) $(SHLIBLDFLAGS) $(LDFLAGS) -o $@ $(OBJ) + +neat: + -mkdir -p $(DLL_OBJDIR) + -mkdir -p $(GAME_SHARED_OBJDIR) + -mkdir -p $(PM_SHARED_OBJDIR) +clean: + -rm -f $(OBJ) + -rm -f $(DLLNAME)_$(ARCH).$(SHLIBEXT) +spotless: clean + -rm -rf $(GAME_SHARED_OBJDIR) + -rm -rf $(PM_SHARED_OBJDIR) + -rm -rf $(DLL_OBJDIR) diff --git a/hlsdk/dmc/dlls/activity.h b/hlsdk/dmc/dlls/activity.h new file mode 100644 index 00000000..6fd3a188 --- /dev/null +++ b/hlsdk/dmc/dlls/activity.h @@ -0,0 +1,109 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef ACTIVITY_H +#define ACTIVITY_H + + +typedef enum { + ACT_RESET = 0, // Set m_Activity to this invalid value to force a reset to m_IdealActivity + ACT_IDLE = 1, + ACT_GUARD, + ACT_WALK, + ACT_RUN, + ACT_FLY, // Fly (and flap if appropriate) + ACT_SWIM, + ACT_HOP, // vertical jump + ACT_LEAP, // long forward jump + ACT_FALL, + ACT_LAND, + ACT_STRAFE_LEFT, + ACT_STRAFE_RIGHT, + ACT_ROLL_LEFT, // tuck and roll, left + ACT_ROLL_RIGHT, // tuck and roll, right + ACT_TURN_LEFT, // turn quickly left (stationary) + ACT_TURN_RIGHT, // turn quickly right (stationary) + ACT_CROUCH, // the act of crouching down from a standing position + ACT_CROUCHIDLE, // holding body in crouched position (loops) + ACT_STAND, // the act of standing from a crouched position + ACT_USE, + ACT_SIGNAL1, + ACT_SIGNAL2, + ACT_SIGNAL3, + ACT_TWITCH, + ACT_COWER, + ACT_SMALL_FLINCH, + ACT_BIG_FLINCH, + ACT_RANGE_ATTACK1, + ACT_RANGE_ATTACK2, + ACT_MELEE_ATTACK1, + ACT_MELEE_ATTACK2, + ACT_RELOAD, + ACT_ARM, // pull out gun, for instance + ACT_DISARM, // reholster gun + ACT_EAT, // monster chowing on a large food item (loop) + ACT_DIESIMPLE, + ACT_DIEBACKWARD, + ACT_DIEFORWARD, + ACT_DIEVIOLENT, + 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_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 ) + ACT_IDLE_ANGRY, // alternate idle animation in which the monster is clearly agitated. (loop) + ACT_WALK_HURT, // limp (loop) + ACT_RUN_HURT, // limp (loop) + ACT_HOVER, // Idle while in flight + ACT_GLIDE, // Fly (don't flap) + ACT_FLY_LEFT, // Turn left in flight + ACT_FLY_RIGHT, // Turn right in flight + ACT_DETECT_SCENT, // this means the monster smells a scent carried by the air + ACT_SNIFF, // this is the act of actually sniffing an item in front of the monster + ACT_BITE, // some large monsters can eat small things in one bite. This plays one time, EAT loops. + ACT_THREAT_DISPLAY, // without attacking, monster demonstrates that it is angry. (Yell, stick out chest, etc ) + ACT_FEAR_DISPLAY, // monster just saw something that it is afraid of + ACT_EXCITED, // for some reason, monster is excited. Sees something he really likes to eat, or whatever. + ACT_SPECIAL_ATTACK1, // very monster specific special attacks. + ACT_SPECIAL_ATTACK2, + ACT_COMBAT_IDLE, // agitated idle. + ACT_WALK_SCARED, + ACT_RUN_SCARED, + ACT_VICTORY_DANCE, // killed a player, do a victory dance. + ACT_DIE_HEADSHOT, // die, hit in head. + ACT_DIE_CHESTSHOT, // die, hit in chest + ACT_DIE_GUTSHOT, // die, hit in gut + ACT_DIE_BACKSHOT, // die, hit in back + ACT_FLINCH_HEAD, + ACT_FLINCH_CHEST, + ACT_FLINCH_STOMACH, + ACT_FLINCH_LEFTARM, + ACT_FLINCH_RIGHTARM, + ACT_FLINCH_LEFTLEG, + ACT_FLINCH_RIGHTLEG, +} Activity; + + +typedef struct { + int type; + char *name; +} activity_map_t; + +extern activity_map_t activity_map[]; + + +#endif //ACTIVITY_H diff --git a/hlsdk/dmc/dlls/activitymap.h b/hlsdk/dmc/dlls/activitymap.h new file mode 100644 index 00000000..5f77c55a --- /dev/null +++ b/hlsdk/dmc/dlls/activitymap.h @@ -0,0 +1,97 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#define _A( a ) { a, #a } + +activity_map_t activity_map[] = +{ +_A( ACT_IDLE ), +_A( ACT_GUARD ), +_A( ACT_WALK ), +_A( ACT_RUN ), +_A( ACT_FLY ), +_A( ACT_SWIM ), +_A( ACT_HOP ), +_A( ACT_LEAP ), +_A( ACT_FALL ), +_A( ACT_LAND ), +_A( ACT_STRAFE_LEFT ), +_A( ACT_STRAFE_RIGHT ), +_A( ACT_ROLL_LEFT ), +_A( ACT_ROLL_RIGHT ), +_A( ACT_TURN_LEFT ), +_A( ACT_TURN_RIGHT ), +_A( ACT_CROUCH ), +_A( ACT_CROUCHIDLE ), +_A( ACT_STAND ), +_A( ACT_USE ), +_A( ACT_SIGNAL1 ), +_A( ACT_SIGNAL2 ), +_A( ACT_SIGNAL3 ), +_A( ACT_TWITCH ), +_A( ACT_COWER ), +_A( ACT_SMALL_FLINCH ), +_A( ACT_BIG_FLINCH ), +_A( ACT_RANGE_ATTACK1 ), +_A( ACT_RANGE_ATTACK2 ), +_A( ACT_MELEE_ATTACK1 ), +_A( ACT_MELEE_ATTACK2 ), +_A( ACT_RELOAD ), +_A( ACT_ARM ), +_A( ACT_DISARM ), +_A( ACT_EAT ), +_A( ACT_DIESIMPLE ), +_A( ACT_DIEBACKWARD ), +_A( ACT_DIEFORWARD ), +_A( ACT_DIEVIOLENT ), +_A( ACT_BARNACLE_HIT ), +_A( ACT_BARNACLE_PULL ), +_A( ACT_BARNACLE_CHOMP ), +_A( ACT_BARNACLE_CHEW ), +_A( ACT_SLEEP ), +_A( ACT_INSPECT_FLOOR ), +_A( ACT_INSPECT_WALL ), +_A( ACT_IDLE_ANGRY ), +_A( ACT_WALK_HURT ), +_A( ACT_RUN_HURT ), +_A( ACT_HOVER ), +_A( ACT_GLIDE ), +_A( ACT_FLY_LEFT ), +_A( ACT_FLY_RIGHT ), +_A( ACT_DETECT_SCENT ), +_A( ACT_SNIFF ), +_A( ACT_BITE ), +_A( ACT_THREAT_DISPLAY ), +_A( ACT_FEAR_DISPLAY ), +_A( ACT_EXCITED ), +_A( ACT_SPECIAL_ATTACK1 ), +_A( ACT_SPECIAL_ATTACK2 ), +_A( ACT_COMBAT_IDLE ), +_A( ACT_WALK_SCARED ), +_A( ACT_RUN_SCARED ), +_A( ACT_VICTORY_DANCE ), +_A( ACT_DIE_HEADSHOT ), +_A( ACT_DIE_CHESTSHOT ), +_A( ACT_DIE_GUTSHOT ), +_A( ACT_DIE_BACKSHOT ), +_A( ACT_FLINCH_HEAD ), +_A( ACT_FLINCH_CHEST ), +_A( ACT_FLINCH_STOMACH ), +_A( ACT_FLINCH_LEFTARM ), +_A( ACT_FLINCH_RIGHTARM ), +_A( ACT_FLINCH_LEFTLEG ), +_A( ACT_FLINCH_RIGHTLEG ), +{ 0, NULL } +}; diff --git a/hlsdk/dmc/dlls/animating.cpp b/hlsdk/dmc/dlls/animating.cpp new file mode 100644 index 00000000..2da7536c --- /dev/null +++ b/hlsdk/dmc/dlls/animating.cpp @@ -0,0 +1,313 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== monsters.cpp ======================================================== + + Monster-related utility code + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "animation.h" +#include "saverestore.h" + +TYPEDESCRIPTION CBaseAnimating::m_SaveData[] = +{ + DEFINE_FIELD( CBaseMonster, m_flFrameRate, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flGroundSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flLastEventCheck, FIELD_TIME ), + DEFINE_FIELD( CBaseMonster, m_fSequenceFinished, FIELD_BOOLEAN ), + DEFINE_FIELD( CBaseMonster, m_fSequenceLoops, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CBaseAnimating, CBaseDelay ); + + +//========================================================= +// StudioFrameAdvance - advance the animation frame up to the current time +// if an flInterval is passed in, only advance animation that number of seconds +//========================================================= +float CBaseAnimating :: StudioFrameAdvance ( float flInterval ) +{ + if (flInterval == 0.0) + { + flInterval = (gpGlobals->time - pev->animtime); + if (flInterval <= 0.001) + { + pev->animtime = gpGlobals->time; + return 0.0; + } + } + if (! pev->animtime) + flInterval = 0.0; + + pev->frame += flInterval * m_flFrameRate * pev->framerate; + pev->animtime = gpGlobals->time; + + if (pev->frame < 0.0 || pev->frame >= 256.0) + { + if (m_fSequenceLoops) + pev->frame -= (int)(pev->frame / 256.0) * 256.0; + else + pev->frame = (pev->frame < 0.0) ? 0 : 255; + m_fSequenceFinished = TRUE; // just in case it wasn't caught in GetEvents + } + + return flInterval; +} + +//========================================================= +// LookupActivity +//========================================================= +int CBaseAnimating :: LookupActivity ( int activity ) +{ + ASSERT( activity != 0 ); + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::LookupActivity( pmodel, pev, activity ); +} + +//========================================================= +// LookupActivityHeaviest +// +// Get activity with highest 'weight' +// +//========================================================= +int CBaseAnimating :: LookupActivityHeaviest ( int activity ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::LookupActivityHeaviest( pmodel, pev, activity ); +} + +//========================================================= +//========================================================= +int CBaseAnimating :: LookupSequence ( const char *label ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::LookupSequence( pmodel, label ); +} + + +//========================================================= +//========================================================= +void CBaseAnimating :: ResetSequenceInfo ( ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + GetSequenceInfo( pmodel, pev, &m_flFrameRate, &m_flGroundSpeed ); + m_fSequenceLoops = ((GetSequenceFlags() & STUDIO_LOOPING) != 0); + pev->animtime = gpGlobals->time; + pev->framerate = 1.0; + m_fSequenceFinished = FALSE; + m_flLastEventCheck = gpGlobals->time; +} + + + +//========================================================= +//========================================================= +BOOL CBaseAnimating :: GetSequenceFlags( ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::GetSequenceFlags( pmodel, pev ); +} + +//========================================================= +// DispatchAnimEvents +//========================================================= +void CBaseAnimating :: DispatchAnimEvents ( float flInterval ) +{ + MonsterEvent_t event; + + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + if ( !pmodel ) + { + ALERT( at_aiconsole, "Gibbed monster is thinking!\n" ); + return; + } + + // FIXME: I have to do this or some events get missed, and this is probably causing the problem below + flInterval = 0.1; + + // FIX: this still sometimes hits events twice + float flStart = pev->frame + (m_flLastEventCheck - pev->animtime) * m_flFrameRate * pev->framerate; + float flEnd = pev->frame + flInterval * m_flFrameRate * pev->framerate; + m_flLastEventCheck = pev->animtime + flInterval; + + m_fSequenceFinished = FALSE; + if (flEnd >= 256 || flEnd <= 0.0) + m_fSequenceFinished = TRUE; + + int index = 0; + + while ( (index = GetAnimationEvent( pmodel, pev, &event, flStart, flEnd, index ) ) != 0 ) + { + HandleAnimEvent( &event ); + } +} + + +//========================================================= +//========================================================= +float CBaseAnimating :: SetBoneController ( int iController, float flValue ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return SetController( pmodel, pev, iController, flValue ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: InitBoneControllers ( void ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + SetController( pmodel, pev, 0, 0.0 ); + SetController( pmodel, pev, 1, 0.0 ); + SetController( pmodel, pev, 2, 0.0 ); + SetController( pmodel, pev, 3, 0.0 ); +} + +//========================================================= +//========================================================= +float CBaseAnimating :: SetBlending ( int iBlender, float flValue ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::SetBlending( pmodel, pev, iBlender, flValue ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: GetBonePosition ( int iBone, Vector &origin, Vector &angles ) +{ + GET_BONE_POSITION( ENT(pev), iBone, origin, angles ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: GetAttachment ( int iAttachment, Vector &origin, Vector &angles ) +{ + GET_ATTACHMENT( ENT(pev), iAttachment, origin, angles ); +} + +//========================================================= +//========================================================= +int CBaseAnimating :: FindTransition( int iEndingSequence, int iGoalSequence, int *piDir ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + if (piDir == NULL) + { + int iDir; + int sequence = ::FindTransition( pmodel, iEndingSequence, iGoalSequence, &iDir ); + if (iDir != 1) + return -1; + else + return sequence; + } + + return ::FindTransition( pmodel, iEndingSequence, iGoalSequence, piDir ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: GetAutomovement( Vector &origin, Vector &angles, float flInterval ) +{ + +} + +void CBaseAnimating :: SetBodygroup( int iGroup, int iValue ) +{ + ::SetBodygroup( GET_MODEL_PTR( ENT(pev) ), pev, iGroup, iValue ); +} + +int CBaseAnimating :: GetBodygroup( int iGroup ) +{ + return ::GetBodygroup( GET_MODEL_PTR( ENT(pev) ), pev, iGroup ); +} + + +int CBaseAnimating :: ExtractBbox( int sequence, float *mins, float *maxs ) +{ + return ::ExtractBbox( GET_MODEL_PTR( ENT(pev) ), sequence, mins, maxs ); +} + +//========================================================= +//========================================================= + +void CBaseAnimating :: SetSequenceBox( void ) +{ + Vector mins, maxs; + + // Get sequence bbox + if ( ExtractBbox( pev->sequence, mins, maxs ) ) + { + // expand box for rotation + // find min / max for rotations + float yaw = pev->angles.y * (M_PI / 180.0); + + Vector xvector, yvector; + xvector.x = cos(yaw); + xvector.y = sin(yaw); + yvector.x = -sin(yaw); + yvector.y = cos(yaw); + Vector bounds[2]; + + bounds[0] = mins; + bounds[1] = maxs; + + Vector rmin( 9999, 9999, 9999 ); + Vector rmax( -9999, -9999, -9999 ); + Vector base, transformed; + + for (int i = 0; i <= 1; i++ ) + { + base.x = bounds[i].x; + for ( int j = 0; j <= 1; j++ ) + { + base.y = bounds[j].y; + for ( int k = 0; k <= 1; k++ ) + { + base.z = bounds[k].z; + + // transform the point + transformed.x = xvector.x*base.x + yvector.x*base.y; + transformed.y = xvector.y*base.x + yvector.y*base.y; + transformed.z = base.z; + + for ( int l = 0; l < 3; l++ ) + { + if (transformed[l] < rmin[l]) + rmin[l] = transformed[l]; + if (transformed[l] > rmax[l]) + rmax[l] = transformed[l]; + } + } + } + } + rmin.z = 0; + rmax.z = rmin.z + 1; + UTIL_SetSize( pev, rmin, rmax ); + } +} + diff --git a/hlsdk/dmc/dlls/animation.cpp b/hlsdk/dmc/dlls/animation.cpp new file mode 100644 index 00000000..f55782fd --- /dev/null +++ b/hlsdk/dmc/dlls/animation.cpp @@ -0,0 +1,535 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include +#include +#include + +#if defined _MSC_VER && _MSC_VER >= 1400 + #ifndef _CRT_SECURE_NO_DEPRECATE + #define _CRT_SECURE_NO_DEPRECATE + #endif + + #pragma warning(disable: 4996) // deprecated functions +#endif + +typedef bool BOOL; + +// hack into header files that we can ship +typedef int qboolean; +typedef unsigned char byte; +#include "../utils/common/mathlib.h" +#include "const.h" + +#include "progdefs.h" +#include "edict.h" +#include "eiface.h" + +#include "studio.h" + +#include "../engine/studio.h" + +#ifndef ACTIVITY_H +#include "activity.h" +#endif + +#include "activitymap.h" + +#ifndef ANIMATION_H +#include "animation.h" +#endif + +#ifndef SCRIPTEVENT_H +#include "scriptevent.h" +#endif + +#ifndef ENGINECALLBACK_H +#include "enginecallback.h" +#endif + +extern globalvars_t *gpGlobals; + +#ifdef _MSC_VER +#pragma warning( disable : 4244 ) +#endif + + +int ExtractBbox( void *pmodel, int sequence, float *mins, float *maxs ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + mins[0] = pseqdesc[ sequence ].bbmin[0]; + mins[1] = pseqdesc[ sequence ].bbmin[1]; + mins[2] = pseqdesc[ sequence ].bbmin[2]; + + maxs[0] = pseqdesc[ sequence ].bbmax[0]; + maxs[1] = pseqdesc[ sequence ].bbmax[1]; + maxs[2] = pseqdesc[ sequence ].bbmax[2]; + + return 1; +} + + +int LookupActivity( void *pmodel, entvars_t *pev, int activity ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + int weighttotal = 0; + int seq = ACTIVITY_NOT_AVAILABLE; + for (int i = 0; i < pstudiohdr->numseq; i++) + { + if (pseqdesc[i].activity == activity) + { + weighttotal += pseqdesc[i].actweight; + if (!weighttotal || RANDOM_LONG(0,weighttotal-1) < pseqdesc[i].actweight) + seq = i; + } + } + + return seq; +} + + +int LookupActivityHeaviest( void *pmodel, entvars_t *pev, int activity ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr ) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + int weight = 0; + int seq = ACTIVITY_NOT_AVAILABLE; + for (int i = 0; i < pstudiohdr->numseq; i++) + { + if (pseqdesc[i].activity == activity) + { + if ( pseqdesc[i].actweight > weight ) + { + weight = pseqdesc[i].actweight; + seq = i; + } + } + } + + return seq; +} + +void GetEyePosition ( void *pmodel, float *vecEyePosition ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + + if ( !pstudiohdr ) + { + ALERT ( at_console, "GetEyePosition() Can't get pstudiohdr ptr!\n" ); + return; + } + + VectorCopy ( pstudiohdr->eyeposition, vecEyePosition ); +} + +int LookupSequence( void *pmodel, const char *label ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + for (int i = 0; i < pstudiohdr->numseq; i++) + { + if (stricmp( pseqdesc[i].label, label ) == 0) + return i; + } + + return -1; +} + + +int IsSoundEvent( int eventNumber ) +{ + if ( eventNumber == SCRIPT_EVENT_SOUND || eventNumber == SCRIPT_EVENT_SOUND_VOICE ) + return 1; + return 0; +} + + +void SequencePrecache( void *pmodel, const char *pSequenceName ) +{ + int index = LookupSequence( pmodel, pSequenceName ); + if ( index >= 0 ) + { + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || index >= pstudiohdr->numseq ) + return; + + mstudioseqdesc_t *pseqdesc; + mstudioevent_t *pevent; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + index; + pevent = (mstudioevent_t *)((byte *)pstudiohdr + pseqdesc->eventindex); + + for (int i = 0; i < pseqdesc->numevents; i++) + { + // Don't send client-side events to the server AI + if ( pevent[i].event >= EVENT_CLIENT ) + continue; + + // UNDONE: Add a callback to check to see if a sound is precached yet and don't allocate a copy + // of it's name if it is. + if ( IsSoundEvent( pevent[i].event ) ) + { + if ( !strlen(pevent[i].options) ) + { + ALERT( at_error, "Bad sound event %d in sequence %s :: %s (sound is \"%s\")\n", pevent[i].event, pstudiohdr->name, pSequenceName, pevent[i].options ); + } + + PRECACHE_SOUND( (char *)(gpGlobals->pStringBase + ALLOC_STRING(pevent[i].options) ) ); + } + } + } +} + + + +void GetSequenceInfo( void *pmodel, entvars_t *pev, float *pflFrameRate, float *pflGroundSpeed ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return; + + mstudioseqdesc_t *pseqdesc; + + if (pev->sequence >= pstudiohdr->numseq) + { + *pflFrameRate = 0.0; + *pflGroundSpeed = 0.0; + return; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + if (pseqdesc->numframes > 1) + { + *pflFrameRate = 256 * pseqdesc->fps / (pseqdesc->numframes - 1); + *pflGroundSpeed = sqrt( pseqdesc->linearmovement[0]*pseqdesc->linearmovement[0]+ pseqdesc->linearmovement[1]*pseqdesc->linearmovement[1]+ pseqdesc->linearmovement[2]*pseqdesc->linearmovement[2] ); + *pflGroundSpeed = *pflGroundSpeed * pseqdesc->fps / (pseqdesc->numframes - 1); + } + else + { + *pflFrameRate = 256.0; + *pflGroundSpeed = 0.0; + } +} + + +int GetSequenceFlags( void *pmodel, entvars_t *pev ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || pev->sequence >= pstudiohdr->numseq ) + return 0; + + mstudioseqdesc_t *pseqdesc; + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + return pseqdesc->flags; +} + + +int GetAnimationEvent( void *pmodel, entvars_t *pev, MonsterEvent_t *pMonsterEvent, float flStart, float flEnd, int index ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || pev->sequence >= pstudiohdr->numseq || !pMonsterEvent ) + return 0; + + mstudioseqdesc_t *pseqdesc; + mstudioevent_t *pevent; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + pevent = (mstudioevent_t *)((byte *)pstudiohdr + pseqdesc->eventindex); + + if (pseqdesc->numevents == 0 || index > pseqdesc->numevents ) + return 0; + + if (pseqdesc->numframes > 1) + { + flStart *= (pseqdesc->numframes - 1) / 256.0; + flEnd *= (pseqdesc->numframes - 1) / 256.0; + } + else + { + flStart = 0; + flEnd = 1.0; + } + + for (; index < pseqdesc->numevents; index++) + { + // Don't send client-side events to the server AI + if ( pevent[index].event >= EVENT_CLIENT ) + continue; + + if ( (pevent[index].frame >= flStart && pevent[index].frame < flEnd) || + ((pseqdesc->flags & STUDIO_LOOPING) && flEnd >= pseqdesc->numframes - 1 && pevent[index].frame < flEnd - pseqdesc->numframes + 1) ) + { + pMonsterEvent->event = pevent[index].event; + pMonsterEvent->options = pevent[index].options; + return index + 1; + } + } + return 0; +} + +float SetController( void *pmodel, entvars_t *pev, int iController, float flValue ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return flValue; + + mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *)((byte *)pstudiohdr + pstudiohdr->bonecontrollerindex); + + int i = 0; + + // find first controller that matches the index + for (i = 0; i < pstudiohdr->numbonecontrollers; i++, pbonecontroller++) + { + if (pbonecontroller->index == iController) + break; + } + if (i >= pstudiohdr->numbonecontrollers) + return flValue; + + // wrap 0..360 if it's a rotational controller + + if (pbonecontroller->type & (STUDIO_XR | STUDIO_YR | STUDIO_ZR)) + { + // ugly hack, invert value if end < start + if (pbonecontroller->end < pbonecontroller->start) + flValue = -flValue; + + // does the controller not wrap? + if (pbonecontroller->start + 359.0 >= pbonecontroller->end) + { + if (flValue > ((pbonecontroller->start + pbonecontroller->end) / 2.0) + 180) + flValue = flValue - 360; + if (flValue < ((pbonecontroller->start + pbonecontroller->end) / 2.0) - 180) + flValue = flValue + 360; + } + else + { + if (flValue > 360) + flValue = flValue - (int)(flValue / 360.0) * 360.0; + else if (flValue < 0) + flValue = flValue + (int)((flValue / -360.0) + 1) * 360.0; + } + } + + int setting = static_cast(255 * (flValue - pbonecontroller->start) / (pbonecontroller->end - pbonecontroller->start)); + + if (setting < 0) setting = 0; + if (setting > 255) setting = 255; + pev->controller[iController] = setting; + + return setting * (1.0 / 255.0) * (pbonecontroller->end - pbonecontroller->start) + pbonecontroller->start; +} + + +float SetBlending( void *pmodel, entvars_t *pev, int iBlender, float flValue ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return flValue; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + if (pseqdesc->blendtype[iBlender] == 0) + return flValue; + + if (pseqdesc->blendtype[iBlender] & (STUDIO_XR | STUDIO_YR | STUDIO_ZR)) + { + // ugly hack, invert value if end < start + if (pseqdesc->blendend[iBlender] < pseqdesc->blendstart[iBlender]) + flValue = -flValue; + + // does the controller not wrap? + if (pseqdesc->blendstart[iBlender] + 359.0 >= pseqdesc->blendend[iBlender]) + { + if (flValue > ((pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender]) / 2.0) + 180) + flValue = flValue - 360; + if (flValue < ((pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender]) / 2.0) - 180) + flValue = flValue + 360; + } + } + + int setting = static_cast(255 * (flValue - pseqdesc->blendstart[iBlender]) / (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender])); + + if (setting < 0) setting = 0; + if (setting > 255) setting = 255; + + pev->blending[iBlender] = setting; + + return setting * (1.0 / 255.0) * (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender]) + pseqdesc->blendstart[iBlender]; +} + + + + +int FindTransition( void *pmodel, int iEndingAnim, int iGoalAnim, int *piDir ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return iGoalAnim; + + mstudioseqdesc_t *pseqdesc; + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + // bail if we're going to or from a node 0 + if (pseqdesc[iEndingAnim].entrynode == 0 || pseqdesc[iGoalAnim].entrynode == 0) + { + return iGoalAnim; + } + + int iEndNode; + + // ALERT( at_console, "from %d to %d: ", pEndNode->iEndNode, pGoalNode->iStartNode ); + + if (*piDir > 0) + { + iEndNode = pseqdesc[iEndingAnim].exitnode; + } + else + { + iEndNode = pseqdesc[iEndingAnim].entrynode; + } + + if (iEndNode == pseqdesc[iGoalAnim].entrynode) + { + *piDir = 1; + return iGoalAnim; + } + + byte *pTransition = ((byte *)pstudiohdr + pstudiohdr->transitionindex); + + int iInternNode = pTransition[(iEndNode-1)*pstudiohdr->numtransitions + (pseqdesc[iGoalAnim].entrynode-1)]; + + if (iInternNode == 0) + return iGoalAnim; + + int i; + + // look for someone going + for (i = 0; i < pstudiohdr->numseq; i++) + { + if (pseqdesc[i].entrynode == iEndNode && pseqdesc[i].exitnode == iInternNode) + { + *piDir = 1; + return i; + } + if (pseqdesc[i].nodeflags) + { + if (pseqdesc[i].exitnode == iEndNode && pseqdesc[i].entrynode == iInternNode) + { + *piDir = -1; + return i; + } + } + } + + ALERT( at_console, "error in transition graph" ); + return iGoalAnim; +} + +void SetBodygroup( void *pmodel, entvars_t *pev, int iGroup, int iValue ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return; + + if (iGroup > pstudiohdr->numbodyparts) + return; + + mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex) + iGroup; + + if (iValue >= pbodypart->nummodels) + return; + + int iCurrent = (pev->body / pbodypart->base) % pbodypart->nummodels; + + pev->body = (pev->body - (iCurrent * pbodypart->base) + (iValue * pbodypart->base)); +} + + +int GetBodygroup( void *pmodel, entvars_t *pev, int iGroup ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + if (iGroup > pstudiohdr->numbodyparts) + return 0; + + mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex) + iGroup; + + if (pbodypart->nummodels <= 1) + return 0; + + int iCurrent = (pev->body / pbodypart->base) % pbodypart->nummodels; + + return iCurrent; +} diff --git a/hlsdk/dmc/dlls/animation.h b/hlsdk/dmc/dlls/animation.h new file mode 100644 index 00000000..174bd712 --- /dev/null +++ b/hlsdk/dmc/dlls/animation.h @@ -0,0 +1,47 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef ANIMATION_H +#define ANIMATION_H + +#define ACTIVITY_NOT_AVAILABLE -1 + +#ifndef MONSTEREVENT_H +#include "monsterevent.h" +#endif + +extern int IsSoundEvent( int eventNumber ); + +int LookupActivity( void *pmodel, entvars_t *pev, int activity ); +int LookupActivityHeaviest( void *pmodel, entvars_t *pev, int activity ); +int LookupSequence( void *pmodel, const char *label ); +void GetSequenceInfo( void *pmodel, entvars_t *pev, float *pflFrameRate, float *pflGroundSpeed ); +int GetSequenceFlags( void *pmodel, entvars_t *pev ); +int LookupAnimationEvents( void *pmodel, entvars_t *pev, float flStart, float flEnd ); +float SetController( void *pmodel, entvars_t *pev, int iController, float flValue ); +float SetBlending( void *pmodel, entvars_t *pev, int iBlender, float flValue ); +void GetEyePosition( void *pmodel, float *vecEyePosition ); +void SequencePrecache( void *pmodel, const char *pSequenceName ); +int FindTransition( void *pmodel, int iEndingAnim, int iGoalAnim, int *piDir ); +void SetBodygroup( void *pmodel, entvars_t *pev, int iGroup, int iValue ); +int GetBodygroup( void *pmodel, entvars_t *pev, int iGroup ); + +int GetAnimationEvent( void *pmodel, entvars_t *pev, MonsterEvent_t *pMonsterEvent, float flStart, float flEnd, int index ); +int ExtractBbox( void *pmodel, int sequence, float *mins, float *maxs ); + +// From /engine/studio.h +#define STUDIO_LOOPING 0x0001 + + +#endif //ANIMATION_H diff --git a/hlsdk/dmc/dlls/basemonster.h b/hlsdk/dmc/dlls/basemonster.h new file mode 100644 index 00000000..190965d9 --- /dev/null +++ b/hlsdk/dmc/dlls/basemonster.h @@ -0,0 +1,339 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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: + 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 ); + +// 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 + virtual void RunAI ( void );// core ai function! + 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 ); + 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 ); + + virtual Schedule_t *ScheduleFromName( const char *pName ); + static Schedule_t *m_scheduleList[]; + + void MaintainSchedule ( void ); + virtual void StartTask ( Task_t *pTask ); + virtual void RunTask ( Task_t *pTask ); + virtual Schedule_t *GetScheduleOfType( int Type ); + virtual Schedule_t *GetSchedule( void ); + 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 MONSTERSTATE GetIdealState ( void ); + virtual void SetActivity ( Activity NewActivity ); + void SetSequenceByName ( char *szSequence ); + void SetState ( MONSTERSTATE State ); + 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 ); + virtual void BarnacleVictimBitten( entvars_t *pevBarnacle ); + virtual void BarnacleVictimReleased( 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 ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + + // combat functions + float UpdateTarget ( entvars_t *pevTarget ); + virtual Activity GetDeathActivity ( void ); + Activity GetSmallFlinchActivity( void ); + virtual void Killed( entvars_t *pevAttacker, int iGib ); + virtual void GibMonster( void ); + BOOL ShouldGibMonster( int iGib ); + void CallGibMonster( void ); + 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 ); + + 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; } + + 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; } + + BOOL ExitScriptedSequence( ); + BOOL CineCleanup( ); + + CBaseEntity* DropItem ( char *pszItemName, const Vector &vecPos, const Vector &vecAng );// drop an item. +}; + + + +#endif // BASEMONSTER_H diff --git a/hlsdk/dmc/dlls/bmodels.cpp b/hlsdk/dmc/dlls/bmodels.cpp new file mode 100644 index 00000000..39c60a70 --- /dev/null +++ b/hlsdk/dmc/dlls/bmodels.cpp @@ -0,0 +1,958 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== bmodels.cpp ======================================================== + + spawn, think, and use functions for entities that use brush models + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "doors.h" + +extern DLL_GLOBAL Vector g_vecAttackDir; + +#define SF_BRUSH_ACCDCC 16// brush should accelerate and decelerate when toggled +#define SF_BRUSH_HURT 32// rotating brush that inflicts pain based on rotation speed +#define SF_ROTATING_NOT_SOLID 64 // some special rotating objects are not solid. + +// covering cheesy noise1, noise2, & noise3 fields so they make more sense (for rotating fans) +#define noiseStart noise1 +#define noiseStop noise2 +#define noiseRunning noise3 + +#define SF_PENDULUM_SWING 2 // spawnflag that makes a pendulum a rope swing. +// +// BModelOrigin - calculates origin of a bmodel from absmin/size because all bmodel origins are 0 0 0 +// +Vector VecBModelOrigin( entvars_t* pevBModel ) +{ + return pevBModel->absmin + ( pevBModel->size * 0.5 ); +} + +// =================== FUNC_WALL ============================================== + +/*QUAKED func_wall (0 .5 .8) ? +This is just a solid wall if not inhibited +*/ +class CFuncWall : public CBaseEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + // Bmodels don't go across transitions + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +LINK_ENTITY_TO_CLASS( func_wall, CFuncWall ); + +void CFuncWall :: Spawn( void ) +{ + pev->angles = g_vecZero; + pev->movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + pev->solid = SOLID_BSP; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + // If it can't move/go away, it's really part of the world + pev->flags |= FL_WORLDBRUSH; +} + + +void CFuncWall :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( ShouldToggle( useType, (int)(pev->frame)) ) + pev->frame = 1 - pev->frame; +} + + +#define SF_WALL_START_OFF 0x0001 + +class CFuncWallToggle : public CFuncWall +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void TurnOff( void ); + void TurnOn( void ); + BOOL IsOn( void ); +}; + +LINK_ENTITY_TO_CLASS( func_wall_toggle, CFuncWallToggle ); + +void CFuncWallToggle :: Spawn( void ) +{ + CFuncWall::Spawn(); + if ( pev->spawnflags & SF_WALL_START_OFF ) + TurnOff(); +} + + +void CFuncWallToggle :: TurnOff( void ) +{ + pev->solid = SOLID_NOT; + pev->effects |= EF_NODRAW; + UTIL_SetOrigin( pev, pev->origin ); +} + + +void CFuncWallToggle :: TurnOn( void ) +{ + pev->solid = SOLID_BSP; + pev->effects &= ~EF_NODRAW; + UTIL_SetOrigin( pev, pev->origin ); +} + + +BOOL CFuncWallToggle :: IsOn( void ) +{ + if ( pev->solid == SOLID_NOT ) + return FALSE; + return TRUE; +} + + +void CFuncWallToggle :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int status = IsOn(); + + if ( ShouldToggle( useType, status ) ) + { + if ( status ) + TurnOff(); + else + TurnOn(); + } +} + + +#define SF_CONVEYOR_VISUAL 0x0001 +#define SF_CONVEYOR_NOTSOLID 0x0002 + +class CFuncConveyor : public CFuncWall +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void UpdateSpeed( float speed ); +}; + +LINK_ENTITY_TO_CLASS( func_conveyor, CFuncConveyor ); +void CFuncConveyor :: Spawn( void ) +{ + SetMovedir( pev ); + CFuncWall::Spawn(); + + if ( !(pev->spawnflags & SF_CONVEYOR_VISUAL) ) + SetBits( pev->flags, FL_CONVEYOR ); + + // HACKHACK - This is to allow for some special effects + if ( pev->spawnflags & SF_CONVEYOR_NOTSOLID ) + { + pev->solid = SOLID_NOT; + pev->skin = 0; // Don't want the engine thinking we've got special contents on this brush + } + + if ( pev->speed == 0 ) + pev->speed = 100; + + UpdateSpeed( pev->speed ); +} + + +// HACKHACK -- This is ugly, but encode the speed in the rendercolor to avoid adding more data to the network stream +void CFuncConveyor :: UpdateSpeed( float speed ) +{ + // Encode it as an integer with 4 fractional bits + int speedCode = (int)(fabs(speed) * 16.0); + + if ( speed < 0 ) + pev->rendercolor.x = 1; + else + pev->rendercolor.x = 0; + + pev->rendercolor.y = (speedCode >> 8); + pev->rendercolor.z = (speedCode & 0xFF); +} + + +void CFuncConveyor :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + pev->speed = -pev->speed; + UpdateSpeed( pev->speed ); +} + + + +// =================== FUNC_ILLUSIONARY ============================================== + + +/*QUAKED func_illusionary (0 .5 .8) ? +A simple entity that looks solid but lets you walk through it. +*/ +class CFuncIllusionary : public CBaseToggle +{ +public: + void Spawn( void ); + void EXPORT SloshTouch( CBaseEntity *pOther ); + void KeyValue( KeyValueData *pkvd ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +LINK_ENTITY_TO_CLASS( func_illusionary, CFuncIllusionary ); + +void CFuncIllusionary :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "skin"))//skin is used for content type + { + pev->skin = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CFuncIllusionary :: Spawn( void ) +{ + pev->angles = g_vecZero; + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT;// always solid_not + SET_MODEL( ENT(pev), STRING(pev->model) ); + + // I'd rather eat the network bandwidth of this than figure out how to save/restore + // these entities after they have been moved to the client, or respawn them ala Quake + // Perhaps we can do this in deathmatch only. + // MAKE_STATIC(ENT(pev)); +} + + +// ------------------------------------------------------------------------------- +// +// Monster only clip brush +// +// This brush will be solid for any entity who has the FL_MONSTERCLIP flag set +// in pev->flags +// +// otherwise it will be invisible and not solid. This can be used to keep +// specific monsters out of certain areas +// +// ------------------------------------------------------------------------------- +class CFuncMonsterClip : public CFuncWall +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) {} // Clear out func_wall's use function +}; + +LINK_ENTITY_TO_CLASS( func_monsterclip, CFuncMonsterClip ); + +void CFuncMonsterClip::Spawn( void ) +{ + CFuncWall::Spawn(); + if ( CVAR_GET_FLOAT("showtriggers") == 0 ) + pev->effects = EF_NODRAW; + pev->flags |= FL_MONSTERCLIP; +} + + +// =================== FUNC_ROTATING ============================================== +class CFuncRotating : public CBaseEntity +{ +public: + // basic functions + void Spawn( void ); + void Precache( void ); + void EXPORT SpinUp ( void ); + void EXPORT SpinDown ( void ); + void KeyValue( KeyValueData* pkvd); + void EXPORT HurtTouch ( CBaseEntity *pOther ); + void EXPORT RotatingUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Rotate( void ); + void RampPitchVol (int fUp ); + void Blocked( CBaseEntity *pOther ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flFanFriction; + float m_flAttenuation; + float m_flVolume; + float m_pitch; + int m_sounds; +}; + +TYPEDESCRIPTION CFuncRotating::m_SaveData[] = +{ + DEFINE_FIELD( CFuncRotating, m_flFanFriction, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_flAttenuation, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_flVolume, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_pitch, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_sounds, FIELD_INTEGER ) +}; + +IMPLEMENT_SAVERESTORE( CFuncRotating, CBaseEntity ); + + +LINK_ENTITY_TO_CLASS( func_rotating, CFuncRotating ); + +void CFuncRotating :: KeyValue( KeyValueData* pkvd) +{ + if (FStrEq(pkvd->szKeyName, "fanfriction")) + { + m_flFanFriction = atof(pkvd->szValue)/100; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "Volume")) + { + m_flVolume = atof(pkvd->szValue)/10.0; + + if (m_flVolume > 1.0) + m_flVolume = 1.0; + if (m_flVolume < 0.0) + m_flVolume = 0.0; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spawnorigin")) + { + Vector tmp; + UTIL_StringToVector( (float *)tmp, pkvd->szValue ); + if ( tmp != g_vecZero ) + pev->origin = tmp; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +/*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS +You need to have an origin brush as part of this entity. The +center of that brush will be +the point around which it is rotated. It will rotate around the Z +axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"speed" determines how fast it moves; default value is 100. +"dmg" damage to inflict when blocked (2 default) + +REVERSE will cause the it to rotate in the opposite direction. +*/ + + +void CFuncRotating :: Spawn( ) +{ + // set final pitch. Must not be PITCH_NORM, since we + // plan on pitch shifting later. + + m_pitch = PITCH_NORM - 1; + + // maintain compatibility with previous maps + if (m_flVolume == 0.0) + m_flVolume = 1.0; + + // if the designer didn't set a sound attenuation, default to one. + m_flAttenuation = ATTN_NORM; + + if ( FBitSet ( pev->spawnflags, SF_BRUSH_ROTATE_SMALLRADIUS) ) + { + m_flAttenuation = ATTN_IDLE; + } + else if ( FBitSet ( pev->spawnflags, SF_BRUSH_ROTATE_MEDIUMRADIUS) ) + { + m_flAttenuation = ATTN_STATIC; + } + else if ( FBitSet ( pev->spawnflags, SF_BRUSH_ROTATE_LARGERADIUS) ) + { + m_flAttenuation = ATTN_NORM; + } + + // prevent divide by zero if level designer forgets friction! + if ( m_flFanFriction == 0 ) + { + m_flFanFriction = 1; + } + + if ( FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_Z_AXIS) ) + pev->movedir = Vector(0,0,1); + else if ( FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_X_AXIS) ) + pev->movedir = Vector(1,0,0); + else + pev->movedir = Vector(0,1,0); // y-axis + + // check for reverse rotation + if ( FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_BACKWARDS) ) + pev->movedir = pev->movedir * -1; + + // some rotating objects like fake volumetric lights will not be solid. + if ( FBitSet(pev->spawnflags, SF_ROTATING_NOT_SOLID) ) + { + pev->solid = SOLID_NOT; + pev->skin = CONTENTS_EMPTY; + pev->movetype = MOVETYPE_PUSH; + } + else + { + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + } + + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + SetUse( &CFuncRotating::RotatingUse ); + // did level designer forget to assign speed? + if (pev->speed <= 0) + pev->speed = 0; + + // Removed this per level designers request. -- JAY + // if (pev->dmg == 0) + // pev->dmg = 2; + + // instant-use brush? + if ( FBitSet( pev->spawnflags, SF_BRUSH_ROTATE_INSTANT) ) + { + SetThink( &CBaseEntity::SUB_CallUseToggle ); + pev->nextthink = pev->ltime + 1.5; // leave a magic delay for client to start up + } + // can this brush inflict pain? + if ( FBitSet (pev->spawnflags, SF_BRUSH_HURT) ) + { + SetTouch( &CFuncRotating::HurtTouch ); + } + + Precache( ); +} + + +void CFuncRotating :: Precache( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + + // set up fan sounds + + if (!FStringNull( pev->message ) && strlen( szSoundFile ) > 0) + { + // if a path is set for a wave, use it + + PRECACHE_SOUND(szSoundFile); + + pev->noiseRunning = ALLOC_STRING(szSoundFile); + } else + { + // otherwise use preset sound + switch (m_sounds) + { + case 1: + PRECACHE_SOUND ("fans/fan1.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan1.wav"); + break; + case 2: + PRECACHE_SOUND ("fans/fan2.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan2.wav"); + break; + case 3: + PRECACHE_SOUND ("fans/fan3.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan3.wav"); + break; + case 4: + PRECACHE_SOUND ("fans/fan4.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan4.wav"); + break; + case 5: + PRECACHE_SOUND ("fans/fan5.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan5.wav"); + break; + + case 0: + default: + if (!FStringNull( pev->message ) && strlen( szSoundFile ) > 0) + { + PRECACHE_SOUND(szSoundFile); + + pev->noiseRunning = ALLOC_STRING(szSoundFile); + break; + } else + { + pev->noiseRunning = ALLOC_STRING("common/null.wav"); + break; + } + } + } + + if (pev->avelocity != g_vecZero ) + { + // if fan was spinning, and we went through transition or save/restore, + // make sure we restart the sound. 1.5 sec delay is magic number. KDB + + SetThink ( &CFuncRotating::SpinUp ); + pev->nextthink = pev->ltime + 1.5; + } +} + + + +// +// Touch - will hurt others based on how fast the brush is spinning +// +void CFuncRotating :: HurtTouch ( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + // we can't hurt this thing, so we're not concerned with it + if ( !pevOther->takedamage ) + return; + + // calculate damage based on rotation speed + pev->dmg = pev->avelocity.Length() / 10; + + pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH); + + pevOther->velocity = (pevOther->origin - VecBModelOrigin(pev) ).Normalize() * pev->dmg; +} + +// +// RampPitchVol - ramp pitch and volume up to final values, based on difference +// between how fast we're going vs how fast we plan to go +// +#define FANPITCHMIN 30 +#define FANPITCHMAX 100 + +void CFuncRotating :: RampPitchVol (int fUp) +{ + + Vector vecAVel = pev->avelocity; + vec_t vecCur; + vec_t vecFinal; + float fpct; + float fvol; + float fpitch; + int pitch; + + // get current angular velocity + + vecCur = abs(vecAVel.x != 0 ? static_cast(vecAVel.x) : static_cast(vecAVel.y != 0 ? vecAVel.y : vecAVel.z)); + + // get target angular velocity + + vecFinal = (pev->movedir.x != 0 ? pev->movedir.x : (pev->movedir.y != 0 ? pev->movedir.y : pev->movedir.z)); + vecFinal *= pev->speed; + vecFinal = abs(static_cast(vecFinal)); + + // calc volume and pitch as % of final vol and pitch + + fpct = vecCur / vecFinal; +// if (fUp) +// fvol = m_flVolume * (0.5 + fpct/2.0); // spinup volume ramps up from 50% max vol +// else + fvol = m_flVolume * fpct; // slowdown volume ramps down to 0 + + fpitch = FANPITCHMIN + (FANPITCHMAX - FANPITCHMIN) * fpct; + + pitch = (int) fpitch; + if (pitch == PITCH_NORM) + pitch = PITCH_NORM-1; + + // change the fan's vol and pitch + + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + fvol, m_flAttenuation, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch); + +} + +// +// SpinUp - accelerates a non-moving func_rotating up to it's speed +// +void CFuncRotating :: SpinUp( void ) +{ + Vector vecAVel;//rotational velocity + + pev->nextthink = pev->ltime + 0.1; + pev->avelocity = pev->avelocity + ( pev->movedir * ( pev->speed * m_flFanFriction ) ); + + vecAVel = pev->avelocity;// cache entity's rotational velocity + + // if we've met or exceeded target speed, set target speed and stop thinking + if ( abs(static_cast(vecAVel.x)) >= abs(static_cast(pev->movedir.x * pev->speed)) && + abs(static_cast(vecAVel.y)) >= abs(static_cast(pev->movedir.y * pev->speed)) && + abs(static_cast(vecAVel.z)) >= abs(static_cast(pev->movedir.z * pev->speed)) ) + { + pev->avelocity = pev->movedir * pev->speed;// set speed in case we overshot + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + m_flVolume, m_flAttenuation, SND_CHANGE_PITCH | SND_CHANGE_VOL, FANPITCHMAX); + + SetThink( &CFuncRotating::Rotate ); + Rotate(); + } + else + { + RampPitchVol(TRUE); + } +} + +// +// SpinDown - decelerates a moving func_rotating to a standstill. +// +void CFuncRotating :: SpinDown( void ) +{ + Vector vecAVel;//rotational velocity + vec_t vecdir; + + pev->nextthink = pev->ltime + 0.1; + + pev->avelocity = pev->avelocity - ( pev->movedir * ( pev->speed * m_flFanFriction ) );//spin down slower than spinup + + vecAVel = pev->avelocity;// cache entity's rotational velocity + + if (pev->movedir.x != 0) + vecdir = pev->movedir.x; + else if (pev->movedir.y != 0) + vecdir = pev->movedir.y; + else + vecdir = pev->movedir.z; + + // if we've met or exceeded target speed, set target speed and stop thinking + // (note: must check for movedir > 0 or < 0) + if (((vecdir > 0) && (vecAVel.x <= 0 && vecAVel.y <= 0 && vecAVel.z <= 0)) || + ((vecdir < 0) && (vecAVel.x >= 0 && vecAVel.y >= 0 && vecAVel.z >= 0))) + { + pev->avelocity = g_vecZero;// set speed in case we overshot + + // stop sound, we're done + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning /* Stop */), + 0, 0, SND_STOP, static_cast(m_pitch)); + + SetThink( &CFuncRotating::Rotate ); + Rotate(); + } + else + { + RampPitchVol(FALSE); + } +} + +void CFuncRotating :: Rotate( void ) +{ + pev->nextthink = pev->ltime + 10; +} + +//========================================================= +// Rotating Use - when a rotating brush is triggered +//========================================================= +void CFuncRotating :: RotatingUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // is this a brush that should accelerate and decelerate when turned on/off (fan)? + if ( FBitSet ( pev->spawnflags, SF_BRUSH_ACCDCC ) ) + { + // fan is spinning, so stop it. + if ( pev->avelocity != g_vecZero ) + { + SetThink ( &CFuncRotating::SpinDown ); + //EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, (char *)STRING(pev->noiseStop), + // m_flVolume, m_flAttenuation, 0, m_pitch); + + pev->nextthink = pev->ltime + 0.1; + } + else// fan is not moving, so start it + { + SetThink ( &CFuncRotating::SpinUp ); + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + 0.01, m_flAttenuation, 0, FANPITCHMIN); + + pev->nextthink = pev->ltime + 0.1; + } + } + else if ( !FBitSet ( pev->spawnflags, SF_BRUSH_ACCDCC ) )//this is a normal start/stop brush. + { + if ( pev->avelocity != g_vecZero ) + { + // play stopping sound here + SetThink ( &CFuncRotating::SpinDown ); + + // EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, (char *)STRING(pev->noiseStop), + // m_flVolume, m_flAttenuation, 0, m_pitch); + + pev->nextthink = pev->ltime + 0.1; + // pev->avelocity = g_vecZero; + } + else + { + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + m_flVolume, m_flAttenuation, 0, FANPITCHMAX); + pev->avelocity = pev->movedir * pev->speed; + + SetThink( &CFuncRotating::Rotate ); + Rotate(); + } + } +} + + +// +// RotatingBlocked - An entity has blocked the brush +// +void CFuncRotating :: Blocked( CBaseEntity *pOther ) + +{ + pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH); +} + + + + + + +//#endif + + +class CPendulum : public CBaseEntity +{ +public: + void Spawn ( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT Swing( void ); + void EXPORT PendulumUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Stop( void ); + void Touch( CBaseEntity *pOther ); + void EXPORT RopeTouch ( CBaseEntity *pOther );// this touch func makes the pendulum a rope + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + void Blocked( CBaseEntity *pOther ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_accel; // Acceleration + float m_distance; // + float m_time; + float m_damp; + float m_maxSpeed; + float m_dampSpeed; + vec3_t m_center; + vec3_t m_start; +}; + +LINK_ENTITY_TO_CLASS( func_pendulum, CPendulum ); + +TYPEDESCRIPTION CPendulum::m_SaveData[] = +{ + DEFINE_FIELD( CPendulum, m_accel, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_distance, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_time, FIELD_TIME ), + DEFINE_FIELD( CPendulum, m_damp, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_maxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_dampSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_center, FIELD_VECTOR ), + DEFINE_FIELD( CPendulum, m_start, FIELD_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CPendulum, CBaseEntity ); + + + +void CPendulum :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "distance")) + { + m_distance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damp")) + { + m_damp = atof(pkvd->szValue) * 0.001; + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +void CPendulum :: Spawn( void ) +{ + // set the axis of rotation + CBaseToggle :: AxisDir( pev ); + + if ( FBitSet (pev->spawnflags, SF_DOOR_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + if ( m_distance == 0 ) + return; + + if (pev->speed == 0) + pev->speed = 100; + + m_accel = (pev->speed * pev->speed) / (2 * fabs(m_distance)); // Calculate constant acceleration from speed and distance + m_maxSpeed = pev->speed; + m_start = pev->angles; + m_center = pev->angles + (m_distance * 0.5) * pev->movedir; + + if ( FBitSet( pev->spawnflags, SF_BRUSH_ROTATE_INSTANT) ) + { + SetThink( &CBaseEntity::SUB_CallUseToggle ); + pev->nextthink = gpGlobals->time + 0.1; + } + pev->speed = 0; + SetUse( &CPendulum::PendulumUse ); + + if ( FBitSet( pev->spawnflags, SF_PENDULUM_SWING ) ) + { + SetTouch ( &CPendulum::RopeTouch ); + } +} + + +void CPendulum :: PendulumUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->speed ) // Pendulum is moving, stop it and auto-return if necessary + { + if ( FBitSet( pev->spawnflags, SF_PENDULUM_AUTO_RETURN ) ) + { + float delta; + + delta = CBaseToggle :: AxisDelta( pev->spawnflags, pev->angles, m_start ); + + pev->avelocity = m_maxSpeed * pev->movedir; + pev->nextthink = pev->ltime + (delta / m_maxSpeed); + SetThink( &CPendulum::Stop ); + } + else + { + pev->speed = 0; // Dead stop + SetThink( NULL ); + pev->avelocity = g_vecZero; + } + } + else + { + pev->nextthink = pev->ltime + 0.1; // Start the pendulum moving + m_time = gpGlobals->time; // Save time to calculate dt + SetThink( &CPendulum::Swing ); + m_dampSpeed = m_maxSpeed; + } +} + + +void CPendulum :: Stop( void ) +{ + pev->angles = m_start; + pev->speed = 0; + SetThink( NULL ); + pev->avelocity = g_vecZero; +} + + +void CPendulum::Blocked( CBaseEntity *pOther ) +{ + m_time = gpGlobals->time; +} + + +void CPendulum :: Swing( void ) +{ + float delta, dt; + + delta = CBaseToggle :: AxisDelta( pev->spawnflags, pev->angles, m_center ); + dt = gpGlobals->time - m_time; // How much time has passed? + m_time = gpGlobals->time; // Remember the last time called + + if ( delta > 0 && m_accel > 0 ) + pev->speed -= m_accel * dt; // Integrate velocity + else + pev->speed += m_accel * dt; + + if ( pev->speed > m_maxSpeed ) + pev->speed = m_maxSpeed; + else if ( pev->speed < -m_maxSpeed ) + pev->speed = -m_maxSpeed; + // scale the destdelta vector by the time spent traveling to get velocity + pev->avelocity = pev->speed * pev->movedir; + + // Call this again + pev->nextthink = pev->ltime + 0.1; + + if ( m_damp ) + { + m_dampSpeed -= m_damp * m_dampSpeed * dt; + if ( m_dampSpeed < 30.0 ) + { + pev->angles = m_center; + pev->speed = 0; + SetThink( NULL ); + pev->avelocity = g_vecZero; + } + else if ( pev->speed > m_dampSpeed ) + pev->speed = m_dampSpeed; + else if ( pev->speed < -m_dampSpeed ) + pev->speed = -m_dampSpeed; + + } +} + + +void CPendulum :: Touch ( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + if ( pev->dmg <= 0 ) + return; + + // we can't hurt this thing, so we're not concerned with it + if ( !pevOther->takedamage ) + return; + + // calculate damage based on rotation speed + float damage = pev->dmg * pev->speed * 0.01; + + if ( damage < 0 ) + damage = -damage; + + pOther->TakeDamage( pev, pev, damage, DMG_CRUSH ); + + pevOther->velocity = (pevOther->origin - VecBModelOrigin(pev) ).Normalize() * damage; +} + +void CPendulum :: RopeTouch ( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + if ( !pOther->IsPlayer() ) + {// not a player! + ALERT ( at_console, "Not a client\n" ); + return; + } + + if ( ENT(pevOther) == pev->enemy ) + {// this player already on the rope. + return; + } + + pev->enemy = pOther->edict(); + pevOther->velocity = g_vecZero; + pevOther->movetype = MOVETYPE_NONE; +} + + diff --git a/hlsdk/dmc/dlls/buttons.cpp b/hlsdk/dmc/dlls/buttons.cpp new file mode 100644 index 00000000..2ffe311a --- /dev/null +++ b/hlsdk/dmc/dlls/buttons.cpp @@ -0,0 +1,1276 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== buttons.cpp ======================================================== + + button-related code + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "doors.h" + + +#define SF_BUTTON_DONTMOVE 1 +#define SF_ROTBUTTON_NOTSOLID 1 +#define SF_BUTTON_TOGGLE 32 // button stays pushed until reactivated +#define SF_BUTTON_SPARK_IF_OFF 64 // button sparks in OFF state +#define SF_BUTTON_TOUCH_ONLY 256 // button only fires as a result of USE key. + +#define SF_GLOBAL_SET 1 // Set global state to initial state on spawn + +class CEnvGlobal : public CPointEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + string_t m_globalstate; + int m_triggermode; + int m_initialstate; +}; + +TYPEDESCRIPTION CEnvGlobal::m_SaveData[] = +{ + DEFINE_FIELD( CEnvGlobal, m_globalstate, FIELD_STRING ), + DEFINE_FIELD( CEnvGlobal, m_triggermode, FIELD_INTEGER ), + DEFINE_FIELD( CEnvGlobal, m_initialstate, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CEnvGlobal, CBaseEntity ); + +LINK_ENTITY_TO_CLASS( env_global, CEnvGlobal ); + +void CEnvGlobal::KeyValue( KeyValueData *pkvd ) +{ + pkvd->fHandled = TRUE; + + if ( FStrEq(pkvd->szKeyName, "globalstate") ) // State name + m_globalstate = ALLOC_STRING( pkvd->szValue ); + else if ( FStrEq(pkvd->szKeyName, "triggermode") ) + m_triggermode = atoi( pkvd->szValue ); + else if ( FStrEq(pkvd->szKeyName, "initialstate") ) + m_initialstate = atoi( pkvd->szValue ); + else + CPointEntity::KeyValue( pkvd ); +} + +void CEnvGlobal::Spawn( void ) +{ + if ( !m_globalstate ) + { + REMOVE_ENTITY( ENT(pev) ); + return; + } + if ( FBitSet( pev->spawnflags, SF_GLOBAL_SET ) ) + { + if ( !gGlobalState.EntityInTable( m_globalstate ) ) + gGlobalState.EntityAdd( m_globalstate, gpGlobals->mapname, (GLOBALESTATE)m_initialstate ); + } +} + + +void CEnvGlobal::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + GLOBALESTATE oldState = gGlobalState.EntityGetState( m_globalstate ); + GLOBALESTATE newState; + + switch( m_triggermode ) + { + case 0: + newState = GLOBAL_OFF; + break; + + case 1: + newState = GLOBAL_ON; + break; + + case 2: + newState = GLOBAL_DEAD; + break; + + default: + case 3: + if ( oldState == GLOBAL_ON ) + newState = GLOBAL_OFF; + else if ( oldState == GLOBAL_OFF ) + newState = GLOBAL_ON; + else + newState = oldState; + } + + if ( gGlobalState.EntityInTable( m_globalstate ) ) + gGlobalState.EntitySetState( m_globalstate, newState ); + else + gGlobalState.EntityAdd( m_globalstate, gpGlobals->mapname, newState ); +} + + + +TYPEDESCRIPTION CMultiSource::m_SaveData[] = +{ + //!!!BUGBUG FIX + DEFINE_ARRAY( CMultiSource, m_rgEntities, FIELD_EHANDLE, MS_MAX_TARGETS ), + DEFINE_ARRAY( CMultiSource, m_rgTriggered, FIELD_INTEGER, MS_MAX_TARGETS ), + DEFINE_FIELD( CMultiSource, m_iTotal, FIELD_INTEGER ), + DEFINE_FIELD( CMultiSource, m_globalstate, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CMultiSource, CBaseEntity ); + +LINK_ENTITY_TO_CLASS( multisource, CMultiSource ); +// +// Cache user-entity-field values until spawn is called. +// + +void CMultiSource::KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "style") || + FStrEq(pkvd->szKeyName, "height") || + FStrEq(pkvd->szKeyName, "killtarget") || + FStrEq(pkvd->szKeyName, "value1") || + FStrEq(pkvd->szKeyName, "value2") || + FStrEq(pkvd->szKeyName, "value3")) + pkvd->fHandled = TRUE; + else if ( FStrEq(pkvd->szKeyName, "globalstate") ) + { + m_globalstate = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +#define SF_MULTI_INIT 1 + +void CMultiSource::Spawn() +{ + // set up think for later registration + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->nextthink = gpGlobals->time + 0.1; + pev->spawnflags |= SF_MULTI_INIT; // Until it's initialized + SetThink(&CMultiSource::Register); +} + +void CMultiSource::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int i = 0; + + // Find the entity in our list + while (i < m_iTotal) + if ( m_rgEntities[i++] == pCaller ) + break; + + // if we didn't find it, report error and leave + if (i > m_iTotal) + { + ALERT(at_console, "MultiSrc:Used by non member %s.\n", STRING(pCaller->pev->classname)); + return; + } + + // CONSIDER: a Use input to the multisource always toggles. Could check useType for ON/OFF/TOGGLE + + m_rgTriggered[i-1] ^= 1; + + // + if ( IsTriggered( pActivator ) ) + { + ALERT( at_aiconsole, "Multisource %s enabled (%d inputs)\n", STRING(pev->targetname), m_iTotal ); + USE_TYPE useType = USE_TOGGLE; + if ( m_globalstate ) + useType = USE_ON; + SUB_UseTargets( NULL, useType, 0 ); + } +} + + +BOOL CMultiSource::IsTriggered( CBaseEntity * ) +{ + // Is everything triggered? + int i = 0; + + // Still initializing? + if ( pev->spawnflags & SF_MULTI_INIT ) + return 0; + + while (i < m_iTotal) + { + if (m_rgTriggered[i] == 0) + break; + i++; + } + + if (i == m_iTotal) + { + if ( !m_globalstate || gGlobalState.EntityGetState( m_globalstate ) == GLOBAL_ON ) + return 1; + } + + return 0; +} + +void CMultiSource::Register(void) +{ + edict_t *pentTarget = NULL; + + m_iTotal = 0; + memset( m_rgEntities, 0, MS_MAX_TARGETS * sizeof(EHANDLE) ); + + SetThink(&CBaseEntity::SUB_DoNothing); + + // search for all entities which target this multisource (pev->targetname) + + pentTarget = FIND_ENTITY_BY_STRING(NULL, "target", STRING(pev->targetname)); + + while (!FNullEnt(pentTarget) && (m_iTotal < MS_MAX_TARGETS)) + { + CBaseEntity *pTarget = CBaseEntity::Instance(pentTarget); + if ( pTarget ) + m_rgEntities[m_iTotal++] = pTarget; + + pentTarget = FIND_ENTITY_BY_STRING( pentTarget, "target", STRING(pev->targetname)); + } + + pentTarget = FIND_ENTITY_BY_STRING(NULL, "classname", "multi_manager"); + while (!FNullEnt(pentTarget) && (m_iTotal < MS_MAX_TARGETS)) + { + CBaseEntity *pTarget = CBaseEntity::Instance(pentTarget); + if ( pTarget && pTarget->HasTarget(pev->targetname) ) + m_rgEntities[m_iTotal++] = pTarget; + + pentTarget = FIND_ENTITY_BY_STRING( pentTarget, "classname", "multi_manager" ); + } + + pev->spawnflags &= ~SF_MULTI_INIT; +} + +// CBaseButton +TYPEDESCRIPTION CBaseButton::m_SaveData[] = +{ + DEFINE_FIELD( CBaseButton, m_fStayPushed, FIELD_BOOLEAN ), + DEFINE_FIELD( CBaseButton, m_fRotating, FIELD_BOOLEAN ), + + DEFINE_FIELD( CBaseButton, m_sounds, FIELD_INTEGER ), + DEFINE_FIELD( CBaseButton, m_bLockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_bLockedSentence, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_bUnlockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_bUnlockedSentence, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_strChangeTarget, FIELD_STRING ), +// DEFINE_FIELD( CBaseButton, m_ls, FIELD_??? ), // This is restored in Precache() +}; + + +IMPLEMENT_SAVERESTORE( CBaseButton, CBaseToggle ); + +void CBaseButton::Precache( void ) +{ + char *pszSound; + + if ( FBitSet ( pev->spawnflags, SF_BUTTON_SPARK_IF_OFF ) )// this button should spark in OFF state + { + PRECACHE_SOUND ("buttons/spark1.wav"); + PRECACHE_SOUND ("buttons/spark2.wav"); + PRECACHE_SOUND ("buttons/spark3.wav"); + PRECACHE_SOUND ("buttons/spark4.wav"); + PRECACHE_SOUND ("buttons/spark5.wav"); + PRECACHE_SOUND ("buttons/spark6.wav"); + } + + // get door button sounds, for doors which require buttons to open + + if (m_bLockedSound) + { + pszSound = ButtonSound( (int)m_bLockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sLockedSound = ALLOC_STRING(pszSound); + } + + if (m_bUnlockedSound) + { + pszSound = ButtonSound( (int)m_bUnlockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sUnlockedSound = ALLOC_STRING(pszSound); + } + + // get sentence group names, for doors which are directly 'touched' to open + + switch (m_bLockedSentence) + { + case 1: m_ls.sLockedSentence = MAKE_STRING("NA"); break; // access denied + case 2: m_ls.sLockedSentence = MAKE_STRING("ND"); break; // security lockout + case 3: m_ls.sLockedSentence = MAKE_STRING("NF"); break; // blast door + case 4: m_ls.sLockedSentence = MAKE_STRING("NFIRE"); break; // fire door + case 5: m_ls.sLockedSentence = MAKE_STRING("NCHEM"); break; // chemical door + case 6: m_ls.sLockedSentence = MAKE_STRING("NRAD"); break; // radiation door + case 7: m_ls.sLockedSentence = MAKE_STRING("NCON"); break; // gen containment + case 8: m_ls.sLockedSentence = MAKE_STRING("NH"); break; // maintenance door + case 9: m_ls.sLockedSentence = MAKE_STRING("NG"); break; // broken door + + default: m_ls.sLockedSentence = 0; break; + } + + switch (m_bUnlockedSentence) + { + case 1: m_ls.sUnlockedSentence = MAKE_STRING("EA"); break; // access granted + case 2: m_ls.sUnlockedSentence = MAKE_STRING("ED"); break; // security door + case 3: m_ls.sUnlockedSentence = MAKE_STRING("EF"); break; // blast door + case 4: m_ls.sUnlockedSentence = MAKE_STRING("EFIRE"); break; // fire door + case 5: m_ls.sUnlockedSentence = MAKE_STRING("ECHEM"); break; // chemical door + case 6: m_ls.sUnlockedSentence = MAKE_STRING("ERAD"); break; // radiation door + case 7: m_ls.sUnlockedSentence = MAKE_STRING("ECON"); break; // gen containment + case 8: m_ls.sUnlockedSentence = MAKE_STRING("EH"); break; // maintenance door + + default: m_ls.sUnlockedSentence = 0; break; + } +} + +// +// Cache user-entity-field values until spawn is called. +// + +void CBaseButton::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "changetarget")) + { + m_strChangeTarget = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sound")) + { + m_bLockedSound = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sentence")) + { + m_bLockedSentence = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sound")) + { + m_bUnlockedSound = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sentence")) + { + m_bUnlockedSentence = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +// +// ButtonShot +// +int CBaseButton::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + BUTTON_CODE code = ButtonResponseToTouch(); + + if ( code == BUTTON_NOTHING ) + return 0; + // Temporarily disable the touch function, until movement is finished. + SetTouch( NULL ); + + m_hActivator = CBaseEntity::Instance( pevAttacker ); + if ( m_hActivator == 0 ) + return 0; + + if ( code == BUTTON_RETURN ) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + // Toggle buttons fire when they get back to their "home" position + if ( !(pev->spawnflags & SF_BUTTON_TOGGLE) ) + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + ButtonReturn(); + } + else // code == BUTTON_ACTIVATE + ButtonActivate( ); + + return 0; +} + +/*QUAKED func_button (0 .5 .8) ? +When a button is touched, it moves some distance in the direction of it's angle, +triggers all of it's targets, waits some time, then returns to it's original position +where it can be triggered again. + +"angle" determines the opening direction +"target" all entities with a matching targetname will be used +"speed" override the default 40 speed +"wait" override the default 1 second wait (-1 = never return) +"lip" override the default 4 pixel lip remaining at end of move +"health" if set, the button must be killed instead of touched +"sounds" +0) steam metal +1) wooden clunk +2) metallic click +3) in-out +*/ +LINK_ENTITY_TO_CLASS( func_button, CBaseButton ); + + +void CBaseButton::Spawn( ) +{ + char *pszSound; + + //---------------------------------------------------- + //determine sounds for buttons + //a sound of 0 should not make a sound + //---------------------------------------------------- + pszSound = ButtonSound( m_sounds ); + PRECACHE_SOUND(pszSound); + pev->noise = ALLOC_STRING(pszSound); + + Precache(); + + if ( FBitSet ( pev->spawnflags, SF_BUTTON_SPARK_IF_OFF ) )// this button should spark in OFF state + { + SetThink ( &CBaseButton::ButtonSpark ); + pev->nextthink = gpGlobals->time + 0.5;// no hurry, make sure everything else spawns + } + + SetMovedir(pev); + + pev->movetype = MOVETYPE_PUSH; + pev->solid = SOLID_BSP; + SET_MODEL(ENT(pev), STRING(pev->model)); + + if (pev->speed == 0) + pev->speed = 40; + + if (pev->health > 0) + { + pev->takedamage = DAMAGE_YES; + } + + if (m_flWait == 0) + m_flWait = 1; + if (m_flLip == 0) + m_flLip = 4; + + m_toggle_state = TS_AT_BOTTOM; + m_vecPosition1 = pev->origin; + // Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big + m_vecPosition2 = m_vecPosition1 + (pev->movedir * (fabs( pev->movedir.x * (pev->size.x-2) ) + fabs( pev->movedir.y * (pev->size.y-2) ) + fabs( pev->movedir.z * (pev->size.z-2) ) - m_flLip)); + + + // Is this a non-moving button? + if ( ((m_vecPosition2 - m_vecPosition1).Length() < 1) || (pev->spawnflags & SF_BUTTON_DONTMOVE) ) + m_vecPosition2 = m_vecPosition1; + + m_fStayPushed = (m_flWait == -1 ? TRUE : FALSE); + m_fRotating = FALSE; + + // if the button is flagged for USE button activation only, take away it's touch function and add a use function + + if ( FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) // touchable button + { + SetTouch( &CBaseButton::ButtonTouch ); + } + else + { + SetTouch ( NULL ); + SetUse ( &CBaseButton::ButtonUse ); + } +} + + +// Button sound table. +// Also used by CBaseDoor to get 'touched' door lock/unlock sounds + +char *ButtonSound( int sound ) +{ + char *pszSound; + + switch ( sound ) + { + case 0: pszSound = "common/null.wav"; break; + case 1: pszSound = "buttons/button1.wav"; break; + case 2: pszSound = "buttons/button2.wav"; break; + case 3: pszSound = "buttons/button3.wav"; break; + case 4: pszSound = "buttons/button4.wav"; break; + case 5: pszSound = "buttons/button5.wav"; break; + case 6: pszSound = "buttons/button6.wav"; break; + case 7: pszSound = "buttons/button7.wav"; break; + case 8: pszSound = "buttons/button8.wav"; break; + case 9: pszSound = "buttons/button9.wav"; break; + case 10: pszSound = "buttons/button10.wav"; break; + case 11: pszSound = "buttons/button11.wav"; break; + case 12: pszSound = "buttons/latchlocked1.wav"; break; + case 13: pszSound = "buttons/latchunlocked1.wav"; break; + case 14: pszSound = "buttons/lightswitch2.wav";break; + +// next 6 slots reserved for any additional sliding button sounds we may add + + case 21: pszSound = "buttons/lever1.wav"; break; + case 22: pszSound = "buttons/lever2.wav"; break; + case 23: pszSound = "buttons/lever3.wav"; break; + case 24: pszSound = "buttons/lever4.wav"; break; + case 25: pszSound = "buttons/lever5.wav"; break; + + default:pszSound = "buttons/button9.wav"; break; + } + + return pszSound; +} + +// +// Makes flagged buttons spark when turned off +// + +void DoSpark(entvars_t *pev, const Vector &location ) +{ + Vector tmp = location + pev->size * 0.5; + UTIL_Sparks( tmp ); + + float flVolume = RANDOM_FLOAT ( 0.25 , 0.75 ) * 0.4;//random volume range + switch ( (int)(RANDOM_FLOAT(0,1) * 6) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark1.wav", flVolume, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark2.wav", flVolume, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark3.wav", flVolume, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark4.wav", flVolume, ATTN_NORM); break; + case 4: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark5.wav", flVolume, ATTN_NORM); break; + case 5: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark6.wav", flVolume, ATTN_NORM); break; + } +} + +void CBaseButton::ButtonSpark ( void ) +{ + SetThink ( &CBaseButton::ButtonSpark ); + pev->nextthink = gpGlobals->time + ( 0.1 + RANDOM_FLOAT ( 0, 1.5 ) );// spark again at random interval + + DoSpark( pev, pev->mins ); +} + + +// +// Button's Use function +// +void CBaseButton::ButtonUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Ignore touches if button is moving, or pushed-in and waiting to auto-come-out. + // UNDONE: Should this use ButtonResponseToTouch() too? + if (m_toggle_state == TS_GOING_UP || m_toggle_state == TS_GOING_DOWN ) + return; + + m_hActivator = pActivator; + if ( m_toggle_state == TS_AT_TOP) + { + if (!m_fStayPushed && FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE)) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + //SUB_UseTargets( m_eoActivator ); + ButtonReturn(); + } + } + else + ButtonActivate( ); +} + + +CBaseButton::BUTTON_CODE CBaseButton::ButtonResponseToTouch( void ) +{ + // Ignore touches if button is moving, or pushed-in and waiting to auto-come-out. + if (m_toggle_state == TS_GOING_UP || + m_toggle_state == TS_GOING_DOWN || + (m_toggle_state == TS_AT_TOP && !m_fStayPushed && !FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE) ) ) + return BUTTON_NOTHING; + + if (m_toggle_state == TS_AT_TOP) + { + if((FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE) ) && !m_fStayPushed) + { + return BUTTON_RETURN; + } + } + else + return BUTTON_ACTIVATE; + + return BUTTON_NOTHING; +} + + +// +// Touching a button simply "activates" it. +// +void CBaseButton:: ButtonTouch( CBaseEntity *pOther ) +{ + // Ignore touches by anything but players + if (!FClassnameIs(pOther->pev, "player")) + return; + + m_hActivator = pOther; + + BUTTON_CODE code = ButtonResponseToTouch(); + + if ( code == BUTTON_NOTHING ) + return; + + if (!UTIL_IsMasterTriggered(m_sMaster, pOther)) + { + // play button locked sound + PlayLockSounds(pev, &m_ls, TRUE, TRUE); + return; + } + + // Temporarily disable the touch function, until movement is finished. + SetTouch( NULL ); + + if ( code == BUTTON_RETURN ) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + ButtonReturn(); + } + else // code == BUTTON_ACTIVATE + ButtonActivate( ); +} + +// +// Starts the button moving "in/up". +// +void CBaseButton::ButtonActivate( ) +{ + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + { + // button is locked, play locked sound + PlayLockSounds(pev, &m_ls, TRUE, TRUE); + return; + } + else + { + // button is unlocked, play unlocked sound + PlayLockSounds(pev, &m_ls, FALSE, TRUE); + } + + ASSERT(m_toggle_state == TS_AT_BOTTOM); + m_toggle_state = TS_GOING_UP; + + SetMoveDone( &CBaseButton::TriggerAndWait ); + if (!m_fRotating) + LinearMove( m_vecPosition2, pev->speed); + else + AngularMove( m_vecAngle2, pev->speed); +} + +// +// Button has reached the "in/up" position. Activate its "targets", and pause before "popping out". +// +void CBaseButton::TriggerAndWait( void ) +{ + ASSERT(m_toggle_state == TS_GOING_UP); + + if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + return; + + m_toggle_state = TS_AT_TOP; + + // If button automatically comes back out, start it moving out. + // Else re-instate touch method + if (m_fStayPushed || FBitSet ( pev->spawnflags, SF_BUTTON_TOGGLE ) ) + { + if ( !FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) // this button only works if USED, not touched! + { + // ALL buttons are now use only + SetTouch ( NULL ); + } + else + SetTouch( &CBaseButton::ButtonTouch ); + } + else + { + pev->nextthink = pev->ltime + m_flWait; + SetThink( &CBaseButton::ButtonReturn ); + } + + pev->frame = 1; // use alternate textures + + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); +} + + +// +// Starts the button moving "out/down". +// +void CBaseButton::ButtonReturn( void ) +{ + ASSERT(m_toggle_state == TS_AT_TOP); + m_toggle_state = TS_GOING_DOWN; + + SetMoveDone( &CBaseButton::ButtonBackHome ); + if (!m_fRotating) + LinearMove( m_vecPosition1, pev->speed); + else + AngularMove( m_vecAngle1, pev->speed); + + pev->frame = 0; // use normal textures +} + + +// +// Button has returned to start state. Quiesce it. +// +void CBaseButton::ButtonBackHome( void ) +{ + ASSERT(m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_AT_BOTTOM; + + if ( FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE) ) + { + //EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + } + + + if (!FStringNull(pev->target)) + { + edict_t* pentTarget = NULL; + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->target)); + + if (FNullEnt(pentTarget)) + break; + + if (!FClassnameIs(pentTarget, "multisource")) + continue; + CBaseEntity *pTarget = CBaseEntity::Instance( pentTarget ); + + if ( pTarget ) + pTarget->Use( m_hActivator, this, USE_TOGGLE, 0 ); + } + } + +// Re-instate touch method, movement cycle is complete. + if ( !FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) // this button only works if USED, not touched! + { + // All buttons are now use only + SetTouch ( NULL ); + } + else + SetTouch( &CBaseButton::ButtonTouch ); + +// reset think for a sparking button + if ( FBitSet ( pev->spawnflags, SF_BUTTON_SPARK_IF_OFF ) ) + { + SetThink ( &CBaseButton::ButtonSpark ); + pev->nextthink = gpGlobals->time + 0.5;// no hurry. + } +} + + + +// +// Rotating button (aka "lever") +// +class CRotButton : public CBaseButton +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( func_rot_button, CRotButton ); + +void CRotButton::Spawn( void ) +{ + char *pszSound; + //---------------------------------------------------- + //determine sounds for buttons + //a sound of 0 should not make a sound + //---------------------------------------------------- + pszSound = ButtonSound( m_sounds ); + PRECACHE_SOUND(pszSound); + pev->noise = ALLOC_STRING(pszSound); + + // set the axis of rotation + CBaseToggle::AxisDir( pev ); + + // check for clockwise rotation + if ( FBitSet (pev->spawnflags, SF_DOOR_ROTATE_BACKWARDS) ) + pev->movedir = pev->movedir * -1; + + pev->movetype = MOVETYPE_PUSH; + + if ( pev->spawnflags & SF_ROTBUTTON_NOTSOLID ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + + SET_MODEL(ENT(pev), STRING(pev->model)); + + if (pev->speed == 0) + pev->speed = 40; + + if (m_flWait == 0) + m_flWait = 1; + + if (pev->health > 0) + { + pev->takedamage = DAMAGE_YES; + } + + m_toggle_state = TS_AT_BOTTOM; + m_vecAngle1 = pev->angles; + m_vecAngle2 = pev->angles + pev->movedir * m_flMoveDistance; + ASSERTSZ(m_vecAngle1 != m_vecAngle2, "rotating button start/end positions are equal"); + + m_fStayPushed = (m_flWait == -1 ? TRUE : FALSE); + m_fRotating = TRUE; + + // if the button is flagged for USE button activation only, take away it's touch function and add a use function + if ( !FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) + { + SetTouch ( NULL ); + SetUse ( &CBaseButton::ButtonUse ); + } + else // touchable button + SetTouch( &CBaseButton::ButtonTouch ); + + //SetTouch( ButtonTouch ); +} + + +// Make this button behave like a door (HACKHACK) +// This will disable use and make the button solid +// rotating buttons were made SOLID_NOT by default since their were some +// collision problems with them... +#define SF_MOMENTARY_DOOR 0x0001 + +class CMomentaryRotButton : public CBaseToggle +{ +public: + void Spawn ( void ); + void KeyValue( KeyValueData *pkvd ); + virtual int ObjectCaps( void ) + { + int flags = CBaseToggle :: ObjectCaps() & (~FCAP_ACROSS_TRANSITION); + if ( pev->spawnflags & SF_MOMENTARY_DOOR ) + return flags; + return flags | FCAP_CONTINUOUS_USE; + } + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Off( void ); + void EXPORT Return( void ); + void UpdateSelf( float value ); + void UpdateSelfReturn( float value ); + void UpdateAllButtons( float value, int start ); + + void PlaySound( void ); + void UpdateTarget( float value ); + + static CMomentaryRotButton *Instance( edict_t *pent ) { return (CMomentaryRotButton *)GET_PRIVATE(pent);}; + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_lastUsed; + int m_direction; + float m_returnSpeed; + vec3_t m_start; + vec3_t m_end; + int m_sounds; +}; +TYPEDESCRIPTION CMomentaryRotButton::m_SaveData[] = +{ + DEFINE_FIELD( CMomentaryRotButton, m_lastUsed, FIELD_INTEGER ), + DEFINE_FIELD( CMomentaryRotButton, m_direction, FIELD_INTEGER ), + DEFINE_FIELD( CMomentaryRotButton, m_returnSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CMomentaryRotButton, m_start, FIELD_VECTOR ), + DEFINE_FIELD( CMomentaryRotButton, m_end, FIELD_VECTOR ), + DEFINE_FIELD( CMomentaryRotButton, m_sounds, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CMomentaryRotButton, CBaseToggle ); + +LINK_ENTITY_TO_CLASS( momentary_rot_button, CMomentaryRotButton ); + +void CMomentaryRotButton::Spawn( void ) +{ + CBaseToggle::AxisDir( pev ); + + if ( pev->speed == 0 ) + pev->speed = 100; + + if ( m_flMoveDistance < 0 ) + { + m_start = pev->angles + pev->movedir * m_flMoveDistance; + m_end = pev->angles; + m_direction = 1; // This will toggle to -1 on the first use() + m_flMoveDistance = -m_flMoveDistance; + } + else + { + m_start = pev->angles; + m_end = pev->angles + pev->movedir * m_flMoveDistance; + m_direction = -1; // This will toggle to +1 on the first use() + } + + if ( pev->spawnflags & SF_MOMENTARY_DOOR ) + pev->solid = SOLID_BSP; + else + pev->solid = SOLID_NOT; + + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + char *pszSound = ButtonSound( m_sounds ); + PRECACHE_SOUND(pszSound); + pev->noise = ALLOC_STRING(pszSound); + m_lastUsed = 0; +} + +void CMomentaryRotButton::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "returnspeed")) + { + m_returnSpeed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CMomentaryRotButton::PlaySound( void ) +{ + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); +} + +// BUGBUG: This design causes a latentcy. When the button is retriggered, the first impulse +// will send the target in the wrong direction because the parameter is calculated based on the +// current, not future position. +void CMomentaryRotButton::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + pev->ideal_yaw = CBaseToggle::AxisDelta( pev->spawnflags, pev->angles, m_start ) / m_flMoveDistance; + + UpdateAllButtons( pev->ideal_yaw, 1 ); + UpdateTarget( pev->ideal_yaw ); +} + +void CMomentaryRotButton::UpdateAllButtons( float value, int start ) +{ + // Update all rot buttons attached to the same target + edict_t *pentTarget = NULL; + for (;;) + { + + pentTarget = FIND_ENTITY_BY_STRING(pentTarget, "target", STRING(pev->target)); + if (FNullEnt(pentTarget)) + break; + + if ( FClassnameIs( VARS(pentTarget), "momentary_rot_button" ) ) + { + CMomentaryRotButton *pEntity = CMomentaryRotButton::Instance(pentTarget); + if ( pEntity ) + { + if ( start ) + pEntity->UpdateSelf( value ); + else + pEntity->UpdateSelfReturn( value ); + } + } + } +} + +void CMomentaryRotButton::UpdateSelf( float value ) +{ + BOOL fplaysound = FALSE; + + if ( !m_lastUsed ) + { + fplaysound = TRUE; + m_direction = -m_direction; + } + m_lastUsed = 1; + + pev->nextthink = pev->ltime + 0.1; + if ( m_direction > 0 && value >= 1.0 ) + { + pev->avelocity = g_vecZero; + pev->angles = m_end; + return; + } + else if ( m_direction < 0 && value <= 0 ) + { + pev->avelocity = g_vecZero; + pev->angles = m_start; + return; + } + + if (fplaysound) + PlaySound(); + + // HACKHACK -- If we're going slow, we'll get multiple player packets per frame, bump nexthink on each one to avoid stalling + if ( pev->nextthink < pev->ltime ) + pev->nextthink = pev->ltime + 0.1; + else + pev->nextthink += 0.1; + + pev->avelocity = (m_direction * pev->speed) * pev->movedir; + SetThink( &CMomentaryRotButton::Off ); +} + +void CMomentaryRotButton::UpdateTarget( float value ) +{ + if (!FStringNull(pev->target)) + { + edict_t* pentTarget = NULL; + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->target)); + if (FNullEnt(pentTarget)) + break; + CBaseEntity *pEntity = CBaseEntity::Instance(pentTarget); + if ( pEntity ) + { + pEntity->Use( this, this, USE_SET, value ); + } + } + } +} + +void CMomentaryRotButton::Off( void ) +{ + pev->avelocity = g_vecZero; + m_lastUsed = 0; + if ( FBitSet( pev->spawnflags, SF_PENDULUM_AUTO_RETURN ) && m_returnSpeed > 0 ) + { + SetThink( &CMomentaryRotButton::Return ); + pev->nextthink = pev->ltime + 0.1; + m_direction = -1; + } + else + SetThink( NULL ); +} + +void CMomentaryRotButton::Return( void ) +{ + float value = CBaseToggle::AxisDelta( pev->spawnflags, pev->angles, m_start ) / m_flMoveDistance; + + UpdateAllButtons( value, 0 ); // This will end up calling UpdateSelfReturn() n times, but it still works right + if ( value > 0 ) + UpdateTarget( value ); +} + + +void CMomentaryRotButton::UpdateSelfReturn( float value ) +{ + if ( value <= 0 ) + { + pev->avelocity = g_vecZero; + pev->angles = m_start; + pev->nextthink = -1; + SetThink( NULL ); + } + else + { + pev->avelocity = -m_returnSpeed * pev->movedir; + pev->nextthink = pev->ltime + 0.1; + } +} + + +//---------------------------------------------------------------- +// Spark +//---------------------------------------------------------------- + +class CEnvSpark : public CBaseEntity +{ +public: + void Spawn(void); + void Precache(void); + void EXPORT SparkThink(void); + void EXPORT SparkStart(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT SparkStop(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue(KeyValueData *pkvd); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flDelay; +}; + + +TYPEDESCRIPTION CEnvSpark::m_SaveData[] = +{ + DEFINE_FIELD( CEnvSpark, m_flDelay, FIELD_FLOAT), +}; + +IMPLEMENT_SAVERESTORE( CEnvSpark, CBaseEntity ); + +LINK_ENTITY_TO_CLASS(env_spark, CEnvSpark); +LINK_ENTITY_TO_CLASS(env_debris, CEnvSpark); + +void CEnvSpark::Spawn(void) +{ + SetThink( NULL ); + SetUse( NULL ); + + if (FBitSet(pev->spawnflags, 32)) // Use for on/off + { + if (FBitSet(pev->spawnflags, 64)) // Start on + { + SetThink(&CEnvSpark::SparkThink); // start sparking + SetUse(&CEnvSpark::SparkStop); // set up +USE to stop sparking + } + else + SetUse(&CEnvSpark::SparkStart); + } + else + SetThink(&CEnvSpark::SparkThink); + + pev->nextthink = gpGlobals->time + ( 0.1 + RANDOM_FLOAT ( 0, 1.5 ) ); + + if (m_flDelay <= 0) + m_flDelay = 1.5; + + Precache( ); +} + + +void CEnvSpark::Precache(void) +{ + PRECACHE_SOUND( "buttons/spark1.wav" ); + PRECACHE_SOUND( "buttons/spark2.wav" ); + PRECACHE_SOUND( "buttons/spark3.wav" ); + PRECACHE_SOUND( "buttons/spark4.wav" ); + PRECACHE_SOUND( "buttons/spark5.wav" ); + PRECACHE_SOUND( "buttons/spark6.wav" ); +} + +void CEnvSpark::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "MaxDelay")) + { + m_flDelay = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "style") || + FStrEq(pkvd->szKeyName, "height") || + FStrEq(pkvd->szKeyName, "killtarget") || + FStrEq(pkvd->szKeyName, "value1") || + FStrEq(pkvd->szKeyName, "value2") || + FStrEq(pkvd->szKeyName, "value3")) + pkvd->fHandled = TRUE; + else + CBaseEntity::KeyValue( pkvd ); +} + +void EXPORT CEnvSpark::SparkThink(void) +{ + pev->nextthink = gpGlobals->time + 0.1 + RANDOM_FLOAT (0, m_flDelay); + DoSpark( pev, pev->origin ); +} + +void EXPORT CEnvSpark::SparkStart(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetUse(&CEnvSpark::SparkStop); + SetThink(&CEnvSpark::SparkThink); + pev->nextthink = gpGlobals->time + (0.1 + RANDOM_FLOAT ( 0, m_flDelay)); +} + +void EXPORT CEnvSpark::SparkStop(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetUse(&CEnvSpark::SparkStart); + SetThink(NULL); +} + +#define SF_BTARGET_USE 0x0001 +#define SF_BTARGET_ON 0x0002 + +class CButtonTarget : public CBaseEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + int ObjectCaps( void ); + +}; + +LINK_ENTITY_TO_CLASS( button_target, CButtonTarget ); + +void CButtonTarget::Spawn( void ) +{ + pev->movetype = MOVETYPE_PUSH; + pev->solid = SOLID_BSP; + SET_MODEL(ENT(pev), STRING(pev->model)); + pev->takedamage = DAMAGE_YES; + + if ( FBitSet( pev->spawnflags, SF_BTARGET_ON ) ) + pev->frame = 1; +} + +void CButtonTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, (int)pev->frame ) ) + return; + pev->frame = 1-pev->frame; + if ( pev->frame ) + SUB_UseTargets( pActivator, USE_ON, 0 ); + else + SUB_UseTargets( pActivator, USE_OFF, 0 ); +} + + +int CButtonTarget :: ObjectCaps( void ) +{ + int caps = CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; + + if ( FBitSet(pev->spawnflags, SF_BTARGET_USE) ) + return caps | FCAP_IMPULSE_USE; + else + return caps; +} + + +int CButtonTarget::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + Use( Instance(pevAttacker), this, USE_TOGGLE, 0 ); + + return 1; +} diff --git a/hlsdk/dmc/dlls/cbase.cpp b/hlsdk/dmc/dlls/cbase.cpp new file mode 100644 index 00000000..0d5c083f --- /dev/null +++ b/hlsdk/dmc/dlls/cbase.cpp @@ -0,0 +1,767 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "client.h" +#include "decals.h" +#include "gamerules.h" +#include "game.h" + +extern "C" void PM_Move ( struct playermove_s *ppmove, int server ); +extern "C" void PM_Init ( struct playermove_s *ppmove ); +extern "C" char PM_FindTextureType( char *name ); + +void EntvarsKeyvalue( entvars_t *pev, KeyValueData *pkvd ); + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); +extern DLL_GLOBAL Vector g_vecAttackDir; +extern DLL_GLOBAL int g_iSkillLevel; + +static DLL_FUNCTIONS gFunctionTable = +{ + GameDLLInit, //pfnGameInit + DispatchSpawn, //pfnSpawn + DispatchThink, //pfnThink + DispatchUse, //pfnUse + DispatchTouch, //pfnTouch + DispatchBlocked, //pfnBlocked + DispatchKeyValue, //pfnKeyValue + DispatchSave, //pfnSave + DispatchRestore, //pfnRestore + DispatchObjectCollsionBox, //pfnAbsBox + + SaveWriteFields, //pfnSaveWriteFields + SaveReadFields, //pfnSaveReadFields + + SaveGlobalState, //pfnSaveGlobalState + RestoreGlobalState, //pfnRestoreGlobalState + ResetGlobalState, //pfnResetGlobalState + + ClientConnect, //pfnClientConnect + ClientDisconnect, //pfnClientDisconnect + ClientKill, //pfnClientKill + ClientPutInServer, //pfnClientPutInServer + ClientCommand, //pfnClientCommand + ClientUserInfoChanged, //pfnClientUserInfoChanged + ServerActivate, //pfnServerActivate + ServerDeactivate, //pfnServerDeactivate + + PlayerPreThink, //pfnPlayerPreThink + PlayerPostThink, //pfnPlayerPostThink + + StartFrame, //pfnStartFrame + ParmsNewLevel, //pfnParmsNewLevel + ParmsChangeLevel, //pfnParmsChangeLevel + + GetGameDescription, //pfnGetGameDescription Returns string describing current .dll game. + PlayerCustomization, //pfnPlayerCustomization Notifies .dll of new customization for player. + + SpectatorConnect, //pfnSpectatorConnect Called when spectator joins server + SpectatorDisconnect, //pfnSpectatorDisconnect Called when spectator leaves the server + SpectatorThink, //pfnSpectatorThink Called when spectator sends a command packet (usercmd_t) + + Sys_Error, //pfnSys_Error Called when engine has encountered an error + + PM_Move, //pfnPM_Move + PM_Init, //pfnPM_Init Server version of player movement initialization + PM_FindTextureType, //pfnPM_FindTextureType + + SetupVisibility, //pfnSetupVisibility Set up PVS and PAS for networking for this client + UpdateClientData, //pfnUpdateClientData Set up data sent only to specific client + AddToFullPack, //pfnAddToFullPack + CreateBaseline, //pfnCreateBaseline Tweak entity baseline for network encoding, allows setup of player baselines, too. + RegisterEncoders, //pfnRegisterEncoders Callbacks for network encoding + GetWeaponData, //pfnGetWeaponData + CmdStart, //pfnCmdStart + CmdEnd, //pfnCmdEnd + ConnectionlessPacket, //pfnConnectionlessPacket + GetHullBounds, //pfnGetHullBounds + CreateInstancedBaselines, //pfnCreateInstancedBaselines + InconsistentFile, //pfnInconsistentFile + AllowLagCompensation, //pfnAllowLagCompensation +}; + +static void SetObjectCollisionBox( entvars_t *pev ); + +int GetEntityAPI( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion ) +{ + if ( !pFunctionTable || interfaceVersion != INTERFACE_VERSION ) + { + return FALSE; + } + + memcpy( pFunctionTable, &gFunctionTable, sizeof( DLL_FUNCTIONS ) ); + return TRUE; +} + +int GetEntityAPI2( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ) +{ + if ( !pFunctionTable || *interfaceVersion != INTERFACE_VERSION ) + { + // Tell engine what version we had, so it can figure out who is out of date. + *interfaceVersion = INTERFACE_VERSION; + return FALSE; + } + + memcpy( pFunctionTable, &gFunctionTable, sizeof( DLL_FUNCTIONS ) ); + return TRUE; +} + + +int DispatchSpawn( edict_t *pent ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if (pEntity) + { + // Initialize these or entities who don't link to the world won't have anything in here + pEntity->pev->absmin = pEntity->pev->origin - Vector(1,1,1); + pEntity->pev->absmax = pEntity->pev->origin + Vector(1,1,1); + + pEntity->Spawn(); + + // Try to get the pointer again, in case the spawn function deleted the entity. + // UNDONE: Spawn() should really return a code to ask that the entity be deleted, but + // that would touch too much code for me to do that right now. + pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if ( pEntity ) + { + if ( g_pGameRules && !g_pGameRules->IsAllowedToSpawn( pEntity ) ) + return -1; // return that this entity should be deleted + if ( pEntity->pev->flags & FL_KILLME ) + return -1; + } + + + // Handle global stuff here + if ( pEntity && pEntity->pev->globalname ) + { + const globalentity_t *pGlobal = gGlobalState.EntityFromTable( pEntity->pev->globalname ); + if ( pGlobal ) + { + // Already dead? delete + if ( pGlobal->state == GLOBAL_DEAD ) + return -1; + else if ( !FStrEq( STRING(gpGlobals->mapname), pGlobal->levelName ) ) + pEntity->MakeDormant(); // Hasn't been moved to this level yet, wait but stay alive + // In this level & not dead, continue on as normal + } + else + { + // Spawned entities default to 'On' + gGlobalState.EntityAdd( pEntity->pev->globalname, gpGlobals->mapname, GLOBAL_ON ); +// ALERT( at_console, "Added global entity %s (%s)\n", STRING(pEntity->pev->classname), STRING(pEntity->pev->globalname) ); + } + } + + } + + return 0; +} + +void DispatchKeyValue( edict_t *pentKeyvalue, KeyValueData *pkvd ) +{ + if ( !pkvd || !pentKeyvalue ) + return; + + EntvarsKeyvalue( VARS(pentKeyvalue), pkvd ); + + // If the key was an entity variable, or there's no class set yet, don't look for the object, it may + // not exist yet. + if ( pkvd->fHandled || pkvd->szClassName == NULL ) + return; + + // Get the actualy entity object + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pentKeyvalue); + + if ( !pEntity ) + return; + + pEntity->KeyValue( pkvd ); +} + + +// HACKHACK -- this is a hack to keep the node graph entity from "touching" things (like triggers) +// while it builds the graph +BOOL gTouchDisabled = FALSE; +void DispatchTouch( edict_t *pentTouched, edict_t *pentOther ) +{ + if ( gTouchDisabled ) + return; + + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pentTouched); + CBaseEntity *pOther = (CBaseEntity *)GET_PRIVATE( pentOther ); + + if ( pEntity && pOther && ! ((pEntity->pev->flags | pOther->pev->flags) & FL_KILLME) ) + pEntity->Touch( pOther ); +} + + +void DispatchUse( edict_t *pentUsed, edict_t *pentOther ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pentUsed); + CBaseEntity *pOther = (CBaseEntity *)GET_PRIVATE(pentOther); + + if (pEntity && !(pEntity->pev->flags & FL_KILLME) ) + pEntity->Use( pOther, pOther, USE_TOGGLE, 0 ); +} + +void DispatchThink( edict_t *pent ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + if (pEntity) + { + if ( FBitSet( pEntity->pev->flags, FL_DORMANT ) ) + ALERT( at_error, "Dormant entity %s is thinking!!\n", STRING(pEntity->pev->classname) ); + + pEntity->Think(); + } +} + +void DispatchBlocked( edict_t *pentBlocked, edict_t *pentOther ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE( pentBlocked ); + CBaseEntity *pOther = (CBaseEntity *)GET_PRIVATE( pentOther ); + + if (pEntity) + pEntity->Blocked( pOther ); +} + +void DispatchSave( edict_t *pent, SAVERESTOREDATA *pSaveData ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if ( pEntity && pSaveData ) + { + ENTITYTABLE *pTable = &pSaveData->pTable[ pSaveData->currentIndex ]; + + if ( pTable->pent != pent ) + ALERT( at_error, "ENTITY TABLE OR INDEX IS WRONG!!!!\n" ); + + if ( pEntity->ObjectCaps() & FCAP_DONT_SAVE ) + return; + + // These don't use ltime & nextthink as times really, but we'll fudge around it. + if ( pEntity->pev->movetype == MOVETYPE_PUSH ) + { + float delta = pEntity->pev->nextthink - pEntity->pev->ltime; + pEntity->pev->ltime = gpGlobals->time; + pEntity->pev->nextthink = pEntity->pev->ltime + delta; + } + + pTable->location = pSaveData->size; // Remember entity position for file I/O + pTable->classname = pEntity->pev->classname; // Remember entity class for respawn + + CSave saveHelper( pSaveData ); + pEntity->Save( saveHelper ); + + pTable->size = pSaveData->size - pTable->location; // Size of entity block is data size written to block + } +} + + +// Find the matching global entity. Spit out an error if the designer made entities of +// different classes with the same global name +CBaseEntity *FindGlobalEntity( string_t classname, string_t globalname ) +{ + edict_t *pent = FIND_ENTITY_BY_STRING( NULL, "globalname", STRING(globalname) ); + CBaseEntity *pReturn = CBaseEntity::Instance( pent ); + if ( pReturn ) + { + if ( !FClassnameIs( pReturn->pev, STRING(classname) ) ) + { + ALERT( at_console, "Global entity found %s, wrong class %s\n", STRING(globalname), STRING(pReturn->pev->classname) ); + pReturn = NULL; + } + } + + return pReturn; +} + + +int DispatchRestore( edict_t *pent, SAVERESTOREDATA *pSaveData, int globalEntity ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if ( pEntity && pSaveData ) + { + entvars_t tmpVars; + Vector oldOffset; + + CRestore restoreHelper( pSaveData ); + if ( globalEntity ) + { + CRestore tmpRestore( pSaveData ); + tmpRestore.PrecacheMode( 0 ); + tmpRestore.ReadEntVars( "ENTVARS", &tmpVars ); + + // HACKHACK - reset the save pointers, we're going to restore for real this time + pSaveData->size = pSaveData->pTable[pSaveData->currentIndex].location; + pSaveData->pCurrentData = pSaveData->pBaseData + pSaveData->size; + // ------------------- + + + const globalentity_t *pGlobal = gGlobalState.EntityFromTable( tmpVars.globalname ); + + // Don't overlay any instance of the global that isn't the latest + // pSaveData->szCurrentMapName is the level this entity is coming from + // pGlobla->levelName is the last level the global entity was active in. + // If they aren't the same, then this global update is out of date. + if ( !FStrEq( pSaveData->szCurrentMapName, pGlobal->levelName ) ) + return 0; + + // Compute the new global offset + oldOffset = pSaveData->vecLandmarkOffset; + CBaseEntity *pNewEntity = FindGlobalEntity( tmpVars.classname, tmpVars.globalname ); + if ( pNewEntity ) + { +// ALERT( at_console, "Overlay %s with %s\n", STRING(pNewEntity->pev->classname), STRING(tmpVars.classname) ); + // Tell the restore code we're overlaying a global entity from another level + restoreHelper.SetGlobalMode( 1 ); // Don't overwrite global fields + pSaveData->vecLandmarkOffset = (pSaveData->vecLandmarkOffset - pNewEntity->pev->mins) + tmpVars.mins; + pEntity = pNewEntity;// we're going to restore this data OVER the old entity + pent = ENT( pEntity->pev ); + // Update the global table to say that the global definition of this entity should come from this level + gGlobalState.EntityUpdate( pEntity->pev->globalname, gpGlobals->mapname ); + } + else + { + // This entity will be freed automatically by the engine. If we don't do a restore on a matching entity (below) + // or call EntityUpdate() to move it to this level, we haven't changed global state at all. + return 0; + } + + } + + if ( pEntity->ObjectCaps() & FCAP_MUST_SPAWN ) + { + pEntity->Restore( restoreHelper ); + pEntity->Spawn(); + } + else + { + pEntity->Restore( restoreHelper ); + pEntity->Precache( ); + } + + // Again, could be deleted, get the pointer again. + pEntity = (CBaseEntity *)GET_PRIVATE(pent); + +#if 0 + if ( pEntity && pEntity->pev->globalname && globalEntity ) + { + ALERT( at_console, "Global %s is %s\n", STRING(pEntity->pev->globalname), STRING(pEntity->pev->model) ); + } +#endif + + // Is this an overriding global entity (coming over the transition), or one restoring in a level + if ( globalEntity ) + { +// ALERT( at_console, "After: %f %f %f %s\n", pEntity->pev->origin.x, pEntity->pev->origin.y, pEntity->pev->origin.z, STRING(pEntity->pev->model) ); + pSaveData->vecLandmarkOffset = oldOffset; + if ( pEntity ) + { + UTIL_SetOrigin( pEntity->pev, pEntity->pev->origin ); + pEntity->OverrideReset(); + } + } + else if ( pEntity && pEntity->pev->globalname ) + { + const globalentity_t *pGlobal = gGlobalState.EntityFromTable( pEntity->pev->globalname ); + if ( pGlobal ) + { + // Already dead? delete + if ( pGlobal->state == GLOBAL_DEAD ) + return -1; + else if ( !FStrEq( STRING(gpGlobals->mapname), pGlobal->levelName ) ) + { + pEntity->MakeDormant(); // Hasn't been moved to this level yet, wait but stay alive + } + // In this level & not dead, continue on as normal + } + else + { + ALERT( at_error, "Global Entity %s (%s) not in table!!!\n", STRING(pEntity->pev->globalname), STRING(pEntity->pev->classname) ); + // Spawned entities default to 'On' + gGlobalState.EntityAdd( pEntity->pev->globalname, gpGlobals->mapname, GLOBAL_ON ); + } + } + } + return 0; +} + + +void DispatchObjectCollsionBox( edict_t *pent ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + if (pEntity) + { + pEntity->SetObjectCollisionBox(); + } + else + SetObjectCollisionBox( &pent->v ); +} + + +void SaveWriteFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + CSave saveHelper( pSaveData ); + saveHelper.WriteFields( pname, pBaseData, pFields, fieldCount ); +} + + +void SaveReadFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + CRestore restoreHelper( pSaveData ); + restoreHelper.ReadFields( pname, pBaseData, pFields, fieldCount ); +} + + +edict_t * EHANDLE::Get( void ) +{ + if (m_pent) + { + if (m_pent->serialnumber == m_serialnumber) + return m_pent; + else + return NULL; + } + return NULL; +}; + +edict_t * EHANDLE::Set( edict_t *pent ) +{ + m_pent = pent; + if (pent) + m_serialnumber = m_pent->serialnumber; + return pent; +}; + + +EHANDLE :: operator CBaseEntity *() +{ + return (CBaseEntity *)GET_PRIVATE( Get( ) ); +}; + + +CBaseEntity * EHANDLE :: operator = (CBaseEntity *pEntity) +{ + if (pEntity) + { + m_pent = ENT( pEntity->pev ); + if (m_pent) + m_serialnumber = m_pent->serialnumber; + } + else + { + m_pent = NULL; + m_serialnumber = 0; + } + return pEntity; +} + +EHANDLE :: operator int () +{ + return Get() != NULL; +} + +CBaseEntity * EHANDLE :: operator -> () +{ + return (CBaseEntity *)GET_PRIVATE( Get( ) ); +} + + +// give health +int CBaseEntity :: TakeHealth( float flHealth, int bitsDamageType ) +{ + if (!pev->takedamage) + return 0; + +// heal + if ( pev->health >= pev->max_health ) + return 0; + + pev->health += flHealth; + + if (pev->health > pev->max_health) + pev->health = pev->max_health; + + return 1; +} + +// inflict damage on this entity. bitsDamageType indicates type of damage inflicted, ie: DMG_CRUSH + +int CBaseEntity :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + Vector vecTemp; + + if (!pev->takedamage) + return 0; + + // UNDONE: some entity types may be immune or resistant to some bitsDamageType + + // if Attacker == Inflictor, the attack was a melee or other instant-hit attack. + // (that is, no actual entity projectile was involved in the attack so use the shooter's origin). + if ( pevAttacker == pevInflictor ) + { + vecTemp = pevAttacker->origin - ( VecBModelOrigin(pev) ); + } + else + // an actual missile was involved. + { + vecTemp = pevInflictor->origin - ( VecBModelOrigin(pev) ); + } + +// this global is still used for glass and other non-monster killables, along with decals. + g_vecAttackDir = vecTemp.Normalize(); + +// save damage based on the target's armor level + +// figure momentum add (don't let hurt brushes or other triggers move player) + if ((!FNullEnt(pevInflictor)) && (pev->movetype == MOVETYPE_WALK || pev->movetype == MOVETYPE_STEP) && (pevAttacker->solid != SOLID_TRIGGER) ) + { + Vector vecDir = pev->origin - (pevInflictor->absmin + pevInflictor->absmax) * 0.5; + vecDir = vecDir.Normalize(); + + float flForce = flDamage * ((32 * 32 * 72.0) / (pev->size.x * pev->size.y * pev->size.z)) * 5; + + if (flForce > 1000.0) + flForce = 1000.0; + pev->velocity = pev->velocity + vecDir * flForce; + } + +// do the damage + pev->health -= flDamage; + if (pev->health <= 0) + { + Killed( pevAttacker, GIB_NORMAL ); + return 0; + } + + return 1; +} + + +void CBaseEntity :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->takedamage = DAMAGE_NO; + pev->deadflag = DEAD_DEAD; + UTIL_Remove( this ); +} + + +CBaseEntity *CBaseEntity::GetNextTarget( void ) +{ + if ( FStringNull( pev->target ) ) + return NULL; + edict_t *pTarget = FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(pev->target) ); + if ( FNullEnt(pTarget) ) + return NULL; + + return Instance( pTarget ); +} + +// Global Savedata for Delay +TYPEDESCRIPTION CBaseEntity::m_SaveData[] = +{ + DEFINE_FIELD( CBaseEntity, m_pGoalEnt, FIELD_CLASSPTR ), + + DEFINE_FIELD( CBaseEntity, m_pfnThink, FIELD_FUNCTION ), // UNDONE: Build table of these!!! + DEFINE_FIELD( CBaseEntity, m_pfnTouch, FIELD_FUNCTION ), + DEFINE_FIELD( CBaseEntity, m_pfnUse, FIELD_FUNCTION ), + DEFINE_FIELD( CBaseEntity, m_pfnBlocked, FIELD_FUNCTION ), +}; + + +int CBaseEntity::Save( CSave &save ) +{ + if ( save.WriteEntVars( "ENTVARS", pev ) ) + return save.WriteFields( "BASE", this, m_SaveData, ARRAYSIZE(m_SaveData) ); + + return 0; +} + +int CBaseEntity::Restore( CRestore &restore ) +{ + int status; + + status = restore.ReadEntVars( "ENTVARS", pev ); + if ( status ) + status = restore.ReadFields( "BASE", this, m_SaveData, ARRAYSIZE(m_SaveData) ); + + if ( pev->modelindex != 0 && !FStringNull(pev->model) ) + { + Vector mins, maxs; + mins = pev->mins; // Set model is about to destroy these + maxs = pev->maxs; + + + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL(ENT(pev), STRING(pev->model)); + UTIL_SetSize(pev, mins, maxs); // Reset them + } + + return status; +} + + +// Initialize absmin & absmax to the appropriate box +void SetObjectCollisionBox( entvars_t *pev ) +{ + if ( (pev->solid == SOLID_BSP) && + (pev->angles.x || pev->angles.y|| pev->angles.z) ) + { // expand for rotation + float max, v; + int i; + + max = 0; + for (i=0 ; i<3 ; i++) + { + v = fabs( pev->mins[i]); + if (v > max) + max = v; + v = fabs( pev->maxs[i]); + if (v > max) + max = v; + } + for (i=0 ; i<3 ; i++) + { + pev->absmin[i] = pev->origin[i] - max; + pev->absmax[i] = pev->origin[i] + max; + } + } + else + { + pev->absmin = pev->origin + pev->mins; + pev->absmax = pev->origin + pev->maxs; + } + + pev->absmin.x -= 1; + pev->absmin.y -= 1; + pev->absmin.z -= 1; + pev->absmax.x += 1; + pev->absmax.y += 1; + pev->absmax.z += 1; +} + + +void CBaseEntity::SetObjectCollisionBox( void ) +{ + ::SetObjectCollisionBox( pev ); +} + + +int CBaseEntity :: Intersects( CBaseEntity *pOther ) +{ + if ( pOther->pev->absmin.x > pev->absmax.x || + pOther->pev->absmin.y > pev->absmax.y || + pOther->pev->absmin.z > pev->absmax.z || + pOther->pev->absmax.x < pev->absmin.x || + pOther->pev->absmax.y < pev->absmin.y || + pOther->pev->absmax.z < pev->absmin.z ) + return 0; + return 1; +} + +void CBaseEntity :: MakeDormant( void ) +{ + SetBits( pev->flags, FL_DORMANT ); + + // Don't touch + pev->solid = SOLID_NOT; + // Don't move + pev->movetype = MOVETYPE_NONE; + // Don't draw + SetBits( pev->effects, EF_NODRAW ); + // Don't think + pev->nextthink = 0; + // Relink + UTIL_SetOrigin( pev, pev->origin ); +} + +int CBaseEntity :: IsDormant( void ) +{ + return FBitSet( pev->flags, FL_DORMANT ); +} + +BOOL CBaseEntity :: IsInWorld( void ) +{ + // position + if (pev->origin.x >= 4096) return FALSE; + if (pev->origin.y >= 4096) return FALSE; + if (pev->origin.z >= 4096) return FALSE; + if (pev->origin.x <= -4096) return FALSE; + if (pev->origin.y <= -4096) return FALSE; + if (pev->origin.z <= -4096) return FALSE; + // speed + if (pev->velocity.x >= 2000) return FALSE; + if (pev->velocity.y >= 2000) return FALSE; + if (pev->velocity.z >= 2000) return FALSE; + if (pev->velocity.x <= -2000) return FALSE; + if (pev->velocity.y <= -2000) return FALSE; + if (pev->velocity.z <= -2000) return FALSE; + + return TRUE; +} + +int CBaseEntity::ShouldToggle( USE_TYPE useType, BOOL currentState ) +{ + if ( useType != USE_TOGGLE && useType != USE_SET ) + { + if ( (currentState && useType == USE_ON) || (!currentState && useType == USE_OFF) ) + return 0; + } + return 1; +} + + +int CBaseEntity :: DamageDecal( int bitsDamageType ) +{ + if ( pev->rendermode == kRenderTransAlpha ) + return -1; + + if ( pev->rendermode != kRenderNormal ) + return DECAL_BPROOF1; + + return DECAL_GUNSHOT1 + RANDOM_LONG(0,4); +} + + + +// NOTE: szName must be a pointer to constant memory, e.g. "monster_class" because the entity +// will keep a pointer to it after this call. +CBaseEntity * CBaseEntity::Create( char *szName, const Vector &vecOrigin, const Vector &vecAngles, edict_t *pentOwner ) +{ + edict_t *pent; + CBaseEntity *pEntity; + + pent = CREATE_NAMED_ENTITY( MAKE_STRING( szName )); + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in Create!\n" ); + return NULL; + } + pEntity = Instance( pent ); + pEntity->pev->owner = pentOwner; + pEntity->pev->origin = vecOrigin; + pEntity->pev->angles = vecAngles; + DispatchSpawn( pEntity->edict() ); + return pEntity; +} + + diff --git a/hlsdk/dmc/dlls/cbase.h b/hlsdk/dmc/dlls/cbase.h new file mode 100644 index 00000000..6cff601d --- /dev/null +++ b/hlsdk/dmc/dlls/cbase.h @@ -0,0 +1,801 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +Class Hierachy + +CBaseEntity + CBaseDelay + CBaseToggle + CBaseItem + CBaseMonster + CBaseCycler + CBasePlayer + CBaseGroup +*/ + +#define MAX_PATH_SIZE 10 // max number of nodes available for a path. + +// These are caps bits to indicate what an object's capabilities (currently used for save/restore and level transitions) +#define FCAP_CUSTOMSAVE 0x00000001 +#define FCAP_ACROSS_TRANSITION 0x00000002 // should transfer between transitions +#define FCAP_MUST_SPAWN 0x00000004 // Spawn after restore +#define FCAP_DONT_SAVE 0x80000000 // Don't save this +#define FCAP_IMPULSE_USE 0x00000008 // can be used by the player +#define FCAP_CONTINUOUS_USE 0x00000010 // can be used by the player +#define FCAP_ONOFF_USE 0x00000020 // can be used by the player +#define FCAP_DIRECTIONAL_USE 0x00000040 // Player sends +/- 1 when using (currently only tracktrains) +#define FCAP_MASTER 0x00000080 // Can be used to "master" other entities (like multisource) + +// UNDONE: This will ignore transition volumes (trigger_transition), but not the PVS!!! +#define FCAP_FORCE_TRANSITION 0x00000080 // ALWAYS goes across transitions + +#include "saverestore.h" +#include "schedule.h" + +#ifndef MONSTEREVENT_H +#include "monsterevent.h" +#endif + +// C functions for external declarations that call the appropriate C++ methods + +#ifdef _WIN32 +#define EXPORT _declspec( dllexport ) +#else +#define EXPORT /* */ +#endif + +extern "C" EXPORT int GetEntityAPI( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion ); +extern "C" EXPORT int GetEntityAPI2( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ); + +extern int DispatchSpawn( edict_t *pent ); +extern void DispatchKeyValue( edict_t *pentKeyvalue, KeyValueData *pkvd ); +extern void DispatchTouch( edict_t *pentTouched, edict_t *pentOther ); +extern void DispatchUse( edict_t *pentUsed, edict_t *pentOther ); +extern void DispatchThink( edict_t *pent ); +extern void DispatchBlocked( edict_t *pentBlocked, edict_t *pentOther ); +extern void DispatchSave( edict_t *pent, SAVERESTOREDATA *pSaveData ); +extern int DispatchRestore( edict_t *pent, SAVERESTOREDATA *pSaveData, int globalEntity ); +extern void DispatchObjectCollsionBox( edict_t *pent ); +extern void SaveWriteFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); +extern void SaveReadFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); +extern void SaveGlobalState( SAVERESTOREDATA *pSaveData ); +extern void RestoreGlobalState( SAVERESTOREDATA *pSaveData ); +extern void ResetGlobalState( void ); + +typedef enum { USE_OFF = 0, USE_ON = 1, USE_SET = 2, USE_TOGGLE = 3 } USE_TYPE; + +extern void FireTargets( const char *targetName, CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +typedef void (CBaseEntity::*BASEPTR)(void); +typedef void (CBaseEntity::*ENTITYFUNCPTR)(CBaseEntity *pOther ); +typedef void (CBaseEntity::*USEPTR)( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +// For CLASSIFY +#define CLASS_NONE 0 +#define CLASS_MACHINE 1 +#define CLASS_PLAYER 2 +#define CLASS_HUMAN_PASSIVE 3 +#define CLASS_HUMAN_MILITARY 4 +#define CLASS_ALIEN_MILITARY 5 +#define CLASS_ALIEN_PASSIVE 6 +#define CLASS_ALIEN_MONSTER 7 +#define CLASS_ALIEN_PREY 8 +#define CLASS_ALIEN_PREDATOR 9 +#define CLASS_INSECT 10 +#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; +class CBasePlayerItem; +class CSquadMonster; + + +#define SF_NORESPAWN ( 1 << 30 )// !!!set this bit on guns and stuff that should never respawn. + +// +// EHANDLE. Safe way to point to CBaseEntities who may die between frames +// +class EHANDLE +{ +private: + edict_t *m_pent; + int m_serialnumber; +public: + edict_t *Get( void ); + edict_t *Set( edict_t *pent ); + + operator int (); + + operator CBaseEntity *(); + + CBaseEntity * operator = (CBaseEntity *pEntity); + CBaseEntity * operator ->(); +}; + + +// +// Base Entity. All entity types derive from this +// +class CBaseEntity +{ +public: + // Constructor. Set engine to use C/C++ callback functions + // pointers to engine data + entvars_t *pev; // Don't need to save/restore this pointer, the engine resets it + + // path corners + CBaseEntity *m_pGoalEnt;// path corner we are heading towards + CBaseEntity *m_pLink;// used for temporary link-list operations. + + // initialization functions + virtual void Spawn( void ) { return; } + virtual void Precache( void ) { return; } + virtual void KeyValue( KeyValueData* pkvd) { pkvd->fHandled = FALSE; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual int ObjectCaps( void ) { return FCAP_ACROSS_TRANSITION; } + virtual void Activate( void ) {} + + // Setup the object->object collision box (pev->mins / pev->maxs is the object->world collision box) + virtual void SetObjectCollisionBox( void ); + +// Classify - returns the type of group (i.e, "houndeye", or "human military" so that monsters with different classnames +// still realize that they are teammates. (overridden for monsters that form groups) + virtual int Classify ( void ) { return CLASS_NONE; }; + virtual void DeathNotice ( entvars_t *pevChild ) {}// monster maker children use this to tell the monster maker that they have died. + + + static TYPEDESCRIPTION m_SaveData[]; + + virtual void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + 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 ); + virtual int BloodColor( void ) { return DONT_BLEED; } + virtual void TraceBleed( float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); + virtual BOOL IsTriggered( CBaseEntity *pActivator ) {return TRUE;} + virtual CBaseMonster *MyMonsterPointer( void ) { return NULL;} + virtual CSquadMonster *MySquadMonsterPointer( void ) { return NULL;} + virtual int GetToggleState( void ) { return TS_AT_TOP; } + virtual void AddPoints( int score, BOOL bAllowNegativeScore ) {} + virtual void AddPointsToTeam( int score, BOOL bAllowNegativeScore ) {} + virtual BOOL AddPlayerItem( CBasePlayerItem *pItem ) { return 0; } + virtual BOOL RemovePlayerItem( CBasePlayerItem *pItem ) { return 0; } + virtual int GiveAmmo( int iAmount, char *szName, int iMax ) { return -1; }; + virtual float GetDelay( void ) { return 0; } + virtual int IsMoving( void ) { return pev->velocity != g_vecZero; } + virtual void OverrideReset( void ) {} + virtual int DamageDecal( int bitsDamageType ); + // This is ONLY used by the node graph to test movement through a door + virtual void SetToggleState( int state ) {} + virtual void StartSneaking( void ) {} + virtual void StopSneaking( void ) {} + virtual BOOL OnControls( entvars_t *onpev ) { return FALSE; } + virtual BOOL IsSneaking( void ) { return FALSE; } + virtual BOOL IsAlive( void ) { return (pev->deadflag == DEAD_NO) && pev->health > 0; } + virtual BOOL IsBSPModel( void ) { return pev->solid == SOLID_BSP || pev->movetype == MOVETYPE_PUSHSTEP; } + virtual BOOL ReflectGauss( void ) { return ( IsBSPModel() && !pev->takedamage ); } + virtual BOOL HasTarget( string_t targetname ) { return FStrEq(STRING(targetname), STRING(pev->targetname) ); } + virtual BOOL IsInWorld( void ); + virtual BOOL IsPlayer( void ) { return FALSE; } + virtual BOOL IsNetClient( void ) { return FALSE; } + virtual const char *TeamID( void ) { return ""; } + + +// virtual void SetActivator( CBaseEntity *pActivator ) {} + virtual CBaseEntity *GetNextTarget( void ); + + // fundamental callbacks + void (CBaseEntity ::*m_pfnThink)(void); + void (CBaseEntity ::*m_pfnTouch)( CBaseEntity *pOther ); + void (CBaseEntity ::*m_pfnUse)( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void (CBaseEntity ::*m_pfnBlocked)( CBaseEntity *pOther ); + + virtual void Think( void ) { if (m_pfnThink) (this->*m_pfnThink)(); }; + virtual void Touch( CBaseEntity *pOther ) { if (m_pfnTouch) (this->*m_pfnTouch)( pOther ); }; + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) + { + if (m_pfnUse) + (this->*m_pfnUse)( pActivator, pCaller, useType, value ); + } + virtual void Blocked( CBaseEntity *pOther ) { if (m_pfnBlocked) (this->*m_pfnBlocked)( pOther ); }; + + // allow engine to allocate instance data + void *operator new( size_t stAllocateBlock, entvars_t *newpev ) + { + return (void *)ALLOC_PRIVATE(ENT(newpev), stAllocateBlock); + }; + + // don't use this. +#if defined(_MSC_VER) && _MSC_VER >= 1200 // only build this code if MSVC++ 6.0 or higher + void operator delete(void *pMem, entvars_t *pev) + { + pev->flags |= FL_KILLME; + }; +#endif + + void UpdateOnRemove( void ); + + // common member functions + void EXPORT SUB_Remove( void ); + void EXPORT SUB_DoNothing( void ); + void EXPORT SUB_StartFadeOut ( void ); + void EXPORT SUB_FadeOut ( void ); + 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 ); + + virtual CBaseEntity *Respawn( void ) { return NULL; } + + void SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ); + // Do the bounding boxes of these two intersect? + int Intersects( CBaseEntity *pOther ); + void MakeDormant( void ); + int IsDormant( void ); + BOOL IsLockedByMaster( void ) { return FALSE; } + + static CBaseEntity *Instance( edict_t *pent ) + { + if ( !pent ) + pent = ENT(0); + CBaseEntity *pEnt = (CBaseEntity *)GET_PRIVATE(pent); + return pEnt; + } + + static CBaseEntity *Instance( entvars_t *instpev ) { return Instance( ENT( instpev ) ); } + static CBaseEntity *Instance( int inst_eoffset) { return Instance( ENT( inst_eoffset) ); } + + CBaseMonster *GetMonsterPointer( entvars_t *pevMonster ) + { + CBaseEntity *pEntity = Instance( pevMonster ); + if ( pEntity ) + return pEntity->MyMonsterPointer(); + return NULL; + } + CBaseMonster *GetMonsterPointer( edict_t *pentMonster ) + { + CBaseEntity *pEntity = Instance( pentMonster ); + if ( pEntity ) + return pEntity->MyMonsterPointer(); + return NULL; + } + + + // Ugly code to lookup all functions to make sure they are exported when set. +#ifdef _DEBUG + void FunctionCheck( void *pFunction, char *name ) + { +#ifdef _WIN32 + if (pFunction && !NAME_FOR_FUNCTION((unsigned long)(pFunction)) ) + ALERT( at_error, "No EXPORT: %s:%s (%08lx)\n", STRING(pev->classname), name, (unsigned long)pFunction ); +#endif // _WIN32 + } + + BASEPTR ThinkSet( BASEPTR func, char *name ) + { + m_pfnThink = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnThink)))), name ); + return func; + } + ENTITYFUNCPTR TouchSet( ENTITYFUNCPTR func, char *name ) + { + m_pfnTouch = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnTouch)))), name ); + return func; + } + USEPTR UseSet( USEPTR func, char *name ) + { + m_pfnUse = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnUse)))), name ); + return func; + } + ENTITYFUNCPTR BlockedSet( ENTITYFUNCPTR func, char *name ) + { + m_pfnBlocked = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnBlocked)))), name ); + return func; + } + +#endif + + + // virtual functions used by a few classes + + // used by monsters that are created by the MonsterMaker + virtual void UpdateOwner( void ) { return; }; + + + // + static CBaseEntity *Create( char *szName, const Vector &vecOrigin, const Vector &vecAngles, edict_t *pentOwner = NULL ); + + virtual BOOL FBecomeProne( void ) {return FALSE;}; + edict_t *edict() { return ENT( pev ); }; + EOFFSET eoffset( ) { return OFFSET( pev ); }; + int entindex( ) { return ENTINDEX( edict() ); }; + + virtual Vector Center( ) { return (pev->absmax + pev->absmin) * 0.5; }; // center point of entity + virtual Vector EyePosition( ) { return pev->origin + pev->view_ofs; }; // position of eyes + virtual Vector EarPosition( ) { return pev->origin + pev->view_ofs; }; // position of ears + virtual Vector BodyTarget( const Vector &posSrc ) { return Center( ); }; // position to shoot at + + virtual int Illumination( ) { return GETENTITYILLUM( ENT( pev ) ); }; + + virtual BOOL FVisible ( CBaseEntity *pEntity ); + virtual BOOL FVisible ( const Vector &vecOrigin ); + + + // QUAKECLASSIC + BOOL m_bAxHitMe; + void Spawn_Telefog( Vector vecOrg, CBaseEntity *pOther ); + Vector m_vecTeleAngles; +}; + + + +// 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. + +#ifdef _DEBUG + +#define SetThink( a ) ThinkSet( static_cast (a), #a ) +#define SetTouch( a ) TouchSet( static_cast (a), #a ) +#define SetUse( a ) UseSet( static_cast (a), #a ) +#define SetBlocked( a ) BlockedSet( static_cast (a), #a ) + +#else + +#define SetThink( a ) m_pfnThink = static_cast (a) +#define SetTouch( a ) m_pfnTouch = static_cast (a) +#define SetUse( a ) m_pfnUse = static_cast (a) +#define SetBlocked( a ) m_pfnBlocked = static_cast (a) + +#endif + + +class CPointEntity : public CBaseEntity +{ +public: + void Spawn( void ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +private: +}; + + +typedef struct locksounds // sounds that doors and buttons make when locked/unlocked +{ + string_t sLockedSound; // sound a door makes when it's locked + string_t sLockedSentence; // sentence group played when door is locked + string_t sUnlockedSound; // sound a door makes when it's unlocked + string_t sUnlockedSentence; // sentence group played when door is unlocked + + int iLockedSentence; // which sentence in sentence group to play next + int iUnlockedSentence; // which sentence in sentence group to play next + + float flwaitSound; // time delay between playing consecutive 'locked/unlocked' sounds + float flwaitSentence; // time delay between playing consecutive sentences + BYTE bEOFLocked; // true if hit end of list of locked sentences + BYTE bEOFUnlocked; // true if hit end of list of unlocked sentences +} locksound_t; + +void PlayLockSounds(entvars_t *pev, locksound_t *pls, int flocked, int fbutton); + +// +// MultiSouce +// + +#define MAX_MULTI_TARGETS 16 // maximum number of targets a single multi_manager entity may be assigned. +#define MS_MAX_TARGETS 32 + +class CMultiSource : public CPointEntity +{ +public: + void Spawn( ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int ObjectCaps( void ) { return (CPointEntity::ObjectCaps() | FCAP_MASTER); } + BOOL IsTriggered( CBaseEntity *pActivator ); + void EXPORT Register( void ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + EHANDLE m_rgEntities[MS_MAX_TARGETS]; + int m_rgTriggered[MS_MAX_TARGETS]; + + int m_iTotal; + string_t m_globalstate; +}; + + +// +// generic Delay entity. +// +class CBaseDelay : public CBaseEntity +{ +public: + float m_flDelay; + int m_iszKillTarget; + + virtual void KeyValue( KeyValueData* pkvd); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + // common member functions + void SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ); + void EXPORT DelayThink( void ); +}; + + +class CBaseAnimating : public CBaseDelay +{ +public: + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + // Basic Monster Animation functions + float StudioFrameAdvance( float flInterval = 0.0 ); // accumulate animation frame time from last time called until now + int GetSequenceFlags( void ); + int LookupActivity ( int activity ); + int LookupActivityHeaviest ( int activity ); + int LookupSequence ( const char *label ); + void ResetSequenceInfo ( ); + void DispatchAnimEvents ( float flFutureInterval = 0.1 ); // Handle events that have happend since last time called up until X seconds into the future + virtual void HandleAnimEvent( MonsterEvent_t *pEvent ) { return; }; + float SetBoneController ( int iController, float flValue ); + void InitBoneControllers ( void ); + float SetBlending ( int iBlender, float flValue ); + void GetBonePosition ( int iBone, Vector &origin, Vector &angles ); + void GetAutomovement( Vector &origin, Vector &angles, float flInterval = 0.1 ); + int FindTransition( int iEndingSequence, int iGoalSequence, int *piDir ); + void GetAttachment ( int iAttachment, Vector &origin, Vector &angles ); + void SetBodygroup( int iGroup, int iValue ); + int GetBodygroup( int iGroup ); + int ExtractBbox( int sequence, float *mins, float *maxs ); + void SetSequenceBox( void ); + + // animation needs + float m_flFrameRate; // computed FPS for current sequence + float m_flGroundSpeed; // computed linear movement rate for current sequence + float m_flLastEventCheck; // last time the event list was checked + BOOL m_fSequenceFinished;// flag set when StudioAdvanceFrame moves across a frame boundry + BOOL m_fSequenceLoops; // true if the sequence loops +}; + + +// +// generic Toggle entity. +// +#define SF_ITEM_USE_ONLY 256 // ITEM_USE_ONLY = BUTTON_USE_ONLY = DOOR_USE_ONLY!!! + +class CBaseToggle : public CBaseAnimating +{ +public: + void KeyValue( KeyValueData *pkvd ); + + TOGGLE_STATE m_toggle_state; + float m_flActivateFinished;//like attack_finished, but for doors + float m_flMoveDistance;// how far a door should slide or rotate + float m_flWait; + float m_flLip; + float m_flTWidth;// for plats + float m_flTLength;// for plats + + Vector m_vecPosition1; + Vector m_vecPosition2; + Vector m_vecAngle1; + Vector m_vecAngle2; + + int m_cTriggersLeft; // trigger_counter only, # of activations remaining + float m_flHeight; + EHANDLE m_hActivator; + void (CBaseToggle::*m_pfnCallWhenMoveDone)(void); + Vector m_vecFinalDest; + Vector m_vecFinalAngle; + + int m_bitsDamageInflict; // DMG_ damage type that the door or tigger does + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + virtual int GetToggleState( void ) { return m_toggle_state; } + virtual float GetDelay( void ) { return m_flWait; } + + // common member functions + void LinearMove( Vector vecDest, float flSpeed ); + void EXPORT LinearMoveDone( void ); + void AngularMove( Vector vecDestAngle, float flSpeed ); + void EXPORT AngularMoveDone( void ); + BOOL IsLockedByMaster( void ); + + static float AxisValue( int flags, const Vector &angles ); + static void AxisDir( entvars_t *pev ); + static float AxisDelta( int flags, const Vector &angle1, const Vector &angle2 ); + + string_t m_sMaster; // If this button has a master switch, this is the targetname. + // A master switch must be of the multisource type. If all + // of the switches in the multisource have been triggered, then + // the button will be allowed to operate. Otherwise, it will be + // deactivated. +}; +#define SetMoveDone( a ) m_pfnCallWhenMoveDone = static_cast (a) + + +// people gib if their health is <= this at the time of death +#define GIB_HEALTH_VALUE -30 + +#define ROUTE_SIZE 8 // how many waypoints a monster can store at one time +#define MAX_OLD_ENEMIES 4 // how many old enemies to remember + +#define bits_CAP_DUCK ( 1 << 0 )// crouch +#define bits_CAP_JUMP ( 1 << 1 )// jump/leap +#define bits_CAP_STRAFE ( 1 << 2 )// strafe ( walk/run sideways) +#define bits_CAP_SQUAD ( 1 << 3 )// can form squads +#define bits_CAP_SWIM ( 1 << 4 )// proficiently navigate in water +#define bits_CAP_CLIMB ( 1 << 5 )// climb ladders/ropes +#define bits_CAP_USE ( 1 << 6 )// open doors/push buttons/pull levers +#define bits_CAP_HEAR ( 1 << 7 )// can hear forced sounds +#define bits_CAP_AUTO_DOORS ( 1 << 8 )// can trigger auto doors +#define bits_CAP_OPEN_DOORS ( 1 << 9 )// can open manual doors +#define bits_CAP_TURN_HEAD ( 1 << 10)// can turn head, always bone controller 0 + +#define bits_CAP_RANGE_ATTACK1 ( 1 << 11)// can do a range attack 1 +#define bits_CAP_RANGE_ATTACK2 ( 1 << 12)// can do a range attack 2 +#define bits_CAP_MELEE_ATTACK1 ( 1 << 13)// can do a melee attack 1 +#define bits_CAP_MELEE_ATTACK2 ( 1 << 14)// can do a melee attack 2 + +#define bits_CAP_FLY ( 1 << 15)// can fly, move all around + +#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 +#define DMG_CRUSH (1 << 0) // crushed by falling or moving object +#define DMG_BULLET (1 << 1) // shot +#define DMG_SLASH (1 << 2) // cut, clawed, stabbed +#define DMG_BURN (1 << 3) // heat burned +#define DMG_FREEZE (1 << 4) // frozen +#define DMG_FALL (1 << 5) // fell too far +#define DMG_BLAST (1 << 6) // explosive blast damage +#define DMG_CLUB (1 << 7) // crowbar, punch, headbutt +#define DMG_SHOCK (1 << 8) // electric shock +#define DMG_SONIC (1 << 9) // sound pulse shockwave +#define DMG_ENERGYBEAM (1 << 10) // laser or other high energy beam +#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. +#define DMG_DROWN (1 << 14) // Drowning +// time-based damage +#define DMG_TIMEBASED (~(0x3fff)) // mask for time-based damage + +#define DMG_PARALYZE (1 << 15) // slows affected creature down +#define DMG_NERVEGAS (1 << 16) // nerve toxins, very bad +#define DMG_POISON (1 << 17) // blood poisioning +#define DMG_RADIATION (1 << 18) // radiation exposure +#define DMG_DROWNRECOVER (1 << 19) // drowning recovery +#define DMG_ACID (1 << 20) // toxic chemicals or acid burns +#define DMG_SLOWBURN (1 << 21) // in an oven +#define DMG_SLOWFREEZE (1 << 22) // in a subzero freezer +#define DMG_MORTAR (1 << 23) // Hit by air raid (done to distinguish grenade from mortar) + +#define DMG_IGNORE_MAXHEALTH (1<< 24) // Used by TakeHealth only. Ignores the player's max health when healing. + +// 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 ) + +// these are the damage types that have client hud art +#define DMG_SHOWNHUD (DMG_POISON | DMG_ACID | DMG_FREEZE | DMG_SLOWFREEZE | DMG_DROWN | DMG_BURN | DMG_SLOWBURN | DMG_NERVEGAS | DMG_RADIATION | DMG_SHOCK) + +// NOTE: tweak these values based on gameplay feedback: + +#define PARALYZE_DURATION 2 // number of 2 second intervals to take damage +#define PARALYZE_DAMAGE 1.0 // damage to take each 2 second interval + +#define NERVEGAS_DURATION 2 +#define NERVEGAS_DAMAGE 5.0 + +#define POISON_DURATION 5 +#define POISON_DAMAGE 2.0 + +#define RADIATION_DURATION 2 +#define RADIATION_DAMAGE 1.0 + +#define ACID_DURATION 2 +#define ACID_DAMAGE 5.0 + +#define SLOWBURN_DURATION 2 +#define SLOWBURN_DAMAGE 1.0 + +#define SLOWFREEZE_DURATION 2 +#define SLOWFREEZE_DAMAGE 1.0 + + +#define itbd_Paralyze 0 +#define itbd_NerveGas 1 +#define itbd_Poison 2 +#define itbd_Radiation 3 +#define itbd_DrownRecover 4 +#define itbd_Acid 5 +#define itbd_SlowBurn 6 +#define itbd_SlowFreeze 7 +#define CDMG_TIMEBASED 8 + +// when calling KILLED(), a value that governs gib behavior is expected to be +// one of these three values +#define GIB_NORMAL 0// gib if entity was overkilled +#define GIB_NEVER 1// never gib, no matter how much death damage is done ( freezing, etc ) +#define GIB_ALWAYS 2// always gib ( Houndeye Shock, Barnacle Bite ) + +class CBaseMonster; +class CCineMonster; +class CSound; + +#include "basemonster.h" + + +char *ButtonSound( int sound ); // get string of button sound number + + +// +// Generic Button +// +class CBaseButton : public CBaseToggle +{ +public: + void Spawn( void ); + virtual void Precache( void ); + void RotSpawn( void ); + virtual void KeyValue( KeyValueData* pkvd); + + void ButtonActivate( ); + void SparkSoundCache( void ); + + void EXPORT ButtonShot( void ); + void EXPORT ButtonTouch( CBaseEntity *pOther ); + void EXPORT ButtonSpark ( void ); + void EXPORT TriggerAndWait( void ); + void EXPORT ButtonReturn( void ); + void EXPORT ButtonBackHome( void ); + void EXPORT ButtonUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + enum BUTTON_CODE { BUTTON_NOTHING, BUTTON_ACTIVATE, BUTTON_RETURN }; + BUTTON_CODE ButtonResponseToTouch( void ); + + static TYPEDESCRIPTION m_SaveData[]; + // Buttons that don't take damage can be IMPULSE used + virtual int ObjectCaps( void ) { return (CBaseToggle:: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | (pev->takedamage?0:FCAP_IMPULSE_USE); } + + BOOL m_fStayPushed; // button stays pushed in until touched again? + BOOL m_fRotating; // a rotating button? default is a sliding button. + + string_t m_strChangeTarget; // if this field is not null, this is an index into the engine string array. + // when this button is touched, it's target entity's TARGET field will be set + // to the button's ChangeTarget. This allows you to make a func_train switch paths, etc. + + locksound_t m_ls; // door lock sounds + + BYTE m_bLockedSound; // ordinals from entity selection + BYTE m_bLockedSentence; + BYTE m_bUnlockedSound; + BYTE m_bUnlockedSentence; + int m_sounds; +}; + +// +// Weapons +// + +#define BAD_WEAPON 0x00007FFF + +// +// Converts a entvars_t * to a class pointer +// It will allocate the class and entity if necessary +// +template T * GetClassPtr( T *a ) +{ + entvars_t *pev = (entvars_t *)a; + + // allocate entity if necessary + if (pev == NULL) + pev = VARS(CREATE_ENTITY()); + + // get the private data + a = (T *)GET_PRIVATE(ENT(pev)); + + if (a == NULL) + { + // allocate private data + a = new(pev) T; + a->pev = pev; + } + return a; +} + + +/* +bit_PUSHBRUSH_DATA | bit_TOGGLE_DATA +bit_MONSTER_DATA +bit_DELAY_DATA +bit_TOGGLE_DATA | bit_DELAY_DATA | bit_MONSTER_DATA +bit_PLAYER_DATA | bit_MONSTER_DATA +bit_MONSTER_DATA | CYCLER_DATA +bit_LIGHT_DATA +path_corner_data +bit_MONSTER_DATA | wildcard_data +bit_MONSTER_DATA | bit_GROUP_DATA +boid_flock_data +boid_data +CYCLER_DATA +bit_ITEM_DATA +bit_ITEM_DATA | func_hud_data +bit_TOGGLE_DATA | bit_ITEM_DATA +EOFFSET +env_sound_data +env_sound_data +push_trigger_data +*/ + +#define TRACER_FREQ 4 // Tracers fire every 4 bullets + +typedef struct _SelAmmo +{ + BYTE Ammo1Type; + BYTE Ammo1; + BYTE Ammo2Type; + BYTE Ammo2; +} SelAmmo; + + +// this moved here from world.cpp, to allow classes to be derived from it +//======================= +// CWorld +// +// This spawns first when each level begins. +//======================= +class CWorld : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); +}; + +class CClientFog : public CBaseEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + + float m_iStartDist; + float m_iEndDist; +}; + +// QUAKECLASSIC +extern char *g_szDeathType; diff --git a/hlsdk/dmc/dlls/cdll_dll.h b/hlsdk/dmc/dlls/cdll_dll.h new file mode 100644 index 00000000..65279fcd --- /dev/null +++ b/hlsdk/dmc/dlls/cdll_dll.h @@ -0,0 +1,46 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cdll_dll.h + +// this file is included by both the game-dll and the client-dll, + +#ifndef CDLL_DLL_H +#define CDLL_DLL_H + +#define MAX_WEAPONS 257 // ??? + +#define MAX_WEAPON_SLOTS 9 // hud item selection slots +#define MAX_ITEM_TYPES 6 // hud item selection slots + +#define MAX_ITEMS 5 // 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 MAX_AMMO_TYPES 32 // ??? +#define MAX_AMMO_SLOTS 32 // not really slots + +#define HUD_PRINTNOTIFY 1 +#define HUD_PRINTCONSOLE 2 +#define HUD_PRINTTALK 3 +#define HUD_PRINTCENTER 4 + + +#define WEAPON_SUIT 31 + +#endif diff --git a/hlsdk/dmc/dlls/client.cpp b/hlsdk/dmc/dlls/client.cpp new file mode 100644 index 00000000..6fd5bcdb --- /dev/null +++ b/hlsdk/dmc/dlls/client.cpp @@ -0,0 +1,1711 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Robin, 4-22-98: Moved set_suicide_frame() here from player.cpp to allow us to +// have one without a hardcoded player.mdl in tf_client.cpp + +/* + +===== client.cpp ======================================================== + + client/server game specific stuff + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "player.h" +#include "spectator.h" +#include "client.h" +#include "gamerules.h" +#include "customentity.h" +#include "weapons.h" +#include "weaponinfo.h" +#include "usercmd.h" +#include "netadr.h" + +extern DLL_GLOBAL ULONG g_ulModelIndexPlayer; +extern DLL_GLOBAL BOOL g_fGameOver; +extern DLL_GLOBAL int g_iSkillLevel; +extern DLL_GLOBAL ULONG g_ulFrameCount; + +extern bool g_bHaveMOTD; + +extern void CopyToBodyQue(entvars_t* pev); +extern int giPrecacheGrunt; +extern int gmsgSayText; + +extern unsigned short g_sGibbed; +extern unsigned short g_sTeleport; +extern unsigned short g_sTrail; +extern unsigned short g_sExplosion; +extern unsigned short g_usPowerUp; + +void LinkUserMessages( void ); + +/* + * used by kill command and disconnect command + * ROBIN: Moved here from player.cpp, to allow multiple player models + */ +void set_suicide_frame(entvars_t* pev) +{ + if (!FStrEq(STRING(pev->model), "models/player.mdl")) + return; // allready gibbed + +// pev->frame = $deatha11; + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_TOSS; + pev->deadflag = DEAD_DEAD; + pev->nextthink = -1; +} + + +/* +=========== +ClientConnect + +called when a player connects to a server +============ +*/ +BOOL ClientConnect( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) +{ + return g_pGameRules->ClientConnected( pEntity, pszName, pszAddress, szRejectReason ); + +// a client connecting during an intermission can cause problems +// if (intermission_running) +// ExitIntermission (); + +} + + +/* +=========== +ClientDisconnect + +called when a player disconnects from a server + +GLOBALS ASSUMED SET: g_fGameOver +============ +*/ +void ClientDisconnect( edict_t *pEntity ) +{ + if (g_fGameOver) + return; + + char text[256]; + sprintf( text, "- %s has left the game\n", STRING(pEntity->v.netname) ); + MESSAGE_BEGIN( MSG_ALL, gmsgSayText, NULL ); + WRITE_BYTE( ENTINDEX(pEntity) ); + WRITE_STRING( text ); + MESSAGE_END(); + + // since the edict doesn't get deleted, fix it so it doesn't interfere. + pEntity->v.takedamage = DAMAGE_NO;// don't attract autoaim + pEntity->v.solid = SOLID_NOT;// nonsolid + UTIL_SetOrigin ( &pEntity->v, pEntity->v.origin ); + + g_pGameRules->ClientDisconnected( pEntity ); +} + + +// called by ClientKill and DeadThink +void respawn(entvars_t* pev, BOOL fCopyCorpse) +{ + if (gpGlobals->coop || gpGlobals->deathmatch) + { + if ( fCopyCorpse ) + { + // make a copy of the dead body for appearances sake + CopyToBodyQue(pev); + } + + // respawn player + GetClassPtr( (CBasePlayer *)pev)->Spawn( ); + } + else + { // restart the entire server + SERVER_COMMAND("reload\n"); + } +} + +/* +============ +ClientKill + +Player entered the suicide command + +GLOBALS ASSUMED SET: g_ulModelIndexPlayer +============ +*/ +void ClientKill( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + + CBasePlayer *pl = (CBasePlayer*) CBasePlayer::Instance( pev ); + + if ( pl->m_fNextSuicideTime > gpGlobals->time ) + return; // prevent suiciding too ofter + + pl->m_fNextSuicideTime = gpGlobals->time + 1; // don't let them suicide for 5 seconds after suiciding + + // have the player kill themself + pev->health = 0; + pl->Killed( pev, GIB_ALWAYS ); + +// pev->modelindex = g_ulModelIndexPlayer; +// pev->frags -= 2; // extra penalty +// respawn( pev ); +} + +/* +=========== +ClientPutInServer + +called each time a player is spawned +============ +*/ +void ClientPutInServer( edict_t *pEntity ) +{ + CBasePlayer *pPlayer; + + entvars_t *pev = &pEntity->v; + + pPlayer = GetClassPtr((CBasePlayer *)pev); + pPlayer->SetCustomDecalFrames(-1); // Assume none; + + pPlayer->m_bHadFirstSpawn = false; + + // Allocate a CBasePlayer for pev, and call spawn + pPlayer->Spawn(); + + // Reset interpolation during first frame + pPlayer->pev->effects |= EF_NOINTERP; +} + +//// HOST_SAY +// String comes in as +// say blah blah blah +// or as +// blah blah blah +// +void Host_Say( edict_t *pEntity, int teamonly ) +{ + CBasePlayer *client; + int j; + char *p; + char text[128]; + char szTemp[256]; + const char *cpSay = "say"; + const char *cpSayTeam = "say_team"; + const char *pcmd = CMD_ARGV(0); + + // We can get a raw string now, without the "say " prepended + if ( CMD_ARGC() == 0 ) + return; + + if ( !stricmp( pcmd, cpSay) || !stricmp( pcmd, cpSayTeam ) ) + { + if ( CMD_ARGC() >= 2 ) + { + p = (char *)CMD_ARGS(); + } + else + { + // say with a blank message, nothing to do + return; + } + } + else // Raw text, need to prepend argv[0] + { + if ( CMD_ARGC() >= 2 ) + { + sprintf( szTemp, "%s %s", ( char * )pcmd, (char *)CMD_ARGS() ); + } + else + { + // Just a one word command, use the first word...sigh + sprintf( szTemp, "%s", ( char * )pcmd ); + } + p = szTemp; + } + +// remove quotes if present + if (*p == '"') + { + p++; + p[strlen(p)-1] = 0; + } + +// make sure the text has content + char *pc = NULL; + for ( pc = p; pc != NULL && *pc != 0; pc++ ) + { + if ( isprint( *pc ) && !isspace( *pc ) ) + { + pc = NULL; // we've found an alphanumeric character, so text is valid + break; + } + } + if ( pc != NULL ) + return; // no character found, so say nothing + +// turn on color set 2 (color on, no sound) + if ( teamonly ) + sprintf( text, "%c(TEAM) %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 ) + p[j] = 0; + + strcat( text, p ); + strcat( text, "\n" ); + + entvars_t *pev = &pEntity->v; + CBasePlayer* player = GetClassPtr((CBasePlayer *)pev); + + // loop through all players + // Start with the first player. + // 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; + + if ( !(client->IsNetClient()) ) // Not a client ? (should never be true) + continue; + if ( ((CHalfLifeMultiplay *)g_pGameRules)->m_VoiceGameMgr.PlayerHasBlockedPlayer( client, player ) ) + 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(); + + } + + // print to the sending client + MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, &pEntity->v ); + WRITE_BYTE( ENTINDEX(pEntity) ); + WRITE_STRING( text ); + MESSAGE_END(); + + // echo to server console + g_engfuncs.pfnServerPrint( text ); + + char * temp; + if ( teamonly ) + temp = "say_team"; + else + temp = "say"; + + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" %s \"%s\"\n", + STRING( pEntity->v.netname ), + GETPLAYERUSERID( pEntity ), + GETPLAYERAUTHID( pEntity ), + GETPLAYERUSERID( pEntity ), + temp, + p ); +} + + +/* +=========== +ClientCommand +called each time a player uses a "cmd" command +============ +*/ +extern float g_flWeaponCheat; + +// Use CMD_ARGV, CMD_ARGV, and CMD_ARGC to get pointers the character string command. +void ClientCommand( edict_t *pEntity ) +{ + const char *pcmd = CMD_ARGV(0); + + // Is the client spawned yet? + if ( !pEntity->pvPrivateData ) + return; + + entvars_t *pev = &pEntity->v; + + if ( FStrEq(pcmd, "say" ) ) + { + Host_Say( pEntity, 0 ); + } + else if ( FStrEq(pcmd, "say_team" ) ) + { + Host_Say( pEntity, 1 ); + } + else if ( FStrEq(pcmd, "fullupdate" ) ) + { + GetClassPtr((CBasePlayer *)pev)->ForceClientDllUpdate(); + } + else if ( FStrEq(pcmd, "give" ) ) + { + if ( g_flWeaponCheat != 0.0) + { + int iszItem = ALLOC_STRING( CMD_ARGV(1) ); // Make a copy of the classname + GetClassPtr((CBasePlayer *)pev)->GiveNamedItem( STRING(iszItem) ); + } + } + else if ( FStrEq(pcmd, "_firstspawn" ) ) + { + CBasePlayer * pPlayer = GetClassPtr((CBasePlayer *)pev); + + if ( pPlayer->m_bHadFirstSpawn == false && g_bHaveMOTD ) + { + pPlayer->m_bHadFirstSpawn = true; + pPlayer->Spawn(); + } + } + + else if ( FStrEq(pcmd, "spectate" ) ) + { + CBasePlayer * pPlayer = GetClassPtr((CBasePlayer *)pev); + + if ( pPlayer->pev->flags & FL_PROXY ) + { + edict_t *pentSpawnSpot = g_pGameRules->GetPlayerSpawnSpot( pPlayer ); + pPlayer->StartObserver( pev->origin, VARS(pentSpawnSpot)->angles); + } + } + else if (FStrEq(pcmd, "lastinv" )) + { + GetClassPtr((CBasePlayer *)pev)->SelectLastItem(); + } + else if ( g_pGameRules->ClientCommand( GetClassPtr((CBasePlayer *)pev), pcmd ) ) + { + // MenuSelect returns true only if the command is properly handled, so don't print a warning + } + else if ( FStrEq( pcmd, "specmode" ) ) // new spectator mode + { + CBasePlayer * pPlayer = GetClassPtr((CBasePlayer *)pev); + if ( pPlayer->IsObserver() ) + pPlayer->Observer_SetMode( atoi( CMD_ARGV(1) ) ); + } + else if ( FStrEq( pcmd, "follownext" ) ) // follow next player + { + CBasePlayer * pPlayer = GetClassPtr((CBasePlayer *)pev); + if ( pPlayer->IsObserver() ) + pPlayer->Observer_FindNextPlayer(); + } + else + { + // tell the user they entered an unknown command + ClientPrint( &pEntity->v, HUD_PRINTCONSOLE, UTIL_VarArgs( "Unknown command: %s\n", pcmd ) ); + } +} + + +/* +======================== +ClientUserInfoChanged + +called after the player changes +userinfo - gives dll a chance to modify it before +it gets sent into the rest of the engine. +======================== +*/ +void ClientUserInfoChanged( edict_t *pEntity, char *infobuffer ) +{ + // Is the client spawned yet? + if ( !pEntity->pvPrivateData ) + return; + + // msg everyone if someone changes their name, and it isn't the first time (changing no name to current name) + if ( pEntity->v.netname && STRING(pEntity->v.netname)[0] != 0 && !FStrEq( STRING(pEntity->v.netname), g_engfuncs.pfnInfoKeyValue( infobuffer, "name" )) ) + { + char text[256]; + sprintf( text, "* %s changed name to %s\n", STRING(pEntity->v.netname), g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ) ); + MESSAGE_BEGIN( MSG_ALL, gmsgSayText, NULL ); + WRITE_BYTE( ENTINDEX(pEntity) ); + WRITE_STRING( text ); + MESSAGE_END(); + + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" changed name to \"%s\"\n", + STRING( pEntity->v.netname ), + GETPLAYERUSERID( pEntity ), + GETPLAYERAUTHID( pEntity ), + GETPLAYERUSERID( pEntity ), + g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ) ); + } + + // QUAKECLASSIC + // Weapon switching behaviour + ((CBasePlayer*)pEntity)->m_iWeaponSwitch = 8; + char *st = g_engfuncs.pfnInfoKeyValue( infobuffer, "w_switch" ); + if (st && st[0]) + { + ((CBasePlayer*)pEntity)->m_iWeaponSwitch = atoi(st); + } + ((CBasePlayer*)pEntity)->m_iBackpackSwitch = 8; + st = g_engfuncs.pfnInfoKeyValue( infobuffer, "b_switch" ); + if (st && st[0]) + { + ((CBasePlayer*)pEntity)->m_iBackpackSwitch = atoi(st); + } + + g_pGameRules->ClientUserInfoChanged( GetClassPtr((CBasePlayer *)&pEntity->v), infobuffer ); + +} + +static int g_serveractive = 0; + +void ServerDeactivate( void ) +{ + // It's possible that the engine will call this function more times than is necessary + // Therefore, only run it one time for each call to ServerActivate + if ( g_serveractive != 1 ) + { + return; + } + + g_serveractive = 0; + + // Peform any shutdown operations here... + // +} + +void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) +{ + int i; + CBaseEntity *pClass; + + // Every call to ServerActivate should be matched by a call to ServerDeactivate + g_serveractive = 1; + + // Clients have not been initialized yet + for ( i = 0; i < edictCount; i++ ) + { + if ( pEdictList[i].free ) + continue; + + // Clients aren't necessarily initialized until ClientPutInServer() + if ( i < clientMax || !pEdictList[i].pvPrivateData ) + continue; + + pClass = CBaseEntity::Instance( &pEdictList[i] ); + // Activate this entity if it's got a class & isn't dormant + if ( pClass && !(pClass->pev->flags & FL_DORMANT) ) + { + pClass->Activate(); + } + else + { + ALERT( at_console, "Can't instance %s\n", STRING(pEdictList[i].v.classname) ); + } + } + + // Link user messages here to make sure first client can get them... + LinkUserMessages(); +} + + +/* +================ +PlayerPreThink + +Called every frame before physics are run +================ +*/ +void PlayerPreThink( edict_t *pEntity ) +{ + CBasePlayer *pPlayer = (CBasePlayer *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->PreThink( ); +} + +/* +================ +PlayerPostThink + +Called every frame after physics are run +================ +*/ +void PlayerPostThink( edict_t *pEntity ) +{ + CBasePlayer *pPlayer = (CBasePlayer *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->PostThink( ); +} + + + +void ParmsNewLevel( void ) +{ +} + + +void ParmsChangeLevel( void ) +{ + // retrieve the pointer to the save data + SAVERESTOREDATA *pSaveData = (SAVERESTOREDATA *)gpGlobals->pSaveData; + + if ( pSaveData ) + pSaveData->connectionCount = BuildChangeList( pSaveData->levelList, MAX_LEVEL_CONNECTIONS ); +} + + +// +// GLOBALS ASSUMED SET: g_ulFrameCount +// +void StartFrame( void ) +{ + if ( g_pGameRules ) + g_pGameRules->Think(); + + if ( g_fGameOver ) + return; + + gpGlobals->teamplay = CVAR_GET_FLOAT("teamplay"); + g_iSkillLevel = static_cast(CVAR_GET_FLOAT("skill")); + g_ulFrameCount++; +} + + +void ClientPrecache( void ) +{ + // setup precaches always needed + PRECACHE_SOUND("player/sprayer.wav"); // spray paint sound for PreAlpha + + // PRECACHE_SOUND("player/pl_jumpland2.wav"); // UNDONE: play 2x step sound + + PRECACHE_SOUND("player/pl_fallpain2.wav"); + PRECACHE_SOUND("player/pl_fallpain3.wav"); + + PRECACHE_SOUND("player/pl_step1.wav"); // walk on concrete + PRECACHE_SOUND("player/pl_step2.wav"); + PRECACHE_SOUND("player/pl_step3.wav"); + PRECACHE_SOUND("player/pl_step4.wav"); + + PRECACHE_SOUND("common/npc_step1.wav"); // NPC walk on concrete + PRECACHE_SOUND("common/npc_step2.wav"); + PRECACHE_SOUND("common/npc_step3.wav"); + PRECACHE_SOUND("common/npc_step4.wav"); + + PRECACHE_SOUND("player/pl_metal1.wav"); // walk on metal + PRECACHE_SOUND("player/pl_metal2.wav"); + PRECACHE_SOUND("player/pl_metal3.wav"); + PRECACHE_SOUND("player/pl_metal4.wav"); + + PRECACHE_SOUND("player/pl_dirt1.wav"); // walk on dirt + PRECACHE_SOUND("player/pl_dirt2.wav"); + PRECACHE_SOUND("player/pl_dirt3.wav"); + PRECACHE_SOUND("player/pl_dirt4.wav"); + + PRECACHE_SOUND("player/pl_duct1.wav"); // walk in duct + PRECACHE_SOUND("player/pl_duct2.wav"); + PRECACHE_SOUND("player/pl_duct3.wav"); + PRECACHE_SOUND("player/pl_duct4.wav"); + + PRECACHE_SOUND("player/pl_grate1.wav"); // walk on grate + PRECACHE_SOUND("player/pl_grate2.wav"); + PRECACHE_SOUND("player/pl_grate3.wav"); + PRECACHE_SOUND("player/pl_grate4.wav"); + + PRECACHE_SOUND("player/pl_slosh1.wav"); // walk in shallow water + PRECACHE_SOUND("player/pl_slosh2.wav"); + PRECACHE_SOUND("player/pl_slosh3.wav"); + PRECACHE_SOUND("player/pl_slosh4.wav"); + + PRECACHE_SOUND("player/pl_tile1.wav"); // walk on tile + PRECACHE_SOUND("player/pl_tile2.wav"); + PRECACHE_SOUND("player/pl_tile3.wav"); + PRECACHE_SOUND("player/pl_tile4.wav"); + PRECACHE_SOUND("player/pl_tile5.wav"); + + PRECACHE_SOUND("player/pl_swim1.wav"); // breathe bubbles + PRECACHE_SOUND("player/pl_swim2.wav"); + PRECACHE_SOUND("player/pl_swim3.wav"); + PRECACHE_SOUND("player/pl_swim4.wav"); + + PRECACHE_SOUND("player/pl_ladder1.wav"); // climb ladder rung + PRECACHE_SOUND("player/pl_ladder2.wav"); + PRECACHE_SOUND("player/pl_ladder3.wav"); + PRECACHE_SOUND("player/pl_ladder4.wav"); + + PRECACHE_SOUND("player/pl_wade1.wav"); // wade in water + PRECACHE_SOUND("player/pl_wade2.wav"); + PRECACHE_SOUND("player/pl_wade3.wav"); + PRECACHE_SOUND("player/pl_wade4.wav"); + + PRECACHE_SOUND("debris/wood1.wav"); // hit wood texture + PRECACHE_SOUND("debris/wood2.wav"); + PRECACHE_SOUND("debris/wood3.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( SOUND_FLASHLIGHT_ON ); + PRECACHE_SOUND( SOUND_FLASHLIGHT_OFF ); + +// player gib sounds + PRECACHE_SOUND("common/bodysplat.wav"); + +// player pain sounds + PRECACHE_SOUND("player/pain1.wav"); + PRECACHE_SOUND("player/pain2.wav"); + PRECACHE_SOUND("player/pain3.wav"); + PRECACHE_SOUND("player/pain4.wav"); + PRECACHE_SOUND("player/pain5.wav"); + PRECACHE_SOUND("player/pain6.wav"); + PRECACHE_SOUND("player/drown1.wav"); + PRECACHE_SOUND("player/drown2.wav"); + PRECACHE_SOUND("player/lburn1.wav"); + PRECACHE_SOUND("player/lburn2.wav"); + PRECACHE_SOUND("player/death1.wav"); + PRECACHE_SOUND("player/death2.wav"); + PRECACHE_SOUND("player/death3.wav"); + PRECACHE_SOUND("player/death4.wav"); + PRECACHE_SOUND("player/death5.wav"); + + PRECACHE_SOUND("player/h2odeath.wav"); + + PRECACHE_MODEL("models/player.mdl"); + + // hud sounds + + 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"); + + PRECACHE_SOUND("player/gib.wav"); + + PRECACHE_MODEL("models/gib_1.mdl"); + PRECACHE_MODEL("models/gib_2.mdl"); + PRECACHE_MODEL("models/gib_3.mdl"); + + PRECACHE_SOUND("player/plyrjmp8.wav"); + + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/p_crowbar.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/p_light.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/p_nail.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/p_nail2.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/p_rock.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/p_rock2.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/p_shot.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/p_shot2.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/spike.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/rocket.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/grenade.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/backpack.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/backpack.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/armor_g.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/armor_r.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/armor_y.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/armor_y.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/b_nail0.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/b_nail1.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/g_light.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/g_nail.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/g_nail2.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/g_rock.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/g_rock2.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/g_shot2.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/pow_invis.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/pow_quad.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/pow_invuln.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/suit.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/w_battery.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/w_batteryl.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/w_medkit.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/w_medkitl.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/w_medkits.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/w_rpgammo.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/w_rpgammo_big.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/w_shotbox.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/w_shotbox_big.mdl"); + + g_sGibbed = PRECACHE_EVENT( 1, "events/gibs.sc" ); + g_sTeleport = PRECACHE_EVENT( 1, "events/teleport.sc" ); + g_sTrail = PRECACHE_EVENT( 1, "events/trail.sc" ); + g_sExplosion = PRECACHE_EVENT( 1, "events/explosion.sc" ); + g_usPowerUp = PRECACHE_EVENT( 1, "events/powerup.sc" ); + + if (giPrecacheGrunt) + UTIL_PrecacheOther("monster_human_grunt"); +} + +/* +================ +Sys_Error + +Engine is going to shut down, allows setting a breakpoint in game .dll to catch that occasion +================ +*/ +void Sys_Error( const char *error_string ) +{ + // Default case, do nothing. MOD AUTHORS: Add code ( e.g., _asm { int 3 }; here to cause a breakpoint for debugging your game .dlls +} + +/* +=============== +const char *GetGameDescription() + +Returns the descriptive name of this .dll. E.g., Half-Life, or Team Fortress 2 +=============== +*/ +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 "DMC"; +} + +/* +================ +PlayerCustomization + +A new player customization has been registered on the server +UNDONE: This only sets the # of frames of the spray can logo +animation right now. +================ +*/ +void PlayerCustomization( edict_t *pEntity, customization_t *pCust ) +{ + CBasePlayer *pPlayer = (CBasePlayer *)GET_PRIVATE(pEntity); + + if (!pPlayer) + { + ALERT(at_console, "PlayerCustomization: Couldn't get player!\n"); + return; + } + + if (!pCust) + { + ALERT(at_console, "PlayerCustomization: NULL customization!\n"); + return; + } + + switch (pCust->resource.type) + { + case t_decal: + pPlayer->SetCustomDecalFrames(pCust->nUserData2); // Second int is max # of frames. + break; + case t_sound: + case t_skin: + case t_model: + // Ignore for now. + break; + default: + ALERT(at_console, "PlayerCustomization: Unknown customization type!\n"); + break; + } +} + +/* +================ +SpectatorConnect + +A spectator has joined the game +================ +*/ +void SpectatorConnect( edict_t *pEntity ) +{ + CBaseSpectator *pPlayer = (CBaseSpectator *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->SpectatorConnect( ); +} + +/* +================ +SpectatorConnect + +A spectator has left the game +================ +*/ +void SpectatorDisconnect( edict_t *pEntity ) +{ + CBaseSpectator *pPlayer = (CBaseSpectator *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->SpectatorDisconnect( ); +} + +/* +================ +SpectatorConnect + +A spectator has sent a usercmd +================ +*/ +void SpectatorThink( edict_t *pEntity ) +{ + CBaseSpectator *pPlayer = (CBaseSpectator *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->SpectatorThink( ); +} + +//////////////////////////////////////////////////////// +// PAS and PVS routines for client messaging +// + +/* +================ +SetupVisibility + +A client can have a separate "view entity" indicating that his/her view should depend on the origin of that +view entity. If that's the case, then pViewEntity will be non-NULL and will be used. Otherwise, the current +entity's origin is used. Either is offset by the view_ofs to get the eye position. + +From the eye position, we set up the PAS and PVS to use for filtering network messages to the client. At this point, we could + override the actual PAS or PVS values, or use a different origin. + +NOTE: Do not cache the values of pas and pvs, as they depend on reusable memory in the engine, they are only good for this one frame +================ +*/ +void SetupVisibility( edict_t *pViewEntity, edict_t *pClient, unsigned char **pvs, unsigned char **pas ) +{ + Vector org; + edict_t *pView = pClient; + + // Find the client's PVS + if ( pViewEntity ) + { + pView = pViewEntity; + } + + if ( pClient->v.flags & FL_PROXY ) + { + *pvs = NULL; // the spectator proxy sees + *pas = NULL; // and hears everything + return; + } + org = pView->v.origin + pView->v.view_ofs; + if ( pView->v.flags & FL_DUCKING ) + { + org = org + ( VEC_HULL_MIN - VEC_DUCK_HULL_MIN ); + } + + *pvs = ENGINE_SET_PVS ( (float *)&org ); + *pas = ENGINE_SET_PAS ( (float *)&org ); +} + +#include "entity_state.h" + +/* +AddToFullPack + +Return 1 if the entity state has been filled in for the ent and the entity will be propagated to the client, 0 otherwise + +state is the server maintained copy of the state info that is transmitted to the client +a MOD could alter values copied into state to send the "host" a different look for a particular entity update, etc. +e and ent are the entity that is being added to the update, if 1 is returned +host is the player's edict of the player whom we are sending the update to +player is 1 if the ent/e is a player and 0 otherwise +pSet is either the PAS or PVS that we previous set up. We can use it to ask the engine to filter the entity against the PAS or PVS. +we could also use the pas/ pvs that we set in SetupVisibility, if we wanted to. Caching the value is valid in that case, but still only for the current frame +*/ +int AddToFullPack( struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet ) +{ + int i; + + // don't send if flagged for NODRAW and it's not the host getting the message + if ( ( ent->v.effects == EF_NODRAW ) && + ( ent != host ) ) + return 0; + + // Ignore ents without valid / visible models + if ( !ent->v.modelindex || !STRING( ent->v.model ) ) + return 0; + + // Don't send spectators to other players + if ( ( ent->v.flags & FL_SPECTATOR ) && ( ent != host ) ) + { + return 0; + } + + // Ignore if not the host and not touching a PVS/PAS leaf + // If pSet is NULL, then the test will always succeed and the entity will be added to the update + if ( ent != host ) + { + //We still want to show all ents for a quick period ( max 700ms of lag ) for when we predict teleporters + //if we don't do this, the entities on the other side wont show until the real origin comes thru and reaches + //the new PVS/PAS. + if ( !ENGINE_CHECK_VISIBILITY( (const struct edict_s *)ent, pSet ) && ent->v.fuser4 < gpGlobals->time ) + { + return 0; + } + } + + + // Don't send entity to local client if the client says it's predicting the entity itself. + if ( ent->v.flags & FL_SKIPLOCALHOST ) + { + if ( ( hostflags & 1 ) && ( ent->v.owner == host ) ) + return 0; + } + + if ( host->v.groupinfo ) + { + UTIL_SetGroupTrace( host->v.groupinfo, GROUP_OP_AND ); + + // Should always be set, of course + if ( ent->v.groupinfo ) + { + if ( g_groupop == GROUP_OP_AND ) + { + if ( !(ent->v.groupinfo & host->v.groupinfo ) ) + return 0; + } + else if ( g_groupop == GROUP_OP_NAND ) + { + if ( ent->v.groupinfo & host->v.groupinfo ) + return 0; + } + } + + UTIL_UnsetGroupTrace(); + } + + memset( state, 0, sizeof( *state ) ); + + // Assign index so we can track this entity from frame to frame and + // delta from it. + state->number = e; + state->entityType = ENTITY_NORMAL; + + // Flag custom entities. + if ( ent->v.flags & FL_CUSTOMENTITY ) + { + state->entityType = ENTITY_BEAM; + } + + // + // Copy state data + // + + // Round animtime to nearest millisecond + state->animtime = (int)(1000.0 * ent->v.animtime ) / 1000.0; + + memcpy( state->origin, ent->v.origin, 3 * sizeof( float ) ); + memcpy( state->angles, ent->v.angles, 3 * sizeof( float ) ); + memcpy( state->mins, ent->v.mins, 3 * sizeof( float ) ); + memcpy( state->maxs, ent->v.maxs, 3 * sizeof( float ) ); + + memcpy( state->startpos, ent->v.startpos, 3 * sizeof( float ) ); + memcpy( state->endpos, ent->v.endpos, 3 * sizeof( float ) ); + + state->impacttime = ent->v.impacttime; + state->starttime = ent->v.starttime; + + state->modelindex = ent->v.modelindex; + + state->frame = ent->v.frame; + state->skin = ent->v.skin; + state->effects = ent->v.effects; + + // This non-player entity is being moved by the game .dll and not the physics simulation system + // make sure that we interpolate it's position on the client if it moves + if ( !player && + ent->v.animtime && + ent->v.velocity[ 0 ] == 0 && + ent->v.velocity[ 1 ] == 0 && + ent->v.velocity[ 2 ] == 0 ) + { + state->eflags |= EFLAG_SLERP; + } + + state->scale = ent->v.scale; + state->solid = ent->v.solid; + state->colormap = ent->v.colormap; + state->movetype = ent->v.movetype; + state->sequence = ent->v.sequence; + state->framerate = ent->v.framerate; + state->body = ent->v.body; + + for (i = 0; i < 4; i++) + { + state->controller[i] = ent->v.controller[i]; + } + + for (i = 0; i < 2; i++) + { + state->blending[i] = ent->v.blending[i]; + } + + state->rendermode = ent->v.rendermode; + state->renderamt = static_cast(ent->v.renderamt); + state->renderfx = ent->v.renderfx; + state->rendercolor.r = static_cast(ent->v.rendercolor[0]); + state->rendercolor.g = static_cast(ent->v.rendercolor[1]); + state->rendercolor.b = static_cast(ent->v.rendercolor[2]); + + state->aiment = 0; + if ( ent->v.aiment ) + { + state->aiment = ENTINDEX( ent->v.aiment ); + } + + state->owner = 0; + if ( ent->v.owner ) + { + int owner = ENTINDEX( ent->v.owner ); + + // Only care if owned by a player + if ( owner >= 1 && owner <= gpGlobals->maxClients ) + { + state->owner = owner; + } + } + // Special stuff for players only + if ( player ) + { + memcpy( state->basevelocity, ent->v.basevelocity, 3 * sizeof( float ) ); + + state->weaponmodel = MODEL_INDEX( STRING( ent->v.weaponmodel ) ); + state->gaitsequence = ent->v.gaitsequence; + state->spectator = ent->v.flags & FL_SPECTATOR; + state->friction = ent->v.friction; + + state->gravity = ent->v.gravity; +// state->team = ent->v.team; +// state->playerclass = ent->v.playerclass; + state->usehull = ( ent->v.flags & FL_DUCKING ) ? 1 : 0; + state->health = static_cast(ent->v.health); + } + + return 1; +} + +// defaults for clientinfo messages +#define DEFAULT_VIEWHEIGHT 28 + +/* +=================== +CreateBaseline + +Creates baselines used for network encoding, especially for player data since players are not spawned until connect time. +=================== +*/ +void CreateBaseline( int player, int eindex, struct entity_state_s *baseline, struct edict_s *entity, int playermodelindex, vec3_t player_mins, vec3_t player_maxs ) +{ + baseline->origin = entity->v.origin; + baseline->angles = entity->v.angles; + baseline->frame = entity->v.frame; + baseline->skin = (short)entity->v.skin; + + // render information + baseline->rendermode = (byte)entity->v.rendermode; + baseline->renderamt = (byte)entity->v.renderamt; + baseline->rendercolor.r = (byte)entity->v.rendercolor[0]; + baseline->rendercolor.g = (byte)entity->v.rendercolor[1]; + baseline->rendercolor.b = (byte)entity->v.rendercolor[2]; + baseline->renderfx = (byte)entity->v.renderfx; + + if ( player ) + { + baseline->mins = player_mins; + baseline->maxs = player_maxs; + + baseline->colormap = eindex; + baseline->modelindex = playermodelindex; + baseline->friction = 1.0; + baseline->movetype = MOVETYPE_WALK; + + baseline->scale = entity->v.scale; + baseline->solid = SOLID_SLIDEBOX; + baseline->framerate = 1.0; + baseline->gravity = 1.0; + + } + else + { + baseline->mins = entity->v.mins; + baseline->maxs = entity->v.maxs; + + baseline->colormap = 0; + baseline->modelindex = entity->v.modelindex;//SV_ModelIndex(pr_strings + entity->v.model); + baseline->movetype = entity->v.movetype; + + baseline->scale = entity->v.scale; + baseline->solid = entity->v.solid; + baseline->framerate = entity->v.framerate; + baseline->gravity = entity->v.gravity; + } +} + +typedef struct +{ + char name[32]; + int field; +} entity_field_alias_t; + +#define FIELD_ORIGIN0 0 +#define FIELD_ORIGIN1 1 +#define FIELD_ORIGIN2 2 +#define FIELD_ANGLES0 3 +#define FIELD_ANGLES1 4 +#define FIELD_ANGLES2 5 + +static entity_field_alias_t entity_field_alias[]= +{ + { "origin[0]", 0 }, + { "origin[1]", 0 }, + { "origin[2]", 0 }, + { "angles[0]", 0 }, + { "angles[1]", 0 }, + { "angles[2]", 0 }, +}; + +void Entity_FieldInit( struct delta_s *pFields ) +{ + entity_field_alias[ FIELD_ORIGIN0 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ORIGIN0 ].name ); + entity_field_alias[ FIELD_ORIGIN1 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ORIGIN1 ].name ); + entity_field_alias[ FIELD_ORIGIN2 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ORIGIN2 ].name ); + entity_field_alias[ FIELD_ANGLES0 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ANGLES0 ].name ); + entity_field_alias[ FIELD_ANGLES1 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ANGLES1 ].name ); + entity_field_alias[ FIELD_ANGLES2 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ANGLES2 ].name ); +} + +/* +================== +Entity_Encode + +Callback for sending entity_state_t info over network. +FIXME: Move to script +================== +*/ +void Entity_Encode( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) +{ + entity_state_t *f, *t; + + int localplayer = 0; + static int initialized = 0; + + if ( !initialized ) + { + Entity_FieldInit( pFields ); + initialized = 1; + } + + f = (entity_state_t *)from; + t = (entity_state_t *)to; + + // Never send origin to local player, it's sent with more resolution in clientdata_t structure + localplayer = ( t->number - 1 ) == ENGINE_CURRENT_PLAYER(); + if ( localplayer ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + + if ( ( t->impacttime != 0 ) && ( t->starttime != 0 ) ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ANGLES0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ANGLES1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ANGLES2 ].field ); + } + + if ( ( t->movetype == MOVETYPE_FOLLOW ) && + ( t->aiment != 0 ) ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + else if ( t->aiment != f->aiment ) + { + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } +} + +static entity_field_alias_t player_field_alias[]= +{ + { "origin[0]", 0 }, + { "origin[1]", 0 }, + { "origin[2]", 0 }, +}; + +void Player_FieldInit( struct delta_s *pFields ) +{ + player_field_alias[ FIELD_ORIGIN0 ].field = DELTA_FINDFIELD( pFields, player_field_alias[ FIELD_ORIGIN0 ].name ); + player_field_alias[ FIELD_ORIGIN1 ].field = DELTA_FINDFIELD( pFields, player_field_alias[ FIELD_ORIGIN1 ].name ); + player_field_alias[ FIELD_ORIGIN2 ].field = DELTA_FINDFIELD( pFields, player_field_alias[ FIELD_ORIGIN2 ].name ); +} + +/* +================== +Player_Encode + +Callback for sending entity_state_t for players info over network. +================== +*/ +void Player_Encode( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) +{ + entity_state_t *f, *t; + int localplayer = 0; + static int initialized = 0; + + if ( !initialized ) + { + Player_FieldInit( pFields ); + initialized = 1; + } + + f = (entity_state_t *)from; + t = (entity_state_t *)to; + + // Never send origin to local player, it's sent with more resolution in clientdata_t structure + localplayer = ( t->number - 1 ) == ENGINE_CURRENT_PLAYER(); + if ( localplayer ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + + if ( ( t->movetype == MOVETYPE_FOLLOW ) && + ( t->aiment != 0 ) ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + else if ( t->aiment != f->aiment ) + { + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } +} + +#define CUSTOMFIELD_ORIGIN0 0 +#define CUSTOMFIELD_ORIGIN1 1 +#define CUSTOMFIELD_ORIGIN2 2 +#define CUSTOMFIELD_ANGLES0 3 +#define CUSTOMFIELD_ANGLES1 4 +#define CUSTOMFIELD_ANGLES2 5 +#define CUSTOMFIELD_SKIN 6 +#define CUSTOMFIELD_SEQUENCE 7 +#define CUSTOMFIELD_ANIMTIME 8 + +entity_field_alias_t custom_entity_field_alias[]= +{ + { "origin[0]", 0 }, + { "origin[1]", 0 }, + { "origin[2]", 0 }, + { "angles[0]", 0 }, + { "angles[1]", 0 }, + { "angles[2]", 0 }, + { "skin", 0 }, + { "sequence", 0 }, + { "animtime", 0 }, +}; + +void Custom_Entity_FieldInit( struct delta_s *pFields ) +{ + custom_entity_field_alias[ CUSTOMFIELD_ORIGIN0 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN0 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ORIGIN1 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN1 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ORIGIN2 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN2 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANGLES0 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES0 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANGLES1 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES1 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANGLES2 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES2 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_SKIN ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_SKIN ].name ); + custom_entity_field_alias[ CUSTOMFIELD_SEQUENCE ].field= DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_SEQUENCE ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANIMTIME ].field= DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANIMTIME ].name ); +} + +/* +================== +Custom_Encode + +Callback for sending entity_state_t info ( for custom entities ) over network. +FIXME: Move to script +================== +*/ +void Custom_Encode( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) +{ + entity_state_t *f, *t; + int beamType; + static int initialized = 0; + + if ( !initialized ) + { + Custom_Entity_FieldInit( pFields ); + initialized = 1; + } + + f = (entity_state_t *)from; + t = (entity_state_t *)to; + + beamType = t->rendermode & 0x0f; + + if ( beamType != BEAM_POINTS && beamType != BEAM_ENTPOINT ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN2 ].field ); + } + + if ( beamType != BEAM_POINTS ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES0 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES1 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES2 ].field ); + } + + if ( beamType != BEAM_ENTS && beamType != BEAM_ENTPOINT ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_SKIN ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_SEQUENCE ].field ); + } + + // animtime is compared by rounding first + // see if we really shouldn't actually send it + if ( (int)f->animtime == (int)t->animtime ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANIMTIME ].field ); + } +} + +/* +================= +RegisterEncoders + +Allows game .dll to override network encoding of certain types of entities and tweak values, etc. +================= +*/ +void RegisterEncoders( void ) +{ + DELTA_ADDENCODER( "Entity_Encode", Entity_Encode ); + DELTA_ADDENCODER( "Custom_Encode", Custom_Encode ); + DELTA_ADDENCODER( "Player_Encode", Player_Encode ); +} + +int GetWeaponData( struct edict_s *player, struct weapon_data_s *info ) +{ + int i; + weapon_data_t *item; + entvars_t *pev = &player->v; + CBasePlayer *pl = ( CBasePlayer *) CBasePlayer::Instance( pev ); + CBasePlayerWeapon *gun; + + ItemInfo II; + + memset( info, 0, 32 * sizeof( weapon_data_t ) ); + + if ( !pl ) + return 1; + + // go through all of the weapons and make a list of the ones to pack + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( pl->m_rgpPlayerItems[ i ] ) + { + // there's a weapon here. Should I pack it? + CBasePlayerItem *pPlayerItem = pl->m_rgpPlayerItems[ i ]; + + while ( pPlayerItem ) + { + gun = (CBasePlayerWeapon *)pPlayerItem->GetWeaponPtr(); + if ( gun && gun->UseDecrement() ) + { + // Get The ID. + memset( &II, 0, sizeof( II ) ); + gun->GetItemInfo( &II ); + + if ( II.iId >= 0 && II.iId < 32 ) + { + item = &info[ II.iId ]; + + item->m_iId = II.iId; + item->m_iClip = gun->m_iClip; + + item->m_flTimeWeaponIdle = max( gun->m_flTimeWeaponIdle, -0.001 ); + item->m_flNextPrimaryAttack = max( gun->m_flNextPrimaryAttack, -0.001 ); + item->m_flNextSecondaryAttack = max( gun->m_flNextSecondaryAttack, -0.001 ); + item->m_fInReload = gun->m_fInReload; + } + } + pPlayerItem = pPlayerItem->m_pNext; + } + } + } + return 1; +} + +/* +================= +UpdateClientData + +Data sent to current client only +engine sets cd to 0 before calling. +================= +*/ +void UpdateClientData ( const struct edict_s *ent, int sendweapons, struct clientdata_s *cd ) +{ + cd->flags = ent->v.flags; + cd->health = ent->v.health; + + cd->viewmodel = MODEL_INDEX( STRING( ent->v.viewmodel ) ); + cd->waterlevel = ent->v.waterlevel; + cd->watertype = ent->v.watertype; + //cd->weapons = ent->v.weapons; + + // Vectors + cd->origin = ent->v.origin; + cd->velocity = ent->v.velocity; + cd->view_ofs = ent->v.view_ofs; + cd->punchangle = ent->v.punchangle; + + cd->bInDuck = ent->v.bInDuck; + cd->flTimeStepSound = ent->v.flTimeStepSound; + cd->flDuckTime = ent->v.flDuckTime; + cd->flSwimTime = ent->v.flSwimTime; + cd->waterjumptime = static_cast(ent->v.teleport_time); + + strcpy( cd->physinfo, ENGINE_GETPHYSINFO( ent ) ); + + cd->maxspeed = ent->v.maxspeed; + cd->fov = ent->v.fov; + cd->weaponanim = ent->v.weaponanim; + + cd->pushmsec = ent->v.pushmsec; + + // Observer + cd->iuser1 = ent->v.iuser1; + cd->iuser2 = ent->v.iuser2; + cd->iuser3 = ent->v.iuser3; + + if ( sendweapons ) + { + entvars_t *pev = (entvars_t *)&ent->v; + CBasePlayer *pl = ( CBasePlayer *) CBasePlayer::Instance( pev ); + + if ( pl ) + { + cd->m_flNextAttack = pl->m_flNextAttack; + cd->weapons = pl->m_iQuakeItems; + + if ( pl->m_pActiveItem ) + { + CBasePlayerWeapon *gun; + gun = (CBasePlayerWeapon *)pl->m_pActiveItem->GetWeaponPtr(); + if ( gun && gun->UseDecrement() ) + { + ItemInfo II; + memset( &II, 0, sizeof( II ) ); + gun->GetItemInfo( &II ); + + cd->m_iId = II.iId; + } + } + + cd->fuser1 = (float)pl->m_iQuakeWeapon; + cd->iuser4 = static_cast(gpGlobals->deathmatch); + cd->fuser2 = pl->m_iNailOffset > 0 ? 1.0 : 0.0; + + cd->iuser3 = pl->m_iQuakeItems; + + cd->ammo_shells = pl->m_iAmmoShells; + cd->ammo_nails = pl->m_iAmmoNails; + cd->ammo_cells = pl->m_iAmmoCells; + cd->ammo_rockets = pl->m_iAmmoRockets; + } + } +} + +/* +================= +CmdStart + +We're about to run this usercmd for the specified player. We can set up groupinfo and masking here, etc. +This is the time to examine the usercmd for anything extra. This call happens even if think does not. +================= +*/ +void CmdStart( const edict_t *player, const struct usercmd_s *cmd, unsigned int random_seed ) +{ + entvars_t *pev = (entvars_t *)&player->v; + CBasePlayer *pl = ( CBasePlayer *) CBasePlayer::Instance( pev ); + + if( !pl ) + return; + + if ( cmd->weaponselect != 0 ) + { + usercmd_t *c = (usercmd_t *)cmd; + pl->W_ChangeWeapon( c->weaponselect ); + c->weaponselect = 0; + } + + if ( pl->pev->groupinfo != 0 ) + { + UTIL_SetGroupTrace( pl->pev->groupinfo, GROUP_OP_AND ); + } + + pl->random_seed = random_seed; +} + +/* +================= +CmdEnd + +Each cmdstart is exactly matched with a cmd end, clean up any group trace flags, etc. here +================= +*/ +void CmdEnd ( const edict_t *player ) +{ + entvars_t *pev = (entvars_t *)&player->v; + CBasePlayer *pl = ( CBasePlayer *) CBasePlayer::Instance( pev ); + + if( !pl ) + return; + if ( pl->pev->groupinfo != 0 ) + { + UTIL_UnsetGroupTrace(); + } +} + +/* +================================ +ConnectionlessPacket + + Return 1 if the packet is valid. Set response_buffer_size if you want to send a response packet. Incoming, it holds the max + size of the response_buffer, so you must zero it out if you choose not to respond. +================================ +*/ +int ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ) +{ + // Zero it out since we aren't going to respond. + // If we wanted to response, we'd write data into response_buffer + *response_buffer_size = 0; + + // Since we don't listen for anything here, just respond that it's a bogus message + // If we didn't reject the message, we'd return 1 for success instead. + return 0; +} + +/* +================================ +GetHullBounds + + Engine calls this to enumerate player collision hulls, for prediction. Return 0 if the hullnumber doesn't exist. +================================ +*/ +int GetHullBounds( int hullnumber, float *mins, float *maxs ) +{ + int iret = 0; + + switch ( hullnumber ) + { + case 0: // Normal player + mins = Vector(-16, -16, -32); + maxs = Vector(16, 16, 32); + iret = 1; + break; + case 1: // Crouched player + mins = Vector(-16, -16, -32); + maxs = Vector(16, 16, 32); + iret = 1; + break; + case 2: // Point based hull + mins = Vector( 0, 0, 0 ); + maxs = Vector( 0, 0, 0 ); + iret = 1; + break; + } + + return iret; +} + +/* +================================ +CreateInstancedBaselines + +Create pseudo-baselines for items that aren't placed in the map at spawn time, but which are likely +to be created during play ( e.g., grenades, ammo packs, projectiles, corpses, etc. ) +================================ +*/ +void CreateInstancedBaselines ( void ) +{ + entity_state_t state; + + memset( &state, 0, sizeof( state ) ); + + // Create any additional baselines here for things like grendates, etc. + // iret = ENGINE_INSTANCE_BASELINE( pc->pev->classname, &state ); + + // Destroy objects. + //UTIL_Remove( pc ); +} + +/* +================================ +InconsistentFile + +One of the ENGINE_FORCE_UNMODIFIED files failed the consistency check for the specified player + Return 0 to allow the client to continue, 1 to force immediate disconnection ( with an optional disconnect message of up to 256 characters ) +================================ +*/ +int InconsistentFile( const edict_t *player, const char *filename, char *disconnect_message ) +{ + // Server doesn't care? + if ( CVAR_GET_FLOAT( "mp_consistency" ) != 1 ) + return 0; + + // Default behavior is to kick the player + sprintf( disconnect_message, "Server is enforcing file consistency for %s\n", filename ); + + // Kick now with specified disconnect message. + return 1; +} + +/* +================================ +AllowLagCompensation + + The game .dll should return 1 if lag compensation should be allowed ( could also just set + the sv_unlag cvar. + Most games right now should return 0, until client-side weapon prediction code is written + and tested for them ( note you can predict weapons, but not do lag compensation, too, + if you want. +================================ +*/ +int AllowLagCompensation( void ) +{ + return 0; +} + diff --git a/hlsdk/dmc/dlls/client.h b/hlsdk/dmc/dlls/client.h new file mode 100644 index 00000000..1e66cc89 --- /dev/null +++ b/hlsdk/dmc/dlls/client.h @@ -0,0 +1,65 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef CLIENT_H +#define CLIENT_H + +extern void respawn( entvars_t* pev, BOOL fCopyCorpse ); +extern BOOL ClientConnect( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); +extern void ClientDisconnect( edict_t *pEntity ); +extern void ClientKill( edict_t *pEntity ); +extern void ClientPutInServer( edict_t *pEntity ); +extern void ClientCommand( edict_t *pEntity ); +extern void ClientUserInfoChanged( edict_t *pEntity, char *infobuffer ); +extern void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ); +extern void ServerDeactivate( void ); +extern void StartFrame( void ); +extern void PlayerPostThink( edict_t *pEntity ); +extern void PlayerPreThink( edict_t *pEntity ); +extern void ParmsNewLevel( void ); +extern void ParmsChangeLevel( void ); + +extern void ClientPrecache( void ); + +extern const char *GetGameDescription( void ); +extern void PlayerCustomization( edict_t *pEntity, customization_t *pCust ); + +extern void SpectatorConnect ( edict_t *pEntity ); +extern void SpectatorDisconnect ( edict_t *pEntity ); +extern void SpectatorThink ( edict_t *pEntity ); + +extern void Sys_Error( const char *error_string ); + +extern void SetupVisibility( edict_t *pViewEntity, edict_t *pClient, unsigned char **pvs, unsigned char **pas ); +extern void UpdateClientData ( const struct edict_s *ent, int sendweapons, struct clientdata_s *cd ); +extern int AddToFullPack( struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet ); +extern void CreateBaseline( int player, int eindex, struct entity_state_s *baseline, struct edict_s *entity, int playermodelindex, vec3_t player_mins, vec3_t player_maxs ); +extern void RegisterEncoders( void ); + +extern int GetWeaponData( struct edict_s *player, struct weapon_data_s *info ); + +extern void CmdStart( const edict_t *player, const struct usercmd_s *cmd, unsigned int random_seed ); +extern void CmdEnd ( const edict_t *player ); + +extern int ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); + +extern int GetHullBounds( int hullnumber, float *mins, float *maxs ); + +extern void CreateInstancedBaselines ( void ); + +extern int InconsistentFile( const edict_t *player, const char *filename, char *disconnect_message ); + +extern int AllowLagCompensation( void ); + +#endif // CLIENT_H diff --git a/hlsdk/dmc/dlls/combat.cpp b/hlsdk/dmc/dlls/combat.cpp new file mode 100644 index 00000000..a1cf343d --- /dev/null +++ b/hlsdk/dmc/dlls/combat.cpp @@ -0,0 +1,1099 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== combat.cpp ======================================================== + + functions dealing with damage infliction & death + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "soundent.h" +#include "decals.h" +#include "animation.h" +#include "weapons.h" +#include "func_break.h" +#include "player.h" + +extern DLL_GLOBAL Vector g_vecAttackDir; +extern DLL_GLOBAL int g_iSkillLevel; + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); +extern entvars_t *g_pevLastInflictor; + +unsigned short g_sGibbed; +unsigned short g_sTeleport; +unsigned short g_sTrail; +unsigned short g_sExplosion; +unsigned short g_usPowerUp; + +#define GERMAN_GIB_COUNT 4 +#define HUMAN_GIB_COUNT 6 +#define ALIEN_GIB_COUNT 4 + + +// HACKHACK -- The gib velocity equations don't work +void CGib :: LimitVelocity( void ) +{ + float length = pev->velocity.Length(); + + // ceiling at 1500. The gib velocity equation is not bounded properly. Rather than tune it + // in 3 separate places again, I'll just limit it here. + if ( length > 1500.0 ) + pev->velocity = pev->velocity.Normalize() * 1500; // This should really be sv_maxvelocity * 0.75 or something +} + + +void CGib :: SpawnStickyGibs( entvars_t *pevVictim, Vector vecOrigin, int cGibs ) +{ + int i; + + if ( g_Language == LANGUAGE_GERMAN ) + { + // no sticky gibs in germany right now! + return; + } + + for ( i = 0 ; i < cGibs ; i++ ) + { + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + pGib->Spawn( "models/stickygib.mdl" ); + pGib->pev->body = RANDOM_LONG(0,2); + + if ( pevVictim ) + { + pGib->pev->origin.x = vecOrigin.x + RANDOM_FLOAT( -3, 3 ); + pGib->pev->origin.y = vecOrigin.y + RANDOM_FLOAT( -3, 3 ); + pGib->pev->origin.z = vecOrigin.z + RANDOM_FLOAT( -3, 3 ); + + /* + pGib->pev->origin.x = pevVictim->absmin.x + pevVictim->size.x * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.y = pevVictim->absmin.y + pevVictim->size.y * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.z = pevVictim->absmin.z + pevVictim->size.z * (RANDOM_FLOAT ( 0 , 1 ) ); + */ + + // make the gib fly away from the attack vector + pGib->pev->velocity = g_vecAttackDir * -1; + + // mix in some noise + pGib->pev->velocity.x += RANDOM_FLOAT ( -0.15, 0.15 ); + pGib->pev->velocity.y += RANDOM_FLOAT ( -0.15, 0.15 ); + pGib->pev->velocity.z += RANDOM_FLOAT ( -0.15, 0.15 ); + + pGib->pev->velocity = pGib->pev->velocity * 900; + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 250, 400 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 250, 400 ); + + // copy owner's blood color + pGib->m_bloodColor = (CBaseEntity::Instance(pevVictim))->BloodColor(); + + if ( pevVictim->health > -50) + { + pGib->pev->velocity = pGib->pev->velocity * 0.7; + } + else if ( pevVictim->health > -200) + { + pGib->pev->velocity = pGib->pev->velocity * 2; + } + else + { + pGib->pev->velocity = pGib->pev->velocity * 4; + } + + + pGib->pev->movetype = MOVETYPE_TOSS; + pGib->pev->solid = SOLID_BBOX; + UTIL_SetSize ( pGib->pev, Vector ( 0, 0 ,0 ), Vector ( 0, 0, 0 ) ); + pGib->SetTouch ( &CGib::StickyGibTouch ); + pGib->SetThink (NULL); + } + pGib->LimitVelocity(); + } +} + +void CGib :: SpawnHeadGib( entvars_t *pevVictim ) +{ + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + if ( g_Language == LANGUAGE_GERMAN ) + { + pGib->Spawn( "models/germangibs.mdl" );// throw one head + pGib->pev->body = 0; + } + else + { + pGib->Spawn( "models/hgibs.mdl" );// throw one head + pGib->pev->body = 0; + } + + if ( pevVictim ) + { + pGib->pev->origin = pevVictim->origin + pevVictim->view_ofs; + + edict_t *pentPlayer = FIND_CLIENT_IN_PVS( pGib->edict() ); + + if ( RANDOM_LONG ( 0, 100 ) <= 5 && pentPlayer ) + { + // 5% chance head will be thrown at player's face. + entvars_t *pevPlayer; + + pevPlayer = VARS( pentPlayer ); + pGib->pev->velocity = ( ( pevPlayer->origin + pevPlayer->view_ofs ) - pGib->pev->origin ).Normalize() * 300; + pGib->pev->velocity.z += 100; + } + else + { + pGib->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + } + + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + + // copy owner's blood color + pGib->m_bloodColor = (CBaseEntity::Instance(pevVictim))->BloodColor(); + + if ( pevVictim->health > -50) + { + pGib->pev->velocity = pGib->pev->velocity * 0.7; + } + else if ( pevVictim->health > -200) + { + pGib->pev->velocity = pGib->pev->velocity * 2; + } + else + { + pGib->pev->velocity = pGib->pev->velocity * 4; + } + } + pGib->LimitVelocity(); +} + +void CGib :: SpawnRandomGibs( entvars_t *pevVictim, int cGibs, int human ) +{ + int cSplat; + + for ( cSplat = 0 ; cSplat < cGibs ; cSplat++ ) + { + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + if ( g_Language == LANGUAGE_GERMAN ) + { + pGib->Spawn( "models/germangibs.mdl" ); + pGib->pev->body = RANDOM_LONG(0,GERMAN_GIB_COUNT-1); + } + else + { + if ( human ) + { + // human pieces + pGib->Spawn( "models/hgibs.mdl" ); + pGib->pev->body = RANDOM_LONG(1,HUMAN_GIB_COUNT-1);// start at one to avoid throwing random amounts of skulls (0th gib) + } + else + { + // aliens + pGib->Spawn( "models/agibs.mdl" ); + pGib->pev->body = RANDOM_LONG(0,ALIEN_GIB_COUNT-1); + } + } + + if ( pevVictim ) + { + // spawn the gib somewhere in the monster's bounding volume + pGib->pev->origin.x = pevVictim->absmin.x + pevVictim->size.x * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.y = pevVictim->absmin.y + pevVictim->size.y * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.z = pevVictim->absmin.z + pevVictim->size.z * (RANDOM_FLOAT ( 0 , 1 ) ) + 1; // absmin.z is in the floor because the engine subtracts 1 to enlarge the box + + // make the gib fly away from the attack vector + pGib->pev->velocity = g_vecAttackDir * -1; + + // mix in some noise + pGib->pev->velocity.x += RANDOM_FLOAT ( -0.25, 0.25 ); + pGib->pev->velocity.y += RANDOM_FLOAT ( -0.25, 0.25 ); + pGib->pev->velocity.z += RANDOM_FLOAT ( -0.25, 0.25 ); + + pGib->pev->velocity = pGib->pev->velocity * RANDOM_FLOAT ( 300, 400 ); + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + + // copy owner's blood color + pGib->m_bloodColor = (CBaseEntity::Instance(pevVictim))->BloodColor(); + + if ( pevVictim->health > -50) + { + pGib->pev->velocity = pGib->pev->velocity * 0.7; + } + else if ( pevVictim->health > -200) + { + pGib->pev->velocity = pGib->pev->velocity * 2; + } + else + { + pGib->pev->velocity = pGib->pev->velocity * 4; + } + + pGib->pev->solid = SOLID_BBOX; + UTIL_SetSize ( pGib->pev, Vector( 0 , 0 , 0 ), Vector ( 0, 0, 0 ) ); + } + pGib->LimitVelocity(); + } +} + +//========================================================= +// WaitTillLand - in order to emit their meaty scent from +// the proper location, gibs should wait until they stop +// bouncing to emit their scent. That's what this function +// does. +//========================================================= +void CGib :: WaitTillLand ( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + if ( pev->velocity == g_vecZero ) + { + SetThink (&CBaseEntity::SUB_StartFadeOut); + pev->nextthink = gpGlobals->time + m_lifeTime; + } + else + { + // wait and check again in another half second. + pev->nextthink = gpGlobals->time + 0.5; + } +} + +// +// Gib bounces on the ground or wall, sponges some blood down, too! +// +void CGib :: BounceGibTouch ( CBaseEntity *pOther ) +{ + Vector vecSpot; + TraceResult tr; + + //if ( RANDOM_LONG(0,1) ) + // return;// don't bleed everytime + + if (pev->flags & FL_ONGROUND) + { + pev->velocity = pev->velocity * 0.9; + pev->angles.x = 0; + pev->angles.z = 0; + pev->avelocity.x = 0; + pev->avelocity.z = 0; + } + else + { + if ( g_Language != LANGUAGE_GERMAN && m_cBloodDecals > 0 && m_bloodColor != DONT_BLEED ) + { + vecSpot = pev->origin + Vector ( 0 , 0 , 8 );//move up a bit, and trace down. + UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -24 ), ignore_monsters, ENT(pev), & tr); + + UTIL_BloodDecalTrace( &tr, m_bloodColor ); + + m_cBloodDecals--; + } + + if ( m_material != matNone && RANDOM_LONG(0,2) == 0 ) + { + float volume; + float zvel = fabs(pev->velocity.z); + + volume = 0.8 * min(1.0, ((float)zvel) / 450.0); + + CBreakable::MaterialSoundRandom( edict(), (Materials)m_material, volume ); + } + } +} + +// +// Sticky gib puts blood on the wall and stays put. +// +void CGib :: StickyGibTouch ( CBaseEntity *pOther ) +{ + Vector vecSpot; + TraceResult tr; + + SetThink ( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time + 10; + + if ( !FClassnameIs( pOther->pev, "worldspawn" ) ) + { + pev->nextthink = gpGlobals->time; + return; + } + + UTIL_TraceLine ( pev->origin, pev->origin + pev->velocity * 32, ignore_monsters, ENT(pev), & tr); + + UTIL_BloodDecalTrace( &tr, m_bloodColor ); + + pev->velocity = tr.vecPlaneNormal * -1; + pev->angles = UTIL_VecToAngles ( pev->velocity ); + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + pev->movetype = MOVETYPE_NONE; +} + +//========================================================= +// GibMonster - create some gore and get rid of a monster's +// model. +//========================================================= +void CBaseMonster :: GibMonster( void ) +{ + TraceResult tr; + BOOL gibbed = FALSE; + + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "common/bodysplat.wav", 1, ATTN_NORM); + + if ( CVAR_GET_FLOAT("violence_hgibs") != 0 ) // Only the player will ever get here + { + if ( IsPlayer() ) + { + PLAYBACK_EVENT_FULL ( FEV_GLOBAL, edict(), + g_sGibbed, 0.0, (float *)&pev->origin, (float *)&g_vecAttackDir, 0.0, 0.0, 0, 0, 0, 0); + } + + /*CGib::SpawnHeadGib( pev ); + CGib::SpawnRandomGibs( pev, 4, 1 ); // throw some human gibs.*/ + } + gibbed = TRUE; +} + +//========================================================= +// GetDeathActivity - determines the best type of death +// anim to play. +//========================================================= +Activity CBaseMonster :: GetDeathActivity ( void ) +{ + Activity deathActivity; + BOOL fTriedDirection; + float flDot; + TraceResult tr; + Vector vecSrc; + + if ( pev->deadflag != DEAD_NO ) + { + // don't run this while dying. + return m_IdealActivity; + } + + vecSrc = Center(); + + fTriedDirection = FALSE; + deathActivity = ACT_DIESIMPLE;// in case we can't find any special deaths to do. + + UTIL_MakeVectors ( pev->angles ); + flDot = DotProduct ( gpGlobals->v_forward, g_vecAttackDir * -1 ); + + switch ( m_LastHitGroup ) + { + // try to pick a region-specific death. + case HITGROUP_HEAD: + deathActivity = ACT_DIE_HEADSHOT; + break; + + case HITGROUP_STOMACH: + deathActivity = ACT_DIE_GUTSHOT; + break; + + case HITGROUP_GENERIC: + // try to pick a death based on attack direction + fTriedDirection = TRUE; + + if ( flDot > 0.3 ) + { + deathActivity = ACT_DIEFORWARD; + } + else if ( flDot <= -0.3 ) + { + deathActivity = ACT_DIEBACKWARD; + } + break; + + default: + // try to pick a death based on attack direction + fTriedDirection = TRUE; + + if ( flDot > 0.3 ) + { + deathActivity = ACT_DIEFORWARD; + } + else if ( flDot <= -0.3 ) + { + deathActivity = ACT_DIEBACKWARD; + } + break; + } + + + // can we perform the prescribed death? + if ( LookupActivity ( deathActivity ) == ACTIVITY_NOT_AVAILABLE ) + { + // no! did we fail to perform a directional death? + if ( fTriedDirection ) + { + // if yes, we're out of options. Go simple. + deathActivity = ACT_DIESIMPLE; + } + else + { + // cannot perform the ideal region-specific death, so try a direction. + if ( flDot > 0.3 ) + { + deathActivity = ACT_DIEFORWARD; + } + else if ( flDot <= -0.3 ) + { + deathActivity = ACT_DIEBACKWARD; + } + } + } + + if ( LookupActivity ( deathActivity ) == ACTIVITY_NOT_AVAILABLE ) + { + // if we're still invalid, simple is our only option. + deathActivity = ACT_DIESIMPLE; + } + + if ( deathActivity == ACT_DIEFORWARD ) + { + // make sure there's room to fall forward + UTIL_TraceHull ( vecSrc, vecSrc + gpGlobals->v_forward * 64, dont_ignore_monsters, head_hull, edict(), &tr ); + + if ( tr.flFraction != 1.0 ) + { + deathActivity = ACT_DIESIMPLE; + } + } + + if ( deathActivity == ACT_DIEBACKWARD ) + { + // make sure there's room to fall backward + UTIL_TraceHull ( vecSrc, vecSrc - gpGlobals->v_forward * 64, dont_ignore_monsters, head_hull, edict(), &tr ); + + if ( tr.flFraction != 1.0 ) + { + deathActivity = ACT_DIESIMPLE; + } + } + + return deathActivity; +} + +//========================================================= +// GetSmallFlinchActivity - determines the best type of flinch +// anim to play. +//========================================================= +Activity CBaseMonster :: GetSmallFlinchActivity ( void ) +{ + Activity flinchActivity; + BOOL fTriedDirection; + float flDot; + + fTriedDirection = FALSE; + UTIL_MakeVectors ( pev->angles ); + flDot = DotProduct ( gpGlobals->v_forward, g_vecAttackDir * -1 ); + + switch ( m_LastHitGroup ) + { + // pick a region-specific flinch + case HITGROUP_HEAD: + flinchActivity = ACT_FLINCH_HEAD; + break; + case HITGROUP_STOMACH: + flinchActivity = ACT_FLINCH_STOMACH; + break; + case HITGROUP_LEFTARM: + flinchActivity = ACT_FLINCH_LEFTARM; + break; + case HITGROUP_RIGHTARM: + flinchActivity = ACT_FLINCH_RIGHTARM; + break; + case HITGROUP_LEFTLEG: + flinchActivity = ACT_FLINCH_LEFTLEG; + break; + case HITGROUP_RIGHTLEG: + flinchActivity = ACT_FLINCH_RIGHTLEG; + break; + case HITGROUP_GENERIC: + default: + // just get a generic flinch. + flinchActivity = ACT_SMALL_FLINCH; + break; + } + + + // do we have a sequence for the ideal activity? + if ( LookupActivity ( flinchActivity ) == ACTIVITY_NOT_AVAILABLE ) + { + flinchActivity = ACT_SMALL_FLINCH; + } + + return flinchActivity; +} + + +BOOL CBaseMonster::ShouldGibMonster( int iGib ) +{ + if ( ( iGib == GIB_NORMAL && pev->health < GIB_HEALTH_VALUE ) || ( iGib == GIB_ALWAYS ) ) + return TRUE; + + return FALSE; +} + + +void CBaseMonster::CallGibMonster( void ) +{ + BOOL fade = FALSE; + + if ( HasHumanGibs() ) + { + if ( CVAR_GET_FLOAT("violence_hgibs") == 0 ) + fade = TRUE; + } + else if ( HasAlienGibs() ) + { + if ( CVAR_GET_FLOAT("violence_agibs") == 0 ) + fade = TRUE; + } + + pev->takedamage = DAMAGE_NO; + pev->solid = SOLID_NOT;// do something with the body. while monster blows up + + if ( fade ) + { + FadeMonster(); + } + else + { + pev->effects = EF_NODRAW; // make the model invisible. + GibMonster(); + } + + pev->deadflag = DEAD_DEAD; + FCheckAITrigger(); + + // don't let the status bar glitch for players.with <0 health. + if (pev->health < -99) + { + pev->health = 0; + } + + if ( ShouldFadeOnDeath() && !fade ) + UTIL_Remove(this); +} + +// +// fade out - slowly fades a entity out, then removes it. +// +// DON'T USE ME FOR GIBS AND STUFF IN MULTIPLAYER! +// SET A FUTURE THINK AND A RENDERMODE!! +void CBaseEntity :: SUB_StartFadeOut ( void ) +{ + if (pev->rendermode == kRenderNormal) + { + pev->renderamt = 255; + pev->rendermode = kRenderTransTexture; + } + + pev->solid = SOLID_NOT; + pev->avelocity = g_vecZero; + + pev->nextthink = gpGlobals->time + 0.1; + SetThink ( &CBaseEntity::SUB_FadeOut ); +} + +void CBaseEntity :: SUB_FadeOut ( void ) +{ + if ( pev->renderamt > 7 ) + { + pev->renderamt -= 7; + pev->nextthink = gpGlobals->time + 0.1; + } + else + { + pev->renderamt = 0; + pev->nextthink = gpGlobals->time + 0.2; + SetThink ( &CBaseEntity::SUB_Remove ); + } +} + +// +// Throw a chunk +// +void CGib :: Spawn( const char *szGibModel ) +{ + pev->movetype = MOVETYPE_BOUNCE; + pev->friction = 0.55; // deading the bounce a bit + + // sometimes an entity inherits the edict from a former piece of glass, + // and will spawn using the same render FX or rendermode! bad! + pev->renderamt = 255; + pev->rendermode = kRenderNormal; + pev->renderfx = kRenderFxNone; + pev->solid = SOLID_SLIDEBOX;/// hopefully this will fix the VELOCITY TOO LOW crap + pev->classname = MAKE_STRING("gib"); + + SET_MODEL(ENT(pev), szGibModel); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + + pev->nextthink = gpGlobals->time + 4; + m_lifeTime = 25; + SetThink ( &CGib::WaitTillLand ); + SetTouch ( &CGib::BounceGibTouch ); + + m_material = matNone; + m_cBloodDecals = 5;// how many blood decals this gib can place (1 per bounce until none remain). +} + +/* +============ +TakeDamage + +The damage is coming from inflictor, but get mad at attacker +This should be the only function that ever reduces health. +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 +============ +*/ +int CBaseMonster :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + float flTake; + Vector vecDir; + + if (!pev->takedamage) + return 0; + + if ( !IsAlive() ) + { + return DeadTakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); + } + + if ( pev->deadflag == DEAD_NO ) + { + // no pain sound during death animation. + PainSound();// "Ouch!" + } + + //!!!LATER - make armor consideration here! + flTake = flDamage; + + // set damage type sustained + m_bitsDamageType |= bitsDamageType; + + // 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 )) + { + CBaseEntity *pInflictor = CBaseEntity :: Instance( pevInflictor ); + if (pInflictor) + { + vecDir = ( pInflictor->Center() - Vector ( 0, 0, 10 ) - Center() ).Normalize(); + vecDir = g_vecAttackDir = vecDir.Normalize(); + } + } + + // add to the damage total for clients, which will be sent as a single + // message at the end of the frame + // todo: remove after combining shotgun blasts? + if ( IsPlayer() ) + { + if ( pevInflictor ) + pev->dmg_inflictor = ENT(pevInflictor); + + pev->dmg_take += flTake; + + // check for godmode or invincibility + if ( pev->flags & FL_GODMODE ) + { + return 0; + } + } + + // if this is a player, move him around! + if ( ( !FNullEnt( pevInflictor ) ) && (pev->movetype == MOVETYPE_WALK) && (!pevAttacker || pevAttacker->solid != SOLID_TRIGGER) ) + { + pev->velocity = pev->velocity + vecDir * -DamageForce( flDamage ); + } + + // 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 ) + { + SetConditions( bits_COND_LIGHT_DAMAGE ); + return 0; + } + + if ( pev->health <= 0 ) + { + g_pevLastInflictor = pevInflictor; + + if ( bitsDamageType & DMG_ALWAYSGIB ) + { + Killed( pevAttacker, GIB_ALWAYS ); + } + else if ( bitsDamageType & DMG_NEVERGIB ) + { + Killed( pevAttacker, GIB_NEVER ); + } + else + { + Killed( pevAttacker, GIB_NORMAL ); + } + + g_pevLastInflictor = NULL; + + return 0; + } + + // react to the damage (get mad) + if ( (pev->flags & FL_MONSTER) && !FNullEnt(pevAttacker) ) + { + if ( pevAttacker->flags & (FL_MONSTER | FL_CLIENT) ) + {// only if the attack was a monster or client! + + // enemy's last known position is somewhere down the vector that the attack came from. + if (pevInflictor) + { + if (m_hEnemy == 0 || pevInflictor == m_hEnemy->pev || !HasConditions(bits_COND_SEE_ENEMY)) + { + m_vecEnemyLKP = pevInflictor->origin; + } + } + else + { + m_vecEnemyLKP = pev->origin + ( g_vecAttackDir * 64 ); + } + + MakeIdealYaw( m_vecEnemyLKP ); + + // add pain to the conditions + // !!!HACKHACK - fudged for now. Do we want to have a virtual function to determine what is light and + // heavy damage per monster class? + if ( flDamage > 0 ) + { + SetConditions(bits_COND_LIGHT_DAMAGE); + } + + if ( flDamage >= 20 ) + { + SetConditions(bits_COND_HEAVY_DAMAGE); + } + } + } + + return 1; +} + +//========================================================= +// DeadTakeDamage - takedamage function called when a monster's +// corpse is damaged. +//========================================================= +int CBaseMonster :: DeadTakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + Vector vecDir; + + // 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 )) + { + CBaseEntity *pInflictor = CBaseEntity :: Instance( pevInflictor ); + if (pInflictor) + { + vecDir = ( pInflictor->Center() - Vector ( 0, 0, 10 ) - Center() ).Normalize(); + vecDir = g_vecAttackDir = vecDir.Normalize(); + } + } + +#if 0// turn this back on when the bounding box issues are resolved. + + pev->flags &= ~FL_ONGROUND; + pev->origin.z += 1; + + // let the damage scoot the corpse around a bit. + if ( !FNullEnt(pevInflictor) && (pevAttacker->solid != SOLID_TRIGGER) ) + { + pev->velocity = pev->velocity + vecDir * -DamageForce( flDamage ); + } + +#endif + + // kill the corpse if enough damage was done to destroy the corpse and the damage is of a type that is allowed to destroy the corpse. + if ( bitsDamageType & DMG_GIB_CORPSE ) + { + if ( pev->health <= flDamage ) + { + pev->health = -50; + Killed( pevAttacker, GIB_ALWAYS ); + return 0; + } + // Accumulate corpse gibbing damage, so you can gib with multiple hits + pev->health -= flDamage * 0.1; + } + + return 1; +} + + +float CBaseMonster :: DamageForce( float damage ) +{ + float force = damage * ((32 * 32 * 72.0) / (pev->size.x * pev->size.y * pev->size.z)) * 5; + + if ( force > 1000.0) + { + force = 1000.0; + } + + return force; +} + +// +// RadiusDamage - this entity is exploding, or otherwise needs to inflict damage upon entities within a certain range. +// +// only damage ents that can clearly be seen by the explosion! + + +void RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, float flRadius, int iClassIgnore, int bitsDamageType ) +{ + CBaseEntity *pEntity = NULL; + TraceResult tr; + float flAdjustedDamage, falloff; + Vector vecSpot; + + if ( flRadius ) + falloff = flDamage / flRadius; + else + falloff = 1.0; + + int bInWater = (UTIL_PointContents ( vecSrc ) == CONTENTS_WATER); + + vecSrc.z += 1;// in case grenade is lying on the ground + + if ( !pevAttacker ) + pevAttacker = pevInflictor; + + // iterate on all entities in the vicinity. + while ((pEntity = UTIL_FindEntityInSphere( pEntity, vecSrc, flRadius )) != NULL) + { + if ( pEntity->pev->takedamage != DAMAGE_NO ) + { + // UNDONE: this should check a damage mask, not an ignore + if ( iClassIgnore != CLASS_NONE && pEntity->Classify() == iClassIgnore ) + {// houndeyes don't hurt other houndeyes with their attack + continue; + } + + // blast's don't tavel into or out of water + if (bInWater && pEntity->pev->waterlevel == 0) + continue; + if (!bInWater && pEntity->pev->waterlevel == 3) + continue; + + vecSpot = pEntity->BodyTarget( vecSrc ); + + UTIL_TraceLine ( vecSrc, vecSpot, dont_ignore_monsters, ENT(pevInflictor), &tr ); + + if ( tr.flFraction == 1.0 || tr.pHit == pEntity->edict() ) + {// the explosion can 'see' this entity, so hurt them! + if (tr.fStartSolid) + { + // if we're stuck inside them, fixup the position and distance + tr.vecEndPos = vecSrc; + tr.flFraction = 0.0; + } + + // decrease damage for an ent that's farther from the bomb. + flAdjustedDamage = ( vecSrc - tr.vecEndPos ).Length() * falloff; + flAdjustedDamage = flDamage - flAdjustedDamage; + + if ( flAdjustedDamage < 0 ) + { + flAdjustedDamage = 0; + } + + // ALERT( at_console, "hit %s\n", STRING( pEntity->pev->classname ) ); + if (tr.flFraction != 1.0) + { + ClearMultiDamage( ); + pEntity->TraceAttack( pevInflictor, flAdjustedDamage, (tr.vecEndPos - vecSrc).Normalize( ), &tr, bitsDamageType ); + ApplyMultiDamage( pevInflictor, pevAttacker ); + } + else + { + pEntity->TakeDamage ( pevInflictor, pevAttacker, flAdjustedDamage, bitsDamageType ); + } + } + } + } +} + + +void CBaseMonster :: RadiusDamage(entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) +{ + ::RadiusDamage( pev->origin, pevInflictor, pevAttacker, flDamage, flDamage * 2.5, iClassIgnore, bitsDamageType ); +} + + +void CBaseMonster :: RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) +{ + ::RadiusDamage( vecSrc, pevInflictor, pevAttacker, flDamage, flDamage * 2.5, iClassIgnore, bitsDamageType ); +} + +/* +//========================================================= +// TraceAttack +//========================================================= +void CBaseMonster::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + Vector vecOrigin = ptr->vecEndPos - vecDir * 4; + + ALERT ( at_console, "%d\n", ptr->iHitgroup ); + + + if ( pev->takedamage ) + { + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + + int blood = BloodColor(); + + if ( blood != DONT_BLEED ) + { + SpawnBlood(vecOrigin, blood, flDamage);// a little surface blood. + } + } +} +*/ + +void CBaseEntity :: TraceBleed( float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + if (BloodColor() == DONT_BLEED) + return; + + if (flDamage == 0) + return; + + if (! (bitsDamageType & (DMG_CRUSH | DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB | DMG_MORTAR))) + return; + + // make blood decal on the wall! + TraceResult Bloodtr; + Vector vecTraceDir; + float flNoise; + int cCount; + int i; + + if (flDamage < 10) + { + flNoise = 0.1; + cCount = 1; + } + else if (flDamage < 25) + { + flNoise = 0.2; + cCount = 2; + } + else + { + flNoise = 0.3; + cCount = 4; + } + + for ( i = 0 ; i < cCount ; i++ ) + { + vecTraceDir = vecDir * -1;// trace in the opposite direction the shot came from (the direction the shot is going) + + vecTraceDir.x += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.y += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.z += RANDOM_FLOAT( -flNoise, flNoise ); + + UTIL_TraceLine( ptr->vecEndPos, ptr->vecEndPos + vecTraceDir * -172, ignore_monsters, ENT(pev), &Bloodtr); + + if ( Bloodtr.flFraction != 1.0 ) + { + UTIL_BloodDecalTrace( &Bloodtr, BloodColor() ); + } + } +} + +//========================================================= +//========================================================= +void CBaseMonster :: MakeDamageBloodDecal ( int cCount, float flNoise, TraceResult *ptr, const Vector &vecDir ) +{ + // make blood decal on the wall! + TraceResult Bloodtr; + Vector vecTraceDir; + int i; + + if ( !IsAlive() ) + { + // dealing with a dead monster. + if ( pev->max_health <= 0 ) + { + // no blood decal for a monster that has already decalled its limit. + return; + } + else + { + pev->max_health--; + } + } + + for ( i = 0 ; i < cCount ; i++ ) + { + vecTraceDir = vecDir; + + vecTraceDir.x += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.y += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.z += RANDOM_FLOAT( -flNoise, flNoise ); + + UTIL_TraceLine( ptr->vecEndPos, ptr->vecEndPos + vecTraceDir * 172, ignore_monsters, ENT(pev), &Bloodtr); + +/* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + WRITE_COORD( ptr->vecEndPos.x ); + WRITE_COORD( ptr->vecEndPos.y ); + WRITE_COORD( ptr->vecEndPos.z ); + + WRITE_COORD( Bloodtr.vecEndPos.x ); + WRITE_COORD( Bloodtr.vecEndPos.y ); + WRITE_COORD( Bloodtr.vecEndPos.z ); + MESSAGE_END(); +*/ + + if ( Bloodtr.flFraction != 1.0 ) + { + UTIL_BloodDecalTrace( &Bloodtr, BloodColor() ); + } + } +} diff --git a/hlsdk/dmc/dlls/decals.h b/hlsdk/dmc/dlls/decals.h new file mode 100644 index 00000000..95fa44f5 --- /dev/null +++ b/hlsdk/dmc/dlls/decals.h @@ -0,0 +1,75 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef DECALS_H +#define DECALS_H + +// +// Dynamic Decals +// +enum decal_e +{ + DECAL_GUNSHOT1 = 0, + DECAL_GUNSHOT2, + DECAL_GUNSHOT3, + DECAL_GUNSHOT4, + DECAL_GUNSHOT5, + DECAL_LAMBDA1, + DECAL_LAMBDA2, + DECAL_LAMBDA3, + DECAL_LAMBDA4, + DECAL_LAMBDA5, + DECAL_LAMBDA6, + DECAL_SCORCH1, + DECAL_SCORCH2, + DECAL_BLOOD1, + DECAL_BLOOD2, + DECAL_BLOOD3, + DECAL_BLOOD4, + DECAL_BLOOD5, + DECAL_BLOOD6, + DECAL_YBLOOD1, + DECAL_YBLOOD2, + DECAL_YBLOOD3, + DECAL_YBLOOD4, + DECAL_YBLOOD5, + DECAL_YBLOOD6, + DECAL_GLASSBREAK1, + DECAL_GLASSBREAK2, + DECAL_GLASSBREAK3, + DECAL_BIGSHOT1, + DECAL_BIGSHOT2, + DECAL_BIGSHOT3, + DECAL_BIGSHOT4, + DECAL_BIGSHOT5, + DECAL_SPIT1, + DECAL_SPIT2, + DECAL_BPROOF1, // Bulletproof glass decal + DECAL_GARGSTOMP1, // Gargantua stomp crack + DECAL_SMALLSCORCH1, // Small scorch mark + DECAL_SMALLSCORCH2, // Small scorch mark + DECAL_SMALLSCORCH3, // Small scorch mark + DECAL_MOMMABIRTH, // Big momma birth splatter + DECAL_MOMMASPLAT, +}; + +typedef struct +{ + char *name; + int index; +} DLL_DECALLIST; + +extern DLL_DECALLIST gDecals[]; + +#endif // DECALS_H diff --git a/hlsdk/dmc/dlls/defaultai.cpp b/hlsdk/dmc/dlls/defaultai.cpp new file mode 100644 index 00000000..2b231245 --- /dev/null +++ b/hlsdk/dmc/dlls/defaultai.cpp @@ -0,0 +1,240 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// Default behaviors. +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "defaultai.h" +#include "nodes.h" +#include "scripted.h" + +Schedule_t *CBaseMonster::m_scheduleList[] = +{ +}; + +Schedule_t *CBaseMonster::ScheduleFromName( const char *pName ) +{ + return ScheduleInList( pName, m_scheduleList, ARRAYSIZE(m_scheduleList) ); +} + + +Schedule_t *CBaseMonster :: ScheduleInList( const char *pName, Schedule_t **pList, int listCount ) +{ + int i; + + if ( !pName ) + { + ALERT( at_console, "%s set to unnamed schedule!\n", STRING(pev->classname) ); + return NULL; + } + + + for ( i = 0; i < listCount; i++ ) + { + if ( !pList[i]->pName ) + { + ALERT( at_console, "Unnamed schedule!\n" ); + continue; + } + if ( stricmp( pName, pList[i]->pName ) == 0 ) + return pList[i]; + } + return NULL; +} + +//========================================================= +// GetScheduleOfType - returns a pointer to one of the +// monster's available schedules of the indicated type. +//========================================================= +Schedule_t* CBaseMonster :: GetScheduleOfType ( int Type ) +{ +// ALERT ( at_console, "Sched Type:%d\n", Type ); + switch ( Type ) + { + // This is the schedule for scripted sequences AND scripted AI + case SCHED_AISCRIPT: + { + ASSERT( m_pCine != NULL ); + if ( !m_pCine ) + { + ALERT( at_aiconsole, "Script failed for %s\n", STRING(pev->classname) ); + CineCleanup(); + return GetScheduleOfType( SCHED_IDLE_STAND ); + } +// else +// ALERT( at_aiconsole, "Starting script %s for %s\n", STRING( m_pCine->m_iszPlay ), STRING(pev->classname) ); + + switch ( m_pCine->m_fMoveTo ) + { + case 0: + case 4: + return slWaitScript; + case 1: + return slWalkToScript; + case 2: + return slRunToScript; + case 5: + return slFaceScript; + } + break; + } + case SCHED_IDLE_STAND: + { + if ( RANDOM_LONG(0,14) == 0 && FCanActiveIdle() ) + { + return &slActiveIdle[ 0 ]; + } + + return &slIdleStand[ 0 ]; + } + case SCHED_IDLE_WALK: + { + return &slIdleWalk[ 0 ]; + } + case SCHED_WAIT_TRIGGER: + { + return &slIdleTrigger[ 0 ]; + } + case SCHED_WAKE_ANGRY: + { + return &slWakeAngry[ 0 ]; + } + case SCHED_ALERT_FACE: + { + return &slAlertFace[ 0 ]; + } + case SCHED_ALERT_STAND: + { + return &slAlertStand[ 0 ]; + } + case SCHED_COMBAT_STAND: + { + return &slCombatStand[ 0 ]; + } + case SCHED_COMBAT_FACE: + { + return &slCombatFace[ 0 ]; + } + case SCHED_CHASE_ENEMY: + { + return &slChaseEnemy[ 0 ]; + } + case SCHED_CHASE_ENEMY_FAILED: + { + return &slFail[ 0 ]; + } + case SCHED_SMALL_FLINCH: + { + return &slSmallFlinch[ 0 ]; + } + case SCHED_ALERT_SMALL_FLINCH: + { + return &slAlertSmallFlinch[ 0 ]; + } + case SCHED_RELOAD: + { + return &slReload[ 0 ]; + } + case SCHED_ARM_WEAPON: + { + return &slArmWeapon[ 0 ]; + } + case SCHED_STANDOFF: + { + return &slStandoff[ 0 ]; + } + case SCHED_RANGE_ATTACK1: + { + return &slRangeAttack1[ 0 ]; + } + case SCHED_RANGE_ATTACK2: + { + return &slRangeAttack2[ 0 ]; + } + case SCHED_MELEE_ATTACK1: + { + return &slPrimaryMeleeAttack[ 0 ]; + } + case SCHED_MELEE_ATTACK2: + { + return &slSecondaryMeleeAttack[ 0 ]; + } + case SCHED_SPECIAL_ATTACK1: + { + return &slSpecialAttack1[ 0 ]; + } + case SCHED_SPECIAL_ATTACK2: + { + return &slSpecialAttack2[ 0 ]; + } + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + { + return &slTakeCoverFromBestSound[ 0 ]; + } + case SCHED_TAKE_COVER_FROM_ENEMY: + { + return &slTakeCoverFromEnemy[ 0 ]; + } + case SCHED_COWER: + { + return &slCower[ 0 ]; + } + case SCHED_AMBUSH: + { + return &slAmbush[ 0 ]; + } + case SCHED_BARNACLE_VICTIM_GRAB: + { + return &slBarnacleVictimGrab[ 0 ]; + } + case SCHED_BARNACLE_VICTIM_CHOMP: + { + return &slBarnacleVictimChomp[ 0 ]; + } + case SCHED_INVESTIGATE_SOUND: + { + return &slInvestigateSound[ 0 ]; + } + case SCHED_DIE: + { + return &slDie[ 0 ]; + } + case SCHED_TAKE_COVER_FROM_ORIGIN: + { + return &slTakeCoverFromOrigin[ 0 ]; + } + case SCHED_VICTORY_DANCE: + { + return &slVictoryDance[ 0 ]; + } + case SCHED_FAIL: + { + return slFail; + } + default: + { + ALERT ( at_console, "GetScheduleOfType()\nNo CASE for Schedule Type %d!\n", Type ); + + return &slIdleStand[ 0 ]; + break; + } + } + + return NULL; +} diff --git a/hlsdk/dmc/dlls/defaultai.h b/hlsdk/dmc/dlls/defaultai.h new file mode 100644 index 00000000..652d1085 --- /dev/null +++ b/hlsdk/dmc/dlls/defaultai.h @@ -0,0 +1,98 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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 DEFAULTAI_H +#define DEFAULTAI_H + +//========================================================= +// Failed +//========================================================= +extern Schedule_t slFail[]; + +//========================================================= +// Idle Schedules +//========================================================= +extern Schedule_t slIdleStand[]; +extern Schedule_t slIdleTrigger[]; +extern Schedule_t slIdleWalk[]; + +//========================================================= +// Wake Schedules +//========================================================= +extern Schedule_t slWakeAngry[]; + +//========================================================= +// AlertTurn Schedules +//========================================================= +extern Schedule_t slAlertFace[]; + +//========================================================= +// AlertIdle Schedules +//========================================================= +extern Schedule_t slAlertStand[]; + +//========================================================= +// CombatIdle Schedule +//========================================================= +extern Schedule_t slCombatStand[]; + +//========================================================= +// CombatFace Schedule +//========================================================= +extern Schedule_t slCombatFace[]; + +//========================================================= +// reload schedule +//========================================================= +extern Schedule_t slReload[]; + +//========================================================= +// Attack Schedules +//========================================================= + +extern Schedule_t slRangeAttack1[]; +extern Schedule_t slRangeAttack2[]; + +extern Schedule_t slTakeCoverFromBestSound[]; + +// primary melee attack +extern Schedule_t slMeleeAttack[]; + +// Chase enemy schedule +extern Schedule_t slChaseEnemy[]; + +//========================================================= +// small flinch, used when a relatively minor bit of damage +// is inflicted. +//========================================================= +extern Schedule_t slSmallFlinch[]; + +//========================================================= +// Die! +//========================================================= +extern Schedule_t slDie[]; + +//========================================================= +// Universal Error Schedule +//========================================================= +extern Schedule_t slError[]; + +//========================================================= +// Scripted sequences +//========================================================= +extern Schedule_t slWalkToScript[]; +extern Schedule_t slRunToScript[]; +extern Schedule_t slWaitScript[]; + +#endif // DEFAULTAI_H diff --git a/hlsdk/dmc/dlls/dmc.def b/hlsdk/dmc/dlls/dmc.def new file mode 100644 index 00000000..adf191e5 --- /dev/null +++ b/hlsdk/dmc/dlls/dmc.def @@ -0,0 +1,5 @@ +LIBRARY dmc +EXPORTS + GiveFnptrsToDll @1 +SECTIONS + .data READ WRITE diff --git a/hlsdk/dmc/dlls/dmcgl.def b/hlsdk/dmc/dlls/dmcgl.def new file mode 100644 index 00000000..19c1c138 --- /dev/null +++ b/hlsdk/dmc/dlls/dmcgl.def @@ -0,0 +1,15 @@ +LIBRARY dmcgl +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/hlsdk/dmc/dlls/doors.cpp b/hlsdk/dmc/dlls/doors.cpp new file mode 100644 index 00000000..28e8f026 --- /dev/null +++ b/hlsdk/dmc/dlls/doors.cpp @@ -0,0 +1,1093 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== doors.cpp ======================================================== + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "doors.h" + + +extern void SetMovedir(entvars_t* ev); + +#define noiseMoving noise1 +#define noiseArrived noise2 + +class CBaseDoor : public CBaseToggle +{ +public: + void Spawn( void ); + void Precache( void ); + virtual void KeyValue( KeyValueData *pkvd ); + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual void Blocked( CBaseEntity *pOther ); + + + virtual int ObjectCaps( void ) + { + if (pev->spawnflags & SF_ITEM_USE_ONLY) + return (CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_IMPULSE_USE; + else + return (CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION); + }; + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + virtual void SetToggleState( int state ); + + // used to selectivly override defaults + void EXPORT DoorTouch( CBaseEntity *pOther ); + + // local functions + int DoorActivate( ); + void EXPORT DoorGoUp( void ); + void EXPORT DoorGoDown( void ); + void EXPORT DoorHitTop( void ); + void EXPORT DoorHitBottom( void ); + + BYTE m_bHealthValue;// some doors are medi-kit doors, they give players health + + BYTE m_bMoveSnd; // sound a door makes while moving + BYTE m_bStopSnd; // sound a door makes when it stops + + locksound_t m_ls; // door lock sounds + + BYTE m_bLockedSound; // ordinals from entity selection + BYTE m_bLockedSentence; + BYTE m_bUnlockedSound; + BYTE m_bUnlockedSentence; + float m_fNextSoundPlay; + bool m_bIsReopening; // If the bIsReopening flag is set, the door's not fully shut, but it still wants to reopen + // because a player's standing in it's field. + bool m_bStoppedOpenSound; // TRUE once the original opening sound has been stopped + +private: + unsigned short m_usDoorGoUp; + unsigned short m_usDoorGoDown; + unsigned short m_usDoorHitTop; + unsigned short m_usDoorHitBottom; +}; + + +TYPEDESCRIPTION CBaseDoor::m_SaveData[] = +{ + DEFINE_FIELD( CBaseDoor, m_bHealthValue, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bMoveSnd, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bStopSnd, FIELD_CHARACTER ), + + DEFINE_FIELD( CBaseDoor, m_bLockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bLockedSentence, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bUnlockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bUnlockedSentence, FIELD_CHARACTER ), + +}; + +IMPLEMENT_SAVERESTORE( CBaseDoor, CBaseToggle ); + + +#define DOOR_SENTENCEWAIT 6 +#define DOOR_SOUNDWAIT 3 +#define BUTTON_SOUNDWAIT 0.5 + +// play door or button locked or unlocked sounds. +// pass in pointer to valid locksound struct. +// if flocked is true, play 'door is locked' sound, +// otherwise play 'door is unlocked' sound +// NOTE: this routine is shared by doors and buttons + +void PlayLockSounds(entvars_t *pev, locksound_t *pls, int flocked, int fbutton) +{ + // LOCKED SOUND + + // CONSIDER: consolidate the locksound_t struct (all entries are duplicates for lock/unlock) + // CONSIDER: and condense this code. + float flsoundwait; + + if (fbutton) + flsoundwait = BUTTON_SOUNDWAIT; + else + flsoundwait = DOOR_SOUNDWAIT; + + if (flocked) + { + int fplaysound = (pls->sLockedSound && gpGlobals->time > pls->flwaitSound); + int fplaysentence = (pls->sLockedSentence && !pls->bEOFLocked && gpGlobals->time > pls->flwaitSentence); + float fvol; + + if (fplaysound && fplaysentence) + fvol = 0.25; + else + fvol = 1.0; + + // if there is a locked sound, and we've debounced, play sound + if (fplaysound) + { + // play 'door locked' sound + EMIT_SOUND(ENT(pev), CHAN_ITEM, (char*)STRING(pls->sLockedSound), fvol, ATTN_NORM); + pls->flwaitSound = gpGlobals->time + flsoundwait; + } + + // if there is a sentence, we've not played all in list, and we've debounced, play sound + if (fplaysentence) + { + // play next 'door locked' sentence in group + int iprev = pls->iLockedSentence; + + pls->iLockedSentence = SENTENCEG_PlaySequentialSz(ENT(pev), STRING(pls->sLockedSentence), + 0.85, ATTN_NORM, 0, 100, pls->iLockedSentence, FALSE); + pls->iUnlockedSentence = 0; + + // make sure we don't keep calling last sentence in list + pls->bEOFLocked = (iprev == pls->iLockedSentence); + + pls->flwaitSentence = gpGlobals->time + DOOR_SENTENCEWAIT; + } + } + else + { + // UNLOCKED SOUND + + int fplaysound = (pls->sUnlockedSound && gpGlobals->time > pls->flwaitSound); + int fplaysentence = (pls->sUnlockedSentence && !pls->bEOFUnlocked && gpGlobals->time > pls->flwaitSentence); + float fvol; + + // if playing both sentence and sound, lower sound volume so we hear sentence + if (fplaysound && fplaysentence) + fvol = 0.25; + else + fvol = 1.0; + + // play 'door unlocked' sound if set + if (fplaysound) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, (char*)STRING(pls->sUnlockedSound), fvol, ATTN_NORM); + pls->flwaitSound = gpGlobals->time + flsoundwait; + } + + // play next 'door unlocked' sentence in group + if (fplaysentence) + { + int iprev = pls->iUnlockedSentence; + + pls->iUnlockedSentence = SENTENCEG_PlaySequentialSz(ENT(pev), STRING(pls->sUnlockedSentence), + 0.85, ATTN_NORM, 0, 100, pls->iUnlockedSentence, FALSE); + pls->iLockedSentence = 0; + + // make sure we don't keep calling last sentence in list + pls->bEOFUnlocked = (iprev == pls->iUnlockedSentence); + pls->flwaitSentence = gpGlobals->time + DOOR_SENTENCEWAIT; + } + } +} + +// +// Cache user-entity-field values until spawn is called. +// + +void CBaseDoor::KeyValue( KeyValueData *pkvd ) +{ + + if (FStrEq(pkvd->szKeyName, "skin"))//skin is used for content type + { + pev->skin = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "movesnd")) + { + m_bMoveSnd = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "stopsnd")) + { + m_bStopSnd = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "healthvalue")) + { + m_bHealthValue = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sound")) + { + m_bLockedSound = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sentence")) + { + m_bLockedSentence = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sound")) + { + m_bUnlockedSound = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sentence")) + { + m_bUnlockedSentence = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "WaveHeight")) + { + pev->scale = atof(pkvd->szValue) * (1.0/8.0); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +/*QUAKED func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK TOGGLE +if two doors touch, they are assumed to be connected and operate as a unit. + +TOGGLE causes the door to wait in both the start and end states for a trigger event. + +START_OPEN causes the door to move to its destination when spawned, and operate in reverse. +It is used to temporarily or permanently close off an area when triggered (not usefull for +touch or takedamage doors). + +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger + field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"lip" lip remaining at end of move (8 default) +"dmg" damage to inflict when blocked (2 default) +"sounds" +0) no sound +1) stone +2) base +3) stone chain +4) screechy metal +*/ + +LINK_ENTITY_TO_CLASS( func_door, CBaseDoor ); +// +// func_water - same as a door. +// +LINK_ENTITY_TO_CLASS( func_water, CBaseDoor ); + + +void CBaseDoor::Spawn( ) +{ + Precache(); + SetMovedir (pev); + + if ( pev->skin == 0 ) + {//normal door + if ( FBitSet (pev->spawnflags, SF_DOOR_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + } + else + {// special contents + pev->solid = SOLID_NOT; + SetBits( pev->spawnflags, SF_DOOR_SILENT ); // water is silent for now + } + + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + if (pev->speed == 0) + pev->speed = 100; + + m_vecPosition1 = pev->origin; + // Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big + m_vecPosition2 = m_vecPosition1 + (pev->movedir * (fabs( pev->movedir.x * (pev->size.x-2) ) + fabs( pev->movedir.y * (pev->size.y-2) ) + fabs( pev->movedir.z * (pev->size.z-2) ) - m_flLip)); + ASSERTSZ(m_vecPosition1 != m_vecPosition2, "door start/end positions are equal"); + if ( FBitSet (pev->spawnflags, SF_DOOR_START_OPEN) ) + { // swap pos1 and pos2, put door at pos2 + UTIL_SetOrigin(pev, m_vecPosition2); + m_vecPosition2 = m_vecPosition1; + m_vecPosition1 = pev->origin; + } + + m_toggle_state = TS_AT_BOTTOM; + + m_fNextSoundPlay = 0; + m_bIsReopening = false; + m_bStoppedOpenSound = false; + // if the door is flagged for USE button activation only, use NULL touch function + if ( FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) ) + { + SetTouch ( NULL ); + } + else // touchable button + SetTouch( &CBaseDoor::DoorTouch ); +} + + +void CBaseDoor :: SetToggleState( int state ) +{ + if ( state == TS_AT_TOP ) + UTIL_SetOrigin( pev, m_vecPosition2 ); + else + UTIL_SetOrigin( pev, m_vecPosition1 ); +} + + +void CBaseDoor::Precache( void ) +{ + char *pszSound; + +// set the door's "in-motion" sound + switch (m_bMoveSnd) + { + case 0: + pev->noiseMoving = ALLOC_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("doors/doormove1.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove1.wav"); + break; + case 2: + PRECACHE_SOUND ("doors/doormove2.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove2.wav"); + break; + case 3: + PRECACHE_SOUND ("doors/doormove3.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove3.wav"); + break; + case 4: + PRECACHE_SOUND ("doors/doormove4.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove4.wav"); + break; + case 5: + PRECACHE_SOUND ("doors/doormove5.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove5.wav"); + break; + case 6: + PRECACHE_SOUND ("doors/doormove6.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove6.wav"); + break; + case 7: + PRECACHE_SOUND ("doors/doormove7.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove7.wav"); + break; + case 8: + PRECACHE_SOUND ("doors/doormove8.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove8.wav"); + break; + case 9: + PRECACHE_SOUND ("doors/doormove9.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove9.wav"); + break; + case 10: + PRECACHE_SOUND ("doors/doormove10.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove10.wav"); + break; + default: + pev->noiseMoving = ALLOC_STRING("common/null.wav"); + break; + } + +// set the door's 'reached destination' stop sound + switch (m_bStopSnd) + { + case 0: + pev->noiseArrived = ALLOC_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("doors/doorstop1.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop1.wav"); + break; + case 2: + PRECACHE_SOUND ("doors/doorstop2.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop2.wav"); + break; + case 3: + PRECACHE_SOUND ("doors/doorstop3.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop3.wav"); + break; + case 4: + PRECACHE_SOUND ("doors/doorstop4.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop4.wav"); + break; + case 5: + PRECACHE_SOUND ("doors/doorstop5.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop5.wav"); + break; + case 6: + PRECACHE_SOUND ("doors/doorstop6.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop6.wav"); + break; + case 7: + PRECACHE_SOUND ("doors/doorstop7.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop7.wav"); + break; + case 8: + PRECACHE_SOUND ("doors/doorstop8.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop8.wav"); + break; + default: + pev->noiseArrived = ALLOC_STRING("common/null.wav"); + break; + } + + // get door button sounds, for doors which are directly 'touched' to open + + if (m_bLockedSound) + { + pszSound = ButtonSound( (int)m_bLockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sLockedSound = ALLOC_STRING(pszSound); + } + + if (m_bUnlockedSound) + { + pszSound = ButtonSound( (int)m_bUnlockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sUnlockedSound = ALLOC_STRING(pszSound); + } + + // get sentence group names, for doors which are directly 'touched' to open + + switch (m_bLockedSentence) + { + case 1: m_ls.sLockedSentence = ALLOC_STRING("NA"); break; // access denied + case 2: m_ls.sLockedSentence = ALLOC_STRING("ND"); break; // security lockout + case 3: m_ls.sLockedSentence = ALLOC_STRING("NF"); break; // blast door + case 4: m_ls.sLockedSentence = ALLOC_STRING("NFIRE"); break; // fire door + case 5: m_ls.sLockedSentence = ALLOC_STRING("NCHEM"); break; // chemical door + case 6: m_ls.sLockedSentence = ALLOC_STRING("NRAD"); break; // radiation door + case 7: m_ls.sLockedSentence = ALLOC_STRING("NCON"); break; // gen containment + case 8: m_ls.sLockedSentence = ALLOC_STRING("NH"); break; // maintenance door + case 9: m_ls.sLockedSentence = ALLOC_STRING("NG"); break; // broken door + + default: m_ls.sLockedSentence = 0; break; + } + + switch (m_bUnlockedSentence) + { + case 1: m_ls.sUnlockedSentence = ALLOC_STRING("EA"); break; // access granted + case 2: m_ls.sUnlockedSentence = ALLOC_STRING("ED"); break; // security door + case 3: m_ls.sUnlockedSentence = ALLOC_STRING("EF"); break; // blast door + case 4: m_ls.sUnlockedSentence = ALLOC_STRING("EFIRE"); break; // fire door + case 5: m_ls.sUnlockedSentence = ALLOC_STRING("ECHEM"); break; // chemical door + case 6: m_ls.sUnlockedSentence = ALLOC_STRING("ERAD"); break; // radiation door + case 7: m_ls.sUnlockedSentence = ALLOC_STRING("ECON"); break; // gen containment + case 8: m_ls.sUnlockedSentence = ALLOC_STRING("EH"); break; // maintenance door + + default: m_ls.sUnlockedSentence = 0; break; + } + m_usDoorGoUp = PRECACHE_EVENT( 1, "events/door/doorgoup.sc" ); + m_usDoorGoDown = PRECACHE_EVENT( 1, "events/door/doorgodown.sc" ); + m_usDoorHitTop = PRECACHE_EVENT( 1, "events/door/doorhittop.sc" ); + m_usDoorHitBottom = PRECACHE_EVENT( 1, "events/door/doorhitbottom.sc" ); +} + +// +// Doors not tied to anything (e.g. button, another door) can be touched, to make them activate. +// +void CBaseDoor::DoorTouch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + + // Ignore touches by anything but players + if (!FClassnameIs(pevToucher, "player")) + return; + + // If door has master, and it's not ready to trigger, + // play 'locked' sound + + if (m_sMaster && !UTIL_IsMasterTriggered(m_sMaster, pOther)) + PlayLockSounds(pev, &m_ls, TRUE, FALSE); + + // If door is somebody's target, then touching does nothing. + // You have to activate the owner (e.g. button). + + if (!FStringNull(pev->targetname)) + { + // play locked sound + PlayLockSounds(pev, &m_ls, TRUE, FALSE); + return; + } + + m_hActivator = pOther;// remember who activated the door + + if (DoorActivate( )) + SetTouch( NULL ); // Temporarily disable the touch function, until movement is finished. +} + + +// +// Used by SUB_UseTargets, when a door is the target of a button. +// +void CBaseDoor::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_hActivator = pActivator; + // if not ready to be used, ignore "use" command. + if (m_toggle_state == TS_AT_BOTTOM || FBitSet(pev->spawnflags, SF_DOOR_NO_AUTO_RETURN) && m_toggle_state == TS_AT_TOP) + DoorActivate(); +} + +// +// Causes the door to "do its thing", i.e. start moving, and cascade activation. +// +int CBaseDoor::DoorActivate( ) +{ + if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + return 0; + + if (FBitSet(pev->spawnflags, SF_DOOR_NO_AUTO_RETURN) && m_toggle_state == TS_AT_TOP) + {// door should close + DoorGoDown(); + } + else + {// door should open + + if ( m_hActivator != 0 && m_hActivator->IsPlayer() ) + {// give health if player opened the door (medikit) + // VARS( m_eoActivator )->health += m_bHealthValue; + + m_hActivator->TakeHealth( m_bHealthValue, DMG_GENERIC ); + + } + + // play door unlock sounds + if (!m_bIsReopening) + { + PlayLockSounds(pev, &m_ls, FALSE, FALSE); + } + DoorGoUp(); + } + + return 1; +} + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); + +// +// Starts the door going to its "up" position (simply ToggleData->vecPosition2). +// +void CBaseDoor::DoorGoUp( void ) +{ + entvars_t *pevActivator; + + // It could be going-down, if blocked. + ASSERT( m_bIsReopening == true || (m_toggle_state == TS_AT_BOTTOM || m_toggle_state == TS_GOING_DOWN) ); + + // emit door moving and stop sounds on CHAN_STATIC so that the multicast doesn't + // filter them out and leave a client stuck with looping door sounds! + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) && m_bIsReopening == false ) + { + // don't play sounds too often + if ( m_fNextSoundPlay < gpGlobals->time ) + { + Vector v = Center(); + PLAYBACK_EVENT_FULL( FEV_RELIABLE, NULL, m_usDoorGoUp, 0.0, (float *)&v, (float *)&g_vecZero, 0.0, 0.0, ( m_bMoveSnd << 8 ) | ( m_bStopSnd & 0xff ), 0, 0, 0 ); +#if defined ( OLD_SOUNDS ) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving) ); + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving), 1, ATTN_NORM); +#endif + } + } + + m_toggle_state = TS_GOING_UP; + + SetMoveDone( &CBaseDoor::DoorHitTop ); + if ( FClassnameIs(pev, "func_door_rotating")) // !!! BUGBUG Triggered doors don't work with this yet + { + float sign = 1.0; + + if ( m_hActivator != 0 ) + { + pevActivator = m_hActivator->pev; + + if ( !FBitSet( pev->spawnflags, SF_DOOR_ONEWAY ) && pev->movedir.y ) // Y axis rotation, move away from the player + { + Vector vec = pevActivator->origin - pev->origin; + Vector angles = pevActivator->angles; + angles.x = 0; + angles.z = 0; + UTIL_MakeVectors (angles); + // Vector vnext = (pevToucher->origin + (pevToucher->velocity * 10)) - pev->origin; + UTIL_MakeVectors ( pevActivator->angles ); + Vector vnext = (pevActivator->origin + (gpGlobals->v_forward * 10)) - pev->origin; + if ( (vec.x*vnext.y - vec.y*vnext.x) < 0 ) + sign = -1.0; + } + } + AngularMove(m_vecAngle2*sign, pev->speed); + } + else + LinearMove(m_vecPosition2, pev->speed); +} + + +// +// The door has reached the "up" position. Either go back down, or wait for another activation. +// +void CBaseDoor::DoorHitTop( void ) +{ + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + { + // don't play sounds too often + if ( (( m_fNextSoundPlay < gpGlobals->time ) && !m_bIsReopening) || (!m_bStoppedOpenSound) ) + { + m_bStoppedOpenSound = true; + + Vector v = Center(); + PLAYBACK_EVENT_FULL( FEV_RELIABLE, NULL, m_usDoorHitTop, 0.0, (float *)&v, (float *)&g_vecZero, 0.0, 0.0, ( m_bMoveSnd << 8 ) | ( m_bStopSnd & 0xff ), 0, 0, 0 ); +#if defined ( OLD_SOUNDS ) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving) ); + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseArrived), 1, ATTN_NORM); +#endif + } + } + + ASSERT(m_toggle_state == TS_GOING_UP); + m_toggle_state = TS_AT_TOP; + m_bIsReopening = false; + + // toggle-doors don't come down automatically, they wait for refire. + if (FBitSet(pev->spawnflags, SF_DOOR_NO_AUTO_RETURN)) + { + // Re-instate touch method, movement is complete + if ( !FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) ) + SetTouch( &CBaseDoor::DoorTouch ); + } + else + { + // In flWait seconds, DoorGoDown will fire, unless wait is -1, then door stays open + pev->nextthink = pev->ltime + m_flWait; + SetThink( &CBaseDoor::DoorGoDown ); + + if ( m_flWait == -1 ) + { + pev->nextthink = -1; + } + } + + // Fire the close target (if startopen is set, then "top" is closed) - netname is the close target + if ( pev->netname && (pev->spawnflags & SF_DOOR_START_OPEN) ) + FireTargets( STRING(pev->netname), m_hActivator, this, USE_TOGGLE, 0 ); + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); // this isn't finished +} + + +// +// Starts the door going to its "down" position (simply ToggleData->vecPosition1). +// +void CBaseDoor::DoorGoDown( void ) +{ + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + { + // don't play sounds too often + if ( m_fNextSoundPlay < gpGlobals->time ) + { + Vector v = Center(); + PLAYBACK_EVENT_FULL( FEV_RELIABLE, NULL, m_usDoorGoDown, 0.0, (float *)&v, (float *)&g_vecZero, 0.0, 0.0, ( m_bMoveSnd << 8 ) | ( m_bStopSnd & 0xff ), 0, 0, 0 ); +#if defined ( OLD_SOUNDS ) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving) ); + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving), 1, ATTN_NORM); +#endif + } + } + +#ifdef DOOR_ASSERT + ASSERT(m_toggle_state == TS_AT_TOP); +#endif // DOOR_ASSERT + m_toggle_state = TS_GOING_DOWN; + + SetMoveDone( &CBaseDoor::DoorHitBottom ); + if ( FClassnameIs(pev, "func_door_rotating"))//rotating door + AngularMove( m_vecAngle1, pev->speed); + else + LinearMove( m_vecPosition1, pev->speed); +} + +// +// The door has reached the "down" position. Back to quiescence. +// +void CBaseDoor::DoorHitBottom( void ) +{ + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + { + // don't play sounds too often + if ( m_fNextSoundPlay < gpGlobals->time ) + { + Vector v = Center(); + PLAYBACK_EVENT_FULL( FEV_RELIABLE, NULL, m_usDoorHitBottom, 0.0, (float *)&v, (float *)&g_vecZero, 0.0, 0.0, ( m_bMoveSnd << 8 ) | ( m_bStopSnd & 0xff ), 0, 0, 0 ); +#if defined ( OLD_SOUNDS ) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving) ); + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseArrived), 1, ATTN_NORM); +#endif + } + } + + ASSERT(m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_AT_BOTTOM; + + // Re-instate touch method, cycle is complete + if ( FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) ) + {// use only door + SetTouch ( NULL ); + } + else // touchable door + SetTouch( &CBaseDoor::DoorTouch ); + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); // this isn't finished + + // Fire the close target (if startopen is set, then "top" is closed) - netname is the close target + if ( pev->netname && !(pev->spawnflags & SF_DOOR_START_OPEN) ) + FireTargets( STRING(pev->netname), m_hActivator, this, USE_TOGGLE, 0 ); +} + +void CBaseDoor::Blocked( CBaseEntity *pOther ) +{ + edict_t *pentTarget = NULL; + CBaseDoor *pDoor = NULL; + + + // Hurt the blocker a little. + if ( pev->dmg ) + pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH ); + + // if a door has a negative wait, it would never come back if blocked, + // so let it just squash the object to death real fast + + if (m_flWait >= 0) + { + if (m_toggle_state == TS_GOING_DOWN) + { + DoorGoUp(); + } + else + { + DoorGoDown(); + } + } + + // Don't play blocked sounds too often + if ( m_fNextSoundPlay <= gpGlobals->time ) + { + m_fNextSoundPlay = gpGlobals->time + 0.3; + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving) ); + } + + // Block all door pieces with the same targetname here. + if ( !FStringNull ( pev->targetname ) ) + { + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->targetname)); + + if ( VARS( pentTarget ) != pev ) + { + if (FNullEnt(pentTarget)) + break; + + if ( FClassnameIs ( pentTarget, "func_door" ) || FClassnameIs ( pentTarget, "func_door_rotating" ) ) + { + + pDoor = GetClassPtr( (CBaseDoor *) VARS(pentTarget) ); + + if ( pDoor->m_flWait >= 0) + { + if (pDoor->pev->velocity == pev->velocity && pDoor->pev->avelocity == pev->velocity) + { + // this is the most hacked, evil, bastardized thing I've ever seen. kjb + if ( FClassnameIs ( pentTarget, "func_door" ) ) + {// set origin to realign normal doors + pDoor->pev->origin = pev->origin; + pDoor->pev->velocity = g_vecZero;// stop! + } + else + {// set angles to realign rotating doors + pDoor->pev->angles = pev->angles; + pDoor->pev->avelocity = g_vecZero; + } + } + + if ( pDoor->m_toggle_state == TS_GOING_DOWN) + pDoor->DoorGoUp(); + else + pDoor->DoorGoDown(); + } + } + } + } + } +} + + +/*QUAKED FuncRotDoorSpawn (0 .5 .8) ? START_OPEN REVERSE +DOOR_DONT_LINK TOGGLE X_AXIS Y_AXIS +if two doors touch, they are assumed to be connected and operate as +a unit. + +TOGGLE causes the door to wait in both the start and end states for +a trigger event. + +START_OPEN causes the door to move to its destination when spawned, +and operate in reverse. It is used to temporarily or permanently +close off an area when triggered (not usefull for touch or +takedamage doors). + +You need to have an origin brush as part of this entity. The +center of that brush will be +the point around which it is rotated. It will rotate around the Z +axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"distance" is how many degrees the door will be rotated. +"speed" determines how fast the door moves; default value is 100. + +REVERSE will cause the door to rotate in the opposite direction. + +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote +button or trigger field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"dmg" damage to inflict when blocked (2 default) +"sounds" +0) no sound +1) stone +2) base +3) stone chain +4) screechy metal +*/ +class CRotDoor : public CBaseDoor +{ +public: + void Spawn( void ); + virtual void SetToggleState( int state ); +}; + +LINK_ENTITY_TO_CLASS( func_door_rotating, CRotDoor ); + + +void CRotDoor::Spawn( void ) +{ + Precache(); + // set the axis of rotation + CBaseToggle::AxisDir( pev ); + + // check for clockwise rotation + if ( FBitSet (pev->spawnflags, SF_DOOR_ROTATE_BACKWARDS) ) + pev->movedir = pev->movedir * -1; + + //m_flWait = 2; who the hell did this? (sjb) + m_vecAngle1 = pev->angles; + m_vecAngle2 = pev->angles + pev->movedir * m_flMoveDistance; + + ASSERTSZ(m_vecAngle1 != m_vecAngle2, "rotating door start/end positions are equal"); + + if ( FBitSet (pev->spawnflags, SF_DOOR_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + if (pev->speed == 0) + pev->speed = 100; + +// DOOR_START_OPEN is to allow an entity to be lighted in the closed position +// but spawn in the open position + if ( FBitSet (pev->spawnflags, SF_DOOR_START_OPEN) ) + { // swap pos1 and pos2, put door at pos2, invert movement direction + pev->angles = m_vecAngle2; + Vector vecSav = m_vecAngle1; + m_vecAngle2 = m_vecAngle1; + m_vecAngle1 = vecSav; + pev->movedir = pev->movedir * -1; + } + + m_toggle_state = TS_AT_BOTTOM; + + if ( FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) ) + { + SetTouch ( NULL ); + } + else // touchable button + SetTouch( &CBaseDoor::DoorTouch ); +} + + +void CRotDoor :: SetToggleState( int state ) +{ + if ( state == TS_AT_TOP ) + pev->angles = m_vecAngle2; + else + pev->angles = m_vecAngle1; + + UTIL_SetOrigin( pev, pev->origin ); +} + + +class CMomentaryDoor : public CBaseToggle +{ +public: + void Spawn( void ); + void Precache( void ); + + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + BYTE m_bMoveSnd; // sound a door makes while moving +}; + +LINK_ENTITY_TO_CLASS( momentary_door, CMomentaryDoor ); + +TYPEDESCRIPTION CMomentaryDoor::m_SaveData[] = +{ + DEFINE_FIELD( CMomentaryDoor, m_bMoveSnd, FIELD_CHARACTER ), +}; + +IMPLEMENT_SAVERESTORE( CMomentaryDoor, CBaseToggle ); + +void CMomentaryDoor::Spawn( void ) +{ + SetMovedir (pev); + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + if (pev->speed == 0) + pev->speed = 100; + if (pev->dmg == 0) + pev->dmg = 2; + + m_vecPosition1 = pev->origin; + // Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big + m_vecPosition2 = m_vecPosition1 + (pev->movedir * (fabs( pev->movedir.x * (pev->size.x-2) ) + fabs( pev->movedir.y * (pev->size.y-2) ) + fabs( pev->movedir.z * (pev->size.z-2) ) - m_flLip)); + ASSERTSZ(m_vecPosition1 != m_vecPosition2, "door start/end positions are equal"); + + if ( FBitSet (pev->spawnflags, SF_DOOR_START_OPEN) ) + { // swap pos1 and pos2, put door at pos2 + UTIL_SetOrigin(pev, m_vecPosition2); + m_vecPosition2 = m_vecPosition1; + m_vecPosition1 = pev->origin; + } + SetTouch( NULL ); + + Precache(); +} + +void CMomentaryDoor::Precache( void ) +{ + +// set the door's "in-motion" sound + switch (m_bMoveSnd) + { + case 0: + pev->noiseMoving = ALLOC_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("doors/doormove1.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove1.wav"); + break; + case 2: + PRECACHE_SOUND ("doors/doormove2.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove2.wav"); + break; + case 3: + PRECACHE_SOUND ("doors/doormove3.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove3.wav"); + break; + case 4: + PRECACHE_SOUND ("doors/doormove4.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove4.wav"); + break; + case 5: + PRECACHE_SOUND ("doors/doormove5.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove5.wav"); + break; + case 6: + PRECACHE_SOUND ("doors/doormove6.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove6.wav"); + break; + case 7: + PRECACHE_SOUND ("doors/doormove7.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove7.wav"); + break; + case 8: + PRECACHE_SOUND ("doors/doormove8.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove8.wav"); + break; + default: + pev->noiseMoving = ALLOC_STRING("common/null.wav"); + break; + } +} + +void CMomentaryDoor::KeyValue( KeyValueData *pkvd ) +{ + + if (FStrEq(pkvd->szKeyName, "movesnd")) + { + m_bMoveSnd = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "stopsnd")) + { +// m_bStopSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "healthvalue")) + { +// m_bHealthValue = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CMomentaryDoor::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( useType != USE_SET ) // Momentary buttons will pass down a float in here + return; + + if ( value > 1.0 ) + value = 1.0; + Vector move = m_vecPosition1 + (value * (m_vecPosition2 - m_vecPosition1)); + + Vector delta = move - pev->origin; + float speed = delta.Length() * 10; + + if ( speed != 0 ) + { + // This entity only thinks when it moves, so if it's thinking, it's in the process of moving + // play the sound when it starts moving + if ( pev->nextthink < pev->ltime || pev->nextthink == 0 ) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving), 1, ATTN_NORM); + + LinearMove( move, speed ); + } + +} diff --git a/hlsdk/dmc/dlls/doors.h b/hlsdk/dmc/dlls/doors.h new file mode 100644 index 00000000..80088615 --- /dev/null +++ b/hlsdk/dmc/dlls/doors.h @@ -0,0 +1,33 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef DOORS_H +#define DOORS_H + +// doors +#define SF_DOOR_ROTATE_Y 0 +#define SF_DOOR_START_OPEN 1 +#define SF_DOOR_ROTATE_BACKWARDS 2 +#define SF_DOOR_PASSABLE 8 +#define SF_DOOR_ONEWAY 16 +#define SF_DOOR_NO_AUTO_RETURN 32 +#define SF_DOOR_ROTATE_Z 64 +#define SF_DOOR_ROTATE_X 128 +#define SF_DOOR_USE_ONLY 256 // door must be opened by player's use button. +#define SF_DOOR_NOMONSTERS 512 // Monster can't open +#define SF_DOOR_SILENT 0x80000000 + + + +#endif //DOORS_H diff --git a/hlsdk/dmc/dlls/effects.cpp b/hlsdk/dmc/dlls/effects.cpp new file mode 100644 index 00000000..b46df857 --- /dev/null +++ b/hlsdk/dmc/dlls/effects.cpp @@ -0,0 +1,2268 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "customentity.h" +#include "effects.h" +#include "weapons.h" +#include "decals.h" +#include "func_break.h" +#include "shake.h" + +#define SF_GIBSHOOTER_REPEATABLE 1 // allows a gibshooter to be refired + +#define SF_FUNNEL_REVERSE 1 // funnel effect repels particles instead of attracting them. + + +// Lightning target, just alias landmark +LINK_ENTITY_TO_CLASS( info_target, CPointEntity ); + + +class CBubbling : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + void EXPORT FizzThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + static TYPEDESCRIPTION m_SaveData[]; + + int m_density; + int m_frequency; + int m_bubbleModel; + int m_state; +}; + +LINK_ENTITY_TO_CLASS( env_bubbles, CBubbling ); + +TYPEDESCRIPTION CBubbling::m_SaveData[] = +{ + DEFINE_FIELD( CBubbling, m_density, FIELD_INTEGER ), + DEFINE_FIELD( CBubbling, m_frequency, FIELD_INTEGER ), + DEFINE_FIELD( CBubbling, m_state, FIELD_INTEGER ), + // Let spawn restore this! + // DEFINE_FIELD( CBubbling, m_bubbleModel, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CBubbling, CBaseEntity ); + + +#define SF_BUBBLES_STARTOFF 0x0001 + +void CBubbling::Spawn( void ) +{ + Precache( ); + SET_MODEL( ENT(pev), STRING(pev->model) ); // Set size + + pev->solid = SOLID_NOT; // Remove model & collisions + pev->renderamt = 0; // The engine won't draw this model if this is set to 0 and blending is on + pev->rendermode = kRenderTransTexture; + int speed = pev->speed > 0 ? static_cast(pev->speed) : static_cast(-pev->speed); + + // HACKHACK!!! - Speed in rendercolor + pev->rendercolor.x = speed >> 8; + pev->rendercolor.y = speed & 255; + pev->rendercolor.z = (pev->speed < 0) ? 1 : 0; + + + if ( !(pev->spawnflags & SF_BUBBLES_STARTOFF) ) + { + SetThink( &CBubbling::FizzThink ); + pev->nextthink = gpGlobals->time + 2.0; + m_state = 1; + } + else + m_state = 0; +} + +void CBubbling::Precache( void ) +{ + m_bubbleModel = PRECACHE_MODEL("sprites/bubble.spr"); // Precache bubble sprite +} + + +void CBubbling::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( ShouldToggle( useType, m_state ) ) + m_state = !m_state; + + if ( m_state ) + { + SetThink( &CBubbling::FizzThink ); + pev->nextthink = gpGlobals->time + 0.1; + } + else + { + SetThink( NULL ); + pev->nextthink = 0; + } +} + + +void CBubbling::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "density")) + { + m_density = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "frequency")) + { + m_frequency = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "current")) + { + pev->speed = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +void CBubbling::FizzThink( void ) +{ + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, VecBModelOrigin(pev) ); + WRITE_BYTE( TE_FIZZ ); + WRITE_SHORT( (short)ENTINDEX( edict() ) ); + WRITE_SHORT( (short)m_bubbleModel ); + WRITE_BYTE( m_density ); + MESSAGE_END(); + + if ( m_frequency > 19 ) + pev->nextthink = gpGlobals->time + 0.5; + else + pev->nextthink = gpGlobals->time + 2.5 - (0.1 * m_frequency); +} + +// -------------------------------------------------- +// +// Beams +// +// -------------------------------------------------- + +LINK_ENTITY_TO_CLASS( beam, CBeam ); + +void CBeam::Spawn( void ) +{ + pev->solid = SOLID_NOT; // Remove model & collisions + Precache( ); +} + +void CBeam::Precache( void ) +{ + if ( pev->owner ) + SetStartEntity( ENTINDEX( pev->owner ) ); + if ( pev->aiment ) + SetEndEntity( ENTINDEX( pev->aiment ) ); +} + +void CBeam::SetStartEntity( int entityIndex ) +{ + pev->sequence = (entityIndex & 0x0FFF) | ((pev->sequence&0xF000)<<12); + pev->owner = g_engfuncs.pfnPEntityOfEntIndex( entityIndex ); +} + +void CBeam::SetEndEntity( int entityIndex ) +{ + pev->skin = (entityIndex & 0x0FFF) | ((pev->skin&0xF000)<<12); + pev->aiment = g_engfuncs.pfnPEntityOfEntIndex( entityIndex ); +} + + +// These don't take attachments into account +const Vector &CBeam::GetStartPos( void ) +{ + if ( GetType() == BEAM_ENTS ) + { + edict_t *pent = g_engfuncs.pfnPEntityOfEntIndex( GetStartEntity() ); + return pent->v.origin; + } + return pev->origin; +} + + +const Vector &CBeam::GetEndPos( void ) +{ + int type = GetType(); + if ( type == BEAM_POINTS || type == BEAM_HOSE ) + { + return pev->angles; + } + + edict_t *pent = g_engfuncs.pfnPEntityOfEntIndex( GetEndEntity() ); + if ( pent ) + return pent->v.origin; + return pev->angles; +} + + +CBeam *CBeam::BeamCreate( const char *pSpriteName, int width ) +{ + // Create a new entity with CBeam private data + CBeam *pBeam = GetClassPtr( (CBeam *)NULL ); + pBeam->pev->classname = MAKE_STRING("beam"); + + pBeam->BeamInit( pSpriteName, width ); + + return pBeam; +} + + +void CBeam::BeamInit( const char *pSpriteName, int width ) +{ + pev->flags |= FL_CUSTOMENTITY; + SetColor( 255, 255, 255 ); + SetBrightness( 255 ); + SetNoise( 0 ); + SetFrame( 0 ); + SetScrollRate( 0 ); + pev->model = MAKE_STRING( pSpriteName ); + SetTexture( PRECACHE_MODEL( (char *)pSpriteName ) ); + SetWidth( width ); + pev->skin = 0; + pev->sequence = 0; + pev->rendermode = 0; +} + + +void CBeam::PointsInit( const Vector &start, const Vector &end ) +{ + SetType( BEAM_POINTS ); + SetStartPos( start ); + SetEndPos( end ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + + +void CBeam::HoseInit( const Vector &start, const Vector &direction ) +{ + SetType( BEAM_HOSE ); + SetStartPos( start ); + SetEndPos( direction ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + + +void CBeam::PointEntInit( const Vector &start, int endIndex ) +{ + SetType( BEAM_ENTPOINT ); + SetStartPos( start ); + SetEndEntity( endIndex ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + +void CBeam::EntsInit( int startIndex, int endIndex ) +{ + SetType( BEAM_ENTS ); + SetStartEntity( startIndex ); + SetEndEntity( endIndex ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + + +void CBeam::RelinkBeam( void ) +{ + const Vector &startPos = GetStartPos(), &endPos = GetEndPos(); + + pev->mins.x = min( startPos.x, endPos.x ); + pev->mins.y = min( startPos.y, endPos.y ); + pev->mins.z = min( startPos.z, endPos.z ); + pev->maxs.x = max( startPos.x, endPos.x ); + pev->maxs.y = max( startPos.y, endPos.y ); + pev->maxs.z = max( startPos.z, endPos.z ); + pev->mins = pev->mins - pev->origin; + pev->maxs = pev->maxs - pev->origin; + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( pev, pev->origin ); +} + +#if 0 +void CBeam::SetObjectCollisionBox( void ) +{ + const Vector &startPos = GetStartPos(), &endPos = GetEndPos(); + + pev->absmin.x = min( startPos.x, endPos.x ); + pev->absmin.y = min( startPos.y, endPos.y ); + pev->absmin.z = min( startPos.z, endPos.z ); + pev->absmax.x = max( startPos.x, endPos.x ); + pev->absmax.y = max( startPos.y, endPos.y ); + pev->absmax.z = max( startPos.z, endPos.z ); +} +#endif + + +void CBeam::TriggerTouch( CBaseEntity *pOther ) +{ + if ( pOther->pev->flags & (FL_CLIENT | FL_MONSTER) ) + { + if ( pev->owner ) + { + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + pOwner->Use( pOther, this, USE_TOGGLE, 0 ); + } + ALERT( at_console, "Firing targets!!!\n" ); + } +} + + +CBaseEntity *CBeam::RandomTargetname( const char *szName ) +{ + int total = 0; + + CBaseEntity *pEntity = NULL; + CBaseEntity *pNewEntity = NULL; + while ((pNewEntity = UTIL_FindEntityByTargetname( pNewEntity, szName )) != NULL) + { + total++; + if (RANDOM_LONG(0,total-1) < 1) + pEntity = pNewEntity; + } + return pEntity; +} + + +void CBeam::DoSparks( const Vector &start, const Vector &end ) +{ + if ( pev->spawnflags & (SF_BEAM_SPARKSTART|SF_BEAM_SPARKEND) ) + { + if ( pev->spawnflags & SF_BEAM_SPARKSTART ) + { + UTIL_Sparks( start ); + } + if ( pev->spawnflags & SF_BEAM_SPARKEND ) + { + UTIL_Sparks( end ); + } + } +} + + +class CLightning : public CBeam +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void Activate( void ); + + void EXPORT StrikeThink( void ); + void EXPORT DamageThink( void ); + void RandomArea( void ); + void RandomPoint( Vector &vecSrc ); + void Zap( const Vector &vecSrc, const Vector &vecDest ); + void EXPORT StrikeUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + inline BOOL ServerSide( void ) + { + if ( m_life == 0 && !(pev->spawnflags & SF_BEAM_RING) ) + return TRUE; + return FALSE; + } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void BeamUpdateVars( void ); + + int m_active; + int m_iszStartEntity; + int m_iszEndEntity; + float m_life; + int m_boltWidth; + int m_noiseAmplitude; + int m_brightness; + int m_speed; + float m_restrike; + int m_spriteTexture; + int m_iszSpriteName; + int m_frameStart; + + float m_radius; +}; + +LINK_ENTITY_TO_CLASS( env_lightning, CLightning ); +LINK_ENTITY_TO_CLASS( env_beam, CLightning ); + +// UNDONE: Jay -- This is only a test +#if _DEBUG +class CTripBeam : public CLightning +{ + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS( trip_beam, CTripBeam ); + +void CTripBeam::Spawn( void ) +{ + CLightning::Spawn(); + SetTouch( &CBeam::TriggerTouch ); + pev->solid = SOLID_TRIGGER; + RelinkBeam(); +} +#endif + + + +TYPEDESCRIPTION CLightning::m_SaveData[] = +{ + DEFINE_FIELD( CLightning, m_active, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_iszStartEntity, FIELD_STRING ), + DEFINE_FIELD( CLightning, m_iszEndEntity, FIELD_STRING ), + DEFINE_FIELD( CLightning, m_life, FIELD_FLOAT ), + DEFINE_FIELD( CLightning, m_boltWidth, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_noiseAmplitude, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_brightness, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_speed, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_restrike, FIELD_FLOAT ), + DEFINE_FIELD( CLightning, m_spriteTexture, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_iszSpriteName, FIELD_STRING ), + DEFINE_FIELD( CLightning, m_frameStart, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_radius, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CLightning, CBeam ); + + +void CLightning::Spawn( void ) +{ + if ( FStringNull( m_iszSpriteName ) ) + { + SetThink( &CBaseEntity::SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; // Remove model & collisions + Precache( ); + + pev->dmgtime = gpGlobals->time; + + if ( ServerSide() ) + { + SetThink( NULL ); + if ( pev->dmg > 0 ) + { + SetThink( &CLightning::DamageThink ); + pev->nextthink = gpGlobals->time + 0.1; + } + if ( pev->targetname ) + { + if ( !(pev->spawnflags & SF_BEAM_STARTON) ) + { + pev->effects = EF_NODRAW; + m_active = 0; + pev->nextthink = 0; + } + else + m_active = 1; + + SetUse( &CLightning::ToggleUse ); + } + } + else + { + m_active = 0; + if ( !FStringNull(pev->targetname) ) + { + SetUse( &CLightning::StrikeUse ); + } + if ( FStringNull(pev->targetname) || FBitSet(pev->spawnflags, SF_BEAM_STARTON) ) + { + SetThink( &CLightning::StrikeThink ); + pev->nextthink = gpGlobals->time + 1.0; + } + } +} + +void CLightning::Precache( void ) +{ + m_spriteTexture = PRECACHE_MODEL( (char *)STRING(m_iszSpriteName) ); + CBeam::Precache(); +} + + +void CLightning::Activate( void ) +{ + if ( ServerSide() ) + BeamUpdateVars(); +} + + +void CLightning::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "LightningStart")) + { + m_iszStartEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "LightningEnd")) + { + m_iszEndEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "life")) + { + m_life = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "BoltWidth")) + { + m_boltWidth = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "NoiseAmplitude")) + { + m_noiseAmplitude = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "TextureScroll")) + { + m_speed = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "StrikeTime")) + { + m_restrike = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "texture")) + { + m_iszSpriteName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "framestart")) + { + m_frameStart = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "Radius")) + { + m_radius = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damage")) + { + pev->dmg = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBeam::KeyValue( pkvd ); +} + + +void CLightning::ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_active ) ) + return; + if ( m_active ) + { + m_active = 0; + pev->effects |= EF_NODRAW; + pev->nextthink = 0; + } + else + { + m_active = 1; + pev->effects &= ~EF_NODRAW; + DoSparks( GetStartPos(), GetEndPos() ); + if ( pev->dmg > 0 ) + { + pev->nextthink = gpGlobals->time; + pev->dmgtime = gpGlobals->time; + } + } +} + + +void CLightning::StrikeUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_active ) ) + return; + + if ( m_active ) + { + m_active = 0; + SetThink( NULL ); + } + else + { + SetThink( &CLightning::StrikeThink ); + pev->nextthink = gpGlobals->time + 0.1; + } + + if ( !FBitSet( pev->spawnflags, SF_BEAM_TOGGLE ) ) + SetUse( NULL ); +} + + +int IsPointEntity( CBaseEntity *pEnt ) +{ + if ( !pEnt->pev->modelindex ) + return 1; + if ( FClassnameIs( pEnt->pev, "info_target" ) || FClassnameIs( pEnt->pev, "info_landmark" ) || FClassnameIs( pEnt->pev, "path_corner" ) ) + return 1; + + return 0; +} + + +void CLightning::StrikeThink( void ) +{ + if ( m_life != 0 ) + { + if ( pev->spawnflags & SF_BEAM_RANDOM ) + pev->nextthink = gpGlobals->time + m_life + RANDOM_FLOAT( 0, m_restrike ); + else + pev->nextthink = gpGlobals->time + m_life + m_restrike; + } + m_active = 1; + + if (FStringNull(m_iszEndEntity)) + { + if (FStringNull(m_iszStartEntity)) + { + RandomArea( ); + } + else + { + CBaseEntity *pStart = RandomTargetname( STRING(m_iszStartEntity) ); + if (pStart != NULL) + RandomPoint( pStart->pev->origin ); + else + ALERT( at_console, "env_beam: unknown entity \"%s\"\n", STRING(m_iszStartEntity) ); + } + return; + } + + CBaseEntity *pStart = RandomTargetname( STRING(m_iszStartEntity) ); + CBaseEntity *pEnd = RandomTargetname( STRING(m_iszEndEntity) ); + + if ( pStart != NULL && pEnd != NULL ) + { + if ( IsPointEntity( pStart ) || IsPointEntity( pEnd ) ) + { + if ( pev->spawnflags & SF_BEAM_RING) + { + // don't work + return; + } + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + if ( IsPointEntity( pStart ) || IsPointEntity( pEnd ) ) + { + if ( !IsPointEntity( pEnd ) ) // One point entity must be in pEnd + { + CBaseEntity *pTemp; + pTemp = pStart; + pStart = pEnd; + pEnd = pTemp; + } + if ( !IsPointEntity( pStart ) ) // One sided + { + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( pStart->entindex() ); + WRITE_COORD( pEnd->pev->origin.x); + WRITE_COORD( pEnd->pev->origin.y); + WRITE_COORD( pEnd->pev->origin.z); + } + else + { + WRITE_BYTE( TE_BEAMPOINTS); + WRITE_COORD( pStart->pev->origin.x); + WRITE_COORD( pStart->pev->origin.y); + WRITE_COORD( pStart->pev->origin.z); + WRITE_COORD( pEnd->pev->origin.x); + WRITE_COORD( pEnd->pev->origin.y); + WRITE_COORD( pEnd->pev->origin.z); + } + + + } + else + { + if ( pev->spawnflags & SF_BEAM_RING) + WRITE_BYTE( TE_BEAMRING ); + else + WRITE_BYTE( TE_BEAMENTS ); + WRITE_SHORT( pStart->entindex() ); + WRITE_SHORT( pEnd->entindex() ); + } + + WRITE_SHORT( m_spriteTexture ); + WRITE_BYTE( m_frameStart ); // framestart + WRITE_BYTE( (int)pev->framerate); // framerate + WRITE_BYTE( (int)(m_life*10.0) ); // life + WRITE_BYTE( m_boltWidth ); // width + WRITE_BYTE( m_noiseAmplitude ); // noise + WRITE_BYTE( (int)pev->rendercolor.x ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.y ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.z ); // r, g, b + WRITE_BYTE( (int)pev->renderamt ); // brightness + WRITE_BYTE( m_speed ); // speed + MESSAGE_END(); + DoSparks( pStart->pev->origin, pEnd->pev->origin ); + if ( pev->dmg > 0 ) + { + TraceResult tr; + UTIL_TraceLine( pStart->pev->origin, pEnd->pev->origin, dont_ignore_monsters, NULL, &tr ); + BeamDamageInstant( &tr, pev->dmg ); + } + } +} + + +void CBeam::BeamDamage( TraceResult *ptr ) +{ + RelinkBeam(); + if ( ptr->flFraction != 1.0 && ptr->pHit != NULL ) + { + CBaseEntity *pHit = CBaseEntity::Instance(ptr->pHit); + if ( pHit ) + { + ClearMultiDamage(); + pHit->TraceAttack( pev, pev->dmg * (gpGlobals->time - pev->dmgtime), (ptr->vecEndPos - pev->origin).Normalize(), ptr, DMG_ENERGYBEAM ); + ApplyMultiDamage( pev, pev ); + if ( pev->spawnflags & SF_BEAM_DECALS ) + { + if ( pHit->IsBSPModel() ) + UTIL_DecalTrace( ptr, DECAL_BIGSHOT1 + RANDOM_LONG(0,4) ); + } + } + } + pev->dmgtime = gpGlobals->time; +} + + +void CLightning::DamageThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + TraceResult tr; + UTIL_TraceLine( GetStartPos(), GetEndPos(), dont_ignore_monsters, NULL, &tr ); + BeamDamage( &tr ); +} + + + +void CLightning::Zap( const Vector &vecSrc, const Vector &vecDest ) +{ +#if 1 + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS); + WRITE_COORD(vecSrc.x); + WRITE_COORD(vecSrc.y); + WRITE_COORD(vecSrc.z); + WRITE_COORD(vecDest.x); + WRITE_COORD(vecDest.y); + WRITE_COORD(vecDest.z); + WRITE_SHORT( m_spriteTexture ); + WRITE_BYTE( m_frameStart ); // framestart + WRITE_BYTE( (int)pev->framerate); // framerate + WRITE_BYTE( (int)(m_life*10.0) ); // life + WRITE_BYTE( m_boltWidth ); // width + WRITE_BYTE( m_noiseAmplitude ); // noise + WRITE_BYTE( (int)pev->rendercolor.x ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.y ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.z ); // r, g, b + WRITE_BYTE( (int)pev->renderamt ); // brightness + WRITE_BYTE( m_speed ); // speed + MESSAGE_END(); +#else + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE(TE_LIGHTNING); + WRITE_COORD(vecSrc.x); + WRITE_COORD(vecSrc.y); + WRITE_COORD(vecSrc.z); + WRITE_COORD(vecDest.x); + WRITE_COORD(vecDest.y); + WRITE_COORD(vecDest.z); + WRITE_BYTE(10); + WRITE_BYTE(50); + WRITE_BYTE(40); + WRITE_SHORT(m_spriteTexture); + MESSAGE_END(); +#endif + DoSparks( vecSrc, vecDest ); +} + +void CLightning::RandomArea( void ) +{ + int iLoops = 0; + + for (iLoops = 0; iLoops < 10; iLoops++) + { + Vector vecSrc = pev->origin; + + Vector vecDir1 = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + vecDir1 = vecDir1.Normalize(); + TraceResult tr1; + UTIL_TraceLine( vecSrc, vecSrc + vecDir1 * m_radius, ignore_monsters, ENT(pev), &tr1 ); + + if (tr1.flFraction == 1.0) + continue; + + Vector vecDir2; + do { + vecDir2 = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + } while (DotProduct(vecDir1, vecDir2 ) > 0); + vecDir2 = vecDir2.Normalize(); + TraceResult tr2; + UTIL_TraceLine( vecSrc, vecSrc + vecDir2 * m_radius, ignore_monsters, ENT(pev), &tr2 ); + + if (tr2.flFraction == 1.0) + continue; + + if ((tr1.vecEndPos - tr2.vecEndPos).Length() < m_radius * 0.1) + continue; + + UTIL_TraceLine( tr1.vecEndPos, tr2.vecEndPos, ignore_monsters, ENT(pev), &tr2 ); + + if (tr2.flFraction != 1.0) + continue; + + Zap( tr1.vecEndPos, tr2.vecEndPos ); + + break; + } +} + + +void CLightning::RandomPoint( Vector &vecSrc ) +{ + int iLoops = 0; + + for (iLoops = 0; iLoops < 10; iLoops++) + { + Vector vecDir1 = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + vecDir1 = vecDir1.Normalize(); + TraceResult tr1; + UTIL_TraceLine( vecSrc, vecSrc + vecDir1 * m_radius, ignore_monsters, ENT(pev), &tr1 ); + + if ((tr1.vecEndPos - vecSrc).Length() < m_radius * 0.1) + continue; + + if (tr1.flFraction == 1.0) + continue; + + Zap( vecSrc, tr1.vecEndPos ); + break; + } +} + + + +void CLightning::BeamUpdateVars( void ) +{ + int beamType; + int pointStart, pointEnd; + + edict_t *pStart = FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(m_iszStartEntity) ); + edict_t *pEnd = FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(m_iszEndEntity) ); + pointStart = IsPointEntity( CBaseEntity::Instance(pStart) ); + pointEnd = IsPointEntity( CBaseEntity::Instance(pEnd) ); + + pev->skin = 0; + pev->sequence = 0; + pev->rendermode = 0; + pev->flags |= FL_CUSTOMENTITY; + pev->model = m_iszSpriteName; + SetTexture( m_spriteTexture ); + + beamType = BEAM_ENTS; + if ( pointStart || pointEnd ) + { + if ( !pointStart ) // One point entity must be in pStart + { + edict_t *pTemp; + // Swap start & end + pTemp = pStart; + pStart = pEnd; + pEnd = pTemp; + int swap = pointStart; + pointStart = pointEnd; + pointEnd = swap; + } + if ( !pointEnd ) + beamType = BEAM_ENTPOINT; + else + beamType = BEAM_POINTS; + } + + SetType( beamType ); + if ( beamType == BEAM_POINTS || beamType == BEAM_ENTPOINT || beamType == BEAM_HOSE ) + { + SetStartPos( pStart->v.origin ); + if ( beamType == BEAM_POINTS || beamType == BEAM_HOSE ) + SetEndPos( pEnd->v.origin ); + else + SetEndEntity( ENTINDEX(pEnd) ); + } + else + { + SetStartEntity( ENTINDEX(pStart) ); + SetEndEntity( ENTINDEX(pEnd) ); + } + + RelinkBeam(); + + SetWidth( m_boltWidth ); + SetNoise( m_noiseAmplitude ); + SetFrame( m_frameStart ); + SetScrollRate( m_speed ); + if ( pev->spawnflags & SF_BEAM_SHADEIN ) + SetFlags( BEAM_FSHADEIN ); + else if ( pev->spawnflags & SF_BEAM_SHADEOUT ) + SetFlags( BEAM_FSHADEOUT ); +} + + +LINK_ENTITY_TO_CLASS( env_laser, CLaser ); + +TYPEDESCRIPTION CLaser::m_SaveData[] = +{ + DEFINE_FIELD( CLaser, m_pSprite, FIELD_CLASSPTR ), + DEFINE_FIELD( CLaser, m_iszSpriteName, FIELD_STRING ), + DEFINE_FIELD( CLaser, m_firePosition, FIELD_POSITION_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CLaser, CBeam ); + +void CLaser::Spawn( void ) +{ + if ( FStringNull( pev->model ) ) + { + SetThink( &CBaseEntity::SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; // Remove model & collisions + Precache( ); + + SetThink( &CLaser::StrikeThink ); + pev->flags |= FL_CUSTOMENTITY; + + PointsInit( pev->origin, pev->origin ); + + if ( !m_pSprite && m_iszSpriteName ) + m_pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteName), pev->origin, TRUE ); + else + m_pSprite = NULL; + + if ( m_pSprite ) + m_pSprite->SetTransparency( kRenderGlow, static_cast(pev->rendercolor.x), static_cast(pev->rendercolor.y), static_cast(pev->rendercolor.z), static_cast(pev->renderamt), static_cast(pev->renderfx) ); + + if ( pev->targetname && !(pev->spawnflags & SF_BEAM_STARTON) ) + TurnOff(); + else + TurnOn(); +} + +void CLaser::Precache( void ) +{ + pev->modelindex = PRECACHE_MODEL( (char *)STRING(pev->model) ); + if ( m_iszSpriteName ) + PRECACHE_MODEL( (char *)STRING(m_iszSpriteName) ); +} + + +void CLaser::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "LaserTarget")) + { + pev->message = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "width")) + { + SetWidth( atoi(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "NoiseAmplitude")) + { + SetNoise( atoi(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "TextureScroll")) + { + SetScrollRate( atoi(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "texture")) + { + pev->model = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "EndSprite")) + { + m_iszSpriteName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "framestart")) + { + pev->frame = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damage")) + { + pev->dmg = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBeam::KeyValue( pkvd ); +} + + +int CLaser::IsOn( void ) +{ + if (pev->effects & EF_NODRAW) + return 0; + return 1; +} + + +void CLaser::TurnOff( void ) +{ + pev->effects |= EF_NODRAW; + pev->nextthink = 0; + if ( m_pSprite ) + m_pSprite->TurnOff(); +} + + +void CLaser::TurnOn( void ) +{ + pev->effects &= ~EF_NODRAW; + if ( m_pSprite ) + m_pSprite->TurnOn(); + pev->dmgtime = gpGlobals->time; + pev->nextthink = gpGlobals->time; +} + + +void CLaser::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int active = IsOn(); + + if ( !ShouldToggle( useType, active ) ) + return; + if ( active ) + { + TurnOff(); + } + else + { + TurnOn(); + } +} + + +void CLaser::FireAtPoint( TraceResult &tr ) +{ + SetEndPos( tr.vecEndPos ); + if ( m_pSprite ) + UTIL_SetOrigin( m_pSprite->pev, tr.vecEndPos ); + + BeamDamage( &tr ); + DoSparks( GetStartPos(), tr.vecEndPos ); +} + +void CLaser::StrikeThink( void ) +{ + CBaseEntity *pEnd = RandomTargetname( STRING(pev->message) ); + + if ( pEnd ) + m_firePosition = pEnd->pev->origin; + + TraceResult tr; + + UTIL_TraceLine( pev->origin, m_firePosition, dont_ignore_monsters, NULL, &tr ); + FireAtPoint( tr ); + pev->nextthink = gpGlobals->time + 0.1; +} + + + +class CGlow : public CPointEntity +{ +public: + void Spawn( void ); + void Think( void ); + void Animate( float frames ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + float m_lastTime; + float m_maxFrame; +}; + +LINK_ENTITY_TO_CLASS( env_glow, CGlow ); + +TYPEDESCRIPTION CGlow::m_SaveData[] = +{ + DEFINE_FIELD( CGlow, m_lastTime, FIELD_TIME ), + DEFINE_FIELD( CGlow, m_maxFrame, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CGlow, CPointEntity ); + +void CGlow::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; + + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; + if ( m_maxFrame > 1.0 && pev->framerate != 0 ) + pev->nextthink = gpGlobals->time + 0.1; + + m_lastTime = gpGlobals->time; +} + + +void CGlow::Think( void ) +{ + Animate( pev->framerate * (gpGlobals->time - m_lastTime) ); + + pev->nextthink = gpGlobals->time + 0.1; + m_lastTime = gpGlobals->time; +} + + +void CGlow::Animate( float frames ) +{ + if ( m_maxFrame > 0 ) + pev->frame = fmod( pev->frame + frames, m_maxFrame ); +} + + +LINK_ENTITY_TO_CLASS( env_sprite, CSprite ); + +TYPEDESCRIPTION CSprite::m_SaveData[] = +{ + DEFINE_FIELD( CSprite, m_lastTime, FIELD_TIME ), + DEFINE_FIELD( CSprite, m_maxFrame, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CSprite, CPointEntity ); + +void CSprite::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; + + Precache(); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; + if ( pev->targetname && !(pev->spawnflags & SF_SPRITE_STARTON) ) + TurnOff(); + else + TurnOn(); + + // Worldcraft only sets y rotation, copy to Z + if ( pev->angles.y != 0 && pev->angles.z == 0 ) + { + pev->angles.z = pev->angles.y; + pev->angles.y = 0; + } +} + + +void CSprite::Precache( void ) +{ + PRECACHE_MODEL( (char *)STRING(pev->model) ); + + // Reset attachment after save/restore + if ( pev->aiment ) + SetAttachment( pev->aiment, pev->body ); + else + { + // Clear attachment + pev->skin = 0; + pev->body = 0; + } +} + + +void CSprite::SpriteInit( const char *pSpriteName, const Vector &origin ) +{ + pev->model = MAKE_STRING(pSpriteName); + pev->origin = origin; + Spawn(); +} + +CSprite *CSprite::SpriteCreate( const char *pSpriteName, const Vector &origin, BOOL animate ) +{ + CSprite *pSprite = GetClassPtr( (CSprite *)NULL ); + pSprite->SpriteInit( pSpriteName, origin ); + pSprite->pev->classname = MAKE_STRING("env_sprite"); + pSprite->pev->solid = SOLID_NOT; + pSprite->pev->movetype = MOVETYPE_NOCLIP; + if ( animate ) + pSprite->TurnOn(); + + return pSprite; +} + + +void CSprite::AnimateThink( void ) +{ + Animate( pev->framerate * (gpGlobals->time - m_lastTime) ); + + pev->nextthink = gpGlobals->time + 0.1; + m_lastTime = gpGlobals->time; +} + +void CSprite::AnimateUntilDead( void ) +{ + if ( gpGlobals->time > pev->dmgtime ) + UTIL_Remove(this); + else + { + AnimateThink(); + pev->nextthink = gpGlobals->time; + } +} + +void CSprite::Expand( float scaleSpeed, float fadeSpeed ) +{ + pev->speed = scaleSpeed; + pev->health = fadeSpeed; + SetThink( &CSprite::ExpandThink ); + + pev->nextthink = gpGlobals->time; + m_lastTime = gpGlobals->time; +} + + +void CSprite::ExpandThink( void ) +{ + float frametime = gpGlobals->time - m_lastTime; + pev->scale += pev->speed * frametime; + pev->renderamt -= pev->health * frametime; + if ( pev->renderamt <= 0 ) + { + pev->renderamt = 0; + UTIL_Remove( this ); + } + else + { + pev->nextthink = gpGlobals->time + 0.1; + m_lastTime = gpGlobals->time; + } +} + + +void CSprite::Animate( float frames ) +{ + pev->frame += frames; + if ( pev->frame > m_maxFrame ) + { + if ( pev->spawnflags & SF_SPRITE_ONCE ) + { + TurnOff(); + } + else + { + if ( m_maxFrame > 0 ) + pev->frame = fmod( pev->frame, m_maxFrame ); + } + } +} + + +void CSprite::TurnOff( void ) +{ + pev->effects = EF_NODRAW; + pev->nextthink = 0; +} + + +void CSprite::TurnOn( void ) +{ + pev->effects = 0; + if ( (pev->framerate && m_maxFrame > 1.0) || (pev->spawnflags & SF_SPRITE_ONCE) ) + { + SetThink( &CSprite::AnimateThink ); + pev->nextthink = gpGlobals->time; + m_lastTime = gpGlobals->time; + } + pev->frame = 0; +} + + +void CSprite::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int on = pev->effects != EF_NODRAW; + if ( ShouldToggle( useType, on ) ) + { + if ( on ) + { + TurnOff(); + } + else + { + TurnOn(); + } + } +} + + +class CGibShooter : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT ShootThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual CGib *CreateGib( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_iGibs; + int m_iGibCapacity; + int m_iGibMaterial; + int m_iGibModelIndex; + float m_flGibVelocity; + float m_flVariance; + float m_flGibLife; +}; + +TYPEDESCRIPTION CGibShooter::m_SaveData[] = +{ + DEFINE_FIELD( CGibShooter, m_iGibs, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_iGibCapacity, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_iGibMaterial, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_iGibModelIndex, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_flGibVelocity, FIELD_FLOAT ), + DEFINE_FIELD( CGibShooter, m_flVariance, FIELD_FLOAT ), + DEFINE_FIELD( CGibShooter, m_flGibLife, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CGibShooter, CBaseDelay ); +LINK_ENTITY_TO_CLASS( gibshooter, CGibShooter ); + + +void CGibShooter :: Precache ( void ) +{ + if ( g_Language == LANGUAGE_GERMAN ) + { + m_iGibModelIndex = PRECACHE_MODEL ("models/germanygibs.mdl"); + } + else + { + m_iGibModelIndex = PRECACHE_MODEL ("models/hgibs.mdl"); + } +} + + +void CGibShooter::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iGibs")) + { + m_iGibs = m_iGibCapacity = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flVelocity")) + { + m_flGibVelocity = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flVariance")) + { + m_flVariance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flGibLife")) + { + m_flGibLife = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + { + CBaseDelay::KeyValue( pkvd ); + } +} + +void CGibShooter::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink( &CGibShooter::ShootThink ); + pev->nextthink = gpGlobals->time; +} + +void CGibShooter::Spawn( void ) +{ + Precache(); + + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; + + if ( m_flDelay == 0 ) + { + m_flDelay = 0.1; + } + + if ( m_flGibLife == 0 ) + { + m_flGibLife = 25; + } + + SetMovedir ( pev ); + pev->body = MODEL_FRAMES( m_iGibModelIndex ); +} + + +CGib *CGibShooter :: CreateGib ( void ) +{ + if ( CVAR_GET_FLOAT("violence_hgibs") == 0 ) + return NULL; + + CGib *pGib = GetClassPtr( (CGib *)NULL ); + pGib->Spawn( "models/hgibs.mdl" ); + pGib->m_bloodColor = BLOOD_COLOR_RED; + + if ( pev->body <= 1 ) + { + ALERT ( at_aiconsole, "GibShooter Body is <= 1!\n" ); + } + + pGib->pev->body = RANDOM_LONG ( 1, pev->body - 1 );// avoid throwing random amounts of the 0th gib. (skull). + + return pGib; +} + + +void CGibShooter :: ShootThink ( void ) +{ + pev->nextthink = gpGlobals->time + m_flDelay; + + Vector vecShootDir; + + vecShootDir = pev->movedir; + + vecShootDir = vecShootDir + gpGlobals->v_right * RANDOM_FLOAT( -1, 1) * m_flVariance;; + vecShootDir = vecShootDir + gpGlobals->v_forward * RANDOM_FLOAT( -1, 1) * m_flVariance;; + vecShootDir = vecShootDir + gpGlobals->v_up * RANDOM_FLOAT( -1, 1) * m_flVariance;; + + vecShootDir = vecShootDir.Normalize(); + CGib *pGib = CreateGib(); + + if ( pGib ) + { + pGib->pev->origin = pev->origin; + pGib->pev->velocity = vecShootDir * m_flGibVelocity; + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + + float thinkTime = pGib->pev->nextthink - gpGlobals->time; + + pGib->m_lifeTime = (m_flGibLife * RANDOM_FLOAT( 0.95, 1.05 )); // +/- 5% + if ( pGib->m_lifeTime < thinkTime ) + { + pGib->pev->nextthink = gpGlobals->time + pGib->m_lifeTime; + pGib->m_lifeTime = 0; + } + + } + + if ( --m_iGibs <= 0 ) + { + if ( pev->spawnflags & SF_GIBSHOOTER_REPEATABLE ) + { + m_iGibs = m_iGibCapacity; + SetThink ( NULL ); + pev->nextthink = gpGlobals->time; + } + else + { + SetThink ( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time; + } + } +} + + +class CEnvShooter : public CGibShooter +{ + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + CGib *CreateGib( void ); +}; + +LINK_ENTITY_TO_CLASS( env_shooter, CEnvShooter ); + +void CEnvShooter :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "shootmodel")) + { + pev->model = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "shootsounds")) + { + int iNoise = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + switch( iNoise ) + { + case 0: + m_iGibMaterial = matGlass; + break; + case 1: + m_iGibMaterial = matWood; + break; + case 2: + m_iGibMaterial = matMetal; + break; + case 3: + m_iGibMaterial = matFlesh; + break; + case 4: + m_iGibMaterial = matRocks; + break; + + default: + case -1: + m_iGibMaterial = matNone; + break; + } + } + else + { + CGibShooter::KeyValue( pkvd ); + } +} + + +void CEnvShooter :: Precache ( void ) +{ + m_iGibModelIndex = PRECACHE_MODEL( (char *)STRING(pev->model) ); + CBreakable::MaterialSoundPrecache( (Materials)m_iGibMaterial ); +} + + +CGib *CEnvShooter :: CreateGib ( void ) +{ + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + pGib->Spawn( STRING(pev->model) ); + + int bodyPart = 0; + + if ( pev->body > 1 ) + bodyPart = RANDOM_LONG( 0, pev->body-1 ); + + pGib->pev->body = bodyPart; + pGib->m_bloodColor = DONT_BLEED; + pGib->m_material = m_iGibMaterial; + + pGib->pev->rendermode = pev->rendermode; + pGib->pev->renderamt = pev->renderamt; + pGib->pev->rendercolor = pev->rendercolor; + pGib->pev->renderfx = pev->renderfx; + pGib->pev->scale = pev->scale; + pGib->pev->skin = pev->skin; + + return pGib; +} + + + + +class CTestEffect : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + // void KeyValue( KeyValueData *pkvd ); + void EXPORT TestThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int m_iLoop; + int m_iBeam; + CBeam *m_pBeam[24]; + float m_flBeamTime[24]; + float m_flStartTime; +}; + + +LINK_ENTITY_TO_CLASS( test_effect, CTestEffect ); + +void CTestEffect::Spawn( void ) +{ + Precache( ); +} + +void CTestEffect::Precache( void ) +{ + PRECACHE_MODEL( "sprites/lgtning.spr" ); +} + +void CTestEffect::TestThink( void ) +{ + int i; + float t = (gpGlobals->time - m_flStartTime); + + if (m_iBeam < 24) + { + CBeam *pbeam = CBeam::BeamCreate( "sprites/lgtning.spr", 100 ); + + TraceResult tr; + + Vector vecSrc = pev->origin; + Vector vecDir = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + vecDir = vecDir.Normalize(); + UTIL_TraceLine( vecSrc, vecSrc + vecDir * 128, ignore_monsters, ENT(pev), &tr); + + pbeam->PointsInit( vecSrc, tr.vecEndPos ); + // pbeam->SetColor( 80, 100, 255 ); + pbeam->SetColor( 255, 180, 100 ); + pbeam->SetWidth( 100 ); + pbeam->SetScrollRate( 12 ); + + m_flBeamTime[m_iBeam] = gpGlobals->time; + m_pBeam[m_iBeam] = pbeam; + m_iBeam++; + +#if 0 + Vector vecMid = (vecSrc + tr.vecEndPos) * 0.5; + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE(TE_DLIGHT); + WRITE_COORD(vecMid.x); // X + WRITE_COORD(vecMid.y); // Y + WRITE_COORD(vecMid.z); // Z + WRITE_BYTE( 20 ); // radius * 0.1 + WRITE_BYTE( 255 ); // r + WRITE_BYTE( 180 ); // g + WRITE_BYTE( 100 ); // b + WRITE_BYTE( 20 ); // time * 10 + WRITE_BYTE( 0 ); // decay * 0.1 + MESSAGE_END( ); +#endif + } + + if (t < 3.0) + { + for (i = 0; i < m_iBeam; i++) + { + t = (gpGlobals->time - m_flBeamTime[i]) / ( 3 + m_flStartTime - m_flBeamTime[i]); + m_pBeam[i]->SetBrightness( static_cast(255 * t) ); + // m_pBeam[i]->SetScrollRate( 20 * t ); + } + pev->nextthink = gpGlobals->time + 0.1; + } + else + { + for (i = 0; i < m_iBeam; i++) + { + UTIL_Remove( m_pBeam[i] ); + } + m_flStartTime = gpGlobals->time; + m_iBeam = 0; + // pev->nextthink = gpGlobals->time; + SetThink( NULL ); + } +} + + +void CTestEffect::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink( &CTestEffect::TestThink ); + pev->nextthink = gpGlobals->time + 0.1; + m_flStartTime = gpGlobals->time; +} + + + +// Blood effects +class CBlood : public CPointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline int Color( void ) { return pev->impulse; } + inline float BloodAmount( void ) { return pev->dmg; } + + inline void SetColor( int color ) { pev->impulse = color; } + inline void SetBloodAmount( float amount ) { pev->dmg = amount; } + + Vector Direction( void ); + Vector BloodPosition( CBaseEntity *pActivator ); + +private: +}; + +LINK_ENTITY_TO_CLASS( env_blood, CBlood ); + + + +#define SF_BLOOD_RANDOM 0x0001 +#define SF_BLOOD_STREAM 0x0002 +#define SF_BLOOD_PLAYER 0x0004 +#define SF_BLOOD_DECAL 0x0008 + +void CBlood::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; + SetMovedir( pev ); +} + + +void CBlood::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "color")) + { + int color = atoi(pkvd->szValue); + switch( color ) + { + case 1: + SetColor( BLOOD_COLOR_YELLOW ); + break; + default: + SetColor( BLOOD_COLOR_RED ); + break; + } + + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "amount")) + { + SetBloodAmount( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +Vector CBlood::Direction( void ) +{ + if ( pev->spawnflags & SF_BLOOD_RANDOM ) + return UTIL_RandomBloodVector(); + + return pev->movedir; +} + + +Vector CBlood::BloodPosition( CBaseEntity *pActivator ) +{ + if ( pev->spawnflags & SF_BLOOD_PLAYER ) + { + edict_t *pPlayer; + + if ( pActivator && pActivator->IsPlayer() ) + { + pPlayer = pActivator->edict(); + } + else + pPlayer = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + if ( pPlayer ) + return (pPlayer->v.origin + pPlayer->v.view_ofs) + Vector( RANDOM_FLOAT(-10,10), RANDOM_FLOAT(-10,10), RANDOM_FLOAT(-10,10) ); + } + + return pev->origin; +} + + +void CBlood::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->spawnflags & SF_BLOOD_STREAM ) + UTIL_BloodStream( BloodPosition(pActivator), Direction(), (Color() == BLOOD_COLOR_RED) ? 70 : Color(), static_cast(BloodAmount()) ); + else + UTIL_BloodDrips( BloodPosition(pActivator), Direction(), Color(), static_cast(BloodAmount()) ); + + if ( pev->spawnflags & SF_BLOOD_DECAL ) + { + Vector forward = Direction(); + Vector start = BloodPosition( pActivator ); + TraceResult tr; + + UTIL_TraceLine( start, start + forward * BloodAmount() * 2, ignore_monsters, NULL, &tr ); + if ( tr.flFraction != 1.0 ) + UTIL_BloodDecalTrace( &tr, Color() ); + } +} + + + +// Screen shake +class CShake : public CPointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline float Amplitude( void ) { return pev->scale; } + inline float Frequency( void ) { return pev->dmg_save; } + inline float Duration( void ) { return pev->dmg_take; } + inline float Radius( void ) { return pev->dmg; } + + inline void SetAmplitude( float amplitude ) { pev->scale = amplitude; } + inline void SetFrequency( float frequency ) { pev->dmg_save = frequency; } + inline void SetDuration( float duration ) { pev->dmg_take = duration; } + inline void SetRadius( float radius ) { pev->dmg = radius; } +private: +}; + +LINK_ENTITY_TO_CLASS( env_shake, CShake ); + +// pev->scale is amplitude +// pev->dmg_save is frequency +// pev->dmg_take is duration +// pev->dmg is radius +// radius of 0 means all players +// NOTE: UTIL_ScreenShake() will only shake players who are on the ground + +#define SF_SHAKE_EVERYONE 0x0001 // Don't check radius +// UNDONE: These don't work yet +#define SF_SHAKE_DISRUPT 0x0002 // Disrupt controls +#define SF_SHAKE_INAIR 0x0004 // Shake players in air + +void CShake::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; + + if ( pev->spawnflags & SF_SHAKE_EVERYONE ) + pev->dmg = 0; +} + + +void CShake::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "amplitude")) + { + SetAmplitude( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "frequency")) + { + SetFrequency( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "duration")) + { + SetDuration( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "radius")) + { + SetRadius( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CShake::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + UTIL_ScreenShake( pev->origin, Amplitude(), Frequency(), Duration(), Radius() ); +} + + +class CFade : public CPointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline float Duration( void ) { return pev->dmg_take; } + inline float HoldTime( void ) { return pev->dmg_save; } + + inline void SetDuration( float duration ) { pev->dmg_take = duration; } + inline void SetHoldTime( float hold ) { pev->dmg_save = hold; } +private: +}; + +LINK_ENTITY_TO_CLASS( env_fade, CFade ); + +// pev->dmg_take is duration +// pev->dmg_save is hold duration +#define SF_FADE_IN 0x0001 // Fade in, not out +#define SF_FADE_MODULATE 0x0002 // Modulate, don't blend +#define SF_FADE_ONLYONE 0x0004 + +void CFade::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; +} + + +void CFade::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "duration")) + { + SetDuration( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "holdtime")) + { + SetHoldTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CFade::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int fadeFlags = 0; + + if ( !(pev->spawnflags & SF_FADE_IN) ) + fadeFlags |= FFADE_OUT; + + if ( pev->spawnflags & SF_FADE_MODULATE ) + fadeFlags |= FFADE_MODULATE; + + if ( pev->spawnflags & SF_FADE_ONLYONE ) + { + if ( pActivator->IsNetClient() ) + { + UTIL_ScreenFade( pActivator, pev->rendercolor, Duration(), HoldTime(), static_cast(pev->renderamt), fadeFlags ); + } + } + else + { + UTIL_ScreenFadeAll( pev->rendercolor, Duration(), HoldTime(), static_cast(pev->renderamt), fadeFlags ); + } + SUB_UseTargets( this, USE_TOGGLE, 0 ); +} + + +class CMessage : public CPointEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); +private: +}; + +LINK_ENTITY_TO_CLASS( env_message, CMessage ); + + +void CMessage::Spawn( void ) +{ + Precache(); + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + switch( pev->impulse ) + { + case 1: // Medium radius + pev->speed = ATTN_STATIC; + break; + + case 2: // Large radius + pev->speed = ATTN_NORM; + break; + + case 3: //EVERYWHERE + pev->speed = ATTN_NONE; + break; + + default: + case 0: // Small radius + pev->speed = ATTN_IDLE; + break; + } + pev->impulse = 0; + + // No volume, use normal + if ( pev->scale <= 0 ) + pev->scale = 1.0; +} + + +void CMessage::Precache( void ) +{ + if ( pev->noise ) + PRECACHE_SOUND( (char *)STRING(pev->noise) ); +} + +void CMessage::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "messagesound")) + { + pev->noise = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "messagevolume")) + { + pev->scale = atof(pkvd->szValue) * 0.1; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "messageattenuation")) + { + pev->impulse = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CMessage::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBaseEntity *pPlayer = NULL; + + if ( pev->spawnflags & SF_MESSAGE_ALL ) + UTIL_ShowMessageAll( STRING(pev->message) ); + else + { + if ( pActivator && pActivator->IsPlayer() ) + pPlayer = pActivator; + else + { + pPlayer = CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) ); + } + if ( pPlayer ) + UTIL_ShowMessage( STRING(pev->message), pPlayer ); + } + if ( pev->noise ) + { + EMIT_SOUND( edict(), CHAN_BODY, STRING(pev->noise), pev->scale, pev->speed ); + } + if ( pev->spawnflags & SF_MESSAGE_ONCE ) + UTIL_Remove( this ); + + SUB_UseTargets( this, USE_TOGGLE, 0 ); +} + + + +//========================================================= +// FunnelEffect +//========================================================= +class CEnvFunnel : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int m_iSprite; // Don't save, precache +}; + +void CEnvFunnel :: Precache ( void ) +{ + m_iSprite = PRECACHE_MODEL ( "sprites/flare6.spr" ); +} + +LINK_ENTITY_TO_CLASS( env_funnel, CEnvFunnel ); + +void CEnvFunnel::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_LARGEFUNNEL ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( m_iSprite ); + + if ( pev->spawnflags & SF_FUNNEL_REVERSE )// funnel flows in reverse? + { + WRITE_SHORT( 1 ); + } + else + { + WRITE_SHORT( 0 ); + } + + + MESSAGE_END(); + + SetThink( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time; +} + +void CEnvFunnel::Spawn( void ) +{ + Precache(); + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; +} + +//========================================================= +// Beverage Dispenser +// overloaded pev->frags, is now a flag for whether or not a can is stuck in the dispenser. +// overloaded pev->health, is now how many cans remain in the machine. +//========================================================= +class CEnvBeverage : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; + +void CEnvBeverage :: Precache ( void ) +{ + PRECACHE_MODEL( "models/can.mdl" ); + PRECACHE_SOUND( "weapons/g_bounce3.wav" ); +} + +LINK_ENTITY_TO_CLASS( env_beverage, CEnvBeverage ); + +void CEnvBeverage::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->frags != 0 || pev->health <= 0 ) + { + // no more cans while one is waiting in the dispenser, or if I'm out of cans. + return; + } + + CBaseEntity *pCan = CBaseEntity::Create( "item_sodacan", pev->origin, pev->angles, edict() ); + + if ( pev->skin == 6 ) + { + // random + pCan->pev->skin = RANDOM_LONG( 0, 5 ); + } + else + { + pCan->pev->skin = pev->skin; + } + + pev->frags = 1; + pev->health--; + + //SetThink (SUB_Remove); + //pev->nextthink = gpGlobals->time; +} + +void CEnvBeverage::Spawn( void ) +{ + Precache(); + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; + pev->frags = 0; + + if ( pev->health == 0 ) + { + pev->health = 10; + } +} + +//========================================================= +// Soda can +//========================================================= +class CItemSoda : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT CanThink ( void ); + void EXPORT CanTouch ( CBaseEntity *pOther ); +}; + +void CItemSoda :: Precache ( void ) +{ +} + +LINK_ENTITY_TO_CLASS( item_sodacan, CItemSoda ); + +void CItemSoda::Spawn( void ) +{ + Precache(); + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_TOSS; + + SET_MODEL ( ENT(pev), "models/can.mdl" ); + UTIL_SetSize ( pev, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ) ); + + SetThink (&CItemSoda::CanThink); + pev->nextthink = gpGlobals->time + 0.5; +} + +void CItemSoda::CanThink ( void ) +{ + EMIT_SOUND (ENT(pev), CHAN_WEAPON, "weapons/g_bounce3.wav", 1, ATTN_NORM ); + + pev->solid = SOLID_TRIGGER; + UTIL_SetSize ( pev, Vector ( -8, -8, 0 ), Vector ( 8, 8, 8 ) ); + SetThink ( NULL ); + SetTouch ( &CItemSoda::CanTouch ); +} + +void CItemSoda::CanTouch ( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + { + return; + } + + // spoit sound here + + pOther->TakeHealth( 1, DMG_GENERIC );// a bit of health. + + if ( !FNullEnt( pev->owner ) ) + { + // tell the machine the can was taken + pev->owner->v.frags = 0; + } + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = EF_NODRAW; + SetTouch ( NULL ); + SetThink ( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time; +} diff --git a/hlsdk/dmc/dlls/effects.h b/hlsdk/dmc/dlls/effects.h new file mode 100644 index 00000000..bcfc78ae --- /dev/null +++ b/hlsdk/dmc/dlls/effects.h @@ -0,0 +1,209 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef EFFECTS_H +#define EFFECTS_H + +#define SF_BEAM_STARTON 0x0001 +#define SF_BEAM_TOGGLE 0x0002 +#define SF_BEAM_RANDOM 0x0004 +#define SF_BEAM_RING 0x0008 +#define SF_BEAM_SPARKSTART 0x0010 +#define SF_BEAM_SPARKEND 0x0020 +#define SF_BEAM_DECALS 0x0040 +#define SF_BEAM_SHADEIN 0x0080 +#define SF_BEAM_SHADEOUT 0x0100 +#define SF_BEAM_TEMPORARY 0x8000 + +#define SF_SPRITE_STARTON 0x0001 +#define SF_SPRITE_ONCE 0x0002 +#define SF_SPRITE_TEMPORARY 0x8000 + +class CSprite : public CPointEntity +{ +public: + void Spawn( void ); + void Precache( void ); + + int ObjectCaps( void ) + { + int flags = 0; + if ( pev->spawnflags & SF_SPRITE_TEMPORARY ) + flags = FCAP_DONT_SAVE; + return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | flags; + } + void EXPORT AnimateThink( void ); + void EXPORT ExpandThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Animate( float frames ); + void Expand( float scaleSpeed, float fadeSpeed ); + void SpriteInit( const char *pSpriteName, const Vector &origin ); + + inline void SetAttachment( edict_t *pEntity, int attachment ) + { + if ( pEntity ) + { + pev->skin = ENTINDEX(pEntity); + pev->body = attachment; + pev->aiment = pEntity; + pev->movetype = MOVETYPE_FOLLOW; + } + } + void TurnOff( void ); + void TurnOn( void ); + inline float Frames( void ) { return m_maxFrame; } + inline void SetTransparency( int rendermode, int r, int g, int b, int a, int fx ) + { + pev->rendermode = rendermode; + pev->rendercolor.x = r; + pev->rendercolor.y = g; + pev->rendercolor.z = b; + pev->renderamt = a; + pev->renderfx = fx; + } + inline void SetTexture( int spriteIndex ) { pev->modelindex = spriteIndex; } + inline void SetScale( float scale ) { pev->scale = scale; } + inline void SetColor( int r, int g, int b ) { pev->rendercolor.x = r; pev->rendercolor.y = g; pev->rendercolor.z = b; } + inline void SetBrightness( int brightness ) { pev->renderamt = brightness; } + + inline void AnimateAndDie( float framerate ) + { + SetThink(&CSprite::AnimateUntilDead); + pev->framerate = framerate; + pev->dmgtime = gpGlobals->time + (m_maxFrame / framerate); + pev->nextthink = gpGlobals->time; + } + + void EXPORT AnimateUntilDead( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + static CSprite *SpriteCreate( const char *pSpriteName, const Vector &origin, BOOL animate ); + +private: + + float m_lastTime; + float m_maxFrame; +}; + + +class CBeam : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + int ObjectCaps( void ) + { + int flags = 0; + if ( pev->spawnflags & SF_BEAM_TEMPORARY ) + flags = FCAP_DONT_SAVE; + return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | flags; + } + + void EXPORT TriggerTouch( CBaseEntity *pOther ); + + // These functions are here to show the way beams are encoded as entities. + // Encoding beams as entities simplifies their management in the client/server architecture + inline void SetType( int type ) { pev->rendermode = (pev->rendermode & 0xF0) | (type&0x0F); } + inline void SetFlags( int flags ) { pev->rendermode = (pev->rendermode & 0x0F) | (flags&0xF0); } + inline void SetStartPos( const Vector& pos ) { pev->origin = pos; } + inline void SetEndPos( const Vector& pos ) { pev->angles = pos; } + void SetStartEntity( int entityIndex ); + void SetEndEntity( int entityIndex ); + + inline void SetStartAttachment( int attachment ) { pev->sequence = (pev->sequence & 0x0FFF) | ((attachment&0xF)<<12); } + inline void SetEndAttachment( int attachment ) { pev->skin = (pev->skin & 0x0FFF) | ((attachment&0xF)<<12); } + + inline void SetTexture( int spriteIndex ) { pev->modelindex = spriteIndex; } + inline void SetWidth( int width ) { pev->scale = width; } + inline void SetNoise( int amplitude ) { pev->body = amplitude; } + inline void SetColor( int r, int g, int b ) { pev->rendercolor.x = r; pev->rendercolor.y = g; pev->rendercolor.z = b; } + inline void SetBrightness( int brightness ) { pev->renderamt = brightness; } + inline void SetFrame( float frame ) { pev->frame = frame; } + inline void SetScrollRate( int speed ) { pev->animtime = speed; } + + inline int GetType( void ) { return pev->rendermode & 0x0F; } + inline int GetFlags( void ) { return pev->rendermode & 0xF0; } + inline int GetStartEntity( void ) { return pev->sequence & 0xFFF; } + inline int GetEndEntity( void ) { return pev->skin & 0xFFF; } + + const Vector &GetStartPos( void ); + const Vector &GetEndPos( void ); + + Vector Center( void ) { return (GetStartPos() + GetEndPos()) * 0.5; }; // center point of beam + + inline int GetTexture( void ) { return pev->modelindex; } + inline int GetWidth( void ) { return static_cast(pev->scale); } + inline int GetNoise( void ) { return pev->body; } + // inline void GetColor( int r, int g, int b ) { pev->rendercolor.x = r; pev->rendercolor.y = g; pev->rendercolor.z = b; } + inline int GetBrightness( void ) { return static_cast(pev->renderamt); } + inline int GetFrame( void ) { return static_cast(pev->frame); } + inline int GetScrollRate( void ) { return static_cast(pev->animtime); } + + // Call after you change start/end positions + void RelinkBeam( void ); +// void SetObjectCollisionBox( void ); + + void DoSparks( const Vector &start, const Vector &end ); + CBaseEntity *RandomTargetname( const char *szName ); + void BeamDamage( TraceResult *ptr ); + // Init after BeamCreate() + void BeamInit( const char *pSpriteName, int width ); + void PointsInit( const Vector &start, const Vector &end ); + void PointEntInit( const Vector &start, int endIndex ); + void EntsInit( int startIndex, int endIndex ); + void HoseInit( const Vector &start, const Vector &direction ); + + static CBeam *BeamCreate( const char *pSpriteName, int width ); + + inline void LiveForTime( float time ) { SetThink(&CBaseEntity::SUB_Remove); pev->nextthink = gpGlobals->time + time; } + inline void BeamDamageInstant( TraceResult *ptr, float damage ) + { + pev->dmg = damage; + pev->dmgtime = gpGlobals->time - 1; + BeamDamage(ptr); + } +}; + + +#define SF_MESSAGE_ONCE 0x0001 // Fade in, not out +#define SF_MESSAGE_ALL 0x0002 // Send to all clients + + +class CLaser : public CBeam +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + void TurnOn( void ); + void TurnOff( void ); + int IsOn( void ); + + void FireAtPoint( TraceResult &point ); + + void EXPORT StrikeThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + CSprite *m_pSprite; + int m_iszSpriteName; + Vector m_firePosition; +}; + +#endif //EFFECTS_H diff --git a/hlsdk/dmc/dlls/enginecallback.h b/hlsdk/dmc/dlls/enginecallback.h new file mode 100644 index 00000000..d29ddcd4 --- /dev/null +++ b/hlsdk/dmc/dlls/enginecallback.h @@ -0,0 +1,165 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef ENGINECALLBACK_H +#define ENGINECALLBACK_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +#include "event_flags.h" + +// Fix warning in MSVC8 +#undef SERVER_EXECUTE +// Must be provided by user of this code +extern enginefuncs_t g_engfuncs; + +// The actual engine callbacks +#define GETPLAYERUSERID (*g_engfuncs.pfnGetPlayerUserId) +#define PRECACHE_MODEL (*g_engfuncs.pfnPrecacheModel) +#define PRECACHE_SOUND (*g_engfuncs.pfnPrecacheSound) +#define PRECACHE_GENERIC (*g_engfuncs.pfnPrecacheGeneric) +#define SET_MODEL (*g_engfuncs.pfnSetModel) +#define MODEL_INDEX (*g_engfuncs.pfnModelIndex) +#define MODEL_FRAMES (*g_engfuncs.pfnModelFrames) +#define SET_SIZE (*g_engfuncs.pfnSetSize) +#define CHANGE_LEVEL (*g_engfuncs.pfnChangeLevel) +#define GET_SPAWN_PARMS (*g_engfuncs.pfnGetSpawnParms) +#define SAVE_SPAWN_PARMS (*g_engfuncs.pfnSaveSpawnParms) +#define VEC_TO_YAW (*g_engfuncs.pfnVecToYaw) +#define VEC_TO_ANGLES (*g_engfuncs.pfnVecToAngles) +#define MOVE_TO_ORIGIN (*g_engfuncs.pfnMoveToOrigin) +#define oldCHANGE_YAW (*g_engfuncs.pfnChangeYaw) +#define CHANGE_PITCH (*g_engfuncs.pfnChangePitch) +#define MAKE_VECTORS (*g_engfuncs.pfnMakeVectors) +#define CREATE_ENTITY (*g_engfuncs.pfnCreateEntity) +#define REMOVE_ENTITY (*g_engfuncs.pfnRemoveEntity) +#define CREATE_NAMED_ENTITY (*g_engfuncs.pfnCreateNamedEntity) +#define MAKE_STATIC (*g_engfuncs.pfnMakeStatic) +#define ENT_IS_ON_FLOOR (*g_engfuncs.pfnEntIsOnFloor) +#define DROP_TO_FLOOR (*g_engfuncs.pfnDropToFloor) +#define WALK_MOVE (*g_engfuncs.pfnWalkMove) +#define SET_ORIGIN (*g_engfuncs.pfnSetOrigin) +#define EMIT_SOUND_DYN2 (*g_engfuncs.pfnEmitSound) +#define BUILD_SOUND_MSG (*g_engfuncs.pfnBuildSoundMsg) +#define TRACE_LINE (*g_engfuncs.pfnTraceLine) +#define TRACE_TOSS (*g_engfuncs.pfnTraceToss) +#define TRACE_MONSTER_HULL (*g_engfuncs.pfnTraceMonsterHull) +#define TRACE_HULL (*g_engfuncs.pfnTraceHull) +#define GET_AIM_VECTOR (*g_engfuncs.pfnGetAimVector) +#define SERVER_COMMAND (*g_engfuncs.pfnServerCommand) +#define SERVER_EXECUTE (*g_engfuncs.pfnServerExecute) +#define CLIENT_COMMAND (*g_engfuncs.pfnClientCommand) +#define PARTICLE_EFFECT (*g_engfuncs.pfnParticleEffect) +#define LIGHT_STYLE (*g_engfuncs.pfnLightStyle) +#define DECAL_INDEX (*g_engfuncs.pfnDecalIndex) +#define POINT_CONTENTS (*g_engfuncs.pfnPointContents) +#define CRC32_INIT (*g_engfuncs.pfnCRC32_Init) +#define CRC32_PROCESS_BUFFER (*g_engfuncs.pfnCRC32_ProcessBuffer) +#define CRC32_PROCESS_BYTE (*g_engfuncs.pfnCRC32_ProcessByte) +#define CRC32_FINAL (*g_engfuncs.pfnCRC32_Final) +#define RANDOM_LONG (*g_engfuncs.pfnRandomLong) +#define RANDOM_FLOAT (*g_engfuncs.pfnRandomFloat) +#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); +} +#define MESSAGE_END (*g_engfuncs.pfnMessageEnd) +#define WRITE_BYTE (*g_engfuncs.pfnWriteByte) +#define WRITE_CHAR (*g_engfuncs.pfnWriteChar) +#define WRITE_SHORT (*g_engfuncs.pfnWriteShort) +#define WRITE_LONG (*g_engfuncs.pfnWriteLong) +#define WRITE_ANGLE (*g_engfuncs.pfnWriteAngle) +#define WRITE_COORD (*g_engfuncs.pfnWriteCoord) +#define WRITE_STRING (*g_engfuncs.pfnWriteString) +#define WRITE_ENTITY (*g_engfuncs.pfnWriteEntity) +#define CVAR_REGISTER (*g_engfuncs.pfnCVarRegister) +#define CVAR_GET_FLOAT (*g_engfuncs.pfnCVarGetFloat) +#define CVAR_GET_STRING (*g_engfuncs.pfnCVarGetString) +#define CVAR_SET_FLOAT (*g_engfuncs.pfnCVarSetFloat) +#define CVAR_SET_STRING (*g_engfuncs.pfnCVarSetString) +#define CVAR_GET_POINTER (*g_engfuncs.pfnCVarGetPointer) +#define ALERT (*g_engfuncs.pfnAlertMessage) +#define ENGINE_FPRINTF (*g_engfuncs.pfnEngineFprintf) +#define ALLOC_PRIVATE (*g_engfuncs.pfnPvAllocEntPrivateData) +inline void *GET_PRIVATE( edict_t *pent ) +{ + if ( pent ) + return pent->pvPrivateData; + return NULL; +} + +#define FREE_PRIVATE (*g_engfuncs.pfnFreeEntPrivateData) +//#define STRING (*g_engfuncs.pfnSzFromIndex) +#define ALLOC_STRING (*g_engfuncs.pfnAllocString) +#define FIND_ENTITY_BY_STRING (*g_engfuncs.pfnFindEntityByString) +#define GETENTITYILLUM (*g_engfuncs.pfnGetEntityIllum) +#define FIND_ENTITY_IN_SPHERE (*g_engfuncs.pfnFindEntityInSphere) +#define FIND_CLIENT_IN_PVS (*g_engfuncs.pfnFindClientInPVS) +#define EMIT_AMBIENT_SOUND (*g_engfuncs.pfnEmitAmbientSound) +#define GET_MODEL_PTR (*g_engfuncs.pfnGetModelPtr) +#define REG_USER_MSG (*g_engfuncs.pfnRegUserMsg) +#define GET_BONE_POSITION (*g_engfuncs.pfnGetBonePosition) +#define FUNCTION_FROM_NAME (*g_engfuncs.pfnFunctionFromName) +#define NAME_FOR_FUNCTION (*g_engfuncs.pfnNameForFunction) +#define TRACE_TEXTURE (*g_engfuncs.pfnTraceTexture) +#define CLIENT_PRINTF (*g_engfuncs.pfnClientPrintf) +#define CMD_ARGS (*g_engfuncs.pfnCmd_Args) +#define CMD_ARGC (*g_engfuncs.pfnCmd_Argc) +#define CMD_ARGV (*g_engfuncs.pfnCmd_Argv) +#define GET_ATTACHMENT (*g_engfuncs.pfnGetAttachment) +#define SET_VIEW (*g_engfuncs.pfnSetView) +#define SET_CROSSHAIRANGLE (*g_engfuncs.pfnCrosshairAngle) +#define LOAD_FILE_FOR_ME (*g_engfuncs.pfnLoadFileForMe) +#define FREE_FILE (*g_engfuncs.pfnFreeFile) +#define COMPARE_FILE_TIME (*g_engfuncs.pfnCompareFileTime) +#define GET_GAME_DIR (*g_engfuncs.pfnGetGameDir) +#define IS_MAP_VALID (*g_engfuncs.pfnIsMapValid) +#define NUMBER_OF_ENTITIES (*g_engfuncs.pfnNumberOfEntities) +#define IS_DEDICATED_SERVER (*g_engfuncs.pfnIsDedicatedServer) + + +#define PRECACHE_EVENT (*g_engfuncs.pfnPrecacheEvent) +#define PLAYBACK_EVENT_FULL (*g_engfuncs.pfnPlaybackEvent) + +#define ENGINE_SET_PVS (*g_engfuncs.pfnSetFatPVS) +#define ENGINE_SET_PAS (*g_engfuncs.pfnSetFatPAS) + +#define ENGINE_CHECK_VISIBILITY (*g_engfuncs.pfnCheckVisibility) + +#define DELTA_SET ( *g_engfuncs.pfnDeltaSetField ) +#define DELTA_UNSET ( *g_engfuncs.pfnDeltaUnsetField ) +#define DELTA_ADDENCODER ( *g_engfuncs.pfnDeltaAddEncoder ) +#define ENGINE_CURRENT_PLAYER ( *g_engfuncs.pfnGetCurrentPlayer ) + +#define ENGINE_CANSKIP ( *g_engfuncs.pfnCanSkipPlayer ) + +#define DELTA_FINDFIELD ( *g_engfuncs.pfnDeltaFindField ) +#define DELTA_SETBYINDEX ( *g_engfuncs.pfnDeltaSetFieldByIndex ) +#define DELTA_UNSETBYINDEX ( *g_engfuncs.pfnDeltaUnsetFieldByIndex ) + +#define ENGINE_GETPHYSINFO ( *g_engfuncs.pfnGetPhysicsInfoString ) + +#define ENGINE_SETGROUPMASK ( *g_engfuncs.pfnSetGroupMask ) + +#define ENGINE_INSTANCE_BASELINE ( *g_engfuncs.pfnCreateInstancedBaseline ) + +#define ENGINE_FORCE_UNMODIFIED ( *g_engfuncs.pfnForceUnmodified ) + +#define PLAYER_CNX_STATS ( *g_engfuncs.pfnGetPlayerStats ) + +#endif //ENGINECALLBACK_H diff --git a/hlsdk/dmc/dlls/explode.cpp b/hlsdk/dmc/dlls/explode.cpp new file mode 100644 index 00000000..4e5f1c15 --- /dev/null +++ b/hlsdk/dmc/dlls/explode.cpp @@ -0,0 +1,273 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== explode.cpp ======================================================== + + Explosion-related code + +*/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "decals.h" +#include "explode.h" + +// Spark Shower +class CShower : public CBaseEntity +{ + void Spawn( void ); + void Think( void ); + void Touch( CBaseEntity *pOther ); + int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +}; + +LINK_ENTITY_TO_CLASS( spark_shower, CShower ); + +void CShower::Spawn( void ) +{ + pev->velocity = RANDOM_FLOAT( 200, 300 ) * pev->angles; + pev->velocity.x += RANDOM_FLOAT(-100.f,100.f); + pev->velocity.y += RANDOM_FLOAT(-100.f,100.f); + if ( pev->velocity.z >= 0 ) + pev->velocity.z += 200; + else + pev->velocity.z -= 200; + pev->movetype = MOVETYPE_BOUNCE; + pev->gravity = 0.5; + pev->nextthink = gpGlobals->time + 0.1; + pev->solid = SOLID_NOT; + SET_MODEL( edict(), "models/grenade.mdl"); // Need a model, just use the grenade, we don't draw it anyway + UTIL_SetSize(pev, g_vecZero, g_vecZero ); + pev->effects |= EF_NODRAW; + pev->speed = RANDOM_FLOAT( 0.5, 1.5 ); + + pev->angles = g_vecZero; +} + + +void CShower::Think( void ) +{ + UTIL_Sparks( pev->origin ); + + pev->speed -= 0.1; + if ( pev->speed > 0 ) + pev->nextthink = gpGlobals->time + 0.1; + else + UTIL_Remove( this ); + pev->flags &= ~FL_ONGROUND; +} + +void CShower::Touch( CBaseEntity *pOther ) +{ + if ( pev->flags & FL_ONGROUND ) + pev->velocity = pev->velocity * 0.1; + else + pev->velocity = pev->velocity * 0.6; + + if ( (pev->velocity.x*pev->velocity.x+pev->velocity.y*pev->velocity.y) < 10.0 ) + pev->speed = 0; +} + +class CEnvExplosion : public CBaseMonster +{ +public: + void Spawn( ); + void EXPORT Smoke ( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_iMagnitude;// how large is the fireball? how much damage? + int m_spriteScale; // what's the exact fireball sprite scale? +}; + +TYPEDESCRIPTION CEnvExplosion::m_SaveData[] = +{ + DEFINE_FIELD( CEnvExplosion, m_iMagnitude, FIELD_INTEGER ), + DEFINE_FIELD( CEnvExplosion, m_spriteScale, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CEnvExplosion, CBaseMonster ); +LINK_ENTITY_TO_CLASS( env_explosion, CEnvExplosion ); + +void CEnvExplosion::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "iMagnitude")) + { + m_iMagnitude = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CEnvExplosion::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; + + pev->movetype = MOVETYPE_NONE; + /* + if ( m_iMagnitude > 250 ) + { + m_iMagnitude = 250; + } + */ + + float flSpriteScale; + flSpriteScale = ( m_iMagnitude - 50) * 0.6; + + /* + if ( flSpriteScale > 50 ) + { + flSpriteScale = 50; + } + */ + if ( flSpriteScale < 10 ) + { + flSpriteScale = 10; + } + + m_spriteScale = (int)flSpriteScale; +} + +void CEnvExplosion::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + TraceResult tr; + + pev->model = iStringNull;//invisible + pev->solid = SOLID_NOT;// intangible + + 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); + + // Pull out of the wall a bit + if ( tr.flFraction != 1.0 ) + { + pev->origin = tr.vecEndPos + (tr.vecPlaneNormal * (m_iMagnitude - 24) * 0.6); + } + else + { + pev->origin = pev->origin; + } + + // draw decal + if (! ( pev->spawnflags & SF_ENVEXPLOSION_NODECAL)) + { + if ( RANDOM_FLOAT( 0 , 1 ) < 0.5 ) + { + UTIL_DecalTrace( &tr, DECAL_SCORCH1 ); + } + else + { + UTIL_DecalTrace( &tr, DECAL_SCORCH2 ); + } + } + + // draw fireball + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NOFIREBALL ) ) + { + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( (BYTE)m_spriteScale ); // scale * 10 + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + } + else + { + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( 0 ); // no sprite + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + } + + // do damage + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NODAMAGE ) ) + { + RadiusDamage ( pev, pev, m_iMagnitude, CLASS_NONE, DMG_BLAST ); + } + + SetThink( &CEnvExplosion::Smoke ); + pev->nextthink = gpGlobals->time + 0.3; + + // draw sparks + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NOSPARKS ) ) + { + int sparkCount = RANDOM_LONG(0,3); + + for ( int i = 0; i < sparkCount; i++ ) + { + Create( "spark_shower", pev->origin, tr.vecPlaneNormal, NULL ); + } + } +} + +void CEnvExplosion::Smoke( void ) +{ + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NOSMOKE ) ) + { + MESSAGE_BEGIN( MSG_PAS, 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)m_spriteScale ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + } + + if ( !(pev->spawnflags & SF_ENVEXPLOSION_REPEATABLE) ) + { + UTIL_Remove( this ); + } +} + + +// HACKHACK -- create one of these and fake a keyvalue to get the right explosion setup +void ExplosionCreate( const Vector ¢er, const Vector &angles, edict_t *pOwner, int magnitude, BOOL doDamage ) +{ + KeyValueData kvd; + char buf[128]; + + CBaseEntity *pExplosion = CBaseEntity::Create( "env_explosion", center, angles, pOwner ); + sprintf( buf, "%3d", magnitude ); + kvd.szKeyName = "iMagnitude"; + kvd.szValue = buf; + pExplosion->KeyValue( &kvd ); + if ( !doDamage ) + pExplosion->pev->spawnflags |= SF_ENVEXPLOSION_NODAMAGE; + + pExplosion->Spawn(); + pExplosion->Use( NULL, NULL, USE_TOGGLE, 0 ); +} diff --git a/hlsdk/dmc/dlls/explode.h b/hlsdk/dmc/dlls/explode.h new file mode 100644 index 00000000..3feb011c --- /dev/null +++ b/hlsdk/dmc/dlls/explode.h @@ -0,0 +1,32 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef EXPLODE_H +#define EXPLODE_H + + +#define SF_ENVEXPLOSION_NODAMAGE ( 1 << 0 ) // when set, ENV_EXPLOSION will not actually inflict damage +#define SF_ENVEXPLOSION_REPEATABLE ( 1 << 1 ) // can this entity be refired? +#define SF_ENVEXPLOSION_NOFIREBALL ( 1 << 2 ) // don't draw the fireball +#define SF_ENVEXPLOSION_NOSMOKE ( 1 << 3 ) // don't draw the smoke +#define SF_ENVEXPLOSION_NODECAL ( 1 << 4 ) // don't make a scorch mark +#define SF_ENVEXPLOSION_NOSPARKS ( 1 << 5 ) // don't make a scorch mark + +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/hlsdk/dmc/dlls/extdll.h b/hlsdk/dmc/dlls/extdll.h new file mode 100644 index 00000000..1b5eaf4f --- /dev/null +++ b/hlsdk/dmc/dlls/extdll.h @@ -0,0 +1,107 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef EXTDLL_H +#define EXTDLL_H + + +// +// Global header file for extension DLLs +// + +// Allow "DEBUG" in addition to default "_DEBUG" +#ifdef _DEBUG + #define DEBUG 1 +#endif + +// Silence certain warnings +#ifdef _MSC_VER + #pragma warning(disable : 4244) // int or float down-conversion + #pragma warning(disable : 4305) // int or float data truncation + #pragma warning(disable : 4201) // nameless struct/union + #pragma warning(disable : 4514) // unreferenced inline function removed + #pragma warning(disable : 4100) // unreferenced formal parameter + + #if _MSC_VER >= 1400 + #ifndef _CRT_SECURE_NO_DEPRECATE + #define _CRT_SECURE_NO_DEPRECATE + #endif + + #pragma warning(disable: 4996) // deprecated functions + #endif +#endif + +#ifdef _WIN32 +// Prevent tons of unused windows definitions +#define WIN32_LEAN_AND_MEAN +#define NOWINRES +#define NOSERVICE +#define NOMCX +#define NOIME +#include "windows.h" + +#else // _WIN32 +#define FALSE 0 +#define TRUE (!FALSE) + +typedef unsigned long ULONG; +typedef unsigned char BYTE; +typedef int BOOL; + +#define MAX_PATH PATH_MAX + +#include +#include +#include +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +#define itoa(a,b,c) sprintf(b, "%d", a) +#define _snprintf snprintf +#define _vsnprintf vsnprintf +#endif //_WIN32 + +// Misc C-runtime library headers +#include "stdio.h" +#include "stdlib.h" +#include "math.h" + +// Header file containing definition of globalvars_t and entvars_t +typedef int func_t; // +typedef int string_t; // from engine's pr_comp.h; +typedef float vec_t; // needed before including progdefs.h + +// Vector class +#include "vector.h" + +// Defining it as a (bogus) struct helps enforce type-checking +#define vec3_t Vector + +// Shared engine/DLL constants +#include "const.h" +#include "progdefs.h" +#include "edict.h" + +// Shared header describing protocol between engine and DLLs +#include "eiface.h" + +// Shared header between the client DLL and the game DLLs +#include "cdll_dll.h" + +#endif //EXTDLL_H diff --git a/hlsdk/dmc/dlls/func_break.cpp b/hlsdk/dmc/dlls/func_break.cpp new file mode 100644 index 00000000..c3179f7b --- /dev/null +++ b/hlsdk/dmc/dlls/func_break.cpp @@ -0,0 +1,1005 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== bmodels.cpp ======================================================== + + spawn, think, and use functions for entities that use brush models + +*/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "func_break.h" +#include "decals.h" +#include "explode.h" + +extern DLL_GLOBAL Vector g_vecAttackDir; + +// =================== FUNC_Breakable ============================================== + +// Just add more items to the bottom of this array and they will automagically be supported +// This is done instead of just a classname in the FGD so we can control which entities can +// be spawned, and still remain fairly flexible +const char *CBreakable::pSpawnObjects[] = +{ + NULL, // 0 + "item_battery", // 1 + "item_healthkit", // 2 + "weapon_9mmhandgun",// 3 + "ammo_9mmclip", // 4 + "weapon_9mmAR", // 5 + "ammo_9mmAR", // 6 + "ammo_ARgrenades", // 7 + "weapon_shotgun", // 8 + "ammo_buckshot", // 9 + "weapon_crossbow", // 10 + "ammo_crossbow", // 11 + "weapon_357", // 12 + "ammo_357", // 13 + "weapon_rpg", // 14 + "ammo_rpgclip", // 15 + "ammo_gaussclip", // 16 + "weapon_handgrenade",// 17 + "weapon_tripmine", // 18 + "weapon_satchel", // 19 + "weapon_snark", // 20 + "weapon_hornetgun", // 21 +}; + +void CBreakable::KeyValue( KeyValueData* pkvd ) +{ + // UNDONE_WC: explicitly ignoring these fields, but they shouldn't be in the map file! + if (FStrEq(pkvd->szKeyName, "explosion")) + { + if (!stricmp(pkvd->szValue, "directed")) + m_Explosion = expDirected; + else if (!stricmp(pkvd->szValue, "random")) + m_Explosion = expRandom; + else + m_Explosion = expRandom; + + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "material")) + { + int i = atoi( pkvd->szValue); + + // 0:glass, 1:metal, 2:flesh, 3:wood + + if ((i < 0) || (i >= matLastMaterial)) + m_Material = matWood; + else + m_Material = (Materials)i; + + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "deadmodel")) + { + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "shards")) + { +// m_iShards = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "gibmodel") ) + { + m_iszGibModel = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spawnobject") ) + { + int object = atoi( pkvd->szValue ); + if ( object > 0 && object < static_cast(ARRAYSIZE(pSpawnObjects)) ) + m_iszSpawnObject = MAKE_STRING( pSpawnObjects[object] ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "explodemagnitude") ) + { + ExplosionSetMagnitude( atoi( pkvd->szValue ) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "lip") ) + pkvd->fHandled = TRUE; + else + CBaseDelay::KeyValue( pkvd ); +} + + +// +// func_breakable - bmodel that breaks into pieces after taking damage +// +LINK_ENTITY_TO_CLASS( func_breakable, CBreakable ); +TYPEDESCRIPTION CBreakable::m_SaveData[] = +{ + DEFINE_FIELD( CBreakable, m_Material, FIELD_INTEGER ), + DEFINE_FIELD( CBreakable, m_Explosion, FIELD_INTEGER ), + +// Don't need to save/restore these because we precache after restore +// DEFINE_FIELD( CBreakable, m_idShard, FIELD_INTEGER ), + + DEFINE_FIELD( CBreakable, m_angle, FIELD_FLOAT ), + DEFINE_FIELD( CBreakable, m_iszGibModel, FIELD_STRING ), + DEFINE_FIELD( CBreakable, m_iszSpawnObject, FIELD_STRING ), + + // Explosion magnitude is stored in pev->impulse +}; + +IMPLEMENT_SAVERESTORE( CBreakable, CBaseEntity ); + +void CBreakable::Spawn( void ) +{ + Precache( ); + + if ( FBitSet( pev->spawnflags, SF_BREAK_TRIGGER_ONLY ) ) + pev->takedamage = DAMAGE_NO; + else + pev->takedamage = DAMAGE_YES; + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + m_angle = pev->angles.y; + pev->angles.y = 0; + + + SET_MODEL(ENT(pev), STRING(pev->model) );//set size and link into world. + + SetTouch( &CBreakable::BreakTouch ); + if ( FBitSet( pev->spawnflags, SF_BREAK_TRIGGER_ONLY ) ) // Only break on trigger + SetTouch( NULL ); + + // Flag unbreakable glass as "worldbrush" so it will block ALL tracelines + if ( !IsBreakable() && pev->rendermode != kRenderNormal ) + pev->flags |= FL_WORLDBRUSH; +} + + +const char *CBreakable::pSoundsWood[] = +{ + "debris/wood1.wav", + "debris/wood2.wav", + "debris/wood3.wav", +}; + +const char *CBreakable::pSoundsFlesh[] = +{ + "debris/flesh1.wav", + "debris/flesh2.wav", + "debris/flesh3.wav", + "debris/flesh5.wav", + "debris/flesh6.wav", + "debris/flesh7.wav", +}; + +const char *CBreakable::pSoundsMetal[] = +{ + "debris/metal1.wav", + "debris/metal2.wav", + "debris/metal3.wav", +}; + +const char *CBreakable::pSoundsConcrete[] = +{ + "debris/concrete1.wav", + "debris/concrete2.wav", + "debris/concrete3.wav", +}; + + +const char *CBreakable::pSoundsGlass[] = +{ + "debris/glass1.wav", + "debris/glass2.wav", + "debris/glass3.wav", +}; + +const char **CBreakable::MaterialSoundList( Materials precacheMaterial, int &soundCount ) +{ + const char **pSoundList = NULL; + + switch ( precacheMaterial ) + { + case matWood: + pSoundList = pSoundsWood; + soundCount = ARRAYSIZE(pSoundsWood); + break; + case matFlesh: + pSoundList = pSoundsFlesh; + soundCount = ARRAYSIZE(pSoundsFlesh); + break; + case matComputer: + case matUnbreakableGlass: + case matGlass: + pSoundList = pSoundsGlass; + soundCount = ARRAYSIZE(pSoundsGlass); + break; + + case matMetal: + pSoundList = pSoundsMetal; + soundCount = ARRAYSIZE(pSoundsMetal); + break; + + case matCinderBlock: + case matRocks: + pSoundList = pSoundsConcrete; + soundCount = ARRAYSIZE(pSoundsConcrete); + break; + + + case matCeilingTile: + case matNone: + default: + soundCount = 0; + break; + } + + return pSoundList; +} + +void CBreakable::MaterialSoundPrecache( Materials precacheMaterial ) +{ + const char **pSoundList; + int i, soundCount = 0; + + pSoundList = MaterialSoundList( precacheMaterial, soundCount ); + + for ( i = 0; i < soundCount; i++ ) + { + PRECACHE_SOUND( (char *)pSoundList[i] ); + } +} + +void CBreakable::MaterialSoundRandom( edict_t *pEdict, Materials soundMaterial, float volume ) +{ + const char **pSoundList; + int soundCount = 0; + + pSoundList = MaterialSoundList( soundMaterial, soundCount ); + + if ( soundCount ) + EMIT_SOUND( pEdict, CHAN_BODY, pSoundList[ RANDOM_LONG(0,soundCount-1) ], volume, 1.0 ); +} + + +void CBreakable::Precache( void ) +{ + const char *pGibName = NULL; + + switch (m_Material) + { + case matWood: + pGibName = "models/woodgibs.mdl"; + + PRECACHE_SOUND("debris/bustcrate1.wav"); + PRECACHE_SOUND("debris/bustcrate2.wav"); + break; + case matFlesh: + pGibName = "models/fleshgibs.mdl"; + + PRECACHE_SOUND("debris/bustflesh1.wav"); + PRECACHE_SOUND("debris/bustflesh2.wav"); + break; + case matComputer: + PRECACHE_SOUND("buttons/spark5.wav"); + PRECACHE_SOUND("buttons/spark6.wav"); + pGibName = "models/computergibs.mdl"; + + PRECACHE_SOUND("debris/bustmetal1.wav"); + PRECACHE_SOUND("debris/bustmetal2.wav"); + break; + + case matUnbreakableGlass: + case matGlass: + pGibName = "models/glassgibs.mdl"; + + PRECACHE_SOUND("debris/bustglass1.wav"); + PRECACHE_SOUND("debris/bustglass2.wav"); + break; + case matMetal: + pGibName = "models/metalplategibs.mdl"; + + PRECACHE_SOUND("debris/bustmetal1.wav"); + PRECACHE_SOUND("debris/bustmetal2.wav"); + break; + case matCinderBlock: + pGibName = "models/cindergibs.mdl"; + + PRECACHE_SOUND("debris/bustconcrete1.wav"); + PRECACHE_SOUND("debris/bustconcrete2.wav"); + break; + case matRocks: + pGibName = "models/rockgibs.mdl"; + + PRECACHE_SOUND("debris/bustconcrete1.wav"); + PRECACHE_SOUND("debris/bustconcrete2.wav"); + break; + case matCeilingTile: + pGibName = "models/ceilinggibs.mdl"; + + PRECACHE_SOUND ("debris/bustceiling.wav"); + break; + default: + break; + } + MaterialSoundPrecache( m_Material ); + if ( m_iszGibModel ) + pGibName = STRING(m_iszGibModel); + + m_idShard = PRECACHE_MODEL( (char *)pGibName ); + + // Precache the spawn item's data + if ( m_iszSpawnObject ) + UTIL_PrecacheOther( (char *)STRING( m_iszSpawnObject ) ); +} + +// play shard sound when func_breakable takes damage. +// the more damage, the louder the shard sound. + + +void CBreakable::DamageSound( void ) +{ + int pitch; + float fvol; + char *rgpsz[6]; + int i = 0; + int material = m_Material; + +// if (RANDOM_LONG(0,1)) +// return; + + if (RANDOM_LONG(0,2)) + pitch = PITCH_NORM; + else + pitch = 95 + RANDOM_LONG(0,34); + + fvol = RANDOM_FLOAT(0.75, 1.0); + + if (material == matComputer && RANDOM_LONG(0,1)) + material = matMetal; + + switch (material) + { + case matComputer: + case matGlass: + case matUnbreakableGlass: + rgpsz[0] = "debris/glass1.wav"; + rgpsz[1] = "debris/glass2.wav"; + rgpsz[2] = "debris/glass3.wav"; + i = 3; + break; + + case matWood: + rgpsz[0] = "debris/wood1.wav"; + rgpsz[1] = "debris/wood2.wav"; + rgpsz[2] = "debris/wood3.wav"; + i = 3; + break; + + case matMetal: + rgpsz[0] = "debris/metal1.wav"; + rgpsz[1] = "debris/metal3.wav"; + rgpsz[2] = "debris/metal2.wav"; + i = 2; + break; + + case matFlesh: + rgpsz[0] = "debris/flesh1.wav"; + rgpsz[1] = "debris/flesh2.wav"; + rgpsz[2] = "debris/flesh3.wav"; + rgpsz[3] = "debris/flesh5.wav"; + rgpsz[4] = "debris/flesh6.wav"; + rgpsz[5] = "debris/flesh7.wav"; + i = 6; + break; + + case matRocks: + case matCinderBlock: + rgpsz[0] = "debris/concrete1.wav"; + rgpsz[1] = "debris/concrete2.wav"; + rgpsz[2] = "debris/concrete3.wav"; + i = 3; + break; + + case matCeilingTile: + // UNDONE: no ceiling tile shard sound yet + i = 0; + break; + } + + if (i) + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, rgpsz[RANDOM_LONG(0,i-1)], fvol, ATTN_NORM, 0, pitch); +} + +void CBreakable::BreakTouch( CBaseEntity *pOther ) +{ + float flDamage; + entvars_t* pevToucher = pOther->pev; + + // only players can break these right now + if ( !pOther->IsPlayer() || !IsBreakable() ) + { + return; + } + + if ( FBitSet ( pev->spawnflags, SF_BREAK_TOUCH ) ) + {// can be broken when run into + flDamage = pevToucher->velocity.Length() * 0.01; + + if (flDamage >= pev->health) + { + SetTouch( NULL ); + TakeDamage(pevToucher, pevToucher, flDamage, DMG_CRUSH); + + // do a little damage to player if we broke glass or computer + pOther->TakeDamage( pev, pev, flDamage/4, DMG_SLASH ); + } + } + + if ( FBitSet ( pev->spawnflags, SF_BREAK_PRESSURE ) && pevToucher->absmin.z >= pev->maxs.z - 2 ) + {// can be broken when stood upon + + // play creaking sound here. + DamageSound(); + + SetThink ( &CBreakable::Die ); + SetTouch( NULL ); + + if ( m_flDelay == 0 ) + {// !!!BUGBUG - why doesn't zero delay work? + m_flDelay = 0.1; + } + + pev->nextthink = pev->ltime + m_flDelay; + + } + +} + + +// +// Smash the our breakable object +// + +// Break when triggered +void CBreakable::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( IsBreakable() ) + { + pev->angles.y = m_angle; + UTIL_MakeVectors(pev->angles); + g_vecAttackDir = gpGlobals->v_forward; + + Die(); + } +} + + +void CBreakable::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + // random spark if this is a 'computer' object + if (RANDOM_LONG(0,1) ) + { + switch( m_Material ) + { + case matComputer: + { + UTIL_Sparks( ptr->vecEndPos ); + + float flVolume = RANDOM_FLOAT ( 0.7 , 1.0 );//random volume range + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark5.wav", flVolume, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark6.wav", flVolume, ATTN_NORM); break; + } + } + break; + + case matUnbreakableGlass: + UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT(0.5,1.5) ); + break; + + default: + break; + } + } + + CBaseDelay::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + +//========================================================= +// Special takedamage for func_breakable. Allows us to make +// exceptions that are breakable-specific +// bitsDamageType indicates the type of damage sustained ie: DMG_CRUSH +//========================================================= +int CBreakable :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + Vector vecTemp; + + // if Attacker == Inflictor, the attack was a melee or other instant-hit attack. + // (that is, no actual entity projectile was involved in the attack so use the shooter's origin). + if ( pevAttacker == pevInflictor ) + { + vecTemp = pevInflictor->origin - ( pev->absmin + ( pev->size * 0.5 ) ); + + // if a client hit the breakable with a crowbar, and breakable is crowbar-sensitive, break it now. + if ( FBitSet ( pevAttacker->flags, FL_CLIENT ) && + FBitSet ( pev->spawnflags, SF_BREAK_CROWBAR ) && (bitsDamageType & DMG_CLUB)) + flDamage = pev->health; + } + else + // an actual missile was involved. + { + vecTemp = pevInflictor->origin - ( pev->absmin + ( pev->size * 0.5 ) ); + } + + if (!IsBreakable()) + return 0; + + // Breakables take double damage from the crowbar + if ( bitsDamageType & DMG_CLUB ) + flDamage *= 2; + + // Boxes / glass / etc. don't take much poison damage, just the impact of the dart - consider that 10% + if ( bitsDamageType & DMG_POISON ) + flDamage *= 0.1; + +// this global is still used for glass and other non-monster killables, along with decals. + g_vecAttackDir = vecTemp.Normalize(); + +// do the damage + pev->health -= flDamage; + if (pev->health <= 0) + { + Killed( pevAttacker, GIB_NORMAL ); + Die(); + return 0; + } + + // Make a shard noise each time func breakable is hit. + // Don't play shard noise if cbreakable actually died. + + DamageSound(); + + return 1; +} + + +void CBreakable::Die( void ) +{ + Vector vecSpot;// shard origin + Vector vecVelocity;// shard velocity + char cFlag = 0; + int pitch; + float fvol; + + pitch = 95 + RANDOM_LONG(0,29); + + if (pitch > 97 && pitch < 103) + pitch = 100; + + // The more negative pev->health, the louder + // the sound should be. + + fvol = RANDOM_FLOAT(0.85, 1.0) + (abs(static_cast(pev->health)) / 100.0); + + if (fvol > 1.0) + fvol = 1.0; + + + switch (m_Material) + { + case matGlass: + switch ( RANDOM_LONG(0,1) ) + { + 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); + break; + } + cFlag = BREAK_GLASS; + break; + + case matWood: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustcrate1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustcrate2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_WOOD; + break; + + case matComputer: + case matMetal: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustmetal1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustmetal2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_METAL; + break; + + case matFlesh: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustflesh1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustflesh2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_FLESH; + break; + + case matRocks: + case matCinderBlock: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustconcrete1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustconcrete2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_CONCRETE; + break; + + case matCeilingTile: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustceiling.wav", fvol, ATTN_NORM, 0, pitch); + break; + + default: + break; + } + + + if (m_Explosion == expDirected) + vecVelocity = g_vecAttackDir * 200; + else + { + vecVelocity.x = 0; + vecVelocity.y = 0; + vecVelocity.z = 0; + } + + vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z ); + + // size + WRITE_COORD( pev->size.x); + WRITE_COORD( pev->size.y); + WRITE_COORD( pev->size.z); + + // velocity + WRITE_COORD( vecVelocity.x ); + WRITE_COORD( vecVelocity.y ); + WRITE_COORD( vecVelocity.z ); + + // randomization + WRITE_BYTE( 10 ); + + // Model + WRITE_SHORT( m_idShard ); //model id# + + // # of shards + WRITE_BYTE( 0 ); // let client decide + + // duration + WRITE_BYTE( 25 );// 2.5 seconds + + // flags + WRITE_BYTE( cFlag ); + MESSAGE_END(); + + float size = pev->size.x; + if ( size < pev->size.y ) + size = pev->size.y; + if ( size < pev->size.z ) + size = pev->size.z; + + // !!! HACK This should work! + // Build a box above the entity that looks like an 8 pixel high sheet + Vector mins = pev->absmin; + Vector maxs = pev->absmax; + mins.z = pev->absmax.z; + maxs.z += 8; + + // BUGBUG -- can only find 256 entities on a breakable -- should be enough + CBaseEntity *pList[256]; + int count = UTIL_EntitiesInBox( pList, 256, mins, maxs, FL_ONGROUND ); + if ( count ) + { + for ( int i = 0; i < count; i++ ) + { + ClearBits( pList[i]->pev->flags, FL_ONGROUND ); + pList[i]->pev->groundentity = NULL; + } + } + + // Don't fire something that could fire myself + pev->targetname = 0; + + pev->solid = SOLID_NOT; + // Fire targets on break + SUB_UseTargets( NULL, USE_TOGGLE, 0 ); + + SetThink( &CBaseEntity::SUB_Remove ); + pev->nextthink = pev->ltime + 0.1; + if ( m_iszSpawnObject ) + CBaseEntity::Create( (char *)STRING(m_iszSpawnObject), VecBModelOrigin(pev), pev->angles, edict() ); + + + if ( Explodable() ) + { + ExplosionCreate( Center(), pev->angles, edict(), ExplosionMagnitude(), TRUE ); + } +} + + + +BOOL CBreakable :: IsBreakable( void ) +{ + return m_Material != matUnbreakableGlass; +} + + +int CBreakable :: DamageDecal( int bitsDamageType ) +{ + if ( m_Material == matGlass ) + return DECAL_GLASSBREAK1 + RANDOM_LONG(0,2); + + if ( m_Material == matUnbreakableGlass ) + return DECAL_BPROOF1; + + return CBaseEntity::DamageDecal( bitsDamageType ); +} + + +class CPushable : public CBreakable +{ +public: + void Spawn ( void ); + void Precache( void ); + void Touch ( CBaseEntity *pOther ); + void Move( CBaseEntity *pMover, int push ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT StopSound( void ); +// virtual void SetActivator( CBaseEntity *pActivator ) { m_pPusher = pActivator; } + + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_CONTINUOUS_USE; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + inline float MaxSpeed( void ) { return m_maxSpeed; } + + // breakables use an overridden takedamage + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + + static TYPEDESCRIPTION m_SaveData[]; + + static char *m_soundNames[3]; + int m_lastSound; // no need to save/restore, just keeps the same sound from playing twice in a row + float m_maxSpeed; + float m_soundTime; +}; + +TYPEDESCRIPTION CPushable::m_SaveData[] = +{ + DEFINE_FIELD( CPushable, m_maxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CPushable, m_soundTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CPushable, CBreakable ); + +LINK_ENTITY_TO_CLASS( func_pushable, CPushable ); + +char *CPushable :: m_soundNames[3] = { "debris/pushbox1.wav", "debris/pushbox2.wav", "debris/pushbox3.wav" }; + + +void CPushable :: Spawn( void ) +{ + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + CBreakable::Spawn(); + else + Precache( ); + + pev->movetype = MOVETYPE_PUSHSTEP; + pev->solid = SOLID_BBOX; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + if ( pev->friction > 399 ) + pev->friction = 399; + + m_maxSpeed = 400 - pev->friction; + SetBits( pev->flags, FL_FLOAT ); + pev->friction = 0; + + pev->origin.z += 1; // Pick up off of the floor + UTIL_SetOrigin( pev, pev->origin ); + + // Multiply by area of the box's cross-section (assume 1000 units^3 standard volume) + pev->skin = static_cast(( pev->skin * (pev->maxs.x - pev->mins.x) * (pev->maxs.y - pev->mins.y) ) * 0.0005); + m_soundTime = 0; +} + + +void CPushable :: Precache( void ) +{ + for ( int i = 0; i < 3; i++ ) + PRECACHE_SOUND( m_soundNames[i] ); + + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + CBreakable::Precache( ); +} + + +void CPushable :: KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "size") ) + { + int bbox = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + switch( bbox ) + { + case 0: // Point + UTIL_SetSize(pev, Vector(-8, -8, -8), Vector(8, 8, 8)); + break; + + case 2: // Big Hull!?!? !!!BUGBUG Figure out what this hull really is + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN*2, VEC_DUCK_HULL_MAX*2); + break; + + case 3: // Player duck + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + break; + + default: + case 1: // Player + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + break; + } + + } + else if ( FStrEq(pkvd->szKeyName, "buoyancy") ) + { + pev->skin = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBreakable::KeyValue( pkvd ); +} + + +// Pull the func_pushable +void CPushable :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !pActivator || !pActivator->IsPlayer() ) + { + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + this->CBreakable::Use( pActivator, pCaller, useType, value ); + return; + } + + if ( pActivator->pev->velocity != g_vecZero ) + Move( pActivator, 0 ); +} + + +void CPushable :: Touch( CBaseEntity *pOther ) +{ + if ( FClassnameIs( pOther->pev, "worldspawn" ) ) + return; + + Move( pOther, 1 ); +} + + +void CPushable :: Move( CBaseEntity *pOther, int push ) +{ + entvars_t* pevToucher = pOther->pev; + int playerTouch = 0; + + // Is entity standing on this pushable ? + if ( FBitSet(pevToucher->flags,FL_ONGROUND) && pevToucher->groundentity && VARS(pevToucher->groundentity) == pev ) + { + // Only push if floating + if ( pev->waterlevel > 0 ) + pev->velocity.z += pevToucher->velocity.z * 0.1; + + return; + } + + + if ( pOther->IsPlayer() ) + { + if ( push && !(pevToucher->button & (IN_FORWARD|IN_USE)) ) // Don't push unless the player is pushing forward and NOT use (pull) + return; + playerTouch = 1; + } + + float factor; + + if ( playerTouch ) + { + if ( !(pevToucher->flags & FL_ONGROUND) ) // Don't push away from jumping/falling players unless in water + { + if ( pev->waterlevel < 1 ) + return; + else + factor = 0.1; + } + else + factor = 1; + } + else + factor = 0.25; + + pev->velocity.x += pevToucher->velocity.x * factor; + pev->velocity.y += pevToucher->velocity.y * factor; + + float length = sqrt( pev->velocity.x * pev->velocity.x + pev->velocity.y * pev->velocity.y ); + if ( push && (length > MaxSpeed()) ) + { + pev->velocity.x = (pev->velocity.x * MaxSpeed() / length ); + pev->velocity.y = (pev->velocity.y * MaxSpeed() / length ); + } + if ( playerTouch ) + { + pevToucher->velocity.x = pev->velocity.x; + pevToucher->velocity.y = pev->velocity.y; + if ( (gpGlobals->time - m_soundTime) > 0.7 ) + { + m_soundTime = gpGlobals->time; + if ( length > 0 && FBitSet(pev->flags,FL_ONGROUND) ) + { + m_lastSound = RANDOM_LONG(0,2); + EMIT_SOUND(ENT(pev), CHAN_WEAPON, m_soundNames[m_lastSound], 0.5, ATTN_NORM); + // SetThink( StopSound ); + // pev->nextthink = pev->ltime + 0.1; + } + else + STOP_SOUND( ENT(pev), CHAN_WEAPON, m_soundNames[m_lastSound] ); + } + } +} + +#if 0 +void CPushable::StopSound( void ) +{ + Vector dist = pev->oldorigin - pev->origin; + if ( dist.Length() <= 0 ) + STOP_SOUND( ENT(pev), CHAN_WEAPON, m_soundNames[m_lastSound] ); +} +#endif + +int CPushable::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + return CBreakable::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); + + return 1; +} + diff --git a/hlsdk/dmc/dlls/func_break.h b/hlsdk/dmc/dlls/func_break.h new file mode 100644 index 00000000..9bb281d6 --- /dev/null +++ b/hlsdk/dmc/dlls/func_break.h @@ -0,0 +1,74 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef FUNC_BREAK_H +#define FUNC_BREAK_H + +typedef enum { expRandom, expDirected} Explosions; +typedef enum { matGlass = 0, matWood, matMetal, matFlesh, matCinderBlock, matCeilingTile, matComputer, matUnbreakableGlass, matRocks, matNone, matLastMaterial } Materials; + +#define NUM_SHARDS 6 // this many shards spawned when breakable objects break; + +class CBreakable : public CBaseDelay +{ +public: + // basic functions + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData* pkvd); + void EXPORT BreakTouch( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void DamageSound( void ); + + // 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 ); + + BOOL IsBreakable( void ); + BOOL SparkWhenHit( void ); + + int DamageDecal( int bitsDamageType ); + + void EXPORT Die( void ); + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + inline BOOL Explodable( void ) { return ExplosionMagnitude() > 0; } + inline int ExplosionMagnitude( void ) { return pev->impulse; } + inline void ExplosionSetMagnitude( int magnitude ) { pev->impulse = magnitude; } + + static void MaterialSoundPrecache( Materials precacheMaterial ); + static void MaterialSoundRandom( edict_t *pEdict, Materials soundMaterial, float volume ); + static const char **MaterialSoundList( Materials precacheMaterial, int &soundCount ); + + static const char *pSoundsWood[]; + static const char *pSoundsFlesh[]; + static const char *pSoundsGlass[]; + static const char *pSoundsMetal[]; + static const char *pSoundsConcrete[]; + static const char *pSpawnObjects[]; + + static TYPEDESCRIPTION m_SaveData[]; + + Materials m_Material; + Explosions m_Explosion; + int m_idShard; + float m_angle; + int m_iszGibModel; + int m_iszSpawnObject; +}; + +#endif // FUNC_BREAK_H diff --git a/hlsdk/dmc/dlls/game.cpp b/hlsdk/dmc/dlls/game.cpp new file mode 100644 index 00000000..e5148153 --- /dev/null +++ b/hlsdk/dmc/dlls/game.cpp @@ -0,0 +1,892 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "eiface.h" +#include "util.h" +#include "game.h" + +// QUAKECLASSIC +cvar_t rj = {"rj", "0"}; + +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 }; // " " + +// multiplayer server rules +cvar_t teamplay = {"mp_teamplay","0", FCVAR_SERVER }; + +cvar_t fraglimit = {"mp_fraglimit","0", 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","1", 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 mp_chattime = {"mp_chattime","10", FCVAR_SERVER }; + +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 + +// Register your console variables here +// This gets called one time when the game is initialied +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" ); + + // QUAKECLASSIC + CVAR_REGISTER (&rj); + + CVAR_REGISTER (&displaysoundlist); + + CVAR_REGISTER (&teamplay); + CVAR_REGISTER (&fraglimit); + 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 (&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/hlsdk/dmc/dlls/game.h b/hlsdk/dmc/dlls/game.h new file mode 100644 index 00000000..e61c3f30 --- /dev/null +++ b/hlsdk/dmc/dlls/game.h @@ -0,0 +1,46 @@ +/*** +* +* Copyright (c) 1996-2002 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef GAME_H +#define GAME_H + +extern void GameDLLInit( void ); + + +extern cvar_t displaysoundlist; +extern cvar_t mapcyclefile; +extern cvar_t servercfgfile; +extern cvar_t lservercfgfile; + +// multiplayer server rules +extern cvar_t fraglimit; +extern cvar_t timelimit; +extern cvar_t friendlyfir; +extern cvar_t falldamage; +extern cvar_t weaponstay; +extern cvar_t forcerespaw; +extern cvar_t flashlight; +extern cvar_t aimcrosshair; +extern cvar_t decalfrequency; +extern cvar_t teamlist; +extern cvar_t teamoverride; +extern cvar_t defaultteam; + +// 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/hlsdk/dmc/dlls/gamerules.cpp b/hlsdk/dmc/dlls/gamerules.cpp new file mode 100644 index 00000000..c5da3fcf --- /dev/null +++ b/hlsdk/dmc/dlls/gamerules.cpp @@ -0,0 +1,179 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// GameRules.cpp +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "teamplay_gamerules.h" + +extern Vector g_vecTeleMins[ MAX_TELES ]; +extern Vector g_vecTeleMaxs[ MAX_TELES ]; +extern int g_iTeleNum; + +#include "skill.h" + +extern edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ); + +DLL_GLOBAL CGameRules* g_pGameRules = NULL; +extern DLL_GLOBAL BOOL g_fGameOver; +extern int gmsgDeathMsg; // client dll messages +extern int gmsgScoreInfo; +extern int gmsgMOTD; + +//========================================================= +//========================================================= +BOOL CGameRules::CanHaveAmmo( CBasePlayer *pPlayer, const char *pszAmmoName, int iMaxCarry ) +{ + int iAmmoIndex; + + if ( pszAmmoName ) + { + iAmmoIndex = pPlayer->GetAmmoIndex( pszAmmoName ); + + if ( iAmmoIndex > -1 ) + { + if ( pPlayer->AmmoInventory( iAmmoIndex ) < iMaxCarry ) + { + // player has room for more of this type of ammo + return TRUE; + } + } + } + + return FALSE; +} + +//========================================================= +//========================================================= +edict_t *CGameRules :: GetPlayerSpawnSpot( CBasePlayer *pPlayer ) +{ + edict_t *pentSpawnSpot = EntSelectSpawnPoint( pPlayer ); + + pPlayer->pev->origin = VARS(pentSpawnSpot)->origin + Vector(0,0,1); + pPlayer->pev->v_angle = g_vecZero; + pPlayer->pev->velocity = g_vecZero; + pPlayer->pev->angles = VARS(pentSpawnSpot)->angles; + pPlayer->pev->punchangle = g_vecZero; + pPlayer->pev->fixangle = TRUE; + + return pentSpawnSpot; +} + +//========================================================= +//========================================================= +BOOL CGameRules::CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ + if ( pWeapon->pszAmmo1() ) + { + if ( !CanHaveAmmo( pPlayer, pWeapon->pszAmmo1(), pWeapon->iMaxAmmo1() ) ) + { + // we can't carry anymore ammo for this gun. We can only + // have the gun if we aren't already carrying one of this type + if ( pPlayer->HasPlayerItem( pWeapon ) ) + { + return FALSE; + } + } + } + else + { + // weapon doesn't use ammo, don't take another if you already have it. + if ( pPlayer->HasPlayerItem( pWeapon ) ) + { + return FALSE; + } + } + + // note: will fall through to here if GetItemInfo doesn't fill the struct! + return TRUE; +} + +//========================================================= +// load the SkillData struct with the proper values based on the skill level. +//========================================================= +void CGameRules::RefreshSkillData ( void ) +{ + int iSkill; + + iSkill = (int)CVAR_GET_FLOAT("skill"); + + if ( iSkill < 1 ) + { + iSkill = 1; + } + else if ( iSkill > 3 ) + { + iSkill = 3; + } + + gSkillData.iSkillLevel = iSkill; +} + +//========================================================= +// instantiate the proper game rules object +//========================================================= + +CGameRules *InstallGameRules( void ) +{ + SERVER_COMMAND( "exec game.cfg\n" ); + SERVER_EXECUTE( ); + + //Clear all the teleporters + for ( int i = 0; i < MAX_TELES; i++ ) + { + g_vecTeleMins[ i ].x = 0.0; + g_vecTeleMins[ i ].y = 0.0; + g_vecTeleMins[ i ].z = 0.0; + + g_vecTeleMaxs[ i ].x = 0.0; + g_vecTeleMaxs[ i ].y = 0.0; + g_vecTeleMaxs[ i ].z = 0.0; + } + + g_iTeleNum = 0; + + if ( !gpGlobals->deathmatch ) + { + // generic half-life + return new CHalfLifeRules; + } + else + { + if ( CVAR_GET_FLOAT( "mp_teamplay" ) > 0 ) + { + // teamplay + return new CHalfLifeTeamplay; + } + if ((int)gpGlobals->deathmatch == 1) + { + // vanilla deathmatch + return new CHalfLifeMultiplay; + } + else + { + // vanilla deathmatch?? + return new CHalfLifeMultiplay; + } + } +} + + + diff --git a/hlsdk/dmc/dlls/gamerules.h b/hlsdk/dmc/dlls/gamerules.h new file mode 100644 index 00000000..7886df4a --- /dev/null +++ b/hlsdk/dmc/dlls/gamerules.h @@ -0,0 +1,370 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// GameRules +//========================================================= + +//#include "weapons.h" +//#include "items.h" +class CBasePlayerItem; +class CBasePlayer; +class CItem; +class CBasePlayerAmmo; + +// weapon respawning return codes +enum +{ + GR_NONE = 0, + + GR_WEAPON_RESPAWN_YES, + GR_WEAPON_RESPAWN_NO, + + GR_AMMO_RESPAWN_YES, + GR_AMMO_RESPAWN_NO, + + GR_ITEM_RESPAWN_YES, + GR_ITEM_RESPAWN_NO, + + GR_PLR_DROP_GUN_ALL, + GR_PLR_DROP_GUN_ACTIVE, + GR_PLR_DROP_GUN_NO, + + GR_PLR_DROP_AMMO_ALL, + GR_PLR_DROP_AMMO_ACTIVE, + GR_PLR_DROP_AMMO_NO, +}; + +// Player relationship return codes +enum +{ + GR_NOTTEAMMATE = 0, + GR_TEAMMATE, + GR_ENEMY, + GR_ALLY, + GR_NEUTRAL, +}; + +class CGameRules +{ +public: + virtual void RefreshSkillData( void );// fill skill data struct with proper values + virtual void Think( void ) = 0;// GR_Think - runs every server frame, should handle any timer tasks, periodic events, etc. + virtual BOOL IsAllowedToSpawn( CBaseEntity *pEntity ) = 0; // Can this item spawn (eg monsters don't spawn in deathmatch). + + virtual BOOL FAllowFlashlight( void ) = 0;// Are players allowed to switch on their flashlight? + virtual BOOL FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) = 0;// should the player switch to this weapon? + virtual BOOL GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) = 0;// I can't use this weapon anymore, get me the next best one. + +// Functions to verify the single/multiplayer status of a game + virtual BOOL IsMultiplayer( void ) = 0;// is this a multiplayer game? (either coop or deathmatch) + 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 "DMC"; } // 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) + virtual void InitHUD( CBasePlayer *pl ) = 0; // the client dll is ready for updating + virtual void ClientDisconnected( edict_t *pClient ) = 0;// a client just disconnected from the server + virtual void UpdateGameMode( CBasePlayer *pPlayer ) {} // the client needs to be informed of the current game mode + +// Client damage rules + 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; } + +// Client spawn/respawn control + virtual void PlayerSpawn( CBasePlayer *pPlayer ) = 0;// called by CBasePlayer::Spawn just before releasing player into the game + virtual void PlayerThink( CBasePlayer *pPlayer ) = 0; // called by CBasePlayer::PreThink every frame, before physics are run and after keys are accepted + virtual BOOL FPlayerCanRespawn( CBasePlayer *pPlayer ) = 0;// is this player allowed to respawn now? + virtual float FlPlayerSpawnTime( CBasePlayer *pPlayer ) = 0;// When in the future will this player be able to spawn? + virtual edict_t *GetPlayerSpawnSpot( CBasePlayer *pPlayer );// Place this player on their spawnspot and face them the proper direction. + + virtual BOOL AllowAutoTargetCrosshair( void ) { return TRUE; }; + virtual BOOL ClientCommand( CBasePlayer *pPlayer, const char *pcmd ) { return FALSE; }; // handles the user commands; returns TRUE if command handled properly + virtual void ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ) {} // the player has changed userinfo; can change it now + +// 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 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? + virtual void PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) = 0;// Called each time a player picks up a weapon from the ground + +// Weapon spawn/respawn control + virtual int WeaponShouldRespawn( CBasePlayerItem *pWeapon ) = 0;// should this weapon respawn? + virtual float FlWeaponRespawnTime( CBasePlayerItem *pWeapon ) = 0;// when may this weapon respawn? + virtual float FlWeaponTryRespawn( CBasePlayerItem *pWeapon ) = 0; // can i respawn now, and if not, when should i try again? + virtual Vector VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ) = 0;// where in the world should this weapon respawn? + +// 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) + +// Item spawn/respawn control + virtual int ItemShouldRespawn( CItem *pItem ) = 0;// Should this item respawn? + virtual float FlItemRespawnTime( CItem *pItem ) = 0;// when may this item respawn? + virtual Vector VecItemRespawnSpot( CItem *pItem ) = 0;// where in the world should this item respawn? + +// Ammo retrieval + virtual BOOL CanHaveAmmo( CBasePlayer *pPlayer, const char *pszAmmoName, int iMaxCarry );// can this player take more of this ammo? + virtual void PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ) = 0;// called each time a player picks up some ammo in the world + +// Ammo spawn/respawn control + virtual int AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ) = 0;// should this ammo item respawn? + virtual float FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ) = 0;// when should this ammo item respawn? + virtual Vector VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ) = 0;// where in the world should this ammo item respawn? + // by default, everything spawns + +// Healthcharger respawn control + virtual float FlHealthChargerRechargeTime( void ) = 0;// how long until a depleted HealthCharger recharges itself? + virtual float FlHEVChargerRechargeTime( void ) { return 0; }// how long until a depleted HealthCharger recharges itself? + +// What happens to a dead player's weapons + virtual int DeadPlayerWeapons( CBasePlayer *pPlayer ) = 0;// what do I do with a player's weapons when he's killed? + +// What happens to a dead player's ammo + virtual int DeadPlayerAmmo( CBasePlayer *pPlayer ) = 0;// Do I drop ammo when the player dies? How much? + +// Teamplay stuff + virtual const char *GetTeamID( CBaseEntity *pEntity ) = 0;// what team is this entity on? + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) = 0;// What is the player's relationship with this entity? + virtual int GetTeamIndex( const char *pTeamName ) { return -1; } + virtual const char *GetIndexedTeamName( int teamIndex ) { return ""; } + virtual BOOL IsValidTeam( const char *pTeamName ) { return TRUE; } + virtual void ChangePlayerTeam( CBasePlayer *pPlayer, const char *pTeamName, BOOL bKill, BOOL bGib ) {} + virtual const char *SetDefaultPlayerTeam( CBasePlayer *pPlayer ) { return ""; } + +// Sounds + virtual BOOL PlayTextureSounds( void ) { return TRUE; } + virtual BOOL PlayFootstepSounds( CBasePlayer *pl, float fvol ) { return TRUE; } + +// Monsters + virtual BOOL FAllowMonsters( void ) = 0;//are monsters allowed + + // Immediately end a multiplayer game + virtual void EndMultiplayerGame( void ) {} +}; + +extern CGameRules *InstallGameRules( void ); + + +//========================================================= +// CHalfLifeRules - rules for the single player Half-Life +// game. +//========================================================= +class CHalfLifeRules : public CGameRules +{ +public: + CHalfLifeRules ( void ); + +// GR_Think + virtual void Think( void ); + virtual BOOL IsAllowedToSpawn( CBaseEntity *pEntity ); + virtual BOOL FAllowFlashlight( void ) { return TRUE; }; + + virtual BOOL FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + virtual BOOL GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ); + +// Functions to verify the single/multiplayer status of a game + virtual BOOL IsMultiplayer( void ); + virtual BOOL IsDeathmatch( void ); + virtual BOOL IsCoOp( void ); + +// Client connection/disconnection + virtual BOOL ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); + virtual void InitHUD( CBasePlayer *pl ); // the client dll is ready for updating + virtual void ClientDisconnected( edict_t *pClient ); + +// Client damage rules + virtual float FlPlayerFallDamage( CBasePlayer *pPlayer ); + +// Client spawn/respawn control + virtual void PlayerSpawn( CBasePlayer *pPlayer ); + virtual void PlayerThink( CBasePlayer *pPlayer ); + virtual BOOL FPlayerCanRespawn( CBasePlayer *pPlayer ); + virtual float FlPlayerSpawnTime( CBasePlayer *pPlayer ); + + virtual BOOL AllowAutoTargetCrosshair( void ); + +// 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 ); + +// Weapon retrieval + virtual void PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + +// Weapon spawn/respawn control + virtual int WeaponShouldRespawn( CBasePlayerItem *pWeapon ); + virtual float FlWeaponRespawnTime( CBasePlayerItem *pWeapon ); + virtual float FlWeaponTryRespawn( CBasePlayerItem *pWeapon ); + virtual Vector VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ); + +// Item retrieval + virtual BOOL CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ); + virtual void PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ); + +// Item spawn/respawn control + virtual int ItemShouldRespawn( CItem *pItem ); + virtual float FlItemRespawnTime( CItem *pItem ); + virtual Vector VecItemRespawnSpot( CItem *pItem ); + +// Ammo retrieval + virtual void PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ); + +// Ammo spawn/respawn control + virtual int AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ); + virtual float FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ); + virtual Vector VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ); + +// Healthcharger respawn control + virtual float FlHealthChargerRechargeTime( void ); + +// What happens to a dead player's weapons + virtual int DeadPlayerWeapons( CBasePlayer *pPlayer ); + +// What happens to a dead player's ammo + virtual int DeadPlayerAmmo( CBasePlayer *pPlayer ); + +// Monsters + virtual BOOL FAllowMonsters( void ); + +// Teamplay stuff + virtual const char *GetTeamID( CBaseEntity *pEntity ) {return "";}; + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); +}; + + +#include "voice_gamemgr.h" + +//========================================================= +// CHalfLifeMultiplay - rules for the basic half life multiplayer +// competition +//========================================================= +class CHalfLifeMultiplay : public CGameRules +{ +public: + CHalfLifeMultiplay(); + + virtual BOOL ClientCommand( CBasePlayer *pPlayer, const char *pcmd ); + +// GR_Think + virtual void Think( void ); + virtual void RefreshSkillData( void ); + virtual BOOL IsAllowedToSpawn( CBaseEntity *pEntity ); + virtual BOOL FAllowFlashlight( void ); + + virtual BOOL FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + virtual BOOL GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ); + +// Functions to verify the single/multiplayer status of a game + virtual BOOL IsMultiplayer( void ); + virtual BOOL IsDeathmatch( void ); + virtual BOOL IsCoOp( void ); + +// Client connection/disconnection + // If ClientConnected returns FALSE, the connection is rejected and the user is provided the reason specified in + // svRejectReason + // Only the client's name and remote address are provided to the dll for verification. + virtual BOOL ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); + virtual void InitHUD( CBasePlayer *pl ); // the client dll is ready for updating + virtual void ClientDisconnected( edict_t *pClient ); + virtual void UpdateGameMode( CBasePlayer *pPlayer ); // the client needs to be informed of the current game mode + +// Client damage rules + virtual float FlPlayerFallDamage( CBasePlayer *pPlayer ); + virtual BOOL FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ); + +// Client spawn/respawn control + virtual void PlayerSpawn( CBasePlayer *pPlayer ); + virtual void PlayerThink( CBasePlayer *pPlayer ); + virtual BOOL FPlayerCanRespawn( CBasePlayer *pPlayer ); + virtual float FlPlayerSpawnTime( CBasePlayer *pPlayer ); + virtual edict_t *GetPlayerSpawnSpot( CBasePlayer *pPlayer ); + + virtual BOOL AllowAutoTargetCrosshair( void ); + +// 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 ); + +// Weapon retrieval + virtual void PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + virtual BOOL CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon );// The player is touching an CBasePlayerItem, do I give it to him? + +// Weapon spawn/respawn control + virtual int WeaponShouldRespawn( CBasePlayerItem *pWeapon ); + virtual float FlWeaponRespawnTime( CBasePlayerItem *pWeapon ); + virtual float FlWeaponTryRespawn( CBasePlayerItem *pWeapon ); + virtual Vector VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ); + +// Item retrieval + virtual BOOL CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ); + virtual void PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ); + +// Item spawn/respawn control + virtual int ItemShouldRespawn( CItem *pItem ); + virtual float FlItemRespawnTime( CItem *pItem ); + virtual Vector VecItemRespawnSpot( CItem *pItem ); + +// Ammo retrieval + virtual void PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ); + +// Ammo spawn/respawn control + virtual int AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ); + virtual float FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ); + virtual Vector VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ); + +// Healthcharger respawn control + virtual float FlHealthChargerRechargeTime( void ); + virtual float FlHEVChargerRechargeTime( void ); + +// What happens to a dead player's weapons + virtual int DeadPlayerWeapons( CBasePlayer *pPlayer ); + +// What happens to a dead player's ammo + virtual int DeadPlayerAmmo( CBasePlayer *pPlayer ); + +// Teamplay stuff + virtual const char *GetTeamID( CBaseEntity *pEntity ) {return "";} + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); + + virtual BOOL PlayTextureSounds( void ) { return FALSE; } + virtual BOOL PlayFootstepSounds( CBasePlayer *pl, float fvol ); + +// Monsters + virtual BOOL FAllowMonsters( void ); + + // Immediately end a multiplayer game + virtual void EndMultiplayerGame( void ) { GoToIntermission(); } + + CVoiceGameMgr m_VoiceGameMgr; + +protected: + virtual void ChangeLevel( void ); + virtual void GoToIntermission( void ); + float m_flIntermissionEndTime; + float m_flIntermissionStartTime; + BOOL m_iEndIntermissionButtonHit; + void SendMOTDToClient( edict_t *client ); + + float m_flGameEndTime; + virtual void ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ); +}; + +extern DLL_GLOBAL CGameRules* g_pGameRules; diff --git a/hlsdk/dmc/dlls/globals.cpp b/hlsdk/dmc/dlls/globals.cpp new file mode 100644 index 00000000..ef657c2d --- /dev/null +++ b/hlsdk/dmc/dlls/globals.cpp @@ -0,0 +1,39 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== globals.cpp ======================================================== + + DLL-wide global variable definitions. + They're all defined here, for convenient centralization. + Source files that need them should "extern ..." declare each + variable, to better document what globals they care about. + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "soundent.h" + +DLL_GLOBAL ULONG g_ulFrameCount; +DLL_GLOBAL ULONG g_ulModelIndexEyes; +DLL_GLOBAL ULONG g_ulModelIndexPlayer; +DLL_GLOBAL Vector g_vecAttackDir; +DLL_GLOBAL int g_iSkillLevel; +DLL_GLOBAL int gDisplayTitle; +DLL_GLOBAL BOOL g_fGameOver; +DLL_GLOBAL const Vector g_vecZero = Vector(0,0,0); +DLL_GLOBAL int g_Language; diff --git a/hlsdk/dmc/dlls/h_ai.cpp b/hlsdk/dmc/dlls/h_ai.cpp new file mode 100644 index 00000000..7eabdf83 --- /dev/null +++ b/hlsdk/dmc/dlls/h_ai.cpp @@ -0,0 +1,198 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + + h_ai.cpp - halflife specific ai code + +*/ + + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" + +#define NUM_LATERAL_CHECKS 13 // how many checks are made on each side of a monster looking for lateral cover +#define NUM_LATERAL_LOS_CHECKS 6 // how many checks are made on each side of a monster looking for lateral cover + +//float flRandom = RANDOM_FLOAT(0,1); + +DLL_GLOBAL BOOL g_fDrawLines = FALSE; + +//========================================================= +// +// AI UTILITY FUNCTIONS +// +// !!!UNDONE - move CBaseMonster functions to monsters.cpp +//========================================================= + +//========================================================= +// FBoxVisible - a more accurate ( and slower ) version +// of FVisible. +// +// !!!UNDONE - make this CBaseMonster? +//========================================================= +BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget, Vector &vecTargetOrigin, float flSize ) +{ + // don't look through water + if ((pevLooker->waterlevel != 3 && pevTarget->waterlevel == 3) + || (pevLooker->waterlevel == 3 && pevTarget->waterlevel == 0)) + return FALSE; + + TraceResult tr; + Vector vecLookerOrigin = pevLooker->origin + pevLooker->view_ofs;//look through the monster's 'eyes' + for (int i = 0; i < 5; i++) + { + Vector vecTarget = pevTarget->origin; + vecTarget.x += RANDOM_FLOAT( pevTarget->mins.x + flSize, pevTarget->maxs.x - flSize); + vecTarget.y += RANDOM_FLOAT( pevTarget->mins.y + flSize, pevTarget->maxs.y - flSize); + vecTarget.z += RANDOM_FLOAT( pevTarget->mins.z + flSize, pevTarget->maxs.z - flSize); + + UTIL_TraceLine(vecLookerOrigin, vecTarget, ignore_monsters, ignore_glass, ENT(pevLooker)/*pentIgnore*/, &tr); + + if (tr.flFraction == 1.0) + { + vecTargetOrigin = vecTarget; + return TRUE;// line of sight is valid. + } + } + return FALSE;// Line of sight is not established +} + +// +// VecCheckToss - returns the velocity at which an object should be lobbed from vecspot1 to land near vecspot2. +// returns g_vecZero if toss is not feasible. +// +Vector VecCheckToss ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flGravityAdj ) +{ + TraceResult tr; + Vector vecMidPoint;// halfway point between Spot1 and Spot2 + Vector vecApex;// highest point + Vector vecScale; + Vector vecGrenadeVel; + Vector vecTemp; + float flGravity = CVAR_GET_FLOAT( "sv_gravity" ) * flGravityAdj; + + if (vecSpot2.z - vecSpot1.z > 500) + { + // to high, fail + return g_vecZero; + } + + UTIL_MakeVectors (pev->angles); + + // toss a little bit to the left or right, not right down on the enemy's bean (head). + vecSpot2 = vecSpot2 + gpGlobals->v_right * ( RANDOM_FLOAT(-8,8) + RANDOM_FLOAT(-16,16) ); + vecSpot2 = vecSpot2 + gpGlobals->v_forward * ( RANDOM_FLOAT(-8,8) + RANDOM_FLOAT(-16,16) ); + + // calculate the midpoint and apex of the 'triangle' + // UNDONE: normalize any Z position differences between spot1 and spot2 so that triangle is always RIGHT + + // How much time does it take to get there? + + // get a rough idea of how high it can be thrown + vecMidPoint = vecSpot1 + (vecSpot2 - vecSpot1) * 0.5; + UTIL_TraceLine(vecMidPoint, vecMidPoint + Vector(0,0,500), ignore_monsters, ENT(pev), &tr); + vecMidPoint = tr.vecEndPos; + // (subtract 15 so the grenade doesn't hit the ceiling) + vecMidPoint.z -= 15; + + if (vecMidPoint.z < vecSpot1.z || vecMidPoint.z < vecSpot2.z) + { + // to not enough space, fail + return g_vecZero; + } + + // How high should the grenade travel to reach the apex + float distance1 = (vecMidPoint.z - vecSpot1.z); + float distance2 = (vecMidPoint.z - vecSpot2.z); + + // How long will it take for the grenade to travel this distance + float time1 = sqrt( distance1 / (0.5 * flGravity) ); + float time2 = sqrt( distance2 / (0.5 * flGravity) ); + + if (time1 < 0.1) + { + // too close + return g_vecZero; + } + + // how hard to throw sideways to get there in time. + vecGrenadeVel = (vecSpot2 - vecSpot1) / (time1 + time2); + // how hard upwards to reach the apex at the right time. + vecGrenadeVel.z = flGravity * time1; + + // find the apex + vecApex = vecSpot1 + vecGrenadeVel * time1; + vecApex.z = vecMidPoint.z; + + UTIL_TraceLine(vecSpot1, vecApex, dont_ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + // UNDONE: either ignore monsters or change it to not care if we hit our enemy + UTIL_TraceLine(vecSpot2, vecApex, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + return vecGrenadeVel; +} + + +// +// VecCheckThrow - returns the velocity vector at which an object should be thrown from vecspot1 to hit vecspot2. +// returns g_vecZero if throw is not feasible. +// +Vector VecCheckThrow ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flSpeed, float flGravityAdj ) +{ + float flGravity = CVAR_GET_FLOAT( "sv_gravity" ) * flGravityAdj; + + Vector vecGrenadeVel = (vecSpot2 - vecSpot1); + + // throw at a constant time + float time = vecGrenadeVel.Length( ) / flSpeed; + vecGrenadeVel = vecGrenadeVel * (1.0 / time); + + // adjust upward toss to compensate for gravity loss + vecGrenadeVel.z += flGravity * time * 0.5; + + Vector vecApex = vecSpot1 + (vecSpot2 - vecSpot1) * 0.5; + vecApex.z += 0.5 * flGravity * (time * 0.5) * (time * 0.5); + + TraceResult tr; + UTIL_TraceLine(vecSpot1, vecApex, dont_ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + UTIL_TraceLine(vecSpot2, vecApex, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + return vecGrenadeVel; +} + + diff --git a/hlsdk/dmc/dlls/h_export.cpp b/hlsdk/dmc/dlls/h_export.cpp new file mode 100644 index 00000000..e7cdc7c1 --- /dev/null +++ b/hlsdk/dmc/dlls/h_export.cpp @@ -0,0 +1,69 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== h_export.cpp ======================================================== + + Entity classes exported by Halflife. + +*/ + +#include "extdll.h" +#include "util.h" + +#include "cbase.h" + +// Holds engine functionality callbacks +enginefuncs_t g_engfuncs; +globalvars_t *gpGlobals; + + +#ifdef _WIN32 + +// Required DLL entry point +BOOL WINAPI DllMain( + HINSTANCE hinstDLL, + DWORD fdwReason, + LPVOID lpvReserved) +{ + if (fdwReason == DLL_PROCESS_ATTACH) + { + } + else if (fdwReason == DLL_PROCESS_DETACH) + { + } + return TRUE; +} + +void DLLEXPORT GiveFnptrsToDll( enginefuncs_t* pengfuncsFromEngine, globalvars_t *pGlobals ) +{ + memcpy(&g_engfuncs, pengfuncsFromEngine, sizeof(enginefuncs_t)); + gpGlobals = pGlobals; +} + + +#else + +extern "C" { + +void GiveFnptrsToDll( enginefuncs_t* pengfuncsFromEngine, globalvars_t *pGlobals ) +{ + memcpy(&g_engfuncs, pengfuncsFromEngine, sizeof(enginefuncs_t)); + gpGlobals = pGlobals; +} + +} + +#endif diff --git a/hlsdk/dmc/dlls/items.cpp b/hlsdk/dmc/dlls/items.cpp new file mode 100644 index 00000000..865639eb --- /dev/null +++ b/hlsdk/dmc/dlls/items.cpp @@ -0,0 +1,337 @@ +/*** +* +* Copyright (c) 1996-2002 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== items.cpp ======================================================== + + functions governing the selection/use of weapons for players + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "player.h" +#include "skill.h" +#include "items.h" +#include "gamerules.h" + +extern int gmsgItemPickup; + +class CWorldItem : public CBaseEntity +{ +public: + void KeyValue(KeyValueData *pkvd ); + void Spawn( void ); + int m_iType; +}; + +LINK_ENTITY_TO_CLASS(world_items, CWorldItem); + +void CWorldItem::KeyValue(KeyValueData *pkvd) +{ + if (FStrEq(pkvd->szKeyName, "type")) + { + m_iType = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CWorldItem::Spawn( void ) +{ + CBaseEntity *pEntity = NULL; + + 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) + { + ALERT( at_console, "unable to create world_item %d\n", m_iType ); + } + else + { + pEntity->pev->target = pev->target; + pEntity->pev->targetname = pev->targetname; + pEntity->pev->spawnflags = pev->spawnflags; + } + + REMOVE_ENTITY(edict()); +} + + +void CItem::Spawn( void ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + UTIL_SetOrigin( pev, pev->origin ); + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 16)); + SetTouch(&CItem::ItemTouch); + + if (DROP_TO_FLOOR(ENT(pev)) == 0) + { + ALERT(at_error, "Item %s fell out of level at %f,%f,%f", STRING( pev->classname ), pev->origin.x, pev->origin.y, pev->origin.z); + UTIL_Remove( this ); + return; + } +} + +extern int gEvilImpulse101; + +void CItem::ItemTouch( CBaseEntity *pOther ) +{ + // if it's not a player, ignore + if ( !pOther->IsPlayer() ) + { + return; + } + + CBasePlayer *pPlayer = (CBasePlayer *)pOther; + + // ok, a player is touching this item, but can he have it? + if ( !g_pGameRules->CanHaveItem( pPlayer, this ) ) + { + // no? Ignore the touch. + return; + } + + if (MyTouch( pPlayer )) + { + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); + SetTouch( NULL ); + + // player grabbed the item. + g_pGameRules->PlayerGotItem( pPlayer, this ); + if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_YES ) + { + Respawn(); + } + else + { + UTIL_Remove( this ); + } + } + else if (gEvilImpulse101) + { + UTIL_Remove( this ); + } +} + +CBaseEntity* CItem::Respawn( void ) +{ + SetTouch( NULL ); + pev->effects |= EF_NODRAW; + + UTIL_SetOrigin( pev, g_pGameRules->VecItemRespawnSpot( this ) );// blip to whereever you should respawn. + + SetThink ( &CItem::Materialize ); + pev->nextthink = g_pGameRules->FlItemRespawnTime( this ); + return this; +} + +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; + } + + SetTouch( &CItem::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->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/hlsdk/dmc/dlls/items.h b/hlsdk/dmc/dlls/items.h new file mode 100644 index 00000000..e9852966 --- /dev/null +++ b/hlsdk/dmc/dlls/items.h @@ -0,0 +1,29 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef ITEMS_H +#define ITEMS_H + + +class CItem : public CBaseEntity +{ +public: + void Spawn( void ); + CBaseEntity* Respawn( void ); + void EXPORT ItemTouch( CBaseEntity *pOther ); + void EXPORT Materialize( void ); + virtual BOOL MyTouch( CBasePlayer *pPlayer ) { return FALSE; }; +}; + +#endif // ITEMS_H diff --git a/hlsdk/dmc/dlls/lights.cpp b/hlsdk/dmc/dlls/lights.cpp new file mode 100644 index 00000000..b9488b5e --- /dev/null +++ b/hlsdk/dmc/dlls/lights.cpp @@ -0,0 +1,199 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== lights.cpp ======================================================== + + spawn and think functions for editor-placed lights + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" + + + +class CLight : public CPointEntity +{ +public: + virtual void KeyValue( KeyValueData* pkvd ); + virtual void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_iStyle; + int m_iszPattern; +}; +LINK_ENTITY_TO_CLASS( light, CLight ); + +TYPEDESCRIPTION CLight::m_SaveData[] = +{ + DEFINE_FIELD( CLight, m_iStyle, FIELD_INTEGER ), + DEFINE_FIELD( CLight, m_iszPattern, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CLight, CPointEntity ); + + +// +// Cache user-entity-field values until spawn is called. +// +void CLight :: KeyValue( KeyValueData* pkvd) +{ + if (FStrEq(pkvd->szKeyName, "style")) + { + m_iStyle = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitch")) + { + pev->angles.x = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pattern")) + { + m_iszPattern = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + { + CPointEntity::KeyValue( pkvd ); + } +} + +/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) LIGHT_START_OFF +Non-displayed light. +Default light value is 300 +Default style is 0 +If targeted, it will toggle between on or off. +*/ + +void CLight :: Spawn( void ) +{ + if (FStringNull(pev->targetname)) + { // inert light + REMOVE_ENTITY(ENT(pev)); + return; + } + + if (m_iStyle >= 32) + { +// CHANGE_METHOD(ENT(pev), em_use, light_use); + if (FBitSet(pev->spawnflags, SF_LIGHT_START_OFF)) + LIGHT_STYLE(m_iStyle, "a"); + else if (m_iszPattern) + LIGHT_STYLE(m_iStyle, (char *)STRING( m_iszPattern )); + else + LIGHT_STYLE(m_iStyle, "m"); + } +} + + +void CLight :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (m_iStyle >= 32) + { + if ( !ShouldToggle( useType, !FBitSet(pev->spawnflags, SF_LIGHT_START_OFF) ) ) + return; + + if (FBitSet(pev->spawnflags, SF_LIGHT_START_OFF)) + { + if (m_iszPattern) + LIGHT_STYLE(m_iStyle, (char *)STRING( m_iszPattern )); + else + LIGHT_STYLE(m_iStyle, "m"); + ClearBits(pev->spawnflags, SF_LIGHT_START_OFF); + } + else + { + LIGHT_STYLE(m_iStyle, "a"); + SetBits(pev->spawnflags, SF_LIGHT_START_OFF); + } + } +} + +// +// shut up spawn functions for new spotlights +// +LINK_ENTITY_TO_CLASS( light_spot, CLight ); + + +class CEnvLight : public CLight +{ +public: + void KeyValue( KeyValueData* pkvd ); + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( light_environment, CEnvLight ); + +void CEnvLight::KeyValue( KeyValueData* pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "_light")) + { + int r, g, b, v, j; + char szColor[64]; + j = sscanf( pkvd->szValue, "%d %d %d %d\n", &r, &g, &b, &v ); + if (j == 1) + { + g = b = r; + } + else if (j == 4) + { + r = r * (v / 255); + g = g * (v / 255); + b = b * (v / 255); + } + + // simulate qrad direct, ambient,and gamma adjustments, as well as engine scaling + r = static_cast(powf( r / 114.0f, 0.6f ) * 264); + g = static_cast(powf( g / 114.0f, 0.6f ) * 264); + b = static_cast(powf( b / 114.0f, 0.6f ) * 264); + + pkvd->fHandled = TRUE; + sprintf( szColor, "%d", r ); + CVAR_SET_STRING( "sv_skycolor_r", szColor ); + sprintf( szColor, "%d", g ); + CVAR_SET_STRING( "sv_skycolor_g", szColor ); + sprintf( szColor, "%d", b ); + CVAR_SET_STRING( "sv_skycolor_b", szColor ); + } + else + { + CLight::KeyValue( pkvd ); + } +} + + +void CEnvLight :: Spawn( void ) +{ + char szVector[64]; + UTIL_MakeAimVectors( pev->angles ); + + sprintf( szVector, "%f", gpGlobals->v_forward.x ); + CVAR_SET_STRING( "sv_skyvec_x", szVector ); + sprintf( szVector, "%f", gpGlobals->v_forward.y ); + CVAR_SET_STRING( "sv_skyvec_y", szVector ); + sprintf( szVector, "%f", gpGlobals->v_forward.z ); + CVAR_SET_STRING( "sv_skyvec_z", szVector ); + + CLight::Spawn( ); +} diff --git a/hlsdk/dmc/dlls/maprules.cpp b/hlsdk/dmc/dlls/maprules.cpp new file mode 100644 index 00000000..a07562df --- /dev/null +++ b/hlsdk/dmc/dlls/maprules.cpp @@ -0,0 +1,918 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +// ------------------------------------------- +// +// maprules.cpp +// +// This module contains entities for implementing/changing game +// rules dynamically within each map (.BSP) +// +// ------------------------------------------- + +#include "extdll.h" +#include "eiface.h" +#include "util.h" +#include "gamerules.h" +#include "maprules.h" +#include "cbase.h" +#include "player.h" + +class CRuleEntity : public CBaseEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void SetMaster( int iszMaster ) { m_iszMaster = iszMaster; } + +protected: + BOOL CanFireForActivator( CBaseEntity *pActivator ); + +private: + string_t m_iszMaster; +}; + +TYPEDESCRIPTION CRuleEntity::m_SaveData[] = +{ + DEFINE_FIELD( CRuleEntity, m_iszMaster, FIELD_STRING), +}; + +IMPLEMENT_SAVERESTORE( CRuleEntity, CBaseEntity ); + + +void CRuleEntity::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = EF_NODRAW; +} + + +void CRuleEntity::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "master")) + { + SetMaster( ALLOC_STRING(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +BOOL CRuleEntity::CanFireForActivator( CBaseEntity *pActivator ) +{ + if ( m_iszMaster ) + { + if ( UTIL_IsMasterTriggered( m_iszMaster, pActivator ) ) + return TRUE; + else + return FALSE; + } + + return TRUE; +} + +// +// CRulePointEntity -- base class for all rule "point" entities (not brushes) +// +class CRulePointEntity : public CRuleEntity +{ +public: + void Spawn( void ); +}; + +void CRulePointEntity::Spawn( void ) +{ + CRuleEntity::Spawn(); + pev->frame = 0; + pev->model = 0; +} + +// +// CRuleBrushEntity -- base class for all rule "brush" entities (not brushes) +// Default behavior is to set up like a trigger, invisible, but keep the model for volume testing +// +class CRuleBrushEntity : public CRuleEntity +{ +public: + void Spawn( void ); + +private: +}; + +void CRuleBrushEntity::Spawn( void ) +{ + SET_MODEL( edict(), STRING(pev->model) ); + CRuleEntity::Spawn(); +} + + +// CGameScore / game_score -- award points to player / team +// Points +/- total +// Flag: Allow negative scores SF_SCORE_NEGATIVE +// Flag: Award points to team in teamplay SF_SCORE_TEAM + +#define SF_SCORE_NEGATIVE 0x0001 +#define SF_SCORE_TEAM 0x0002 + +class CGameScore : public CRulePointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline int Points( void ) { return static_cast(pev->frags); } + inline BOOL AllowNegativeScore( void ) { return pev->spawnflags & SF_SCORE_NEGATIVE; } + inline BOOL AwardToTeam( void ) { return pev->spawnflags & SF_SCORE_TEAM; } + + inline void SetPoints( int points ) { pev->frags = points; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_score, CGameScore ); + + +void CGameScore::Spawn( void ) +{ + CRulePointEntity::Spawn(); +} + + +void CGameScore::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "points")) + { + SetPoints( atoi(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CRulePointEntity::KeyValue( pkvd ); +} + + + +void CGameScore::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + // Only players can use this + if ( pActivator->IsPlayer() ) + { + if ( AwardToTeam() ) + { + pActivator->AddPointsToTeam( Points(), AllowNegativeScore() ); + } + else + { + pActivator->AddPoints( Points(), AllowNegativeScore() ); + } + } +} + + +// CGameEnd / game_end -- Ends the game in MP + +class CGameEnd : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +private: +}; + +LINK_ENTITY_TO_CLASS( game_end, CGameEnd ); + + +void CGameEnd::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + g_pGameRules->EndMultiplayerGame(); +} + + +// +// CGameText / game_text -- NON-Localized HUD Message (use env_message to display a titles.txt message) +// Flag: All players SF_ENVTEXT_ALLPLAYERS +// + + +#define SF_ENVTEXT_ALLPLAYERS 0x0001 + + +class CGameText : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + inline BOOL MessageToAll( void ) { return (pev->spawnflags & SF_ENVTEXT_ALLPLAYERS); } + inline void MessageSet( const char *pMessage ) { pev->message = ALLOC_STRING(pMessage); } + inline const char *MessageGet( void ) { return STRING(pev->message); } + +private: + + hudtextparms_t m_textParms; +}; + +LINK_ENTITY_TO_CLASS( game_text, CGameText ); + +// Save parms as a block. Will break save/restore if the structure changes, but this entity didn't ship with Half-Life, so +// it can't impact saved Half-Life games. +TYPEDESCRIPTION CGameText::m_SaveData[] = +{ + DEFINE_ARRAY( CGameText, m_textParms, FIELD_CHARACTER, sizeof(hudtextparms_t) ), +}; + +IMPLEMENT_SAVERESTORE( CGameText, CRulePointEntity ); + + +void CGameText::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "channel")) + { + m_textParms.channel = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "x")) + { + m_textParms.x = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "y")) + { + m_textParms.y = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "effect")) + { + m_textParms.effect = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "color")) + { + int color[4]; + UTIL_StringToIntArray( color, 4, pkvd->szValue ); + m_textParms.r1 = color[0]; + m_textParms.g1 = color[1]; + m_textParms.b1 = color[2]; + m_textParms.a1 = color[3]; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "color2")) + { + int color[4]; + UTIL_StringToIntArray( color, 4, pkvd->szValue ); + m_textParms.r2 = color[0]; + m_textParms.g2 = color[1]; + m_textParms.b2 = color[2]; + m_textParms.a2 = color[3]; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "fadein")) + { + m_textParms.fadeinTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "fadeout")) + { + m_textParms.fadeoutTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "holdtime")) + { + m_textParms.holdTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "fxtime")) + { + m_textParms.fxTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CRulePointEntity::KeyValue( pkvd ); +} + + +void CGameText::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( MessageToAll() ) + { + UTIL_HudMessageAll( m_textParms, MessageGet() ); + } + else + { + if ( pActivator->IsNetClient() ) + { + UTIL_HudMessage( pActivator, m_textParms, MessageGet() ); + } + } +} + + +// +// 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 +// +// team index (pulled from server team list "mp_teamlist" +// Flag: Remove on Fire +// Flag: Any team until set? -- Any team can use this until the team is set (otherwise no teams can use it) +// + +#define SF_TEAMMASTER_FIREONCE 0x0001 +#define SF_TEAMMASTER_ANYTEAM 0x0002 + +class CGameTeamMaster : public CRulePointEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int ObjectCaps( void ) { return CRulePointEntity:: ObjectCaps() | FCAP_MASTER; } + + BOOL IsTriggered( CBaseEntity *pActivator ); + const char *TeamID( void ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_TEAMMASTER_FIREONCE) ? TRUE : FALSE; } + inline BOOL AnyTeam( void ) { return (pev->spawnflags & SF_TEAMMASTER_ANYTEAM) ? TRUE : FALSE; } + +private: + BOOL TeamMatch( CBaseEntity *pActivator ); + + int m_teamIndex; + USE_TYPE triggerType; +}; + +LINK_ENTITY_TO_CLASS( game_team_master, CGameTeamMaster ); + +void CGameTeamMaster::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "teamindex")) + { + m_teamIndex = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "triggerstate")) + { + int type = atoi( pkvd->szValue ); + switch( type ) + { + case 0: + triggerType = USE_OFF; + break; + case 2: + triggerType = USE_TOGGLE; + break; + default: + triggerType = USE_ON; + break; + } + pkvd->fHandled = TRUE; + } + else + CRulePointEntity::KeyValue( pkvd ); +} + + +void CGameTeamMaster::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( useType == USE_SET ) + { + if ( value < 0 ) + { + m_teamIndex = -1; + } + else + { + m_teamIndex = g_pGameRules->GetTeamIndex( pActivator->TeamID() ); + } + return; + } + + if ( TeamMatch( pActivator ) ) + { + SUB_UseTargets( pActivator, triggerType, value ); + if ( RemoveOnFire() ) + UTIL_Remove( this ); + } +} + + +BOOL CGameTeamMaster::IsTriggered( CBaseEntity *pActivator ) +{ + return TeamMatch( pActivator ); +} + + +const char *CGameTeamMaster::TeamID( void ) +{ + if ( m_teamIndex < 0 ) // Currently set to "no team" + return ""; + + return g_pGameRules->GetIndexedTeamName( m_teamIndex ); // UNDONE: Fill this in with the team from the "teamlist" +} + + +BOOL CGameTeamMaster::TeamMatch( CBaseEntity *pActivator ) +{ + if ( m_teamIndex < 0 && AnyTeam() ) + return TRUE; + + if ( !pActivator ) + return FALSE; + + return UTIL_TeamsMatch( pActivator->TeamID(), TeamID() ); +} + + +// +// CGameTeamSet / game_team_set -- Changes the team of the entity it targets to the activator's team +// Flag: Fire once +// Flag: Clear team -- Sets the team to "NONE" instead of activator + +#define SF_TEAMSET_FIREONCE 0x0001 +#define SF_TEAMSET_CLEARTEAM 0x0002 + +class CGameTeamSet : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_TEAMSET_FIREONCE) ? TRUE : FALSE; } + inline BOOL ShouldClearTeam( void ) { return (pev->spawnflags & SF_TEAMSET_CLEARTEAM) ? TRUE : FALSE; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_team_set, CGameTeamSet ); + + +void CGameTeamSet::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( ShouldClearTeam() ) + { + SUB_UseTargets( pActivator, USE_SET, -1 ); + } + else + { + SUB_UseTargets( pActivator, USE_SET, 0 ); + } + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + +// +// CGamePlayerZone / game_player_zone -- players in the zone fire my target when I'm fired +// +// Needs master? +class CGamePlayerZone : public CRuleBrushEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + string_t m_iszInTarget; + string_t m_iszOutTarget; + string_t m_iszInCount; + string_t m_iszOutCount; +}; + +LINK_ENTITY_TO_CLASS( game_zone_player, CGamePlayerZone ); +TYPEDESCRIPTION CGamePlayerZone::m_SaveData[] = +{ + DEFINE_FIELD( CGamePlayerZone, m_iszInTarget, FIELD_STRING ), + DEFINE_FIELD( CGamePlayerZone, m_iszOutTarget, FIELD_STRING ), + DEFINE_FIELD( CGamePlayerZone, m_iszInCount, FIELD_STRING ), + DEFINE_FIELD( CGamePlayerZone, m_iszOutCount, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CGamePlayerZone, CRuleBrushEntity ); + +void CGamePlayerZone::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "intarget")) + { + m_iszInTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "outtarget")) + { + m_iszOutTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "incount")) + { + m_iszInCount = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "outcount")) + { + m_iszOutCount = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CRuleBrushEntity::KeyValue( pkvd ); +} + +void CGamePlayerZone::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int playersInCount = 0; + int playersOutCount = 0; + + if ( !CanFireForActivator( pActivator ) ) + return; + + CBaseEntity *pPlayer = NULL; + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + { + TraceResult trace; + int hullNumber; + + hullNumber = human_hull; + if ( pPlayer->pev->flags & FL_DUCKING ) + { + hullNumber = head_hull; + } + + UTIL_TraceModel( pPlayer->pev->origin, pPlayer->pev->origin, hullNumber, edict(), &trace ); + + if ( trace.fStartSolid ) + { + playersInCount++; + if ( m_iszInTarget ) + { + FireTargets( STRING(m_iszInTarget), pPlayer, pActivator, useType, value ); + } + } + else + { + playersOutCount++; + if ( m_iszOutTarget ) + { + FireTargets( STRING(m_iszOutTarget), pPlayer, pActivator, useType, value ); + } + } + } + } + + if ( m_iszInCount ) + { + FireTargets( STRING(m_iszInCount), pActivator, this, USE_SET, playersInCount ); + } + + if ( m_iszOutCount ) + { + FireTargets( STRING(m_iszOutCount), pActivator, this, USE_SET, playersOutCount ); + } +} + + + +// +// CGamePlayerHurt / game_player_hurt -- Damages the player who fires it +// Flag: Fire once + +#define SF_PKILL_FIREONCE 0x0001 +class CGamePlayerHurt : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_PKILL_FIREONCE) ? TRUE : FALSE; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_player_hurt, CGamePlayerHurt ); + + +void CGamePlayerHurt::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( pActivator->IsPlayer() ) + { + if ( pev->dmg < 0 ) + pActivator->TakeHealth( -pev->dmg, DMG_GENERIC ); + else + pActivator->TakeDamage( pev, pev, pev->dmg, DMG_GENERIC ); + } + + SUB_UseTargets( pActivator, useType, value ); + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + + +// +// CGameCounter / game_counter -- Counts events and fires target +// Flag: Fire once +// Flag: Reset on Fire + +#define SF_GAMECOUNT_FIREONCE 0x0001 +#define SF_GAMECOUNT_RESET 0x0002 + +class CGameCounter : public CRulePointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_GAMECOUNT_FIREONCE) ? TRUE : FALSE; } + inline BOOL ResetOnFire( void ) { return (pev->spawnflags & SF_GAMECOUNT_RESET) ? TRUE : FALSE; } + + inline void CountUp( void ) { pev->frags++; } + inline void CountDown( void ) { pev->frags--; } + inline void ResetCount( void ) { pev->frags = pev->dmg; } + inline int CountValue( void ) { return static_cast(pev->frags); } + inline int LimitValue( void ) { return static_cast(pev->health); } + + inline BOOL HitLimit( void ) { return CountValue() == LimitValue(); } + +private: + + inline void SetCountValue( int value ) { pev->frags = value; } + inline void SetInitialValue( int value ) { pev->dmg = value; } +}; + +LINK_ENTITY_TO_CLASS( game_counter, CGameCounter ); + +void CGameCounter::Spawn( void ) +{ + // Save off the initial count + SetInitialValue( CountValue() ); + CRulePointEntity::Spawn(); +} + + +void CGameCounter::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + switch( useType ) + { + case USE_ON: + case USE_TOGGLE: + CountUp(); + break; + + case USE_OFF: + CountDown(); + break; + + case USE_SET: + SetCountValue( (int)value ); + break; + } + + if ( HitLimit() ) + { + SUB_UseTargets( pActivator, USE_TOGGLE, 0 ); + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } + + if ( ResetOnFire() ) + { + ResetCount(); + } + } +} + + + +// +// CGameCounterSet / game_counter_set -- Sets the counter's value +// Flag: Fire once + +#define SF_GAMECOUNTSET_FIREONCE 0x0001 + +class CGameCounterSet : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_GAMECOUNTSET_FIREONCE) ? TRUE : FALSE; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_counter_set, CGameCounterSet ); + + +void CGameCounterSet::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + SUB_UseTargets( pActivator, USE_SET, pev->frags ); + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + +// +// CGamePlayerEquip / game_playerequip -- Sets the default player equipment +// Flag: USE Only + +#define SF_PLAYEREQUIP_USEONLY 0x0001 +#define MAX_EQUIP 32 + +class CGamePlayerEquip : public CRulePointEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Touch( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + inline BOOL UseOnly( void ) { return (pev->spawnflags & SF_PLAYEREQUIP_USEONLY) ? TRUE : FALSE; } + +private: + + void EquipPlayer( CBaseEntity *pPlayer ); + + string_t m_weaponNames[MAX_EQUIP]; + int m_weaponCount[MAX_EQUIP]; +}; + +LINK_ENTITY_TO_CLASS( game_player_equip, CGamePlayerEquip ); + + +void CGamePlayerEquip::KeyValue( KeyValueData *pkvd ) +{ + CRulePointEntity::KeyValue( pkvd ); + + if ( !pkvd->fHandled ) + { + for ( int i = 0; i < MAX_EQUIP; i++ ) + { + if ( !m_weaponNames[i] ) + { + char tmp[128]; + + UTIL_StripToken( pkvd->szKeyName, tmp ); + + m_weaponNames[i] = ALLOC_STRING(tmp); + m_weaponCount[i] = atoi(pkvd->szValue); + m_weaponCount[i] = max(1,m_weaponCount[i]); + pkvd->fHandled = TRUE; + break; + } + } + } +} + + +void CGamePlayerEquip::Touch( CBaseEntity *pOther ) +{ + if ( !CanFireForActivator( pOther ) ) + return; + + if ( UseOnly() ) + return; + + EquipPlayer( pOther ); +} + +void CGamePlayerEquip::EquipPlayer( CBaseEntity *pEntity ) +{ + CBasePlayer *pPlayer = NULL; + + if ( pEntity->IsPlayer() ) + { + pPlayer = (CBasePlayer *)pEntity; + } + + if ( !pPlayer ) + return; + + for ( int i = 0; i < MAX_EQUIP; i++ ) + { + if ( !m_weaponNames[i] ) + break; + for ( int j = 0; j < m_weaponCount[i]; j++ ) + { + pPlayer->GiveNamedItem( STRING(m_weaponNames[i]) ); + } + } +} + + +void CGamePlayerEquip::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + EquipPlayer( pActivator ); +} + + +// +// CGamePlayerTeam / game_player_team -- Changes the team of the player who fired it +// Flag: Fire once +// Flag: Kill Player +// Flag: Gib Player + +#define SF_PTEAM_FIREONCE 0x0001 +#define SF_PTEAM_KILL 0x0002 +#define SF_PTEAM_GIB 0x0004 + +class CGamePlayerTeam : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +private: + + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_PTEAM_FIREONCE) ? TRUE : FALSE; } + inline BOOL ShouldKillPlayer( void ) { return (pev->spawnflags & SF_PTEAM_KILL) ? TRUE : FALSE; } + inline BOOL ShouldGibPlayer( void ) { return (pev->spawnflags & SF_PTEAM_GIB) ? TRUE : FALSE; } + + const char *TargetTeamName( const char *pszTargetName ); +}; + +LINK_ENTITY_TO_CLASS( game_player_team, CGamePlayerTeam ); + + +const char *CGamePlayerTeam::TargetTeamName( const char *pszTargetName ) +{ + CBaseEntity *pTeamEntity = NULL; + + while ((pTeamEntity = UTIL_FindEntityByTargetname( pTeamEntity, pszTargetName )) != NULL) + { + if ( FClassnameIs( pTeamEntity->pev, "game_team_master" ) ) + return pTeamEntity->TeamID(); + } + + return NULL; +} + + +void CGamePlayerTeam::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( pActivator->IsPlayer() ) + { + const char *pszTargetTeam = TargetTeamName( STRING(pev->target) ); + if ( pszTargetTeam ) + { + CBasePlayer *pPlayer = (CBasePlayer *)pActivator; + g_pGameRules->ChangePlayerTeam( pPlayer, pszTargetTeam, ShouldKillPlayer(), ShouldGibPlayer() ); + } + } + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + diff --git a/hlsdk/dmc/dlls/maprules.h b/hlsdk/dmc/dlls/maprules.h new file mode 100644 index 00000000..37720944 --- /dev/null +++ b/hlsdk/dmc/dlls/maprules.h @@ -0,0 +1,22 @@ +/*** +* +* Copyright (c) 1996-2002 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef MAPRULES_H +#define MAPRULES_H + + + +#endif // MAPRULES_H + diff --git a/hlsdk/dmc/dlls/monsterevent.h b/hlsdk/dmc/dlls/monsterevent.h new file mode 100644 index 00000000..46c5624a --- /dev/null +++ b/hlsdk/dmc/dlls/monsterevent.h @@ -0,0 +1,34 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef MONSTEREVENT_H +#define MONSTEREVENT_H + +typedef struct +{ + int event; + char *options; +} MonsterEvent_t; + +#define EVENT_SPECIFIC 0 +#define EVENT_SCRIPTED 1000 +#define EVENT_SHARED 2000 +#define EVENT_CLIENT 5000 + +#define MONSTER_EVENT_BODYDROP_LIGHT 2001 +#define MONSTER_EVENT_BODYDROP_HEAVY 2002 + +#define MONSTER_EVENT_SWISHSOUND 2010 + +#endif // MONSTEREVENT_H diff --git a/hlsdk/dmc/dlls/monsters.cpp b/hlsdk/dmc/dlls/monsters.cpp new file mode 100644 index 00000000..f2ff0b49 --- /dev/null +++ b/hlsdk/dmc/dlls/monsters.cpp @@ -0,0 +1,134 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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" + +CBaseEntity* CBaseMonster :: CheckTraceHullAttack( float flDist, int iDamage, int iDmgType ) { return NULL; } +void CBaseMonster :: Eat ( float flFullDuration ) { } +BOOL CBaseMonster :: FShouldEat ( void ) { return TRUE; } +void CBaseMonster :: BarnacleVictimBitten ( entvars_t *pevBarnacle ) { } +void CBaseMonster :: BarnacleVictimReleased ( void ) { } +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; } +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; } +void CBaseMonster :: SetEyePosition ( void ) { } +void CBaseMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) { } +Vector CBaseMonster :: GetGunPosition( void ) { return g_vecZero; } +void CBaseEntity::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) { } +void CBaseEntity::FireBullets(ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq, int iDamage, entvars_t *pevAttacker ) { } +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) { } +CBaseEntity* CBaseMonster :: DropItem ( char *pszItemName, const Vector &vecPos, const Vector &vecAng ) { return NULL; } +BOOL CBaseMonster :: ShouldFadeOnDeath( void ) { return FALSE; } +void CBaseMonster::FadeMonster( void ) { } +BOOL CBaseMonster :: HasHumanGibs( void ) { return TRUE; } +BOOL CBaseMonster :: HasAlienGibs( void ) { return FALSE; } +MONSTERSTATE CBaseMonster :: GetIdealState ( void ) { return MONSTERSTATE_ALERT; } +Schedule_t* CBaseMonster :: GetScheduleOfType ( int Type ) { return NULL; } +Schedule_t *CBaseMonster :: GetSchedule ( void ) { return NULL; } +void CBaseMonster :: RunTask ( Task_t *pTask ) { } +void CBaseMonster :: StartTask ( Task_t *pTask ) { } +Schedule_t *CBaseMonster::ScheduleFromName( const char *pName ) { return NULL;} +void CBaseMonster::BecomeDead( void ) {} +void CBaseMonster :: RunAI ( void ) {} +void CBaseMonster :: Killed( entvars_t *pevAttacker, int iGib ) {} +int CBaseMonster :: TakeHealth (float flHealth, int bitsDamageType) { return 0; } +int CBaseMonster::Restore( class CRestore & ) { return 1; } +int CBaseMonster::Save( class CSave & ) { return 1; } + + diff --git a/hlsdk/dmc/dlls/monsters.h b/hlsdk/dmc/dlls/monsters.h new file mode 100644 index 00000000..f7f1a03f --- /dev/null +++ b/hlsdk/dmc/dlls/monsters.h @@ -0,0 +1,183 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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 + +/* + +===== monsters.h ======================================================== + + Header file for monster-related utility code + +*/ + +// 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 +#define HITGROUP_CHEST 2 +#define HITGROUP_STOMACH 3 +#define HITGROUP_LEFTARM 4 +#define HITGROUP_RIGHTARM 5 +#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 ); + +Vector VecCheckToss ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flGravityAdj = 1.0 ); +Vector VecCheckThrow ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flSpeed, float flGravityAdj = 1.0 ); +extern DLL_GLOBAL Vector g_vecAttackDir; +extern DLL_GLOBAL CONSTANT float g_flMeleeRange; +extern DLL_GLOBAL CONSTANT float g_flMediumRange; +extern DLL_GLOBAL CONSTANT float g_flLongRange; +extern void EjectBrass (const Vector &vecOrigin, const Vector &vecVelocity, float rotation, int model, int soundtype ); +extern void ExplodeModel( const Vector &vecOrigin, float speed, int model, int count ); + +BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget ); +BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget, Vector &vecTargetOrigin, float flSize = 0.0 ); + +// monster to monster relationship types +#define R_AL -2 // (ALLY) pals. Good alternative to R_NO when applicable. +#define R_FR -1// (FEAR)will run +#define R_NO 0// (NO RELATIONSHIP) disregard +#define R_DL 1// (DISLIKE) will attack +#define R_HT 2// (HATE)will attack this character instead of any visible DISLIKEd characters +#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. +// +class CGib : public CBaseEntity +{ +public: + void Spawn( const char *szGibModel ); + void EXPORT BounceGibTouch ( CBaseEntity *pOther ); + void EXPORT StickyGibTouch ( CBaseEntity *pOther ); + void EXPORT WaitTillLand( void ); + void LimitVelocity( void ); + + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DONT_SAVE; } + static void SpawnHeadGib( entvars_t *pevVictim ); + static void SpawnRandomGibs( entvars_t *pevVictim, int cGibs, int human ); + static void SpawnStickyGibs( entvars_t *pevVictim, Vector vecOrigin, int cGibs ); + + int m_bloodColor; + int m_cBloodDecals; + int m_material; + float m_lifeTime; +}; + + +#define CUSTOM_SCHEDULES\ + virtual Schedule_t *ScheduleFromName( const char *pName );\ + static Schedule_t *m_scheduleList[]; + +#define DEFINE_CUSTOM_SCHEDULES(derivedClass)\ + Schedule_t *derivedClass::m_scheduleList[] = + +#define IMPLEMENT_CUSTOM_SCHEDULES(derivedClass, baseClass)\ + Schedule_t *derivedClass::ScheduleFromName( const char *pName )\ + {\ + Schedule_t *pSchedule = ScheduleInList( pName, m_scheduleList, ARRAYSIZE(m_scheduleList) );\ + if ( !pSchedule )\ + return baseClass::ScheduleFromName(pName);\ + return pSchedule;\ + } + + + +#endif //MONSTERS_H diff --git a/hlsdk/dmc/dlls/monsterstate.cpp b/hlsdk/dmc/dlls/monsterstate.cpp new file mode 100644 index 00000000..ddc0e70d --- /dev/null +++ b/hlsdk/dmc/dlls/monsterstate.cpp @@ -0,0 +1,28 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// monsterstate.cpp - base class monster functions for +// controlling core AI. +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "nodes.h" +#include "monsters.h" +#include "animation.h" +#include "saverestore.h" + +void CBaseMonster :: SetState ( MONSTERSTATE State ) { }; diff --git a/hlsdk/dmc/dlls/msvc6/dmc.dsp b/hlsdk/dmc/dlls/msvc6/dmc.dsp new file mode 100644 index 00000000..4f31d52d --- /dev/null +++ b/hlsdk/dmc/dlls/msvc6/dmc.dsp @@ -0,0 +1,469 @@ +# Microsoft Developer Studio Project File - Name="dmc" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=dmc - 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 "dmc.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 "dmc.mak" CFG="dmc - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "dmc - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "dmc - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "dmc - 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 ".\Releasedmc" +# PROP Intermediate_Dir ".\Releasedmc" +# PROP Ignore_Export_Lib 0 +# 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 "..\..\..\engine" /I "..\..\..\common" /I "." /I "..\..\..\game_shared" /I "..\..\dlls" /I "..\..\pm_shared" /I "..\\" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "QUIVER" /D "VOXEL" /D "QUAKE2" /D "VALVE_DLL" /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:"..\dmc.def" +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "dmc - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\dmc___Win" +# PROP BASE Intermediate_Dir ".\dmc___Win" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir ".\Debugdmc" +# PROP Intermediate_Dir ".\Debugdmc" +# 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 "." /I "..\..\..\game_shared" /I "..\..\dlls" /I "..\..\..\engine" /I "..\..\..\common" /I "..\..\pm_shared" /I "..\..\" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "QUIVER" /D "VOXEL" /D "QUAKE2" /D "VALVE_DLL" /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:"..\dmc.def" +# SUBTRACT LINK32 /pdb:none + +!ENDIF + +# Begin Target + +# Name "dmc - Win32 Release" +# Name "dmc - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Group "Shared Weapons" + +# PROP Default_Filter "*.cpp" +# Begin Source File + +SOURCE=..\quake_gun.cpp +# End Source File +# Begin Source File + +SOURCE=..\quake_weapons_all.cpp +# End Source File +# End Group +# 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=..\game.cpp +# End Source File +# Begin Source File + +SOURCE=..\gamerules.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_export.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=..\monsterstate.cpp +# End Source File +# Begin Source File + +SOURCE=..\multiplay_gamerules.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=..\quake_items.cpp +# End Source File +# Begin Source File + +SOURCE=..\quake_nail.cpp +# End Source File +# Begin Source File + +SOURCE=..\quake_player.cpp +# End Source File +# Begin Source File + +SOURCE=..\quake_rocket.cpp +# End Source File +# Begin Source File + +SOURCE=..\schedule.cpp +# End Source File +# Begin Source File + +SOURCE=..\singleplay_gamerules.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=..\spectator.cpp +# End Source File +# Begin Source File + +SOURCE=..\subs.cpp +# End Source File +# Begin Source File + +SOURCE=..\teamplay_gamerules.cpp +# End Source File +# Begin Source File + +SOURCE=..\triggers.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=..\defaultai.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=..\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=..\gamerules.h +# End Source File +# Begin Source File + +SOURCE=..\items.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=..\quake_gun.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=..\scripted.h +# End Source File +# Begin Source File + +SOURCE=..\scriptevent.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=..\trains.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=..\..\..\game_shared\voice_gamemgr.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/hlsdk/dmc/dlls/msvc7/dmc.vcproj b/hlsdk/dmc/dlls/msvc7/dmc.vcproj new file mode 100644 index 00000000..16f8250d --- /dev/null +++ b/hlsdk/dmc/dlls/msvc7/dmc.vcproj @@ -0,0 +1,1310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hlsdk/dmc/dlls/msvc8/dmc.vcproj b/hlsdk/dmc/dlls/msvc8/dmc.vcproj new file mode 100644 index 00000000..92cedea2 --- /dev/null +++ b/hlsdk/dmc/dlls/msvc8/dmc.vcproj @@ -0,0 +1,1656 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hlsdk/dmc/dlls/multiplay_gamerules.cpp b/hlsdk/dmc/dlls/multiplay_gamerules.cpp new file mode 100644 index 00000000..a66dc6a9 --- /dev/null +++ b/hlsdk/dmc/dlls/multiplay_gamerules.cpp @@ -0,0 +1,1614 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// teamplay_gamerules.cpp +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "skill.h" +#include "game.h" +#include "items.h" +#include "hltv.h" + +#define INTERMISSION_TIME 60 + +class CDMCGameMgrHelper : public IVoiceGameMgrHelper +{ +public: + virtual bool CanPlayerHearPlayer(CBasePlayer *pPlayer1, CBasePlayer *pPlayer2) + { + return true; + } +}; +static CDMCGameMgrHelper g_GameMgrHelper; + +bool g_bHaveMOTD; + +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 gmsgServerName; + +#define ITEM_RESPAWN_TIME 30 +#define WEAPON_RESPAWN_TIME 20 +#define AMMO_RESPAWN_TIME 20 + +char *g_szDeathType; + +//********************************************************* +// Rules for the half-life multiplayer game. +//********************************************************* + +CHalfLifeMultiplay :: CHalfLifeMultiplay() +{ + m_VoiceGameMgr.Init(&g_GameMgrHelper, gpGlobals->maxClients); + int length; + + char * pFileList = (char*)LOAD_FILE_FOR_ME( "motd.txt", &length ); + + if ( pFileList ) + g_bHaveMOTD = true; + else + g_bHaveMOTD = false; + + RefreshSkillData(); + m_flIntermissionEndTime = 0; + m_flGameEndTime = 0.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 ); + } + } +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay::RefreshSkillData( void ) +{ +// load all default values + CGameRules::RefreshSkillData(); + +// override some values for multiplay. + + // suitcharger + gSkillData.suitchargerCapacity = 30; + + // Crowbar whack + gSkillData.plrDmgCrowbar = 25; + + // Glock Round + gSkillData.plrDmg9MM = 12; + + // 357 Round + gSkillData.plrDmg357 = 40; + + // MP5 Round + gSkillData.plrDmgMP5 = 12; + + // M203 grenade + gSkillData.plrDmgM203Grenade = 100; + + // Shotgun buckshot + gSkillData.plrDmgBuckshot = 20;// fewer pellets in deathmatch + + // Crossbow + gSkillData.plrDmgCrossbowClient = 20; + + // RPG + gSkillData.plrDmgRPG = 120; + + // Egon + gSkillData.plrDmgEgonWide = 20; + gSkillData.plrDmgEgonNarrow = 10; + + // Hand Grendade + gSkillData.plrDmgHandGrenade = 100; + + // Satchel Charge + gSkillData.plrDmgSatchel = 120; + + // Tripmine + gSkillData.plrDmgTripmine = 150; + + // hornet + gSkillData.plrDmgHornet = 10; +} + +// longest the intermission can last, in seconds +#define MAX_INTERMISSION_TIME 120 + +BOOL CHalfLifeMultiplay::ClientCommand( CBasePlayer *pPlayer, const char *pcmd ) +{ + if(m_VoiceGameMgr.ClientCommand(pPlayer, pcmd)) + return TRUE; + + return CGameRules::ClientCommand(pPlayer, pcmd); +} + +extern cvar_t mp_chattime; + +extern cvar_t timeleft, fragsleft; +//========================================================= +//========================================================= +void CHalfLifeMultiplay :: Think ( void ) +{ + m_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 < 1 ) + CVAR_SET_STRING( "mp_chattime", "1" ); + else if ( time > INTERMISSION_TIME ) + CVAR_SET_STRING( "mp_chattime", UTIL_dtos1( INTERMISSION_TIME ) ); + + m_flIntermissionEndTime = m_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 + || ( ( m_flIntermissionStartTime + INTERMISSION_TIME ) < gpGlobals->time) ) + ChangeLevel(); // intermission is over + } + + return; + } + + if ( m_flGameEndTime != 0.0 && m_flGameEndTime <= gpGlobals->time ) + { + GoToIntermission(); + m_flGameEndTime = 0.0; + return; + } + + float flTimeLimit = timelimit.value * 60; + float flFragLimit = fraglimit.value; + + time_remaining = (int)(flTimeLimit ? ( flTimeLimit - gpGlobals->time ) : 0); + + if ( flTimeLimit != 0 && gpGlobals->time >= flTimeLimit && m_flGameEndTime == 0.0 ) + { + GoToIntermission(); + return; + } + + if ( flFragLimit && m_flGameEndTime == 0.0 ) + { + 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 ) + { + m_flGameEndTime = gpGlobals->time + 1.5; + return; + } + + + if ( pPlayer ) + { + remain = static_cast(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; +} + + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsMultiplayer( void ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsDeathmatch( void ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsCoOp( void ) +{ + return static_cast(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; + } + + if ( pWeapon->iWeight() > pPlayer->m_pActiveItem->iWeight() ) + { + 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 ) + { + return FALSE; + } + + pPlayer->SwitchWeapon( pBest ); + + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay :: ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) +{ + m_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" ) ); + + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" entered the game\n", + STRING( pl->pev->netname ), + GETPLAYERUSERID( pl->edict() ), + GETPLAYERAUTHID( 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 ); + 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( static_cast(plr->pev->frags) ); + WRITE_SHORT( plr->m_iDeaths ); + WRITE_SHORT( plr->pev->team ); + 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 ); + + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" disconnected\n", + STRING( pPlayer->pev->netname ), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + GETPLAYERUSERID( pPlayer->edict() ) ); + + pPlayer->RemoveAllItems( TRUE );// destroy all of the players weapons and items + } + } +} + +//========================================================= +//========================================================= +float CHalfLifeMultiplay :: FlPlayerFallDamage( CBasePlayer *pPlayer ) +{ + int iFallDamage = (int)CVAR_GET_FLOAT("mp_falldamage"); + + switch ( iFallDamage ) + { + case 1://progressive + pPlayer->m_flFallVelocity -= PLAYER_MAX_SAFE_FALL_SPEED; + return pPlayer->m_flFallVelocity * DAMAGE_FOR_FALL_SPEED; + break; + default: + case 0:// fixed + return 5; + break; + } +} + +//========================================================= +//========================================================= +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; + + pPlayer->pev->weapons |= (1<Touch( pPlayer ); + addDefault = FALSE; + } + + if ( addDefault ) + { + // Start with init ammoload + pPlayer->m_iAmmoShells = 25; + + // Start with shotgun and axe + pPlayer->GiveNamedItem( "weapon_quakegun" ); + pPlayer->m_iQuakeItems |= (IT_SHOTGUN | IT_AXE); + pPlayer->m_iQuakeWeapon = pPlayer->W_BestWeapon(); + pPlayer->W_SetCurrentAmmo(); + } +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay :: FPlayerCanRespawn( CBasePlayer *pPlayer ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +float CHalfLifeMultiplay :: FlPlayerSpawnTime( CBasePlayer *pPlayer ) +{ + return gpGlobals->time;//now! +} + +BOOL CHalfLifeMultiplay :: AllowAutoTargetCrosshair( void ) +{ + return ( CVAR_GET_FLOAT( "mp_autocrosshair" ) != 0 ); +} + +//========================================================= +// IPointsForKill - how many points awarded to anyone +// that kills this player? +//========================================================= +int CHalfLifeMultiplay :: IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) +{ + return 1; +} + +void CHalfLifeMultiplay::ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ) +{ + pPlayer->m_iFOV = atoi( g_engfuncs.pfnInfoKeyValue( infobuffer, "cl_fov" ) ); + pPlayer->m_iAutoWepSwitch = atoi( g_engfuncs.pfnInfoKeyValue( infobuffer, "cl_autowepswitch" ) ); +} + +//========================================================= +// 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() ) + { + // if a player dies in a deathmatch game and the killer is a client, award the killer some points + pKiller->frags += IPointsForKill( peKiller, pVictim ); + + FireTargets( "game_playerkill", ktmp, ktmp, USE_TOGGLE, 0 ); + } + else + { // killed by the world + pVictim->pev->frags -= 1; + } + + // update the scores + // killed scores + MESSAGE_BEGIN( MSG_ALL, gmsgScoreInfo ); + WRITE_BYTE( ENTINDEX(pVictim->edict()) ); + WRITE_SHORT( static_cast(pVictim->pev->frags) ); + WRITE_SHORT( pVictim->m_iDeaths ); + WRITE_SHORT( pVictim->pev->team ); + 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( static_cast(PK->pev->frags) ); + WRITE_SHORT( PK->m_iDeaths ); + WRITE_SHORT( PK->pev->team ); + 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::Instance( pKiller ); + + const char *killer_weapon_name = "world"; // by default, the player is killed by the world + int killer_index = 0; + + // Hack to fix name change + char *tau = "tau_cannon"; + char *gluon = "gluon gun"; + + // QUAKECLASSIC + // Might have overridden + if (g_szDeathType) + { + killer_weapon_name = g_szDeathType; + } + else + { + 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 ); + + switch( pPlayer->m_iQuakeWeapon ) + { + case IT_AXE: killer_weapon_name = "weapon_axe"; break; + case IT_SHOTGUN: killer_weapon_name = "weapon_shotgun"; break; + case IT_SUPER_SHOTGUN: killer_weapon_name = "weapon_doubleshotgun"; break; + case IT_NAILGUN: killer_weapon_name = "weapon_nailgun"; break; + case IT_SUPER_NAILGUN: killer_weapon_name = "weapon_supernail"; break; + case IT_GRENADE_LAUNCHER: killer_weapon_name = "weapon_grenadel"; break; + case IT_ROCKET_LAUNCHER: killer_weapon_name = "weapon_rocketl"; break; + case IT_LIGHTNING: killer_weapon_name = "weapon_lightning"; break; + + default: + killer_weapon_name = "Empty"; + } + + } + 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; + } + + MESSAGE_BEGIN( MSG_ALL, gmsgDeathMsg ); + WRITE_BYTE( killer_index ); // the killer + WRITE_BYTE( ENTINDEX(pVictim->edict()) ); // the victim + WRITE_STRING( killer_weapon_name ); // what they were killed by (should this be a string?) + MESSAGE_END(); + + // replace the code names with the 'real' names + if ( !strcmp( killer_weapon_name, "egon" ) ) + killer_weapon_name = gluon; + else if ( !strcmp( killer_weapon_name, "gauss" ) ) + killer_weapon_name = tau; + + if ( pVictim->pev == pKiller ) + { + // killed self + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" committed suicide with \"%s\"\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); + } + else if ( pKiller->flags & FL_CLIENT ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" killed \"%s<%i><%s><%i>\" with \"%s\"\n", + STRING( pKiller->netname ), + GETPLAYERUSERID( ENT(pKiller) ), + GETPLAYERAUTHID( ENT(pKiller) ), + GETPLAYERUSERID( ENT(pKiller) ), + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); + } + else + { + // killed by the world + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" committed suicide with \"%s\" (world)\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); + } + + g_szDeathType = NULL; + + // HLTV event msg + MESSAGE_BEGIN( MSG_SPEC, SVC_DIRECTOR ); + WRITE_BYTE ( 9 ); // command length in bytes + WRITE_BYTE ( DRC_CMD_EVENT ); // player killed + WRITE_SHORT( ENTINDEX(pVictim->edict()) ); // index number of primary entity + 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); // eventflags (priority and flags) + MESSAGE_END(); + +// Print a standard message + // TODO: make this go direct to console + return; // just remove for now + +} + +//========================================================= +// 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 ( CVAR_GET_FLOAT("mp_weaponstay") > 0 ) + { + // make sure it's only certain weapons + if ( !(pWeapon->iFlags() & ITEM_FLAG_LIMITINWORLD) ) + { + return gpGlobals->time + 0; // weapon respawns almost instantly + } + } + + 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 ( CVAR_GET_FLOAT("mp_weaponstay") > 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; + + 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_ACTIVE; +} + +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 ) +{ + return FALSE; + + 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 CVAR_GET_FLOAT( "mp_flashlight" ) != 0; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay :: FAllowMonsters( void ) +{ + return ( CVAR_GET_FLOAT( "mp_allowmonsters" ) != 0 ); +} + +//========================================================= +//======== CHalfLifeMultiplay private functions =========== + +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 < 1 ) + CVAR_SET_STRING( "mp_chattime", "1" ); + else if ( time > INTERMISSION_TIME ) + CVAR_SET_STRING( "mp_chattime", UTIL_dtos1( INTERMISSION_TIME ) ); + + m_flIntermissionEndTime = gpGlobals->time + ( (int)mp_chattime.value ); + m_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 (MAX_MOTD_CHUNK * 4) + +void CHalfLifeMultiplay :: SendMOTDToClient( edict_t *client ) +{ + // read from the MOTD.txt file + int length, char_count = 0; + char *pFileList; + char *aFileList = pFileList = (char*)LOAD_FILE_FOR_ME( "motd.txt", &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]; + + 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(); + } + + FREE_FILE( aFileList ); +} + + diff --git a/hlsdk/dmc/dlls/nodes.cpp b/hlsdk/dmc/dlls/nodes.cpp new file mode 100644 index 00000000..850d20a1 --- /dev/null +++ b/hlsdk/dmc/dlls/nodes.cpp @@ -0,0 +1,3648 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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 +#include + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "nodes.h" +#include "animation.h" +#include "doors.h" + +#ifndef _WIN32 +#include +#define CreateDirectory(p, n) mkdir(p, 0777) +#endif // _WIN32 + +#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 ); + +//========================================================= +// 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 = 0; + + 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. + // + int i = 0; + for ( 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(static_cast(vecOrigin.x), static_cast(m_RegionMin[0]), static_cast(m_RegionMax[0])), m_pNodes[iNode].m_Region[0]); + UpdateRange(m_minY, m_maxY, CALC_RANGE(static_cast(vecOrigin.y), static_cast(m_RegionMin[1]), static_cast(m_RegionMax[1])), m_pNodes[iNode].m_Region[1]); + UpdateRange(m_minZ, m_maxZ, CALC_RANGE(static_cast(vecOrigin.z), static_cast(m_RegionMin[2]), static_cast(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(static_cast(vecOrigin.x - flDist), static_cast(m_RegionMin[0]), static_cast(m_RegionMax[0])); + m_maxBoxX = CALC_RANGE(static_cast(vecOrigin.x + flDist), static_cast(m_RegionMin[0]), static_cast(m_RegionMax[0])); + m_minBoxY = CALC_RANGE(static_cast(vecOrigin.y - flDist), static_cast(m_RegionMin[1]), static_cast(m_RegionMax[1])); + m_maxBoxY = CALC_RANGE(static_cast(vecOrigin.y + flDist), static_cast(m_RegionMin[1]), static_cast(m_RegionMax[1])); + m_minBoxZ = CALC_RANGE(static_cast(vecOrigin.z - flDist), static_cast(m_RegionMin[2]), static_cast(m_RegionMax[2])); + m_maxBoxZ = CALC_RANGE(static_cast(vecOrigin.z + flDist), static_cast(m_RegionMin[2]), static_cast(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"); + } + + 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 ( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time; + } + else + { + SetThink ( &CTestHull::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 ( &CTestHull::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 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 ( &CBaseEntity::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 ( &CTestHull::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", static_cast(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", static_cast(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", static_cast(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 (int 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 (int 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, i = 0; + m_pNodes[0].m_iPreviousNode = iNodeCnt++; + for (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) +{ + int i = 0; + 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 (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. + // + // + int i = 0; + for (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(static_cast(m_pNodes[i].m_vecOrigin.x), static_cast(m_RegionMin[0]), static_cast(m_RegionMax[0])); + m_pNodes[i].m_Region[1] = CALC_RANGE(static_cast(m_pNodes[i].m_vecOrigin.y), static_cast(m_RegionMin[1]), static_cast(m_RegionMax[1])); + m_pNodes[i].m_Region[2] = CALC_RANGE(static_cast(m_pNodes[i].m_vecOrigin.z), static_cast(m_RegionMin[2]), static_cast(m_RegionMax[2])); + } + + for (i = 0; i < 3; i++) + { + int j = 0; + for (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 = 0; + 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 = 0; + 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 = 0; + 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. + // + int iFrom = 0; + for (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) + { + int i = 0; + for (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 = 0; + 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; + int i = 0; + for (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 (int 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( &CNodeViewer::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( static_cast(m_vecColor.x) ); // r, g, b + WRITE_BYTE( static_cast(m_vecColor.y) ); // r, g, b + WRITE_BYTE( static_cast(m_vecColor.z) ); // r, g, b + WRITE_BYTE( 128 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + m_iDraw++; + } +} + + diff --git a/hlsdk/dmc/dlls/nodes.h b/hlsdk/dmc/dlls/nodes.h new file mode 100644 index 00000000..269fe420 --- /dev/null +++ b/hlsdk/dmc/dlls/nodes.h @@ -0,0 +1,374 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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 +//========================================================= + +//========================================================= +// DEFINE +//========================================================= +#define MAX_STACK_NODES 100 +#define NO_NODE -1 +#define MAX_NODE_HULLS 4 + +#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? + + 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 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); + +}; + +//========================================================= +// 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, + + HINT_TACTICAL_EXIT = 100, + HINT_TACTICAL_VANTAGE, + HINT_TACTICAL_AMBUSH, + + HINT_STUKA_PERCH = 300, + HINT_STUKA_LANDING, +}; + +extern CGraph WorldGraph; diff --git a/hlsdk/dmc/dlls/observer.cpp b/hlsdk/dmc/dlls/observer.cpp new file mode 100644 index 00000000..b14f7f56 --- /dev/null +++ b/hlsdk/dmc/dlls/observer.cpp @@ -0,0 +1,142 @@ +//=========== (C) Copyright 1996-2002, 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: Functionality for the observer chase camera +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "pm_shared.h" + +// Find the next client in the game for this player to spectate +void CBasePlayer::Observer_FindNextPlayer() +{ + // 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. + + CBaseEntity *client = m_hObserverTarget; + while ( (client = (CBaseEntity*)UTIL_FindEntityByClassname( client, "player" )) != m_hObserverTarget ) + { + if ( !client ) + continue; + if ( !client->pev ) + continue; + if ( client == this ) + continue; + + // Add checks on target here. + + m_hObserverTarget = client; + break; + } + + // Did we find a target? + if ( m_hObserverTarget ) + { + // Store the target in pev so the physics DLL can get to it + if (pev->iuser1 != OBS_ROAMING) + 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->classname ) ); + } + 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_CHASE_LOCKED ) + Observer_SetMode( OBS_CHASE_FREE ); + + else if ( pev->iuser1 == OBS_CHASE_FREE ) + Observer_SetMode( OBS_ROAMING ); + + else if ( pev->iuser1 == OBS_ROAMING ) + Observer_SetMode( OBS_IN_EYE ); + + else if ( pev->iuser1 == OBS_IN_EYE ) + Observer_SetMode( OBS_MAP_FREE ); + + else if ( pev->iuser1 == OBS_MAP_FREE ) + Observer_SetMode( OBS_MAP_CHASE ); + + else + Observer_SetMode( OBS_CHASE_FREE ); // don't use OBS_CHASE_LOCKED anymore + + m_flNextObserverInput = gpGlobals->time + 0.2; + } + + // Attack moves to the next player + if ( m_afButtonPressed & IN_ATTACK ) + { + Observer_FindNextPlayer(); + + 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; + + // is valid mode ? + if ( iMode < OBS_CHASE_LOCKED || iMode > OBS_MAP_CHASE ) + iMode = OBS_IN_EYE; // now it is + + // if we are not roaming, we need a valid target to track + if ( (iMode != OBS_ROAMING) && (m_hObserverTarget == 0) ) + { + Observer_FindNextPlayer(); + + // if we didn't find a valid target switch to roaming + if (m_hObserverTarget == 0) + { + ClientPrint( pev, HUD_PRINTCENTER, "#Spec_NoTarget" ); + iMode = OBS_ROAMING; + } + } + + // set spectator mode + pev->iuser1 = iMode; + + // set target if not roaming + if (iMode == OBS_ROAMING) + pev->iuser2 = 0; + else + pev->iuser2 = ENTINDEX( m_hObserverTarget->edict() ); + + // print spepctaor mode on client screen + + char modemsg[16]; + sprintf(modemsg,"#Spec_Mode%i", iMode); + ClientPrint( pev, HUD_PRINTCENTER, modemsg ); +} diff --git a/hlsdk/dmc/dlls/pathcorner.cpp b/hlsdk/dmc/dlls/pathcorner.cpp new file mode 100644 index 00000000..b2c1e3f5 --- /dev/null +++ b/hlsdk/dmc/dlls/pathcorner.cpp @@ -0,0 +1,428 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// ========================== PATH_CORNER =========================== +// + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "trains.h" +#include "saverestore.h" + +class CPathCorner : public CPointEntity +{ +public: + void Spawn( ); + void KeyValue( KeyValueData* pkvd ); + float GetDelay( void ) { return m_flWait; } +// void Touch( CBaseEntity *pOther ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + float m_flWait; +}; + +LINK_ENTITY_TO_CLASS( path_corner, CPathCorner ); + +// Global Savedata for Delay +TYPEDESCRIPTION CPathCorner::m_SaveData[] = +{ + DEFINE_FIELD( CPathCorner, m_flWait, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CPathCorner, CPointEntity ); + +// +// Cache user-entity-field values until spawn is called. +// +void CPathCorner :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CPathCorner :: Spawn( ) +{ + ASSERTSZ(!FStringNull(pev->targetname), "path_corner without a targetname"); +} + +#if 0 +void CPathCorner :: Touch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + + if ( FBitSet ( pevToucher->flags, FL_MONSTER ) ) + {// monsters don't navigate path corners based on touch anymore + return; + } + + // If OTHER isn't explicitly looking for this path_corner, bail out + if ( pOther->m_pGoalEnt != this ) + { + return; + } + + // If OTHER has an enemy, this touch is incidental, ignore + if ( !FNullEnt(pevToucher->enemy) ) + { + return; // fighting, not following a path + } + + // UNDONE: support non-zero flWait + /* + if (m_flWait != 0) + ALERT(at_warning, "Non-zero path-cornder waits NYI"); + */ + + // Find the next "stop" on the path, make it the goal of the "toucher". + if (FStringNull(pev->target)) + { + ALERT(at_warning, "PathCornerTouch: no next stop specified"); + } + + pOther->m_pGoalEnt = CBaseEntity::Instance( FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(pev->target) ) ); + + // If "next spot" was not found (does not exist - level design error) + if ( !pOther->m_pGoalEnt ) + { + ALERT(at_console, "PathCornerTouch--%s couldn't find next stop in path: %s", STRING(pev->classname), STRING(pev->target)); + return; + } + + // Turn towards the next stop in the path. + pevToucher->ideal_yaw = UTIL_VecToYaw ( pOther->m_pGoalEnt->pev->origin - pevToucher->origin ); +} +#endif + + + +TYPEDESCRIPTION CPathTrack::m_SaveData[] = +{ + DEFINE_FIELD( CPathTrack, m_length, FIELD_FLOAT ), + DEFINE_FIELD( CPathTrack, m_pnext, FIELD_CLASSPTR ), + DEFINE_FIELD( CPathTrack, m_paltpath, FIELD_CLASSPTR ), + DEFINE_FIELD( CPathTrack, m_pprevious, FIELD_CLASSPTR ), + DEFINE_FIELD( CPathTrack, m_altName, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CPathTrack, CBaseEntity ); +LINK_ENTITY_TO_CLASS( path_track, CPathTrack ); + +// +// Cache user-entity-field values until spawn is called. +// +void CPathTrack :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "altpath")) + { + m_altName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CPathTrack :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int on; + + // Use toggles between two paths + if ( m_paltpath ) + { + on = !FBitSet( pev->spawnflags, SF_PATH_ALTERNATE ); + if ( ShouldToggle( useType, on ) ) + { + if ( on ) + SetBits( pev->spawnflags, SF_PATH_ALTERNATE ); + else + ClearBits( pev->spawnflags, SF_PATH_ALTERNATE ); + } + } + else // Use toggles between enabled/disabled + { + on = !FBitSet( pev->spawnflags, SF_PATH_DISABLED ); + + if ( ShouldToggle( useType, on ) ) + { + if ( on ) + SetBits( pev->spawnflags, SF_PATH_DISABLED ); + else + ClearBits( pev->spawnflags, SF_PATH_DISABLED ); + } + } +} + + +void CPathTrack :: Link( void ) +{ + edict_t *pentTarget; + + if ( !FStringNull(pev->target) ) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) ); + if ( !FNullEnt(pentTarget) ) + { + m_pnext = CPathTrack::Instance( pentTarget ); + + if ( m_pnext ) // If no next pointer, this is the end of a path + { + m_pnext->SetPrevious( this ); + } + } + else + ALERT( at_console, "Dead end link %s\n", STRING(pev->target) ); + } + + // Find "alternate" path + if ( m_altName ) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_altName) ); + if ( !FNullEnt(pentTarget) ) + { + m_paltpath = CPathTrack::Instance( pentTarget ); + + if ( m_paltpath ) // If no next pointer, this is the end of a path + { + m_paltpath->SetPrevious( this ); + } + } + } +} + + +void CPathTrack :: Spawn( void ) +{ + pev->solid = SOLID_TRIGGER; + UTIL_SetSize(pev, Vector(-8, -8, -8), Vector(8, 8, 8)); + + m_pnext = NULL; + m_pprevious = NULL; +// DEBUGGING CODE +#if PATH_SPARKLE_DEBUG + SetThink( Sparkle ); + pev->nextthink = gpGlobals->time + 0.5; +#endif +} + + +void CPathTrack::Activate( void ) +{ + if ( !FStringNull( pev->targetname ) ) // Link to next, and back-link + Link(); +} + +CPathTrack *CPathTrack :: ValidPath( CPathTrack *ppath, int testFlag ) +{ + if ( !ppath ) + return NULL; + + if ( testFlag && FBitSet( ppath->pev->spawnflags, SF_PATH_DISABLED ) ) + return NULL; + + return ppath; +} + + +void CPathTrack :: Project( CPathTrack *pstart, CPathTrack *pend, Vector *origin, float dist ) +{ + if ( pstart && pend ) + { + Vector dir = (pend->pev->origin - pstart->pev->origin); + dir = dir.Normalize(); + *origin = pend->pev->origin + dir * dist; + } +} + +CPathTrack *CPathTrack::GetNext( void ) +{ + if ( m_paltpath && FBitSet( pev->spawnflags, SF_PATH_ALTERNATE ) && !FBitSet( pev->spawnflags, SF_PATH_ALTREVERSE ) ) + return m_paltpath; + + return m_pnext; +} + + + +CPathTrack *CPathTrack::GetPrevious( void ) +{ + if ( m_paltpath && FBitSet( pev->spawnflags, SF_PATH_ALTERNATE ) && FBitSet( pev->spawnflags, SF_PATH_ALTREVERSE ) ) + return m_paltpath; + + return m_pprevious; +} + + + +void CPathTrack::SetPrevious( CPathTrack *pprev ) +{ + // Only set previous if this isn't my alternate path + if ( pprev && !FStrEq( STRING(pprev->pev->targetname), STRING(m_altName) ) ) + m_pprevious = pprev; +} + + +// Assumes this is ALWAYS enabled +CPathTrack *CPathTrack :: LookAhead( Vector *origin, float dist, int move ) +{ + CPathTrack *pcurrent; + float originalDist = dist; + + pcurrent = this; + Vector currentPos = *origin; + + if ( dist < 0 ) // Travelling backwards through path + { + dist = -dist; + while ( dist > 0 ) + { + Vector dir = pcurrent->pev->origin - currentPos; + float length = dir.Length(); + if ( !length ) + { + if ( !ValidPath(pcurrent->GetPrevious(), move) ) // If there is no previous node, or it's disabled, return now. + { + if ( !move ) + Project( pcurrent->GetNext(), pcurrent, origin, dist ); + return NULL; + } + pcurrent = pcurrent->GetPrevious(); + } + else if ( length > dist ) // enough left in this path to move + { + *origin = currentPos + (dir * (dist / length)); + return pcurrent; + } + else + { + dist -= length; + currentPos = pcurrent->pev->origin; + *origin = currentPos; + if ( !ValidPath(pcurrent->GetPrevious(), move) ) // If there is no previous node, or it's disabled, return now. + return NULL; + + pcurrent = pcurrent->GetPrevious(); + } + } + *origin = currentPos; + return pcurrent; + } + else + { + while ( dist > 0 ) + { + if ( !ValidPath(pcurrent->GetNext(), move) ) // If there is no next node, or it's disabled, return now. + { + if ( !move ) + Project( pcurrent->GetPrevious(), pcurrent, origin, dist ); + return NULL; + } + Vector dir = pcurrent->GetNext()->pev->origin - currentPos; + float length = dir.Length(); + if ( !length && !ValidPath( pcurrent->GetNext()->GetNext(), move ) ) + { + if ( dist == originalDist ) // HACK -- up against a dead end + return NULL; + return pcurrent; + } + if ( length > dist ) // enough left in this path to move + { + *origin = currentPos + (dir * (dist / length)); + return pcurrent; + } + else + { + dist -= length; + currentPos = pcurrent->GetNext()->pev->origin; + pcurrent = pcurrent->GetNext(); + *origin = currentPos; + } + } + *origin = currentPos; + } + + return pcurrent; +} + + +// Assumes this is ALWAYS enabled +CPathTrack *CPathTrack :: Nearest( Vector origin ) +{ + int deadCount; + float minDist, dist; + Vector delta; + CPathTrack *ppath, *pnearest; + + + delta = origin - pev->origin; + delta.z = 0; + minDist = delta.Length(); + pnearest = this; + ppath = GetNext(); + + // Hey, I could use the old 2 racing pointers solution to this, but I'm lazy :) + deadCount = 0; + while ( ppath && ppath != this ) + { + deadCount++; + if ( deadCount > 9999 ) + { + ALERT( at_error, "Bad sequence of path_tracks from %s", STRING(pev->targetname) ); + return NULL; + } + delta = origin - ppath->pev->origin; + delta.z = 0; + dist = delta.Length(); + if ( dist < minDist ) + { + minDist = dist; + pnearest = ppath; + } + ppath = ppath->GetNext(); + } + return pnearest; +} + + +CPathTrack *CPathTrack::Instance( edict_t *pent ) +{ + if ( FClassnameIs( pent, "path_track" ) ) + return (CPathTrack *)GET_PRIVATE(pent); + return NULL; +} + + + // DEBUGGING CODE +#if PATH_SPARKLE_DEBUG +void CPathTrack :: Sparkle( void ) +{ + + pev->nextthink = gpGlobals->time + 0.2; + if ( FBitSet( pev->spawnflags, SF_PATH_DISABLED ) ) + UTIL_ParticleEffect(pev->origin, Vector(0,0,100), 210, 10); + else + UTIL_ParticleEffect(pev->origin, Vector(0,0,100), 84, 10); +} +#endif + diff --git a/hlsdk/dmc/dlls/plane.cpp b/hlsdk/dmc/dlls/plane.cpp new file mode 100644 index 00000000..39a91c32 --- /dev/null +++ b/hlsdk/dmc/dlls/plane.cpp @@ -0,0 +1,60 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "plane.h" + +//========================================================= +// Plane +//========================================================= +CPlane :: CPlane ( void ) +{ + m_fInitialized = FALSE; +} + +//========================================================= +// InitializePlane - Takes a normal for the plane and a +// point on the plane and +//========================================================= +void CPlane :: InitializePlane ( const Vector &vecNormal, const Vector &vecPoint ) +{ + m_vecNormal = vecNormal; + m_flDist = DotProduct ( m_vecNormal, vecPoint ); + m_fInitialized = TRUE; +} + + +//========================================================= +// PointInFront - determines whether the given vector is +// in front of the plane. +//========================================================= +BOOL CPlane :: PointInFront ( const Vector &vecPoint ) +{ + float flFace; + + if ( !m_fInitialized ) + { + return FALSE; + } + + flFace = DotProduct ( m_vecNormal, vecPoint ) - m_flDist; + + if ( flFace >= 0 ) + { + return TRUE; + } + + return FALSE; +} + diff --git a/hlsdk/dmc/dlls/plane.h b/hlsdk/dmc/dlls/plane.h new file mode 100644 index 00000000..af70f1cc --- /dev/null +++ b/hlsdk/dmc/dlls/plane.h @@ -0,0 +1,43 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PLANE_H +#define PLANE_H + +//========================================================= +// Plane +//========================================================= +class CPlane +{ +public: + CPlane ( void ); + + //========================================================= + // InitializePlane - Takes a normal for the plane and a + // point on the plane and + //========================================================= + void InitializePlane ( const Vector &vecNormal, const Vector &vecPoint ); + + //========================================================= + // PointInFront - determines whether the given vector is + // in front of the plane. + //========================================================= + BOOL PointInFront ( const Vector &vecPoint ); + + Vector m_vecNormal; + float m_flDist; + BOOL m_fInitialized; +}; + +#endif // PLANE_H diff --git a/hlsdk/dmc/dlls/plats.cpp b/hlsdk/dmc/dlls/plats.cpp new file mode 100644 index 00000000..5b727f79 --- /dev/null +++ b/hlsdk/dmc/dlls/plats.cpp @@ -0,0 +1,2285 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== plats.cpp ======================================================== + + spawn, think, and touch functions for trains, etc + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "trains.h" +#include "saverestore.h" + +static void PlatSpawnInsideTrigger(entvars_t* pevPlatform); + +#define SF_PLAT_TOGGLE 0x0001 + +class CBasePlatTrain : public CBaseToggle +{ +public: + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + void KeyValue( KeyValueData* pkvd); + void Precache( void ); + + // This is done to fix spawn flag collisions between this class and a derived class + virtual BOOL IsTogglePlat( void ) { return (pev->spawnflags & SF_PLAT_TOGGLE) ? TRUE : FALSE; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + BYTE m_bMoveSnd; // sound a plat makes while moving + BYTE m_bStopSnd; // sound a plat makes when it stops + float m_volume; // Sound volume +}; + +TYPEDESCRIPTION CBasePlatTrain::m_SaveData[] = +{ + DEFINE_FIELD( CBasePlatTrain, m_bMoveSnd, FIELD_CHARACTER ), + DEFINE_FIELD( CBasePlatTrain, m_bStopSnd, FIELD_CHARACTER ), + DEFINE_FIELD( CBasePlatTrain, m_volume, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CBasePlatTrain, CBaseToggle ); + +void CBasePlatTrain :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "lip")) + { + m_flLip = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "height")) + { + m_flHeight = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "rotation")) + { + m_vecFinalAngle.x = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "movesnd")) + { + m_bMoveSnd = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "stopsnd")) + { + m_bStopSnd = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "volume")) + { + m_volume = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +#define noiseMoving noise +#define noiseArrived noise1 + +void CBasePlatTrain::Precache( void ) +{ +// set the plat's "in-motion" sound + switch (m_bMoveSnd) + { + case 0: + pev->noiseMoving = MAKE_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("plats/bigmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/bigmove1.wav"); + break; + case 2: + PRECACHE_SOUND ("plats/bigmove2.wav"); + pev->noiseMoving = MAKE_STRING("plats/bigmove2.wav"); + break; + case 3: + PRECACHE_SOUND ("plats/elevmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/elevmove1.wav"); + break; + case 4: + PRECACHE_SOUND ("plats/elevmove2.wav"); + pev->noiseMoving = MAKE_STRING("plats/elevmove2.wav"); + break; + case 5: + PRECACHE_SOUND ("plats/elevmove3.wav"); + pev->noiseMoving = MAKE_STRING("plats/elevmove3.wav"); + break; + case 6: + PRECACHE_SOUND ("plats/freightmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/freightmove1.wav"); + break; + case 7: + PRECACHE_SOUND ("plats/freightmove2.wav"); + pev->noiseMoving = MAKE_STRING("plats/freightmove2.wav"); + break; + case 8: + PRECACHE_SOUND ("plats/heavymove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/heavymove1.wav"); + break; + case 9: + PRECACHE_SOUND ("plats/rackmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/rackmove1.wav"); + break; + case 10: + PRECACHE_SOUND ("plats/railmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/railmove1.wav"); + break; + case 11: + PRECACHE_SOUND ("plats/squeekmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/squeekmove1.wav"); + break; + case 12: + PRECACHE_SOUND ("plats/talkmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/talkmove1.wav"); + break; + case 13: + PRECACHE_SOUND ("plats/talkmove2.wav"); + pev->noiseMoving = MAKE_STRING("plats/talkmove2.wav"); + break; + default: + pev->noiseMoving = MAKE_STRING("common/null.wav"); + break; + } + +// set the plat's 'reached destination' stop sound + switch (m_bStopSnd) + { + case 0: + pev->noiseArrived = MAKE_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("plats/bigstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/bigstop1.wav"); + break; + case 2: + PRECACHE_SOUND ("plats/bigstop2.wav"); + pev->noiseArrived = MAKE_STRING("plats/bigstop2.wav"); + break; + case 3: + PRECACHE_SOUND ("plats/freightstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/freightstop1.wav"); + break; + case 4: + PRECACHE_SOUND ("plats/heavystop2.wav"); + pev->noiseArrived = MAKE_STRING("plats/heavystop2.wav"); + break; + case 5: + PRECACHE_SOUND ("plats/rackstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/rackstop1.wav"); + break; + case 6: + PRECACHE_SOUND ("plats/railstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/railstop1.wav"); + break; + case 7: + PRECACHE_SOUND ("plats/squeekstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/squeekstop1.wav"); + break; + case 8: + PRECACHE_SOUND ("plats/talkstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/talkstop1.wav"); + break; + + default: + pev->noiseArrived = MAKE_STRING("common/null.wav"); + break; + } +} + +// +//====================== PLAT code ==================================================== +// + + +#define noiseMovement noise +#define noiseStopMoving noise1 + +class CFuncPlat : public CBasePlatTrain +{ +public: + void Spawn( void ); + void Precache( void ); + void Setup( void ); + + virtual void Blocked( CBaseEntity *pOther ); + + + void EXPORT PlatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + void EXPORT CallGoDown( void ) { GoDown(); } + void EXPORT CallHitTop( void ) { HitTop(); } + void EXPORT CallHitBottom( void ) { HitBottom(); } + + virtual void GoUp( void ); + virtual void GoDown( void ); + virtual void HitTop( void ); + virtual void HitBottom( void ); +}; +LINK_ENTITY_TO_CLASS( func_plat, CFuncPlat ); + + +// UNDONE: Need to save this!!! It needs class & linkage +class CPlatTrigger : public CBaseEntity +{ +public: + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DONT_SAVE; } + void SpawnInsideTrigger( CFuncPlat *pPlatform ); + void Touch( CBaseEntity *pOther ); + CFuncPlat *m_pPlatform; +}; + + + +/*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER +speed default 150 + +Plats are always drawn in the extended position, so they will light correctly. + +If the plat is the target of another trigger or button, it will start out disabled in +the extended position until it is trigger, when it will lower and become a normal plat. + +If the "height" key is set, that will determine the amount the plat moves, instead of +being implicitly determined by the model's height. + +Set "sounds" to one of the following: +1) base fast +2) chain slow +*/ + +void CFuncPlat :: Setup( void ) +{ + //pev->noiseMovement = MAKE_STRING("plats/platmove1.wav"); + //pev->noiseStopMoving = MAKE_STRING("plats/platstop1.wav"); + + if (m_flTLength == 0) + m_flTLength = 80; + if (m_flTWidth == 0) + m_flTWidth = 10; + + pev->angles = g_vecZero; + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); // set size and link into world + UTIL_SetSize(pev, pev->mins, pev->maxs); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + // vecPosition1 is the top position, vecPosition2 is the bottom + m_vecPosition1 = pev->origin; + m_vecPosition2 = pev->origin; + if (m_flHeight != 0) + m_vecPosition2.z = pev->origin.z - m_flHeight; + else + m_vecPosition2.z = pev->origin.z - pev->size.z + 8; + if (pev->speed == 0) + pev->speed = 150; + + if ( m_volume == 0 ) + m_volume = 0.85; +} + + +void CFuncPlat :: Precache( ) +{ + CBasePlatTrain::Precache(); + //PRECACHE_SOUND("plats/platmove1.wav"); + //PRECACHE_SOUND("plats/platstop1.wav"); + if ( !IsTogglePlat() ) + PlatSpawnInsideTrigger( pev ); // the "start moving" trigger +} + + +void CFuncPlat :: Spawn( ) +{ + Setup(); + + Precache(); + + // If this platform is the target of some button, it starts at the TOP position, + // and is brought down by that button. Otherwise, it starts at BOTTOM. + if ( !FStringNull(pev->targetname) ) + { + UTIL_SetOrigin (pev, m_vecPosition1); + m_toggle_state = TS_AT_TOP; + SetUse( &CFuncPlat::PlatUse ); + } + else + { + UTIL_SetOrigin (pev, m_vecPosition2); + m_toggle_state = TS_AT_BOTTOM; + } +} + + + +static void PlatSpawnInsideTrigger(entvars_t* pevPlatform) +{ + GetClassPtr( (CPlatTrigger *)NULL)->SpawnInsideTrigger( GetClassPtr( (CFuncPlat *)pevPlatform ) ); +} + + +// +// Create a trigger entity for a platform. +// +void CPlatTrigger :: SpawnInsideTrigger( CFuncPlat *pPlatform ) +{ + m_pPlatform = pPlatform; + // Create trigger entity, "point" it at the owning platform, give it a touch method + pev->solid = SOLID_TRIGGER; + pev->movetype = MOVETYPE_NONE; + pev->origin = pPlatform->pev->origin; + + // Establish the trigger field's size + Vector vecTMin = m_pPlatform->pev->mins + Vector ( 25 , 25 , 0 ); + Vector vecTMax = m_pPlatform->pev->maxs + Vector ( 25 , 25 , 8 ); + vecTMin.z = vecTMax.z - ( m_pPlatform->m_vecPosition1.z - m_pPlatform->m_vecPosition2.z + 8 ); + if (m_pPlatform->pev->size.x <= 50) + { + vecTMin.x = (m_pPlatform->pev->mins.x + m_pPlatform->pev->maxs.x) / 2; + vecTMax.x = vecTMin.x + 1; + } + if (m_pPlatform->pev->size.y <= 50) + { + vecTMin.y = (m_pPlatform->pev->mins.y + m_pPlatform->pev->maxs.y) / 2; + vecTMax.y = vecTMin.y + 1; + } + UTIL_SetSize ( pev, vecTMin, vecTMax ); +} + + +// +// When the platform's trigger field is touched, the platform ??? +// +void CPlatTrigger :: Touch( CBaseEntity *pOther ) +{ + // Ignore touches by non-players + entvars_t* pevToucher = pOther->pev; + if ( !FClassnameIs (pevToucher, "player") ) + return; + + // Ignore touches by corpses + if (!pOther->IsAlive()) + return; + + // Make linked platform go up/down. + if (m_pPlatform->m_toggle_state == TS_AT_BOTTOM) + m_pPlatform->GoUp(); + else if (m_pPlatform->m_toggle_state == TS_AT_TOP) + m_pPlatform->pev->nextthink = m_pPlatform->pev->ltime + 1;// delay going down +} + + +// +// Used by SUB_UseTargets, when a platform is the target of a button. +// Start bringing platform down. +// +void CFuncPlat :: PlatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( IsTogglePlat() ) + { + // Top is off, bottom is on + BOOL on = (m_toggle_state == TS_AT_BOTTOM) ? TRUE : FALSE; + + if ( !ShouldToggle( useType, on ) ) + return; + + if (m_toggle_state == TS_AT_TOP) + GoDown(); + else if ( m_toggle_state == TS_AT_BOTTOM ) + GoUp(); + } + else + { + SetUse( NULL ); + + if (m_toggle_state == TS_AT_TOP) + GoDown(); + } +} + + +// +// Platform is at top, now starts moving down. +// +void CFuncPlat :: GoDown( void ) +{ + if(pev->noiseMovement) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_AT_TOP || m_toggle_state == TS_GOING_UP); + m_toggle_state = TS_GOING_DOWN; + SetMoveDone(&CFuncPlat::CallHitBottom); + LinearMove(m_vecPosition2, pev->speed); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncPlat :: HitBottom( void ) +{ + if(pev->noiseMovement) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement)); + + if (pev->noiseStopMoving) + EMIT_SOUND(ENT(pev), CHAN_WEAPON, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_AT_BOTTOM; +} + + +// +// Platform is at bottom, now starts moving up +// +void CFuncPlat :: GoUp( void ) +{ + if (pev->noiseMovement) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_AT_BOTTOM || m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_GOING_UP; + SetMoveDone(&CFuncPlat::CallHitTop); + LinearMove(m_vecPosition1, pev->speed); +} + + +// +// Platform has hit top. Pauses, then starts back down again. +// +void CFuncPlat :: HitTop( void ) +{ + if(pev->noiseMovement) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement)); + + if (pev->noiseStopMoving) + EMIT_SOUND(ENT(pev), CHAN_WEAPON, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_GOING_UP); + m_toggle_state = TS_AT_TOP; + + if ( !IsTogglePlat() ) + { + // After a delay, the platform will automatically start going down again. + SetThink( &CFuncPlat::CallGoDown ); + pev->nextthink = pev->ltime + 3; + } +} + + +void CFuncPlat :: Blocked( CBaseEntity *pOther ) +{ + ALERT( at_aiconsole, "%s Blocked by %s\n", STRING(pev->classname), STRING(pOther->pev->classname) ); + // Hurt the blocker a little + pOther->TakeDamage(pev, pev, 1, DMG_CRUSH); + + if(pev->noiseMovement) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement)); + + // Send the platform back where it came from + ASSERT(m_toggle_state == TS_GOING_UP || m_toggle_state == TS_GOING_DOWN); + if (m_toggle_state == TS_GOING_UP) + GoDown(); + else if (m_toggle_state == TS_GOING_DOWN) + GoUp (); +} + + +class CFuncPlatRot : public CFuncPlat +{ +public: + void Spawn( void ); + void SetupRotation( void ); + + virtual void GoUp( void ); + virtual void GoDown( void ); + virtual void HitTop( void ); + virtual void HitBottom( void ); + + void RotMove( Vector &destAngle, float time ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + Vector m_end, m_start; +}; +LINK_ENTITY_TO_CLASS( func_platrot, CFuncPlatRot ); +TYPEDESCRIPTION CFuncPlatRot::m_SaveData[] = +{ + DEFINE_FIELD( CFuncPlatRot, m_end, FIELD_VECTOR ), + DEFINE_FIELD( CFuncPlatRot, m_start, FIELD_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CFuncPlatRot, CFuncPlat ); + + +void CFuncPlatRot :: SetupRotation( void ) +{ + if ( m_vecFinalAngle.x != 0 ) // This plat rotates too! + { + CBaseToggle :: AxisDir( pev ); + m_start = pev->angles; + m_end = pev->angles + pev->movedir * m_vecFinalAngle.x; + } + else + { + m_start = g_vecZero; + m_end = g_vecZero; + } + if ( !FStringNull(pev->targetname) ) // Start at top + { + pev->angles = m_end; + } +} + + +void CFuncPlatRot :: Spawn( void ) +{ + CFuncPlat :: Spawn(); + SetupRotation(); +} + +void CFuncPlatRot :: GoDown( void ) +{ + CFuncPlat :: GoDown(); + RotMove( m_start, pev->nextthink - pev->ltime ); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncPlatRot :: HitBottom( void ) +{ + CFuncPlat :: HitBottom(); + pev->avelocity = g_vecZero; + pev->angles = m_start; +} + + +// +// Platform is at bottom, now starts moving up +// +void CFuncPlatRot :: GoUp( void ) +{ + CFuncPlat :: GoUp(); + RotMove( m_end, pev->nextthink - pev->ltime ); +} + + +// +// Platform has hit top. Pauses, then starts back down again. +// +void CFuncPlatRot :: HitTop( void ) +{ + CFuncPlat :: HitTop(); + pev->avelocity = g_vecZero; + pev->angles = m_end; +} + + +void CFuncPlatRot :: RotMove( Vector &destAngle, float time ) +{ + // set destdelta to the vector needed to move + Vector vecDestDelta = destAngle - pev->angles; + + // Travel time is so short, we're practically there already; so make it so. + if ( time >= 0.1) + pev->avelocity = vecDestDelta / time; + else + { + pev->avelocity = vecDestDelta; + pev->nextthink = pev->ltime + 1; + } +} + + +// +//====================== TRAIN code ================================================== +// + +class CFuncTrain : public CBasePlatTrain +{ +public: + void Spawn( void ); + void Precache( void ); + void Activate( void ); + void OverrideReset( void ); + + void Blocked( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + + void EXPORT Wait( void ); + void EXPORT Next( void ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + entvars_t *m_pevCurrentTarget; + int m_sounds; + BOOL m_activated; +}; + +LINK_ENTITY_TO_CLASS( func_train, CFuncTrain ); +TYPEDESCRIPTION CFuncTrain::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTrain, m_sounds, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrain, m_pevCurrentTarget, FIELD_EVARS ), + DEFINE_FIELD( CFuncTrain, m_activated, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTrain, CBasePlatTrain ); + + +void CFuncTrain :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBasePlatTrain::KeyValue( pkvd ); +} + + +void CFuncTrain :: Blocked( CBaseEntity *pOther ) + +{ + if ( gpGlobals->time < m_flActivateFinished) + return; + + m_flActivateFinished = gpGlobals->time + 0.5; + + pOther->TakeDamage(pev, pev, pev->dmg, DMG_CRUSH); +} + + +void CFuncTrain :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->spawnflags & SF_TRAIN_WAIT_RETRIGGER ) + { + // Move toward my target + pev->spawnflags &= ~SF_TRAIN_WAIT_RETRIGGER; + Next(); + } + else + { + pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER; + // Pop back to last target if it's available + if ( pev->enemy ) + pev->target = pev->enemy->v.targetname; + pev->nextthink = 0; + pev->velocity = g_vecZero; + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + } +} + + +void CFuncTrain :: Wait( void ) +{ + // Fire the pass target if there is one + if ( m_pevCurrentTarget->message ) + { + FireTargets( STRING(m_pevCurrentTarget->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( m_pevCurrentTarget->spawnflags, SF_CORNER_FIREONCE ) ) + m_pevCurrentTarget->message = 0; + } + + // need pointer to LAST target. + if ( FBitSet (m_pevCurrentTarget->spawnflags , SF_TRAIN_WAIT_RETRIGGER ) || ( pev->spawnflags & SF_TRAIN_WAIT_RETRIGGER ) ) + { + pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER; + // clear the sound channel. + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + pev->nextthink = 0; + return; + } + + // ALERT ( at_console, "%f\n", m_flWait ); + + if (m_flWait != 0) + {// -1 wait will wait forever! + pev->nextthink = pev->ltime + m_flWait; + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + SetThink( &CFuncTrain::Next ); + } + else + { + Next();// do it RIGHT now! + } +} + + +// +// Train next - path corner needs to change to next target +// +void CFuncTrain :: Next( void ) +{ + CBaseEntity *pTarg; + + + // now find our next target + pTarg = GetNextTarget(); + + if ( !pTarg ) + { + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + // Play stop sound + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + return; + } + + // Save last target in case we need to find it again + pev->message = pev->target; + + pev->target = pTarg->pev->target; + m_flWait = pTarg->GetDelay(); + + if ( m_pevCurrentTarget && m_pevCurrentTarget->speed != 0 ) + {// don't copy speed from target if it is 0 (uninitialized) + pev->speed = m_pevCurrentTarget->speed; + ALERT( at_aiconsole, "Train %s speed to %4.2f\n", STRING(pev->targetname), pev->speed ); + } + m_pevCurrentTarget = pTarg->pev;// keep track of this since path corners change our target for us. + + pev->enemy = pTarg->edict();//hack + + if(FBitSet(m_pevCurrentTarget->spawnflags, SF_CORNER_TELEPORT)) + { + // Path corner has indicated a teleport to the next corner. + SetBits(pev->effects, EF_NOINTERP); + UTIL_SetOrigin(pev, pTarg->pev->origin - (pev->mins + pev->maxs)* 0.5); + Wait(); // Get on with doing the next path corner. + } + else + { + // Normal linear move. + + // CHANGED this from CHAN_VOICE to CHAN_STATIC around OEM beta time because trains should + // use CHAN_STATIC for their movement sounds to prevent sound field problems. + // this is not a hack or temporary fix, this is how things should be. (sjb). + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + if ( pev->noiseMovement ) + EMIT_SOUND (ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement), m_volume, ATTN_NORM); + ClearBits(pev->effects, EF_NOINTERP); + SetMoveDone( &CFuncTrain::Wait ); + LinearMove (pTarg->pev->origin - (pev->mins + pev->maxs)* 0.5, pev->speed); + } +} + + +void CFuncTrain :: Activate( void ) +{ + // Not yet active, so teleport to first target + if ( !m_activated ) + { + m_activated = TRUE; + entvars_t *pevTarg = VARS( FIND_ENTITY_BY_TARGETNAME (NULL, STRING(pev->target) ) ); + + pev->target = pevTarg->target; + m_pevCurrentTarget = pevTarg;// keep track of this since path corners change our target for us. + + UTIL_SetOrigin (pev, pevTarg->origin - (pev->mins + pev->maxs) * 0.5 ); + + if ( FStringNull(pev->targetname) ) + { // not triggered, so start immediately + pev->nextthink = pev->ltime + 0.1; + SetThink( &CFuncTrain::Next ); + } + else + pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER; + } +} + +/*QUAKED func_train (0 .5 .8) ? +Trains are moving platforms that players can ride. +The targets origin specifies the min point of the train at each corner. +The train spawns at the first target it is pointing at. +If the train is the target of a button or trigger, it will not begin moving until activated. +speed default 100 +dmg default 2 +sounds +1) ratchet metal +*/ + +void CFuncTrain :: Spawn( void ) +{ + Precache(); + if (pev->speed == 0) + pev->speed = 100; + + if ( FStringNull(pev->target) ) + ALERT(at_console, "FuncTrain with no target"); + + if (pev->dmg == 0) + pev->dmg = 2; + + pev->movetype = MOVETYPE_PUSH; + + if ( FBitSet (pev->spawnflags, SF_TRACKTRAIN_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + + SET_MODEL( ENT(pev), STRING(pev->model) ); + UTIL_SetSize (pev, pev->mins, pev->maxs); + UTIL_SetOrigin(pev, pev->origin); + + m_activated = FALSE; + + if ( m_volume == 0 ) + m_volume = 0.85; +} + + +void CFuncTrain::Precache( void ) +{ + CBasePlatTrain::Precache(); + +#if 0 // obsolete + // otherwise use preset sound + switch (m_sounds) + { + case 0: + pev->noise = 0; + pev->noise1 = 0; + break; + + case 1: + PRECACHE_SOUND ("plats/train2.wav"); + PRECACHE_SOUND ("plats/train1.wav"); + pev->noise = MAKE_STRING("plats/train2.wav"); + pev->noise1 = MAKE_STRING("plats/train1.wav"); + break; + + case 2: + PRECACHE_SOUND ("plats/platmove1.wav"); + PRECACHE_SOUND ("plats/platstop1.wav"); + pev->noise = MAKE_STRING("plats/platstop1.wav"); + pev->noise1 = MAKE_STRING("plats/platmove1.wav"); + break; + } +#endif +} + + +void CFuncTrain::OverrideReset( void ) +{ + CBaseEntity *pTarg; + + // Are we moving? + if ( pev->velocity != g_vecZero && pev->nextthink != 0 ) + { + pev->target = pev->message; + // now find our next target + pTarg = GetNextTarget(); + if ( !pTarg ) + { + pev->nextthink = 0; + pev->velocity = g_vecZero; + } + else // Keep moving for 0.1 secs, then find path_corner again and restart + { + SetThink( &CFuncTrain::Next ); + pev->nextthink = pev->ltime + 0.1; + } + } +} + + + + +// --------------------------------------------------------------------- +// +// Track Train +// +// --------------------------------------------------------------------- + +TYPEDESCRIPTION CFuncTrackTrain::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTrackTrain, m_ppath, FIELD_CLASSPTR ), + DEFINE_FIELD( CFuncTrackTrain, m_length, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_height, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_speed, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_dir, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_startSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_controlMins, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTrackTrain, m_controlMaxs, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTrackTrain, m_sounds, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrackTrain, m_flVolume, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_flBank, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_oldSpeed, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTrackTrain, CBaseEntity ); +LINK_ENTITY_TO_CLASS( func_tracktrain, CFuncTrackTrain ); + +void CFuncTrackTrain :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "wheels")) + { + m_length = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "height")) + { + m_height = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "startspeed")) + { + m_startSpeed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "volume")) + { + m_flVolume = (float) (atoi(pkvd->szValue)); + m_flVolume *= 0.1; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "bank")) + { + m_flBank = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +void CFuncTrackTrain :: NextThink( float thinkTime, BOOL alwaysThink ) +{ + if ( alwaysThink ) + pev->flags |= FL_ALWAYSTHINK; + else + pev->flags &= ~FL_ALWAYSTHINK; + + pev->nextthink = thinkTime; +} + + +void CFuncTrackTrain :: Blocked( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + // Blocker is on-ground on the train + if ( FBitSet( pevOther->flags, FL_ONGROUND ) && VARS(pevOther->groundentity) == pev ) + { + float deltaSpeed = fabs(pev->speed); + if ( deltaSpeed > 50 ) + deltaSpeed = 50; + if ( !pevOther->velocity.z ) + pevOther->velocity.z += deltaSpeed; + return; + } + else + pevOther->velocity = (pevOther->origin - pev->origin ).Normalize() * pev->dmg; + + ALERT( at_aiconsole, "TRAIN(%s): Blocked by %s (dmg:%.2f)\n", STRING(pev->targetname), STRING(pOther->pev->classname), pev->dmg ); + if ( pev->dmg <= 0 ) + return; + // we can't hurt this thing, so we're not concerned with it + pOther->TakeDamage(pev, pev, pev->dmg, DMG_CRUSH); +} + + +void CFuncTrackTrain :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( useType != USE_SET ) + { + if ( !ShouldToggle( useType, (pev->speed != 0) ) ) + return; + + if ( pev->speed == 0 ) + { + pev->speed = m_speed * m_dir; + + Next(); + } + else + { + pev->speed = 0; + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + StopSound(); + SetThink( NULL ); + } + } + else + { + float delta = value; + + delta = ((int)(pev->speed * 4) / (int)m_speed)*0.25 + 0.25 * delta; + if ( delta > 1 ) + delta = 1; + else if ( delta < -1 ) + delta = -1; + if ( pev->spawnflags & SF_TRACKTRAIN_FORWARDONLY ) + { + if ( delta < 0 ) + delta = 0; + } + pev->speed = m_speed * delta; + Next(); + ALERT( at_aiconsole, "TRAIN(%s), speed to %.2f\n", STRING(pev->targetname), pev->speed ); + } +} + + +static float Fix( float angle ) +{ + while ( angle < 0 ) + angle += 360; + while ( angle > 360 ) + angle -= 360; + + return angle; +} + + +static void FixupAngles( Vector &v ) +{ + v.x = Fix( v.x ); + v.y = Fix( v.y ); + v.z = Fix( v.z ); +} + +#define TRAIN_STARTPITCH 60 +#define TRAIN_MAXPITCH 200 +#define TRAIN_MAXSPEED 1000 // approx max speed for sound pitch calculation + +void CFuncTrackTrain :: StopSound( void ) +{ + // if sound playing, stop it + if (m_soundPlaying && pev->noise) + { + unsigned short us_encode; + unsigned short us_sound = ( ( unsigned short )( m_sounds ) & 0x0007 ) << 12; + + us_encode = us_sound; + + PLAYBACK_EVENT_FULL( FEV_RELIABLE | FEV_UPDATE, edict(), m_usAdjustPitch, 0.0, + (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, us_encode, 0, 1, 0 ); + + /* + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noise)); + */ + EMIT_SOUND_DYN(ENT(pev), CHAN_ITEM, "plats/ttrain_brake1.wav", m_flVolume, ATTN_NORM, 0, 100); + } + + m_soundPlaying = 0; +} + +// update pitch based on speed, start sound if not playing +// NOTE: when train goes through transition, m_soundPlaying should go to 0, +// which will cause the looped sound to restart. + +void CFuncTrackTrain :: UpdateSound( void ) +{ + float flpitch; + + if (!pev->noise) + return; + + flpitch = TRAIN_STARTPITCH + (abs(static_cast(pev->speed)) * (TRAIN_MAXPITCH - TRAIN_STARTPITCH) / TRAIN_MAXSPEED); + + if (!m_soundPlaying) + { + // play startup sound for train + EMIT_SOUND_DYN(ENT(pev), CHAN_ITEM, "plats/ttrain_start1.wav", m_flVolume, ATTN_NORM, 0, 100); + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noise), m_flVolume, ATTN_NORM, 0, (int) flpitch); + m_soundPlaying = 1; + } + else + { +/* + // update pitch + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noise), m_flVolume, ATTN_NORM, SND_CHANGE_PITCH, (int) flpitch); +*/ + // volume 0.0 - 1.0 - 6 bits + // m_sounds 3 bits + // flpitch = 6 bits + // 15 bits total + + unsigned short us_encode; + unsigned short us_sound = ( ( unsigned short )( m_sounds ) & 0x0007 ) << 12; + unsigned short us_pitch = ( ( unsigned short )( flpitch / 10.0 ) & 0x003f ) << 6; + unsigned short us_volume = ( ( unsigned short )( m_flVolume * 40.0 ) & 0x003f ); + + us_encode = us_sound | us_pitch | us_volume; + + PLAYBACK_EVENT_FULL( FEV_RELIABLE | FEV_UPDATE, edict(), m_usAdjustPitch, 0.0, + (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, us_encode, 0, 0, 0 ); + } +} + + +void CFuncTrackTrain :: Next( void ) +{ + float time = 0.5; + + if ( !pev->speed ) + { + ALERT( at_aiconsole, "TRAIN(%s): Speed is 0\n", STRING(pev->targetname) ); + StopSound(); + return; + } + +// if ( !m_ppath ) +// m_ppath = CPathTrack::Instance(FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) )); + if ( !m_ppath ) + { + ALERT( at_aiconsole, "TRAIN(%s): Lost path\n", STRING(pev->targetname) ); + StopSound(); + return; + } + + UpdateSound(); + + Vector nextPos = pev->origin; + + nextPos.z -= m_height; + CPathTrack *pnext = m_ppath->LookAhead( &nextPos, pev->speed * 0.1, 1 ); + nextPos.z += m_height; + + pev->velocity = (nextPos - pev->origin) * 10; + Vector nextFront = pev->origin; + + nextFront.z -= m_height; + if ( m_length > 0 ) + m_ppath->LookAhead( &nextFront, m_length, 0 ); + else + m_ppath->LookAhead( &nextFront, 100, 0 ); + nextFront.z += m_height; + + Vector delta = nextFront - pev->origin; + Vector angles = UTIL_VecToAngles( delta ); + // The train actually points west + angles.y += 180; + + // !!! All of this crap has to be done to make the angles not wrap around, revisit this. + FixupAngles( angles ); + FixupAngles( pev->angles ); + + if ( !pnext || (delta.x == 0 && delta.y == 0) ) + angles = pev->angles; + + float vy, vx; + if ( !(pev->spawnflags & SF_TRACKTRAIN_NOPITCH) ) + vx = UTIL_AngleDistance( angles.x, pev->angles.x ); + else + vx = 0; + vy = UTIL_AngleDistance( angles.y, pev->angles.y ); + + pev->avelocity.y = vy * 10; + pev->avelocity.x = vx * 10; + + if ( m_flBank != 0 ) + { + if ( pev->avelocity.y < -5 ) + pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( -m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z); + else if ( pev->avelocity.y > 5 ) + pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z); + else + pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( 0, pev->angles.z, m_flBank*4 ), pev->angles.z) * 4; + } + + if ( pnext ) + { + if ( pnext != m_ppath ) + { + CPathTrack *pFire; + if ( pev->speed >= 0 ) + pFire = pnext; + else + pFire = m_ppath; + + m_ppath = pnext; + // Fire the pass target if there is one + if ( pFire->pev->message ) + { + FireTargets( STRING(pFire->pev->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( pFire->pev->spawnflags, SF_PATH_FIREONCE ) ) + pFire->pev->message = 0; + } + + if ( pFire->pev->spawnflags & SF_PATH_DISABLE_TRAIN ) + pev->spawnflags |= SF_TRACKTRAIN_NOCONTROL; + + // Don't override speed if under user control + if ( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL ) + { + if ( pFire->pev->speed != 0 ) + {// don't copy speed from target if it is 0 (uninitialized) + pev->speed = pFire->pev->speed; + ALERT( at_aiconsole, "TrackTrain %s speed to %4.2f\n", STRING(pev->targetname), pev->speed ); + } + } + + } + SetThink( &CFuncTrackTrain::Next ); + NextThink( pev->ltime + time, TRUE ); + } + else // end of path, stop + { + StopSound(); + pev->velocity = (nextPos - pev->origin); + pev->avelocity = g_vecZero; + float distance = pev->velocity.Length(); + m_oldSpeed = pev->speed; + + + pev->speed = 0; + + // Move to the dead end + + // Are we there yet? + if ( distance > 0 ) + { + // no, how long to get there? + time = distance / m_oldSpeed; + pev->velocity = pev->velocity * (m_oldSpeed / distance); + SetThink( &CFuncTrackTrain::DeadEnd ); + NextThink( pev->ltime + time, FALSE ); + } + else + { + DeadEnd(); + } + } +} + + +void CFuncTrackTrain::DeadEnd( void ) +{ + // Fire the dead-end target if there is one + CPathTrack *pTrack, *pNext; + + pTrack = m_ppath; + + ALERT( at_aiconsole, "TRAIN(%s): Dead end ", STRING(pev->targetname) ); + // Find the dead end path node + // HACKHACK -- This is bugly, but the train can actually stop moving at a different node depending on it's speed + // so we have to traverse the list to it's end. + if ( pTrack ) + { + if ( m_oldSpeed < 0 ) + { + do + { + pNext = pTrack->ValidPath( pTrack->GetPrevious(), TRUE ); + if ( pNext ) + pTrack = pNext; + } while ( pNext ); + } + else + { + do + { + pNext = pTrack->ValidPath( pTrack->GetNext(), TRUE ); + if ( pNext ) + pTrack = pNext; + } while ( pNext ); + } + } + + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + if ( pTrack ) + { + ALERT( at_aiconsole, "at %s\n", STRING(pTrack->pev->targetname) ); + if ( pTrack->pev->netname ) + FireTargets( STRING(pTrack->pev->netname), this, this, USE_TOGGLE, 0 ); + } + else + ALERT( at_aiconsole, "\n" ); +} + + +void CFuncTrackTrain :: SetControls( entvars_t *pevControls ) +{ + Vector offset = pevControls->origin - pev->oldorigin; + + m_controlMins = pevControls->mins + offset; + m_controlMaxs = pevControls->maxs + offset; +} + + +BOOL CFuncTrackTrain :: OnControls( entvars_t *pevTest ) +{ + Vector offset = pevTest->origin - pev->origin; + + if ( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL ) + return FALSE; + + // Transform offset into local coordinates + UTIL_MakeVectors( pev->angles ); + Vector local; + local.x = DotProduct( offset, gpGlobals->v_forward ); + local.y = -DotProduct( offset, gpGlobals->v_right ); + local.z = DotProduct( offset, gpGlobals->v_up ); + + if ( local.x >= m_controlMins.x && local.y >= m_controlMins.y && local.z >= m_controlMins.z && + local.x <= m_controlMaxs.x && local.y <= m_controlMaxs.y && local.z <= m_controlMaxs.z ) + return TRUE; + + return FALSE; +} + + +void CFuncTrackTrain :: Find( void ) +{ + m_ppath = CPathTrack::Instance(FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) )); + if ( !m_ppath ) + return; + + entvars_t *pevTarget = m_ppath->pev; + if ( !FClassnameIs( pevTarget, "path_track" ) ) + { + ALERT( at_error, "func_track_train must be on a path of path_track\n" ); + m_ppath = NULL; + return; + } + + Vector nextPos = pevTarget->origin; + nextPos.z += m_height; + + Vector look = nextPos; + look.z -= m_height; + m_ppath->LookAhead( &look, m_length, 0 ); + look.z += m_height; + + pev->angles = UTIL_VecToAngles( look - nextPos ); + // The train actually points west + pev->angles.y += 180; + + if ( pev->spawnflags & SF_TRACKTRAIN_NOPITCH ) + pev->angles.x = 0; + UTIL_SetOrigin( pev, nextPos ); + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncTrackTrain::Next ); + pev->speed = m_startSpeed; + + UpdateSound(); +} + + +void CFuncTrackTrain :: NearestPath( void ) +{ + CBaseEntity *pTrack = NULL; + CBaseEntity *pNearest = NULL; + float dist, closest; + + closest = 1024; + + while ((pTrack = UTIL_FindEntityInSphere( pTrack, pev->origin, 1024 )) != NULL) + { + // filter out non-tracks + if ( !(pTrack->pev->flags & (FL_CLIENT|FL_MONSTER)) && FClassnameIs( pTrack->pev, "path_track" ) ) + { + dist = (pev->origin - pTrack->pev->origin).Length(); + if ( dist < closest ) + { + closest = dist; + pNearest = pTrack; + } + } + } + + if ( !pNearest ) + { + ALERT( at_console, "Can't find a nearby track !!!\n" ); + SetThink(NULL); + return; + } + + ALERT( at_aiconsole, "TRAIN: %s, Nearest track is %s\n", STRING(pev->targetname), STRING(pNearest->pev->targetname) ); + // If I'm closer to the next path_track on this path, then it's my real path + pTrack = ((CPathTrack *)pNearest)->GetNext(); + if ( pTrack ) + { + if ( (pev->origin - pTrack->pev->origin).Length() < (pev->origin - pNearest->pev->origin).Length() ) + pNearest = pTrack; + } + + m_ppath = (CPathTrack *)pNearest; + + if ( pev->speed != 0 ) + { + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncTrackTrain::Next ); + } +} + + +void CFuncTrackTrain::OverrideReset( void ) +{ + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncTrackTrain::NearestPath ); +} + + +CFuncTrackTrain *CFuncTrackTrain::Instance( edict_t *pent ) +{ + if ( FClassnameIs( pent, "func_tracktrain" ) ) + return (CFuncTrackTrain *)GET_PRIVATE(pent); + return NULL; +} + +/*QUAKED func_train (0 .5 .8) ? +Trains are moving platforms that players can ride. +The targets origin specifies the min point of the train at each corner. +The train spawns at the first target it is pointing at. +If the train is the target of a button or trigger, it will not begin moving until activated. +speed default 100 +dmg default 2 +sounds +1) ratchet metal +*/ + +void CFuncTrackTrain :: Spawn( void ) +{ + if ( pev->speed == 0 ) + m_speed = 100; + else + m_speed = pev->speed; + + pev->speed = 0; + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + pev->impulse = static_cast(m_speed); + + m_dir = 1; + + if ( FStringNull(pev->target) ) + ALERT( at_console, "FuncTrain with no target" ); + + if ( pev->spawnflags & SF_TRACKTRAIN_PASSABLE ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + SET_MODEL( ENT(pev), STRING(pev->model) ); + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( pev, pev->origin ); + + // Cache off placed origin for train controls + pev->oldorigin = pev->origin; + + m_controlMins = pev->mins; + m_controlMaxs = pev->maxs; + m_controlMaxs.z += 72; +// start trains on the next frame, to make sure their targets have had +// a chance to spawn/activate + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncTrackTrain::Find ); + Precache(); +} + +void CFuncTrackTrain :: Precache( void ) +{ + if (m_flVolume == 0.0) + m_flVolume = 1.0; + + switch (m_sounds) + { + default: + // no sound + pev->noise = 0; + break; + case 1: PRECACHE_SOUND("plats/ttrain1.wav"); pev->noise = MAKE_STRING("plats/ttrain1.wav");break; + case 2: PRECACHE_SOUND("plats/ttrain2.wav"); pev->noise = MAKE_STRING("plats/ttrain2.wav");break; + case 3: PRECACHE_SOUND("plats/ttrain3.wav"); pev->noise = MAKE_STRING("plats/ttrain3.wav");break; + case 4: PRECACHE_SOUND("plats/ttrain4.wav"); pev->noise = MAKE_STRING("plats/ttrain4.wav");break; + case 5: PRECACHE_SOUND("plats/ttrain6.wav"); pev->noise = MAKE_STRING("plats/ttrain6.wav");break; + case 6: PRECACHE_SOUND("plats/ttrain7.wav"); pev->noise = MAKE_STRING("plats/ttrain7.wav");break; + } + + PRECACHE_SOUND("plats/ttrain_brake1.wav"); + PRECACHE_SOUND("plats/ttrain_start1.wav"); + + m_usAdjustPitch = PRECACHE_EVENT( 1, "events/train.sc" ); +} + +// This class defines the volume of space that the player must stand in to control the train +class CFuncTrainControls : public CBaseEntity +{ +public: + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + void Spawn( void ); + void EXPORT Find( void ); +}; +LINK_ENTITY_TO_CLASS( func_traincontrols, CFuncTrainControls ); + + +void CFuncTrainControls :: Find( void ) +{ + edict_t *pTarget = NULL; + + do + { + pTarget = FIND_ENTITY_BY_TARGETNAME( pTarget, STRING(pev->target) ); + } while ( !FNullEnt(pTarget) && !FClassnameIs(pTarget, "func_tracktrain") ); + + if ( FNullEnt( pTarget ) ) + { + ALERT( at_console, "No train %s\n", STRING(pev->target) ); + return; + } + + CFuncTrackTrain *ptrain = CFuncTrackTrain::Instance(pTarget); + ptrain->SetControls( pev ); + UTIL_Remove( this ); +} + + +void CFuncTrainControls :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( pev, pev->origin ); + + SetThink( &CFuncTrainControls::Find ); + pev->nextthink = gpGlobals->time; +} + + + +// ---------------------------------------------------------------------------- +// +// Track changer / Train elevator +// +// ---------------------------------------------------------------------------- + +#define SF_TRACK_ACTIVATETRAIN 0x00000001 +#define SF_TRACK_RELINK 0x00000002 +#define SF_TRACK_ROTMOVE 0x00000004 +#define SF_TRACK_STARTBOTTOM 0x00000008 +#define SF_TRACK_DONT_MOVE 0x00000010 + +// +// This entity is a rotating/moving platform that will carry a train to a new track. +// It must be larger in X-Y planar area than the train, since it must contain the +// train within these dimensions in order to operate when the train is near it. +// + +typedef enum { TRAIN_SAFE, TRAIN_BLOCKING, TRAIN_FOLLOWING } TRAIN_CODE; + +class CFuncTrackChange : public CFuncPlatRot +{ +public: + void Spawn( void ); + void Precache( void ); + +// virtual void Blocked( void ); + virtual void EXPORT GoUp( void ); + virtual void EXPORT GoDown( void ); + + void KeyValue( KeyValueData* pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Find( void ); + TRAIN_CODE EvaluateTrain( CPathTrack *pcurrent ); + void UpdateTrain( Vector &dest ); + virtual void HitBottom( void ); + virtual void HitTop( void ); + void Touch( CBaseEntity *pOther ); + virtual void UpdateAutoTargets( int toggleState ); + virtual BOOL IsTogglePlat( void ) { return TRUE; } + + void DisableUse( void ) { m_use = 0; } + void EnableUse( void ) { m_use = 1; } + int UseEnabled( void ) { return m_use; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + virtual void OverrideReset( void ); + + + CPathTrack *m_trackTop; + CPathTrack *m_trackBottom; + + CFuncTrackTrain *m_train; + + int m_trackTopName; + int m_trackBottomName; + int m_trainName; + TRAIN_CODE m_code; + int m_targetState; + int m_use; +}; +LINK_ENTITY_TO_CLASS( func_trackchange, CFuncTrackChange ); + +TYPEDESCRIPTION CFuncTrackChange::m_SaveData[] = +{ + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackTop, FIELD_CLASSPTR ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackBottom, FIELD_CLASSPTR ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_train, FIELD_CLASSPTR ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackTopName, FIELD_STRING ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackBottomName, FIELD_STRING ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trainName, FIELD_STRING ), + DEFINE_FIELD( CFuncTrackChange, m_code, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrackChange, m_targetState, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrackChange, m_use, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTrackChange, CFuncPlatRot ); + +void CFuncTrackChange :: Spawn( void ) +{ + Setup(); + if ( FBitSet( pev->spawnflags, SF_TRACK_DONT_MOVE ) ) + m_vecPosition2.z = pev->origin.z; + + SetupRotation(); + + if ( FBitSet( pev->spawnflags, SF_TRACK_STARTBOTTOM ) ) + { + UTIL_SetOrigin (pev, m_vecPosition2); + m_toggle_state = TS_AT_BOTTOM; + pev->angles = m_start; + m_targetState = TS_AT_TOP; + } + else + { + UTIL_SetOrigin (pev, m_vecPosition1); + m_toggle_state = TS_AT_TOP; + pev->angles = m_end; + m_targetState = TS_AT_BOTTOM; + } + + EnableUse(); + pev->nextthink = pev->ltime + 2.0; + SetThink( &CFuncTrackChange::Find ); + Precache(); +} + +void CFuncTrackChange :: Precache( void ) +{ + // Can't trigger sound + PRECACHE_SOUND( "buttons/button11.wav" ); + + CFuncPlatRot::Precache(); +} + + +// UNDONE: Filter touches before re-evaluating the train. +void CFuncTrackChange :: Touch( CBaseEntity *pOther ) +{ +#if 0 + TRAIN_CODE code; + entvars_t *pevToucher = pOther->pev; +#endif +} + + + +void CFuncTrackChange :: KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "train") ) + { + m_trainName = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "toptrack") ) + { + m_trackTopName = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "bottomtrack") ) + { + m_trackBottomName = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + { + CFuncPlatRot::KeyValue( pkvd ); // Pass up to base class + } +} + + +void CFuncTrackChange::OverrideReset( void ) +{ + pev->nextthink = pev->ltime + 1.0; + SetThink( &CFuncTrackChange::Find ); +} + +void CFuncTrackChange :: Find( void ) +{ + // Find track entities + edict_t *target; + + target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trackTopName) ); + if ( !FNullEnt(target) ) + { + m_trackTop = CPathTrack::Instance( target ); + target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trackBottomName) ); + if ( !FNullEnt(target) ) + { + m_trackBottom = CPathTrack::Instance( target ); + target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trainName) ); + if ( !FNullEnt(target) ) + { + m_train = CFuncTrackTrain::Instance( FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trainName) ) ); + if ( !m_train ) + { + ALERT( at_error, "Can't find train for track change! %s\n", STRING(m_trainName) ); + return; + } + Vector center = (pev->absmin + pev->absmax) * 0.5; + m_trackBottom = m_trackBottom->Nearest( center ); + m_trackTop = m_trackTop->Nearest( center ); + UpdateAutoTargets( m_toggle_state ); + SetThink( NULL ); + return; + } + else + { + ALERT( at_error, "Can't find train for track change! %s\n", STRING(m_trainName) ); + target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trainName) ); + } + } + else + ALERT( at_error, "Can't find bottom track for track change! %s\n", STRING(m_trackBottomName) ); + } + else + ALERT( at_error, "Can't find top track for track change! %s\n", STRING(m_trackTopName) ); +} + + + +TRAIN_CODE CFuncTrackChange :: EvaluateTrain( CPathTrack *pcurrent ) +{ + // Go ahead and work, we don't have anything to switch, so just be an elevator + if ( !pcurrent || !m_train ) + return TRAIN_SAFE; + + if ( m_train->m_ppath == pcurrent || (pcurrent->m_pprevious && m_train->m_ppath == pcurrent->m_pprevious) || + (pcurrent->m_pnext && m_train->m_ppath == pcurrent->m_pnext) ) + { + if ( m_train->pev->speed != 0 ) + return TRAIN_BLOCKING; + + Vector dist = pev->origin - m_train->pev->origin; + float length = dist.Length2D(); + if ( length < m_train->m_length ) // Empirically determined close distance + return TRAIN_FOLLOWING; + else if ( length > (150 + m_train->m_length) ) + return TRAIN_SAFE; + + return TRAIN_BLOCKING; + } + + return TRAIN_SAFE; +} + + +void CFuncTrackChange :: UpdateTrain( Vector &dest ) +{ + float time = (pev->nextthink - pev->ltime); + + m_train->pev->velocity = pev->velocity; + m_train->pev->avelocity = pev->avelocity; + m_train->NextThink( m_train->pev->ltime + time, FALSE ); + + // Attempt at getting the train to rotate properly around the origin of the trackchange + if ( time <= 0 ) + return; + + Vector offset = m_train->pev->origin - pev->origin; + Vector delta = dest - pev->angles; + // Transform offset into local coordinates + UTIL_MakeInvVectors( delta, gpGlobals ); + Vector local; + local.x = DotProduct( offset, gpGlobals->v_forward ); + local.y = DotProduct( offset, gpGlobals->v_right ); + local.z = DotProduct( offset, gpGlobals->v_up ); + + local = local - offset; + m_train->pev->velocity = pev->velocity + (local * (1.0/time)); +} + +void CFuncTrackChange :: GoDown( void ) +{ + if ( m_code == TRAIN_BLOCKING ) + return; + + // HitBottom may get called during CFuncPlat::GoDown(), so set up for that + // before you call GoDown() + + UpdateAutoTargets( TS_GOING_DOWN ); + // If ROTMOVE, move & rotate + if ( FBitSet( pev->spawnflags, SF_TRACK_DONT_MOVE ) ) + { + SetMoveDone( &CFuncPlat::CallHitBottom ); + m_toggle_state = TS_GOING_DOWN; + AngularMove( m_start, pev->speed ); + } + else + { + CFuncPlat :: GoDown(); + SetMoveDone( &CFuncPlat::CallHitBottom ); + RotMove( m_start, pev->nextthink - pev->ltime ); + } + // Otherwise, rotate first, move second + + // If the train is moving with the platform, update it + if ( m_code == TRAIN_FOLLOWING ) + { + UpdateTrain( m_start ); + m_train->m_ppath = NULL; + } +} + + +// +// Platform is at bottom, now starts moving up +// +void CFuncTrackChange :: GoUp( void ) +{ + if ( m_code == TRAIN_BLOCKING ) + return; + + // HitTop may get called during CFuncPlat::GoUp(), so set up for that + // before you call GoUp(); + + UpdateAutoTargets( TS_GOING_UP ); + if ( FBitSet( pev->spawnflags, SF_TRACK_DONT_MOVE ) ) + { + m_toggle_state = TS_GOING_UP; + SetMoveDone( &CFuncPlat::CallHitTop ); + AngularMove( m_end, pev->speed ); + } + else + { + // If ROTMOVE, move & rotate + CFuncPlat :: GoUp(); + SetMoveDone( &CFuncPlat::CallHitTop ); + RotMove( m_end, pev->nextthink - pev->ltime ); + } + + // Otherwise, move first, rotate second + + // If the train is moving with the platform, update it + if ( m_code == TRAIN_FOLLOWING ) + { + UpdateTrain( m_end ); + m_train->m_ppath = NULL; + } +} + + +// Normal track change +void CFuncTrackChange :: UpdateAutoTargets( int toggleState ) +{ + if ( !m_trackTop || !m_trackBottom ) + return; + + if ( toggleState == TS_AT_TOP ) + ClearBits( m_trackTop->pev->spawnflags, SF_PATH_DISABLED ); + else + SetBits( m_trackTop->pev->spawnflags, SF_PATH_DISABLED ); + + if ( toggleState == TS_AT_BOTTOM ) + ClearBits( m_trackBottom->pev->spawnflags, SF_PATH_DISABLED ); + else + SetBits( m_trackBottom->pev->spawnflags, SF_PATH_DISABLED ); +} + + +void CFuncTrackChange :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( m_toggle_state != TS_AT_TOP && m_toggle_state != TS_AT_BOTTOM ) + return; + + // If train is in "safe" area, but not on the elevator, play alarm sound + if ( m_toggle_state == TS_AT_TOP ) + m_code = EvaluateTrain( m_trackTop ); + else if ( m_toggle_state == TS_AT_BOTTOM ) + m_code = EvaluateTrain( m_trackBottom ); + else + m_code = TRAIN_BLOCKING; + if ( m_code == TRAIN_BLOCKING ) + { + // Play alarm and return + EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/button11.wav", 1, ATTN_NORM); + return; + } + + // Otherwise, it's safe to move + // If at top, go down + // at bottom, go up + + DisableUse(); + if (m_toggle_state == TS_AT_TOP) + GoDown(); + else + GoUp(); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncTrackChange :: HitBottom( void ) +{ + CFuncPlatRot :: HitBottom(); + if ( m_code == TRAIN_FOLLOWING ) + { +// UpdateTrain(); + m_train->SetTrack( m_trackBottom ); + } + SetThink( NULL ); + pev->nextthink = -1; + + UpdateAutoTargets( m_toggle_state ); + + EnableUse(); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncTrackChange :: HitTop( void ) +{ + CFuncPlatRot :: HitTop(); + if ( m_code == TRAIN_FOLLOWING ) + { +// UpdateTrain(); + m_train->SetTrack( m_trackTop ); + } + + // Don't let the plat go back down + SetThink( NULL ); + pev->nextthink = -1; + UpdateAutoTargets( m_toggle_state ); + EnableUse(); +} + + + +class CFuncTrackAuto : public CFuncTrackChange +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual void UpdateAutoTargets( int toggleState ); +}; + +LINK_ENTITY_TO_CLASS( func_trackautochange, CFuncTrackAuto ); + +// Auto track change +void CFuncTrackAuto :: UpdateAutoTargets( int toggleState ) +{ + CPathTrack *pTarget, *pNextTarget; + + if ( !m_trackTop || !m_trackBottom ) + return; + + if ( m_targetState == TS_AT_TOP ) + { + pTarget = m_trackTop->GetNext(); + pNextTarget = m_trackBottom->GetNext(); + } + else + { + pTarget = m_trackBottom->GetNext(); + pNextTarget = m_trackTop->GetNext(); + } + if ( pTarget ) + { + ClearBits( pTarget->pev->spawnflags, SF_PATH_DISABLED ); + if ( m_code == TRAIN_FOLLOWING && m_train && m_train->pev->speed == 0 ) + m_train->Use( this, this, USE_ON, 0 ); + } + + if ( pNextTarget ) + SetBits( pNextTarget->pev->spawnflags, SF_PATH_DISABLED ); + +} + + +void CFuncTrackAuto :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CPathTrack *pTarget; + + if ( !UseEnabled() ) + return; + + if ( m_toggle_state == TS_AT_TOP ) + pTarget = m_trackTop; + else if ( m_toggle_state == TS_AT_BOTTOM ) + pTarget = m_trackBottom; + else + pTarget = NULL; + + if ( FClassnameIs( pActivator->pev, "func_tracktrain" ) ) + { + m_code = EvaluateTrain( pTarget ); + // Safe to fire? + if ( m_code == TRAIN_FOLLOWING && m_toggle_state != m_targetState ) + { + DisableUse(); + if (m_toggle_state == TS_AT_TOP) + GoDown(); + else + GoUp(); + } + } + else + { + if ( pTarget ) + pTarget = pTarget->GetNext(); + if ( pTarget && m_train->m_ppath != pTarget && ShouldToggle( useType, m_targetState ) ) + { + if ( m_targetState == TS_AT_TOP ) + m_targetState = TS_AT_BOTTOM; + else + m_targetState = TS_AT_TOP; + } + + UpdateAutoTargets( m_targetState ); + } +} + + +// ---------------------------------------------------------- +// +// +// pev->speed is the travel speed +// pev->health is current health +// pev->max_health is the amount to reset to each time it starts + +#define FGUNTARGET_START_ON 0x0001 + +class CGunTarget : public CBaseMonster +{ +public: + void Spawn( void ); + void Activate( void ); + void EXPORT Next( void ); + void EXPORT Start( void ); + void EXPORT Wait( void ); + void Stop( void ); + + int BloodColor( void ) { return DONT_BLEED; } + int Classify( void ) { return CLASS_MACHINE; } + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + Vector BodyTarget( const Vector &posSrc ) { return pev->origin; } + + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + BOOL m_on; +}; + + +LINK_ENTITY_TO_CLASS( func_guntarget, CGunTarget ); + +TYPEDESCRIPTION CGunTarget::m_SaveData[] = +{ + DEFINE_FIELD( CGunTarget, m_on, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CGunTarget, CBaseMonster ); + + +void CGunTarget::Spawn( void ) +{ + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + if ( pev->speed == 0 ) + pev->speed = 100; + + // Don't take damage until "on" + pev->takedamage = DAMAGE_NO; + pev->flags |= FL_MONSTER; + + m_on = FALSE; + pev->max_health = pev->health; + + if ( pev->spawnflags & FGUNTARGET_START_ON ) + { + SetThink( &CGunTarget::Start ); + pev->nextthink = pev->ltime + 0.3; + } +} + + +void CGunTarget::Activate( void ) +{ + CBaseEntity *pTarg; + + // now find our next target + pTarg = GetNextTarget(); + if ( pTarg ) + { + m_hTargetEnt = pTarg; + UTIL_SetOrigin( pev, pTarg->pev->origin - (pev->mins + pev->maxs) * 0.5 ); + } +} + + +void CGunTarget::Start( void ) +{ + Use( this, this, USE_ON, 0 ); +} + + +void CGunTarget::Next( void ) +{ + SetThink( NULL ); + + m_hTargetEnt = GetNextTarget(); + CBaseEntity *pTarget = m_hTargetEnt; + + if ( !pTarget ) + { + Stop(); + return; + } + SetMoveDone( &CGunTarget::Wait ); + LinearMove( pTarget->pev->origin - (pev->mins + pev->maxs) * 0.5, pev->speed ); +} + + +void CGunTarget::Wait( void ) +{ + CBaseEntity *pTarget = m_hTargetEnt; + + if ( !pTarget ) + { + Stop(); + return; + } + + // Fire the pass target if there is one + if ( pTarget->pev->message ) + { + FireTargets( STRING(pTarget->pev->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( pTarget->pev->spawnflags, SF_CORNER_FIREONCE ) ) + pTarget->pev->message = 0; + } + + m_flWait = pTarget->GetDelay(); + + pev->target = pTarget->pev->target; + SetThink( &CGunTarget::Next ); + if (m_flWait != 0) + {// -1 wait will wait forever! + pev->nextthink = pev->ltime + m_flWait; + } + else + { + Next();// do it RIGHT now! + } +} + + +void CGunTarget::Stop( void ) +{ + pev->velocity = g_vecZero; + pev->nextthink = 0; + pev->takedamage = DAMAGE_NO; +} + + +int CGunTarget::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( pev->health > 0 ) + { + pev->health -= flDamage; + if ( pev->health <= 0 ) + { + pev->health = 0; + Stop(); + if ( pev->message ) + FireTargets( STRING(pev->message), this, this, USE_TOGGLE, 0 ); + } + } + return 0; +} + + +void CGunTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_on ) ) + return; + + if ( m_on ) + { + Stop(); + } + else + { + pev->takedamage = DAMAGE_AIM; + m_hTargetEnt = GetNextTarget(); + if ( m_hTargetEnt == 0 ) + return; + pev->health = pev->max_health; + Next(); + } +} + + + diff --git a/hlsdk/dmc/dlls/player.cpp b/hlsdk/dmc/dlls/player.cpp new file mode 100644 index 00000000..d17ff59c --- /dev/null +++ b/hlsdk/dmc/dlls/player.cpp @@ -0,0 +1,4880 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== player.cpp ======================================================== + + functions dealing with the player + +*/ + +#include "extdll.h" +#include "util.h" + +#include "cbase.h" +#include "player.h" +#include "trains.h" +#include "nodes.h" +#include "weapons.h" +#include "monsters.h" +#include "../engine/shake.h" +#include "decals.h" +#include "gamerules.h" +#include "quake_gun.h" + +// #define DUCKFIX + +extern bool g_bHaveMOTD; +#include "pm_shared.h" +#include "hltv.h" + +extern DLL_GLOBAL ULONG g_ulModelIndexPlayer; +extern DLL_GLOBAL BOOL g_fGameOver; +extern DLL_GLOBAL BOOL g_fDrawLines; +extern unsigned short g_sTeleport; +extern unsigned short g_usPowerUp; + +Vector g_vecTeleMins[ MAX_TELES ]; +Vector g_vecTeleMaxs[ MAX_TELES ]; +int g_iTeleNum; + +int gEvilImpulse101; +extern DLL_GLOBAL int g_iSkillLevel, gDisplayTitle; +BOOL gInitHUD = TRUE; + +extern void CopyToBodyQue(entvars_t* pev); +extern void respawn(entvars_t *pev, BOOL fCopyCorpse); +extern Vector VecBModelOrigin(entvars_t *pevBModel ); + +extern edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ); + +int MapTextureTypeStepType(char chTextureType); + +void AddAmmoNameToAmmoRegistry( const char *szAmmoname ); + +// the world node graph +extern CGraph WorldGraph; + +#define PLAYER_WALLJUMP_SPEED 300 // how fast we can spring off walls +#define PLAYER_LONGJUMP_SPEED 350 // how fast we longjump + +#define TRAIN_ACTIVE 0x80 +#define TRAIN_NEW 0xc0 +#define TRAIN_OFF 0x00 +#define TRAIN_NEUTRAL 0x01 +#define TRAIN_SLOW 0x02 +#define TRAIN_MEDIUM 0x03 +#define TRAIN_FAST 0x04 +#define TRAIN_BACK 0x05 + +#define FLASH_DRAIN_TIME 1.2 //100 units/3 minutes +#define FLASH_CHARGE_TIME 0.2 // 100 units/20 seconds (seconds per unit) + + +//#define PLAYER_MAX_SAFE_FALL_DIST 20// falling any farther than this many feet will inflict damage +//#define PLAYER_FATAL_FALL_DIST 60// 100% damage inflicted if player falls this many feet +//#define DAMAGE_PER_UNIT_FALLEN (float)( 100 ) / ( ( PLAYER_FATAL_FALL_DIST - PLAYER_MAX_SAFE_FALL_DIST ) * 12 ) +//#define MAX_SAFE_FALL_UNITS ( PLAYER_MAX_SAFE_FALL_DIST * 12 ) + +// Global Savedata for player +TYPEDESCRIPTION CBasePlayer::m_playerSaveData[] = +{ + DEFINE_FIELD( CBasePlayer, m_flFlashLightTime, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_iFlashBattery, FIELD_INTEGER ), + + DEFINE_FIELD( CBasePlayer, m_afButtonLast, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_afButtonPressed, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_afButtonReleased, FIELD_INTEGER ), + + DEFINE_ARRAY( CBasePlayer, m_rgItems, FIELD_INTEGER, MAX_ITEMS ), + DEFINE_FIELD( CBasePlayer, m_afPhysicsFlags, FIELD_INTEGER ), + + DEFINE_FIELD( CBasePlayer, m_flTimeStepSound, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flTimeWeaponIdle, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flSwimTime, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flDuckTime, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flWallJumpTime, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_flSuitUpdate, FIELD_TIME ), + DEFINE_ARRAY( CBasePlayer, m_rgSuitPlayList, FIELD_INTEGER, CSUITPLAYLIST ), + DEFINE_FIELD( CBasePlayer, m_iSuitPlayNext, FIELD_INTEGER ), + DEFINE_ARRAY( CBasePlayer, m_rgiSuitNoRepeat, FIELD_INTEGER, CSUITNOREPEAT ), + DEFINE_ARRAY( CBasePlayer, m_rgflSuitNoRepeatTime, FIELD_TIME, CSUITNOREPEAT ), + DEFINE_FIELD( CBasePlayer, m_lastDamageAmount, FIELD_INTEGER ), + + DEFINE_ARRAY( CBasePlayer, m_rgpPlayerItems, FIELD_CLASSPTR, MAX_ITEM_TYPES ), + DEFINE_FIELD( CBasePlayer, m_pActiveItem, FIELD_CLASSPTR ), + DEFINE_FIELD( CBasePlayer, m_pLastItem, FIELD_CLASSPTR ), + + DEFINE_ARRAY( CBasePlayer, m_rgAmmo, FIELD_INTEGER, MAX_AMMO_SLOTS ), + DEFINE_FIELD( CBasePlayer, m_idrowndmg, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_idrownrestored, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_tSneaking, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_iTrain, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_bitsHUDDamage, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_flFallVelocity, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayer, m_iTargetVolume, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iWeaponVolume, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iExtraSoundTypes, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iWeaponFlash, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_fLongJump, FIELD_BOOLEAN ), + DEFINE_FIELD( CBasePlayer, m_fInitHUD, FIELD_BOOLEAN ), + DEFINE_FIELD( CBasePlayer, m_tbdPrev, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_pTank, FIELD_EHANDLE ), + DEFINE_FIELD( CBasePlayer, m_iHideHUD, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iFOV, FIELD_INTEGER ), + + //DEFINE_FIELD( CBasePlayer, m_fDeadTime, FIELD_FLOAT ), // only used in multiplayer games + //DEFINE_FIELD( CBasePlayer, m_fGameHUDInitialized, FIELD_INTEGER ), // only used in multiplayer games + //DEFINE_FIELD( CBasePlayer, m_flStopExtraSoundTime, FIELD_TIME ), + //DEFINE_FIELD( CBasePlayer, m_fKnownItem, FIELD_INTEGER ), // reset to zero on load + //DEFINE_FIELD( CBasePlayer, m_iPlayerSound, FIELD_INTEGER ), // Don't restore, set in Precache() + //DEFINE_FIELD( CBasePlayer, m_pentSndLast, FIELD_EDICT ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_flSndRoomtype, FIELD_FLOAT ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_flSndRange, FIELD_FLOAT ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_fNewAmmo, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_flgeigerRange, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( CBasePlayer, m_flgeigerDelay, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( CBasePlayer, m_igeigerRangePrev, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( CBasePlayer, m_iStepLeft, FIELD_INTEGER ), // Don't need to restore + //DEFINE_ARRAY( CBasePlayer, m_szTextureName, FIELD_CHARACTER, CBTEXTURENAMEMAX ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_chTextureType, FIELD_CHARACTER ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_fNoPlayerSound, FIELD_BOOLEAN ), // Don't need to restore, debug + //DEFINE_FIELD( CBasePlayer, m_iUpdateTime, FIELD_INTEGER ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_iClientHealth, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_iClientBattery, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_iClientHideHUD, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_fWeapon, FIELD_BOOLEAN ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_nCustomSprayFrames, FIELD_INTEGER ), // Don't restore, depends on server message after spawning and only matters in multiplayer + //DEFINE_FIELD( CBasePlayer, m_vecAutoAim, FIELD_VECTOR ), // Don't save/restore - this is recomputed + //DEFINE_ARRAY( CBasePlayer, m_rgAmmoLast, FIELD_INTEGER, MAX_AMMO_SLOTS ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_fOnTarget, FIELD_BOOLEAN ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_nCustomSprayFrames, FIELD_INTEGER ), // Don't need to restore + +}; + + +int giPrecacheGrunt = 0; +int gmsgShake = 0; +int gmsgFade = 0; +int gmsgSelAmmo = 0; +int gmsgFlashlight = 0; +int gmsgFlashBattery = 0; +int gmsgResetHUD = 0; +int gmsgInitHUD = 0; +int gmsgShowGameTitle = 0; +int gmsgCurWeapon = 0; +int gmsgHealth = 0; +int gmsgDamage = 0; +int gmsgBattery = 0; +int gmsgTrain = 0; +int gmsgLogo = 0; +int gmsgWeaponList = 0; +int gmsgAmmoX = 0; +int gmsgHudText = 0; +int gmsgDeathMsg = 0; +int gmsgScoreInfo = 0; +int gmsgTeamInfo = 0; +int gmsgTeamScore = 0; +int gmsgGameMode = 0; +int gmsgMOTD = 0; +int gmsgServerName = 0; +int gmsgAmmoPickup = 0; +int gmsgWeapPickup = 0; +int gmsgItemPickup = 0; +int gmsgHideWeapon = 0; +int gmsgSetCurWeap = 0; +int gmsgSayText = 0; +int gmsgTextMsg = 0; +int gmsgSetFOV = 0; +int gmsgShowMenu = 0; +int gmsgGeigerRange = 0; + +// QUAKECLASSIC +int gmsgQItems = 0; +int gmsgStatusText = 0; +int gmsgStatusValue = 0; + +void LinkUserMessages( void ) +{ + // Already taken care of? + if ( gmsgSelAmmo ) + { + return; + } + + gmsgSelAmmo = REG_USER_MSG("SelAmmo", sizeof(SelAmmo)); + gmsgCurWeapon = REG_USER_MSG("CurWeapon", 3); + gmsgGeigerRange = REG_USER_MSG("Geiger", 1); + gmsgFlashlight = REG_USER_MSG("Flashlight", 2); + gmsgFlashBattery = REG_USER_MSG("FlashBat", 1); + gmsgHealth = REG_USER_MSG( "Health", 1 ); + gmsgDamage = REG_USER_MSG( "Damage", 12 ); + gmsgBattery = REG_USER_MSG( "Battery", 2); + gmsgTrain = REG_USER_MSG( "Train", 1); + gmsgHudText = REG_USER_MSG( "HudText", -1 ); + gmsgSayText = REG_USER_MSG( "SayText", -1 ); + gmsgTextMsg = REG_USER_MSG( "TextMsg", -1 ); + gmsgWeaponList = REG_USER_MSG("WeaponList", -1); + gmsgResetHUD = REG_USER_MSG("ResetHUD", 1); // called every respawn + gmsgInitHUD = REG_USER_MSG("InitHUD", -1 ); // called every time a new player joins the server + gmsgShowGameTitle = REG_USER_MSG("GameTitle", 1); + gmsgDeathMsg = REG_USER_MSG( "DeathMsg", -1 ); + gmsgScoreInfo = REG_USER_MSG( "ScoreInfo", 7 ); + gmsgTeamInfo = REG_USER_MSG( "TeamInfo", -1 ); // sets the name of a player's team + gmsgTeamScore = REG_USER_MSG( "TeamScore", -1 ); // sets the score of a team on the scoreboard + gmsgGameMode = REG_USER_MSG( "GameMode", 1 ); + gmsgMOTD = REG_USER_MSG( "MOTD", -1 ); + gmsgServerName = REG_USER_MSG( "ServerName", -1 ); + gmsgAmmoPickup = REG_USER_MSG( "AmmoPickup", 2 ); + gmsgWeapPickup = REG_USER_MSG( "WeapPickup", 1 ); + gmsgItemPickup = REG_USER_MSG( "ItemPickup", -1 ); + gmsgHideWeapon = REG_USER_MSG( "HideWeapon", 1 ); + gmsgSetFOV = REG_USER_MSG( "SetFOV", 1 ); + gmsgShowMenu = REG_USER_MSG( "ShowMenu", -1 ); + gmsgShake = REG_USER_MSG("ScreenShake", sizeof(ScreenShake)); + gmsgFade = REG_USER_MSG("ScreenFade", sizeof(ScreenFade)); + gmsgAmmoX = REG_USER_MSG("AmmoX", 2); + + gmsgQItems = REG_USER_MSG( "QItems", 4 ); + + gmsgStatusText = REG_USER_MSG("StatusText", -1); + gmsgStatusValue = REG_USER_MSG("StatusValue", 3); +} + +LINK_ENTITY_TO_CLASS( player, CBasePlayer ); + + +// QUAKECLASSIC: Play pain sounds +void CBasePlayer :: Pain( CBaseEntity *pAttacker ) +{ + if (pev->health < 0) + return; + + if ( FClassnameIs( pAttacker->pev, "teledeath" ) ) + { + EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/teledth1.wav", 1, ATTN_NORM ); + return; + } + + // water pain sounds + if (pev->watertype == CONTENT_WATER && pev->waterlevel == 3) + { + UTIL_Bubbles( pev->mins, pev->maxs, 3 ); + if ( RANDOM_FLOAT(0,1) > 0.5 ) + EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/drown1.wav", 1, ATTN_NORM ); + else + EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/drown2.wav", 1, ATTN_NORM ); + return; + } + + // slime pain sounds + if (pev->watertype == CONTENT_SLIME) + { + if ( RANDOM_FLOAT(0,1) > 0.5 ) + EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/lburn1.wav", 1, ATTN_NORM ); + else + EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/lburn2.wav", 1, ATTN_NORM ); + return; + } + + // lava pain sounds + if (pev->watertype == CONTENT_LAVA) + { + if ( RANDOM_FLOAT(0,1) > 0.5 ) + EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/lburn1.wav", 1, ATTN_NORM ); + else + EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/lburn2.wav", 1, ATTN_NORM ); + return; + } + + // don't make multiple pain sounds right after each other + if (m_flPainSoundFinished > gpGlobals->time) + { + m_bAxHitMe = 0; + return; + } + m_flPainSoundFinished = gpGlobals->time + 0.5; + + // ax pain sound + if (m_bAxHitMe == 1) + { + m_bAxHitMe = 0; + EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/axhitbod.wav", 1, ATTN_NORM ); + return; + } + + // play random sound + switch ( (int)(RANDOM_FLOAT(0,1) * 6) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/pain1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/pain2.wav", 1, ATTN_NORM ); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/pain3.wav", 1, ATTN_NORM ); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/pain4.wav", 1, ATTN_NORM ); break; + case 4: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/pain5.wav", 1, ATTN_NORM ); break; + case 5: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/pain6.wav", 1, ATTN_NORM ); break; + } +} + +// QUAKECLASSIC: Play a random death sound +void CBasePlayer :: DeathSound( void ) +{ + // water death sounds + if (pev->waterlevel == 3) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/h2odeath.wav", 1, ATTN_NONE); + return; + } + + // play random sound + switch ( (int)(RANDOM_FLOAT(0,1) * 5) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/death1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/death2.wav", 1, ATTN_NORM ); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/death3.wav", 1, ATTN_NORM ); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/death4.wav", 1, ATTN_NORM ); break; + case 4: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/death5.wav", 1, ATTN_NORM ); break; + } +} + +/* + * + */ +Vector VecVelocityForDamage(float flDamage) +{ + Vector vec(RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + + if (flDamage > -50) + vec = vec * 0.7; + else if (flDamage > -200) + vec = vec * 2; + else + vec = vec * 10; + + return vec; +} + +#if 0 /* +static void ThrowGib(entvars_t *pev, char *szGibModel, float flDamage) +{ + edict_t *pentNew = CREATE_ENTITY(); + entvars_t *pevNew = VARS(pentNew); + + pevNew->origin = pev->origin; + SET_MODEL(ENT(pevNew), szGibModel); + UTIL_SetSize(pevNew, g_vecZero, g_vecZero); + + pevNew->velocity = VecVelocityForDamage(flDamage); + pevNew->movetype = MOVETYPE_BOUNCE; + pevNew->solid = SOLID_NOT; + pevNew->avelocity.x = RANDOM_FLOAT(0,600); + pevNew->avelocity.y = RANDOM_FLOAT(0,600); + pevNew->avelocity.z = RANDOM_FLOAT(0,600); + CHANGE_METHOD(ENT(pevNew), em_think, SUB_Remove); + pevNew->ltime = gpGlobals->time; + pevNew->nextthink = gpGlobals->time + RANDOM_FLOAT(10,20); + pevNew->frame = 0; + pevNew->flags = 0; +} + + +static void ThrowHead(entvars_t *pev, char *szGibModel, floatflDamage) +{ + SET_MODEL(ENT(pev), szGibModel); + pev->frame = 0; + pev->nextthink = -1; + pev->movetype = MOVETYPE_BOUNCE; + pev->takedamage = DAMAGE_NO; + pev->solid = SOLID_NOT; + pev->view_ofs = Vector(0,0,8); + UTIL_SetSize(pev, Vector(-16,-16,0), Vector(16,16,56)); + pev->velocity = VecVelocityForDamage(flDamage); + pev->avelocity = RANDOM_FLOAT(-1,1) * Vector(0,600,0); + pev->origin.z -= 24; + ClearBits(pev->flags, FL_ONGROUND); +} + + +*/ +#endif + +int TrainSpeed(int iSpeed, int iMax) +{ + float fSpeed, fMax; + int iRet = 0; + + fMax = (float)iMax; + fSpeed = iSpeed; + + fSpeed = fSpeed/fMax; + + if (iSpeed < 0) + iRet = TRAIN_BACK; + else if (iSpeed == 0) + iRet = TRAIN_NEUTRAL; + else if (fSpeed < 0.33) + iRet = TRAIN_SLOW; + else if (fSpeed < 0.66) + iRet = TRAIN_MEDIUM; + else + iRet = TRAIN_FAST; + + return iRet; +} + + +// override takehealth +// bitsDamageType indicates type of damage healed. + +int CBasePlayer :: TakeHealth( float flHealth, int bitsDamageType ) +{ + // QUAKECLASSIC + if (pev->health <= 0) + return 0; + if ( !(bitsDamageType & DMG_IGNORE_MAXHEALTH) && (pev->health >= pev->max_health) ) + return 0; + int iHealAmount = static_cast(ceil(flHealth)); + + pev->health += iHealAmount; + if ( !(bitsDamageType & DMG_IGNORE_MAXHEALTH) && (pev->health >= pev->max_health) ) + pev->health = pev->max_health; + + if (pev->health > 250) + pev->health = 250; + return 1; +} + +Vector CBasePlayer :: GetGunPosition( ) +{ +// UTIL_MakeVectors(pev->v_angle); +// m_HackedGunPos = pev->view_ofs; + Vector origin; + + origin = pev->origin + pev->view_ofs; + return origin; +} + +//========================================================= +// TraceAttack +//========================================================= +void CBasePlayer :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if ( pev->takedamage ) + { + SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage);// a little surface blood. + TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + } +} + +void CBasePlayer::RemoveAllItems( BOOL removeSuit ) +{ + if (m_pActiveItem) + { + ResetAutoaim( ); + m_pActiveItem->Holster( ); + m_pActiveItem = NULL; + } + + m_pLastItem = NULL; + + int i; + CBasePlayerItem *pPendingItem; + for (i = 0; i < MAX_ITEM_TYPES; i++) + { + m_pActiveItem = m_rgpPlayerItems[i]; + while (m_pActiveItem) + { + pPendingItem = m_pActiveItem->m_pNext; + m_pActiveItem->Drop( ); + m_pActiveItem = pPendingItem; + } + m_rgpPlayerItems[i] = NULL; + } + m_pActiveItem = NULL; + + pev->viewmodel = 0; + pev->weaponmodel = 0; + + if ( removeSuit ) + pev->weapons = 0; + else + pev->weapons &= ~WEAPON_ALLWEAPONS; + + for ( i = 0; i < MAX_AMMO_SLOTS;i++) + m_rgAmmo[i] = 0; + + UpdateClientData(); + // send Selected Weapon Message to our client + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pev ); + WRITE_BYTE(0); + WRITE_BYTE(0); + WRITE_BYTE(0); + MESSAGE_END(); +} + +/* + * GLOBALS ASSUMED SET: g_ulModelIndexPlayer + * + * ENTITY_METHOD(PlayerDie) + */ +entvars_t *g_pevLastInflictor; // Set in combat.cpp. Used to pass the damage inflictor for death messages. + // Better solution: Add as parameter to all Killed() functions. + +void CBasePlayer::Killed( entvars_t *pevAttacker, int iGib ) +{ + g_pGameRules->PlayerKilled( this, pevAttacker, g_pevLastInflictor ); + + if ( m_pTank != 0 ) + { + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + } + + + //Remove all powerups and powerup timers + m_flInvincibleFinished = m_flRadsuitFinished = m_flInvisibleFinished = m_flSuperDamageFinished = 0.0; + PowerUpThink(); + + SetAnimation( PLAYER_DIE ); + + m_iRespawnFrames = 0; + + pev->modelindex = g_ulModelIndexPlayer; // don't use eyes + + pev->deadflag = DEAD_DYING; + pev->movetype = MOVETYPE_TOSS; + ClearBits(pev->flags, FL_ONGROUND); + + if ( m_iQuakeWeapon == IT_LIGHTNING ) + { + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usLightning, 0, (float *)&pev->origin, (float *)&pev->angles, 0.0, 0.0, 0, 1, 0, 0 ); + + if ( m_pActiveItem ) + ((CQuakeGun*)m_pActiveItem)->DestroyEffect(); + } + + if (pev->velocity.z < 10) + pev->velocity.z += RANDOM_FLOAT(0,300); + + // clear out the suit message cache so we don't keep chattering + SetSuitUpdate(NULL, FALSE, 0); + + // send "health" update message to zero + m_iClientHealth = 0; + MESSAGE_BEGIN( MSG_ONE, gmsgHealth, NULL, pev ); + WRITE_BYTE( m_iClientHealth ); + MESSAGE_END(); + + // Tell Ammo Hud that the player is dead + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pev ); + WRITE_BYTE(0); + WRITE_BYTE(0XFF); + WRITE_BYTE(0xFF); + MESSAGE_END(); + + + // UNDONE: Put this in, but add FFADE_PERMANENT and make fade time 8.8 instead of 4.12 + // UTIL_ScreenFade( edict(), Vector(128,0,0), 6, 15, 255, FFADE_OUT | FFADE_MODULATE ); + + //Quake Player always gibs + if ( pev->health < -40 ) + { + pev->solid = SOLID_NOT; + + GibMonster(); // This clears pev->model + pev->effects |= EF_NODRAW; + return; + } + + DeathSound(); + + pev->angles.x = 0; + pev->angles.z = 0; + + SetThink(&CBasePlayer::PlayerDeathThink); + pev->nextthink = gpGlobals->time + 0.1; +} + + +// Set the activity based on an event or current state +void CBasePlayer::SetAnimation( PLAYER_ANIM playerAnim ) +{ + int animDesired; + float speed; + char szAnim[64]; + + speed = pev->velocity.Length2D(); + + if (pev->flags & FL_FROZEN) + { + speed = 0; + playerAnim = PLAYER_IDLE; + } + + switch (playerAnim) + { + case PLAYER_JUMP: + m_IdealActivity = ACT_HOP; + break; + + case PLAYER_SUPERJUMP: + m_IdealActivity = ACT_LEAP; + break; + + case PLAYER_DIE: + m_IdealActivity = ACT_DIESIMPLE; + m_IdealActivity = GetDeathActivity( ); + break; + + case PLAYER_ATTACK1: + switch( m_Activity ) + { + case ACT_HOVER: + case ACT_SWIM: + case ACT_HOP: + case ACT_LEAP: + case ACT_DIESIMPLE: + m_IdealActivity = m_Activity; + break; + default: + m_IdealActivity = ACT_RANGE_ATTACK1; + break; + } + break; + case PLAYER_IDLE: + case PLAYER_WALK: + if ( !FBitSet( pev->flags, FL_ONGROUND ) && (m_Activity == ACT_HOP || m_Activity == ACT_LEAP) ) // Still jumping + { + m_IdealActivity = m_Activity; + } + else if ( pev->waterlevel > 1 ) + { + if ( speed == 0 ) + m_IdealActivity = ACT_HOVER; + else + m_IdealActivity = ACT_SWIM; + } + else + { + m_IdealActivity = ACT_WALK; + } + break; + } + + switch (m_IdealActivity) + { + case ACT_HOVER: + case ACT_LEAP: + case ACT_SWIM: + case ACT_HOP: + case ACT_DIESIMPLE: + default: + if ( m_Activity == m_IdealActivity) + return; + m_Activity = m_IdealActivity; + + animDesired = LookupActivity( m_Activity ); + // Already using the desired animation? + if (pev->sequence == animDesired) + return; + + pev->gaitsequence = 0; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + + case ACT_RANGE_ATTACK1: + if ( FBitSet( pev->flags, FL_DUCKING ) ) // crouching + strcpy( szAnim, "crouch_shoot_" ); + else + strcpy( szAnim, "ref_shoot_" ); + strcat( szAnim, m_szAnimExtention ); + animDesired = LookupSequence( szAnim ); + if (animDesired == -1) + animDesired = 0; + + if ( pev->sequence != animDesired || !m_fSequenceLoops ) + { + pev->frame = 0; + } + + if (!m_fSequenceLoops) + { + pev->effects |= EF_NOINTERP; + } + + m_Activity = m_IdealActivity; + + pev->sequence = animDesired; + ResetSequenceInfo( ); + break; + + case ACT_WALK: + if (m_Activity != ACT_RANGE_ATTACK1 || m_fSequenceFinished) + { + if ( FBitSet( pev->flags, FL_DUCKING ) ) // crouching + strcpy( szAnim, "crouch_aim_" ); + else + strcpy( szAnim, "ref_aim_" ); + strcat( szAnim, m_szAnimExtention ); + animDesired = LookupSequence( szAnim ); + if (animDesired == -1) + animDesired = 0; + m_Activity = ACT_WALK; + } + else + { + animDesired = pev->sequence; + } + } + + if ( FBitSet( pev->flags, FL_DUCKING ) ) + { + if ( speed == 0) + { + pev->gaitsequence = LookupActivity( ACT_CROUCHIDLE ); + // pev->gaitsequence = LookupActivity( ACT_CROUCH ); + } + else + { + pev->gaitsequence = LookupActivity( ACT_CROUCH ); + } + } + else if ( speed > 220 ) + { + pev->gaitsequence = LookupActivity( ACT_RUN ); + } + else if (speed > 0) + { + pev->gaitsequence = LookupActivity( ACT_WALK ); + } + else + { + // pev->gaitsequence = LookupActivity( ACT_WALK ); + pev->gaitsequence = LookupSequence( "deep_idle" ); + } + + + // Already using the desired animation? + if (pev->sequence == animDesired) + return; + + //ALERT( at_console, "Set animation to %d\n", animDesired ); + // Reset to first frame of desired animation + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); +} + + +/* +=========== +WaterMove +============ +*/ +#define AIRTIME 12 // lung full of air lasts this many seconds + +void CBasePlayer::WaterMove() +{ + int air; + + if (pev->movetype == MOVETYPE_NOCLIP) + return; + + if (pev->health < 0) + return; + + // waterlevel 0 - not in water + // waterlevel 1 - feet in water + // waterlevel 2 - waist in water + // waterlevel 3 - head in water + + if (pev->waterlevel != 3) + { + // not underwater + + // play 'up for air' sound + if (pev->air_finished < gpGlobals->time) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_wade1.wav", 1, ATTN_NORM); + else if (pev->air_finished < gpGlobals->time + 9) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_wade2.wav", 1, ATTN_NORM); + + pev->air_finished = gpGlobals->time + AIRTIME; + pev->dmg = 2; + + // if we took drowning damage, give it back slowly + if (m_idrowndmg > m_idrownrestored) + { + // set drowning damage bit. hack - dmg_drownrecover actually + // makes the time based damage code 'give back' health over time. + // make sure counter is cleared so we start count correctly. + + // NOTE: this actually causes the count to continue restarting + // until all drowning damage is healed. + + m_bitsDamageType &= ~DMG_DROWN; + + /* m_bitsDamageType |= DMG_DROWNRECOVER; + + m_rgbTimeBasedDamage[itbd_DrownRecover] = 0;*/ + } + + } + else + { // fully under water + // stop restoring damage while underwater + m_bitsDamageType &= ~DMG_DROWNRECOVER; + m_rgbTimeBasedDamage[itbd_DrownRecover] = 0; + + if (pev->air_finished < gpGlobals->time) // drown! + { + if (pev->pain_finished < gpGlobals->time) + { + // take drowning damage + pev->dmg += 1; + if (pev->dmg > 5) + pev->dmg = 5; + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), pev->dmg, DMG_DROWN); + pev->pain_finished = gpGlobals->time + 1; + + // track drowning damage, give it back when + // player finally takes a breath + + m_idrowndmg += static_cast(pev->dmg); + } + } + else + { + m_bitsDamageType &= ~DMG_DROWN; + } + } + + if (!pev->waterlevel) + { + if (FBitSet(pev->flags, FL_INWATER)) + { +#if 0 + // play leave water sound + switch (RANDOM_LONG(0,3)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM); break; + } +#endif + + ClearBits(pev->flags, FL_INWATER); + } + return; + } + + // make bubbles + + air = (int)(pev->air_finished - gpGlobals->time); + if (!RANDOM_LONG(0,0x1f) && RANDOM_LONG(0,AIRTIME-1) >= air) + { + switch (RANDOM_LONG(0,3)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim1.wav", 0.8, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim2.wav", 0.8, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim3.wav", 0.8, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim4.wav", 0.8, ATTN_NORM); break; + } + } + + if (pev->watertype == CONTENT_LAVA) // do damage + { + if (pev->dmgtime < gpGlobals->time) + { + if ( m_iQuakeItems & IT_SUIT ) + pev->dmgtime = gpGlobals->time + 1.0; + else + pev->dmgtime = gpGlobals->time + 0.2; + + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), 10 * pev->waterlevel, DMG_BURN ); + } + } + else if (pev->watertype == CONTENT_SLIME) // do damage + { + if (pev->dmgtime < gpGlobals->time) + { + pev->dmgtime = gpGlobals->time + 1.0; + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), 4 * pev->waterlevel, DMG_ACID ); + } + } + + if (!FBitSet(pev->flags, FL_INWATER)) + { +#if 0 + // player enter water sound + if (pev->watertype == CONTENT_WATER) + { + switch (RANDOM_LONG(0,3)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM); break; + } + } +#endif + + SetBits(pev->flags, FL_INWATER); + pev->dmgtime = 0; + } + + if (!FBitSet(pev->flags, FL_WATERJUMP)) + pev->velocity = pev->velocity - 0.8 * pev->waterlevel * gpGlobals->frametime * pev->velocity; +} + + +// TRUE if the player is attached to a ladder +BOOL CBasePlayer::IsOnLadder( void ) +{ + return (pev->movetype == MOVETYPE_FLY); +} + +void CBasePlayer::PlayerDeathThink(void) +{ + float flForward; + + if (FBitSet(pev->flags, FL_ONGROUND)) + { + flForward = pev->velocity.Length() - 20; + if (flForward <= 0) + pev->velocity = g_vecZero; + else + pev->velocity = flForward * pev->velocity.Normalize(); + } + + if ( m_iQuakeWeapon ) + { + DropBackpack(); + } + + if (pev->modelindex && (!m_fSequenceFinished) && (pev->deadflag == DEAD_DYING)) + { + StudioFrameAdvance( ); + + m_iRespawnFrames++; // Note, these aren't necessarily real "frames", so behavior is dependent on # of client movement commands + if ( m_iRespawnFrames < 120 ) // Animations should be no longer than this + return; + } + + if (m_fSequenceFinished == 0) + { + StudioFrameAdvance(); + return; + } + + if (pev->deadflag == DEAD_DYING) + pev->deadflag = DEAD_DEAD; + + StopAnimation(); + + + pev->effects |= EF_NOINTERP; + pev->framerate = 0.0; + + BOOL fAnyButtonDown = (pev->button & ~IN_SCORE ); + + // wait for all buttons released + if (pev->deadflag == DEAD_DEAD) + { + if (fAnyButtonDown) + return; + + if ( g_pGameRules->FPlayerCanRespawn( this ) ) + { + m_fDeadTime = gpGlobals->time; + pev->deadflag = DEAD_RESPAWNABLE; + } + + return; + } + +// if the player has been dead for one second longer than allowed by forcerespawn, +// forcerespawn isn't on. Send the player off to an intermission camera until they +// choose to respawn. +/* if ( g_pGameRules->IsMultiplayer() && ( gpGlobals->time > (m_fDeadTime + 6) ) && !(m_afPhysicsFlags & PFLAG_OBSERVER) ) + { + // go to dead camera. + StartDeathCam(); + }*/ + +// wait for any button down, or mp_forcerespawn is set and the respawn time is up + if (!fAnyButtonDown + && !( g_pGameRules->IsMultiplayer() && CVAR_GET_FLOAT("mp_forcerespawn") > 0 && (gpGlobals->time > (m_fDeadTime + 5))) ) + return; + + pev->button = 0; + m_iRespawnFrames = 0; + + + + //ALERT(at_console, "Respawn\n"); + + respawn(pev, !(m_afPhysicsFlags & PFLAG_OBSERVER) );// don't copy a corpse if we're in deathcam. + pev->nextthink = -1; +} + +//========================================================= +// StartDeathCam - find an intermission spot and send the +// player off into observer mode +//========================================================= +void CBasePlayer::StartDeathCam( void ) +{ + edict_t *pSpot, *pNewSpot; + int iRand; + + if ( pev->view_ofs == g_vecZero ) + { + // don't accept subsequent attempts to StartDeathCam() + return; + } + + pSpot = FIND_ENTITY_BY_CLASSNAME( NULL, "info_intermission"); + + if ( !FNullEnt( pSpot ) ) + { + // at least one intermission spot in the world. + iRand = RANDOM_LONG( 0, 3 ); + + while ( iRand > 0 ) + { + pNewSpot = FIND_ENTITY_BY_CLASSNAME( pSpot, "info_intermission"); + + if ( pNewSpot ) + { + pSpot = pNewSpot; + } + + iRand--; + } + + CopyToBodyQue( pev ); + StartObserver( pSpot->v.origin, pSpot->v.v_angle ); + } + else + { + // no intermission spot. Push them up in the air, looking down at their corpse + TraceResult tr; + CopyToBodyQue( pev ); + UTIL_TraceLine( pev->origin, pev->origin + Vector( 0, 0, 128 ), ignore_monsters, edict(), &tr ); + StartObserver( tr.vecEndPos, UTIL_VecToAngles( tr.vecEndPos - pev->origin ) ); + return; + } +} + +void CBasePlayer::StartObserver( Vector vecPosition, Vector vecViewAngle ) +{ + m_iHideHUD |= (HIDEHUD_HEALTH | HIDEHUD_WEAPONS); + m_afPhysicsFlags |= PFLAG_OBSERVER; + + pev->effects = EF_NODRAW; + pev->view_ofs = g_vecZero; + pev->angles = pev->v_angle = vecViewAngle; + pev->fixangle = TRUE; + pev->solid = SOLID_NOT; + pev->takedamage = DAMAGE_NO; + pev->movetype = MOVETYPE_NONE; + UTIL_SetOrigin( pev, vecPosition ); + + ClearBits( m_afPhysicsFlags, PFLAG_DUCKING ); + ClearBits( pev->flags, FL_DUCKING ); + // pev->flags = FL_CLIENT | FL_SPECTATOR; // Should we set Spectator flag? Or is it reserver for people connecting with spectator 1? + pev->deadflag = DEAD_RESPAWNABLE; + + // Tell the physics code that this player's now in observer mode + Observer_SetMode(OBS_CHASE_LOCKED); + m_flNextObserverInput = 0; +} + +// +// PlayerUse - handles USE keypress +// +#define PLAYER_SEARCH_RADIUS (float)64 + +void CBasePlayer::PlayerUse ( void ) +{ + // Was use pressed or released? + if ( ! ((pev->button | m_afButtonPressed | m_afButtonReleased) & IN_USE) ) + return; + + // Hit Use on a train? + if ( m_afButtonPressed & IN_USE ) + { + if ( m_pTank != 0 ) + { + // Stop controlling the tank + // TODO: Send HUD Update + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + return; + } + else + { + if ( m_afPhysicsFlags & PFLAG_ONTRAIN ) + { + m_afPhysicsFlags &= ~PFLAG_ONTRAIN; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + else + { // Start controlling the train! + CBaseEntity *pTrain = CBaseEntity::Instance( pev->groundentity ); + + if ( pTrain && !(pev->button & IN_JUMP) && FBitSet(pev->flags, FL_ONGROUND) && (pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) && pTrain->OnControls(pev) ) + { + m_afPhysicsFlags |= PFLAG_ONTRAIN; + m_iTrain = TrainSpeed(static_cast(pTrain->pev->speed), pTrain->pev->impulse); + m_iTrain |= TRAIN_NEW; + EMIT_SOUND( ENT(pev), CHAN_ITEM, "plats/train_use1.wav", 0.8, ATTN_NORM); + return; + } + } + } + } + + CBaseEntity *pObject = NULL; + CBaseEntity *pClosest = NULL; + Vector vecLOS; + float flMaxDot = VIEW_FIELD_NARROW; + float flDot; + + UTIL_MakeVectors ( pev->v_angle );// so we know which way we are facing + + while ((pObject = UTIL_FindEntityInSphere( pObject, pev->origin, PLAYER_SEARCH_RADIUS )) != NULL) + { + + if (pObject->ObjectCaps() & (FCAP_IMPULSE_USE | FCAP_CONTINUOUS_USE | FCAP_ONOFF_USE)) + { + // !!!PERFORMANCE- should this check be done on a per case basis AFTER we've determined that + // this object is actually usable? This dot is being done for every object within PLAYER_SEARCH_RADIUS + // when player hits the use key. How many objects can be in that area, anyway? (sjb) + vecLOS = (VecBModelOrigin( pObject->pev ) - (pev->origin + pev->view_ofs)); + + // This essentially moves the origin of the target to the corner nearest the player to test to see + // if it's "hull" is in the view cone + vecLOS = UTIL_ClampVectorToBox( vecLOS, pObject->pev->size * 0.5 ); + + flDot = DotProduct (vecLOS , gpGlobals->v_forward); + if (flDot > flMaxDot ) + {// only if the item is in front of the user + pClosest = pObject; + flMaxDot = flDot; +// ALERT( at_console, "%s : %f\n", STRING( pObject->pev->classname ), flDot ); + } +// ALERT( at_console, "%s : %f\n", STRING( pObject->pev->classname ), flDot ); + } + } + pObject = pClosest; + + // Found an object + if (pObject ) + { + //!!!UNDONE: traceline here to prevent USEing buttons through walls + int caps = pObject->ObjectCaps(); + + if ( m_afButtonPressed & IN_USE ) + EMIT_SOUND( ENT(pev), CHAN_ITEM, "common/wpn_select.wav", 0.4, ATTN_NORM); + + if ( ( (pev->button & IN_USE) && (caps & FCAP_CONTINUOUS_USE) ) || + ( (m_afButtonPressed & IN_USE) && (caps & (FCAP_IMPULSE_USE|FCAP_ONOFF_USE)) ) ) + { + if ( caps & FCAP_CONTINUOUS_USE ) + m_afPhysicsFlags |= PFLAG_USING; + + pObject->Use( this, this, USE_SET, 1 ); + } + // UNDONE: Send different USE codes for ON/OFF. Cache last ONOFF_USE object to send 'off' if you turn away + else if ( (m_afButtonReleased & IN_USE) && (pObject->ObjectCaps() & FCAP_ONOFF_USE) ) // BUGBUG This is an "off" use + { + pObject->Use( this, this, USE_SET, 0 ); + } + } + else + { + if ( m_afButtonPressed & IN_USE ) + EMIT_SOUND( ENT(pev), CHAN_ITEM, "common/wpn_denyselect.wav", 0.4, ATTN_NORM); + } +} + + + +void CBasePlayer::Jump() +{ + Vector vecWallCheckDir;// direction we're tracing a line to find a wall when walljumping + Vector vecAdjustedVelocity; + Vector vecSpot; + TraceResult tr; + + if (FBitSet(pev->flags, FL_WATERJUMP)) + return; + + if (pev->waterlevel >= 2) + { + return; + } + + // jump velocity is sqrt( height * gravity * 2) + + // If this isn't the first frame pressing the jump button, break out. + if ( !FBitSet( m_afButtonPressed, IN_JUMP ) ) + return; // don't pogo stick + + if ( !(pev->flags & FL_ONGROUND) || !pev->groundentity ) + { + return; + } + +// many features in this function use v_forward, so makevectors now. + UTIL_MakeVectors (pev->angles); + + SetAnimation( PLAYER_JUMP ); + + if ( FBitSet(pev->flags, FL_DUCKING ) || FBitSet(m_afPhysicsFlags, PFLAG_DUCKING) ) + { + if ( m_fLongJump && (pev->button & IN_DUCK) && gpGlobals->time - m_flDuckTime < 1 && pev->velocity.Length() > 50 ) + {// If jump pressed within a second of duck while moving, long jump! + SetAnimation( PLAYER_SUPERJUMP ); + } + } + + // If you're standing on a conveyor, add it's velocity to yours (for momentum) + entvars_t *pevGround = VARS(pev->groundentity); + if ( pevGround && (pevGround->flags & FL_CONVEYOR) ) + { + pev->velocity = pev->velocity + pev->basevelocity; + } +} + + + +// This is a glorious hack to find free space when you've crouched into some solid space +// Our crouching collisions do not work correctly for some reason and this is easier +// than fixing the problem :( +void FixPlayerCrouchStuck( edict_t *pPlayer ) +{ + TraceResult trace; + + // Move up as many as 18 pixels if the player is stuck. + for ( int i = 0; i < 18; i++ ) + { + UTIL_TraceHull( pPlayer->v.origin, pPlayer->v.origin, dont_ignore_monsters, head_hull, pPlayer, &trace ); + if ( trace.fStartSolid ) + pPlayer->v.origin.z ++; + else + break; + } +} + +void CBasePlayer::Duck( ) +{ + if (pev->button & IN_DUCK) + { + SetAnimation( PLAYER_WALK ); + } +} + +// +// ID's player as such. +// +int CBasePlayer::Classify ( void ) +{ + return CLASS_PLAYER; +} + + +void CBasePlayer::AddPoints( int score, BOOL bAllowNegativeScore ) +{ + // Positive score always adds + if ( score < 0 ) + { + if ( !bAllowNegativeScore ) + { + if ( pev->frags < 0 ) // Can't go more negative + return; + + if ( -score > pev->frags ) // Will this go negative? + { + score = static_cast(-pev->frags); // Sum will be 0 + } + } + } + + pev->frags += score; + + MESSAGE_BEGIN( MSG_ALL, gmsgScoreInfo ); + WRITE_BYTE( ENTINDEX(edict()) ); + WRITE_SHORT( static_cast(pev->frags) ); + WRITE_SHORT( m_iDeaths ); + WRITE_SHORT( pev->team ); + MESSAGE_END(); +} + + +void CBasePlayer::AddPointsToTeam( int score, BOOL bAllowNegativeScore ) +{ + int index = entindex(); + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + if ( pPlayer && i != index ) + { + if ( g_pGameRules->PlayerRelationship( this, pPlayer ) == GR_TEAMMATE ) + { + pPlayer->AddPoints( score, bAllowNegativeScore ); + } + } + } +} + +#if 0 +void CBasePlayer::CheckWeapon(void) +{ + // play a weapon idle anim if it's time! + if ( gpGlobals->time > m_flTimeWeaponIdle ) + { + WeaponIdle ( ); + } +} +#endif + + +// play a footstep if it's time - this will eventually be frame-based. not time based. + +#define STEP_CONCRETE 0 // default step sound +#define STEP_METAL 1 // metal floor +#define STEP_DIRT 2 // dirt, sand, rock +#define STEP_VENT 3 // ventillation duct +#define STEP_GRATE 4 // metal grating +#define STEP_TILE 5 // floor tiles +#define STEP_SLOSH 6 // shallow liquid puddle +#define STEP_WADE 7 // wading in liquid +#define STEP_LADDER 8 // climbing ladder + +// Play correct step sound for material we're on or in + +void CBasePlayer :: PlayStepSound(int step, float fvol) +{ + return; + + static int iSkipStep = 0; + + if ( !g_pGameRules->PlayFootstepSounds( this, fvol ) ) + return; + + // irand - 0,1 for right foot, 2,3 for left foot + // used to alternate left and right foot + int irand = RANDOM_LONG(0,1) + (m_iStepLeft * 2); + + m_iStepLeft = !m_iStepLeft; + + switch (step) + { + default: + case STEP_CONCRETE: + switch (irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_METAL: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_metal1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_metal3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_metal2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_metal4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_DIRT: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_dirt1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_dirt3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_dirt2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_dirt4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_VENT: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_duct1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_duct3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_duct2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_duct4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_GRATE: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_grate1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_grate3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_grate2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_grate4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_TILE: + if (!RANDOM_LONG(0,4)) + irand = 4; + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile4.wav", fvol, ATTN_NORM); break; + case 4: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile5.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_SLOSH: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_slosh1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_slosh3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_slosh2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_slosh4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_WADE: + if ( iSkipStep == 0 ) + { + iSkipStep++; + break; + } + + if ( iSkipStep++ == 3 ) + { + iSkipStep = 0; + } + + switch (irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_wade1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_wade2.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_wade3.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_wade4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_LADDER: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_ladder1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_ladder3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_ladder2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_ladder4.wav", fvol, ATTN_NORM); break; + } + break; + } +} + +// Simple mapping from texture type character to step type + +int MapTextureTypeStepType(char chTextureType) +{ +switch (chTextureType) + { + default: + case CHAR_TEX_CONCRETE: return STEP_CONCRETE; + case CHAR_TEX_METAL: return STEP_METAL; + case CHAR_TEX_DIRT: return STEP_DIRT; + case CHAR_TEX_VENT: return STEP_VENT; + case CHAR_TEX_GRATE: return STEP_GRATE; + case CHAR_TEX_TILE: return STEP_TILE; + case CHAR_TEX_SLOSH: return STEP_SLOSH; + } +} + +// Play left or right footstep based on material player is on or in + +void CBasePlayer :: UpdateStepSound( void ) +{ + int fWalking; + float fvol; + char szbuffer[64]; + const char *pTextureName; + Vector start, end; + float rgfl1[3]; + float rgfl2[3]; + Vector knee; + Vector feet; + Vector center; + float height; + float speed; + float velrun; + float velwalk; + float flduck; + int fLadder; + int step; + + if (gpGlobals->time <= m_flTimeStepSound) + return; + + if (pev->flags & FL_FROZEN) + return; + + speed = pev->velocity.Length(); + + // determine if we are on a ladder + fLadder = IsOnLadder(); + + // UNDONE: need defined numbers for run, walk, crouch, crouch run velocities!!!! + if (FBitSet(pev->flags, FL_DUCKING) || fLadder) + { + velwalk = 60; // These constants should be based on cl_movespeedkey * cl_forwardspeed somehow + velrun = 80; // UNDONE: Move walking to server + flduck = 0.1; + } + else + { + velwalk = 120; + velrun = 210; + flduck = 0.0; + } + + // ALERT (at_console, "vel: %f\n", vecVel.Length()); + + // if we're on a ladder or on the ground, and we're moving fast enough, + // play step sound. Also, if m_flTimeStepSound is zero, get the new + // sound right away - we just started moving in new level. + + if ((fLadder || FBitSet (pev->flags, FL_ONGROUND)) && pev->velocity != g_vecZero + && (speed >= velwalk || !m_flTimeStepSound)) + { + SetAnimation( PLAYER_WALK ); + + fWalking = speed < velrun; + + center = knee = feet = (pev->absmin + pev->absmax) * 0.5; + height = pev->absmax.z - pev->absmin.z; + + knee.z = pev->absmin.z + height * 0.2; + feet.z = pev->absmin.z; + + // find out what we're stepping in or on... + if (fLadder) + { + step = STEP_LADDER; + fvol = 0.35; + m_flTimeStepSound = gpGlobals->time + 0.35; + } + else if ( UTIL_PointContents ( knee ) == CONTENTS_WATER ) + { + step = STEP_WADE; + fvol = 0.65; + m_flTimeStepSound = gpGlobals->time + 0.6; + } + else if (UTIL_PointContents ( feet ) == CONTENTS_WATER ) + { + step = STEP_SLOSH; + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + } + else + { + // find texture under player, if different from current texture, + // get material type + + start = end = center; // center point of player BB + start.z = end.z = pev->absmin.z; // copy zmin + start.z += 4.0; // extend start up + end.z -= 24.0; // extend end down + + start.CopyToArray(rgfl1); + end.CopyToArray(rgfl2); + + pTextureName = TRACE_TEXTURE( ENT( pev->groundentity), rgfl1, rgfl2 ); + if ( pTextureName ) + { + // strip leading '-0' or '{' or '!' + if (*pTextureName == '-') + pTextureName += 2; + if (*pTextureName == '{' || *pTextureName == '!') + pTextureName++; + + if (_strnicmp(pTextureName, m_szTextureName, CBTEXTURENAMEMAX-1)) + { + // current texture is different from texture player is on... + // set current texture + strcpy(szbuffer, pTextureName); + szbuffer[CBTEXTURENAMEMAX - 1] = 0; + strcpy(m_szTextureName, szbuffer); + + // ALERT ( at_aiconsole, "texture: %s\n", m_szTextureName ); + + // get texture type + m_chTextureType = TEXTURETYPE_Find(m_szTextureName); + } + } + + step = MapTextureTypeStepType(m_chTextureType); + + switch (m_chTextureType) + { + default: + case CHAR_TEX_CONCRETE: + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_METAL: + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_DIRT: + fvol = fWalking ? 0.25 : 0.55; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_VENT: + fvol = fWalking ? 0.4 : 0.7; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_GRATE: + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_TILE: + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_SLOSH: + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + } + } + + m_flTimeStepSound += flduck; // slower step time if ducking + + // play the sound + + // 35% volume if ducking + if ( pev->flags & FL_DUCKING ) + fvol *= 0.35; + } +} + +void CBasePlayer::PowerUpThink(void) +{ + int iPowerUp = 0; + bool bUpdate = FALSE; + + //Quad time ran out + if ( m_iQuakeItems & IT_QUAD && m_flSuperDamageFinished < gpGlobals->time ) + { + //Clear the glowing shell effect + pev->renderfx = kRenderFxNone; + //Reset quad timer + m_flSuperDamageFinished = 0.0; + m_bPlayedQuadSound = FALSE; + + //Remove the powerup + m_iQuakeItems &= ~IT_QUAD; + + //We have other powerups, choose the one with more time remaining + if ( m_iQuakeItems & IT_INVISIBILITY || m_iQuakeItems & IT_INVULNERABILITY ) + { + if ( m_iQuakeItems & IT_INVULNERABILITY ) + { + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 255, 128, 0 ); // RGB + pev->renderamt = 100; // Shell size + + iPowerUp = 2; + } + + if ( m_iQuakeItems & IT_INVISIBILITY ) + { + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 128, 128, 128 ); // RGB + pev->renderamt = 5; // Shell size + } + } + + bUpdate = TRUE; + } + + if ( m_iQuakeItems & IT_QUAD && m_flSuperDamageFinished <= gpGlobals->time + 3 && !m_bPlayedQuadSound ) + { + ClientPrint(pev, HUD_PRINTNOTIFY, "#Quad_Damage_Off" ); + EMIT_SOUND( ENT(pev), CHAN_ITEM, "items/damage2.wav", 1, ATTN_NORM ); + + m_bPlayedQuadSound = TRUE; + } + + //Env Suit time ran out + if ( m_iQuakeItems & IT_SUIT && m_flRadsuitFinished < gpGlobals->time ) + { + m_flRadsuitFinished = 0.0; + m_bPlayedEnvSound = FALSE; + + //Remove the powerup + m_iQuakeItems &= ~IT_SUIT; + + //We have other powerups, choose the one with more time remaining + if ( m_iQuakeItems & IT_QUAD || m_iQuakeItems & IT_INVULNERABILITY ) + { + + if ( m_iQuakeItems & IT_INVULNERABILITY && m_iQuakeItems & IT_QUAD ) + { + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 255, 125, 255 ); // RGB + pev->renderamt = 100; // Shell size + } + + else if ( m_flSuperDamageFinished > m_flInvincibleFinished ) + { + pev->renderfx = kRenderFxNone; + pev->rendermode = kRenderNormal; + pev->renderamt = 255; + + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 128, 128, 255 ); // RGB + pev->renderamt = 100; // Shell size + } + else + { + pev->renderfx = kRenderFxNone; + pev->rendermode = kRenderNormal; + pev->renderamt = 255; + + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 255, 128, 0 ); // RGB + pev->renderamt = 100; // Shell size + } + } + else //Clear the invisi screen effect + { + pev->renderfx = kRenderFxNone; + pev->rendermode = kRenderNormal; + pev->renderamt = 255; + } + } + + if ( m_iQuakeItems & IT_SUIT && m_flRadsuitFinished <= gpGlobals->time + 3 && !m_bPlayedEnvSound ) + { + ClientPrint(pev, HUD_PRINTNOTIFY, "#BioSuit_Off" ); + EMIT_SOUND( ENT(pev), CHAN_ITEM, "items/suit2.wav", 1, ATTN_NORM ); + + m_bPlayedEnvSound = TRUE; + + } + + //Invisibility time ran out + if ( m_iQuakeItems & IT_INVISIBILITY && m_flInvisibleFinished < gpGlobals->time ) + { + //Reset the invi timer + m_flInvisibleFinished = 0.0; + m_bPlayedProtectSound = FALSE; + + //Remove the powerup + m_iQuakeItems &= ~IT_INVISIBILITY; + + pev->renderfx = kRenderFxNone; + pev->rendermode = kRenderNormal; + pev->renderamt = 255; + + //We have other powerups, choose the one with more time remaining + if ( m_iQuakeItems & IT_QUAD || m_iQuakeItems & IT_INVULNERABILITY ) + { + if ( m_iQuakeItems & IT_QUAD && m_iQuakeItems & IT_INVULNERABILITY ) + { + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 255, 125, 255 ); // RGB + pev->renderamt = 100; // Shell size + + } + + else if ( m_flSuperDamageFinished > m_flInvincibleFinished ) + { + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 128, 128, 255 ); // RGB + pev->renderamt = 100; // Shell size + } + else + { + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 255, 128, 0 ); // RGB + pev->renderamt = 100; // Shell size + } + } + else //Clear the invisi screen effect + { + pev->renderfx = kRenderFxNone; + pev->rendermode = kRenderNormal; + pev->renderamt = 255; + } + + //Forces our view model to re-appear + W_SetCurrentAmmo(); + + } + + if ( m_iQuakeItems & IT_INVISIBILITY && m_flInvisibleFinished <= gpGlobals->time + 3 && !m_bPlayedInvSound ) + { + ClientPrint(pev, HUD_PRINTNOTIFY, "#Ring_Shadows_Off" ); + EMIT_SOUND( ENT(pev), CHAN_ITEM, "items/inv2.wav", 1, ATTN_NORM ); + + m_bPlayedInvSound = TRUE; + + } + + //666 time ran out + if ( m_iQuakeItems & IT_INVULNERABILITY && m_flInvincibleFinished < gpGlobals->time ) + { + //Clear the glowing shell effect + pev->renderfx = kRenderFxNone; + //Reset 666 timer + m_flInvincibleFinished = 0.0; + m_bPlayedProtectSound = FALSE; + + //Remove the powerup + m_iQuakeItems &= ~IT_INVULNERABILITY; + + //We have other powerups, choose the one with more time remaining + if ( m_iQuakeItems & IT_QUAD || m_iQuakeItems & IT_INVISIBILITY ) + { + if ( m_flSuperDamageFinished > m_flInvisibleFinished ) + { + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 128, 128, 255 ); // RGB + pev->renderamt = 100; // Shell size + + iPowerUp = 1; + } + else + { + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 128, 128, 128 ); // RGB + pev->renderamt = 5; // Shell size + } + } + + bUpdate = TRUE; + } + + if ( m_iQuakeItems & IT_INVULNERABILITY && m_flInvincibleFinished <= gpGlobals->time + 3 && !m_bPlayedProtectSound ) + { + ClientPrint(pev, HUD_PRINTNOTIFY, "#Protection_Off" ); + EMIT_SOUND( ENT(pev), CHAN_ITEM, "items/protect2.wav", 1, ATTN_NORM ); + + m_bPlayedProtectSound = TRUE; + } + + if ( bUpdate ) + { + W_SetCurrentAmmo(); + + PLAYBACK_EVENT_FULL( FEV_GLOBAL | FEV_RELIABLE, + edict(), g_usPowerUp, 0, (float *)&g_vecZero, (float *)&g_vecZero, + (float)iPowerUp, 0.0, entindex(), pev->team, 1, 0 ); + } + +} + + +#define CLIMB_SHAKE_FREQUENCY 22 // how many frames in between screen shakes when climbing +#define MAX_CLIMB_SPEED 200 // fastest vertical climbing speed possible +#define CLIMB_SPEED_DEC 15 // climbing deceleration rate +#define CLIMB_PUNCH_X -7 // how far to 'punch' client X axis when climbing +#define CLIMB_PUNCH_Z 7 // how far to 'punch' client Z axis when climbing + +void CBasePlayer::PreThink(void) +{ + int buttonsChanged = (m_afButtonLast ^ pev->button); // These buttons have changed this frame + + // Debounced button codes for pressed/released + // UNDONE: Do we need auto-repeat? + m_afButtonPressed = buttonsChanged & pev->button; // The changed ones still down are "pressed" + m_afButtonReleased = buttonsChanged & (~pev->button); // The ones not down are "released" + + g_pGameRules->PlayerThink( this ); + + if ( g_fGameOver ) + return; // intermission or finale + + UTIL_MakeVectors(pev->v_angle); // is this still used? + + ItemPreFrame( ); + WaterMove(); + + if ( g_pGameRules && g_pGameRules->FAllowFlashlight() ) + m_iHideHUD &= ~HIDEHUD_FLASHLIGHT; + else + m_iHideHUD |= HIDEHUD_FLASHLIGHT; + + + // JOHN: checks if new client data (for HUD and view control) needs to be sent to the client + UpdateClientData(); + + CheckTimeBasedDamage(); + + CheckSuitUpdate(); + + // Observer Button Handling + if ( pev->iuser1 != 0 ) + { + Observer_HandleButtons(); + return; + } + + //Run Powerup think (removes powerups over time, etc) + PowerUpThink(); + + // Dead think only if they're not an observer + if (pev->deadflag >= DEAD_DYING) + { + PlayerDeathThink(); + return; + } + + // So the correct flags get sent to client asap. + // + if ( m_afPhysicsFlags & PFLAG_ONTRAIN ) + pev->flags |= FL_ONTRAIN; + else + pev->flags &= ~FL_ONTRAIN; + + // Train speed control + if ( m_afPhysicsFlags & PFLAG_ONTRAIN ) + { + CBaseEntity *pTrain = CBaseEntity::Instance( pev->groundentity ); + float vel; + + if ( !pTrain ) + { + TraceResult trainTrace; + // Maybe this is on the other side of a level transition + UTIL_TraceLine( pev->origin, pev->origin + Vector(0,0,-38), ignore_monsters, ENT(pev), &trainTrace ); + + // HACKHACK - Just look for the func_tracktrain classname + if ( trainTrace.flFraction != 1.0 && trainTrace.pHit ) + pTrain = CBaseEntity::Instance( trainTrace.pHit ); + + + if ( !pTrain || !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) || !pTrain->OnControls(pev) ) + { + //ALERT( at_error, "In train mode with no train!\n" ); + m_afPhysicsFlags &= ~PFLAG_ONTRAIN; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + } + else if ( !FBitSet( pev->flags, FL_ONGROUND ) || FBitSet( pTrain->pev->spawnflags, SF_TRACKTRAIN_NOCONTROL ) || (pev->button & (IN_MOVELEFT|IN_MOVERIGHT) ) ) + { + // Turn off the train if you jump, strafe, or the train controls go dead + m_afPhysicsFlags &= ~PFLAG_ONTRAIN; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + + pev->velocity = g_vecZero; + vel = 0; + if ( m_afButtonPressed & IN_FORWARD ) + { + vel = 1; + pTrain->Use( this, this, USE_SET, (float)vel ); + } + else if ( m_afButtonPressed & IN_BACK ) + { + vel = -1; + pTrain->Use( this, this, USE_SET, (float)vel ); + } + + if (vel) + { + m_iTrain = TrainSpeed(static_cast(pTrain->pev->speed), pTrain->pev->impulse); + m_iTrain |= TRAIN_ACTIVE|TRAIN_NEW; + } + + } else if (m_iTrain & TRAIN_ACTIVE) + m_iTrain = TRAIN_NEW; // turn off train + + if (pev->button & IN_JUMP) + { + // If on a ladder, jump off the ladder + // else Jump + Jump(); + } + + // QUAKECLASSIC + // Duck removed + + // play a footstep if it's time - this will eventually be frame-based. not time based. + UpdateStepSound(); + + if ( !FBitSet ( pev->flags, FL_ONGROUND ) ) + { + m_flFallVelocity = -pev->velocity.z; + } + + // StudioFrameAdvance( );//!!!HACKHACK!!! Can't be hit by traceline when not animating? + + // Clear out ladder pointer + m_hEnemy = NULL; + + if ( m_afPhysicsFlags & PFLAG_ONBARNACLE ) + { + pev->velocity = g_vecZero; + } +} +/* Time based Damage works as follows: + 1) There are several types of timebased damage: + + #define DMG_PARALYZE (1 << 14) // slows affected creature down + #define DMG_NERVEGAS (1 << 15) // nerve toxins, very bad + #define DMG_POISON (1 << 16) // blood poisioning + #define DMG_RADIATION (1 << 17) // radiation exposure + #define DMG_DROWNRECOVER (1 << 18) // drown recovery + #define DMG_ACID (1 << 19) // toxic chemicals or acid burns + #define DMG_SLOWBURN (1 << 20) // in an oven + #define DMG_SLOWFREEZE (1 << 21) // in a subzero freezer + + 2) A new hit inflicting tbd restarts the tbd counter - each monster has an 8bit counter, + per damage type. The counter is decremented every second, so the maximum time + an effect will last is 255/60 = 4.25 minutes. Of course, staying within the radius + of a damaging effect like fire, nervegas, radiation will continually reset the counter to max. + + 3) Every second that a tbd counter is running, the player takes damage. The damage + is determined by the type of tdb. + Paralyze - 1/2 movement rate, 30 second duration. + Nervegas - 5 points per second, 16 second duration = 80 points max dose. + Poison - 2 points per second, 25 second duration = 50 points max dose. + Radiation - 1 point per second, 50 second duration = 50 points max dose. + Drown - 5 points per second, 2 second duration. + Acid/Chemical - 5 points per second, 10 second duration = 50 points max. + Burn - 10 points per second, 2 second duration. + Freeze - 3 points per second, 10 second duration = 30 points max. + + 4) Certain actions or countermeasures counteract the damaging effects of tbds: + + Armor/Heater/Cooler - Chemical(acid),burn, freeze all do damage to armor power, then to body + - recharged by suit recharger + Air In Lungs - drowning damage is done to air in lungs first, then to body + - recharged by poking head out of water + - 10 seconds if swiming fast + Air In SCUBA - drowning damage is done to air in tanks first, then to body + - 2 minutes in tanks. Need new tank once empty. + Radiation Syringe - Each syringe full provides protection vs one radiation dosage + Antitoxin Syringe - Each syringe full provides protection vs one poisoning (nervegas or poison). + Health kit - Immediate stop to acid/chemical, fire or freeze damage. + Radiation Shower - Immediate stop to radiation damage, acid/chemical or fire damage. + + +*/ + +// If player is taking time based damage, continue doing damage to player - +// this simulates the effect of being poisoned, gassed, dosed with radiation etc - +// anything that continues to do damage even after the initial contact stops. +// Update all time based damage counters, and shut off any that are done. + +// The m_bitsDamageType bit MUST be set if any damage is to be taken. +// This routine will detect the initial on value of the m_bitsDamageType +// and init the appropriate counter. Only processes damage every second. + +//#define PARALYZE_DURATION 30 // number of 2 second intervals to take damage +//#define PARALYZE_DAMAGE 0.0 // damage to take each 2 second interval + +//#define NERVEGAS_DURATION 16 +//#define NERVEGAS_DAMAGE 5.0 + +//#define POISON_DURATION 25 +//#define POISON_DAMAGE 2.0 + +//#define RADIATION_DURATION 50 +//#define RADIATION_DAMAGE 1.0 + +//#define ACID_DURATION 10 +//#define ACID_DAMAGE 5.0 + +//#define SLOWBURN_DURATION 2 +//#define SLOWBURN_DAMAGE 1.0 + +//#define SLOWFREEZE_DURATION 1.0 +//#define SLOWFREEZE_DAMAGE 3.0 + +/* */ + + +void CBasePlayer::CheckTimeBasedDamage() +{ + int i; + BYTE bDuration = 0; + + if (!(m_bitsDamageType & DMG_TIMEBASED)) + return; + + // only check for time based damage approx. every 2 seconds + if (abs(static_cast(gpGlobals->time - m_tbdPrev)) < 2.0) + return; + + m_tbdPrev = gpGlobals->time; + + for (i = 0; i < CDMG_TIMEBASED; i++) + { + // make sure bit is set for damage type + if (m_bitsDamageType & (DMG_PARALYZE << i)) + { + switch (i) + { + case itbd_Paralyze: + // UNDONE - flag movement as half-speed + bDuration = PARALYZE_DURATION; + break; + case itbd_NerveGas: +// TakeDamage(pev, pev, NERVEGAS_DAMAGE, DMG_GENERIC); + bDuration = NERVEGAS_DURATION; + break; + case itbd_Poison: + TakeDamage(pev, pev, POISON_DAMAGE, DMG_GENERIC); + bDuration = POISON_DURATION; + break; + case itbd_Radiation: +// TakeDamage(pev, pev, RADIATION_DAMAGE, DMG_GENERIC); + bDuration = RADIATION_DURATION; + break; + case itbd_DrownRecover: + // NOTE: this hack is actually used to RESTORE health + // after the player has been drowning and finally takes a breath + if (m_idrowndmg > m_idrownrestored) + { + int idif = min(m_idrowndmg - m_idrownrestored, 10); + + TakeHealth(idif, DMG_GENERIC); + m_idrownrestored += idif; + } + bDuration = 4; // get up to 5*10 = 50 points back + break; + case itbd_Acid: +// TakeDamage(pev, pev, ACID_DAMAGE, DMG_GENERIC); + bDuration = ACID_DURATION; + break; + case itbd_SlowBurn: +// TakeDamage(pev, pev, SLOWBURN_DAMAGE, DMG_GENERIC); + bDuration = SLOWBURN_DURATION; + break; + case itbd_SlowFreeze: +// TakeDamage(pev, pev, SLOWFREEZE_DAMAGE, DMG_GENERIC); + bDuration = SLOWFREEZE_DURATION; + break; + default: + bDuration = 0; + } + + if (m_rgbTimeBasedDamage[i]) + { + // use up an antitoxin on poison or nervegas after a few seconds of damage + if (((i == itbd_NerveGas) && (m_rgbTimeBasedDamage[i] < NERVEGAS_DURATION)) || + ((i == itbd_Poison) && (m_rgbTimeBasedDamage[i] < POISON_DURATION))) + { + if (m_rgItems[ITEM_ANTIDOTE]) + { + m_rgbTimeBasedDamage[i] = 0; + m_rgItems[ITEM_ANTIDOTE]--; + SetSuitUpdate("!HEV_HEAL4", FALSE, SUIT_REPEAT_OK); + } + } + + + // decrement damage duration, detect when done. + if (!m_rgbTimeBasedDamage[i] || --m_rgbTimeBasedDamage[i] == 0) + { + m_rgbTimeBasedDamage[i] = 0; + // if we're done, clear damage bits + m_bitsDamageType &= ~(DMG_PARALYZE << i); + } + } + else + // first time taking this damage type - init damage duration + m_rgbTimeBasedDamage[i] = bDuration; + } + } +} + +/* +THE POWER SUIT + +The Suit provides 3 main functions: Protection, Notification and Augmentation. +Some functions are automatic, some require power. +The player gets the suit shortly after getting off the train in C1A0 and it stays +with him for the entire game. + +Protection + + Heat/Cold + When the player enters a hot/cold area, the heating/cooling indicator on the suit + will come on and the battery will drain while the player stays in the area. + After the battery is dead, the player starts to take damage. + This feature is built into the suit and is automatically engaged. + Radiation Syringe + This will cause the player to be immune from the effects of radiation for N seconds. Single use item. + Anti-Toxin Syringe + This will cure the player from being poisoned. Single use item. + Health + Small (1st aid kits, food, etc.) + Large (boxes on walls) + Armor + The armor works using energy to create a protective field that deflects a + percentage of damage projectile and explosive attacks. After the armor has been deployed, + it will attempt to recharge itself to full capacity with the energy reserves from the battery. + It takes the armor N seconds to fully charge. + +Notification (via the HUD) + +x Health +x Ammo +x Automatic Health Care + Notifies the player when automatic healing has been engaged. +x Geiger counter + Classic Geiger counter sound and status bar at top of HUD + alerts player to dangerous levels of radiation. This is not visible when radiation levels are normal. +x Poison + Armor + Displays the current level of armor. + +Augmentation + + Reanimation (w/adrenaline) + Causes the player to come back to life after he has been dead for 3 seconds. + Will not work if player was gibbed. Single use. + Long Jump + Used by hitting the ??? key(s). Caused the player to further than normal. + SCUBA + Used automatically after picked up and after player enters the water. + Works for N seconds. Single use. + +Things powered by the battery + + Armor + Uses N watts for every M units of damage. + Heat/Cool + Uses N watts for every second in hot/cold area. + Long Jump + Uses N watts for every jump. + Alien Cloak + Uses N watts for each use. Each use lasts M seconds. + Alien Shield + Augments armor. Reduces Armor drain by one half + +*/ + +// if in range of radiation source, ping geiger counter + +#define GEIGERDELAY 0.25 + +void CBasePlayer :: UpdateGeigerCounter( void ) +{ + BYTE range; + + // delay per update ie: don't flood net with these msgs + if (gpGlobals->time < m_flgeigerDelay) + return; + + m_flgeigerDelay = gpGlobals->time + GEIGERDELAY; + + // send range to radition source to client + + range = (BYTE) (m_flgeigerRange / 4); + + if (range != m_igeigerRangePrev) + { + m_igeigerRangePrev = range; + + MESSAGE_BEGIN( MSG_ONE, gmsgGeigerRange, NULL, pev ); + WRITE_BYTE( range ); + MESSAGE_END(); + } + + // reset counter and semaphore + if (!RANDOM_LONG(0,3)) + m_flgeigerRange = 1000; + +} + +/* +================ +CheckSuitUpdate + +Play suit update if it's time +================ +*/ + +#define SUITUPDATETIME 3.5 +#define SUITFIRSTUPDATETIME 0.1 + +void CBasePlayer::CheckSuitUpdate() +{ + int i; + int isentence = 0; + int isearch = m_iSuitPlayNext; + + // Ignore suit updates if no suit + if ( !(pev->weapons & (1<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 + +Check for turning off powerups + +GLOBALS ASSUMED SET: g_ulModelIndexPlayer +================ +*/ + static void +CheckPowerups(entvars_t *pev) +{ + if (pev->health <= 0) + return; + + pev->modelindex = g_ulModelIndexPlayer; // don't use eyes +} + +void CBasePlayer::PostThink() +{ + if ( g_fGameOver ) + goto pt_end; // intermission or finale + + if (!IsAlive()) + goto pt_end; + + // Handle Tank controlling + if ( m_pTank != 0 ) + { // if they've moved too far from the gun, or selected a weapon, unuse the gun + if ( m_pTank->OnControls( pev ) && !pev->weaponmodel ) + { + m_pTank->Use( this, this, USE_SET, 2 ); // try fire the gun + } + else + { // they've moved off the platform + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + } + } + +// do weapon stuff + ItemPostFrame( ); + +// check to see if player landed hard enough to make a sound +// falling farther than half of the maximum safe distance, but not as far a max safe distance will +// play a bootscrape sound, and no damage will be inflicted. Fallling a distance shorter than half +// of maximum safe distance will make no sound. Falling farther than max safe distance will play a +// fallpain sound, and damage will be inflicted based on how far the player fell + + if ( (FBitSet(pev->flags, FL_ONGROUND)) && (pev->health > 0) && m_flFallVelocity >= PLAYER_FALL_PUNCH_THRESHHOLD ) + { + float fvol = 0.5; + + // ALERT ( at_console, "%f\n", m_flFallVelocity ); + + if (pev->watertype == CONTENT_WATER) + { + // Did he hit the world or a non-moving entity? + // BUG - this happens all the time in water, especially when + // BUG - water has current force + // if ( !pev->groundentity || VARS(pev->groundentity)->velocity.z == 0 ) + // EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM); + } + else if ( m_flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED ) + {// after this point, we start doing damage + float flFallDamage = g_pGameRules->FlPlayerFallDamage( this ); + + if ( flFallDamage > pev->health ) + {//splat + // note: play on item channel because we play footstep landing on body channel + EMIT_SOUND(ENT(pev), CHAN_ITEM, "common/bodysplat.wav", 1, ATTN_NORM); + } + + if ( flFallDamage > 0 ) + { + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), flFallDamage, DMG_FALL ); + pev->punchangle.x = 0; + } + + fvol = 1.0; + } + else if ( m_flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED / 2 ) + { + // EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_jumpland2.wav", 1, ATTN_NORM); + fvol = 0.85; + } + else if ( m_flFallVelocity < PLAYER_MIN_BOUNCE_SPEED ) + { + fvol = 0; + } + + if ( fvol > 0.0 ) + { + // get current texture under player right away + m_flTimeStepSound = 0; + UpdateStepSound(); + } + + if ( IsAlive() ) + { + SetAnimation( PLAYER_WALK ); + } + } + + if (FBitSet(pev->flags, FL_ONGROUND)) + { + //if (m_flFallVelocity > 64 && !g_pGameRules->IsMultiplayer()) + // TODO Make noise + m_flFallVelocity = 0; + } + + // select the proper animation for the player character + if ( IsAlive() ) + { + if (!pev->velocity.x && !pev->velocity.y) + SetAnimation( PLAYER_IDLE ); + else if ((pev->velocity.x || pev->velocity.y) && (FBitSet(pev->flags, FL_ONGROUND))) + SetAnimation( PLAYER_WALK ); + else if (pev->waterlevel > 1) + SetAnimation( PLAYER_WALK ); + } + + StudioFrameAdvance( ); + CheckPowerups(pev); + + // Track button info so we can detect 'pressed' and 'released' buttons next frame + m_afButtonLast = pev->button; + +pt_end: + // Decay timers on weapons + // go through all of the weapons and make a list of the ones to pack + for ( int i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + CBasePlayerItem *pPlayerItem = m_rgpPlayerItems[ i ]; + + while ( pPlayerItem ) + { + CBasePlayerWeapon *gun; + + gun = (CBasePlayerWeapon *)pPlayerItem->GetWeaponPtr(); + + if ( gun && gun->UseDecrement() ) + { + gun->m_flNextPrimaryAttack = max( gun->m_flNextPrimaryAttack - gpGlobals->frametime, -1.0 ); + gun->m_flNextSecondaryAttack = max( gun->m_flNextSecondaryAttack - gpGlobals->frametime, -0.001 ); + + if ( gun->m_flTimeWeaponIdle != 1000 ) + { + gun->m_flTimeWeaponIdle = max( gun->m_flTimeWeaponIdle - gpGlobals->frametime, -0.001 ); + } + } + + pPlayerItem = pPlayerItem->m_pNext; + } + } + } + + m_flNextAttack -= gpGlobals->frametime; + if ( m_flNextAttack < -0.001 ) + m_flNextAttack = -0.001; +} + +BOOL IsSpawnPointValid( CBaseEntity *pPlayer, CBaseEntity *pSpot ) +{ + CBaseEntity *ent = NULL; + + if ( !pSpot->IsTriggered( pPlayer ) ) + { + return FALSE; + } + + while ( (ent = UTIL_FindEntityInSphere( ent, pSpot->pev->origin, 96 )) != NULL ) + { + // if ent is a client, don't spawn on 'em + if ( ent->pev->flags & FL_CLIENT ) + return FALSE; + } + + return TRUE; +} + +DLL_GLOBAL CBaseEntity *g_pLastSpawn; +inline int FNullEnt( CBaseEntity *ent ) { return (ent == NULL) || FNullEnt( ent->edict() ); } + +/* +============ +EntSelectSpawnPoint + +Returns the entity to spawn at + +USES AND SETS GLOBAL g_pLastSpawn +============ +*/ +edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ) +{ + CBaseEntity *pSpot; + edict_t *player; + + player = pPlayer->edict(); + +// choose a info_player_deathmatch point + if (g_pGameRules->IsCoOp()) + { + pSpot = UTIL_FindEntityByClassname( g_pLastSpawn, "info_player_coop"); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + pSpot = UTIL_FindEntityByClassname( g_pLastSpawn, "info_player_start"); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + } + else if ( g_pGameRules->IsDeathmatch() ) + { + pSpot = g_pLastSpawn; + // Randomize the start spot + for ( int i = RANDOM_LONG(1,5); i > 0; i-- ) + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + if ( FNullEnt( pSpot ) ) // skip over the null point + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + + CBaseEntity *pFirstSpot = pSpot; + + do + { + if ( pSpot ) + { + // check if pSpot is valid + if ( IsSpawnPointValid( pPlayer, pSpot ) ) + { + if ( pSpot->pev->origin == Vector( 0, 0, 0 ) ) + { + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + continue; + } + + // if so, go to pSpot + goto ReturnSpot; + } + } + // increment pSpot + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + } while ( pSpot != pFirstSpot ); // loop if we're not back to the start + + // we haven't found a place to spawn yet, so kill any guy at the first spawn point and spawn there + if ( !FNullEnt( pSpot ) ) + { + CBaseEntity *ent = NULL; + while ( (ent = UTIL_FindEntityInSphere( ent, pSpot->pev->origin, 128 )) != NULL ) + { + // if ent is a client, kill em (unless they are ourselves) + if ( ent->IsPlayer() && !(ent->edict() == player) ) + ent->TakeDamage( VARS(INDEXENT(0)), VARS(INDEXENT(0)), 300, DMG_GENERIC ); + } + goto ReturnSpot; + } + } + + // If startspot is set, (re)spawn there. + if ( FStringNull( gpGlobals->startspot ) || !strlen(STRING(gpGlobals->startspot))) + { + pSpot = UTIL_FindEntityByClassname(NULL, "info_player_start"); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + } + else + { + pSpot = UTIL_FindEntityByTargetname( NULL, STRING(gpGlobals->startspot) ); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + } + +ReturnSpot: + if ( FNullEnt( pSpot ) ) + { + ALERT(at_error, "PutClientInServer: no info_player_start on level"); + return INDEXENT(0); + } + + g_pLastSpawn = pSpot; + return pSpot->edict(); +} + +void CBasePlayer::Spawn( void ) +{ + pev->classname = MAKE_STRING("player"); + pev->health = 100; + pev->armorvalue = 0; + pev->takedamage = DAMAGE_AIM; + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_WALK; + pev->max_health = pev->health; + pev->flags &= FL_PROXY; // keep proxy flag sey by engine + pev->flags |= FL_CLIENT; + pev->air_finished = gpGlobals->time + 12; + pev->dmg = 2; // initial water damage + pev->effects = 0; + pev->deadflag = DEAD_NO; + pev->dmg_take = 0; + pev->dmg_save = 0; + pev->friction = 1.0; + pev->gravity = 1.0; + m_bitsHUDDamage = -1; + m_bitsDamageType = 0; + m_afPhysicsFlags = 0; + m_fLongJump = FALSE;// no longjump module. + + g_engfuncs.pfnSetPhysicsKeyValue( edict(), "slj", "0" ); + g_engfuncs.pfnSetPhysicsKeyValue( edict(), "hl", "1" ); + + + m_flNextDecalTime = 0;// let this player decal as soon as he spawns. + + m_flgeigerDelay = gpGlobals->time + 2.0; // wait a few seconds until user-defined message registrations + // are recieved by all clients + + m_flTimeStepSound = 0; + m_iStepLeft = 0; + m_flFieldOfView = 0.5;// some monsters use this to determine whether or not the player is looking at them. + + m_bloodColor = BLOOD_COLOR_RED; + m_flNextAttack = UTIL_WeaponTimeBase(); + StartSneaking(); + + m_iFlashBattery = 99; + m_flFlashLightTime = 1; // force first message + +// dont let uninitialized value here hurt the player + m_flFallVelocity = 0; + + //g_pGameRules->SetDefaultPlayerTeam( this ); + g_pGameRules->GetPlayerSpawnSpot( this ); + + SET_MODEL(ENT(pev), "models/player.mdl"); + g_ulModelIndexPlayer = pev->modelindex; + pev->sequence = LookupActivity( ACT_IDLE ); + + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + + pev->view_ofs = VEC_VIEW; + Precache(); + m_HackedGunPos = Vector( 0, 32, 0 ); + + m_fNoPlayerSound = FALSE;// normal sound behavior. + + m_pLastItem = NULL; + m_fInitHUD = TRUE; + m_iClientHideHUD = -1; // force this to be recalculated + m_fWeapon = FALSE; + m_pClientActiveItem = NULL; + m_iClientBattery = -1; + + m_flLightningTime = 0.0; + + InitStatusBar(); + m_iQuakeItems = 0; + + // reset all ammo values to 0 + for ( int i = 0; i < MAX_AMMO_SLOTS; i++ ) + { + m_rgAmmo[i] = 0; + m_rgAmmoLast[i] = 0; // client ammo values also have to be reset (the death hud clear messages does on the client side) + } + + m_lastx = m_lasty = 0; + + if ( !m_bHadFirstSpawn && g_bHaveMOTD ) + { + pev->effects |= EF_NODRAW; + pev->solid = SOLID_NOT; + pev->takedamage = DAMAGE_NO; + pev->movetype = MOVETYPE_NONE; + + g_engfuncs.pfnSetClientMaxspeed( ENT( pev ), 1 ); + m_iHideHUD |= HIDEHUD_WEAPONS | HIDEHUD_FLASHLIGHT | HIDEHUD_HEALTH; + } + else + { + g_engfuncs.pfnSetClientMaxspeed( ENT( pev ), PLAYER_MAX_SPEED ); + m_iHideHUD &= ~( HIDEHUD_WEAPONS | HIDEHUD_HEALTH ); + + pev->effects &= ~EF_NODRAW; + + g_pGameRules->PlayerSpawn( this ); + + PLAYBACK_EVENT_FULL ( FEV_GLOBAL, edict(), g_sTeleport, 0.0, (float *)&pev->origin, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0); + + m_fKnownItem = false; + } + + +} + + +void CBasePlayer :: Precache( void ) +{ + // in the event that the player JUST spawned, and the level node graph + // was loaded, fix all of the node graph pointers before the game starts. + + // !!!BUGBUG - now that we have multiplayer, this needs to be moved! + if ( WorldGraph.m_fGraphPresent && !WorldGraph.m_fGraphPointersSet ) + { + if ( !WorldGraph.FSetGraphPointers() ) + { + ALERT ( at_console, "**Graph pointers were not set!\n"); + } + else + { + ALERT ( at_console, "**Graph Pointers Set!\n" ); + } + } + + // SOUNDS / MODELS ARE PRECACHED in ClientPrecache() (game specific) + // because they need to precache before any clients have connected + m_usShotgunSingle = PRECACHE_EVENT( 1, "events/shotgun1.sc" ); + m_usShotgunDouble = PRECACHE_EVENT( 1, "events/shotgun2.sc" ); + m_usAxe = PRECACHE_EVENT( 1, "events/axe.sc" ); + m_usAxeSwing = PRECACHE_EVENT( 1, "events/axeswing.sc" ); + m_usRocket = PRECACHE_EVENT( 1, "events/rocket.sc" ); + m_usGrenade = PRECACHE_EVENT( 1, "events/grenade.sc" ); + m_usLightning = PRECACHE_EVENT( 1, "events/lightning.sc" ); + m_usSpike = PRECACHE_EVENT( 1, "events/spike.sc" ); + m_usSuperSpike = PRECACHE_EVENT( 1, "events/superspike.sc" ); + + + // init geiger counter vars during spawn and each time + // we cross a level transition + + m_flgeigerRange = 1000; + m_igeigerRangePrev = 1000; + + m_bitsDamageType = 0; + m_bitsHUDDamage = -1; + + m_iClientBattery = -1; + + m_iTrain = TRAIN_NEW; + + // Make sure any necessary user messages have been registered + LinkUserMessages(); + + m_iUpdateTime = 5; // won't update for 1/2 a second + + if ( gInitHUD ) + m_fInitHUD = TRUE; +} + + +int CBasePlayer::Save( CSave &save ) +{ + if ( !CBaseMonster::Save(save) ) + return 0; + + return save.WriteFields( "PLAYER", this, m_playerSaveData, ARRAYSIZE(m_playerSaveData) ); +} + + +// +// Marks everything as new so the player will resend this to the hud. +// +void CBasePlayer::RenewItems(void) +{ + +} + + +int CBasePlayer::Restore( CRestore &restore ) +{ + if ( !CBaseMonster::Restore(restore) ) + return 0; + + int status = restore.ReadFields( "PLAYER", this, m_playerSaveData, ARRAYSIZE(m_playerSaveData) ); + + SAVERESTOREDATA *pSaveData = (SAVERESTOREDATA *)gpGlobals->pSaveData; + // landmark isn't present. + if ( !pSaveData->fUseLandmark ) + { + ALERT( at_console, "No Landmark:%s\n", pSaveData->szLandmarkName ); + + // default to normal spawn + edict_t* pentSpawnSpot = EntSelectSpawnPoint( this ); + pev->origin = VARS(pentSpawnSpot)->origin + Vector(0,0,1); + pev->angles = VARS(pentSpawnSpot)->angles; + } + pev->v_angle.z = 0; // Clear out roll + pev->angles = pev->v_angle; + + pev->fixangle = TRUE; // turn this way immediately + +// Copied from spawn() for now + m_bloodColor = BLOOD_COLOR_RED; + + g_ulModelIndexPlayer = pev->modelindex; + +/* if ( FBitSet(pev->flags, FL_DUCKING) ) + { + // Use the crouch HACK + // FixPlayerCrouchStuck( edict() ); + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + } + else + { + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + }*/ + + RenewItems(); + + return status; +} + + + +void CBasePlayer::SelectNextItem( int iItem ) +{ + CBasePlayerItem *pItem; + + pItem = m_rgpPlayerItems[ iItem ]; + + if (!pItem) + return; + + if (pItem == m_pActiveItem) + { + // select the next one in the chain + pItem = m_pActiveItem->m_pNext; + if (! pItem) + { + return; + } + + CBasePlayerItem *pLast; + pLast = pItem; + while (pLast->m_pNext) + pLast = pLast->m_pNext; + + // relink chain + pLast->m_pNext = m_pActiveItem; + m_pActiveItem->m_pNext = NULL; + m_rgpPlayerItems[ iItem ] = pItem; + } + + ResetAutoaim( ); + + // FIX, this needs to queue them up and delay + if (m_pActiveItem) + { + m_pActiveItem->Holster( ); + } + + m_pActiveItem = pItem; + + if (m_pActiveItem) + { + m_pActiveItem->Deploy( ); + m_pActiveItem->UpdateItemInfo( ); + } +} + +void CBasePlayer::SelectItem(const char *pstr) +{ + if (!pstr) + return; + + CBasePlayerItem *pItem = NULL; + + for (int i = 0; i < MAX_ITEM_TYPES; i++) + { + if (m_rgpPlayerItems[i]) + { + pItem = m_rgpPlayerItems[i]; + + while (pItem) + { + if (FClassnameIs(pItem->pev, pstr)) + break; + pItem = pItem->m_pNext; + } + } + + if (pItem) + break; + } + + if (!pItem) + return; + + + if (pItem == m_pActiveItem) + return; + + ResetAutoaim( ); + + // FIX, this needs to queue them up and delay + if (m_pActiveItem) + m_pActiveItem->Holster( ); + + m_pLastItem = m_pActiveItem; + m_pActiveItem = pItem; + + if (m_pActiveItem) + { + m_pActiveItem->Deploy( ); + m_pActiveItem->UpdateItemInfo( ); + } +} + + +void CBasePlayer::SelectLastItem(void) +{ + if (!m_pLastItem) + { + return; + } + + if ( m_pActiveItem && !m_pActiveItem->CanHolster() ) + { + return; + } + + ResetAutoaim( ); + + // FIX, this needs to queue them up and delay + if (m_pActiveItem) + m_pActiveItem->Holster( ); + + CBasePlayerItem *pTemp = m_pActiveItem; + m_pActiveItem = m_pLastItem; + m_pLastItem = pTemp; + m_pActiveItem->Deploy( ); + m_pActiveItem->UpdateItemInfo( ); +} + +//============================================== +// HasWeapons - do I have any weapons at all? +//============================================== +BOOL CBasePlayer::HasWeapons( void ) +{ + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + return TRUE; + } + } + + return FALSE; +} + +void CBasePlayer::SelectPrevItem( int iItem ) +{ +} + + +const char *CBasePlayer::TeamID( void ) +{ + if ( pev == NULL ) // Not fully connected yet + return ""; + + // return their team name + return m_szTeamName; +} + + +//============================================== +// !!!UNDONE:ultra temporary SprayCan entity to apply +// decal frame at a time. For PreAlpha CD +//============================================== +class CSprayCan : public CBaseEntity +{ +public: + void Spawn ( entvars_t *pevOwner ); + void Think( void ); + + virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +}; + +void CSprayCan::Spawn ( entvars_t *pevOwner ) +{ + pev->origin = pevOwner->origin + Vector ( 0 , 0 , 32 ); + pev->angles = pevOwner->v_angle; + pev->owner = ENT(pevOwner); + pev->frame = 0; + + pev->nextthink = gpGlobals->time + 0.1; + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/sprayer.wav", 1, ATTN_NORM); +} + +void CSprayCan::Think( void ) +{ + TraceResult tr; + int playernum; + int nFrames; + CBasePlayer *pPlayer; + + pPlayer = (CBasePlayer *)GET_PRIVATE(pev->owner); + + if (pPlayer) + nFrames = pPlayer->GetCustomDecalFrames(); + else + nFrames = -1; + + playernum = ENTINDEX(pev->owner); + + // ALERT(at_console, "Spray by player %i, %i of %i\n", playernum, (int)(pev->frame + 1), nFrames); + + UTIL_MakeVectors(pev->angles); + UTIL_TraceLine ( pev->origin, pev->origin + gpGlobals->v_forward * 128, ignore_monsters, pev->owner, & tr); + + // No customization present. + if (nFrames == -1) + { + UTIL_DecalTrace( &tr, DECAL_LAMBDA6 ); + UTIL_Remove( this ); + } + else + { + UTIL_PlayerDecalTrace( &tr, playernum, static_cast(pev->frame), TRUE ); + // Just painted last custom frame. + if ( pev->frame++ >= (nFrames - 1)) + UTIL_Remove( this ); + } + + pev->nextthink = gpGlobals->time + 0.1; +} + +class CBloodSplat : public CBaseEntity +{ +public: + void Spawn ( entvars_t *pevOwner ); + void Spray ( void ); +}; + +void CBloodSplat::Spawn ( entvars_t *pevOwner ) +{ + pev->origin = pevOwner->origin + Vector ( 0 , 0 , 32 ); + pev->angles = pevOwner->v_angle; + pev->owner = ENT(pevOwner); + + SetThink ( &CBloodSplat::Spray ); + pev->nextthink = gpGlobals->time + 0.1; +} + +void CBloodSplat::Spray ( void ) +{ + TraceResult tr; + + if ( g_Language != LANGUAGE_GERMAN ) + { + UTIL_MakeVectors(pev->angles); + UTIL_TraceLine ( pev->origin, pev->origin + gpGlobals->v_forward * 128, ignore_monsters, pev->owner, & tr); + + UTIL_BloodDecalTrace( &tr, BLOOD_COLOR_RED ); + } + SetThink ( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; +} + +//============================================== + + + +void CBasePlayer::GiveNamedItem( const char *pszName ) +{ + edict_t *pent; + + int istr = MAKE_STRING(pszName); + + pent = CREATE_NAMED_ENTITY(istr); + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in GiveNamedItem!\n" ); + return; + } + VARS( pent )->origin = pev->origin; + pent->v.spawnflags |= SF_NORESPAWN; + + DispatchSpawn( pent ); + DispatchTouch( pent, ENT( pev ) ); +} + + + +CBaseEntity *FindEntityForward( CBaseEntity *pMe ) +{ + TraceResult tr; + + UTIL_MakeVectors(pMe->pev->v_angle); + UTIL_TraceLine(pMe->pev->origin + pMe->pev->view_ofs,pMe->pev->origin + pMe->pev->view_ofs + gpGlobals->v_forward * 8192,dont_ignore_monsters, pMe->edict(), &tr ); + if ( tr.flFraction != 1.0 && !FNullEnt( tr.pHit) ) + { + CBaseEntity *pHit = CBaseEntity::Instance( tr.pHit ); + return pHit; + } + return NULL; +} + + +BOOL CBasePlayer :: FlashlightIsOn( void ) +{ + return FBitSet(pev->effects, EF_DIMLIGHT); +} + + +void CBasePlayer :: FlashlightTurnOn( void ) +{ + if ( !g_pGameRules->FAllowFlashlight() ) + { + return; + } + + if ( (pev->weapons & (1<effects, EF_DIMLIGHT); + MESSAGE_BEGIN( MSG_ONE, gmsgFlashlight, NULL, pev ); + WRITE_BYTE(1); + WRITE_BYTE(m_iFlashBattery); + MESSAGE_END(); + + m_flFlashLightTime = FLASH_DRAIN_TIME + gpGlobals->time; + + } +} + + +void CBasePlayer :: FlashlightTurnOff( void ) +{ + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, SOUND_FLASHLIGHT_OFF, 1.0, ATTN_NORM, 0, PITCH_NORM ); + ClearBits(pev->effects, EF_DIMLIGHT); + MESSAGE_BEGIN( MSG_ONE, gmsgFlashlight, NULL, pev ); + WRITE_BYTE(0); + WRITE_BYTE(m_iFlashBattery); + MESSAGE_END(); + + m_flFlashLightTime = FLASH_CHARGE_TIME + gpGlobals->time; + +} + +/* +=============== +ForceClientDllUpdate + +When recording a demo, we need to have the server tell us the entire client state +so that the client side .dll can behave correctly. +Reset stuff so that the state is transmitted. +=============== +*/ +void CBasePlayer :: ForceClientDllUpdate( void ) +{ + m_iClientHealth = -1; + m_iClientBattery = -1; + m_iClientQuakeItems = -1; + m_iClientQuakeWeapon = -1; + m_iTrain |= TRAIN_NEW; // Force new train message. + m_fWeapon = FALSE; // Force weapon send + m_fKnownItem = FALSE; // Force weaponinit messages. + m_fInitHUD = TRUE; // Force HUD gmsgResetHUD message + + // Now force all the necessary messages + // to be sent. + UpdateClientData(); +} + +/* +============ +ImpulseCommands +============ +*/ +extern float g_flWeaponCheat; + +void CBasePlayer::ImpulseCommands( ) +{ + TraceResult tr;// UNDONE: kill me! This is temporary for PreAlpha CDs + + // Handle use events + PlayerUse(); + + int iImpulse = (int)pev->impulse; + + // QUAKECLASSIC + // Handle weapon switches first +/* if (iImpulse >= 1 && iImpulse <= 8) + { + W_ChangeWeapon( iImpulse ); + } + else + {*/ + switch (iImpulse) + { + + // QUAKECLASSIC + case 10: + W_CycleWeaponCommand(); + break; + case 12: + W_CycleWeaponReverseCommand(); + break; + + case 99: + { + + int iOn; + + if (!gmsgLogo) + { + iOn = 1; + gmsgLogo = REG_USER_MSG("Logo", 1); + } + else + { + iOn = 0; + } + + ASSERT( gmsgLogo > 0 ); + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgLogo, NULL, pev ); + WRITE_BYTE(iOn); + MESSAGE_END(); + + if(!iOn) + gmsgLogo = 0; + break; + } + /* case 100: + // temporary flashlight for level designers + if ( FlashlightIsOn() ) + { + FlashlightTurnOff(); + } + else + { + FlashlightTurnOn(); + } + break;*/ + + case 201:// paint decal + + if ( gpGlobals->time < m_flNextDecalTime ) + { + // too early! + break; + } + + UTIL_MakeVectors(pev->v_angle); + UTIL_TraceLine ( pev->origin + pev->view_ofs, pev->origin + pev->view_ofs + gpGlobals->v_forward * 128, ignore_monsters, ENT(pev), & tr); + + if ( tr.flFraction != 1.0 ) + {// line hit something, so paint a decal + m_flNextDecalTime = gpGlobals->time + CVAR_GET_FLOAT("decalfrequency"); + CSprayCan *pCan = GetClassPtr((CSprayCan *)NULL); + pCan->Spawn( pev ); + } + + break; + + default: + // check all of the cheat impulse commands now + CheatImpulseCommands( iImpulse ); + break; + } + //} + + pev->impulse = 0; +} + +//========================================================= +//========================================================= +void CBasePlayer::CheatImpulseCommands( int iImpulse ) +{ +#if !defined( HLDEMO_BUILD ) + if ( g_flWeaponCheat == 0.0 ) + { + return; + } + + CBaseEntity *pEntity; + TraceResult tr; + + switch ( iImpulse ) + { + case 76: + { + if (!giPrecacheGrunt) + { + giPrecacheGrunt = 1; + ALERT(at_console, "You must now restart to use Grunt-o-matic.\n"); + } + else + { + UTIL_MakeVectors( Vector( 0, pev->v_angle.y, 0 ) ); + Create("monster_human_grunt", pev->origin + gpGlobals->v_forward * 128, pev->angles); + } + break; + } + + + case 101: + gEvilImpulse101 = TRUE; + m_iAmmoNails = 200; + m_iAmmoShells = 100; + m_iAmmoRockets = 100; + m_iAmmoCells = 100; + m_iQuakeItems |= IT_NAILGUN | IT_SUPER_NAILGUN | IT_SUPER_SHOTGUN | IT_ROCKET_LAUNCHER | IT_GRENADE_LAUNCHER | IT_LIGHTNING; + CheckAmmo(); + W_SetCurrentAmmo(); + gEvilImpulse101 = FALSE; + break; + + case 102: + // Gibbage!!! + CGib::SpawnRandomGibs( pev, 1, 1 ); + break; + + case 103: + // What the hell are you doing? + pEntity = FindEntityForward( this ); + if ( pEntity ) + { + CBaseMonster *pMonster = pEntity->MyMonsterPointer(); + if ( pMonster ) + pMonster->ReportAIState(); + } + break; + + case 104: + // Dump all of the global state varaibles (and global entity names) + gGlobalState.DumpGlobals(); + break; + + case 105:// player makes no sound for monsters to hear. + { + if ( m_fNoPlayerSound ) + { + ALERT ( at_console, "Player is audible\n" ); + m_fNoPlayerSound = FALSE; + } + else + { + ALERT ( at_console, "Player is silent\n" ); + m_fNoPlayerSound = TRUE; + } + break; + } + + case 106: + // Give me the classname and targetname of this entity. + pEntity = FindEntityForward( this ); + if ( pEntity ) + { + ALERT ( at_console, "Classname: %s", STRING( pEntity->pev->classname ) ); + + if ( !FStringNull ( pEntity->pev->targetname ) ) + { + ALERT ( at_console, " - Targetname: %s\n", STRING( pEntity->pev->targetname ) ); + } + else + { + ALERT ( at_console, " - TargetName: No Targetname\n" ); + } + + ALERT ( at_console, "Model: %s\n", STRING( pEntity->pev->model ) ); + if ( pEntity->pev->globalname ) + ALERT ( at_console, "Globalname: %s\n", STRING( pEntity->pev->globalname ) ); + } + break; + + case 107: + { + TraceResult tr; + + edict_t *pWorld = g_engfuncs.pfnPEntityOfEntIndex( 0 ); + + Vector start = pev->origin + pev->view_ofs; + Vector end = start + gpGlobals->v_forward * 1024; + UTIL_TraceLine( start, end, ignore_monsters, edict(), &tr ); + if ( tr.pHit ) + pWorld = tr.pHit; + const char *pTextureName = TRACE_TEXTURE( pWorld, start, end ); + if ( pTextureName ) + ALERT( at_console, "Texture: %s\n", pTextureName ); + } + break; + case 195:// show shortest paths for entire level to nearest node + { + Create("node_viewer_fly", pev->origin, pev->angles); + } + break; + case 196:// show shortest paths for entire level to nearest node + { + Create("node_viewer_large", pev->origin, pev->angles); + } + break; + case 197:// show shortest paths for entire level to nearest node + { + Create("node_viewer_human", pev->origin, pev->angles); + } + break; + case 199:// show nearest node and all connections + { + ALERT ( at_console, "%d\n", WorldGraph.FindNearestNode ( pev->origin, bits_NODE_GROUP_REALM ) ); + WorldGraph.ShowNodeConnections ( WorldGraph.FindNearestNode ( pev->origin, bits_NODE_GROUP_REALM ) ); + } + break; + case 202:// Random blood splatter + UTIL_MakeVectors(pev->v_angle); + UTIL_TraceLine ( pev->origin + pev->view_ofs, pev->origin + pev->view_ofs + gpGlobals->v_forward * 128, ignore_monsters, ENT(pev), & tr); + + if ( tr.flFraction != 1.0 ) + {// line hit something, so paint a decal + CBloodSplat *pBlood = GetClassPtr((CBloodSplat *)NULL); + pBlood->Spawn( pev ); + } + break; + case 203:// remove creature. + pEntity = FindEntityForward( this ); + if ( pEntity ) + { + if ( pEntity->pev->takedamage ) + pEntity->SetThink(&CBaseEntity::SUB_Remove); + } + break; + } +#endif // HLDEMO_BUILD +} + +// +// Add a weapon to the player (Item == Weapon == Selectable Object) +// +int CBasePlayer::AddPlayerItem( CBasePlayerItem *pItem ) +{ + CBasePlayerItem *pInsert; + + pInsert = m_rgpPlayerItems[pItem->iItemSlot()]; + + while (pInsert) + { + if (FClassnameIs( pInsert->pev, STRING( pItem->pev->classname) )) + { + if (pItem->AddDuplicate( pInsert )) + { + g_pGameRules->PlayerGotWeapon ( this, pItem ); + pItem->CheckRespawn(); + + // ugly hack to update clip w/o an update clip message + pInsert->UpdateItemInfo( ); + if (m_pActiveItem) + m_pActiveItem->UpdateItemInfo( ); + + pItem->Kill( ); + } + else if (gEvilImpulse101) + { + // FIXME: remove anyway for deathmatch testing + pItem->Kill( ); + } + return FALSE; + } + pInsert = pInsert->m_pNext; + } + + + if (pItem->AddToPlayer( this )) + { + g_pGameRules->PlayerGotWeapon ( this, pItem ); + pItem->CheckRespawn(); + + pItem->m_pNext = m_rgpPlayerItems[pItem->iItemSlot()]; + m_rgpPlayerItems[pItem->iItemSlot()] = pItem; + + // should we switch to this item? + if ( g_pGameRules->FShouldSwitchWeapon( this, pItem ) ) + { + SwitchWeapon( pItem ); + } + + return TRUE; + } + else if (gEvilImpulse101) + { + // FIXME: remove anyway for deathmatch testing + pItem->Kill( ); + } + return FALSE; +} + + + +int CBasePlayer::RemovePlayerItem( CBasePlayerItem *pItem ) +{ + if (m_pActiveItem == pItem) + { + ResetAutoaim( ); + pItem->Holster( ); + pItem->pev->nextthink = 0;// crowbar may be trying to swing again, etc. + pItem->SetThink( NULL ); + m_pActiveItem = NULL; + pev->viewmodel = 0; + pev->weaponmodel = 0; + } + else if ( m_pLastItem == pItem ) + m_pLastItem = NULL; + + CBasePlayerItem *pPrev = m_rgpPlayerItems[pItem->iItemSlot()]; + + if (pPrev == pItem) + { + m_rgpPlayerItems[pItem->iItemSlot()] = pItem->m_pNext; + return TRUE; + } + else + { + while (pPrev && pPrev->m_pNext != pItem) + { + pPrev = pPrev->m_pNext; + } + if (pPrev) + { + pPrev->m_pNext = pItem->m_pNext; + return TRUE; + } + } + return FALSE; +} + + +// +// Returns the unique ID for the ammo, or -1 if error +// +int CBasePlayer :: GiveAmmo( int iCount, char *szName, int iMax ) +{ + if ( !szName ) + { + // no ammo. + return -1; + } + + if ( !g_pGameRules->CanHaveAmmo( this, szName, iMax ) ) + { + // game rules say I can't have any more of this ammo type. + return -1; + } + + int i = 0; + + i = GetAmmoIndex( szName ); + + if ( i < 0 || i >= MAX_AMMO_SLOTS ) + return -1; + + int iAdd = min( iCount, iMax - m_rgAmmo[i] ); + if ( iAdd < 1 ) + return i; + + m_rgAmmo[ i ] += iAdd; + + + if ( gmsgAmmoPickup ) // make sure the ammo messages have been linked first + { + // Send the message that ammo has been picked up + MESSAGE_BEGIN( MSG_ONE, gmsgAmmoPickup, NULL, pev ); + WRITE_BYTE( GetAmmoIndex(szName) ); // ammo ID + WRITE_BYTE( iAdd ); // amount + MESSAGE_END(); + } + + return i; +} + + +/* +============ +ItemPreFrame + +Called every frame by the player PreThink +============ +*/ +void CBasePlayer::ItemPreFrame() +{ + if ( m_flNextAttack > 0 ) + { + return; + } + + if (!m_pActiveItem) + return; + + m_pActiveItem->ItemPreFrame( ); +} + + +/* +============ +ItemPostFrame + +Called every frame by the player PostThink +============ +*/ +void CBasePlayer::ItemPostFrame() +{ + // check if the player is using a tank + if ( m_pTank != 0 ) + return; + + // HACKHACK: To make the axe fire 0.3 sec after fire is pressed + // See if the axe should fire now + if (m_flAxeFire && m_flAxeFire <= gpGlobals->time) + { + m_flAxeFire = 0; + W_FireAxe(); + } + + if ( m_flNextAttack > 0 ) + { + return; + } + + ImpulseCommands(); + + if (!m_pActiveItem) + return; + + m_pActiveItem->ItemPostFrame( ); +} + +int CBasePlayer::AmmoInventory( int iAmmoIndex ) +{ + if (iAmmoIndex == -1) + { + return -1; + } + + return m_rgAmmo[ iAmmoIndex ]; +} + +int CBasePlayer::GetAmmoIndex(const char *psz) +{ + int i; + + if (!psz) + return -1; + + for (i = 1; i < MAX_AMMO_SLOTS; i++) + { + if ( !CBasePlayerItem::AmmoInfoArray[i].pszName ) + continue; + + if (stricmp( psz, CBasePlayerItem::AmmoInfoArray[i].pszName ) == 0) + return i; + } + + return -1; +} + +// Called from UpdateClientData +// makes sure the client has all the necessary ammo info, if values have changed +void CBasePlayer::SendAmmoUpdate(void) +{ + for (int i=0; i < MAX_AMMO_SLOTS;i++) + { + if (m_rgAmmo[i] != m_rgAmmoLast[i]) + { + m_rgAmmoLast[i] = m_rgAmmo[i]; + + ASSERT( m_rgAmmo[i] >= 0 ); + ASSERT( m_rgAmmo[i] < 255 ); + + // send "Ammo" update message + MESSAGE_BEGIN( MSG_ONE, gmsgAmmoX, NULL, pev ); + WRITE_BYTE( i ); + WRITE_BYTE( max( min( m_rgAmmo[i], 254 ), 0 ) ); // clamp the value to one byte + MESSAGE_END(); + } + } +} + +/* +========================================================= + UpdateClientData + +resends any changed player HUD info to the client. +Called every frame by PlayerPreThink +Also called at start of demo recording and playback by +ForceClientDllUpdate to ensure the demo gets messages +reflecting all of the HUD state info. +========================================================= +*/ +void CBasePlayer :: UpdateClientData( void ) +{ + if (m_fInitHUD) + { + m_fInitHUD = FALSE; + gInitHUD = FALSE; + + MESSAGE_BEGIN( MSG_ONE, gmsgResetHUD, NULL, pev ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + + if ( !m_fGameHUDInitialized ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgInitHUD, NULL, pev ); + WRITE_BYTE ( g_iTeleNum ); + for ( int i = 0; i < g_iTeleNum; i++ ) + { + WRITE_COORD( g_vecTeleMins[ i ].x ); + WRITE_COORD( g_vecTeleMins[ i ].y ); + WRITE_COORD( g_vecTeleMins[ i ].z ); + + WRITE_COORD( g_vecTeleMaxs[ i ].x ); + WRITE_COORD( g_vecTeleMaxs[ i ].y ); + WRITE_COORD( g_vecTeleMaxs[ i ].z ); + } + + CBaseEntity *pEntity = NULL; + pEntity = UTIL_FindEntityByClassname( pEntity, "env_fog" ); + + if ( pEntity ) + { + ALERT( at_console, "Map has fog!\n" ); + CClientFog *pFog = (CClientFog *)pEntity; + + //Send as bytes?. + WRITE_SHORT ( static_cast(pFog->pev->rendercolor.x) ); + WRITE_SHORT ( static_cast(pFog->pev->rendercolor.y) ); + WRITE_SHORT ( static_cast(pFog->pev->rendercolor.z) ); + WRITE_SHORT ( static_cast(pFog->m_iStartDist) ); + WRITE_SHORT ( static_cast(pFog->m_iEndDist) ); + } + else + ALERT( at_console, "Map doesn't have any fog!\n" ); + + + MESSAGE_END(); + + g_pGameRules->InitHUD( this ); + m_fGameHUDInitialized = TRUE; + if ( g_pGameRules->IsMultiplayer() ) + { + FireTargets( "game_playerjoin", this, this, USE_TOGGLE, 0 ); + } + } + FireTargets( "game_playerspawn", this, this, USE_TOGGLE, 0 ); + } + + if ( m_iHideHUD != m_iClientHideHUD ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgHideWeapon, NULL, pev ); + WRITE_BYTE( m_iHideHUD ); + MESSAGE_END(); + + m_iClientHideHUD = m_iHideHUD; + } + + if ( m_iFOV != m_iClientFOV ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgSetFOV, NULL, pev ); + WRITE_BYTE( m_iFOV ); + MESSAGE_END(); + + // cache FOV change at end of function, so weapon updates can see that FOV has changed + } + + // HACKHACK -- send the message to display the game title + if (gDisplayTitle) + { + MESSAGE_BEGIN( MSG_ONE, gmsgShowGameTitle, NULL, pev ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + gDisplayTitle = 0; + } + + if (pev->health != m_iClientHealth) + { + int iHealth = max( static_cast(pev->health), 0 ); // make sure that no negative health values are sent + + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgHealth, NULL, pev ); + WRITE_BYTE( iHealth ); + MESSAGE_END(); + + m_iClientHealth = static_cast(pev->health); + } + + // QUAKECLASSIC + // Send down the state of the QuakeItems + if ( m_iQuakeItems != m_iClientQuakeItems ) + { + // send "items" update message + MESSAGE_BEGIN( MSG_ONE, gmsgQItems, NULL, pev ); + WRITE_LONG( m_iQuakeItems ); + MESSAGE_END(); + + m_iClientQuakeItems = m_iQuakeItems; + } + + if (pev->armorvalue != m_iClientBattery) + { + m_iClientBattery = static_cast(pev->armorvalue); + + ASSERT( gmsgBattery > 0 ); + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgBattery, NULL, pev ); + WRITE_SHORT( (int)pev->armorvalue); + MESSAGE_END(); + } + + if (pev->dmg_take || pev->dmg_save || m_bitsHUDDamage != m_bitsDamageType) + { + // Comes from inside me if not set + Vector damageOrigin = pev->origin; + // send "damage" message + // causes screen to flash, and pain compass to show direction of damage + edict_t *other = pev->dmg_inflictor; + if ( other && UTIL_IsValidEntity(other) ) + { + CBaseEntity *pEntity = CBaseEntity::Instance(other); + if ( pEntity ) + damageOrigin = pEntity->Center(); + } + + // only send down damage type that have hud art + int visibleDamageBits = m_bitsDamageType & DMG_SHOWNHUD; + + MESSAGE_BEGIN( MSG_ONE, gmsgDamage, NULL, pev ); + WRITE_BYTE( static_cast(pev->dmg_save) ); + WRITE_BYTE( static_cast(pev->dmg_take) ); + WRITE_LONG( visibleDamageBits ); + WRITE_COORD( damageOrigin.x ); + WRITE_COORD( damageOrigin.y ); + WRITE_COORD( damageOrigin.z ); + MESSAGE_END(); + + pev->dmg_take = 0; + pev->dmg_save = 0; + m_bitsHUDDamage = m_bitsDamageType; + + // Clear off non-time-based damage indicators + m_bitsDamageType &= DMG_TIMEBASED; + } + + // Update Status Bar + if ( m_flNextSBarUpdateTime < gpGlobals->time ) + { + UpdateStatusBar(); + m_flNextSBarUpdateTime = gpGlobals->time + 0.2; + } + + if (m_iTrain & TRAIN_NEW) + { + ASSERT( gmsgTrain > 0 ); + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgTrain, NULL, pev ); + WRITE_BYTE(m_iTrain & 0xF); + MESSAGE_END(); + + m_iTrain &= ~TRAIN_NEW; + } + + int iShellIndex = GetAmmoIndex("shells"); + int iNailIndex = GetAmmoIndex("nails"); + int iRocketIndex = GetAmmoIndex("rockets"); + int iCellIndex = GetAmmoIndex("cells"); + + // + // New Weapon? + // + if (!m_fKnownItem) + { + m_fKnownItem = TRUE; + + // WeaponInit Message + // byte = # of weapons + // + // for each weapon: + // byte name str length (not including null) + // bytes... name + // byte Ammo Type + // byte Ammo2 Type + // byte bucket + // byte bucket pos + // byte flags + // ???? Icons + + // Send ALL the weapon info now + // QUAKECLASSIC + // Tell the client about the Quake weapons + for (int i = 1; i < 10; i++) + { + const char *pszName = NULL; + int iAmmoIndex = 0; + int iMaxAmmo = 0; + int iCurrentAmmo = 0; + int iBit = 0; + + switch( i ) + { + case 1: iBit = IT_AXE; break; + case 2: iBit = IT_SHOTGUN; break; + case 3: iBit = IT_SUPER_SHOTGUN; break; + case 4: iBit = IT_NAILGUN; break; + case 5: iBit = IT_SUPER_NAILGUN; break; + case 6: iBit = IT_GRENADE_LAUNCHER; break; + case 7: iBit = IT_ROCKET_LAUNCHER; break; + case 8: iBit = IT_LIGHTNING; break; + case 9: iBit = IT_EXTRA_WEAPON; break; + + default: + pszName = "Empty"; + } + + if ( ! ( m_iQuakeItems & iBit ) ) + continue; + + switch( i ) + { + case 1: pszName = "weapon_axe"; iAmmoIndex = -1; iBit |= IT_AXE; break; + case 2: pszName = "weapon_shotgun"; iAmmoIndex = iShellIndex; iBit |= IT_SHOTGUN; iCurrentAmmo = m_iAmmoShells ; iMaxAmmo = 100; break; + case 3: pszName = "weapon_doubleshotgun"; iAmmoIndex = iShellIndex; iBit |= IT_SUPER_SHOTGUN; iCurrentAmmo = m_iAmmoShells ; iMaxAmmo = 100; break; + case 4: pszName = "weapon_nailgun"; iAmmoIndex = iNailIndex; iBit |= IT_NAILGUN; iCurrentAmmo = m_iAmmoNails; iMaxAmmo = 200; break; + case 5: pszName = "weapon_supernail"; iAmmoIndex = iNailIndex; iBit |= IT_SUPER_NAILGUN; iCurrentAmmo = m_iAmmoNails; iMaxAmmo = 200; break; + case 6: pszName = "weapon_grenadel"; iAmmoIndex = iRocketIndex; iBit |= IT_GRENADE_LAUNCHER; iCurrentAmmo = m_iAmmoRockets; iMaxAmmo = 100; break; + case 7: pszName = "weapon_rocketl"; iAmmoIndex = iRocketIndex; iBit |= IT_ROCKET_LAUNCHER; iCurrentAmmo = m_iAmmoRockets; iMaxAmmo = 100; break; + case 8: pszName = "weapon_lightning"; iAmmoIndex = iCellIndex; iBit |= IT_LIGHTNING; iCurrentAmmo = m_iAmmoCells; iMaxAmmo = 100; break; + case 9: pszName = "weapon_grapple"; iAmmoIndex = -1; iBit |= IT_EXTRA_WEAPON; break; + + default: + pszName = "Empty"; + } + + MESSAGE_BEGIN( MSG_ONE, gmsgWeaponList, NULL, pev ); + WRITE_STRING(pszName); // string weapon name + WRITE_BYTE(iAmmoIndex); // byte Ammo Type + WRITE_BYTE( iMaxAmmo ); // byte Max Ammo 1 + WRITE_BYTE( -1 ); // byte Ammo2 Type + WRITE_BYTE( iCurrentAmmo ); // byte Max Ammo 2 + WRITE_BYTE( i ); // byte bucket + WRITE_BYTE( 0 ); // byte bucket pos + WRITE_LONG( iBit ); // byte id (bit index into pev->weapons) + + if ( i == 1 || i == 9 ) + WRITE_BYTE( 1 ); // We can select this one on empty + else + WRITE_BYTE( 0 ); // Can't select it when empty. + + MESSAGE_END(); + } + } + + + // QUAKECLASSIC + // HACKHACK: Make the HL ammo types equal the Quake ammo + m_rgAmmo[ iShellIndex ] = m_iAmmoShells; + m_rgAmmo[ iNailIndex ] = m_iAmmoNails; + m_rgAmmo[ iRocketIndex ] = m_iAmmoRockets; + m_rgAmmo[ iCellIndex ] = m_iAmmoCells; + SendAmmoUpdate(); + + // Update all the items + for ( int i = 0; i < MAX_ITEM_TYPES; i++ ) + { + if ( m_rgpPlayerItems[i] ) // each item updates it's successors + m_rgpPlayerItems[i]->UpdateClientData( this ); + } + + // Cache and client weapon change + m_pClientActiveItem = m_pActiveItem; + m_iClientFOV = m_iFOV; + + // QUAKECLASSIC + m_iClientQuakeWeapon = m_iQuakeWeapon; +} + + +//========================================================= +// FBecomeProne - Overridden for the player to set the proper +// physics flags when a barnacle grabs player. +//========================================================= +BOOL CBasePlayer :: FBecomeProne ( void ) +{ + m_afPhysicsFlags |= PFLAG_ONBARNACLE; + return TRUE; +} + +//========================================================= +// BarnacleVictimBitten - bad name for a function that is called +// by Barnacle victims when the barnacle pulls their head +// into its mouth. For the player, just die. +//========================================================= +void CBasePlayer :: BarnacleVictimBitten ( entvars_t *pevBarnacle ) +{ + TakeDamage ( pevBarnacle, pevBarnacle, pev->health + pev->armorvalue, DMG_SLASH | DMG_ALWAYSGIB ); +} + +//========================================================= +// BarnacleVictimReleased - overridden for player who has +// physics flags concerns. +//========================================================= +void CBasePlayer :: BarnacleVictimReleased ( void ) +{ + m_afPhysicsFlags &= ~PFLAG_ONBARNACLE; +} + + +//========================================================= +// Illumination +// return player light level plus virtual muzzle flash +//========================================================= +int CBasePlayer :: Illumination( void ) +{ + int iIllum = CBaseEntity::Illumination( ); + + iIllum += m_iWeaponFlash; + if (iIllum > 255) + return 255; + return iIllum; +} + + +void CBasePlayer :: EnableControl(BOOL fControl) +{ + if (!fControl) + pev->flags |= FL_FROZEN; + else + pev->flags &= ~FL_FROZEN; + +} + + +#define DOT_1DEGREE 0.9998476951564 +#define DOT_2DEGREE 0.9993908270191 +#define DOT_3DEGREE 0.9986295347546 +#define DOT_4DEGREE 0.9975640502598 +#define DOT_5DEGREE 0.9961946980917 +#define DOT_6DEGREE 0.9945218953683 +#define DOT_7DEGREE 0.9925461516413 +#define DOT_8DEGREE 0.9902680687416 +#define DOT_9DEGREE 0.9876883405951 +#define DOT_10DEGREE 0.9848077530122 +#define DOT_15DEGREE 0.9659258262891 +#define DOT_20DEGREE 0.9396926207859 +#define DOT_25DEGREE 0.9063077870367 + +//========================================================= +// Autoaim +// set crosshair position to point to enemey +//========================================================= +Vector CBasePlayer :: GetAutoaimVector( float flDelta ) +{ + if (g_iSkillLevel == SKILL_HARD) + { + UTIL_MakeVectors( pev->v_angle + pev->punchangle ); + return gpGlobals->v_forward; + } + + Vector vecSrc = GetGunPosition( ); + float flDist = 8192; + + // always use non-sticky autoaim + // UNDONE: use sever variable to chose! + if (1 || g_iSkillLevel == SKILL_MEDIUM) + { + m_vecAutoAim = Vector( 0, 0, 0 ); + // flDelta *= 0.5; + } + + BOOL m_fOldTargeting = m_fOnTarget; + Vector angles = AutoaimDeflection(vecSrc, flDist, flDelta ); + + // update ontarget if changed + if ( !g_pGameRules->AllowAutoTargetCrosshair() ) + m_fOnTarget = 0; + else if (m_fOldTargeting != m_fOnTarget) + { + m_pActiveItem->UpdateItemInfo( ); + } + + if (angles.x > 180) + angles.x -= 360; + if (angles.x < -180) + angles.x += 360; + if (angles.y > 180) + angles.y -= 360; + if (angles.y < -180) + angles.y += 360; + + if (angles.x > 25) + angles.x = 25; + if (angles.x < -25) + angles.x = -25; + if (angles.y > 12) + angles.y = 12; + if (angles.y < -12) + angles.y = -12; + + + // always use non-sticky autoaim + // UNDONE: use sever variable to chose! + if (0 || g_iSkillLevel == SKILL_EASY) + { + m_vecAutoAim = m_vecAutoAim * 0.67 + angles * 0.33; + } + else + { + m_vecAutoAim = angles * 0.9; + } + + // m_vecAutoAim = m_vecAutoAim * 0.99; + + // Don't send across network if sv_aim is 0 + if ( CVAR_GET_FLOAT( "sv_aim" ) != 0 ) + { + if ( m_vecAutoAim.x != m_lastx || + m_vecAutoAim.y != m_lasty ) + { + SET_CROSSHAIRANGLE( edict(), -m_vecAutoAim.x, m_vecAutoAim.y ); + + m_lastx = static_cast(m_vecAutoAim.x); + m_lasty = static_cast(m_vecAutoAim.y); + } + } + + // ALERT( at_console, "%f %f\n", angles.x, angles.y ); + + UTIL_MakeVectors( pev->v_angle + pev->punchangle + m_vecAutoAim ); + return gpGlobals->v_forward; +} + + +Vector CBasePlayer :: AutoaimDeflection( Vector &vecSrc, float flDist, float flDelta ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + float bestdot; + Vector bestdir; + edict_t *bestent; + TraceResult tr; + + if ( CVAR_GET_FLOAT("sv_aim") == 0 ) + { + m_fOnTarget = FALSE; + return g_vecZero; + } + + UTIL_MakeVectors( pev->v_angle + pev->punchangle + m_vecAutoAim ); + + // try all possible entities + bestdir = gpGlobals->v_forward; + bestdot = flDelta; // +- 10 degrees + bestent = NULL; + + m_fOnTarget = FALSE; + + UTIL_TraceLine( vecSrc, vecSrc + bestdir * flDist, dont_ignore_monsters, edict(), &tr ); + + + if ( tr.pHit && tr.pHit->v.takedamage != DAMAGE_NO) + { + // don't look through water + if (!((pev->waterlevel != 3 && tr.pHit->v.waterlevel == 3) + || (pev->waterlevel == 3 && tr.pHit->v.waterlevel == 0))) + { + if (tr.pHit->v.takedamage == DAMAGE_AIM) + m_fOnTarget = TRUE; + + return m_vecAutoAim; + } + } + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + Vector center; + Vector dir; + float dot; + + if ( pEdict->free ) // Not in use + continue; + + if (pEdict->v.takedamage != DAMAGE_AIM) + continue; + if (pEdict == edict()) + continue; +// if (pev->team > 0 && pEdict->v.team == pev->team) +// continue; // don't aim at teammate + if ( !g_pGameRules->ShouldAutoAim( this, pEdict ) ) + continue; + + pEntity = Instance( pEdict ); + if (pEntity == NULL) + continue; + + if (!pEntity->IsAlive()) + continue; + + // don't look through water + if ((pev->waterlevel != 3 && pEntity->pev->waterlevel == 3) + || (pev->waterlevel == 3 && pEntity->pev->waterlevel == 0)) + continue; + + center = pEntity->BodyTarget( vecSrc ); + + dir = (center - vecSrc).Normalize( ); + + // make sure it's in front of the player + if (DotProduct (dir, gpGlobals->v_forward ) < 0) + continue; + + dot = fabs( DotProduct (dir, gpGlobals->v_right ) ) + + fabs( DotProduct (dir, gpGlobals->v_up ) ) * 0.5; + + // tweek for distance + dot *= 1.0 + 0.2 * ((center - vecSrc).Length() / flDist); + + if (dot > bestdot) + continue; // to far to turn + + UTIL_TraceLine( vecSrc, center, dont_ignore_monsters, edict(), &tr ); + if (tr.flFraction != 1.0 && tr.pHit != pEdict) + { + // ALERT( at_console, "hit %s, can't see %s\n", STRING( tr.pHit->v.classname ), STRING( pEdict->v.classname ) ); + continue; + } + + // don't shoot at friends + if (IRelationship( pEntity ) < 0) + { + if ( !pEntity->IsPlayer() && !g_pGameRules->IsDeathmatch()) + // ALERT( at_console, "friend\n"); + continue; + } + + // can shoot at this one + bestdot = dot; + bestent = pEdict; + bestdir = dir; + } + + if (bestent) + { + bestdir = UTIL_VecToAngles (bestdir); + bestdir.x = -bestdir.x; + bestdir = bestdir - pev->v_angle - pev->punchangle; + + if (bestent->v.takedamage == DAMAGE_AIM) + m_fOnTarget = TRUE; + + return bestdir; + } + + return Vector( 0, 0, 0 ); +} + + +void CBasePlayer :: ResetAutoaim( ) +{ + if (m_vecAutoAim.x != 0 || m_vecAutoAim.y != 0) + { + m_vecAutoAim = Vector( 0, 0, 0 ); + SET_CROSSHAIRANGLE( edict(), 0, 0 ); + } + m_fOnTarget = FALSE; +} + +/* +============= +SetCustomDecalFrames + + UNDONE: Determine real frame limit, 8 is a placeholder. + Note: -1 means no custom frames present. +============= +*/ +void CBasePlayer :: SetCustomDecalFrames( int nFrames ) +{ + if (nFrames > 0 && + nFrames < 8) + m_nCustomSprayFrames = nFrames; + else + m_nCustomSprayFrames = -1; +} + +/* +============= +GetCustomDecalFrames + + Returns the # of custom frames this player's custom clan logo contains. +============= +*/ +int CBasePlayer :: GetCustomDecalFrames( void ) +{ + return m_nCustomSprayFrames; +} + + +//========================================================= +// DropPlayerItem - drop the named item, or if no name, +// the active item. +//========================================================= +void CBasePlayer::DropPlayerItem ( char *pszItemName ) +{ + if ( !g_pGameRules->IsMultiplayer() || (CVAR_GET_FLOAT("mp_weaponstay") > 0) ) + { + // no dropping in single player. + return; + } + + if ( !strlen( pszItemName ) ) + { + // if this string has no length, the client didn't type a name! + // assume player wants to drop the active item. + // make the string null to make future operations in this function easier + pszItemName = NULL; + } + + CBasePlayerItem *pWeapon; + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + pWeapon = m_rgpPlayerItems[ i ]; + + while ( pWeapon ) + { + if ( pszItemName ) + { + // try to match by name. + if ( !strcmp( pszItemName, STRING( pWeapon->pev->classname ) ) ) + { + // match! + break; + } + } + else + { + // trying to drop active item + if ( pWeapon == m_pActiveItem ) + { + // active item! + break; + } + } + + pWeapon = pWeapon->m_pNext; + } + + + // if we land here with a valid pWeapon pointer, that's because we found the + // item we want to drop and hit a BREAK; pWeapon is the item. + if ( pWeapon ) + { + g_pGameRules->GetNextBestWeapon( this, pWeapon ); + + UTIL_MakeVectors ( pev->angles ); + + pev->weapons &= ~(1<m_iId);// take item off hud + + CWeaponBox *pWeaponBox = (CWeaponBox *)CBaseEntity::Create( "weaponbox", pev->origin + gpGlobals->v_forward * 10, pev->angles, edict() ); + pWeaponBox->pev->angles.x = 0; + pWeaponBox->pev->angles.z = 0; + pWeaponBox->PackWeapon( pWeapon ); + pWeaponBox->pev->velocity = gpGlobals->v_forward * 300 + gpGlobals->v_forward * 100; + + // drop half of the ammo for this weapon. + int iAmmoIndex; + + iAmmoIndex = GetAmmoIndex ( pWeapon->pszAmmo1() ); // ??? + + if ( iAmmoIndex != -1 ) + { + // this weapon weapon uses ammo, so pack an appropriate amount. + if ( pWeapon->iFlags() & ITEM_FLAG_EXHAUSTIBLE ) + { + // pack up all the ammo, this weapon is its own ammo type + pWeaponBox->PackAmmo( MAKE_STRING(pWeapon->pszAmmo1()), m_rgAmmo[ iAmmoIndex ] ); + m_rgAmmo[ iAmmoIndex ] = 0; + + } + else + { + // pack half of the ammo + pWeaponBox->PackAmmo( MAKE_STRING(pWeapon->pszAmmo1()), m_rgAmmo[ iAmmoIndex ] / 2 ); + m_rgAmmo[ iAmmoIndex ] /= 2; + } + + } + + return;// we're done, so stop searching with the FOR loop. + } + } +} + +//========================================================= +// HasPlayerItem Does the player already have this item? +//========================================================= +BOOL CBasePlayer::HasPlayerItem( CBasePlayerItem *pCheckItem ) +{ + CBasePlayerItem *pItem = m_rgpPlayerItems[pCheckItem->iItemSlot()]; + + while (pItem) + { + if (FClassnameIs( pItem->pev, STRING( pCheckItem->pev->classname) )) + { + return TRUE; + } + pItem = pItem->m_pNext; + } + + return FALSE; +} + +//========================================================= +// HasNamedPlayerItem Does the player already have this item? +//========================================================= +BOOL CBasePlayer::HasNamedPlayerItem( const char *pszItemName ) +{ + CBasePlayerItem *pItem; + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + pItem = m_rgpPlayerItems[ i ]; + + while (pItem) + { + if ( !strcmp( pszItemName, STRING( pItem->pev->classname ) ) ) + { + return TRUE; + } + pItem = pItem->m_pNext; + } + } + + return FALSE; +} + +//========================================================= +// +//========================================================= +BOOL CBasePlayer :: SwitchWeapon( CBasePlayerItem *pWeapon ) +{ + if ( !pWeapon->CanDeploy() ) + { + return FALSE; + } + + ResetAutoaim( ); + + if (m_pActiveItem) + { + m_pActiveItem->Holster( ); + } + + m_pActiveItem = pWeapon; + pWeapon->Deploy( ); + + return TRUE; +} + +//========================================================= +// Dead HEV suit prop +//========================================================= +class CDeadHEV : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_HUMAN_MILITARY; } + + void KeyValue( KeyValueData *pkvd ); + + int m_iPose;// which sequence to display -- temporary, don't need to save + static char *m_szPoses[4]; +}; + +char *CDeadHEV::m_szPoses[] = { "deadback", "deadsitting", "deadstomach", "deadtable" }; + +void CDeadHEV::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "pose")) + { + m_iPose = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +LINK_ENTITY_TO_CLASS( monster_hevsuit_dead, CDeadHEV ); + +//========================================================= +// ********** DeadHEV SPAWN ********** +//========================================================= +void CDeadHEV :: Spawn( void ) +{ + PRECACHE_MODEL("models/player.mdl"); + SET_MODEL(ENT(pev), "models/player.mdl"); + + pev->effects = 0; + pev->yaw_speed = 8; + pev->sequence = 0; + pev->body = 1; + m_bloodColor = BLOOD_COLOR_RED; + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + + if (pev->sequence == -1) + { + ALERT ( at_console, "Dead hevsuit with bad pose\n" ); + pev->sequence = 0; + pev->effects = EF_BRIGHTFIELD; + } + + // Corpses have less health + pev->health = 8; + + MonsterInitDead(); +} + + +class CStripWeapons : public CPointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +private: +}; + +LINK_ENTITY_TO_CLASS( player_weaponstrip, CStripWeapons ); + +void CStripWeapons :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBasePlayer *pPlayer = NULL; + + if ( pActivator && pActivator->IsPlayer() ) + { + pPlayer = (CBasePlayer *)pActivator; + } + else if ( !g_pGameRules->IsDeathmatch() ) + { + pPlayer = (CBasePlayer *)CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) ); + } + + if ( pPlayer ) + pPlayer->RemoveAllItems( FALSE ); +} + + +class CRevertSaved : public CPointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT MessageThink( void ); + void EXPORT LoadThink( void ); + void KeyValue( KeyValueData *pkvd ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + inline float Duration( void ) { return pev->dmg_take; } + inline float HoldTime( void ) { return pev->dmg_save; } + inline float MessageTime( void ) { return m_messageTime; } + inline float LoadTime( void ) { return m_loadTime; } + + inline void SetDuration( float duration ) { pev->dmg_take = duration; } + inline void SetHoldTime( float hold ) { pev->dmg_save = hold; } + inline void SetMessageTime( float time ) { m_messageTime = time; } + inline void SetLoadTime( float time ) { m_loadTime = time; } + +private: + float m_messageTime; + float m_loadTime; +}; + +LINK_ENTITY_TO_CLASS( player_loadsaved, CRevertSaved ); + +TYPEDESCRIPTION CRevertSaved::m_SaveData[] = +{ + DEFINE_FIELD( CRevertSaved, m_messageTime, FIELD_FLOAT ), // These are not actual times, but durations, so save as floats + DEFINE_FIELD( CRevertSaved, m_loadTime, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CRevertSaved, CPointEntity ); + +void CRevertSaved :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "duration")) + { + SetDuration( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "holdtime")) + { + SetHoldTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "messagetime")) + { + SetMessageTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "loadtime")) + { + SetLoadTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CRevertSaved :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + UTIL_ScreenFadeAll( pev->rendercolor, Duration(), HoldTime(), static_cast(pev->renderamt), FFADE_OUT ); + pev->nextthink = gpGlobals->time + MessageTime(); + SetThink( &CRevertSaved::MessageThink ); +} + + +void CRevertSaved :: MessageThink( void ) +{ + UTIL_ShowMessageAll( STRING(pev->message) ); + float nextThink = LoadTime() - MessageTime(); + if ( nextThink > 0 ) + { + pev->nextthink = gpGlobals->time + nextThink; + SetThink( &CRevertSaved::LoadThink ); + } + else + LoadThink(); +} + + +void CRevertSaved :: LoadThink( void ) +{ + if ( !gpGlobals->deathmatch ) + { + SERVER_COMMAND("reload\n"); + } +} + + +//========================================================= +// Multiplayer intermission spots. +//========================================================= +class CInfoIntermission:public CPointEntity +{ + void Spawn( void ); + void Think( void ); +}; + +void CInfoIntermission::Spawn( void ) +{ + UTIL_SetOrigin( pev, pev->origin ); + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; + pev->v_angle = g_vecZero; + + pev->nextthink = gpGlobals->time + 2;// let targets spawn! + +} + +void CInfoIntermission::Think ( void ) +{ + edict_t *pTarget; + + // find my target + pTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) ); + + if ( !FNullEnt(pTarget) ) + { + pev->v_angle = UTIL_VecToAngles( (pTarget->v.origin - pev->origin).Normalize() ); + pev->v_angle.x = -pev->v_angle.x; + } +} + +LINK_ENTITY_TO_CLASS( info_intermission, CInfoIntermission ); + diff --git a/hlsdk/dmc/dlls/player.h b/hlsdk/dmc/dlls/player.h new file mode 100644 index 00000000..34952b5b --- /dev/null +++ b/hlsdk/dmc/dlls/player.h @@ -0,0 +1,436 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PLAYER_H +#define PLAYER_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_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. + +// +// Player PHYSICS FLAGS bits +// +#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. + +// +// 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" + +#define TEAM_NAME_LENGTH 16 + +typedef enum +{ + PLAYER_IDLE, + PLAYER_WALK, + PLAYER_JUMP, + PLAYER_SUPERJUMP, + PLAYER_DIE, + PLAYER_ATTACK1, +} PLAYER_ANIM; + +#define MAX_ID_RANGE 2048 +#define SBAR_STRING_SIZE 128 +enum sbar_data +{ +SBAR_ID_TARGETNAME = 1, +SBAR_ID_TARGETHEALTH, +SBAR_ID_TARGETARMOR, +SBAR_ID_TARGETTEAM, +SBAR_END, +}; + +#define PLAYER_MAX_SPEED 300 + +class CBasePlayer : public CBaseMonster +{ +public: + int random_seed; // See that is shared between client & server for shared weapons code + + int m_iPlayerSound;// the index of the sound list slot reserved for this player + int m_iTargetVolume;// ideal sound volume. + int m_iWeaponVolume;// how loud the player's weapon is right now. + int m_iExtraSoundTypes;// additional classification for this weapon's sound + int m_iWeaponFlash;// brightness of the weapon flash + float m_flStopExtraSoundTime; + + float m_flFlashLightTime; // Time until next battery draw/Recharge + int m_iFlashBattery; // Flashlight Battery Draw + + int m_afButtonLast; + int m_afButtonPressed; + int m_afButtonReleased; + + edict_t *m_pentSndLast; // last sound entity to modify player room type + float m_flSndRoomtype; // last roomtype set by sound entity + float m_flSndRange; // dist from player to sound entity + + float m_flFallVelocity; + + int m_rgItems[MAX_ITEMS]; + int m_fKnownItem; // True when a new item needs to be added + int m_fNewAmmo; // True when a new item has been added + + 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 + + +// these are time-sensitive things that we keep track of + float m_flTimeStepSound; // when the last stepping sound was made + float m_flTimeWeaponIdle; // when to play another weapon idle animation. + float m_flSwimTime; // how long player has been underwater + 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 + + int m_idrowndmg; // track drowning damage taken + int m_idrownrestored; // track drowning damage restored + + int m_bitsHUDDamage; // Damage bits for the current fame. These get sent to + // the hude via the DAMAGE message + BOOL m_fInitHUD; // True when deferred HUD restart msg needs to be sent + BOOL m_fGameHUDInitialized; + int m_iTrain; // Train control position + BOOL m_fWeapon; // Set this to FALSE to force a reset of the current weapon HUD info + + EHANDLE m_pTank; // the tank which the player is currently controlling, NULL if no tank + float m_fDeadTime; // the time at which the player died (used in PlayerDeathThink()) + + BOOL m_fNoPlayerSound; // a debugging feature. Player makes no sound if this is true. + BOOL m_fLongJump; // does this player have the longjump module? + + 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 + int m_iClientFOV; // client's known FOV + // usable player items + CBasePlayerItem *m_rgpPlayerItems[MAX_ITEM_TYPES]; + CBasePlayerItem *m_pActiveItem; + CBasePlayerItem *m_pClientActiveItem; // client version of the active item + CBasePlayerItem *m_pLastItem; + // shared ammo slots + int m_rgAmmo[MAX_AMMO_SLOTS]; + int m_rgAmmoLast[MAX_AMMO_SLOTS]; + + Vector m_vecAutoAim; + BOOL m_fOnTarget; + int m_iDeaths; + float m_iRespawnFrames; // used in PlayerDeathThink() to make sure players can always respawn + + int m_lastx, m_lasty; // These are the previous update's crosshair angles, DON"T SAVE/RESTORE + + int m_nCustomSprayFrames;// Custom clan logo frames for this player + float m_flNextDecalTime;// next time this player can spray a decal + + char m_szTeamName[TEAM_NAME_LENGTH]; + + virtual void Spawn( void ); + +// virtual void Think( void ); + virtual void Jump( void ); + virtual void Duck( void ); + virtual void PreThink( void ); + virtual void PostThink( 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 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 + virtual void StartSneaking( void ) { m_tSneaking = gpGlobals->time - 1; } + 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 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 + // Spectators should return TRUE for this + virtual const char *TeamID( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + void RenewItems(void); + void RemoveAllItems( BOOL removeSuit ); + BOOL SwitchWeapon( CBasePlayerItem *pWeapon ); + + // JOHN: sends custom messages if player HUD data has changed (eg health, ammo) + virtual void UpdateClientData( void ); + + static TYPEDESCRIPTION m_playerSaveData[]; + + // Player is moved across the transition by other means + virtual int ObjectCaps( void ) { return CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual void Precache( void ); + BOOL IsOnLadder( void ); + BOOL FlashlightIsOn( void ); + void FlashlightTurnOn( void ); + void FlashlightTurnOff( void ); + + void DeathSound ( void ); + + int Classify ( void ); + void SetAnimation( PLAYER_ANIM playerAnim ); + void SetWeaponAnimType( const char *szExtention ); + char m_szAnimExtention[32]; + + // custom player functions + virtual void ImpulseCommands( void ); + void CheatImpulseCommands( int iImpulse ); + + void StartDeathCam( void ); + void StartObserver( Vector vecPosition, Vector vecViewAngle ); + + void AddPoints( int score, BOOL bAllowNegativeScore ); + void AddPointsToTeam( int score, BOOL bAllowNegativeScore ); + BOOL AddPlayerItem( CBasePlayerItem *pItem ); + BOOL RemovePlayerItem( CBasePlayerItem *pItem ); + void DropPlayerItem ( char *pszItemName ); + BOOL HasPlayerItem( CBasePlayerItem *pCheckItem ); + BOOL HasNamedPlayerItem( const char *pszItemName ); + BOOL HasWeapons( void );// do I have ANY weapons? + void SelectPrevItem( int iItem ); + void SelectNextItem( int iItem ); + void SelectLastItem(void); + void SelectItem(const char *pstr); + void ItemPreFrame( void ); + void ItemPostFrame( void ); + void GiveNamedItem( const char *szName ); + void EnableControl(BOOL fControl); + + int GiveAmmo( int iAmount, char *szName, int iMax ); + void SendAmmoUpdate(void); + + void WaterMove( void ); + void EXPORT PlayerDeathThink( void ); + void PlayerUse( void ); + + void CheckSuitUpdate(); + void SetSuitUpdate(char *name, int fgroup, int iNoRepeat); + void UpdateGeigerCounter( void ); + void CheckTimeBasedDamage( void ); + void UpdateStepSound( void ); + void PlayStepSound(int step, float fvol); + + BOOL FBecomeProne ( void ); + void BarnacleVictimBitten ( entvars_t *pevBarnacle ); + void BarnacleVictimReleased ( void ); + static int GetAmmoIndex(const char *psz); + int AmmoInventory( int iAmmoIndex ); + int Illumination( void ); + + void ResetAutoaim( void ); + Vector GetAutoaimVector( float flDelta ); + 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 ); + + // Observer camera + void Observer_FindNextPlayer(); + void Observer_HandleButtons(); + void Observer_SetMode( int iMode ); + EHANDLE m_hObserverTarget; + float m_flNextObserverInput; + int IsObserver() { return pev->iuser1; }; + + + // QUAKECLASSIC + // Player + void Pain( CBaseEntity *pAttacker ); + float m_flPainSoundFinished; + + BOOL m_bHadFirstSpawn; // used to handle the MOTD + + // Weapon selection + int W_BestWeapon( void ); + void W_SetCurrentAmmo( int sendanim = 1 ); + BOOL W_CheckNoAmmo( void ); + void W_ChangeWeapon( int iWeaponNumber ); + void W_CycleWeaponCommand( void ); + void W_CycleWeaponReverseCommand( void ); + + // Weapon functionality + void Q_FireBullets(int iShots, Vector vecDir, Vector vecSpread); + void LightningDamage( Vector p1, Vector p2, CBaseEntity *pAttacker, float flDamage,Vector vecDir); + + // Weapons + void W_Attack( int iQuadSound ); + void W_FireAxe( void ); + void W_FireShotgun( int QuadSound ); + void W_FireSuperShotgun( int QuadSound ); + void W_FireRocket( int QuadSound ); + void W_FireLightning( int QuadSound ); + void W_FireGrenade( int QuadSound ); + void W_FireSuperSpikes( int QuadSound ); + void W_FireSpikes( int QuadSound ); + + // Ammunition + void CheckAmmo( void ); + int *m_pCurrentAmmo; // Always points to one of the four ammo counts below + int m_iAmmoRockets; + int m_iAmmoCells; + int m_iAmmoShells; + int m_iAmmoNails; + + // Backpacks + void DropBackpack( void ); + + // Weapons + void Deathmatch_Weapon(int iOldWeapon, int iNewWeapon); + int m_iQuakeWeapon; + int m_iClientQuakeWeapon; // The last status of the m_iQuakeWeapon sent to the client. + int m_iQuakeItems; + int m_iClientQuakeItems; // The last status of the m_iQuakeItems sent to the client. + int m_iWeaponSwitch; + int m_iBackpackSwitch; + int m_iAutoWepSwitch; + + // Weapon Data + float m_flAxeFire; + float m_flLightningTime; + int m_iNailOffset; + float m_flNextQuadSound; + + // Powerups + float m_flSuperDamageFinished; + float m_flInvincibleFinished; + float m_flInvisibleFinished; + float m_flRadsuitFinished; + void PowerUpThink( void ); //Checks powerup timers and hadles their effects + char m_chOldModel[64]; //Save the player's model here + bool m_bPlayedQuadSound; + bool m_bPlayedEnvSound; + bool m_bPlayedInvSound; + bool m_bPlayedProtectSound; + + BOOL m_bLostInvincSound; + BOOL m_bLostInvisSound; + BOOL m_bLostSuperSound; + BOOL m_bLostRadSound; + float m_fInvincSound; + float m_fSuperSound; + + void InitStatusBar( void ); + void UpdateStatusBar( void ); + int m_izSBarState[ SBAR_END ]; + float m_flNextSBarUpdateTime; + float m_flStatusBarDisappearDelay; + char m_SbarString0[ SBAR_STRING_SIZE ]; + char m_SbarString1[ SBAR_STRING_SIZE ]; + + unsigned short m_usShotgunSingle; + unsigned short m_usShotgunDouble; + unsigned short m_usAxe; + unsigned short m_usAxeSwing; + unsigned short m_usRocket; + unsigned short m_usGrenade; + unsigned short m_usLightning; + unsigned short m_usSpike; + unsigned short m_usSuperSpike; +}; + +#define AUTOAIM_2DEGREES 0.0348994967025 +#define AUTOAIM_5DEGREES 0.08715574274766 +#define AUTOAIM_8DEGREES 0.1391731009601 +#define AUTOAIM_10DEGREES 0.1736481776669 + +// QUAKECLASSIC +#define Q_SMALL_PUNCHANGLE_KICK -2 +#define Q_BIG_PUNCHANGLE_KICK -4 + +#define IT_AXE (1 << 0) +#define IT_SHOTGUN (1 << 1) +#define IT_SUPER_SHOTGUN (1 << 2) +#define IT_NAILGUN (1 << 3) +#define IT_SUPER_NAILGUN (1 << 4) +#define IT_GRENADE_LAUNCHER (1 << 5) +#define IT_ROCKET_LAUNCHER (1 << 6) +#define IT_LIGHTNING (1 << 7) +#define IT_EXTRA_WEAPON (1 << 8) + +#define IT_SHELLS (1 << 9) +#define IT_NAILS (1 << 10) +#define IT_ROCKETS (1 << 11) +#define IT_CELLS (1 << 12) + +#define IT_ARMOR1 (1 << 13) +#define IT_ARMOR2 (1 << 14) +#define IT_ARMOR3 (1 << 15) +#define IT_SUPERHEALTH (1 << 16) + +#define IT_KEY1 (1 << 17) +#define IT_KEY2 (1 << 18) + +#define IT_INVISIBILITY (1 << 19) +#define IT_INVULNERABILITY (1 << 20) +#define IT_SUIT (1 << 21) +#define IT_QUAD (1 << 22) + +extern int gmsgHudText; +extern BOOL gInitHUD; + +#define MAX_TELES 256 + +#endif // PLAYER_H diff --git a/hlsdk/dmc/dlls/quake_gun.cpp b/hlsdk/dmc/dlls/quake_gun.cpp new file mode 100644 index 00000000..ff550ff8 --- /dev/null +++ b/hlsdk/dmc/dlls/quake_gun.cpp @@ -0,0 +1,174 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== quake_gun.cpp ======================================================== + + This is a half-life weapon that fires every one of the quake weapons. + It's automatically given to all players. + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "quake_gun.h" + +LINK_ENTITY_TO_CLASS( weapon_quakegun, CQuakeGun ); + + +//=========================================================== +void CQuakeGun::Spawn( ) +{ + Precache( ); + SET_MODEL(ENT(pev), "models/v_crowbar.mdl"); + m_iDefaultAmmo = GLOCK_DEFAULT_GIVE; + FallInit(); +} + +void CQuakeGun::Precache( void ) +{ + PRECACHE_MODEL("models/v_crowbar.mdl"); + PRECACHE_MODEL("models/p_9mmhandgun.mdl"); +} + +int CQuakeGun::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = NULL; + p->iMaxAmmo1 = -1; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = -1; + p->iSlot = 1; + p->iPosition = 1; + p->iFlags = 0; + p->iId = m_iId = WEAPON_GLOCK; + p->iWeight = GLOCK_WEIGHT; + + return 1; +} + +void CQuakeGun::DestroyEffect( void ) +{ + +#ifndef CLIENT_DLL + if ( m_pBeam ) + { + UTIL_Remove( m_pBeam ); + m_pBeam = NULL; + } +#endif + +} + +void CQuakeGun::CreateEffect( void ) +{ + +#ifndef CLIENT_DLL + DestroyEffect(); + + m_pBeam = CBeam::BeamCreate( "sprites/laserbeam.spr", 40 ); + m_pBeam->PointEntInit( pev->origin, m_pPlayer->entindex() ); + m_pBeam->SetBrightness( 100 ); + m_pBeam->SetEndAttachment( 1 ); + m_pBeam->pev->spawnflags |= SF_BEAM_TEMPORARY; // Flag these to be destroyed on save/restore or level transition + m_pBeam->pev->flags |= FL_SKIPLOCALHOST; + m_pBeam->pev->owner = m_pPlayer->edict(); + + m_pBeam->SetScrollRate( 110 ); + m_pBeam->SetNoise( 5 ); +#endif + +} + +void CQuakeGun::UpdateEffect( void ) +{ +#if !defined( CLIENT_DLL ) + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + Vector vecAiming = gpGlobals->v_forward; + Vector vecSrc = m_pPlayer->GetGunPosition( ); + + Vector vecDest = vecSrc + vecAiming * 2048; + edict_t *pentIgnore; + TraceResult tr; + + pentIgnore = m_pPlayer->edict(); + Vector tmpSrc = vecSrc + gpGlobals->v_up * -8 + gpGlobals->v_right * 3; + + // ALERT( at_console, "." ); + + UTIL_TraceLine( vecSrc, vecDest, dont_ignore_monsters, pentIgnore, &tr ); + + if (tr.fAllSolid) + return; + + if ( !m_pBeam ) + { + CreateEffect(); + } + + m_pBeam->SetStartPos( tr.vecEndPos ); +#endif + +} + +#if !defined( CLIENT_DLL ) +BOOL CQuakeGun::Deploy( ) +{ + m_pPlayer->pev->viewmodel = MAKE_STRING("models/v_crowbar.mdl"); + m_pPlayer->pev->weaponmodel = MAKE_STRING("models/p_9mmhandgun.mdl"); + strcpy( m_pPlayer->m_szAnimExtention, "onehanded" ); + +#ifdef CLIENT_DLL + g_flLightTime = 0.0; +#endif + + SendWeaponAnim( 0 ); + return TRUE; +} +#endif + +// Plays quad sound if needed +int CQuakeGun::SuperDamageSound() +{ + if ( m_pPlayer->m_iQuakeItems & IT_QUAD ) + { + if ( m_pPlayer->m_flNextQuadSound < gpGlobals->time) + { + m_pPlayer->m_flNextQuadSound = gpGlobals->time + 1; + return 1; + } + } + + return 0; +} + +// Firing the Quakegun forces the player to fire the appropriate weapon +void CQuakeGun::PrimaryAttack( void ) +{ + int iQuadSound = 0; + iQuadSound = SuperDamageSound(); + m_pPlayer->W_Attack( iQuadSound ); + +#if !defined( CLIENT_DLL ) + if ( m_pPlayer->m_iQuakeWeapon == IT_LIGHTNING && m_pPlayer->pev->deadflag == DEAD_NO ) + UpdateEffect(); +#endif + + m_bPlayedIdleAnim = FALSE; +} diff --git a/hlsdk/dmc/dlls/quake_gun.h b/hlsdk/dmc/dlls/quake_gun.h new file mode 100644 index 00000000..a0d16ec5 --- /dev/null +++ b/hlsdk/dmc/dlls/quake_gun.h @@ -0,0 +1,44 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== quake_gun.h ======================================================== + + This is a half-life weapon that fires every one of the quake weapons. + It's automatically given to all players. + +*/ +#include "effects.h" + +class CQuakeGun : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 1; } + int GetItemInfo(ItemInfo *p); + + int SuperDamageSound( void ); + + void PrimaryAttack( void ); + BOOL Deploy( void ); + + void UpdateEffect( void ); + + void CreateEffect ( void ); + void DestroyEffect ( void ); + + CBeam *m_pBeam; +}; diff --git a/hlsdk/dmc/dlls/quake_items.cpp b/hlsdk/dmc/dlls/quake_items.cpp new file mode 100644 index 00000000..8d7c4f05 --- /dev/null +++ b/hlsdk/dmc/dlls/quake_items.cpp @@ -0,0 +1,1866 @@ +//=========== (C) Copyright 1996-2002, 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: Quake world items +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "shake.h" +#include "../engine/studio.h" +#include "weapons.h" +#include "quake_gun.h" +#include "hltv.h" + +extern unsigned short g_usPowerUp; + +class CQuakeItem : public CBaseEntity +{ +public: + void Spawn( void ); + + // Respawning + void EXPORT Materialize( void ); + void Respawn( float flTime ); + + virtual void SetObjectCollisionBox ( void ); + + // Touch + void EXPORT ItemTouch( CBaseEntity *pOther ); + virtual BOOL MyTouch( CBasePlayer *pOther ) { return FALSE; }; + + float m_flRespawnTime; +}; + +//----------------------------------------------------------------------------- +// Purpose: Spawn and drop to the floor +//----------------------------------------------------------------------------- + +void CQuakeItem :: SetObjectCollisionBox( void ) +{ + pev->absmin = pev->origin + Vector(-32, -32, 0); + pev->absmax = pev->origin + Vector(32, 32, 56); +} + +void CQuakeItem::Spawn() +{ + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + + SetTouch(&CQuakeItem::ItemTouch); + + if (DROP_TO_FLOOR(ENT(pev)) == 0) + { + ALERT(at_error, "Item %s fell out of level at %f,%f,%f", STRING( pev->classname ), pev->origin.x, pev->origin.y, pev->origin.z); + UTIL_Remove( this ); + return; + } + + //UTIL_SetOrigin( pev, pev->origin + Vector(0,0,16) ); + + if (!m_flRespawnTime) + m_flRespawnTime = 20; +} + +//----------------------------------------------------------------------------- +// Purpose: Bring the item back +//----------------------------------------------------------------------------- +void CQuakeItem::Materialize() +{ + // Become visible and touchable + pev->effects &= ~EF_NODRAW; + SetTouch( &CQuakeItem::ItemTouch ); + + // Play respawn sound + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "items/itembk2.wav", 1, ATTN_NORM ); +} + +//----------------------------------------------------------------------------- +// Purpose: Setup the item's respawn in the time set +//----------------------------------------------------------------------------- +void CQuakeItem::Respawn( float flTime ) +{ + pev->effects |= EF_NODRAW; + SetTouch( NULL ); + + // Come back in time + SetThink ( &CQuakeItem::Materialize ); + pev->nextthink = gpGlobals->time + flTime; +} + + +//----------------------------------------------------------------------------- +// Purpose: Touch function that calls the virtual touch function +//----------------------------------------------------------------------------- +void CQuakeItem::ItemTouch( CBaseEntity *pOther ) +{ + // if it's not a player, ignore + if ( !pOther->IsPlayer() ) + return; + + //Dead? + if (pOther->pev->health <= 0) + return; + + CBasePlayer *pPlayer = (CBasePlayer *)pOther; + + // Call the virtual touch function + if ( MyTouch( pPlayer ) ) + { + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); + + // Respawn if it's not DM==2 + if (gpGlobals->deathmatch != 2) + { + Respawn( m_flRespawnTime ); + } + else + { + UTIL_Remove( this ); + } + } +} + +//====================================================================================== +// HEALTH ITEMS +//====================================================================================== +#define H_ROTTEN 1 +#define H_MEGA 2 + +class CItemHealth : public CQuakeItem +{ +public: + void Spawn( void ); + void Precache( void ); + BOOL MyTouch( CBasePlayer *pPlayer ); + void EXPORT MegahealthRot( void ); + + EHANDLE m_hRotTarget; + int m_iHealAmount; + int m_iHealType; +}; +LINK_ENTITY_TO_CLASS(item_health, CItemHealth); + +//-------------------------------------------- +// Spawn +void CItemHealth::Spawn( void ) +{ + Precache(); + + // Setup healing method + if (pev->spawnflags & H_ROTTEN) + { + SET_MODEL(ENT(pev), "models/w_medkits.mdl"); + pev->noise = MAKE_STRING( "items/r_item1.wav" ); + m_iHealAmount = 15; + m_iHealType = H_ROTTEN; + } + else if (pev->spawnflags & H_MEGA) + { + SET_MODEL(ENT(pev), "models/w_medkitl.mdl"); + pev->noise = MAKE_STRING( "items/r_item2.wav" ); + m_iHealAmount = 100; + m_iHealType = H_MEGA; + } + else + { + SET_MODEL(ENT(pev), "models/w_medkit.mdl"); + pev->noise = MAKE_STRING( "items/health1.wav" ); + m_iHealAmount = 25; + m_iHealType = H_ROTTEN; + } + + CQuakeItem::Spawn(); +} + +//-------------------------------------------- +// Precache +void CItemHealth::Precache() +{ + PRECACHE_MODEL("models/w_medkitl.mdl"); + PRECACHE_MODEL("models/w_medkits.mdl"); + PRECACHE_MODEL("models/w_medkit.mdl"); + PRECACHE_SOUND("items/r_item1.wav"); + PRECACHE_SOUND("items/r_item2.wav"); + PRECACHE_SOUND("items/health1.wav"); +} + +//-------------------------------------------- +// Health Touch +BOOL CItemHealth::MyTouch( CBasePlayer *pPlayer ) +{ + // Don't heal in DM==4 if they're invincible + if (gpGlobals->deathmatch == 4 && pPlayer->m_flInvincibleFinished > 0) + return FALSE; + + if (pPlayer->pev->health <= 0) + return FALSE; + + if (m_iHealType == H_MEGA) + { + if (pPlayer->pev->health >= 250) + return FALSE; + if ( !pPlayer->TakeHealth( m_iHealAmount, DMG_GENERIC | DMG_IGNORE_MAXHEALTH) ) + return FALSE; + } + else + { + // Heal the Player + if ( !pPlayer->TakeHealth( m_iHealAmount, DMG_GENERIC ) ) + return FALSE; + } + + ClientPrint( pPlayer->pev, HUD_PRINTNOTIFY, "#Get_Health", UTIL_dtos1(m_iHealAmount) ); + EMIT_SOUND( ENT(pev), CHAN_ITEM, STRING(pev->noise), 1, ATTN_NORM ); + + // Setup for respawn + if (m_iHealType == H_MEGA) + { + // Go invisible and fire targets + pev->effects |= EF_NODRAW; + SetTouch( NULL ); + SUB_UseTargets( pPlayer, USE_TOGGLE, 0 ); + + pPlayer->m_iQuakeItems |= IT_SUPERHEALTH; + if (gpGlobals->deathmatch != 4) + { + SetThink( &CItemHealth::MegahealthRot ); + pev->nextthink = gpGlobals->time + 5; + } + m_hRotTarget = pPlayer; + + // Return FALSE, because we want to handle our respawn ourselves + return FALSE; + } + + // Respawn as normal + return TRUE; +} + +//-------------------------------------------- +// Megahealth Rot function. Reduce player's health until it's below 100. Then respawn. +void CItemHealth::MegahealthRot( void ) +{ + if (m_hRotTarget) + { + CBasePlayer *pPlayer = ((CBasePlayer *)((CBaseEntity *)m_hRotTarget)); + + if (pPlayer->pev->health > pPlayer->pev->max_health ) + { + pPlayer->pev->health--; + pev->nextthink = gpGlobals->time + 1; + return; + } + + pPlayer->m_iQuakeItems &= ~IT_SUPERHEALTH; + } + + // Respawn if it's not DM==2 + if (gpGlobals->deathmatch != 2) + { + SetThink ( &CQuakeItem::Materialize ); + pev->nextthink = gpGlobals->time + 20; + } + else + { + UTIL_Remove( this ); + } +} + +//====================================================================================== +// ARMOR ITEMS +//====================================================================================== +class CItemArmor : public CQuakeItem +{ +public: + BOOL MyTouch( CBasePlayer *pPlayer ); + + float m_flArmorValue; + float m_flArmorType; + int m_iArmorBit; +}; + +// Armor Touch +BOOL CItemArmor::MyTouch( CBasePlayer *pPlayer ) +{ + if (pPlayer->pev->health <= 0) + return FALSE; + + // Don't pickup in DM==4 if they're invincible + if (gpGlobals->deathmatch == 4 && pPlayer->m_flInvincibleFinished > 0) + return FALSE; + + // Don't pickup if this armor isn't as good as the stuff we've got + if ( (pPlayer->pev->armortype * pPlayer->pev->armorvalue) >= (m_flArmorType * m_flArmorValue) ) + return FALSE; + + pPlayer->pev->armortype = m_flArmorType; + pPlayer->pev->armorvalue = m_flArmorValue; + pPlayer->m_iQuakeItems &= ~(IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3); + pPlayer->m_iQuakeItems |= m_iArmorBit; + + EMIT_SOUND( ENT( pPlayer->pev ), CHAN_ITEM, "items/armor1.wav", 1, ATTN_NORM ); + + return TRUE; +} + +//=============== +// Green Armor +class CItemArmorGreen : public CItemArmor +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(item_armor1, CItemArmorGreen); + +// Spawn +void CItemArmorGreen::Spawn( void ) +{ + Precache(); + SET_MODEL(ENT(pev), "models/armour_g.mdl"); + CItemArmor::Spawn(); + + m_flArmorValue = 100; + m_flArmorType = 0.3; + m_iArmorBit = IT_ARMOR1; +} + +// Precache +void CItemArmorGreen::Precache( void ) +{ + PRECACHE_MODEL( "models/armour_g.mdl" ); + PRECACHE_SOUND( "items/armor1.wav" ); +} + +//=============== +// Yellow Armor +class CItemArmorYellow : public CItemArmor +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(item_armor2, CItemArmorYellow); + +// Spawn +void CItemArmorYellow::Spawn( void ) +{ + Precache(); + SET_MODEL(ENT(pev), "models/armour_y.mdl"); + CItemArmor::Spawn(); + + m_flArmorValue = 150; + m_flArmorType = 0.6; + m_iArmorBit = IT_ARMOR2; +} + +// Precache +void CItemArmorYellow::Precache( void ) +{ + PRECACHE_MODEL( "models/armour_y.mdl" ); + PRECACHE_SOUND( "items/armor1.wav" ); +} + +//=============== +// Red Armor +class CItemArmorRed : public CItemArmor +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(item_armor3, CItemArmorRed); +LINK_ENTITY_TO_CLASS(item_armorInv, CItemArmorRed); + +// Spawn +void CItemArmorRed::Spawn( void ) +{ + Precache(); + SET_MODEL(ENT(pev), "models/armour_r.mdl"); + CItemArmor::Spawn(); + + m_flArmorValue = 200; + m_flArmorType = 0.8; + m_iArmorBit = IT_ARMOR3; +} + +// Precache +void CItemArmorRed::Precache( void ) +{ + PRECACHE_MODEL( "models/armour_r.mdl" ); + PRECACHE_SOUND( "items/armor1.wav" ); +} + +//====================================================================================== +// WEAPON ITEMS +//====================================================================================== +void CBasePlayer::CheckAmmo() +{ + if (m_iAmmoShells > 100) + m_iAmmoShells = 100; + if (m_iAmmoNails > 200) + m_iAmmoNails = 200; + if (m_iAmmoRockets > 100) + m_iAmmoRockets = 100; + if (m_iAmmoCells > 100) + m_iAmmoCells = 100; +} + +int RankForWeapon(int iWeapon) +{ + switch (iWeapon) + { + case IT_LIGHTNING: + return 1; break; + case IT_ROCKET_LAUNCHER: + return 2; break; + case IT_SUPER_NAILGUN: + return 3; break; + case IT_GRENADE_LAUNCHER: + return 4; break; + case IT_SUPER_SHOTGUN: + return 5; break; + case IT_NAILGUN: + return 6; break; + + default: + break; + } + + return 7; +} + +int WeaponCode(int iWeapon) +{ + switch (iWeapon) + { + case IT_SUPER_SHOTGUN: + return 3; break; + case IT_NAILGUN: + return 4; break; + case IT_SUPER_NAILGUN: + return 5; break; + case IT_GRENADE_LAUNCHER: + return 6; break; + case IT_ROCKET_LAUNCHER: + return 7; break; + case IT_LIGHTNING: + return 8; break; + + default: + break; + } + + return 1; +} + +int GetWeaponValue ( int iWeapon ) +{ + int iWepValue = 0; + + switch ( iWeapon ) + { + case IT_AXE: iWepValue = 1; break; + case IT_SHOTGUN: iWepValue = 2; break; + case IT_SUPER_SHOTGUN: iWepValue = 3; break; + case IT_NAILGUN: iWepValue = 4; break; + case IT_SUPER_NAILGUN: iWepValue = 5; break; + case IT_GRENADE_LAUNCHER: iWepValue = 6; break; + case IT_ROCKET_LAUNCHER: iWepValue = 7; break; + case IT_LIGHTNING: iWepValue = 8; break; + } + + return iWepValue; +} +// Change weapon only if the new one's better +void CBasePlayer::Deathmatch_Weapon(int iOldWeapon, int iNewWeapon) +{ + int iPickedWep = GetWeaponValue( iNewWeapon ); + int iOldWep = GetWeaponValue( m_iQuakeWeapon ); + + switch ( m_iAutoWepSwitch ) + { + case 0: return; break; + case 1: + W_ChangeWeapon( iPickedWep ); break; + case 2: + + if ( iPickedWep == 8 && !FBitSet(pev->flags , FL_INWATER) || iPickedWep > iOldWep ) + W_ChangeWeapon( iPickedWep ); + break; + } + + +} + +//----------------------------------------------- +// Base Quake Weapon object +class CItemWeapon : public CQuakeItem +{ +public: + BOOL MyTouch( CBasePlayer *pPlayer ); + + int m_iWeapon; +}; + +BOOL CItemWeapon::MyTouch( CBasePlayer *pPlayer ) +{ + BOOL bLeaveWeapon = FALSE; + + if (gpGlobals->deathmatch == 2 || gpGlobals->deathmatch == 3 || gpGlobals->deathmatch == 5 || CVAR_GET_FLOAT("mp_weaponstay") > 0 ) + bLeaveWeapon = TRUE; + + // Leave the weapon if the player's already got it + if ( bLeaveWeapon && (pPlayer->m_iQuakeItems & m_iWeapon) ) + return FALSE; + + if ( pPlayer->pev->health <= 0) + return FALSE; + + // Give the player some ammo + switch (m_iWeapon) + { + case IT_NAILGUN: + pPlayer->m_iAmmoNails += 30; + break; + case IT_SUPER_NAILGUN: + pPlayer->m_iAmmoNails += 30; + break; + case IT_SUPER_SHOTGUN: + pPlayer->m_iAmmoShells += 5; + break; + case IT_ROCKET_LAUNCHER: + pPlayer->m_iAmmoRockets += 5; + break; + case IT_GRENADE_LAUNCHER: + pPlayer->m_iAmmoRockets += 5; + break; + case IT_LIGHTNING: + pPlayer->m_iAmmoCells += 15; + break; + default: + break; + } + pPlayer->CheckAmmo(); + + EMIT_SOUND( ENT(pev), CHAN_ITEM, "weapons/pkup.wav", 1, ATTN_NORM ); + + // Change to new weapon? + int iOldItems = pPlayer->m_iQuakeWeapon; + pPlayer->m_iQuakeItems |= m_iWeapon; + + pPlayer->Deathmatch_Weapon(iOldItems, m_iWeapon); + + + // Update HUD + pPlayer->W_SetCurrentAmmo(); + pPlayer->m_iClientQuakeWeapon = -1; + pPlayer->m_fWeapon = FALSE; + pPlayer->m_fKnownItem = FALSE; + pPlayer->UpdateClientData(); + + if (bLeaveWeapon) + return FALSE; + + // Respawn + m_flRespawnTime = 30; + + return TRUE; +} + +//=============== +// Super Shotgun +class CItemWeaponSuperShotgun : public CItemWeapon +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(weapon_supershotgun, CItemWeaponSuperShotgun); + +// Spawn +void CItemWeaponSuperShotgun::Spawn( void ) +{ + if ( gpGlobals->deathmatch > 3) + { + UTIL_Remove(this); + return; + } + + Precache(); + SET_MODEL(ENT(pev), "models/g_shot2.mdl"); + CItemWeapon::Spawn(); + + m_iWeapon = IT_SUPER_SHOTGUN; + pev->netname = MAKE_STRING("Double-barrelled Shotgun"); +} + +// Precache +void CItemWeaponSuperShotgun::Precache( void ) +{ + PRECACHE_MODEL( "models/g_shot2.mdl" ); +} + +//=============== +// Nailgun +class CItemWeaponNailgun : public CItemWeapon +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(weapon_nailgun, CItemWeaponNailgun); + +// Spawn +void CItemWeaponNailgun::Spawn( void ) +{ + if ( gpGlobals->deathmatch > 3) + { + UTIL_Remove(this); + return; + } + + Precache(); + SET_MODEL(ENT(pev), "models/g_nail.mdl"); + CItemWeapon::Spawn(); + + m_iWeapon = IT_NAILGUN; + pev->netname = MAKE_STRING("Nailgun"); +} + +// Precache +void CItemWeaponNailgun::Precache( void ) +{ + PRECACHE_MODEL( "models/g_nail.mdl" ); +} + +//=============== +// Super Nailgun +class CItemWeaponSuperNailgun : public CItemWeapon +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(weapon_supernailgun, CItemWeaponSuperNailgun); + +// Spawn +void CItemWeaponSuperNailgun::Spawn( void ) +{ + if ( gpGlobals->deathmatch > 3) + { + UTIL_Remove(this); + return; + } + + Precache(); + SET_MODEL(ENT(pev), "models/g_nail2.mdl"); + CItemWeapon::Spawn(); + + m_iWeapon = IT_SUPER_NAILGUN; + pev->netname = MAKE_STRING("Super Nailgun"); +} + +// Precache +void CItemWeaponSuperNailgun::Precache( void ) +{ + PRECACHE_MODEL( "models/g_nail2.mdl" ); +} + +//=============== +// Grenade Launcher +class CItemWeaponGrenadeLauncher : public CItemWeapon +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(weapon_grenadelauncher, CItemWeaponGrenadeLauncher); + +// Spawn +void CItemWeaponGrenadeLauncher::Spawn( void ) +{ + if ( gpGlobals->deathmatch > 3) + { + UTIL_Remove(this); + return; + } + + Precache(); + SET_MODEL(ENT(pev), "models/g_rock.mdl"); + CItemWeapon::Spawn(); + + m_iWeapon = IT_GRENADE_LAUNCHER; + pev->netname = MAKE_STRING("Grenade Launcher"); +} + +// Precache +void CItemWeaponGrenadeLauncher::Precache( void ) +{ + PRECACHE_MODEL( "models/g_rock.mdl" ); +} + +//=============== +// Rocket Launcher +class CItemWeaponRocketLauncher : public CItemWeapon +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(weapon_rocketlauncher, CItemWeaponRocketLauncher); + +// Spawn +void CItemWeaponRocketLauncher::Spawn( void ) +{ + if ( gpGlobals->deathmatch > 3) + { + UTIL_Remove(this); + return; + } + + Precache(); + SET_MODEL(ENT(pev), "models/g_rock2.mdl"); + CItemWeapon::Spawn(); + + m_iWeapon = IT_ROCKET_LAUNCHER; + pev->netname = MAKE_STRING("Rocket Launcher"); +} + +// Precache +void CItemWeaponRocketLauncher::Precache( void ) +{ + PRECACHE_MODEL( "models/g_rock2.mdl" ); +} + +//=============== +// Lightning Gun +class CItemWeaponLightning : public CItemWeapon +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(weapon_lightning, CItemWeaponLightning); + +// Spawn +void CItemWeaponLightning::Spawn( void ) +{ + if ( gpGlobals->deathmatch > 3) + { + UTIL_Remove(this); + return; + } + + Precache(); + SET_MODEL(ENT(pev), "models/g_light.mdl"); + CItemWeapon::Spawn(); + + m_iWeapon = IT_LIGHTNING; + pev->netname = MAKE_STRING("Thunderbolt"); +} + +// Precache +void CItemWeaponLightning::Precache( void ) +{ + PRECACHE_MODEL( "models/g_light.mdl" ); +} + +//====================================================================================== +// AMMO ITEMS +//====================================================================================== +#define BIG_AMMOBOX 1 + +class CItemAmmo : public CQuakeItem +{ +public: + void Spawn( void ); + void Precache( void ); + BOOL MyTouch( CBasePlayer *pPlayer ); + + int m_isSmallBox; + int m_isLargeBox; + int ammo_shells; + int ammo_nails; + int ammo_rockets; + int ammo_cells; +}; + +// Spawn +void CItemAmmo::Spawn( void ) +{ + Precache(); + + // Set the box size + if (pev->spawnflags & BIG_AMMOBOX) + { + SET_MODEL( ENT(pev), STRING(m_isLargeBox) ); + ammo_shells *= 2; + ammo_nails *= 2; + ammo_rockets *= 2; + ammo_cells *= 2; + } + else + { + SET_MODEL( ENT(pev), STRING(m_isSmallBox) ); + } + + // Halve respawn times in DM==3 and DM==5 + if (gpGlobals->deathmatch == 3 || gpGlobals->deathmatch == 5) + m_flRespawnTime = 15; + else + m_flRespawnTime = 30; + + CQuakeItem::Spawn(); +} + +// Precache +void CItemAmmo::Precache( void ) +{ + if (pev->spawnflags & BIG_AMMOBOX) + PRECACHE_MODEL( (char*)STRING(m_isLargeBox) ); + else + PRECACHE_MODEL( (char*)STRING(m_isSmallBox) ); +} + +BOOL CItemAmmo::MyTouch( CBasePlayer *pPlayer ) +{ + if (pPlayer->pev->health <= 0) + return FALSE; + + // Find the player's best weapon + int iBestWeapon = pPlayer->W_BestWeapon(); + + // Return if the player can't carry + if (ammo_shells && pPlayer->m_iAmmoShells >= 100) + return FALSE; + if (ammo_nails && pPlayer->m_iAmmoNails >= 200) + return FALSE; + if (ammo_rockets && pPlayer->m_iAmmoRockets >= 100) + return FALSE; + if (ammo_cells && pPlayer->m_iAmmoCells >= 100) + return FALSE; + + pPlayer->m_iAmmoShells += ammo_shells; + pPlayer->m_iAmmoNails += ammo_nails; + pPlayer->m_iAmmoRockets += ammo_rockets; + pPlayer->m_iAmmoCells += ammo_cells; + pPlayer->CheckAmmo(); + + EMIT_SOUND( ENT(pev), CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM ); + + // Change to a better weapon if possible + if ( pPlayer->m_iQuakeWeapon == iBestWeapon ) + { + pPlayer->m_iQuakeWeapon = pPlayer->W_BestWeapon(); + } + + pPlayer->W_SetCurrentAmmo(); + return TRUE; +} + +//=============== +// Shells +class CItemAmmoShells : public CItemAmmo +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS(item_shells, CItemAmmoShells); + +// Spawn +void CItemAmmoShells::Spawn( void ) +{ + if ( gpGlobals->deathmatch == 4) + { + UTIL_Remove(this); + return; + } + + m_isSmallBox = MAKE_STRING("models/w_shotbox.mdl"); + m_isLargeBox = MAKE_STRING("models/w_shotbox_big.mdl"); + pev->netname = MAKE_STRING("shells"); + ammo_shells = 20; + + CItemAmmo::Spawn(); +} + +//=============== +// Spikes +class CItemAmmoSpikes : public CItemAmmo +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS(item_spikes, CItemAmmoSpikes); + +// Spawn +void CItemAmmoSpikes::Spawn( void ) +{ + if ( gpGlobals->deathmatch == 4) + { + UTIL_Remove(this); + return; + } + + m_isSmallBox = MAKE_STRING("models/b_nail0.mdl"); + m_isLargeBox = MAKE_STRING("models/b_nail1.mdl"); + pev->netname = MAKE_STRING("nails"); + ammo_nails = 25; + + CItemAmmo::Spawn(); +} + +//=============== +// Rockets +class CItemAmmoRockets : public CItemAmmo +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS(item_rockets, CItemAmmoRockets); + +// Spawn +void CItemAmmoRockets::Spawn( void ) +{ + if ( gpGlobals->deathmatch == 4) + { + UTIL_Remove(this); + return; + } + + m_isSmallBox = MAKE_STRING("models/w_rpgammo.mdl"); + m_isLargeBox = MAKE_STRING("models/w_rpgammo_big.mdl"); + pev->netname = MAKE_STRING("rockets"); + ammo_rockets = 5; + + CItemAmmo::Spawn(); +} + +//=============== +// Cells +class CItemAmmoCells : public CItemAmmo +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS(item_cells, CItemAmmoCells); + +// Spawn +void CItemAmmoCells::Spawn( void ) +{ + if ( gpGlobals->deathmatch == 4) + { + UTIL_Remove(this); + return; + } + + m_isSmallBox = MAKE_STRING("models/w_battery.mdl"); + m_isLargeBox = MAKE_STRING("models/w_battery.mdl"); + pev->netname = MAKE_STRING("cells"); + ammo_cells = 6; + + CItemAmmo::Spawn(); +} + +//=============== +// Weapon ammo +// Another method of placing ammo. Quake still uses it in some maps. +#define AW_SHOTGUN 1 +#define AW_ROCKET 2 +#define AW_SPIKES 4 +#define AW_BIG 8 + +class CItemAmmoWeapon : public CItemAmmo +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS(item_weapon, CItemAmmoWeapon); + +// Spawn +void CItemAmmoWeapon::Spawn( void ) +{ + if ( gpGlobals->deathmatch == 4) + { + UTIL_Remove(this); + return; + } + + // Shells + if (pev->spawnflags & AW_SHOTGUN) + { + m_isSmallBox = MAKE_STRING("models/w_shotbox.mdl"); + m_isLargeBox = MAKE_STRING("models/w_shotbox_big.mdl"); + pev->netname = MAKE_STRING("shells"); + ammo_shells = 20; + } + + // Nails + if (pev->spawnflags & AW_SPIKES) + { + m_isSmallBox = MAKE_STRING("models/b_nail0.mdl"); + m_isLargeBox = MAKE_STRING("models/b_nail1.mdl"); + pev->netname = MAKE_STRING("nails"); + ammo_nails = 25; + } + + // Rockets + if (pev->spawnflags & AW_ROCKET) + { + m_isSmallBox = MAKE_STRING("models/w_rpgammo.mdl"); + m_isLargeBox = MAKE_STRING("models/w_rpgammo_big.mdl"); + pev->netname = MAKE_STRING("rockets"); + ammo_rockets = 5; + } + + // Big? + if (pev->spawnflags & AW_BIG) + pev->spawnflags = BIG_AMMOBOX; + else + pev->spawnflags = 0; + + CItemAmmo::Spawn(); +} + +//=============================================================================== +// POWERUPS +//=============================================================================== +class CItemPowerup : public CQuakeItem +{ +public: + BOOL MyTouch( CBasePlayer *pPlayer ); + + int m_iPowerupBit; + float invincible_finished; + float radsuit_finished; + float invisible_finished; + float super_damage_finished; +}; + +// Powerup Touch +BOOL CItemPowerup::MyTouch( CBasePlayer *pPlayer ) +{ + if (pPlayer->pev->health <= 0) + return FALSE; + + EMIT_SOUND( ENT(pev), CHAN_ITEM, STRING(pev->noise), 1, ATTN_NORM ); + + pPlayer->m_iQuakeItems |= m_iPowerupBit; + + int iPowerUp = 0; + + // Invincibility + if (invincible_finished) + { + // Make them glow red + + if ( pPlayer->m_iQuakeItems & IT_QUAD ) + { + pPlayer->pev->renderfx = kRenderFxGlowShell; + pPlayer->pev->rendercolor = Vector( 255, 125, 255 ); // RGB + pPlayer->pev->renderamt = 100; // Shell size + + iPowerUp = 3; + } + else + { + pPlayer->pev->renderfx = kRenderFxGlowShell; + pPlayer->pev->rendercolor = Vector( 255, 128, 0 ); // RGB + pPlayer->pev->renderamt = 100; // Shell size + + iPowerUp = 2; + } + + if ( pPlayer->m_iQuakeItems & IT_INVISIBILITY ) + { + pPlayer->pev->rendermode = kRenderTransColor; + pPlayer->pev->renderamt = 1; + } + pPlayer->m_flInvincibleFinished = gpGlobals->time + invincible_finished; + + } + + // Quad Damage + if (super_damage_finished) + { + // Make them glow blue + + if ( pPlayer->m_iQuakeItems & IT_INVULNERABILITY ) + { + pPlayer->pev->renderfx = kRenderFxGlowShell; + pPlayer->pev->rendercolor = Vector( 255, 125, 255 ); // RGB + pPlayer->pev->renderamt = 100; // Shell size + + iPowerUp = 3; + } + else + { + pPlayer->pev->renderfx = kRenderFxGlowShell; + pPlayer->pev->rendercolor = Vector( 128, 128, 255 ); // RGB + pPlayer->pev->renderamt = 100; // Shell size + + iPowerUp = 1; + } + + if ( pPlayer->m_iQuakeItems & IT_INVISIBILITY ) + { + pPlayer->pev->rendermode = kRenderTransColor; + pPlayer->pev->renderamt = 1; + } + + + pPlayer->m_flSuperDamageFinished = gpGlobals->time + super_damage_finished; + + // Remove armor and cells if DM==4 + if (gpGlobals->deathmatch == 4) + { + pPlayer->pev->armortype = 0; + pPlayer->pev->armorvalue = 0; + pPlayer->m_iAmmoCells = 0; + } + } + + // Radiation suit + if (radsuit_finished) + pPlayer->m_flRadsuitFinished = gpGlobals->time + radsuit_finished; + + // Invisibility + if (invisible_finished) + { + pPlayer->m_flInvisibleFinished = gpGlobals->time + invisible_finished; + + pPlayer->pev->renderfx = kRenderFxGlowShell; + pPlayer->pev->rendercolor = Vector( 128, 128, 128 ); // RGB + pPlayer->pev->renderamt = 5; // Shell size + + } + + // tell director about it + MESSAGE_BEGIN( MSG_SPEC, SVC_DIRECTOR ); + WRITE_BYTE ( 9 ); // command length in bytes + WRITE_BYTE ( DRC_CMD_EVENT ); // powerup pickup + WRITE_SHORT( ENTINDEX(pPlayer->edict()) ); // player is primary target + WRITE_SHORT( ENTINDEX(this->edict()) ); // powerup as second target + WRITE_LONG( 9 ); // highst prio in game + MESSAGE_END(); + + pPlayer->W_SetCurrentAmmo(); + + PLAYBACK_EVENT_FULL( FEV_GLOBAL | FEV_RELIABLE, + pPlayer->edict(), g_usPowerUp, 0, (float *)&g_vecZero, (float *)&g_vecZero, + (float)iPowerUp, 0.0, pPlayer->entindex(), pPlayer->pev->team, 0, 0 ); + + return TRUE; +} + + +//=============== +// Pentagram +class CItemPowerupInvincible : public CItemPowerup +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(item_artifact_invulnerability, CItemPowerupInvincible); + +// Spawn +void CItemPowerupInvincible::Spawn( void ) +{ + Precache(); + CQuakeItem::Spawn(); + + m_flRespawnTime = 300; + invincible_finished = 30; + + SET_MODEL(ENT(pev), "models/pow_invuln.mdl"); + pev->netname = MAKE_STRING("Pentagram of Protection"); + pev->noise = MAKE_STRING("items/protect.wav"); + m_iPowerupBit = IT_INVULNERABILITY; + + // Make it glow red + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 255, 128, 0 ); // RGB + pev->renderamt = 100; // Shellsize +} + +// Precache +void CItemPowerupInvincible::Precache( void ) +{ + PRECACHE_MODEL("models/pow_invuln.mdl"); + PRECACHE_SOUND("items/protect.wav"); + PRECACHE_SOUND("items/protect2.wav"); + PRECACHE_SOUND("items/protect3.wav"); +} + +//=============== +// Radiation Suit +class CItemPowerupRadsuit : public CItemPowerup +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(item_artifact_envirosuit, CItemPowerupRadsuit); + +// Spawn +void CItemPowerupRadsuit::Spawn( void ) +{ + Precache(); + CQuakeItem::Spawn(); + + m_flRespawnTime = 60; + radsuit_finished = 30; + + SET_MODEL(ENT(pev), "models/suit.mdl"); + pev->netname = MAKE_STRING("Biosuit"); + pev->noise = MAKE_STRING("items/suit.wav"); + m_iPowerupBit = IT_SUIT; +} + +// Precache +void CItemPowerupRadsuit::Precache( void ) +{ + PRECACHE_MODEL("models/suit.mdl"); + PRECACHE_SOUND("items/suit.wav"); + PRECACHE_SOUND("items/suit2.wav"); +} + +//=============== +// Ring of Invisibility +class CItemPowerupInvisibility : public CItemPowerup +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(item_artifact_invisibility, CItemPowerupInvisibility); + +// Spawn +void CItemPowerupInvisibility::Spawn( void ) +{ + Precache(); + CQuakeItem::Spawn(); + + m_flRespawnTime = 300; + invisible_finished = 30; + + SET_MODEL(ENT(pev), "models/pow_invis.mdl"); + pev->netname = MAKE_STRING("Ring of Shadows"); + pev->noise = MAKE_STRING("items/inv1.wav"); + m_iPowerupBit = IT_INVISIBILITY; + + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 128, 128, 128 ); // RGB + pev->renderamt = 25; // Shell size + + pev->rendermode = kRenderTransColor; + pev->renderamt = 30; +} + +// Precache +void CItemPowerupInvisibility::Precache( void ) +{ + PRECACHE_MODEL("models/pow_invis.mdl"); + PRECACHE_SOUND("items/inv1.wav"); + PRECACHE_SOUND("items/inv2.wav"); + PRECACHE_SOUND("items/inv3.wav"); +} + +//=============== +// Quad Damage +class CItemPowerupQuad : public CItemPowerup +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(item_artifact_super_damage, CItemPowerupQuad); + +// Spawn +void CItemPowerupQuad::Spawn( void ) +{ + Precache(); + CQuakeItem::Spawn(); + + m_flRespawnTime = 60; + super_damage_finished = 30; + + SET_MODEL(ENT(pev), "models/pow_quad.mdl"); + pev->netname = MAKE_STRING("Quad Damage"); + pev->noise = MAKE_STRING("items/damage.wav"); + m_iPowerupBit = IT_QUAD; + + // Make it glow blue + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 128, 128, 255 ); // RGB + pev->renderamt = 100; // Shell size +} + +// Precache +void CItemPowerupQuad::Precache( void ) +{ + PRECACHE_MODEL("models/pow_quad.mdl"); + PRECACHE_SOUND("items/damage.wav"); + PRECACHE_SOUND("items/damage2.wav"); + PRECACHE_SOUND("items/damage3.wav"); +} + +//=============================================================================== +// PLAYER BACKPACKS +//=============================================================================== +class CItemBackpack : public CQuakeItem +{ +public: + void Spawn( void ); +// void SetBox ( void ); + virtual void SetObjectCollisionBox ( void ); + + BOOL MyTouch( CBasePlayer *pPlayer ); + + int m_iItems; + int ammo_shells; + int ammo_nails; + int ammo_rockets; + int ammo_cells; +}; +LINK_ENTITY_TO_CLASS(item_backpack, CItemBackpack); + +void CItemBackpack :: SetObjectCollisionBox( void ) +{ + pev->absmin = pev->origin + Vector(-32, -32, 0); + pev->absmax = pev->origin + Vector(32, 32, 56); +} + +// Spawn +void CItemBackpack::Spawn() +{ + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + UTIL_SetOrigin( pev, pev->origin ); + SET_MODEL(ENT(pev), "models/backpack.mdl"); + + SetTouch(&CQuakeItem::ItemTouch); +} + +// Drop a backpack containing this player's ammo/weapons +void CBasePlayer::DropBackpack() +{ + // Any ammo to drop? + if ( !(m_iAmmoShells + m_iAmmoNails + m_iAmmoRockets + m_iAmmoCells) ) + return; + + // Create the pack + CItemBackpack *pPack = (CItemBackpack *)CBaseEntity::Create( "item_backpack", pev->origin - Vector(0, 0, 24), g_vecZero, edict() ); + pPack->pev->velocity = Vector( RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), 300 ); + pPack->Spawn(); + + // Put the player's weapon in the pack + pPack->m_iItems = m_iQuakeWeapon; + switch (pPack->m_iItems) + { + case IT_AXE: + pPack->pev->netname = MAKE_STRING("Crowbar"); break; + case IT_SHOTGUN: + pPack->pev->netname = MAKE_STRING("Shotgun"); break; + case IT_SUPER_SHOTGUN: + pPack->pev->netname = MAKE_STRING("Double-barrelled Shotgun"); break; + case IT_NAILGUN: + pPack->pev->netname = MAKE_STRING("Nailgun"); break; + case IT_SUPER_NAILGUN: + pPack->pev->netname = MAKE_STRING("Super Nailgun"); break; + case IT_GRENADE_LAUNCHER: + pPack->pev->netname = MAKE_STRING("Grenade Launcher"); break; + case IT_ROCKET_LAUNCHER: + pPack->pev->netname = MAKE_STRING("Rocket Launcher"); break; + case IT_LIGHTNING: + pPack->pev->netname = MAKE_STRING("Thunderbolt"); break; + default: + pPack->pev->netname = MAKE_STRING("Invalid weapon."); break; + } + + // Put the ammo in + pPack->ammo_shells = m_iAmmoShells; + pPack->ammo_nails = m_iAmmoNails; + pPack->ammo_rockets = m_iAmmoRockets; + pPack->ammo_cells = m_iAmmoCells; + + //Remove them from the player + m_iAmmoShells = m_iAmmoNails = m_iAmmoRockets = m_iAmmoCells = 0; + + // Remove after 2 mins + pPack->pev->nextthink = gpGlobals->time + 120; + pPack->SetThink( &CBaseEntity::SUB_Remove ); + + // Remove all weapons + m_iQuakeItems = 0; + m_iQuakeWeapon = 0; +} + +// Pickup backpack +BOOL CItemBackpack::MyTouch( CBasePlayer *pPlayer ) +{ + if (pPlayer->pev->health <= 0) + return FALSE; + if (gpGlobals->deathmatch == 4 && pPlayer->m_flInvincibleFinished > 0) + return FALSE; + + + + if (gpGlobals->deathmatch == 4) + { + pPlayer->pev->health += 10; + ClientPrint( pPlayer->pev, HUD_PRINTNOTIFY, "#Additional_Health" ); + if ((pPlayer->pev->health > 250) && (pPlayer->pev->health < 300)) + EMIT_SOUND( ENT(pPlayer->pev), CHAN_ITEM, "items/protect3.wav", 1, ATTN_NORM ); + else + EMIT_SOUND( ENT(pPlayer->pev), CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM ); + + // Become invulnerable if the player's reached 300 health + if (pPlayer->pev->health > 299) + { + if (pPlayer->m_flInvincibleFinished == 0) + { + // Give player invincibility and quad + pPlayer->m_flInvincibleFinished = gpGlobals->time + 30; + pPlayer->m_flSuperDamageFinished = gpGlobals->time + 30; + pPlayer->m_iQuakeItems |= (IT_INVULNERABILITY | IT_QUAD); + pPlayer->m_iAmmoCells = 0; + + // Make player glow red + pPlayer->pev->renderfx = kRenderFxGlowShell; + pPlayer->pev->rendercolor = Vector( 255, 128, 0 ); // RGB + pPlayer->pev->renderamt = 100; // Shell size + + EMIT_SOUND( ENT(pPlayer->pev), CHAN_VOICE, "items/sight1.wav", 1, ATTN_NORM ); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "#Bonus_Power", STRING(pPlayer->pev->netname) ); + } + } + + UTIL_Remove( this ); + + // We've removed ourself, so don't let CQuakeItem handle respawn + return FALSE; + } + + BOOL bPrintComma = FALSE; + + // Get the weapon from the pack + if (m_iItems) + { + if ( !(pPlayer->m_iQuakeItems & m_iItems) ) + { + bPrintComma = TRUE; + + switch ( m_iItems ) + { + case IT_SUPER_SHOTGUN: + ClientPrint( pPlayer->pev, HUD_PRINTNOTIFY, "#You_Get_SS", UTIL_dtos1( ammo_shells ), UTIL_dtos2 ( ammo_nails ), UTIL_dtos3 ( ammo_rockets ), UTIL_dtos4 ( ammo_cells ) ); break; + case IT_NAILGUN: + ClientPrint( pPlayer->pev, HUD_PRINTNOTIFY, "#You_Get_NG", UTIL_dtos1( ammo_shells ), UTIL_dtos2 ( ammo_nails ), UTIL_dtos3 ( ammo_rockets ), UTIL_dtos4 ( ammo_cells ) ); break; + case IT_SUPER_NAILGUN: + ClientPrint( pPlayer->pev, HUD_PRINTNOTIFY, "#You_Get_SG", UTIL_dtos1( ammo_shells ), UTIL_dtos2 ( ammo_nails ), UTIL_dtos3 ( ammo_rockets ), UTIL_dtos4 ( ammo_cells ) ); break; + case IT_GRENADE_LAUNCHER: + ClientPrint( pPlayer->pev, HUD_PRINTNOTIFY, "#You_Get_GL", UTIL_dtos1( ammo_shells ), UTIL_dtos2 ( ammo_nails ), UTIL_dtos3 ( ammo_rockets ), UTIL_dtos4 ( ammo_cells ) ); break; + case IT_ROCKET_LAUNCHER: + ClientPrint( pPlayer->pev, HUD_PRINTNOTIFY, "#You_Get_RL", UTIL_dtos1( ammo_shells ), UTIL_dtos2 ( ammo_nails ), UTIL_dtos3 ( ammo_rockets ), UTIL_dtos4 ( ammo_cells ) ); break; + case IT_LIGHTNING: + ClientPrint( pPlayer->pev, HUD_PRINTNOTIFY, "#You_Get_LG", UTIL_dtos1( ammo_shells ), UTIL_dtos2 ( ammo_nails ), UTIL_dtos3 ( ammo_rockets ), UTIL_dtos4 ( ammo_cells ) ); break; + } + } + else + ClientPrint( pPlayer->pev, HUD_PRINTNOTIFY, "#You_Get_NoGun", UTIL_dtos1( ammo_shells ), UTIL_dtos2 ( ammo_nails ), UTIL_dtos3 ( ammo_rockets ), UTIL_dtos4 ( ammo_cells ) ); + } + + // Get ammo from pack + pPlayer->m_iAmmoShells += ammo_shells; + pPlayer->m_iAmmoNails += ammo_nails; + pPlayer->m_iAmmoRockets += ammo_rockets; + pPlayer->m_iAmmoCells += ammo_cells; + pPlayer->CheckAmmo(); + + int iNewWeapon = m_iItems; + if (!iNewWeapon) + iNewWeapon = pPlayer->m_iQuakeWeapon; + int iOldWeapon = pPlayer->m_iQuakeItems; + pPlayer->m_iQuakeItems |= m_iItems; + + // Give them at least 5 rockets in DM==3 and DM==5 + if ( (gpGlobals->deathmatch==3 || gpGlobals->deathmatch == 5) & ( (WeaponCode(iNewWeapon)==6) || (WeaponCode(iNewWeapon)==7) ) & (pPlayer->m_iAmmoRockets < 5) ) + pPlayer->m_iAmmoRockets = 5; + + EMIT_SOUND( ENT(pPlayer->pev), CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM ); + + // Switch to a better weapon + if ( WeaponCode(iNewWeapon) <= pPlayer->m_iBackpackSwitch ) + { + if (pPlayer->pev->flags & FL_INWATER) + { + if (iNewWeapon != IT_LIGHTNING) + { + pPlayer->Deathmatch_Weapon(iOldWeapon, iNewWeapon); + } + } + else + { + pPlayer->Deathmatch_Weapon(iOldWeapon, iNewWeapon); + } + } + pPlayer->W_SetCurrentAmmo(); + pPlayer->m_iClientQuakeWeapon = -1; + pPlayer->m_fWeapon = FALSE; + pPlayer->m_fKnownItem = FALSE; + pPlayer->UpdateClientData(); + + UTIL_Remove( this ); + + // We've removed ourself, so don't let CQuakeItem handle respawn + return FALSE; +} + +#if 0 + +/* +=============================================================================== +KEYS +=============================================================================== +*/ + +void() key_touch = +{ +local entity stemp; +local float best; + + if (other.classname != "player") + return; + if (other.health <= 0) + return; + if (other.items & self.items) + return; + + sprint (other, PRINT_LOW, "You got the "); + sprint (other, PRINT_LOW, self.netname); + sprint (other,PRINT_LOW, "\n"); + + sound (other, CHAN_ITEM, self.noise, 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + other.items = other.items | self.items; + + self.solid = SOLID_NOT; + self.model = string_null; + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + +void() key_setsounds = +{ + if (world.worldtype == 0) + { + precache_sound ("misc/medkey.wav"); + self.noise = "misc/medkey.wav"; + } + if (world.worldtype == 1) + { + precache_sound ("misc/runekey.wav"); + self.noise = "misc/runekey.wav"; + } + if (world.worldtype == 2) + { + precache_sound2 ("misc/basekey.wav"); + self.noise = "misc/basekey.wav"; + } +}; + +/*QUAKED item_key1 (0 .5 .8) (-16 -16 -24) (16 16 32) +SILVER key +In order for keys to work +you MUST set your maps +worldtype to one of the +following: +0: medieval +1: metal +2: base +*/ + +void() item_key1 = +{ + if (world.worldtype == 0) + { + precache_model ("progs/w_s_key.mdl"); + setmodel (self, "progs/w_s_key.mdl"); + self.netname = "silver key"; + } + else if (world.worldtype == 1) + { + precache_model ("progs/m_s_key.mdl"); + setmodel (self, "progs/m_s_key.mdl"); + self.netname = "silver runekey"; + } + else if (world.worldtype == 2) + { + precache_model2 ("progs/b_s_key.mdl"); + setmodel (self, "progs/b_s_key.mdl"); + self.netname = "silver keycard"; + } + key_setsounds(); + self.touch = key_touch; + self.items = IT_KEY1; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + +/*QUAKED item_key2 (0 .5 .8) (-16 -16 -24) (16 16 32) +GOLD key +In order for keys to work +you MUST set your maps +worldtype to one of the +following: +0: medieval +1: metal +2: base +*/ + +void() item_key2 = +{ + if (world.worldtype == 0) + { + precache_model ("progs/w_g_key.mdl"); + setmodel (self, "progs/w_g_key.mdl"); + self.netname = "gold key"; + } + if (world.worldtype == 1) + { + precache_model ("progs/m_g_key.mdl"); + setmodel (self, "progs/m_g_key.mdl"); + self.netname = "gold runekey"; + } + if (world.worldtype == 2) + { + precache_model2 ("progs/b_g_key.mdl"); + setmodel (self, "progs/b_g_key.mdl"); + self.netname = "gold keycard"; + } + key_setsounds(); + self.touch = key_touch; + self.items = IT_KEY2; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + + + +/* +=============================================================================== + +END OF LEVEL RUNES + +=============================================================================== +*/ + +void() sigil_touch = +{ +local entity stemp; +local float best; + + if (other.classname != "player") + return; + if (other.health <= 0) + return; + + centerprint (other, "You got the rune!"); + + sound (other, CHAN_ITEM, self.noise, 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + self.solid = SOLID_NOT; + self.model = string_null; + serverflags = serverflags | (self.spawnflags & 15); + self.classname = ""; // so rune doors won't find it + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + +/*QUAKED item_sigil (0 .5 .8) (-16 -16 -24) (16 16 32) E1 E2 E3 E4 +End of level sigil, pick up to end episode and return to jrstart. +*/ + +void() item_sigil = +{ + if (!self.spawnflags) + objerror ("no spawnflags"); + + precache_sound ("misc/runekey.wav"); + self.noise = "misc/runekey.wav"; + + if (self.spawnflags & 1) + { + precache_model ("progs/end1.mdl"); + setmodel (self, "progs/end1.mdl"); + } + if (self.spawnflags & 2) + { + precache_model2 ("progs/end2.mdl"); + setmodel (self, "progs/end2.mdl"); + } + if (self.spawnflags & 4) + { + precache_model2 ("progs/end3.mdl"); + setmodel (self, "progs/end3.mdl"); + } + if (self.spawnflags & 8) + { + precache_model2 ("progs/end4.mdl"); + setmodel (self, "progs/end4.mdl"); + } + + self.touch = sigil_touch; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + +void() q_touch = +{ +local entity stemp; +local float best; +local string s; + + if (other.classname != "player") + return; + if (other.health <= 0) + return; + + self.mdl = self.model; + + sound (other, CHAN_VOICE, self.noise, 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + self.solid = SOLID_NOT; + other.items = other.items | IT_QUAD; + self.model = string_null; + if (deathmatch == 4) + { + other.armortype = 0; + other.armorvalue = 0 * 0.01; + other.ammo_cells = 0; + } + +// do the apropriate action + other.super_time = 1; + other.super_damage_finished = self.cnt; + + s=ftos(rint(other.super_damage_finished - time)); + + bprint (PRINT_LOW, other.netname); + if (deathmatch == 4) + bprint (PRINT_LOW, " recovered an OctaPower with "); + else + bprint (PRINT_LOW, " recovered a Quad with "); + bprint (PRINT_LOW, s); + bprint (PRINT_LOW, " seconds remaining!\n"); + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + +void(float timeleft) DropQuad = +{ + local entity item; + + item = spawn(); + item.origin = self.origin - '0 0 24'; + + item.velocity_z = 300; + item.velocity_x = -100 + (random() * 200); + item.velocity_y = -100 + (random() * 200); + + item.flags = FL_ITEM; + item.solid = SOLID_TRIGGER; + item.movetype = MOVETYPE_TOSS; + item.noise = "items/damage.wav"; + setmodel (item, "progs/quaddama.mdl"); + setsize (item, '-16 -16 -24', '16 16 32'); + item.cnt = time + timeleft; + item.touch = q_touch; + item.nextthink = time + timeleft; // remove it with the time left on it + item.think = SUB_Remove; +}; + + +void() r_touch; + +void() r_touch = +{ +local entity stemp; +local float best; +local string s; + + if (other.classname != "player") + return; + if (other.health <= 0) + return; + + self.mdl = self.model; + + sound (other, CHAN_VOICE, self.noise, 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + self.solid = SOLID_NOT; + other.items = other.items | IT_INVISIBILITY; + self.model = string_null; + +// do the apropriate action + other.invisible_time = 1; + other.invisible_finished = self.cnt; + s=ftos(rint(other.invisible_finished - time)); + bprint (PRINT_LOW, other.netname); + bprint (PRINT_LOW, " recovered a Ring with "); + bprint (PRINT_LOW, s); + bprint (PRINT_LOW, " seconds remaining!\n"); + + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + +void(float timeleft) DropRing = +{ + local entity item; + + item = spawn(); + item.origin = self.origin - '0 0 24'; + + item.velocity_z = 300; + item.velocity_x = -100 + (random() * 200); + item.velocity_y = -100 + (random() * 200); + + item.flags = FL_ITEM; + item.solid = SOLID_TRIGGER; + item.movetype = MOVETYPE_TOSS; + item.noise = "items/inv1.wav"; + setmodel (item, "progs/invisibl.mdl"); + setsize (item, '-16 -16 -24', '16 16 32'); + item.cnt = time + timeleft; + item.touch = r_touch; + item.nextthink = time + timeleft; // remove after 30 seconds + item.think = SUB_Remove; +}; +#endif diff --git a/hlsdk/dmc/dlls/quake_nail.cpp b/hlsdk/dmc/dlls/quake_nail.cpp new file mode 100644 index 00000000..98e0a6f2 --- /dev/null +++ b/hlsdk/dmc/dlls/quake_nail.cpp @@ -0,0 +1,122 @@ +//=========== (C) Copyright 1996-2002, 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: Quake nail entity +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "decals.h" +#include "gamerules.h" + +LINK_ENTITY_TO_CLASS( quake_nail, CQuakeNail ); + +//========================================================= +CQuakeNail *CQuakeNail::CreateNail( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner ) +{ + CQuakeNail *pNail = GetClassPtr( (CQuakeNail *)NULL ); + + UTIL_SetOrigin( pNail->pev, vecOrigin ); + + pNail->pev->velocity = vecAngles * 1000; + pNail->pev->angles = UTIL_VecToAngles(vecAngles); + pNail->pev->owner = pOwner->edict(); + pNail->Spawn(); + pNail->pev->classname = MAKE_STRING("spike"); + + // don't send to clients. + pNail->pev->effects |= EF_NODRAW; + + return pNail; +} + +CQuakeNail *CQuakeNail::CreateSuperNail( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner ) +{ + CQuakeNail *pNail = CreateNail( vecOrigin, vecAngles, pOwner ); + pNail->pev->classname = MAKE_STRING("superspike"); + + // Super nails simply do more damage + pNail->pev->dmg = 18; + return pNail; +} + +//========================================================= +void CQuakeNail::Spawn( void ) +{ + Precache(); + + // Setup + pev->movetype = MOVETYPE_FLYMISSILE; + pev->solid = SOLID_BBOX; + + // Safety removal + pev->nextthink = gpGlobals->time + 6; + SetThink( &CBaseEntity::SUB_Remove ); + + // Touch + SetTouch( &CQuakeNail::NailTouch ); + + // Model + SET_MODEL( ENT(pev), "models/spike.mdl" ); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + UTIL_SetOrigin( pev, pev->origin ); + + // Damage + pev->dmg = 9; +} + +//========================================================= +void CQuakeNail::NailTouch( CBaseEntity *pOther ) +{ + if (pOther->pev->solid == SOLID_TRIGGER) + return; + + // Remove if we've hit skybrush + if ( UTIL_PointContents(pev->origin) == CONTENT_SKY ) + { + UTIL_Remove( this ); + return; + } + + // Hit something that bleeds + if (pOther->pev->takedamage) + { + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + + if ( g_pGameRules->PlayerRelationship( pOther, pOwner ) != GR_TEAMMATE ) + SpawnBlood( pev->origin, pOther->BloodColor(), pev->dmg ); + + pOther->TakeDamage( pev, pOwner->pev, pev->dmg, DMG_GENERIC ); + } + else + { + if ( pOther->pev->solid == SOLID_BSP || pOther->pev->movetype == MOVETYPE_PUSHSTEP ) + { + TraceResult tr; + tr.vecEndPos = pev->origin; + tr.pHit = pOther->edict(); + + //Arent we doing this client side? + //UTIL_GunshotDecalTrace( &tr, DECAL_GUNSHOT1 + RANDOM_LONG( 0, 4 ) ); + } + } + + UTIL_Remove( this ); +} + + + diff --git a/hlsdk/dmc/dlls/quake_player.cpp b/hlsdk/dmc/dlls/quake_player.cpp new file mode 100644 index 00000000..f3169438 --- /dev/null +++ b/hlsdk/dmc/dlls/quake_player.cpp @@ -0,0 +1,250 @@ +/*** +* +* Copyright (c) 1996-2002,, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== quake_player.cpp ======================================================== + + Quake Classic player functionality. + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "gamerules.h" +#include "hltv.h" + +extern entvars_t *g_pevLastInflictor; +extern int gmsgStatusText; +extern int gmsgStatusValue; +extern DLL_GLOBAL Vector g_vecAttackDir; + +/************************************* + STATUS BAR + *************************************/ + +// Initialise the player's status bar +void CBasePlayer::InitStatusBar() +{ + m_flStatusBarDisappearDelay = 0; + m_SbarString1[0] = m_SbarString0[0] = 0; +} + +void CBasePlayer::UpdateStatusBar() +{ + int newSBarState[ SBAR_END ]; + memset( newSBarState, 0, sizeof(newSBarState) ); + + // Find an ID Target + TraceResult tr; + UTIL_MakeVectors( pev->v_angle + pev->punchangle ); + Vector vecSrc = EyePosition(); + Vector vecEnd = vecSrc + (gpGlobals->v_forward * MAX_ID_RANGE); + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, edict(), &tr); + + if (tr.flFraction != 1.0) + { + if ( !FNullEnt( tr.pHit ) ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + + if ( pEntity->Classify() == CLASS_PLAYER ) + { + newSBarState[ SBAR_ID_TARGETNAME ] = ENTINDEX( pEntity->edict() ); + newSBarState[ SBAR_ID_TARGETTEAM ] = FALSE; + + m_flStatusBarDisappearDelay = gpGlobals->time + 1.0; + } + } + else if ( m_flStatusBarDisappearDelay > gpGlobals->time ) + { + // hold the values for a short amount of time after viewing the object + newSBarState[ SBAR_ID_TARGETNAME ] = m_izSBarState[ SBAR_ID_TARGETNAME ]; + newSBarState[ SBAR_ID_TARGETHEALTH ] = m_izSBarState[ SBAR_ID_TARGETHEALTH ]; + newSBarState[ SBAR_ID_TARGETARMOR ] = m_izSBarState[ SBAR_ID_TARGETARMOR ]; + newSBarState[ SBAR_ID_TARGETTEAM ] = m_izSBarState[ SBAR_ID_TARGETTEAM ]; + } + } + + // Check values and send if they don't match + for (int i = 1; i < SBAR_END; i++) + { + if ( newSBarState[i] != m_izSBarState[i] ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgStatusValue, NULL, pev ); + WRITE_BYTE( i ); + WRITE_SHORT( newSBarState[i] ); + MESSAGE_END(); + + m_izSBarState[i] = newSBarState[i]; + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Player has taken some damage. This is now using the Quake functionality. +//----------------------------------------------------------------------------- +int CBasePlayer::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( (pev->takedamage == DAMAGE_NO) || (IsAlive() == FALSE) ) + return 0; + + + //We are wearing the suit and we want to be hurt by lava or slime + if ( m_iQuakeItems & IT_SUIT ) + { + if ( bitsDamageType & DMG_BURN || bitsDamageType & DMG_ACID ) + return 0; + } + + CBaseEntity *pAttacker = CBaseEntity::Instance(pevAttacker); + + // keep track of amount of damage last sustained + m_lastDamageAmount = static_cast(flDamage); + + // check for quad damage powerup on the attacker + if (pAttacker->IsPlayer()) + { + if ( ((CBasePlayer*)pAttacker)->m_flSuperDamageFinished > gpGlobals->time ) + { + if (gpGlobals->deathmatch == 4) + flDamage *= 8; + else + flDamage *= 4; + } + } + + // team play damage avoidance + if ( g_pGameRules->PlayerRelationship( this, pAttacker ) == GR_TEAMMATE ) + { + // Teamplay 3 you can still hurt yourself + if ( CVAR_GET_FLOAT( "mp_teamplay" ) == 3 && pAttacker != this ) + return 0; + // Teamplay 1 can't hurt any teammates, including yourself + if ( CVAR_GET_FLOAT( "mp_teamplay" ) == 1 ) + return 0; + // Teamplay 2 you can still hurt teammates + } + + + // save damage based on the target's armor level + float flSave = ceil(pev->armortype * flDamage); + if (flSave >= pev->armorvalue) + { + flSave = pev->armorvalue; + pev->armortype = 0; // lost all armor + m_iQuakeItems &= ~(IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3); + } + pev->armorvalue -= flSave; + float flTake = ceil(flDamage - flSave); + + // add to the damage total for clients, which will be sent as a single message at the end of the frame + pev->dmg_take = pev->dmg_take + flTake; + pev->dmg_inflictor = ENT(pevInflictor); + + Vector vecTemp; + + if ( pevAttacker == pevInflictor ) + { + vecTemp = pevAttacker->origin - ( VecBModelOrigin(pev) ); + } + else + // an actual missile was involved. + { + vecTemp = pevInflictor->origin - ( VecBModelOrigin(pev) ); + } + + // this global is still used for glass and other non-monster killables, along with decals. + g_vecAttackDir = vecTemp.Normalize(); + + + // figure momentum add + if ( (pevInflictor) && (pev->movetype == MOVETYPE_WALK) && !( FBitSet (bitsDamageType, DMG_BURN) ) && !( FBitSet (bitsDamageType, DMG_ACID) ) ) + { + + + Vector vecPush = (pev->origin - (pevInflictor->absmin + pevInflictor->absmax) * 0.5).Normalize(); + // Set kickback for smaller weapons + // Read: only if it's not yourself doing the damage + if ( (flDamage < 60) && pAttacker->IsPlayer() && (pAttacker != this) ) + pev->velocity = pev->velocity + vecPush * flDamage * 11; + else + { + // Otherwise, these rules apply to rockets and grenades + // for blast velocity + if ( pAttacker == this ) + { + if ( m_iQuakeWeapon != IT_LIGHTNING ) + pev->velocity = pev->velocity + vecPush * flDamage * 8; + } + else + pev->velocity = pev->velocity + vecPush * flDamage * 8; + } + + // Rocket Jump modifiers + int iRocketJumpModifier = (int)CVAR_GET_FLOAT("rj"); + + if ( (iRocketJumpModifier > 1) && (pAttacker == this) && m_iQuakeWeapon == ( IT_ROCKET_LAUNCHER | IT_GRENADE_LAUNCHER ) ) + pev->velocity = pev->velocity + vecPush * flDamage * iRocketJumpModifier; + } + + // check for godmode or invincibility + if (pev->flags & FL_GODMODE) + return 0; + if (m_flInvincibleFinished > gpGlobals->time) + { + if (m_fInvincSound < gpGlobals->time) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/protect3.wav", 1, ATTN_NORM); + m_fInvincSound = gpGlobals->time + 2; + } + return 0; + } + + + // do the damage + pev->health -= (int)flTake; + + // tell director about it + MESSAGE_BEGIN( MSG_SPEC, SVC_DIRECTOR ); + WRITE_BYTE ( 9 ); // command length in bytes + WRITE_BYTE ( DRC_CMD_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(); + + // react to the damage + m_bitsDamageType |= bitsDamageType; // Save this so we can report it to the client + m_bitsHUDDamage = -1; // make sure the damage bits get resent + + if ( pev->health <= 0 ) + { + g_pevLastInflictor = pevInflictor; + + Killed( pevAttacker, GIB_NORMAL ); + + g_pevLastInflictor = NULL; + return 0; + } + + // play pain sound + Pain( pAttacker ); + + return static_cast(flTake); +} + diff --git a/hlsdk/dmc/dlls/quake_rocket.cpp b/hlsdk/dmc/dlls/quake_rocket.cpp new file mode 100644 index 00000000..aebbb74c --- /dev/null +++ b/hlsdk/dmc/dlls/quake_rocket.cpp @@ -0,0 +1,191 @@ +//=========== (C) Copyright 1996-2002, 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: Quake rocket entity +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" + +#define GRENADE_TRAIL 1 +#define ROCKET_TRAIL 2 + +extern unsigned short g_sTrail; +extern unsigned short g_sExplosion; + +LINK_ENTITY_TO_CLASS( quake_rocket, CQuakeRocket ); + +//========================================================= +CQuakeRocket *CQuakeRocket::CreateRocket( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner ) +{ + CQuakeRocket *pRocket = GetClassPtr( (CQuakeRocket *)NULL ); + + UTIL_SetOrigin( pRocket->pev, vecOrigin ); + SET_MODEL(ENT(pRocket->pev), "models/rocket.mdl"); + pRocket->Spawn(); + pRocket->pev->classname = MAKE_STRING("missile"); + pRocket->pev->owner = pOwner->edict(); + + // Setup + pRocket->pev->movetype = MOVETYPE_FLYMISSILE; + pRocket->pev->solid = SOLID_BBOX; + + // Velocity + pRocket->pev->velocity = vecAngles * 1000; + pRocket->pev->angles = UTIL_VecToAngles( vecAngles ); + + // Touch + pRocket->SetTouch( &CQuakeRocket::RocketTouch ); + + // Safety Remove + pRocket->pev->nextthink = gpGlobals->time + 5; + pRocket->SetThink( &CBaseEntity::SUB_Remove ); + + // Effects +// pRocket->pev->effects |= EF_LIGHT; + + PLAYBACK_EVENT_FULL (FEV_GLOBAL, pRocket->edict(), g_sTrail, 0.0, + (float *)&pRocket->pev->origin, (float *)&pRocket->pev->angles, 0.7, 0.0, pRocket->entindex(), ROCKET_TRAIL, 0, 0); + + return pRocket; +} + +//========================================================= +CQuakeRocket *CQuakeRocket::CreateGrenade( Vector vecOrigin, Vector vecVelocity, CBaseEntity *pOwner ) +{ + CQuakeRocket *pRocket = GetClassPtr( (CQuakeRocket *)NULL ); + + UTIL_SetOrigin( pRocket->pev, vecOrigin ); + SET_MODEL(ENT(pRocket->pev), "models/grenade.mdl"); + pRocket->Spawn(); + pRocket->pev->classname = MAKE_STRING("grenade"); + pRocket->pev->owner = pOwner->edict(); + + // Setup + pRocket->pev->movetype = MOVETYPE_BOUNCE; + pRocket->pev->solid = SOLID_BBOX; + + pRocket->pev->avelocity = Vector(300,300,300); + + // Velocity + pRocket->pev->velocity = vecVelocity; + pRocket->pev->angles = UTIL_VecToAngles(vecVelocity); + pRocket->pev->friction = 0.5; + + // Touch + pRocket->SetTouch( &CQuakeRocket::GrenadeTouch ); + + // set newmis duration + if ( gpGlobals->deathmatch == 4 ) + { + pRocket->m_flAttackFinished = gpGlobals->time + 1.1; // What's this used for? + if (pOwner) + pOwner->TakeDamage( pOwner->pev, pOwner->pev, 10, DMG_GENERIC ); + } + + pRocket->pev->nextthink = gpGlobals->time + 2.5; + pRocket->SetThink( &CQuakeRocket::GrenadeExplode ); + + PLAYBACK_EVENT_FULL (FEV_GLOBAL, pRocket->edict(), g_sTrail, 0.0, + (float *)&g_vecZero, (float *)&g_vecZero, 0.7, 0.0, pRocket->entindex(), GRENADE_TRAIL, 0, 0); + + + return pRocket; +} + +//========================================================= +void CQuakeRocket::Spawn( void ) +{ + Precache(); + + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + UTIL_SetOrigin( pev, pev->origin ); +} + +//========================================================= +void CQuakeRocket::Precache( void ) +{ + m_iTrail = PRECACHE_MODEL("sprites/smoke.spr"); +} + +//========================================================= +void CQuakeRocket::RocketTouch ( CBaseEntity *pOther ) +{ + // Remove if we've hit skybrush + if ( UTIL_PointContents(pev->origin) == CONTENT_SKY ) + { + UTIL_Remove( this ); + return; + } + + // Do touch damage + float flDmg = RANDOM_FLOAT( 100, 120 ); + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + if (pOther->pev->health) + pOther->TakeDamage( pev, pOwner->pev, flDmg, DMG_BULLET ); + + // Don't do radius damage to the other, because all the damage was done in the impact + Q_RadiusDamage(this, pOwner, 120, pOther); + + // Finish and remove + Explode(); +} + +//========================================================= +void CQuakeRocket::GrenadeTouch( CBaseEntity *pOther ) +{ + if (pOther->pev->takedamage == DAMAGE_AIM) + { + GrenadeExplode(); + return; + } + + if (pev->flags & FL_ONGROUND) + { + // add a bit of static friction + pev->velocity = pev->velocity * 0.75; + + if (pev->velocity.Length() <= 20) + { + pev->avelocity = g_vecZero; + } + } + + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); + + if (pev->velocity == g_vecZero) + pev->avelocity = g_vecZero; +} + +//========================================================= +void CQuakeRocket::GrenadeExplode() +{ + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + Q_RadiusDamage(this, pOwner, 120, NULL); + + // Finish and remove + Explode(); +} + +//========================================================= +void CQuakeRocket::Explode() +{ + //We use the angles field to send the rocket velocity. + PLAYBACK_EVENT_FULL( FEV_GLOBAL, edict(), g_sExplosion, 0.0, (float *)&pev->origin, (float *)&pev->velocity, 0.0, 0.0, 0, 0, 0, 0 ); + + UTIL_Remove( this ); +} diff --git a/hlsdk/dmc/dlls/quake_weapons_all.cpp b/hlsdk/dmc/dlls/quake_weapons_all.cpp new file mode 100644 index 00000000..f9014a3b --- /dev/null +++ b/hlsdk/dmc/dlls/quake_weapons_all.cpp @@ -0,0 +1,996 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== quake_weapons.cpp ======================================================== + + Quake weaponry + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "quake_gun.h" + +char gszQ_DeathType[128]; +DLL_GLOBAL short g_sModelIndexNail; + +#ifdef CLIENT_DLL +#include "cl_entity.h" +struct cl_entity_s *GetViewEntity( void ); +extern float g_flLightTime; +#endif + +// called by worldspawn +void QuakeClassicPrecache( void ) +{ + // Weapon sounds + PRECACHE_SOUND("weapons/ax1.wav"); + PRECACHE_SOUND("player/axhit2.wav"); + PRECACHE_SOUND("player/axhitbod.wav"); + PRECACHE_SOUND("weapons/r_exp3.wav"); // new rocket explosion + PRECACHE_SOUND("weapons/rocket1i.wav");// spike gun + PRECACHE_SOUND("weapons/sgun1.wav"); + PRECACHE_SOUND("weapons/lhit.wav"); + PRECACHE_SOUND("weapons/guncock.wav"); // player shotgun + PRECACHE_SOUND("weapons/ric1.wav"); // ricochet (used in c code) + PRECACHE_SOUND("weapons/ric2.wav"); // ricochet (used in c code) + PRECACHE_SOUND("weapons/ric3.wav"); // ricochet (used in c code) + PRECACHE_SOUND("weapons/spike2.wav"); // super spikes + PRECACHE_SOUND("weapons/tink1.wav"); // spikes tink (used in c code) + PRECACHE_SOUND("weapons/grenade.wav"); // grenade launcher + PRECACHE_SOUND("weapons/bounce.wav"); // grenade bounce + PRECACHE_SOUND("weapons/shotgn2.wav"); // super shotgun + PRECACHE_SOUND("weapons/lstart.wav"); // lightning start + + // Weapon models + PRECACHE_MODEL("models/v_crowbar.mdl"); + PRECACHE_MODEL("models/v_shot.mdl"); + PRECACHE_MODEL("models/v_shot2.mdl"); + PRECACHE_MODEL("models/v_nail.mdl"); + PRECACHE_MODEL("models/v_nail2.mdl"); + PRECACHE_MODEL("models/v_rock.mdl"); + PRECACHE_MODEL("models/v_rock2.mdl"); + PRECACHE_MODEL("models/v_light.mdl"); + + // Weapon player models + PRECACHE_MODEL("models/p_crowbar.mdl"); + PRECACHE_MODEL("models/p_rock2.mdl"); + PRECACHE_MODEL("models/p_rock.mdl"); + PRECACHE_MODEL("models/p_shot2.mdl"); + PRECACHE_MODEL("models/p_nail.mdl"); + PRECACHE_MODEL("models/p_nail2.mdl"); + PRECACHE_MODEL("models/p_light.mdl"); + PRECACHE_MODEL("models/p_shot.mdl"); + + + // Weapon effect models + PRECACHE_MODEL("models/rocket.mdl"); // rocket + PRECACHE_MODEL("models/grenade.mdl"); // grenade + g_sModelIndexNail = PRECACHE_MODEL("models/spike.mdl"); + g_sModelIndexLaser = PRECACHE_MODEL("sprites/laserbeam.spr"); + PRECACHE_MODEL("sprites/smoke.spr"); + + // Powerup models + + // Powerup sounds + PRECACHE_SOUND("items/damage3.wav"); + PRECACHE_SOUND("items/sight1.wav"); + + // Teleport sounds + PRECACHE_SOUND("misc/r_tele1.wav"); + PRECACHE_SOUND("misc/r_tele2.wav"); + PRECACHE_SOUND("misc/r_tele3.wav"); + PRECACHE_SOUND("misc/r_tele4.wav"); + PRECACHE_SOUND("misc/r_tele5.wav"); + + // Misc + PRECACHE_SOUND("weapons/lock4.wav"); + PRECACHE_SOUND("weapons/pkup.wav"); + PRECACHE_SOUND("items/itembk2.wav"); + PRECACHE_MODEL("models/backpack.mdl"); +} + +//================================================================================================ +// WEAPON SELECTION +//================================================================================================ +// Return the ID of the best weapon being carried by the player +int CBasePlayer::W_BestWeapon() +{ + if (pev->waterlevel <= 1 && m_iAmmoCells >= 1 && (m_iQuakeItems & IT_LIGHTNING) ) + return IT_LIGHTNING; + else if(m_iAmmoNails >= 2 && (m_iQuakeItems & IT_SUPER_NAILGUN) ) + return IT_SUPER_NAILGUN; + else if(m_iAmmoShells >= 2 && (m_iQuakeItems & IT_SUPER_SHOTGUN) ) + return IT_SUPER_SHOTGUN; + else if(m_iAmmoNails >= 1 && (m_iQuakeItems & IT_NAILGUN) ) + return IT_NAILGUN; + else if(m_iAmmoShells >= 1 && (m_iQuakeItems & IT_SHOTGUN) ) + return IT_SHOTGUN; + + return IT_AXE; +} + +// Weapon setup after weapon switch +void CBasePlayer::W_SetCurrentAmmo( int sendanim /* = 1 */ ) +{ + m_iQuakeItems &= ~(IT_SHELLS | IT_NAILS | IT_ROCKETS | IT_CELLS); + int iszViewModel = 0; + char *viewmodel = ""; + int iszWeaponModel = 0; + char *szAnimExt = NULL; + + // Find out what weapon the player's using + if (m_iQuakeWeapon == IT_AXE) + { + m_pCurrentAmmo = NULL; + viewmodel = "models/v_crowbar.mdl"; + iszViewModel = MAKE_STRING(viewmodel); + szAnimExt = "crowbar"; + iszWeaponModel = MAKE_STRING("models/p_crowbar.mdl"); + } + else if (m_iQuakeWeapon == IT_SHOTGUN) + { + m_pCurrentAmmo = &m_iAmmoShells; + viewmodel = "models/v_shot.mdl"; + iszViewModel = MAKE_STRING(viewmodel); + iszWeaponModel = MAKE_STRING("models/p_shot.mdl"); + m_iQuakeItems |= IT_SHELLS; + szAnimExt = "shotgun"; + } + else if (m_iQuakeWeapon == IT_SUPER_SHOTGUN) + { + m_pCurrentAmmo = &m_iAmmoShells; + viewmodel = "models/v_shot2.mdl"; + iszViewModel = MAKE_STRING(viewmodel); + iszWeaponModel = MAKE_STRING("models/p_shot2.mdl"); + m_iQuakeItems |= IT_SHELLS; + szAnimExt = "shotgun"; + } + else if (m_iQuakeWeapon == IT_NAILGUN) + { + m_pCurrentAmmo = &m_iAmmoNails; + viewmodel = "models/v_nail.mdl"; + iszViewModel = MAKE_STRING(viewmodel); + iszWeaponModel = MAKE_STRING("models/p_nail.mdl"); + m_iQuakeItems |= IT_NAILS; + szAnimExt = "mp5"; + } + else if (m_iQuakeWeapon == IT_SUPER_NAILGUN) + { + m_pCurrentAmmo = &m_iAmmoNails; + viewmodel = "models/v_nail2.mdl"; + iszViewModel = MAKE_STRING(viewmodel); + iszWeaponModel = MAKE_STRING("models/p_nail2.mdl"); + m_iQuakeItems |= IT_NAILS; + szAnimExt = "mp5"; + } + else if (m_iQuakeWeapon == IT_GRENADE_LAUNCHER) + { + m_pCurrentAmmo = &m_iAmmoRockets; + viewmodel = "models/v_rock.mdl"; + iszViewModel = MAKE_STRING(viewmodel); + m_iQuakeItems |= IT_ROCKETS; + iszWeaponModel = MAKE_STRING("models/p_rock.mdl"); + szAnimExt = "gauss"; + } + else if (m_iQuakeWeapon == IT_ROCKET_LAUNCHER) + { + m_pCurrentAmmo = &m_iAmmoRockets; + viewmodel = "models/v_rock2.mdl"; + iszViewModel = MAKE_STRING(viewmodel); + m_iQuakeItems |= IT_ROCKETS; + iszWeaponModel = MAKE_STRING("models/p_rock2.mdl"); + szAnimExt = "gauss"; + } + else if (m_iQuakeWeapon == IT_LIGHTNING) + { + m_pCurrentAmmo = &m_iAmmoCells; + viewmodel = "models/v_light.mdl"; + iszViewModel = MAKE_STRING(viewmodel); + iszWeaponModel = MAKE_STRING("models/p_light.mdl"); + m_iQuakeItems |= IT_CELLS; + szAnimExt = "gauss"; + } + else + { + m_pCurrentAmmo = NULL; + } + +#if !defined( CLIENT_DLL ) + + pev->viewmodel = iszViewModel; + + pev->weaponmodel = iszWeaponModel; + strcpy( m_szAnimExtention, szAnimExt ); + +#else + { + + int HUD_GetModelIndex( char *modelname ); + pev->viewmodel = HUD_GetModelIndex( viewmodel ); + + cl_entity_t *view; + view = GetViewEntity(); + + //Adrian - The actual "magic" is done in the + //Studio drawing code. + if ( m_iQuakeItems & IT_INVISIBILITY ) + { + if( view ) + { + view->curstate.renderfx = kRenderFxGlowShell; + view->curstate.renderamt = 5; + + view->curstate.rendercolor.r = 125; + view->curstate.rendercolor.g = 125; + view->curstate.rendercolor.b = 125; + } + } + else + { + if ( m_iQuakeItems & IT_INVULNERABILITY ) + { + if( view ) + { + view->curstate.renderfx = kRenderFxGlowShell; + view->curstate.renderamt = 15; + + view->curstate.rendercolor.r = 255; + view->curstate.rendercolor.g = 125; + view->curstate.rendercolor.b = 125; + } + } + else if ( m_iQuakeItems & IT_QUAD ) + { + if( view ) + { + view->curstate.renderfx = kRenderFxGlowShell; + view->curstate.renderamt = 15; + + view->curstate.rendercolor.r = 125; + view->curstate.rendercolor.g = 125; + view->curstate.rendercolor.b = 255; + } + } + else if ( m_iQuakeItems & ( IT_INVULNERABILITY | IT_QUAD ) ) + { + if( view ) + { + view->curstate.renderfx = kRenderFxGlowShell; + view->curstate.renderamt = 15; + + view->curstate.rendercolor.r = 255; + view->curstate.rendercolor.g = 125; + view->curstate.rendercolor.b = 255; + } + } + else + view->curstate.renderfx = kRenderFxNone; // Clear it. + } + } +#endif +} + +// Return TRUE if the weapon still has ammo +BOOL CBasePlayer::W_CheckNoAmmo() +{ + if ( m_pCurrentAmmo && *m_pCurrentAmmo > 0 ) + return TRUE; + + if ( m_iQuakeWeapon == IT_AXE ) + return TRUE; + + if ( m_iQuakeWeapon == IT_LIGHTNING ) + { + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usLightning, 0, (float *)&pev->origin, (float *)&pev->angles, 0.0, 0.0, 0, 1, 0, 0 ); + + if ( m_pActiveItem ) + ((CQuakeGun*)m_pActiveItem)->DestroyEffect(); + } + + m_iQuakeWeapon = W_BestWeapon(); + W_SetCurrentAmmo(); + return FALSE; +} + +// Change to the specified weapon +void CBasePlayer::W_ChangeWeapon( int iWeaponNumber ) +{ + if ( m_iQuakeWeapon == IT_LIGHTNING ) + { + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usLightning, 0, (float *)&pev->origin, (float *)&pev->angles, 0.0, 0.0, 0, 1, 0, 0 ); + + if ( m_pActiveItem ) + ((CQuakeGun*)m_pActiveItem)->DestroyEffect(); + } + + int iWeapon = 0; + BOOL bHaveAmmo = TRUE; + + if (iWeaponNumber == 1) + { + iWeapon = IT_AXE; + } + else if (iWeaponNumber == 2) + { + iWeapon = IT_SHOTGUN; + if (m_iAmmoShells < 1) + bHaveAmmo = FALSE; + } + else if (iWeaponNumber == 3) + { + iWeapon = IT_SUPER_SHOTGUN; + if (m_iAmmoShells < 2) + bHaveAmmo = FALSE; + } + else if (iWeaponNumber == 4) + { + iWeapon = IT_NAILGUN; + if (m_iAmmoNails < 1) + bHaveAmmo = FALSE; + } + else if (iWeaponNumber == 5) + { + iWeapon = IT_SUPER_NAILGUN; + if (m_iAmmoNails < 2) + bHaveAmmo = FALSE; + } + else if (iWeaponNumber == 6) + { + iWeapon = IT_GRENADE_LAUNCHER; + if (m_iAmmoRockets < 1) + bHaveAmmo = FALSE; + } + else if (iWeaponNumber == 7) + { + iWeapon = IT_ROCKET_LAUNCHER; + if (m_iAmmoRockets < 1) + bHaveAmmo = FALSE; + } + else if (iWeaponNumber == 8) + { + iWeapon = IT_LIGHTNING; + + if (m_iAmmoCells < 1) + bHaveAmmo = FALSE; + } + + // Have the weapon? + if ( !(m_iQuakeItems & iWeapon) ) + { + ClientPrint( pev, HUD_PRINTCONSOLE, "#No_Weapon" ); + return; + } + + // Have ammo for it? + if ( !bHaveAmmo ) + { + ClientPrint( pev, HUD_PRINTCONSOLE, "#No_Ammo" ); + return; + } + + // Set weapon, update ammo + m_iQuakeWeapon = iWeapon; + W_SetCurrentAmmo(); + +#ifdef CLIENT_DLL + g_flLightTime = 0.0; +#endif +} + +// Go to the next weapon with ammo +void CBasePlayer::W_CycleWeaponCommand( void ) +{ + while (1) + { + BOOL bHaveAmmo = TRUE; + + if (m_iQuakeWeapon == IT_LIGHTNING) + { + m_iQuakeWeapon = IT_EXTRA_WEAPON; + } + else if (m_iQuakeWeapon == IT_EXTRA_WEAPON) + { + m_iQuakeWeapon = IT_AXE; + } + else if (m_iQuakeWeapon == IT_AXE) + { + m_iQuakeWeapon = IT_SHOTGUN; + if (m_iAmmoShells < 1) + bHaveAmmo = FALSE; + } + else if (m_iQuakeWeapon == IT_SHOTGUN) + { + m_iQuakeWeapon = IT_SUPER_SHOTGUN; + if (m_iAmmoShells < 2) + bHaveAmmo = FALSE; + } + else if (m_iQuakeWeapon == IT_SUPER_SHOTGUN) + { + m_iQuakeWeapon = IT_NAILGUN; + if (m_iAmmoNails < 1) + bHaveAmmo = FALSE; + } + else if (m_iQuakeWeapon == IT_NAILGUN) + { + m_iQuakeWeapon = IT_SUPER_NAILGUN; + if (m_iAmmoNails < 2) + bHaveAmmo = FALSE; + } + else if (m_iQuakeWeapon == IT_SUPER_NAILGUN) + { + m_iQuakeWeapon = IT_GRENADE_LAUNCHER; + if (m_iAmmoRockets < 1) + bHaveAmmo = FALSE; + } + else if (m_iQuakeWeapon == IT_GRENADE_LAUNCHER) + { + m_iQuakeWeapon = IT_ROCKET_LAUNCHER; + if (m_iAmmoRockets < 1) + bHaveAmmo = FALSE; + } + else if (m_iQuakeWeapon == IT_ROCKET_LAUNCHER) + { + m_iQuakeWeapon = IT_LIGHTNING; + if (m_iAmmoCells < 1) + bHaveAmmo = FALSE; + } + + if ( (m_iQuakeItems & m_iQuakeWeapon) && bHaveAmmo ) + { + W_SetCurrentAmmo(); + return; + } + } + +} + +// Go to the prev weapon with ammo +void CBasePlayer::W_CycleWeaponReverseCommand() +{ + while (1) + { + BOOL bHaveAmmo = TRUE; + + if (m_iQuakeWeapon == IT_EXTRA_WEAPON) + { + m_iQuakeWeapon = IT_LIGHTNING; + } + else if (m_iQuakeWeapon == IT_LIGHTNING) + { + m_iQuakeWeapon = IT_ROCKET_LAUNCHER; + if (m_iAmmoRockets < 1) + bHaveAmmo = FALSE; + } + else if (m_iQuakeWeapon == IT_ROCKET_LAUNCHER) + { + m_iQuakeWeapon = IT_GRENADE_LAUNCHER; + if (m_iAmmoRockets < 1) + bHaveAmmo = FALSE; + } + else if (m_iQuakeWeapon == IT_GRENADE_LAUNCHER) + { + m_iQuakeWeapon = IT_SUPER_NAILGUN; + if (m_iAmmoNails < 2) + bHaveAmmo = FALSE; + } + else if (m_iQuakeWeapon == IT_SUPER_NAILGUN) + { + m_iQuakeWeapon = IT_NAILGUN; + if (m_iAmmoNails < 1) + bHaveAmmo = FALSE; + } + else if (m_iQuakeWeapon == IT_NAILGUN) + { + m_iQuakeWeapon = IT_SUPER_SHOTGUN; + if (m_iAmmoShells < 2) + bHaveAmmo = FALSE; + } + else if (m_iQuakeWeapon == IT_SUPER_SHOTGUN) + { + m_iQuakeWeapon = IT_SHOTGUN; + if (m_iAmmoShells < 1) + bHaveAmmo = FALSE; + } + else if (m_iQuakeWeapon == IT_SHOTGUN) + { + m_iQuakeWeapon = IT_AXE; + } + else if (m_iQuakeWeapon == IT_AXE) + { + m_iQuakeWeapon = IT_EXTRA_WEAPON; + } + else if (m_iQuakeWeapon == IT_EXTRA_WEAPON) + { + m_iQuakeWeapon = IT_LIGHTNING; + if (m_iAmmoCells < 1) + bHaveAmmo = FALSE; + } + + + if ( (m_iQuakeItems & m_iQuakeWeapon) && bHaveAmmo ) + { + W_SetCurrentAmmo(); + return; + } + } + +} + +//================================================================================================ +// WEAPON FUNCTIONS +//================================================================================================ +// Returns true if the inflictor can directly damage the target. Used for explosions and melee attacks. +float Q_CanDamage(CBaseEntity *pTarget, CBaseEntity *pInflictor) +{ + TraceResult trace; + + // bmodels need special checking because their origin is 0,0,0 + if (pTarget->pev->movetype == MOVETYPE_PUSH) + { + UTIL_TraceLine( pInflictor->pev->origin, 0.5 * (pTarget->pev->absmin + pTarget->pev->absmax), ignore_monsters, NULL, &trace ); + if (trace.flFraction == 1) + return TRUE; + CBaseEntity *pEntity = CBaseEntity::Instance(trace.pHit); + if (pEntity == pTarget) + return TRUE; + return FALSE; + } + + UTIL_TraceLine( pInflictor->pev->origin, pTarget->pev->origin, ignore_monsters, NULL, &trace ); + if (trace.flFraction == 1) + return TRUE; + UTIL_TraceLine( pInflictor->pev->origin, pTarget->pev->origin + Vector(15,15,0), ignore_monsters, NULL, &trace ); + if (trace.flFraction == 1) + return TRUE; + UTIL_TraceLine( pInflictor->pev->origin, pTarget->pev->origin + Vector(-15,-15,0), ignore_monsters, NULL, &trace ); + if (trace.flFraction == 1) + return TRUE; + UTIL_TraceLine( pInflictor->pev->origin, pTarget->pev->origin + Vector(-15,15,0), ignore_monsters, NULL, &trace ); + if (trace.flFraction == 1) + return TRUE; + UTIL_TraceLine( pInflictor->pev->origin, pTarget->pev->origin + Vector(15,-15,0), ignore_monsters, NULL, &trace ); + if (trace.flFraction == 1) + return TRUE; + + return FALSE; +} + +// Quake Bullet firing +void CBasePlayer::Q_FireBullets(int iShots, Vector vecDir, Vector vecSpread) +{ + TraceResult trace; + UTIL_MakeVectors(pev->v_angle); + + Vector vecSrc = pev->origin + (gpGlobals->v_forward * 10); + vecSrc.z = pev->absmin.z + (pev->size.z * 0.7); + ClearMultiDamage(); + + while ( iShots > 0 ) + { + Vector vecPath = vecDir + ( RANDOM_FLOAT( -1, 1 ) * vecSpread.x * gpGlobals->v_right ) + ( RANDOM_FLOAT( -1, 1 ) * vecSpread.y * gpGlobals->v_up ); + Vector vecEnd = vecSrc + ( vecPath * 2048 ); + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, ENT(pev), &trace ); + if (trace.flFraction != 1.0) + { + CBaseEntity *pEntity = CBaseEntity::Instance(trace.pHit); + if (pEntity && pEntity->pev->takedamage && pEntity->IsPlayer() ) + { + pEntity->TraceAttack(pev, 4, vecPath, &trace, DMG_BULLET); + //AddMultiDamage(pev, pEntity, 4, DMG_BULLET); + } + else if ( pEntity && pEntity->pev->takedamage ) + { + pEntity->TakeDamage( pev, pev, 4, DMG_BULLET ); + } + } + + iShots--; + } + + ApplyMultiDamage( pev, pev ); +} + +#if !defined( CLIENT_DLL ) +// Quake Radius damage +void Q_RadiusDamage( CBaseEntity *pInflictor, CBaseEntity *pAttacker, float flDamage, CBaseEntity *pIgnore ) +{ + CBaseEntity *pEnt = NULL; + + while ( (pEnt = UTIL_FindEntityInSphere( pEnt, pInflictor->pev->origin, flDamage+40 )) != NULL ) + { + if (pEnt != pIgnore) + { + if (pEnt->pev->takedamage) + { + Vector vecOrg = pEnt->pev->origin + ((pEnt->pev->mins + pEnt->pev->maxs) * 0.5); + float flPoints = 0.5 * (pInflictor->pev->origin - vecOrg).Length(); + if (flPoints < 0) + flPoints = 0; + flPoints = flDamage - flPoints; + + if (pEnt == pAttacker) + flPoints = flPoints * 0.5; + if (flPoints > 0) + { + if ( Q_CanDamage( pEnt, pInflictor ) ) + pEnt->TakeDamage( pInflictor->pev, pAttacker->pev, flPoints, DMG_GENERIC ); + } + } + } + } +} +#endif + +// Lightning hit a target +void LightningHit(CBaseEntity *pTarget, CBaseEntity *pAttacker, Vector vecHitPos, float flDamage, TraceResult *ptr, Vector vecDir ) +{ + +#ifndef CLIENT_DLL + SpawnBlood( vecHitPos, BLOOD_COLOR_RED, flDamage * 1.5 ); + + if ( g_pGameRules->PlayerRelationship( pTarget, pAttacker ) != GR_TEAMMATE ) + { + + pTarget->TakeDamage( pAttacker->pev, pAttacker->pev, flDamage, DMG_GENERIC ); + pTarget->TraceBleed( flDamage, vecDir, ptr, DMG_BULLET ); // have to use DMG_BULLET or it wont spawn. + + } +#endif +} + +// Lightning Damage +void CBasePlayer::LightningDamage( Vector p1, Vector p2, CBaseEntity *pAttacker, float flDamage, Vector vecDir) +{ +#if !defined( CLIENT_DLL ) + TraceResult trace; + Vector vecThru = (p2 - p1).Normalize(); + vecThru.x = 0 - vecThru.y; + vecThru.y = vecThru.x; + vecThru.z = 0; + vecThru = vecThru * 16; + + CBaseEntity *pEntity1 = NULL; + CBaseEntity *pEntity2 = NULL; + + // Hit first target? + UTIL_TraceLine( p1, p2, dont_ignore_monsters, ENT(pev), &trace ); + CBaseEntity *pEntity = CBaseEntity::Instance(trace.pHit); + if (pEntity && pEntity->pev->takedamage) + { + LightningHit(pEntity, pAttacker, trace.vecEndPos, flDamage, &trace, vecDir ); + } + pEntity1 = pEntity; + + // Hit second target? + UTIL_TraceLine( p1 + vecThru, p2 + vecThru, dont_ignore_monsters, ENT(pev), &trace ); + pEntity = CBaseEntity::Instance(trace.pHit); + if (pEntity && pEntity != pEntity1 && pEntity->pev->takedamage) + { + LightningHit(pEntity, pAttacker, trace.vecEndPos, flDamage, &trace, vecDir ); + } + pEntity2 = pEntity; + + // Hit third target? + UTIL_TraceLine( p1 - vecThru, p2 - vecThru, dont_ignore_monsters, ENT(pev), &trace ); + pEntity = CBaseEntity::Instance(trace.pHit); + if (pEntity && pEntity != pEntity1 && pEntity != pEntity2 && pEntity->pev->takedamage) + { + LightningHit(pEntity, pAttacker, trace.vecEndPos, flDamage, &trace, vecDir ); + } +#endif +} + +//================================================================================================ +// WEAPON FIRING +//================================================================================================ +// Axe +void CBasePlayer::W_FireAxe() +{ + TraceResult trace; + Vector vecSrc = pev->origin + Vector(0, 0, 16); + + // Swing forward 64 units + UTIL_MakeVectors(pev->v_angle); + UTIL_TraceLine( vecSrc, vecSrc + (gpGlobals->v_forward * 64), dont_ignore_monsters, ENT(pev), &trace ); + if (trace.flFraction == 1.0) + return; + + Vector vecOrg = trace.vecEndPos - gpGlobals->v_forward * 4; + + CBaseEntity *pEntity = CBaseEntity::Instance(trace.pHit); + if (pEntity && pEntity->pev->takedamage) + { + pEntity->m_bAxHitMe = TRUE; + int iDmg = 20; + if (gpGlobals->deathmatch > 3) + iDmg = 75; + + pEntity->TakeDamage( pev, pev, iDmg, DMG_GENERIC ); + +#ifndef CLIENT_DLL + if ( g_pGameRules->PlayerRelationship( this, pEntity ) != GR_TEAMMATE ) + SpawnBlood( vecOrg, BLOOD_COLOR_RED, iDmg * 4 ); // Make a lot of Blood! +#endif + } +} + +// Single barrel shotgun +void CBasePlayer::W_FireShotgun( int iQuadSound ) +{ + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usShotgunSingle, 0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, iQuadSound, 0, 0, 0 ); + + if (gpGlobals->deathmatch != 4 ) + *m_pCurrentAmmo -= 1; + + Vector vecDir = GetAutoaimVector( AUTOAIM_5DEGREES ); + Q_FireBullets(6, vecDir, Vector(0.04, 0.04, 0) ); +} + +// Double barrel shotgun +void CBasePlayer::W_FireSuperShotgun( int iQuadSound ) +{ + if (*m_pCurrentAmmo == 1) + { + W_FireShotgun( iQuadSound ); + return; + } + + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usShotgunDouble, 0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, iQuadSound, 0, 0, 0 ); + + if (gpGlobals->deathmatch != 4 ) + *m_pCurrentAmmo -= 2; + Vector vecDir = GetAutoaimVector( AUTOAIM_5DEGREES ); + Q_FireBullets(14, vecDir, Vector(0.14, 0.08, 0) ); +}; + +// Rocket launcher +void CBasePlayer::W_FireRocket( int iQuadSound ) +{ + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usRocket, 0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, iQuadSound, 0, 0, 0 ); + + if (gpGlobals->deathmatch != 4 ) + *m_pCurrentAmmo -= 1; + + // Create the rocket + UTIL_MakeVectors( pev->v_angle ); + Vector vecOrg = pev->origin + (gpGlobals->v_forward * 8) + Vector(0,0,16); + Vector vecDir = GetAutoaimVector( AUTOAIM_5DEGREES ); + CQuakeRocket::CreateRocket( vecOrg, vecDir, this ); +} + +// Grenade launcher +void CBasePlayer::W_FireGrenade( int iQuadSound ) +{ + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usGrenade, 0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, iQuadSound, 0, 0, 0 ); + + if (gpGlobals->deathmatch != 4 ) + *m_pCurrentAmmo -= 1; + + // Get initial velocity + UTIL_MakeVectors( pev->v_angle ); + Vector vecVelocity; + if ( pev->v_angle.x ) + { + vecVelocity = gpGlobals->v_forward * 600 + gpGlobals->v_up * 200 + RANDOM_FLOAT(-1,1) * gpGlobals->v_right * 10 + RANDOM_FLOAT(-1,1) * gpGlobals->v_up * 10; + } + else + { + vecVelocity = GetAutoaimVector( AUTOAIM_5DEGREES ); + vecVelocity = vecVelocity * 600; + vecVelocity.z = 200; + } + + // Create the grenade + CQuakeRocket::CreateGrenade( pev->origin, vecVelocity, this ); +} + +// Lightning Gun +void CBasePlayer::W_FireLightning( int iQuadSound ) +{ + if (*m_pCurrentAmmo < 1) + { + //This should already be IT_LIGHTNING but what the heck. + if ( m_iQuakeWeapon == IT_LIGHTNING ) + { + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usLightning, 0, (float *)&pev->origin, (float *)&pev->angles, 0.0, 0.0, 0, 1, 0, 0 ); + + if ( m_pActiveItem ) + ((CQuakeGun*)m_pActiveItem)->DestroyEffect(); + } + + m_iQuakeWeapon = W_BestWeapon (); + W_SetCurrentAmmo(); + return; + } + + bool playsound = false; + + // Make lightning sound every 0.6 seconds + if ( m_flLightningTime <= gpGlobals->time ) + { + playsound = true; + m_flLightningTime = gpGlobals->time + 0.6; + } + + // explode if under water + if (pev->waterlevel > 1) + { + if ( (gpGlobals->deathmatch > 3) && (RANDOM_FLOAT(0, 1) <= 0.5) ) + { + strcpy( gszQ_DeathType, "selfwater" ); + TakeDamage( pev, pev, 4000, DMG_GENERIC ); + } + else + { + float flCellsBurnt = *m_pCurrentAmmo; + *m_pCurrentAmmo = 0; + W_SetCurrentAmmo(); +#if !defined( CLIENT_DLL ) + Q_RadiusDamage( this, this, 35 * flCellsBurnt, NULL ); +#endif + return; + } + } + + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usLightning, 0, (float *)&pev->origin, (float *)&pev->angles, 0.0, 0.0, iQuadSound, 0, playsound, 0 ); + +#if !defined( CLIENT_DLL ) + + if (gpGlobals->deathmatch != 4 ) + *m_pCurrentAmmo -= 1; + + // Lightning bolt effect + TraceResult trace; + Vector vecOrg = pev->origin + Vector(0,0,16); + UTIL_MakeVectors( pev->v_angle ); + UTIL_TraceLine( vecOrg, vecOrg + (gpGlobals->v_forward * 600), ignore_monsters, ENT(pev), &trace ); + + Vector vecDir = gpGlobals->v_forward + ( 0.001 * gpGlobals->v_right ) + ( 0.001 * gpGlobals->v_up ); + // Do damage + LightningDamage(pev->origin, trace.vecEndPos + (gpGlobals->v_forward * 4), this, 30, vecDir ); + +#endif +} + +// Super Nailgun +void CBasePlayer::W_FireSuperSpikes( int iQuadSound ) +{ + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usSuperSpike, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, iQuadSound, 0, m_iNailOffset > 0.0 ? 1 : 0, 0 ); + + if (gpGlobals->deathmatch != 4 ) + *m_pCurrentAmmo -= 2; + m_flNextAttack = UTIL_WeaponTimeBase() + 0.1; + + // Fire the Nail + Vector vecDir = GetAutoaimVector( AUTOAIM_5DEGREES ); + CQuakeNail::CreateSuperNail( pev->origin + Vector(0,0,16), vecDir, this ); +} + +// Nailgun +void CBasePlayer::W_FireSpikes( int iQuadSound ) +{ + // If we're wielding the Super nailgun and we've got ammo for it, fire Super nails + if (*m_pCurrentAmmo >= 2 && m_iQuakeWeapon == IT_SUPER_NAILGUN) + { + W_FireSuperSpikes( iQuadSound ); + return; + } + + // Swap to next best weapon if this one just ran out + if (*m_pCurrentAmmo < 1) + { + m_iQuakeWeapon = W_BestWeapon (); + W_SetCurrentAmmo(); + return; + } + + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usSpike, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, iQuadSound, 0, m_iNailOffset > 0.0 ? 1 : 0, 0 ); + + // Fire left then right + if (m_iNailOffset == 2) + m_iNailOffset = -2; + else + m_iNailOffset = 2; + + if (gpGlobals->deathmatch != 4 ) + *m_pCurrentAmmo -= 1; + m_flNextAttack = UTIL_WeaponTimeBase() + 0.1; + + // Fire the nail + UTIL_MakeVectors( pev->v_angle ); + Vector vecDir = GetAutoaimVector( AUTOAIM_5DEGREES ); + CQuakeNail::CreateNail( pev->origin + Vector(0,0,10) + (gpGlobals->v_right * m_iNailOffset), vecDir, this ); +} + +//=============================================================================== +// PLAYER WEAPON USE +//=============================================================================== +void CBasePlayer::W_Attack( int iQuadSound ) +{ + // Out of ammo? + if ( !W_CheckNoAmmo() ) + return; + + if ( m_iQuakeWeapon != IT_LIGHTNING ) + ((CBasePlayerWeapon*)m_pActiveItem)->SendWeaponAnim( 1, 1 ); + + if (m_iQuakeWeapon == IT_AXE) + { + m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usAxeSwing, 0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, iQuadSound, 0, 0, 0 ); + + // Delay attack for 0.3 + m_flAxeFire = gpGlobals->time + 0.3; + + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usAxe, 0.3, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, iQuadSound, 0, 0, 0 ); + + } + else if (m_iQuakeWeapon == IT_SHOTGUN) + { + m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + + W_FireShotgun( iQuadSound ); + } + else if (m_iQuakeWeapon == IT_SUPER_SHOTGUN) + { + m_flNextAttack = UTIL_WeaponTimeBase() + 0.7; + + W_FireSuperShotgun( iQuadSound ); + } + else if (m_iQuakeWeapon == IT_NAILGUN) + { + m_flNextAttack = UTIL_WeaponTimeBase() + 0.1; + + W_FireSpikes( iQuadSound ); + } + else if (m_iQuakeWeapon == IT_SUPER_NAILGUN) + { + m_flNextAttack = UTIL_WeaponTimeBase() + 0.1; + W_FireSpikes( iQuadSound ); + } + else if (m_iQuakeWeapon == IT_GRENADE_LAUNCHER) + { + m_flNextAttack = UTIL_WeaponTimeBase() + 0.6; + + W_FireGrenade( iQuadSound ); + } + else if (m_iQuakeWeapon == IT_ROCKET_LAUNCHER) + { + m_flNextAttack = UTIL_WeaponTimeBase() + 0.8; + + W_FireRocket( iQuadSound ); + } + else if (m_iQuakeWeapon == IT_LIGHTNING) + { + m_flNextAttack = UTIL_WeaponTimeBase() + 0.1; + + // Play the lightning start sound if gun just started firing + if (m_afButtonPressed & IN_ATTACK) + EMIT_SOUND(ENT(pev), CHAN_AUTO, "weapons/lstart.wav", 1, ATTN_NORM); + + W_FireLightning( iQuadSound ); + } + + // Make player attack + if ( pev->health >= 0 ) + SetAnimation( PLAYER_ATTACK1 ); +} diff --git a/hlsdk/dmc/dlls/saverestore.h b/hlsdk/dmc/dlls/saverestore.h new file mode 100644 index 00000000..0ebff94d --- /dev/null +++ b/hlsdk/dmc/dlls/saverestore.h @@ -0,0 +1,177 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Implementation in UTIL.CPP +#ifndef SAVERESTORE_H +#define SAVERESTORE_H + +class CBaseEntity; + +class CSaveRestoreBuffer +{ +public: + CSaveRestoreBuffer( void ); + CSaveRestoreBuffer( SAVERESTOREDATA *pdata ); + virtual ~CSaveRestoreBuffer( void ); + + int EntityIndex( entvars_t *pevLookup ); + int EntityIndex( edict_t *pentLookup ); + int EntityIndex( EOFFSET eoLookup ); + int EntityIndex( CBaseEntity *pEntity ); + + int EntityFlags( int entityIndex, int flags ) { return EntityFlagsSet( entityIndex, 0 ); } + int EntityFlagsSet( int entityIndex, int flags ); + + edict_t *EntityFromIndex( int entityIndex ); + + unsigned short TokenHash( const char *pszToken ); + +protected: + SAVERESTOREDATA *m_pdata; + void BufferRewind( int size ); + unsigned int HashString( const char *pszToken ); +private: + // effc++ rule 11 + void operator=(CSaveRestoreBuffer&); + CSaveRestoreBuffer(const CSaveRestoreBuffer&); +}; + + +class CSave : public CSaveRestoreBuffer +{ +public: + CSave( SAVERESTOREDATA *pdata ) : CSaveRestoreBuffer( pdata ) {}; + + void WriteShort( const char *pname, const short *value, int count ); + void WriteInt( const char *pname, const int *value, int count ); // Save an int + void WriteFloat( const char *pname, const float *value, int count ); // Save a float + void WriteTime( const char *pname, const float *value, int count ); // Save a float (timevalue) + void WriteData( const char *pname, int size, const char *pdata ); // Save a binary data block + void WriteString( const char *pname, const char *pstring ); // Save a null-terminated string + void WriteString( const char *pname, const int *stringId, int count ); // Save a null-terminated string (engine string) + void WriteVector( const char *pname, const Vector &value ); // Save a vector + void WriteVector( const char *pname, const float *value, int count ); // Save a vector + void WritePositionVector( const char *pname, const Vector &value ); // Offset for landmark if necessary + void WritePositionVector( const char *pname, const float *value, int count ); // array of pos vectors + void WriteFunction( const char *pname, const int *value, int count ); // Save a function pointer + int WriteEntVars( const char *pname, entvars_t *pev ); // Save entvars_t (entvars_t) + int WriteFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); + +private: + int DataEmpty( const char *pdata, int size ); + void BufferField( const char *pname, int size, const char *pdata ); + void BufferString( char *pdata, int len ); + void BufferData( const char *pdata, int size ); + void BufferHeader( const char *pname, int size ); +}; + +typedef struct +{ + unsigned short size; + unsigned short token; + char *pData; +} HEADER; + +class CRestore : public CSaveRestoreBuffer +{ +public: + CRestore( SAVERESTOREDATA *pdata ) : CSaveRestoreBuffer( pdata ), m_global(0), m_precache(TRUE) { } + + int ReadEntVars( const char *pname, entvars_t *pev ); // entvars_t + int ReadFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); + int ReadField( void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount, int startField, int size, char *pName, void *pData ); + int ReadInt( void ); + short ReadShort( void ); + int ReadNamedInt( const char *pName ); + char *ReadNamedString( const char *pName ); + int Empty( void ) { return (m_pdata == NULL) || ((m_pdata->pCurrentData-m_pdata->pBaseData)>=m_pdata->bufferSize); } + inline void SetGlobalMode( int global ) { m_global = global; } + void PrecacheMode( BOOL mode ) { m_precache = mode; } + +private: + char *BufferPointer( void ); + void BufferReadBytes( char *pOutput, int size ); + void BufferSkipBytes( int bytes ); + int BufferSkipZString( void ); + int BufferCheckZString( const char *string ); + + void BufferReadHeader( HEADER *pheader ); + + int m_global; // Restoring a global entity? + BOOL m_precache; +}; + +#define MAX_ENTITYARRAY 64 + +//#define ARRAYSIZE(p) (sizeof(p)/sizeof(p[0])) + +#define IMPLEMENT_SAVERESTORE(derivedClass,baseClass) \ + int derivedClass::Save( CSave &save )\ + {\ + if ( !baseClass::Save(save) )\ + return 0;\ + return save.WriteFields( #derivedClass, this, m_SaveData, ARRAYSIZE(m_SaveData) );\ + }\ + int derivedClass::Restore( CRestore &restore )\ + {\ + if ( !baseClass::Restore(restore) )\ + return 0;\ + return restore.ReadFields( #derivedClass, this, m_SaveData, ARRAYSIZE(m_SaveData) );\ + } + + +typedef enum { GLOBAL_OFF = 0, GLOBAL_ON = 1, GLOBAL_DEAD = 2 } GLOBALESTATE; + +typedef struct globalentity_s globalentity_t; + +struct globalentity_s +{ + char name[64]; + char levelName[32]; + GLOBALESTATE state; + globalentity_t *pNext; +}; + +class CGlobalState +{ +public: + CGlobalState(); + void Reset( void ); + void ClearStates( void ); + void EntityAdd( string_t globalname, string_t mapName, GLOBALESTATE state ); + void EntitySetState( string_t globalname, GLOBALESTATE state ); + void EntityUpdate( string_t globalname, string_t mapname ); + const globalentity_t *EntityFromTable( string_t globalname ); + GLOBALESTATE EntityGetState( string_t globalname ); + int EntityInTable( string_t globalname ) { return (Find( globalname ) != NULL) ? 1 : 0; } + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +//#ifdef _DEBUG + void DumpGlobals( void ); +//#endif + +private: + globalentity_t *Find( string_t globalname ); + globalentity_t *m_pList; + int m_listCount; + // effc++ rule 11 + void operator=(CGlobalState&); + CGlobalState(const CGlobalState&); +}; + +extern CGlobalState gGlobalState; + +#endif //SAVERESTORE_H diff --git a/hlsdk/dmc/dlls/schedule.cpp b/hlsdk/dmc/dlls/schedule.cpp new file mode 100644 index 00000000..9b15dcfa --- /dev/null +++ b/hlsdk/dmc/dlls/schedule.cpp @@ -0,0 +1,39 @@ +/*** +* +* Copyright (c) 1996-2002,, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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 "defaultai.h" + +extern CGraph WorldGraph; + +BOOL CBaseMonster :: FHaveSchedule( void ) { return FALSE; }; +void CBaseMonster :: ClearSchedule( void ) { }; +BOOL CBaseMonster :: FScheduleDone ( void ) { return FALSE; }; +void CBaseMonster :: ChangeSchedule ( Schedule_t *pNewSchedule ) { }; +void CBaseMonster :: NextScheduledTask ( void ) { }; +int CBaseMonster :: IScheduleFlags ( void ) { return 0; }; +BOOL CBaseMonster :: FScheduleValid ( void ) { return FALSE; }; +void CBaseMonster :: MaintainSchedule ( void ) { }; +void CBaseMonster :: SetTurnActivity ( void ) { }; +Task_t *CBaseMonster :: GetTask ( void ) { return NULL; }; diff --git a/hlsdk/dmc/dlls/schedule.h b/hlsdk/dmc/dlls/schedule.h new file mode 100644 index 00000000..82cbdeb7 --- /dev/null +++ b/hlsdk/dmc/dlls/schedule.h @@ -0,0 +1,290 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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_BARNACLE_VICTIM_GRAB, + SCHED_BARNACLE_VICTIM_CHOMP, + 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/hlsdk/dmc/dlls/scripted.cpp b/hlsdk/dmc/dlls/scripted.cpp new file mode 100644 index 00000000..10659f54 --- /dev/null +++ b/hlsdk/dmc/dlls/scripted.cpp @@ -0,0 +1,1260 @@ +/*** +* +* Copyright (c) 1996-2002 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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" +#include "defaultai.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 ) +{ + Schedule_t *pNewSchedule; + + 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; + } + } +*/ + // Already in a scripted state? + if ( pTarget->m_MonsterState == MONSTERSTATE_SCRIPT ) + { + pNewSchedule = pTarget->GetScheduleOfType( SCHED_AISCRIPT ); + pTarget->ChangeSchedule( pNewSchedule ); + } + } +} + +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: + pMonster->ChangeSchedule( pMonster->GetScheduleOfType( SCHED_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; + SetState( MONSTERSTATE_DEAD ); + 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/hlsdk/dmc/dlls/scripted.h b/hlsdk/dmc/dlls/scripted.h new file mode 100644 index 00000000..dee12d72 --- /dev/null +++ b/hlsdk/dmc/dlls/scripted.h @@ -0,0 +1,107 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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/hlsdk/dmc/dlls/scriptevent.h b/hlsdk/dmc/dlls/scriptevent.h new file mode 100644 index 00000000..42377cf0 --- /dev/null +++ b/hlsdk/dmc/dlls/scriptevent.h @@ -0,0 +1,29 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef SCRIPTEVENT_H +#define SCRIPTEVENT_H + +#define SCRIPT_EVENT_DEAD 1000 // character is now dead +#define SCRIPT_EVENT_NOINTERRUPT 1001 // does not allow interrupt +#define SCRIPT_EVENT_CANINTERRUPT 1002 // will allow interrupt +#define SCRIPT_EVENT_FIREEVENT 1003 // event now fires +#define SCRIPT_EVENT_SOUND 1004 // Play named wave file (on CHAN_BODY) +#define SCRIPT_EVENT_SENTENCE 1005 // Play named sentence +#define SCRIPT_EVENT_INAIR 1006 // Leave the character in air at the end of the sequence (don't find the floor) +#define SCRIPT_EVENT_ENDANIMATION 1007 // Set the animation by name after the sequence completes +#define SCRIPT_EVENT_SOUND_VOICE 1008 // Play named wave file (on CHAN_VOICE) +#define SCRIPT_EVENT_SENTENCE_RND1 1009 // Play sentence group 25% of the time +#define SCRIPT_EVENT_NOT_DEAD 1010 // Bring back to life (for life/death sequences) +#endif //SCRIPTEVENT_H diff --git a/hlsdk/dmc/dlls/singleplay_gamerules.cpp b/hlsdk/dmc/dlls/singleplay_gamerules.cpp new file mode 100644 index 00000000..947df3f7 --- /dev/null +++ b/hlsdk/dmc/dlls/singleplay_gamerules.cpp @@ -0,0 +1,336 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// teamplay_gamerules.cpp +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "skill.h" +#include "items.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 ) +{ + // Start with init ammoload + pPlayer->m_iAmmoShells = 25; + + // Start with shotgun and axe + pPlayer->GiveNamedItem( "weapon_quakegun" ); + pPlayer->m_iQuakeItems |= (IT_SHOTGUN | IT_AXE); + pPlayer->m_iQuakeWeapon = pPlayer->W_BestWeapon(); + pPlayer->W_SetCurrentAmmo(); +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules :: AllowAutoTargetCrosshair( void ) +{ + return ( g_iSkillLevel == SKILL_EASY ); +} + +//========================================================= +//========================================================= +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/hlsdk/dmc/dlls/skill.cpp b/hlsdk/dmc/dlls/skill.cpp new file mode 100644 index 00000000..d82a607b --- /dev/null +++ b/hlsdk/dmc/dlls/skill.cpp @@ -0,0 +1,47 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// skill.cpp - code for skill level concerns +//========================================================= +#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 +// on, and return the value.of that Cvar +//========================================================= +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; +} + diff --git a/hlsdk/dmc/dlls/skill.h b/hlsdk/dmc/dlls/skill.h new file mode 100644 index 00000000..5ec320c2 --- /dev/null +++ b/hlsdk/dmc/dlls/skill.h @@ -0,0 +1,147 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// skill.h - skill level concerns +//========================================================= + +struct skilldata_t +{ + + int iSkillLevel; // game skill level + +// Monster Health & Damage + float agruntHealth; + float agruntDmgPunch; + + float apacheHealth; + + float barneyHealth; + + float bigmommaHealthFactor; // Multiply each node's health by this + float bigmommaDmgSlash; // melee attack damage + float bigmommaDmgBlast; // mortar attack damage + float bigmommaRadiusBlast; // mortar attack radius + + float bullsquidHealth; + float bullsquidDmgBite; + float bullsquidDmgWhip; + float bullsquidDmgSpit; + + float gargantuaHealth; + float gargantuaDmgSlash; + float gargantuaDmgFire; + float gargantuaDmgStomp; + + float hassassinHealth; + + float headcrabHealth; + float headcrabDmgBite; + + float hgruntHealth; + float hgruntDmgKick; + float hgruntShotgunPellets; + float hgruntGrenadeSpeed; + + float houndeyeHealth; + float houndeyeDmgBlast; + + float slaveHealth; + float slaveDmgClaw; + float slaveDmgClawrake; + float slaveDmgZap; + + float ichthyosaurHealth; + float ichthyosaurDmgShake; + + float leechHealth; + float leechDmgBite; + + float controllerHealth; + float controllerDmgZap; + float controllerSpeedBall; + float controllerDmgBall; + + float nihilanthHealth; + float nihilanthZap; + + float scientistHealth; + + float snarkHealth; + float snarkDmgBite; + float snarkDmgPop; + + float zombieHealth; + float zombieDmgOneSlash; + float zombieDmgBothSlash; + + float turretHealth; + float miniturretHealth; + float sentryHealth; + + +// Player Weapons + float plrDmgCrowbar; + float plrDmg9MM; + float plrDmg357; + float plrDmgMP5; + float plrDmgM203Grenade; + float plrDmgBuckshot; + float plrDmgCrossbowClient; + float plrDmgCrossbowMonster; + float plrDmgRPG; + float plrDmgGauss; + float plrDmgEgonNarrow; + float plrDmgEgonWide; + float plrDmgHornet; + float plrDmgHandGrenade; + float plrDmgSatchel; + float plrDmgTripmine; + +// weapons shared by monsters + float monDmg9MM; + float monDmgMP5; + float monDmg12MM; + float monDmgHornet; + +// health/suit charge + float suitchargerCapacity; + float batteryCapacity; + float healthchargerCapacity; + float healthkitCapacity; + float scientistHeal; + +// monster damage adj + float monHead; + float monChest; + float monStomach; + float monLeg; + float monArm; + +// player damage adj + float plrHead; + float plrChest; + float plrStomach; + float plrLeg; + float plrArm; +}; + +extern DLL_GLOBAL skilldata_t gSkillData; +float GetSkillCvar( char *pName ); + +extern DLL_GLOBAL int g_iSkillLevel; + +#define SKILL_EASY 1 +#define SKILL_MEDIUM 2 +#define SKILL_HARD 3 diff --git a/hlsdk/dmc/dlls/sound.cpp b/hlsdk/dmc/dlls/sound.cpp new file mode 100644 index 00000000..d3fecb7d --- /dev/null +++ b/hlsdk/dmc/dlls/sound.cpp @@ -0,0 +1,1967 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// sound.cpp +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "player.h" +#include "gamerules.h" + + +static char *memfgets( byte *pMemFile, int fileSize, int &filePos, char *pBuffer, int bufferSize ); + + +// ==================== GENERIC AMBIENT SOUND ====================================== + +// runtime pitch shift and volume fadein/out structure + +// NOTE: IF YOU CHANGE THIS STRUCT YOU MUST CHANGE THE SAVE/RESTORE VERSION NUMBER +// SEE BELOW (in the typedescription for the class) +typedef struct dynpitchvol +{ + // NOTE: do not change the order of these parameters + // NOTE: unless you also change order of rgdpvpreset array elements! + int preset; + + int pitchrun; // pitch shift % when sound is running 0 - 255 + int pitchstart; // pitch shift % when sound stops or starts 0 - 255 + int spinup; // spinup time 0 - 100 + int spindown; // spindown time 0 - 100 + + int volrun; // volume change % when sound is running 0 - 10 + int volstart; // volume change % when sound stops or starts 0 - 10 + int fadein; // volume fade in time 0 - 100 + int fadeout; // volume fade out time 0 - 100 + + // Low Frequency Oscillator + int lfotype; // 0) off 1) square 2) triangle 3) random + int lforate; // 0 - 1000, how fast lfo osciallates + + int lfomodpitch; // 0-100 mod of current pitch. 0 is off. + int lfomodvol; // 0-100 mod of current volume. 0 is off. + + int cspinup; // each trigger hit increments counter and spinup pitch + + + int cspincount; + + int pitch; + int spinupsav; + int spindownsav; + int pitchfrac; + + int vol; + int fadeinsav; + int fadeoutsav; + int volfrac; + + int lfofrac; + int lfomult; + + +} dynpitchvol_t; + +#define CDPVPRESETMAX 27 + +// presets for runtime pitch and vol modulation of ambient sounds + +dynpitchvol_t rgdpvpreset[CDPVPRESETMAX] = +{ +// pitch pstart spinup spindwn volrun volstrt fadein fadeout lfotype lforate modptch modvol cspnup +{1, 255, 75, 95, 95, 10, 1, 50, 95, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{2, 255, 85, 70, 88, 10, 1, 20, 88, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{3, 255, 100, 50, 75, 10, 1, 10, 75, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{4, 100, 100, 0, 0, 10, 1, 90, 90, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{5, 100, 100, 0, 0, 10, 1, 80, 80, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{6, 100, 100, 0, 0, 10, 1, 50, 70, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{7, 100, 100, 0, 0, 5, 1, 40, 50, 1, 50, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, +{8, 100, 100, 0, 0, 5, 1, 40, 50, 1, 150, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, +{9, 100, 100, 0, 0, 5, 1, 40, 50, 1, 750, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, +{10,128, 100, 50, 75, 10, 1, 30, 40, 2, 8, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{11,128, 100, 50, 75, 10, 1, 30, 40, 2, 25, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{12,128, 100, 50, 75, 10, 1, 30, 40, 2, 70, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{13,50, 50, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{14,70, 70, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{15,90, 90, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{16,120, 120, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{17,180, 180, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{18,255, 255, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{19,200, 75, 90, 90, 10, 1, 50, 90, 2, 100, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{20,255, 75, 97, 90, 10, 1, 50, 90, 1, 40, 50, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{21,100, 100, 0, 0, 10, 1, 30, 50, 3, 15, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{22,160, 160, 0, 0, 10, 1, 50, 50, 3, 500, 25, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{23,255, 75, 88, 0, 10, 1, 40, 0, 0, 0, 0, 0, 5, 0,0,0,0,0,0,0,0,0,0}, +{24,200, 20, 95, 70, 10, 1, 70, 70, 3, 20, 50, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{25,180, 100, 50, 60, 10, 1, 40, 60, 2, 90, 100, 100, 0, 0,0,0,0,0,0,0,0,0,0}, +{26,60, 60, 0, 0, 10, 1, 40, 70, 3, 80, 20, 50, 0, 0,0,0,0,0,0,0,0,0,0}, +{27,128, 90, 10, 10, 10, 1, 20, 40, 1, 5, 10, 20, 0, 0,0,0,0,0,0,0,0,0,0} +}; + +class CAmbientGeneric : public CBaseEntity +{ +public: + void KeyValue( KeyValueData* pkvd); + void Spawn( void ); + void Precache( void ); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT RampThink( void ); + void InitModulationParms(void); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + + float m_flAttenuation; // attenuation value + dynpitchvol_t m_dpv; + + BOOL m_fActive; // only TRUE when the entity is playing a looping sound + BOOL m_fLooping; // TRUE when the sound played will loop +}; + +LINK_ENTITY_TO_CLASS( ambient_generic, CAmbientGeneric ); +TYPEDESCRIPTION CAmbientGeneric::m_SaveData[] = +{ + DEFINE_FIELD( CAmbientGeneric, m_flAttenuation, FIELD_FLOAT ), + DEFINE_FIELD( CAmbientGeneric, m_fActive, FIELD_BOOLEAN ), + DEFINE_FIELD( CAmbientGeneric, m_fLooping, FIELD_BOOLEAN ), + + // HACKHACK - This is not really in the spirit of the save/restore design, but save this + // out as a binary data block. If the dynpitchvol_t is changed, old saved games will NOT + // load these correctly, so bump the save/restore version if you change the size of the struct + // The right way to do this is to split the input parms (read in keyvalue) into members and re-init this + // struct in Precache(), but it's unlikely that the struct will change, so it's not worth the time right now. + DEFINE_ARRAY( CAmbientGeneric, m_dpv, FIELD_CHARACTER, sizeof(dynpitchvol_t) ), +}; + +IMPLEMENT_SAVERESTORE( CAmbientGeneric, CBaseEntity ); + +// +// ambient_generic - general-purpose user-defined static sound +// +void CAmbientGeneric :: Spawn( void ) +{ +/* + -1 : "Default" + 0 : "Everywhere" + 200 : "Small Radius" + 125 : "Medium Radius" + 80 : "Large Radius" +*/ + + if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_EVERYWHERE) ) + { + m_flAttenuation = ATTN_NONE; + } + else if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_SMALLRADIUS) ) + { + m_flAttenuation = ATTN_IDLE; + } + else if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_MEDIUMRADIUS) ) + { + m_flAttenuation = ATTN_STATIC; + } + else if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_LARGERADIUS) ) + { + m_flAttenuation = ATTN_NORM; + } + else + {// if the designer didn't set a sound attenuation, default to one. + m_flAttenuation = ATTN_STATIC; + } + + char* szSoundFile = (char*) STRING(pev->message); + + if ( FStringNull( pev->message ) || strlen( szSoundFile ) < 1 ) + { + ALERT( at_error, "EMPTY AMBIENT AT: %f, %f, %f\n", pev->origin.x, pev->origin.y, pev->origin.z ); + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CBaseEntity::SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + // Set up think function for dynamic modification + // of ambient sound's pitch or volume. Don't + // start thinking yet. + + SetThink(&CAmbientGeneric::RampThink); + pev->nextthink = 0; + + // allow on/off switching via 'use' function. + + SetUse ( &CAmbientGeneric::ToggleUse ); + + m_fActive = FALSE; + + if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_NOT_LOOPING ) ) + m_fLooping = FALSE; + else + m_fLooping = TRUE; + Precache( ); +} + + +void CAmbientGeneric :: Precache( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + + if ( !FStringNull( pev->message ) && strlen( szSoundFile ) > 1 ) + { + if (*szSoundFile != '!') + PRECACHE_SOUND(szSoundFile); + } + // init all dynamic modulation parms + InitModulationParms(); + + if ( !FBitSet (pev->spawnflags, AMBIENT_SOUND_START_SILENT ) ) + { + // start the sound ASAP + if (m_fLooping) + m_fActive = TRUE; + } + if ( m_fActive ) + { + UTIL_EmitAmbientSound ( ENT(pev), pev->origin, szSoundFile, + (m_dpv.vol * 0.01), m_flAttenuation, SND_SPAWNING, m_dpv.pitch); + + pev->nextthink = gpGlobals->time + 0.1; + } +} + +// RampThink - Think at 5hz if we are dynamically modifying +// pitch or volume of the playing sound. This function will +// ramp pitch and/or volume up or down, modify pitch/volume +// with lfo if active. + +void CAmbientGeneric :: RampThink( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + int pitch = m_dpv.pitch; + int vol = m_dpv.vol; + int flags = 0; + int fChanged = 0; // FALSE if pitch and vol remain unchanged this round + int prev; + + if (!m_dpv.spinup && !m_dpv.spindown && !m_dpv.fadein && !m_dpv.fadeout && !m_dpv.lfotype) + return; // no ramps or lfo, stop thinking + + // ============== + // pitch envelope + // ============== + if (m_dpv.spinup || m_dpv.spindown) + { + prev = m_dpv.pitchfrac >> 8; + + if (m_dpv.spinup > 0) + m_dpv.pitchfrac += m_dpv.spinup; + else if (m_dpv.spindown > 0) + m_dpv.pitchfrac -= m_dpv.spindown; + + pitch = m_dpv.pitchfrac >> 8; + + if (pitch > m_dpv.pitchrun) + { + pitch = m_dpv.pitchrun; + m_dpv.spinup = 0; // done with ramp up + } + + if (pitch < m_dpv.pitchstart) + { + pitch = m_dpv.pitchstart; + m_dpv.spindown = 0; // done with ramp down + + // shut sound off + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + + // return without setting nextthink + return; + } + + if (pitch > 255) pitch = 255; + if (pitch < 1) pitch = 1; + + m_dpv.pitch = pitch; + + fChanged |= (prev != pitch); + flags |= SND_CHANGE_PITCH; + } + + // ================== + // amplitude envelope + // ================== + if (m_dpv.fadein || m_dpv.fadeout) + { + prev = m_dpv.volfrac >> 8; + + if (m_dpv.fadein > 0) + m_dpv.volfrac += m_dpv.fadein; + else if (m_dpv.fadeout > 0) + m_dpv.volfrac -= m_dpv.fadeout; + + vol = m_dpv.volfrac >> 8; + + if (vol > m_dpv.volrun) + { + vol = m_dpv.volrun; + m_dpv.fadein = 0; // done with ramp up + } + + if (vol < m_dpv.volstart) + { + vol = m_dpv.volstart; + m_dpv.fadeout = 0; // done with ramp down + + // shut sound off + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + + // return without setting nextthink + return; + } + + if (vol > 100) vol = 100; + if (vol < 1) vol = 1; + + m_dpv.vol = vol; + + fChanged |= (prev != vol); + flags |= SND_CHANGE_VOL; + } + + // =================== + // pitch/amplitude LFO + // =================== + if (m_dpv.lfotype) + { + int pos; + + if (m_dpv.lfofrac > 0x6fffffff) + m_dpv.lfofrac = 0; + + // update lfo, lfofrac/255 makes a triangle wave 0-255 + m_dpv.lfofrac += m_dpv.lforate; + pos = m_dpv.lfofrac >> 8; + + if (m_dpv.lfofrac < 0) + { + m_dpv.lfofrac = 0; + m_dpv.lforate = abs(m_dpv.lforate); + pos = 0; + } + else if (pos > 255) + { + pos = 255; + m_dpv.lfofrac = (255 << 8); + m_dpv.lforate = -abs(m_dpv.lforate); + } + + switch(m_dpv.lfotype) + { + case LFO_SQUARE: + if (pos < 128) + m_dpv.lfomult = 255; + else + m_dpv.lfomult = 0; + + break; + case LFO_RANDOM: + if (pos == 255) + m_dpv.lfomult = RANDOM_LONG(0, 255); + break; + case LFO_TRIANGLE: + default: + m_dpv.lfomult = pos; + break; + } + + if (m_dpv.lfomodpitch) + { + prev = pitch; + + // pitch 0-255 + pitch += ((m_dpv.lfomult - 128) * m_dpv.lfomodpitch) / 100; + + if (pitch > 255) pitch = 255; + if (pitch < 1) pitch = 1; + + + fChanged |= (prev != pitch); + flags |= SND_CHANGE_PITCH; + } + + if (m_dpv.lfomodvol) + { + // vol 0-100 + prev = vol; + + vol += ((m_dpv.lfomult - 128) * m_dpv.lfomodvol) / 100; + + if (vol > 100) vol = 100; + if (vol < 0) vol = 0; + + fChanged |= (prev != vol); + flags |= SND_CHANGE_VOL; + } + + } + + // Send update to playing sound only if we actually changed + // pitch or volume in this routine. + + if (flags && fChanged) + { + if (pitch == PITCH_NORM) + pitch = PITCH_NORM + 1; // don't send 'no pitch' ! + + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + (vol * 0.01), m_flAttenuation, flags, pitch); + } + + // update ramps at 5hz + pev->nextthink = gpGlobals->time + 0.2; + return; +} + +// Init all ramp params in preparation to +// play a new sound + +void CAmbientGeneric :: InitModulationParms(void) +{ + int pitchinc; + + m_dpv.volrun = static_cast(pev->health * 10); // 0 - 100 + if (m_dpv.volrun > 100) m_dpv.volrun = 100; + if (m_dpv.volrun < 0) m_dpv.volrun = 0; + + // get presets + if (m_dpv.preset != 0 && m_dpv.preset <= CDPVPRESETMAX) + { + // load preset values + m_dpv = rgdpvpreset[m_dpv.preset - 1]; + + // fixup preset values, just like + // fixups in KeyValue routine. + if (m_dpv.spindown > 0) + m_dpv.spindown = (101 - m_dpv.spindown) * 64; + if (m_dpv.spinup > 0) + m_dpv.spinup = (101 - m_dpv.spinup) * 64; + + m_dpv.volstart *= 10; + m_dpv.volrun *= 10; + + if (m_dpv.fadein > 0) + m_dpv.fadein = (101 - m_dpv.fadein) * 64; + if (m_dpv.fadeout > 0) + m_dpv.fadeout = (101 - m_dpv.fadeout) * 64; + + m_dpv.lforate *= 256; + + m_dpv.fadeinsav = m_dpv.fadein; + m_dpv.fadeoutsav = m_dpv.fadeout; + m_dpv.spinupsav = m_dpv.spinup; + m_dpv.spindownsav = m_dpv.spindown; + } + + m_dpv.fadein = m_dpv.fadeinsav; + m_dpv.fadeout = 0; + + if (m_dpv.fadein) + m_dpv.vol = m_dpv.volstart; + else + m_dpv.vol = m_dpv.volrun; + + m_dpv.spinup = m_dpv.spinupsav; + m_dpv.spindown = 0; + + if (m_dpv.spinup) + m_dpv.pitch = m_dpv.pitchstart; + else + m_dpv.pitch = m_dpv.pitchrun; + + if (m_dpv.pitch == 0) + m_dpv.pitch = PITCH_NORM; + + m_dpv.pitchfrac = m_dpv.pitch << 8; + m_dpv.volfrac = m_dpv.vol << 8; + + m_dpv.lfofrac = 0; + m_dpv.lforate = abs(m_dpv.lforate); + + m_dpv.cspincount = 1; + + if (m_dpv.cspinup) + { + pitchinc = (255 - m_dpv.pitchstart) / m_dpv.cspinup; + + m_dpv.pitchrun = m_dpv.pitchstart + pitchinc; + if (m_dpv.pitchrun > 255) m_dpv.pitchrun = 255; + } + + if ((m_dpv.spinupsav || m_dpv.spindownsav || (m_dpv.lfotype && m_dpv.lfomodpitch)) + && (m_dpv.pitch == PITCH_NORM)) + m_dpv.pitch = PITCH_NORM + 1; // must never send 'no pitch' as first pitch + // if we intend to pitch shift later! +} + +// +// ToggleUse - turns an ambient sound on or off. If the +// ambient is a looping sound, mark sound as active (m_fActive) +// if it's playing, innactive if not. If the sound is not +// a looping sound, never mark it as active. +// +void CAmbientGeneric :: ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + char* szSoundFile = (char*) STRING(pev->message); + float fraction; + + if ( useType != USE_TOGGLE ) + { + if ( (m_fActive && useType == USE_ON) || (!m_fActive && useType == USE_OFF) ) + return; + } + // Directly change pitch if arg passed. Only works if sound is already playing. + + if (useType == USE_SET && m_fActive) // Momentary buttons will pass down a float in here + { + + fraction = value; + + if ( fraction > 1.0 ) + fraction = 1.0; + if (fraction < 0.0) + fraction = 0.01; + + m_dpv.pitch = static_cast(fraction * 255); + + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_CHANGE_PITCH, m_dpv.pitch); + + return; + } + + // Toggle + + // m_fActive is TRUE only if a looping sound is playing. + + if ( m_fActive ) + {// turn sound off + + if (m_dpv.cspinup) + { + // Don't actually shut off. Each toggle causes + // incremental spinup to max pitch + + if (m_dpv.cspincount <= m_dpv.cspinup) + { + int pitchinc; + + // start a new spinup + m_dpv.cspincount++; + + pitchinc = (255 - m_dpv.pitchstart) / m_dpv.cspinup; + + m_dpv.spinup = m_dpv.spinupsav; + m_dpv.spindown = 0; + + m_dpv.pitchrun = m_dpv.pitchstart + pitchinc * m_dpv.cspincount; + if (m_dpv.pitchrun > 255) m_dpv.pitchrun = 255; + + pev->nextthink = gpGlobals->time + 0.1; + } + + } + else + { + m_fActive = FALSE; + + // HACKHACK - this makes the code in Precache() work properly after a save/restore + pev->spawnflags |= AMBIENT_SOUND_START_SILENT; + + if (m_dpv.spindownsav || m_dpv.fadeoutsav) + { + // spin it down (or fade it) before shutoff if spindown is set + m_dpv.spindown = m_dpv.spindownsav; + m_dpv.spinup = 0; + + m_dpv.fadeout = m_dpv.fadeoutsav; + m_dpv.fadein = 0; + pev->nextthink = gpGlobals->time + 0.1; + } + else + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + } + } + else + {// turn sound on + + // only toggle if this is a looping sound. If not looping, each + // trigger will cause the sound to play. If the sound is still + // playing from a previous trigger press, it will be shut off + // and then restarted. + + if (m_fLooping) + m_fActive = TRUE; + else + // shut sound off now - may be interrupting a long non-looping sound + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + + // init all ramp params for startup + + InitModulationParms(); + + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + (m_dpv.vol * 0.01), m_flAttenuation, 0, m_dpv.pitch); + + pev->nextthink = gpGlobals->time + 0.1; + + } +} +// KeyValue - load keyvalue pairs into member data of the +// ambient generic. NOTE: called BEFORE spawn! + +void CAmbientGeneric :: KeyValue( KeyValueData *pkvd ) +{ + // NOTE: changing any of the modifiers in this code + // NOTE: also requires changing InitModulationParms code. + + // preset + if (FStrEq(pkvd->szKeyName, "preset")) + { + m_dpv.preset = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + + // pitchrun + else if (FStrEq(pkvd->szKeyName, "pitch")) + { + m_dpv.pitchrun = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + if (m_dpv.pitchrun > 255) m_dpv.pitchrun = 255; + if (m_dpv.pitchrun < 0) m_dpv.pitchrun = 0; + } + + // pitchstart + else if (FStrEq(pkvd->szKeyName, "pitchstart")) + { + m_dpv.pitchstart = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + if (m_dpv.pitchstart > 255) m_dpv.pitchstart = 255; + if (m_dpv.pitchstart < 0) m_dpv.pitchstart = 0; + } + + // spinup + else if (FStrEq(pkvd->szKeyName, "spinup")) + { + m_dpv.spinup = atoi(pkvd->szValue); + + if (m_dpv.spinup > 100) m_dpv.spinup = 100; + if (m_dpv.spinup < 0) m_dpv.spinup = 0; + + if (m_dpv.spinup > 0) + m_dpv.spinup = (101 - m_dpv.spinup) * 64; + m_dpv.spinupsav = m_dpv.spinup; + pkvd->fHandled = TRUE; + } + + // spindown + else if (FStrEq(pkvd->szKeyName, "spindown")) + { + m_dpv.spindown = atoi(pkvd->szValue); + + if (m_dpv.spindown > 100) m_dpv.spindown = 100; + if (m_dpv.spindown < 0) m_dpv.spindown = 0; + + if (m_dpv.spindown > 0) + m_dpv.spindown = (101 - m_dpv.spindown) * 64; + m_dpv.spindownsav = m_dpv.spindown; + pkvd->fHandled = TRUE; + } + + // volstart + else if (FStrEq(pkvd->szKeyName, "volstart")) + { + m_dpv.volstart = atoi(pkvd->szValue); + + if (m_dpv.volstart > 10) m_dpv.volstart = 10; + if (m_dpv.volstart < 0) m_dpv.volstart = 0; + + m_dpv.volstart *= 10; // 0 - 100 + + pkvd->fHandled = TRUE; + } + + // fadein + else if (FStrEq(pkvd->szKeyName, "fadein")) + { + m_dpv.fadein = atoi(pkvd->szValue); + + if (m_dpv.fadein > 100) m_dpv.fadein = 100; + if (m_dpv.fadein < 0) m_dpv.fadein = 0; + + if (m_dpv.fadein > 0) + m_dpv.fadein = (101 - m_dpv.fadein) * 64; + m_dpv.fadeinsav = m_dpv.fadein; + pkvd->fHandled = TRUE; + } + + // fadeout + else if (FStrEq(pkvd->szKeyName, "fadeout")) + { + m_dpv.fadeout = atoi(pkvd->szValue); + + if (m_dpv.fadeout > 100) m_dpv.fadeout = 100; + if (m_dpv.fadeout < 0) m_dpv.fadeout = 0; + + if (m_dpv.fadeout > 0) + m_dpv.fadeout = (101 - m_dpv.fadeout) * 64; + m_dpv.fadeoutsav = m_dpv.fadeout; + pkvd->fHandled = TRUE; + } + + // lfotype + else if (FStrEq(pkvd->szKeyName, "lfotype")) + { + m_dpv.lfotype = atoi(pkvd->szValue); + if (m_dpv.lfotype > 4) m_dpv.lfotype = LFO_TRIANGLE; + pkvd->fHandled = TRUE; + } + + // lforate + else if (FStrEq(pkvd->szKeyName, "lforate")) + { + m_dpv.lforate = atoi(pkvd->szValue); + + if (m_dpv.lforate > 1000) m_dpv.lforate = 1000; + if (m_dpv.lforate < 0) m_dpv.lforate = 0; + + m_dpv.lforate *= 256; + + pkvd->fHandled = TRUE; + } + // lfomodpitch + else if (FStrEq(pkvd->szKeyName, "lfomodpitch")) + { + m_dpv.lfomodpitch = atoi(pkvd->szValue); + if (m_dpv.lfomodpitch > 100) m_dpv.lfomodpitch = 100; + if (m_dpv.lfomodpitch < 0) m_dpv.lfomodpitch = 0; + + + pkvd->fHandled = TRUE; + } + + // lfomodvol + else if (FStrEq(pkvd->szKeyName, "lfomodvol")) + { + m_dpv.lfomodvol = atoi(pkvd->szValue); + if (m_dpv.lfomodvol > 100) m_dpv.lfomodvol = 100; + if (m_dpv.lfomodvol < 0) m_dpv.lfomodvol = 0; + + pkvd->fHandled = TRUE; + } + + // cspinup + else if (FStrEq(pkvd->szKeyName, "cspinup")) + { + m_dpv.cspinup = atoi(pkvd->szValue); + if (m_dpv.cspinup > 100) m_dpv.cspinup = 100; + if (m_dpv.cspinup < 0) m_dpv.cspinup = 0; + + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +// =================== ROOM SOUND FX ========================================== + +class CEnvSound : public CPointEntity +{ +public: + void KeyValue( KeyValueData* pkvd); + void Spawn( void ); + + void Think( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + float m_flRadius; + float m_flRoomtype; +}; + +LINK_ENTITY_TO_CLASS( env_sound, CEnvSound ); +TYPEDESCRIPTION CEnvSound::m_SaveData[] = +{ + DEFINE_FIELD( CEnvSound, m_flRadius, FIELD_FLOAT ), + DEFINE_FIELD( CEnvSound, m_flRoomtype, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CEnvSound, CBaseEntity ); + + +void CEnvSound :: KeyValue( KeyValueData *pkvd ) +{ + + if (FStrEq(pkvd->szKeyName, "radius")) + { + m_flRadius = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + if (FStrEq(pkvd->szKeyName, "roomtype")) + { + m_flRoomtype = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } +} + +// returns TRUE if the given sound entity (pev) is in range +// and can see the given player entity (pevTarget) + +BOOL FEnvSoundInRange(entvars_t *pev, entvars_t *pevTarget, float *pflRange) +{ + CEnvSound *pSound = GetClassPtr( (CEnvSound *)pev ); + Vector vecSpot1 = pev->origin + pev->view_ofs; + Vector vecSpot2 = pevTarget->origin + pevTarget->view_ofs; + Vector vecRange; + float flRange; + TraceResult tr; + + UTIL_TraceLine(vecSpot1, vecSpot2, ignore_monsters, ENT(pev), &tr); + + // check if line of sight crosses water boundary, or is blocked + + if ((tr.fInOpen && tr.fInWater) || tr.flFraction != 1) + return FALSE; + + // calc range from sound entity to player + + vecRange = tr.vecEndPos - vecSpot1; + flRange = vecRange.Length(); + + if (pSound->m_flRadius < flRange) + return FALSE; + + if (pflRange) + *pflRange = flRange; + + return TRUE; +} + +// +// A client that is visible and in range of a sound entity will +// have its room_type set by that sound entity. If two or more +// sound entities are contending for a client, then the nearest +// sound entity to the client will set the client's room_type. +// A client's room_type will remain set to its prior value until +// a new in-range, visible sound entity resets a new room_type. +// + +// CONSIDER: if player in water state, autoset roomtype to 14,15 or 16. + +void CEnvSound :: Think( void ) +{ + // get pointer to client if visible; FIND_CLIENT_IN_PVS will + // cycle through visible clients on consecutive calls. + + edict_t *pentPlayer = FIND_CLIENT_IN_PVS(edict()); + CBasePlayer *pPlayer = NULL; + + if (FNullEnt(pentPlayer)) + goto env_sound_Think_slow; // no player in pvs of sound entity, slow it down + + pPlayer = GetClassPtr( (CBasePlayer *)VARS(pentPlayer)); + float flRange; + + // check to see if this is the sound entity that is + // currently affecting this player + + if(!FNullEnt(pPlayer->m_pentSndLast) && (pPlayer->m_pentSndLast == ENT(pev))) { + + // this is the entity currently affecting player, check + // for validity + + if (pPlayer->m_flSndRoomtype != 0 && pPlayer->m_flSndRange != 0) { + + // we're looking at a valid sound entity affecting + // player, make sure it's still valid, update range + + if (FEnvSoundInRange(pev, VARS(pentPlayer), &flRange)) { + pPlayer->m_flSndRange = flRange; + goto env_sound_Think_fast; + } else { + + // current sound entity affecting player is no longer valid, + // flag this state by clearing room_type and range. + // NOTE: we do not actually change the player's room_type + // NOTE: until we have a new valid room_type to change it to. + + pPlayer->m_flSndRange = 0; + pPlayer->m_flSndRoomtype = 0; + goto env_sound_Think_slow; + } + } else { + // entity is affecting player but is out of range, + // wait passively for another entity to usurp it... + goto env_sound_Think_slow; + } + } + + // if we got this far, we're looking at an entity that is contending + // for current player sound. the closest entity to player wins. + + if (FEnvSoundInRange(pev, VARS(pentPlayer), &flRange)) + { + if (flRange < pPlayer->m_flSndRange || pPlayer->m_flSndRange == 0) + { + // new entity is closer to player, so it wins. + pPlayer->m_pentSndLast = ENT(pev); + pPlayer->m_flSndRoomtype = m_flRoomtype; + pPlayer->m_flSndRange = flRange; + + // send room_type command to player's server. + // this should be a rare event - once per change of room_type + // only! + + //CLIENT_COMMAND(pentPlayer, "room_type %f", m_flRoomtype); + + MESSAGE_BEGIN( MSG_ONE, SVC_ROOMTYPE, NULL, pentPlayer ); // use the magic #1 for "one client" + WRITE_SHORT( (short)m_flRoomtype ); // sequence number + MESSAGE_END(); + + // crank up nextthink rate for new active sound entity + // by falling through to think_fast... + } + // player is not closer to the contending sound entity, + // just fall through to think_fast. this effectively + // cranks up the think_rate of entities near the player. + } + + // player is in pvs of sound entity, but either not visible or + // not in range. do nothing, fall through to think_fast... + +env_sound_Think_fast: + pev->nextthink = gpGlobals->time + 0.25; + return; + +env_sound_Think_slow: + pev->nextthink = gpGlobals->time + 0.75; + return; +} + +// +// env_sound - spawn a sound entity that will set player roomtype +// when player moves in range and sight. +// +// +void CEnvSound :: Spawn( ) +{ + // spread think times + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(0.0, 0.5); +} + +// ==================== SENTENCE GROUPS, UTILITY FUNCTIONS ====================================== + +#define CSENTENCE_LRU_MAX 32 // max number of elements per sentence group + +// group of related sentences + +typedef struct sentenceg +{ + char szgroupname[CBSENTENCENAME_MAX]; + int count; + unsigned char rgblru[CSENTENCE_LRU_MAX]; + +} SENTENCEG; + +#define CSENTENCEG_MAX 200 // max number of sentence groups +// globals + +SENTENCEG rgsentenceg[CSENTENCEG_MAX]; +int fSentencesInit = FALSE; + +char gszallsentencenames[CVOXFILESENTENCEMAX][CBSENTENCENAME_MAX]; +int gcallsentences = 0; + +// randomize list of sentence name indices + +void USENTENCEG_InitLRU(unsigned char *plru, int count) +{ + int i, j, k; + unsigned char temp; + + if (!fSentencesInit) + return; + + if (count > CSENTENCE_LRU_MAX) + count = CSENTENCE_LRU_MAX; + + for (i = 0; i < count; i++) + plru[i] = (unsigned char) i; + + // randomize array + for (i = 0; i < (count * 4); i++) + { + j = RANDOM_LONG(0,count-1); + k = RANDOM_LONG(0,count-1); + temp = plru[j]; + plru[j] = plru[k]; + plru[k] = temp; + } +} + +// ignore lru. pick next sentence from sentence group. Go in order until we hit the last sentence, +// then repeat list if freset is true. If freset is false, then repeat last sentence. +// ipick is passed in as the requested sentence ordinal. +// ipick 'next' is returned. +// return of -1 indicates an error. + +int USENTENCEG_PickSequential(int isentenceg, char *szfound, int ipick, int freset) +{ + char *szgroupname; + unsigned char count; + char sznum[8]; + + if (!fSentencesInit) + return -1; + + if (isentenceg < 0) + return -1; + + szgroupname = rgsentenceg[isentenceg].szgroupname; + count = rgsentenceg[isentenceg].count; + + if (count == 0) + return -1; + + if (ipick >= count) + ipick = count-1; + + strcpy(szfound, "!"); + strcat(szfound, szgroupname); + itoa(ipick, sznum, 10); + strcat(szfound, sznum); + + if (ipick >= count) + { + if (freset) + // reset at end of list + return 0; + else + return count; + } + + return ipick + 1; +} + + + +// pick a random sentence from rootname0 to rootnameX. +// picks from the rgsentenceg[isentenceg] least +// recently used, modifies lru array. returns the sentencename. +// note, lru must be seeded with 0-n randomized sentence numbers, with the +// rest of the lru filled with -1. The first integer in the lru is +// actually the size of the list. Returns ipick, the ordinal +// of the picked sentence within the group. + +int USENTENCEG_Pick(int isentenceg, char *szfound) +{ + char *szgroupname; + unsigned char *plru; + unsigned char i; + unsigned char count; + char sznum[8]; + unsigned char ipick; + int ffound = FALSE; + + if (!fSentencesInit) + return -1; + + if (isentenceg < 0) + return -1; + + szgroupname = rgsentenceg[isentenceg].szgroupname; + count = rgsentenceg[isentenceg].count; + plru = rgsentenceg[isentenceg].rgblru; + + while (!ffound) + { + for (i = 0; i < count; i++) + if (plru[i] != 0xFF) + { + ipick = plru[i]; + plru[i] = 0xFF; + ffound = TRUE; + break; + } + + if (!ffound) + USENTENCEG_InitLRU(plru, count); + else + { + strcpy(szfound, "!"); + strcat(szfound, szgroupname); + itoa(ipick, sznum, 10); + strcat(szfound, sznum); + return ipick; + } + } + return -1; +} + +// ===================== SENTENCE GROUPS, MAIN ROUTINES ======================== + +// Given sentence group rootname (name without number suffix), +// get sentence group index (isentenceg). Returns -1 if no such name. + +int SENTENCEG_GetIndex(const char *szgroupname) +{ + int i; + + if (!fSentencesInit || !szgroupname) + return -1; + + // search rgsentenceg for match on szgroupname + + i = 0; + while (rgsentenceg[i].count) + { + if (!strcmp(szgroupname, rgsentenceg[i].szgroupname)) + return i; + i++; + } + + return -1; +} + +// given sentence group index, play random sentence for given entity. +// returns ipick - which sentence was picked to +// play from the group. Ipick is only needed if you plan on stopping +// the sound before playback is done (see SENTENCEG_Stop). + +int SENTENCEG_PlayRndI(edict_t *entity, int isentenceg, + float volume, float attenuation, int flags, int pitch) +{ + char name[64]; + int ipick; + + if (!fSentencesInit) + return -1; + + name[0] = 0; + + ipick = USENTENCEG_Pick(isentenceg, name); + if (ipick > 0 && name) + EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch); + return ipick; +} + +// same as above, but takes sentence group name instead of index + +int SENTENCEG_PlayRndSz(edict_t *entity, const char *szgroupname, + float volume, float attenuation, int flags, int pitch) +{ + char name[64]; + int ipick; + int isentenceg; + + if (!fSentencesInit) + return -1; + + name[0] = 0; + + isentenceg = SENTENCEG_GetIndex(szgroupname); + if (isentenceg < 0) + { + ALERT( at_console, "No such sentence group %s\n", szgroupname ); + return -1; + } + + ipick = USENTENCEG_Pick(isentenceg, name); + if (ipick >= 0 && name[0]) + EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch); + + return ipick; +} + +// play sentences in sequential order from sentence group. Reset after last sentence. + +int SENTENCEG_PlaySequentialSz(edict_t *entity, const char *szgroupname, + float volume, float attenuation, int flags, int pitch, int ipick, int freset) +{ + char name[64]; + int ipicknext; + int isentenceg; + + if (!fSentencesInit) + return -1; + + name[0] = 0; + + isentenceg = SENTENCEG_GetIndex(szgroupname); + if (isentenceg < 0) + return -1; + + ipicknext = USENTENCEG_PickSequential(isentenceg, name, ipick, freset); + if (ipicknext >= 0 && name[0]) + EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch); + return ipicknext; +} + + +// for this entity, for the given sentence within the sentence group, stop +// the sentence. + +void SENTENCEG_Stop(edict_t *entity, int isentenceg, int ipick) +{ + char buffer[64]; + char sznum[8]; + + if (!fSentencesInit) + return; + + if (isentenceg < 0 || ipick < 0) + return; + + strcpy(buffer, "!"); + strcat(buffer, rgsentenceg[isentenceg].szgroupname); + itoa(ipick, sznum, 10); + strcat(buffer, sznum); + + STOP_SOUND(entity, CHAN_VOICE, buffer); +} + +// open sentences.txt, scan for groups, build rgsentenceg +// Should be called from world spawn, only works on the +// first call and is ignored subsequently. + +void SENTENCEG_Init() +{ + char buffer[512]; + char szgroup[64]; + int i, j; + int isentencegs; + + if (fSentencesInit) + return; + + memset(gszallsentencenames, 0, CVOXFILESENTENCEMAX * CBSENTENCENAME_MAX); + gcallsentences = 0; + + memset(rgsentenceg, 0, CSENTENCEG_MAX * sizeof(SENTENCEG)); + memset(buffer, 0, 512); + memset(szgroup, 0, 64); + isentencegs = -1; + + + int filePos = 0, fileSize; + byte *pMemFile = g_engfuncs.pfnLoadFileForMe( "sound/sentences.txt", &fileSize ); + if ( !pMemFile ) + return; + + // for each line in the file... + while ( memfgets(pMemFile, fileSize, filePos, buffer, 511) != NULL ) + { + // skip whitespace + i = 0; + while(buffer[i] && buffer[i] == ' ') + i++; + + if (!buffer[i]) + continue; + + if (buffer[i] == '/' || !isalpha(buffer[i])) + continue; + + // get sentence name + j = i; + while (buffer[j] && buffer[j] != ' ') + j++; + + if (!buffer[j]) + continue; + + if (gcallsentences > CVOXFILESENTENCEMAX) + { + ALERT (at_error, "Too many sentences in sentences.txt!\n"); + break; + } + + // null-terminate name and save in sentences array + buffer[j] = 0; + const char *pString = buffer + i; + + if ( strlen( pString ) >= CBSENTENCENAME_MAX ) + ALERT( at_warning, "Sentence %s longer than %d letters\n", pString, CBSENTENCENAME_MAX-1 ); + + strcpy( gszallsentencenames[gcallsentences++], pString ); + + j--; + if (j <= i) + continue; + if (!isdigit(buffer[j])) + continue; + + // cut out suffix numbers + while (j > i && isdigit(buffer[j])) + j--; + + if (j <= i) + continue; + + buffer[j+1] = 0; + + // if new name doesn't match previous group name, + // make a new group. + + if (strcmp(szgroup, &(buffer[i]))) + { + // name doesn't match with prev name, + // copy name into group, init count to 1 + isentencegs++; + if (isentencegs >= CSENTENCEG_MAX) + { + ALERT (at_error, "Too many sentence groups in sentences.txt!\n"); + break; + } + + strcpy(rgsentenceg[isentencegs].szgroupname, &(buffer[i])); + rgsentenceg[isentencegs].count = 1; + + strcpy(szgroup, &(buffer[i])); + + continue; + } + else + { + //name matches with previous, increment group count + if (isentencegs >= 0) + rgsentenceg[isentencegs].count++; + } + } + + g_engfuncs.pfnFreeFile( pMemFile ); + + fSentencesInit = TRUE; + + // init lru lists + + i = 0; + + while (rgsentenceg[i].count && i < CSENTENCEG_MAX) + { + USENTENCEG_InitLRU(&(rgsentenceg[i].rgblru[0]), rgsentenceg[i].count); + i++; + } + +} + +// convert sentence (sample) name to !sentencenum, return !sentencenum + +int SENTENCEG_Lookup(const char *sample, char *sentencenum) +{ + char sznum[8]; + int i; + + // this is a sentence name; lookup sentence number + // and give to engine as string. + for (i = 0; i < gcallsentences; i++) + if (!stricmp(gszallsentencenames[i], sample+1)) + { + if (sentencenum) + { + strcpy(sentencenum, "!"); + itoa(i, sznum, 10); + strcat(sentencenum, sznum); + } + return i; + } + // sentence name not found! + return -1; +} + +void EMIT_SOUND_DYN(edict_t *entity, int channel, const char *sample, float volume, float attenuation, + int flags, int pitch) +{ + if (sample && *sample == '!') + { + char name[32]; + if (SENTENCEG_Lookup(sample, name) >= 0) + EMIT_SOUND_DYN2(entity, channel, name, volume, attenuation, flags, pitch); + else + ALERT( at_aiconsole, "Unable to find %s in sentences.txt\n", sample ); + } + else + 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 +// texture name to a material type. Play footstep sound based +// on material type. + +int fTextureTypeInit = FALSE; + +#define CTEXTURESMAX 512 // max number of textures loaded + +int gcTextures = 0; +char grgszTextureName[CTEXTURESMAX][CBTEXTURENAMEMAX]; // texture names +char grgchTextureType[CTEXTURESMAX]; // parallel array of texture types + +// open materials.txt, get size, alloc space, +// save in array. Only works first time called, +// ignored on subsequent calls. + +static char *memfgets( byte *pMemFile, int fileSize, int &filePos, char *pBuffer, int bufferSize ) +{ + // Bullet-proofing + if ( !pMemFile || !pBuffer ) + return NULL; + + if ( filePos >= fileSize ) + return NULL; + + int i = filePos; + int last = fileSize; + + // fgets always NULL terminates, so only read bufferSize-1 characters + if ( last - filePos > (bufferSize-1) ) + last = filePos + (bufferSize-1); + + int stop = 0; + + // Stop at the next newline (inclusive) or end of buffer + while ( i < last && !stop ) + { + if ( pMemFile[i] == '\n' ) + stop = 1; + i++; + } + + + // If we actually advanced the pointer, copy it over + if ( i != filePos ) + { + // We read in size bytes + int size = i - filePos; + // copy it out + memcpy( pBuffer, pMemFile + filePos, sizeof(byte)*size ); + + // If the buffer isn't full, terminate (this is always true) + if ( size < bufferSize ) + pBuffer[size] = 0; + + // Update file pointer + filePos = i; + return pBuffer; + } + + // No data read, bail + return NULL; +} + + +void TEXTURETYPE_Init() +{ + char buffer[512]; + int i, j; + byte *pMemFile; + int fileSize, filePos; + + 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 ); + if ( !pMemFile ) + return; + + // for each line in the file... + while (memfgets(pMemFile, fileSize, filePos, buffer, 511) != NULL && (gcTextures < CTEXTURESMAX)) + { + // skip whitespace + i = 0; + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // skip comment lines + if (buffer[i] == '/' || !isalpha(buffer[i])) + continue; + + // get texture type + grgchTextureType[gcTextures] = toupper(buffer[i++]); + + // skip whitespace + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // get sentence name + j = i; + while (buffer[j] && !isspace(buffer[j])) + j++; + + if (!buffer[j]) + continue; + + // null-terminate name and save in sentences array + j = min (j, CBTEXTURENAMEMAX-1+i); + buffer[j] = 0; + strcpy(&(grgszTextureName[gcTextures++][0]), &(buffer[i])); + } + + g_engfuncs.pfnFreeFile( pMemFile ); + + fTextureTypeInit = TRUE; +} + +// given texture name, find texture type +// if not found, return type 'concrete' + +// NOTE: this routine should ONLY be called if the +// current texture under the player changes! + +char TEXTURETYPE_Find(char *name) +{ + // CONSIDER: pre-sort texture names and perform faster binary search here + + for (int i = 0; i < gcTextures; i++) + { + if (!_strnicmp(name, &(grgszTextureName[i][0]), CBTEXTURENAMEMAX-1)) + return (grgchTextureType[i]); + } + + return CHAR_TEX_CONCRETE; +} + +// 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 + +float TEXTURETYPE_PlaySound(TraceResult *ptr, Vector vecSrc, Vector vecEnd, int iBulletType) +{ +// hit the world, try to play sound based on texture material type + + char chTextureType; + float fvol; + float fvolbar; + char szbuffer[64]; + const char *pTextureName; + float rgfl1[3]; + float rgfl2[3]; + char *rgsz[4]; + int cnt; + float fattn = ATTN_NORM; + + if ( !g_pGameRules->PlayTextureSounds() ) + return 0.0; + + CBaseEntity *pEntity = CBaseEntity::Instance(ptr->pHit); + + chTextureType = 0; + + if (pEntity && pEntity->Classify() != CLASS_NONE && pEntity->Classify() != CLASS_MACHINE) + // hit body + chTextureType = CHAR_TEX_FLESH; + else + { + // hit world + + // find texture under strike, get material type + + // copy trace vector into array for trace_texture + + vecSrc.CopyToArray(rgfl1); + vecEnd.CopyToArray(rgfl2); + + // get texture from entity or world (world is ent(0)) + if (pEntity) + pTextureName = TRACE_TEXTURE( ENT(pEntity->pev), rgfl1, rgfl2 ); + else + pTextureName = TRACE_TEXTURE( ENT(0), rgfl1, rgfl2 ); + + if ( pTextureName ) + { + // strip leading '-0' or '+0~' or '{' or '!' + if (*pTextureName == '-' || *pTextureName == '+') + pTextureName += 2; + + if (*pTextureName == '{' || *pTextureName == '!' || *pTextureName == '~' || *pTextureName == ' ') + pTextureName++; + // '}}' + strcpy(szbuffer, pTextureName); + szbuffer[CBTEXTURENAMEMAX - 1] = 0; + + // ALERT ( at_console, "texture hit: %s\n", szbuffer); + + // get texture type + chTextureType = TEXTURETYPE_Find(szbuffer); + } + } + + switch (chTextureType) + { + default: + case CHAR_TEX_CONCRETE: fvol = 0.9; fvolbar = 0.6; + rgsz[0] = "player/pl_step1.wav"; + rgsz[1] = "player/pl_step2.wav"; + cnt = 2; + break; + case CHAR_TEX_METAL: fvol = 0.9; fvolbar = 0.3; + rgsz[0] = "player/pl_metal1.wav"; + rgsz[1] = "player/pl_metal2.wav"; + cnt = 2; + break; + case CHAR_TEX_DIRT: fvol = 0.9; fvolbar = 0.1; + rgsz[0] = "player/pl_dirt1.wav"; + rgsz[1] = "player/pl_dirt2.wav"; + rgsz[2] = "player/pl_dirt3.wav"; + cnt = 3; + break; + case CHAR_TEX_VENT: fvol = 0.5; fvolbar = 0.3; + rgsz[0] = "player/pl_duct1.wav"; + rgsz[1] = "player/pl_duct1.wav"; + cnt = 2; + break; + case CHAR_TEX_GRATE: fvol = 0.9; fvolbar = 0.5; + rgsz[0] = "player/pl_grate1.wav"; + rgsz[1] = "player/pl_grate4.wav"; + cnt = 2; + break; + case CHAR_TEX_TILE: fvol = 0.8; fvolbar = 0.2; + rgsz[0] = "player/pl_tile1.wav"; + rgsz[1] = "player/pl_tile3.wav"; + rgsz[2] = "player/pl_tile2.wav"; + rgsz[3] = "player/pl_tile4.wav"; + cnt = 4; + break; + case CHAR_TEX_SLOSH: fvol = 0.9; fvolbar = 0.0; + rgsz[0] = "player/pl_slosh1.wav"; + rgsz[1] = "player/pl_slosh3.wav"; + rgsz[2] = "player/pl_slosh2.wav"; + rgsz[3] = "player/pl_slosh4.wav"; + cnt = 4; + break; + case CHAR_TEX_WOOD: fvol = 0.9; fvolbar = 0.2; + rgsz[0] = "debris/wood1.wav"; + rgsz[1] = "debris/wood2.wav"; + rgsz[2] = "debris/wood3.wav"; + cnt = 3; + break; + case CHAR_TEX_GLASS: + case CHAR_TEX_COMPUTER: + fvol = 0.8; fvolbar = 0.2; + rgsz[0] = "debris/glass1.wav"; + rgsz[1] = "debris/glass2.wav"; + rgsz[2] = "debris/glass3.wav"; + 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"; + fattn = 1.0; + cnt = 2; + break; + } + + // did we hit a breakable? + + if (pEntity && FClassnameIs(pEntity->pev, "func_breakable")) + { + // drop volumes, the object will already play a damaged sound + fvol /= 1.5; + fvolbar /= 2.0; + } + else if (chTextureType == CHAR_TEX_COMPUTER) + { + // play random spark if computer + + if ( ptr->flFraction != 1.0 && RANDOM_LONG(0,1)) + { + UTIL_Sparks( ptr->vecEndPos ); + + float flVolume = RANDOM_FLOAT ( 0.7 , 1.0 );//random volume range + switch ( RANDOM_LONG(0,1) ) + { + case 0: UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, "buttons/spark5.wav", flVolume, ATTN_NORM, 0, 100); break; + case 1: UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, "buttons/spark6.wav", flVolume, ATTN_NORM, 0, 100); break; + // case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark5.wav", flVolume, ATTN_NORM); break; + // case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark6.wav", flVolume, ATTN_NORM); break; + } + } + } + + // play material hit sound + UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, rgsz[RANDOM_LONG(0,cnt-1)], fvol, fattn, 0, 96 + RANDOM_LONG(0,0xf)); + //EMIT_SOUND_DYN( ENT(m_pPlayer->pev), CHAN_WEAPON, rgsz[RANDOM_LONG(0,cnt-1)], fvol, ATTN_NORM, 0, 96 + RANDOM_LONG(0,0xf)); + + return fvolbar; +} + +// =================================================================================== +// +// Speaker class. Used for announcements per level, for door lock/unlock spoken voice. +// + +class CSpeaker : public CBaseEntity +{ +public: + void KeyValue( KeyValueData* pkvd); + void Spawn( void ); + void Precache( void ); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT SpeakerThink( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + + int m_preset; // preset number +}; + +LINK_ENTITY_TO_CLASS( speaker, CSpeaker ); +TYPEDESCRIPTION CSpeaker::m_SaveData[] = +{ + DEFINE_FIELD( CSpeaker, m_preset, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CSpeaker, CBaseEntity ); + +// +// ambient_generic - general-purpose user-defined static sound +// +void CSpeaker :: Spawn( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + + if ( !m_preset && (FStringNull( pev->message ) || strlen( szSoundFile ) < 1 )) + { + ALERT( at_error, "SPEAKER with no Level/Sentence! at: %f, %f, %f\n", pev->origin.x, pev->origin.y, pev->origin.z ); + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CBaseEntity::SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + + SetThink(&CSpeaker::SpeakerThink); + pev->nextthink = 0.0; + + // allow on/off switching via 'use' function. + + SetUse ( &CSpeaker::ToggleUse ); + + Precache( ); +} + +#define ANNOUNCE_MINUTES_MIN 0.25 +#define ANNOUNCE_MINUTES_MAX 2.25 + +void CSpeaker :: Precache( void ) +{ + if ( !FBitSet (pev->spawnflags, SPEAKER_START_SILENT ) ) + // set first announcement time for random n second + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(5.0, 15.0); +} +void CSpeaker :: SpeakerThink( void ) +{ + char *szSoundFile = NULL; + float flvolume = pev->health * 0.1; + float flattenuation = 0.3; + int flags = 0; + int pitch = 100; + + if (m_preset) + { + // go lookup preset text, assign szSoundFile + switch (m_preset) + { + case 1: szSoundFile = "C1A0_"; break; + case 2: szSoundFile = "C1A1_"; break; + case 3: szSoundFile = "C1A2_"; break; + case 4: szSoundFile = "C1A3_"; break; + case 5: szSoundFile = "C1A4_"; break; + case 6: szSoundFile = "C2A1_"; break; + case 7: szSoundFile = "C2A2_"; break; + case 8: szSoundFile = "C2A3_"; break; + case 9: szSoundFile = "C2A4_"; break; + case 10: szSoundFile = "C2A5_"; break; + case 11: szSoundFile = "C3A1_"; break; + case 12: szSoundFile = "C3A2_"; break; + } + } else + szSoundFile = (char*) STRING(pev->message); + + if (szSoundFile[0] == '!') + { + // play single sentence, one shot + UTIL_EmitAmbientSound ( ENT(pev), pev->origin, szSoundFile, + flvolume, flattenuation, flags, pitch); + + // shut off and reset + pev->nextthink = 0.0; + } + else + { + // make random announcement from sentence group + + if (SENTENCEG_PlayRndSz(ENT(pev), szSoundFile, flvolume, flattenuation, flags, pitch) < 0) + ALERT(at_console, "Level Design Error!\nSPEAKER has bad sentence group name: %s\n",szSoundFile); + + // 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); + } + + return; +} + + +// +// ToggleUse - if an announcement is pending, cancel it. If no announcement is pending, start one. +// +void CSpeaker :: ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int fActive = (pev->nextthink > 0.0); + + // fActive is TRUE only if an announcement is pending + + if ( useType != USE_TOGGLE ) + { + // ignore if we're just turning something on that's already on, or + // turning something off that's already off. + if ( (fActive && useType == USE_ON) || (!fActive && useType == USE_OFF) ) + return; + } + + if ( useType == USE_ON ) + { + // turn on announcements + pev->nextthink = gpGlobals->time + 0.1; + return; + } + + if ( useType == USE_OFF ) + { + // turn off announcements + pev->nextthink = 0.0; + return; + + } + + // Toggle announcements + + + if ( fActive ) + { + // turn off announcements + pev->nextthink = 0.0; + } + else + { + // turn on announcements + pev->nextthink = gpGlobals->time + 0.1; + } +} + +// KeyValue - load keyvalue pairs into member data +// NOTE: called BEFORE spawn! + +void CSpeaker :: KeyValue( KeyValueData *pkvd ) +{ + + // preset + if (FStrEq(pkvd->szKeyName, "preset")) + { + m_preset = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} diff --git a/hlsdk/dmc/dlls/soundent.cpp b/hlsdk/dmc/dlls/soundent.cpp new file mode 100644 index 00000000..c7a4b811 --- /dev/null +++ b/hlsdk/dmc/dlls/soundent.cpp @@ -0,0 +1,379 @@ +/*** +* +* Copyright (c) 1996-2002 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "soundent.h" + + +LINK_ENTITY_TO_CLASS( soundent, CSoundEnt ); + +CSoundEnt *pSoundEnt; + +//========================================================= +// CSound - Clear - zeros all fields for a sound +//========================================================= +void CSound :: Clear ( void ) +{ + m_vecOrigin = g_vecZero; + m_iType = 0; + m_iVolume = 0; + m_flExpireTime = 0; + m_iNext = SOUNDLIST_EMPTY; + m_iNextAudible = 0; +} + +//========================================================= +// Reset - clears the volume, origin, and type for a sound, +// but doesn't expire or unlink it. +//========================================================= +void CSound :: Reset ( void ) +{ + m_vecOrigin = g_vecZero; + m_iType = 0; + m_iVolume = 0; + m_iNext = SOUNDLIST_EMPTY; +} + +//========================================================= +// FIsSound - returns TRUE if the sound is an Audible sound +//========================================================= +BOOL CSound :: FIsSound ( void ) +{ + if ( m_iType & ( bits_SOUND_COMBAT | bits_SOUND_WORLD | bits_SOUND_PLAYER | bits_SOUND_DANGER ) ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// FIsScent - returns TRUE if the sound is actually a scent +//========================================================= +BOOL CSound :: FIsScent ( void ) +{ + if ( m_iType & ( bits_SOUND_CARCASS | bits_SOUND_MEAT | bits_SOUND_GARBAGE ) ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// Spawn +//========================================================= +void CSoundEnt :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + Initialize(); + + pev->nextthink = gpGlobals->time + 1; +} + +//========================================================= +// Think - at interval, the entire active sound list is checked +// for sounds that have ExpireTimes less than or equal +// to the current world time, and these sounds are deallocated. +//========================================================= +void CSoundEnt :: Think ( void ) +{ + int iSound; + int iPreviousSound; + + pev->nextthink = gpGlobals->time + 0.3;// how often to check the sound list. + + iPreviousSound = SOUNDLIST_EMPTY; + iSound = m_iActiveSound; + + while ( iSound != SOUNDLIST_EMPTY ) + { + if ( m_SoundPool[ iSound ].m_flExpireTime <= gpGlobals->time && m_SoundPool[ iSound ].m_flExpireTime != SOUND_NEVER_EXPIRE ) + { + int iNext = m_SoundPool[ iSound ].m_iNext; + + // move this sound back into the free list + FreeSound( iSound, iPreviousSound ); + + iSound = iNext; + } + else + { + iPreviousSound = iSound; + iSound = m_SoundPool[ iSound ].m_iNext; + } + } + + if ( m_fShowReport ) + { + ALERT ( at_aiconsole, "Soundlist: %d / %d (%d)\n", ISoundsInList( SOUNDLISTTYPE_ACTIVE ),ISoundsInList( SOUNDLISTTYPE_FREE ), ISoundsInList( SOUNDLISTTYPE_ACTIVE ) - m_cLastActiveSounds ); + m_cLastActiveSounds = ISoundsInList ( SOUNDLISTTYPE_ACTIVE ); + } + +} + +//========================================================= +// Precache - dummy function +//========================================================= +void CSoundEnt :: Precache ( void ) +{ +} + +//========================================================= +// FreeSound - clears the passed active sound and moves it +// to the top of the free list. TAKE CARE to only call this +// function for sounds in the Active list!! +//========================================================= +void CSoundEnt :: FreeSound ( int iSound, int iPrevious ) +{ + if ( !pSoundEnt ) + { + // no sound ent! + return; + } + + if ( iPrevious != SOUNDLIST_EMPTY ) + { + // iSound is not the head of the active list, so + // must fix the index for the Previous sound +// pSoundEnt->m_SoundPool[ iPrevious ].m_iNext = m_SoundPool[ iSound ].m_iNext; + pSoundEnt->m_SoundPool[ iPrevious ].m_iNext = pSoundEnt->m_SoundPool[ iSound ].m_iNext; + } + else + { + // the sound we're freeing IS the head of the active list. + pSoundEnt->m_iActiveSound = pSoundEnt->m_SoundPool [ iSound ].m_iNext; + } + + // make iSound the head of the Free list. + pSoundEnt->m_SoundPool[ iSound ].m_iNext = pSoundEnt->m_iFreeSound; + pSoundEnt->m_iFreeSound = iSound; +} + +//========================================================= +// IAllocSound - moves a sound from the Free list to the +// Active list returns the index of the alloc'd sound +//========================================================= +int CSoundEnt :: IAllocSound( void ) +{ + int iNewSound; + + if ( m_iFreeSound == SOUNDLIST_EMPTY ) + { + // no free sound! + ALERT ( at_console, "Free Sound List is full!\n" ); + return SOUNDLIST_EMPTY; + } + + // there is at least one sound available, so move it to the + // Active sound list, and return its SoundPool index. + + iNewSound = m_iFreeSound;// copy the index of the next free sound + + m_iFreeSound = m_SoundPool[ m_iFreeSound ].m_iNext;// move the index down into the free list. + + m_SoundPool[ iNewSound ].m_iNext = m_iActiveSound;// point the new sound at the top of the active list. + + m_iActiveSound = iNewSound;// now make the new sound the top of the active list. You're done. + + return iNewSound; +} + +//========================================================= +// InsertSound - Allocates a free sound and fills it with +// sound info. +//========================================================= +void CSoundEnt :: InsertSound ( int iType, const Vector &vecOrigin, int iVolume, float flDuration ) +{ + int iThisSound; + + if ( !pSoundEnt ) + { + // no sound ent! + return; + } + + iThisSound = pSoundEnt->IAllocSound(); + + if ( iThisSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_console, "Could not AllocSound() for InsertSound() (DLL)\n" ); + return; + } + + pSoundEnt->m_SoundPool[ iThisSound ].m_vecOrigin = vecOrigin; + pSoundEnt->m_SoundPool[ iThisSound ].m_iType = iType; + pSoundEnt->m_SoundPool[ iThisSound ].m_iVolume = iVolume; + pSoundEnt->m_SoundPool[ iThisSound ].m_flExpireTime = gpGlobals->time + flDuration; +} + +//========================================================= +// Initialize - clears all sounds and moves them into the +// free sound list. +//========================================================= +void CSoundEnt :: Initialize ( void ) +{ + int i; + int iSound; + + m_cLastActiveSounds = 0; + m_iFreeSound = 0; + m_iActiveSound = SOUNDLIST_EMPTY; + + for ( i = 0 ; i < MAX_WORLD_SOUNDS ; i++ ) + {// clear all sounds, and link them into the free sound list. + m_SoundPool[ i ].Clear(); + m_SoundPool[ i ].m_iNext = i + 1; + } + + m_SoundPool[ i - 1 ].m_iNext = SOUNDLIST_EMPTY;// terminate the list here. + + + // now reserve enough sounds for each client + for ( i = 0 ; i < gpGlobals->maxClients ; i++ ) + { + iSound = pSoundEnt->IAllocSound(); + + if ( iSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_console, "Could not AllocSound() for Client Reserve! (DLL)\n" ); + return; + } + + pSoundEnt->m_SoundPool[ iSound ].m_flExpireTime = SOUND_NEVER_EXPIRE; + } + + if ( CVAR_GET_FLOAT("displaysoundlist") == 1 ) + { + m_fShowReport = TRUE; + } + else + { + m_fShowReport = FALSE; + } +} + +//========================================================= +// ISoundsInList - returns the number of sounds in the desired +// sound list. +//========================================================= +int CSoundEnt :: ISoundsInList ( int iListType ) +{ + int i; + int iThisSound = 0; + + if ( iListType == SOUNDLISTTYPE_FREE ) + { + iThisSound = m_iFreeSound; + } + else if ( iListType == SOUNDLISTTYPE_ACTIVE ) + { + iThisSound = m_iActiveSound; + } + else + { + ALERT ( at_console, "Unknown Sound List Type!\n" ); + } + + if ( iThisSound == SOUNDLIST_EMPTY ) + { + return 0; + } + + i = 0; + + while ( iThisSound != SOUNDLIST_EMPTY ) + { + i++; + + iThisSound = m_SoundPool[ iThisSound ].m_iNext; + } + + return i; +} + +//========================================================= +// ActiveList - returns the head of the active sound list +//========================================================= +int CSoundEnt :: ActiveList ( void ) +{ + if ( !pSoundEnt ) + { + return SOUNDLIST_EMPTY; + } + + return pSoundEnt->m_iActiveSound; +} + +//========================================================= +// FreeList - returns the head of the free sound list +//========================================================= +int CSoundEnt :: FreeList ( void ) +{ + if ( !pSoundEnt ) + { + return SOUNDLIST_EMPTY; + } + + return pSoundEnt->m_iFreeSound; +} + +//========================================================= +// SoundPointerForIndex - returns a pointer to the instance +// of CSound at index's position in the sound pool. +//========================================================= +CSound* CSoundEnt :: SoundPointerForIndex( int iIndex ) +{ + if ( !pSoundEnt ) + { + return NULL; + } + + if ( iIndex > ( MAX_WORLD_SOUNDS - 1 ) ) + { + ALERT ( at_console, "SoundPointerForIndex() - Index too large!\n" ); + return NULL; + } + + if ( iIndex < 0 ) + { + ALERT ( at_console, "SoundPointerForIndex() - Index < 0!\n" ); + return NULL; + } + + return &pSoundEnt->m_SoundPool[ iIndex ]; +} + +//========================================================= +// Clients are numbered from 1 to MAXCLIENTS, but the client +// reserved sounds in the soundlist are from 0 to MAXCLIENTS - 1, +// so this function ensures that a client gets the proper index +// to his reserved sound in the soundlist. +//========================================================= +int CSoundEnt :: ClientSoundIndex ( edict_t *pClient ) +{ + int iReturn = ENTINDEX( pClient ) - 1; + +#ifdef _DEBUG + if ( iReturn < 0 || iReturn > gpGlobals->maxClients ) + { + ALERT ( at_console, "** ClientSoundIndex returning a bogus value! **\n" ); + } +#endif // _DEBUG + + return iReturn; +} diff --git a/hlsdk/dmc/dlls/soundent.h b/hlsdk/dmc/dlls/soundent.h new file mode 100644 index 00000000..150daac7 --- /dev/null +++ b/hlsdk/dmc/dlls/soundent.h @@ -0,0 +1,95 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// Soundent.h - the entity that spawns when the world +// spawns, and handles the world's active and free sound +// lists. +//========================================================= + +#define MAX_WORLD_SOUNDS 64 // maximum number of sounds handled by the world at one time. + +#define bits_SOUND_NONE 0 +#define bits_SOUND_COMBAT ( 1 << 0 )// gunshots, explosions +#define bits_SOUND_WORLD ( 1 << 1 )// door opening/closing, glass breaking +#define bits_SOUND_PLAYER ( 1 << 2 )// all noises generated by player. walking, shooting, falling, splashing +#define bits_SOUND_CARCASS ( 1 << 3 )// dead body +#define bits_SOUND_MEAT ( 1 << 4 )// gib or pork chop +#define bits_SOUND_DANGER ( 1 << 5 )// pending danger. Grenade that is about to explode, explosive barrel that is damaged, falling crate +#define bits_SOUND_GARBAGE ( 1 << 6 )// trash cans, banana peels, old fast food bags. + +#define bits_ALL_SOUNDS 0xFFFFFFFF + +#define SOUNDLIST_EMPTY -1 + +#define SOUNDLISTTYPE_FREE 1// identifiers passed to functions that can operate on either list, to indicate which list to operate on. +#define SOUNDLISTTYPE_ACTIVE 2 + +#define SOUND_NEVER_EXPIRE -1 // with this set as a sound's ExpireTime, the sound will never expire. + +//========================================================= +// CSound - an instance of a sound in the world. +//========================================================= +class CSound +{ +public: + + void Clear ( void ); + void Reset ( void ); + + Vector m_vecOrigin; // sound's location in space + int m_iType; // what type of sound this is + int m_iVolume; // how loud the sound is + float m_flExpireTime; // when the sound should be purged from the list + int m_iNext; // index of next sound in this list ( Active or Free ) + int m_iNextAudible; // temporary link that monsters use to build a list of audible sounds + + BOOL FIsSound( void ); + BOOL FIsScent( void ); +}; + +//========================================================= +// CSoundEnt - a single instance of this entity spawns when +// the world spawns. The SoundEnt's job is to update the +// world's Free and Active sound lists. +//========================================================= +class CSoundEnt : public CBaseEntity +{ +public: + + void Precache ( void ); + void Spawn( void ); + void Think( void ); + void Initialize ( void ); + + static void InsertSound ( int iType, const Vector &vecOrigin, int iVolume, float flDuration ); + static void FreeSound ( int iSound, int iPrevious ); + static int ActiveList( void );// return the head of the active list + static int FreeList( void );// return the head of the free list + static CSound* SoundPointerForIndex( int iIndex );// return a pointer for this index in the sound list + static int ClientSoundIndex ( edict_t *pClient ); + + BOOL IsEmpty( void ) { return m_iActiveSound == SOUNDLIST_EMPTY; } + int ISoundsInList ( int iListType ); + int IAllocSound ( void ); + virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } + + int m_iFreeSound; // index of the first sound in the free sound list + int m_iActiveSound; // indes of the first sound in the active sound list + int m_cLastActiveSounds; // keeps track of the number of active sounds at the last update. (for diagnostic work) + BOOL m_fShowReport; // if true, dump information about free/active sounds. + +private: + CSound m_SoundPool[ MAX_WORLD_SOUNDS ]; +}; diff --git a/hlsdk/dmc/dlls/spectator.cpp b/hlsdk/dmc/dlls/spectator.cpp new file mode 100644 index 00000000..5f91731e --- /dev/null +++ b/hlsdk/dmc/dlls/spectator.cpp @@ -0,0 +1,149 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// CBaseSpectator + +// YWB: UNDONE + +// Spectator functions +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "spectator.h" + +/* +=========== +SpectatorConnect + +called when a spectator connects to a server +============ +*/ +void CBaseSpectator::SpectatorConnect(void) +{ + pev->flags = FL_SPECTATOR; + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NOCLIP; + + m_pGoalEnt = NULL; +} + +/* +=========== +SpectatorDisconnect + +called when a spectator disconnects from a server +============ +*/ +void CBaseSpectator::SpectatorDisconnect(void) +{ +} + +/* +================ +SpectatorImpulseCommand + +Called by SpectatorThink if the spectator entered an impulse +================ +*/ +void CBaseSpectator::SpectatorImpulseCommand(void) +{ + static edict_t *pGoal = NULL; + edict_t *pPreviousGoal; + edict_t *pCurrentGoal; + BOOL bFound; + + switch (pev->impulse) + { + case 1: + // teleport the spectator to the next spawn point + // note that if the spectator is tracking, this doesn't do + // much + pPreviousGoal = pGoal; + pCurrentGoal = pGoal; + // Start at the current goal, skip the world, and stop if we looped + // back around + + bFound = FALSE; + while (1) + { + pCurrentGoal = FIND_ENTITY_BY_CLASSNAME(pCurrentGoal, "info_player_deathmatch"); + // Looped around, failure + if (pCurrentGoal == pPreviousGoal) + { + ALERT(at_console, "Could not find a spawn spot.\n"); + break; + } + // Found a non-world entity, set success, otherwise, look for the next one. + if (!FNullEnt(pCurrentGoal)) + { + bFound = TRUE; + break; + } + } + + if (!bFound) // Didn't find a good spot. + break; + + pGoal = pCurrentGoal; + UTIL_SetOrigin( pev, pGoal->v.origin ); + pev->angles = pGoal->v.angles; + pev->fixangle = FALSE; + break; + default: + ALERT(at_console, "Unknown spectator impulse\n"); + break; + } + + pev->impulse = 0; +} + +/* +================ +SpectatorThink + +Called every frame after physics are run +================ +*/ +void CBaseSpectator::SpectatorThink(void) +{ + if (!(pev->flags & FL_SPECTATOR)) + { + pev->flags = FL_SPECTATOR; + } + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NOCLIP; + + if (pev->impulse) + SpectatorImpulseCommand(); +} + +/* +=========== +Spawn + + Called when spectator is initialized: + UNDONE: Is this actually being called because spectators are not allocated in normal fashion? +============ +*/ +void CBaseSpectator::Spawn() +{ + pev->flags = FL_SPECTATOR; + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NOCLIP; + + m_pGoalEnt = NULL; +} diff --git a/hlsdk/dmc/dlls/spectator.h b/hlsdk/dmc/dlls/spectator.h new file mode 100644 index 00000000..2f755d6b --- /dev/null +++ b/hlsdk/dmc/dlls/spectator.h @@ -0,0 +1,27 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Spectator.h + +class CBaseSpectator : public CBaseEntity +{ +public: + void Spawn(); + void SpectatorConnect(void); + void SpectatorDisconnect(void); + void SpectatorThink(void); + +private: + void SpectatorImpulseCommand(void); +}; diff --git a/hlsdk/dmc/dlls/subs.cpp b/hlsdk/dmc/dlls/subs.cpp new file mode 100644 index 00000000..d3560bd5 --- /dev/null +++ b/hlsdk/dmc/dlls/subs.cpp @@ -0,0 +1,559 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== subs.cpp ======================================================== + + frequently used global functions + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "nodes.h" +#include "doors.h" + +extern CGraph WorldGraph; + +extern BOOL FEntIsVisible(entvars_t* pev, entvars_t* pevTarget); + +extern DLL_GLOBAL int g_iSkillLevel; + + +// Landmark class +void CPointEntity :: Spawn( void ) +{ + pev->solid = SOLID_NOT; +// UTIL_SetSize(pev, g_vecZero, g_vecZero); +} + + +class CNullEntity : public CBaseEntity +{ +public: + void Spawn( void ); +}; + + +// Null Entity, remove on startup +void CNullEntity :: Spawn( void ) +{ + REMOVE_ENTITY(ENT(pev)); +} +LINK_ENTITY_TO_CLASS(info_null,CNullEntity); + +class CBaseDMStart : public CPointEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + BOOL IsTriggered( CBaseEntity *pEntity ); + +private: +}; + +// These are the new entry points to entities. +LINK_ENTITY_TO_CLASS(info_player_deathmatch,CBaseDMStart); +LINK_ENTITY_TO_CLASS(info_player_start,CPointEntity); +LINK_ENTITY_TO_CLASS(info_landmark,CPointEntity); + +void CBaseDMStart::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "master")) + { + pev->netname = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +BOOL CBaseDMStart::IsTriggered( CBaseEntity *pEntity ) +{ + BOOL master = UTIL_IsMasterTriggered( pev->netname, pEntity ); + + return master; +} + +// This updates global tables that need to know about entities being removed +void CBaseEntity::UpdateOnRemove( void ) +{ + int i; + + if ( FBitSet( pev->flags, FL_GRAPHED ) ) + { + // this entity was a LinkEnt in the world node graph, so we must remove it from + // the graph since we are removing it from the world. + for ( i = 0 ; i < WorldGraph.m_cLinks ; i++ ) + { + if ( WorldGraph.m_pLinkPool [ i ].m_pLinkEnt == pev ) + { + // if this link has a link ent which is the same ent that is removing itself, remove it! + WorldGraph.m_pLinkPool [ i ].m_pLinkEnt = NULL; + } + } + } + if ( pev->globalname ) + gGlobalState.EntitySetState( pev->globalname, GLOBAL_DEAD ); +} + +// Convenient way to delay removing oneself +void CBaseEntity :: SUB_Remove( void ) +{ + UpdateOnRemove(); + if (pev->health > 0) + { + // this situation can screw up monsters who can't tell their entity pointers are invalid. + pev->health = 0; + ALERT( at_aiconsole, "SUB_Remove called on entity with health > 0\n"); + } + + REMOVE_ENTITY(ENT(pev)); +} + + +// Convenient way to explicitly do nothing (passed to functions that require a method) +void CBaseEntity :: SUB_DoNothing( void ) +{ +} + + +// Global Savedata for Delay +TYPEDESCRIPTION CBaseDelay::m_SaveData[] = +{ + DEFINE_FIELD( CBaseDelay, m_flDelay, FIELD_FLOAT ), + DEFINE_FIELD( CBaseDelay, m_iszKillTarget, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CBaseDelay, CBaseEntity ); + +void CBaseDelay :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "delay")) + { + m_flDelay = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "killtarget")) + { + m_iszKillTarget = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + { + CBaseEntity::KeyValue( pkvd ); + } +} + + +/* +============================== +SUB_UseTargets + +If self.delay is set, a DelayedUse entity will be created that will actually +do the SUB_UseTargets after that many seconds have passed. + +Removes all entities with a targetname that match self.killtarget, +and removes them, so some events can remove other triggers. + +Search for (string)targetname in all entities that +match (string)self.target and call their .use function (if they have one) + +============================== +*/ +void CBaseEntity :: SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ) +{ + // + // fire targets + // + if (!FStringNull(pev->target)) + { + FireTargets( STRING(pev->target), pActivator, this, useType, value ); + } +} + + +void FireTargets( const char *targetName, CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + edict_t *pentTarget = NULL; + if ( !targetName ) + return; + + ALERT( at_aiconsole, "Firing: (%s)\n", targetName ); + + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, targetName); + if (FNullEnt(pentTarget)) + break; + + CBaseEntity *pTarget = CBaseEntity::Instance( pentTarget ); + if ( pTarget && !(pTarget->pev->flags & FL_KILLME) ) // Don't use dying ents + { + ALERT( at_aiconsole, "Found: %s, firing (%s)\n", STRING(pTarget->pev->classname), targetName ); + pTarget->Use( pActivator, pCaller, useType, value ); + } + } +} + +LINK_ENTITY_TO_CLASS( DelayedUse, CBaseDelay ); + + +void CBaseDelay :: SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ) +{ + // + // exit immediatly if we don't have a target or kill target + // + if (FStringNull(pev->target) && !m_iszKillTarget) + return; + + // + // check for a delay + // + if (m_flDelay != 0) + { + // create a temp object to fire at a later time + CBaseDelay *pTemp = GetClassPtr( (CBaseDelay *)NULL); + pTemp->pev->classname = MAKE_STRING("DelayedUse"); + + pTemp->pev->nextthink = gpGlobals->time + m_flDelay; + + pTemp->SetThink( &CBaseDelay::DelayThink ); + + // Save the useType + pTemp->pev->button = (int)useType; + pTemp->m_iszKillTarget = m_iszKillTarget; + pTemp->m_flDelay = 0; // prevent "recursion" + pTemp->pev->target = pev->target; + + // HACKHACK + // This wasn't in the release build of Half-Life. We should have moved m_hActivator into this class + // but changing member variable hierarchy would break save/restore without some ugly code. + // This code is not as ugly as that code + if ( pActivator && pActivator->IsPlayer() ) // If a player activates, then save it + { + pTemp->pev->owner = pActivator->edict(); + } + else + { + pTemp->pev->owner = NULL; + } + + return; + } + + // + // kill the killtargets + // + + if ( m_iszKillTarget ) + { + edict_t *pentKillTarget = NULL; + + ALERT( at_aiconsole, "KillTarget: %s\n", STRING(m_iszKillTarget) ); + pentKillTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_iszKillTarget) ); + while ( !FNullEnt(pentKillTarget) ) + { + UTIL_Remove( CBaseEntity::Instance(pentKillTarget) ); + + ALERT( at_aiconsole, "killing %s\n", STRING( pentKillTarget->v.classname ) ); + pentKillTarget = FIND_ENTITY_BY_TARGETNAME( pentKillTarget, STRING(m_iszKillTarget) ); + } + } + + // + // fire targets + // + if (!FStringNull(pev->target)) + { + FireTargets( STRING(pev->target), pActivator, this, useType, value ); + } +} + + +/* +void CBaseDelay :: SUB_UseTargetsEntMethod( void ) +{ + SUB_UseTargets(pev); +} +*/ + +/* +QuakeEd only writes a single float for angles (bad idea), so up and down are +just constant angles. +*/ +void SetMovedir( entvars_t *pev ) +{ + if (pev->angles == Vector(0, -1, 0)) + { + pev->movedir = Vector(0, 0, 1); + } + else if (pev->angles == Vector(0, -2, 0)) + { + pev->movedir = Vector(0, 0, -1); + } + else + { + UTIL_MakeVectors(pev->angles); + pev->movedir = gpGlobals->v_forward; + } + + pev->angles = g_vecZero; +} + + + + +void CBaseDelay::DelayThink( void ) +{ + CBaseEntity *pActivator = NULL; + + if ( pev->owner != NULL ) // A player activated this on delay + { + pActivator = CBaseEntity::Instance( pev->owner ); + } + // The use type is cached (and stashed) in pev->button + SUB_UseTargets( pActivator, (USE_TYPE)pev->button, 0 ); + REMOVE_ENTITY(ENT(pev)); +} + + +// Global Savedata for Toggle +TYPEDESCRIPTION CBaseToggle::m_SaveData[] = +{ + DEFINE_FIELD( CBaseToggle, m_toggle_state, FIELD_INTEGER ), + DEFINE_FIELD( CBaseToggle, m_flActivateFinished, FIELD_TIME ), + DEFINE_FIELD( CBaseToggle, m_flMoveDistance, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flWait, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flLip, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flTWidth, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flTLength, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_vecPosition1, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_vecPosition2, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_vecAngle1, FIELD_VECTOR ), // UNDONE: Position could go through transition, but also angle? + DEFINE_FIELD( CBaseToggle, m_vecAngle2, FIELD_VECTOR ), // UNDONE: Position could go through transition, but also angle? + DEFINE_FIELD( CBaseToggle, m_cTriggersLeft, FIELD_INTEGER ), + DEFINE_FIELD( CBaseToggle, m_flHeight, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_hActivator, FIELD_EHANDLE ), + DEFINE_FIELD( CBaseToggle, m_pfnCallWhenMoveDone, FIELD_FUNCTION ), + DEFINE_FIELD( CBaseToggle, m_vecFinalDest, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_vecFinalAngle, FIELD_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_sMaster, FIELD_STRING), + DEFINE_FIELD( CBaseToggle, m_bitsDamageInflict, FIELD_INTEGER ), // damage type inflicted +}; +IMPLEMENT_SAVERESTORE( CBaseToggle, CBaseAnimating ); + + +void CBaseToggle::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "lip")) + { + m_flLip = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "master")) + { + m_sMaster = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "distance")) + { + m_flMoveDistance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + +/* +============= +LinearMove + +calculate pev->velocity and pev->nextthink to reach vecDest from +pev->origin traveling at flSpeed +=============== +*/ +void CBaseToggle :: LinearMove( Vector vecDest, float flSpeed ) +{ + ASSERTSZ(flSpeed != 0, "LinearMove: no speed is defined!"); +// ASSERTSZ(m_pfnCallWhenMoveDone != NULL, "LinearMove: no post-move function defined"); + + m_vecFinalDest = vecDest; + + // Already there? + if (vecDest == pev->origin) + { + LinearMoveDone(); + return; + } + + // set destdelta to the vector needed to move + Vector vecDestDelta = vecDest - pev->origin; + + // divide vector length by speed to get time to reach dest + float flTravelTime = vecDestDelta.Length() / flSpeed; + + // set nextthink to trigger a call to LinearMoveDone when dest is reached + pev->nextthink = pev->ltime + flTravelTime; + SetThink( &CBaseToggle::LinearMoveDone ); + + // scale the destdelta vector by the time spent traveling to get velocity + pev->velocity = vecDestDelta / flTravelTime; +} + + +/* +============ +After moving, set origin to exact final destination, call "move done" function +============ +*/ +void CBaseToggle :: LinearMoveDone( void ) +{ + UTIL_SetOrigin(pev, m_vecFinalDest); + pev->velocity = g_vecZero; + pev->nextthink = -1; + if ( m_pfnCallWhenMoveDone ) + (this->*m_pfnCallWhenMoveDone)(); +} + +BOOL CBaseToggle :: IsLockedByMaster( void ) +{ + if (m_sMaster && !UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + return TRUE; + else + return FALSE; +} + +/* +============= +AngularMove + +calculate pev->velocity and pev->nextthink to reach vecDest from +pev->origin traveling at flSpeed +Just like LinearMove, but rotational. +=============== +*/ +void CBaseToggle :: AngularMove( Vector vecDestAngle, float flSpeed ) +{ + ASSERTSZ(flSpeed != 0, "AngularMove: no speed is defined!"); +// ASSERTSZ(m_pfnCallWhenMoveDone != NULL, "AngularMove: no post-move function defined"); + + m_vecFinalAngle = vecDestAngle; + + // Already there? + if (vecDestAngle == pev->angles) + { + AngularMoveDone(); + return; + } + + // set destdelta to the vector needed to move + Vector vecDestDelta = vecDestAngle - pev->angles; + + // divide by speed to get time to reach dest + float flTravelTime = vecDestDelta.Length() / flSpeed; + + // set nextthink to trigger a call to AngularMoveDone when dest is reached + pev->nextthink = pev->ltime + flTravelTime; + SetThink( &CBaseToggle::AngularMoveDone ); + + // scale the destdelta vector by the time spent traveling to get velocity + pev->avelocity = vecDestDelta / flTravelTime; +} + + +/* +============ +After rotating, set angle to exact final angle, call "move done" function +============ +*/ +void CBaseToggle :: AngularMoveDone( void ) +{ + pev->angles = m_vecFinalAngle; + pev->avelocity = g_vecZero; + pev->nextthink = -1; + if ( m_pfnCallWhenMoveDone ) + (this->*m_pfnCallWhenMoveDone)(); +} + + +float CBaseToggle :: AxisValue( int flags, const Vector &angles ) +{ + if ( FBitSet(flags, SF_DOOR_ROTATE_Z) ) + return angles.z; + if ( FBitSet(flags, SF_DOOR_ROTATE_X) ) + return angles.x; + + return angles.y; +} + + +void CBaseToggle :: AxisDir( entvars_t *pev ) +{ + if ( FBitSet(pev->spawnflags, SF_DOOR_ROTATE_Z) ) + pev->movedir = Vector ( 0, 0, 1 ); // around z-axis + else if ( FBitSet(pev->spawnflags, SF_DOOR_ROTATE_X) ) + pev->movedir = Vector ( 1, 0, 0 ); // around x-axis + else + pev->movedir = Vector ( 0, 1, 0 ); // around y-axis +} + + +float CBaseToggle :: AxisDelta( int flags, const Vector &angle1, const Vector &angle2 ) +{ + if ( FBitSet (flags, SF_DOOR_ROTATE_Z) ) + return angle1.z - angle2.z; + + if ( FBitSet (flags, SF_DOOR_ROTATE_X) ) + return angle1.x - angle2.x; + + return angle1.y - angle2.y; +} + + +/* +============= +FEntIsVisible + +returns TRUE if the passed entity is visible to caller, even if not infront () +============= +*/ + BOOL +FEntIsVisible( + entvars_t* pev, + entvars_t* pevTarget) + { + Vector vecSpot1 = pev->origin + pev->view_ofs; + Vector vecSpot2 = pevTarget->origin + pevTarget->view_ofs; + TraceResult tr; + + UTIL_TraceLine(vecSpot1, vecSpot2, ignore_monsters, ENT(pev), &tr); + + if (tr.fInOpen && tr.fInWater) + return FALSE; // sight line crossed contents + + if (tr.flFraction == 1) + return TRUE; + + return FALSE; + } + + diff --git a/hlsdk/dmc/dlls/teamplay_gamerules.cpp b/hlsdk/dmc/dlls/teamplay_gamerules.cpp new file mode 100644 index 00000000..9309ffbc --- /dev/null +++ b/hlsdk/dmc/dlls/teamplay_gamerules.cpp @@ -0,0 +1,615 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// 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" + + +/*class CDMCGameMgrHelper : public IVoiceGameMgrHelper +{ +public: + virtual bool ComparePlayerTeams(CBasePlayer *pPlayer1, CBasePlayer *pPlayer2) + { + return true; + } +}; +static CDMCGameMgrHelper g_GameMgrHelper;*/ + + +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_VoiceGameMgr.Init(&g_GameMgrHelper, VGMode(HearPVS), gpGlobals->maxClients); + + 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(); +} + +BOOL CHalfLifeTeamplay::ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) +{ +// m_VoiceGameMgr.ClientConnected(pEntity); + + return CHalfLifeMultiplay::ClientConnected(pEntity, pszName, pszAddress, szRejectReason); +} + + +extern cvar_t timeleft, fragsleft; + +void CHalfLifeTeamplay :: Think ( void ) +{ +// m_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 + { + 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 = static_cast(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(m_VoiceGameMgr.ClientCommand(pPlayer, pcmd)) +// return TRUE; + + if ( FStrEq( pcmd, "menuselect" ) ) + { + if ( CMD_ARGC() < 2 ) + return TRUE; + + // select the item from the current menu + + return TRUE; + } + + return FALSE; +} + +extern int gmsgGameMode; +extern int gmsgSayText; +extern int gmsgTeamInfo; + + +void CHalfLifeTeamplay :: UpdateGameMode( CBasePlayer *pPlayer ) +{ + MESSAGE_BEGIN( MSG_ONE, gmsgGameMode, NULL, pPlayer->edict() ); + WRITE_BYTE( 1 ); // 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 ) +{ + SetDefaultPlayerTeam( pPlayer ); + CHalfLifeMultiplay::InitHUD( pPlayer ); + + 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 ); + 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 ( int 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(); +} + + +//========================================================= +// 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><%s><%s>\" joined team \"%s\"\n", + STRING(pPlayer->pev->netname), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + pPlayer->m_szTeamName, + mdls ); + + ChangePlayerTeam( pPlayer, mdls, TRUE, TRUE ); + // recound stuff + RecountTeams(); +} + +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 ); + + 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_STRING( "teammate" ); // flag this as a teammate kill + 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 ( (CVAR_GET_FLOAT("mp_friendlyfire") == 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( void ) +{ + 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] += static_cast(plr->pev->frags); + } + } + } +} diff --git a/hlsdk/dmc/dlls/teamplay_gamerules.h b/hlsdk/dmc/dlls/teamplay_gamerules.h new file mode 100644 index 00000000..f9de3cb8 --- /dev/null +++ b/hlsdk/dmc/dlls/teamplay_gamerules.h @@ -0,0 +1,62 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// teamplay_gamerules.h +// + +#include "voice_gamemgr.h" + +#define MAX_TEAMNAME_LENGTH 16 +#define MAX_TEAMS 32 + +#define TEAMPLAY_TEAMLISTLENGTH MAX_TEAMS*MAX_TEAMNAME_LENGTH + +class CHalfLifeTeamplay : public CHalfLifeMultiplay +{ +public: + CHalfLifeTeamplay(); + + virtual BOOL ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); + virtual BOOL ClientCommand( CBasePlayer *pPlayer, const char *pcmd ); + virtual void ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ); + virtual BOOL IsTeamplay( void ); + virtual BOOL FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ); + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); + virtual const char *GetTeamID( CBaseEntity *pEntity ); + virtual BOOL ShouldAutoAim( CBasePlayer *pPlayer, edict_t *target ); + 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 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 Think ( void ); + virtual int GetTeamIndex( const char *pTeamName ); + virtual const char *GetIndexedTeamName( int teamIndex ); + virtual BOOL IsValidTeam( const char *pTeamName ); + const char *SetDefaultPlayerTeam( CBasePlayer *pPlayer ); + virtual void ChangePlayerTeam( CBasePlayer *pPlayer, const char *pTeamName, BOOL bKill, BOOL bGib ); + + CVoiceGameMgr m_VoiceGameMgr; + +private: + void RecountTeams( void ); + const char *TeamWithFewestPlayers( void ); + + BOOL m_DisableDeathMessages; + BOOL m_DisableDeathPenalty; + BOOL m_teamLimit; // This means the server set only some teams as valid + char m_szTeamList[TEAMPLAY_TEAMLISTLENGTH]; +}; diff --git a/hlsdk/dmc/dlls/trains.h b/hlsdk/dmc/dlls/trains.h new file mode 100644 index 00000000..87aec763 --- /dev/null +++ b/hlsdk/dmc/dlls/trains.h @@ -0,0 +1,127 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef TRAINS_H +#define TRAINS_H + +// Tracktrain spawn flags +#define SF_TRACKTRAIN_NOPITCH 0x0001 +#define SF_TRACKTRAIN_NOCONTROL 0x0002 +#define SF_TRACKTRAIN_FORWARDONLY 0x0004 +#define SF_TRACKTRAIN_PASSABLE 0x0008 + +// Spawnflag for CPathTrack +#define SF_PATH_DISABLED 0x00000001 +#define SF_PATH_FIREONCE 0x00000002 +#define SF_PATH_ALTREVERSE 0x00000004 +#define SF_PATH_DISABLE_TRAIN 0x00000008 +#define SF_PATH_ALTERNATE 0x00008000 + +// Spawnflags of CPathCorner +#define SF_CORNER_WAITFORTRIG 0x001 +#define SF_CORNER_TELEPORT 0x002 +#define SF_CORNER_FIREONCE 0x004 + +//#define PATH_SPARKLE_DEBUG 1 // This makes a particle effect around path_track entities for debugging +class CPathTrack : public CPointEntity +{ +public: + void Spawn( void ); + void Activate( void ); + void KeyValue( KeyValueData* pkvd); + + void SetPrevious( CPathTrack *pprevious ); + void Link( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + CPathTrack *ValidPath( CPathTrack *ppath, int testFlag ); // Returns ppath if enabled, NULL otherwise + void Project( CPathTrack *pstart, CPathTrack *pend, Vector *origin, float dist ); + + static CPathTrack *Instance( edict_t *pent ); + + CPathTrack *LookAhead( Vector *origin, float dist, int move ); + CPathTrack *Nearest( Vector origin ); + + CPathTrack *GetNext( void ); + CPathTrack *GetPrevious( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; +#if PATH_SPARKLE_DEBUG + void EXPORT Sparkle(void); +#endif + + float m_length; + string_t m_altName; + CPathTrack *m_pnext; + CPathTrack *m_pprevious; + CPathTrack *m_paltpath; +}; + + +class CFuncTrackTrain : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + + void Blocked( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData* pkvd ); + + void EXPORT Next( void ); + void EXPORT Find( void ); + void EXPORT NearestPath( void ); + void EXPORT DeadEnd( void ); + + void NextThink( float thinkTime, BOOL alwaysThink ); + + void SetTrack( CPathTrack *track ) { m_ppath = track->Nearest(pev->origin); } + void SetControls( entvars_t *pevControls ); + BOOL OnControls( entvars_t *pev ); + + void StopSound ( void ); + void UpdateSound ( void ); + + static CFuncTrackTrain *Instance( edict_t *pent ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DIRECTIONAL_USE; } + + virtual void OverrideReset( void ); + + CPathTrack *m_ppath; + float m_length; + float m_height; + float m_speed; + float m_dir; + float m_startSpeed; + Vector m_controlMins; + Vector m_controlMaxs; + int m_soundPlaying; + int m_sounds; + float m_flVolume; + float m_flBank; + float m_oldSpeed; + +private: + unsigned short m_usAdjustPitch; +}; + +#endif diff --git a/hlsdk/dmc/dlls/triggers.cpp b/hlsdk/dmc/dlls/triggers.cpp new file mode 100644 index 00000000..0fffb058 --- /dev/null +++ b/hlsdk/dmc/dlls/triggers.cpp @@ -0,0 +1,2715 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== triggers.cpp ======================================================== + + spawn and use functions for editor-placed triggers + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "saverestore.h" +#include "trains.h" // trigger_camera has train functionality +#include "gamerules.h" + +#define SF_TRIGGER_PUSH_START_OFF 2//spawnflag that makes trigger_push spawn turned OFF +#define SF_TRIGGER_HURT_TARGETONCE 1// Only fire hurt target once +#define SF_TRIGGER_HURT_START_OFF 2//spawnflag that makes trigger_push spawn turned OFF +#define SF_TRIGGER_HURT_NO_CLIENTS 8//spawnflag that makes trigger_push spawn turned OFF +#define SF_TRIGGER_HURT_CLIENTONLYFIRE 16// trigger hurt will only fire its target if it is hurting a client +#define SF_TRIGGER_HURT_CLIENTONLYTOUCH 32// only clients may touch this trigger. + +extern DLL_GLOBAL BOOL g_fGameOver; + +extern Vector g_vecTeleMins[ MAX_TELES ]; +extern Vector g_vecTeleMaxs[ MAX_TELES ]; +extern int g_iTeleNum; + +extern void SetMovedir(entvars_t* pev); +extern Vector VecBModelOrigin( entvars_t* pevBModel ); + +extern unsigned short g_sTeleport; + +class CFrictionModifier : public CBaseEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT ChangeFriction( CBaseEntity *pOther ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + static TYPEDESCRIPTION m_SaveData[]; + + float m_frictionFraction; // Sorry, couldn't resist this name :) +}; + +LINK_ENTITY_TO_CLASS( func_friction, CFrictionModifier ); + +// Global Savedata for changelevel friction modifier +TYPEDESCRIPTION CFrictionModifier::m_SaveData[] = +{ + DEFINE_FIELD( CFrictionModifier, m_frictionFraction, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE(CFrictionModifier,CBaseEntity); + + +// Modify an entity's friction +void CFrictionModifier :: Spawn( void ) +{ + pev->solid = SOLID_TRIGGER; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->movetype = MOVETYPE_NONE; + SetTouch( &CFrictionModifier::ChangeFriction ); +} + + +// Sets toucher's friction to m_frictionFraction (1.0 = normal friction) +void CFrictionModifier :: ChangeFriction( CBaseEntity *pOther ) +{ + if ( pOther->pev->movetype != MOVETYPE_BOUNCEMISSILE && pOther->pev->movetype != MOVETYPE_BOUNCE ) + pOther->pev->friction = m_frictionFraction; +} + + + +// Sets toucher's friction to m_frictionFraction (1.0 = normal friction) +void CFrictionModifier :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "modifier")) + { + m_frictionFraction = atof(pkvd->szValue) / 100.0; + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +// This trigger will fire when the level spawns (or respawns if not fire once) +// It will check a global state before firing. It supports delay and killtargets + +#define SF_AUTO_FIREONCE 0x0001 + +class CAutoTrigger : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Precache( void ); + void Think( void ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_globalstate; + USE_TYPE triggerType; +}; +LINK_ENTITY_TO_CLASS( trigger_auto, CAutoTrigger ); + +TYPEDESCRIPTION CAutoTrigger::m_SaveData[] = +{ + DEFINE_FIELD( CAutoTrigger, m_globalstate, FIELD_STRING ), + DEFINE_FIELD( CAutoTrigger, triggerType, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE(CAutoTrigger,CBaseDelay); + +void CAutoTrigger::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "globalstate")) + { + m_globalstate = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "triggerstate")) + { + int type = atoi( pkvd->szValue ); + switch( type ) + { + case 0: + triggerType = USE_OFF; + break; + case 2: + triggerType = USE_TOGGLE; + break; + default: + triggerType = USE_ON; + break; + } + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + + +void CAutoTrigger::Spawn( void ) +{ + Precache(); +} + + +void CAutoTrigger::Precache( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CAutoTrigger::Think( void ) +{ + if ( !m_globalstate || gGlobalState.EntityGetState( m_globalstate ) == GLOBAL_ON ) + { + SUB_UseTargets( this, triggerType, 0 ); + if ( pev->spawnflags & SF_AUTO_FIREONCE ) + UTIL_Remove( this ); + } +} + + + +#define SF_RELAY_FIREONCE 0x0001 + +class CTriggerRelay : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + USE_TYPE triggerType; +}; +LINK_ENTITY_TO_CLASS( trigger_relay, CTriggerRelay ); + +TYPEDESCRIPTION CTriggerRelay::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerRelay, triggerType, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerRelay,CBaseDelay); + +void CTriggerRelay::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "triggerstate")) + { + int type = atoi( pkvd->szValue ); + switch( type ) + { + case 0: + triggerType = USE_OFF; + break; + case 2: + triggerType = USE_TOGGLE; + break; + default: + triggerType = USE_ON; + break; + } + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + + +void CTriggerRelay::Spawn( void ) +{ +} + + + + +void CTriggerRelay::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SUB_UseTargets( this, triggerType, 0 ); + if ( pev->spawnflags & SF_RELAY_FIREONCE ) + UTIL_Remove( this ); +} + + +//********************************************************** +// The Multimanager Entity - when fired, will fire up to 16 targets +// at specified times. +// FLAG: THREAD (create clones when triggered) +// FLAG: CLONE (this is a clone for a threaded execution) + +#define SF_MULTIMAN_CLONE 0x80000000 +#define SF_MULTIMAN_THREAD 0x00000001 + +class CMultiManager : public CBaseToggle +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn ( void ); + void EXPORT ManagerThink ( void ); + void EXPORT ManagerUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +#if _DEBUG + void EXPORT ManagerReport( void ); +#endif + + BOOL HasTarget( string_t targetname ); + + int ObjectCaps( void ) { return CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_cTargets; // the total number of targets in this manager's fire list. + int m_index; // Current target + float m_startTime;// Time we started firing + int m_iTargetName [ MAX_MULTI_TARGETS ];// list if indexes into global string array + float m_flTargetDelay [ MAX_MULTI_TARGETS ];// delay (in seconds) from time of manager fire to target fire +private: + inline BOOL IsClone( void ) { return (pev->spawnflags & SF_MULTIMAN_CLONE) ? TRUE : FALSE; } + inline BOOL ShouldClone( void ) + { + if ( IsClone() ) + return FALSE; + + return (pev->spawnflags & SF_MULTIMAN_THREAD) ? TRUE : FALSE; + } + + CMultiManager *Clone( void ); +}; +LINK_ENTITY_TO_CLASS( multi_manager, CMultiManager ); + +// Global Savedata for multi_manager +TYPEDESCRIPTION CMultiManager::m_SaveData[] = +{ + DEFINE_FIELD( CMultiManager, m_cTargets, FIELD_INTEGER ), + DEFINE_FIELD( CMultiManager, m_index, FIELD_INTEGER ), + DEFINE_FIELD( CMultiManager, m_startTime, FIELD_TIME ), + DEFINE_ARRAY( CMultiManager, m_iTargetName, FIELD_STRING, MAX_MULTI_TARGETS ), + DEFINE_ARRAY( CMultiManager, m_flTargetDelay, FIELD_FLOAT, MAX_MULTI_TARGETS ), +}; + +IMPLEMENT_SAVERESTORE(CMultiManager,CBaseToggle); + +void CMultiManager :: KeyValue( KeyValueData *pkvd ) +{ + // UNDONE: Maybe this should do something like this: + //CBaseToggle::KeyValue( pkvd ); + // if ( !pkvd->fHandled ) + // ... etc. + + if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else // add this field to the target list + { + // this assumes that additional fields are targetnames and their values are delay values. + if ( m_cTargets < MAX_MULTI_TARGETS ) + { + char tmp[128]; + + UTIL_StripToken( pkvd->szKeyName, tmp ); + m_iTargetName [ m_cTargets ] = ALLOC_STRING( tmp ); + m_flTargetDelay [ m_cTargets ] = atof (pkvd->szValue); + m_cTargets++; + pkvd->fHandled = TRUE; + } + } +} + + +void CMultiManager :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + SetUse ( &CMultiManager::ManagerUse ); + SetThink ( &CMultiManager::ManagerThink); + + // Sort targets + // Quick and dirty bubble sort + int swapped = 1; + + while ( swapped ) + { + swapped = 0; + for ( int i = 1; i < m_cTargets; i++ ) + { + if ( m_flTargetDelay[i] < m_flTargetDelay[i-1] ) + { + // Swap out of order elements + int name = m_iTargetName[i]; + float delay = m_flTargetDelay[i]; + m_iTargetName[i] = m_iTargetName[i-1]; + m_flTargetDelay[i] = m_flTargetDelay[i-1]; + m_iTargetName[i-1] = name; + m_flTargetDelay[i-1] = delay; + swapped = 1; + } + } + } +} + + +BOOL CMultiManager::HasTarget( string_t targetname ) +{ + for ( int i = 0; i < m_cTargets; i++ ) + if ( FStrEq(STRING(targetname), STRING(m_iTargetName[i])) ) + return TRUE; + + return FALSE; +} + + +// Designers were using this to fire targets that may or may not exist -- +// so I changed it to use the standard target fire code, made it a little simpler. +void CMultiManager :: ManagerThink ( void ) +{ + float time; + + time = gpGlobals->time - m_startTime; + while ( m_index < m_cTargets && m_flTargetDelay[ m_index ] <= time ) + { + FireTargets( STRING( m_iTargetName[ m_index ] ), m_hActivator, this, USE_TOGGLE, 0 ); + m_index++; + } + + if ( m_index >= m_cTargets )// have we fired all targets? + { + SetThink( NULL ); + if ( IsClone() ) + { + UTIL_Remove( this ); + return; + } + SetUse ( &CMultiManager::ManagerUse );// allow manager re-use + } + else + pev->nextthink = m_startTime + m_flTargetDelay[ m_index ]; +} + +CMultiManager *CMultiManager::Clone( void ) +{ + CMultiManager *pMulti = GetClassPtr( (CMultiManager *)NULL ); + + edict_t *pEdict = pMulti->pev->pContainingEntity; + memcpy( pMulti->pev, pev, sizeof(*pev) ); + pMulti->pev->pContainingEntity = pEdict; + + pMulti->pev->spawnflags |= SF_MULTIMAN_CLONE; + pMulti->m_cTargets = m_cTargets; + memcpy( pMulti->m_iTargetName, m_iTargetName, sizeof( m_iTargetName ) ); + memcpy( pMulti->m_flTargetDelay, m_flTargetDelay, sizeof( m_flTargetDelay ) ); + + return pMulti; +} + + +// The USE function builds the time table and starts the entity thinking. +void CMultiManager :: ManagerUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // In multiplayer games, clone the MM and execute in the clone (like a thread) + // to allow multiple players to trigger the same multimanager + if ( ShouldClone() ) + { + CMultiManager *pClone = Clone(); + pClone->ManagerUse( pActivator, pCaller, useType, value ); + return; + } + + m_hActivator = pActivator; + m_index = 0; + m_startTime = gpGlobals->time; + + SetUse( NULL );// disable use until all targets have fired + + SetThink ( &CMultiManager::ManagerThink ); + pev->nextthink = gpGlobals->time; +} + +#if _DEBUG +void CMultiManager :: ManagerReport ( void ) +{ + int cIndex; + + for ( cIndex = 0 ; cIndex < m_cTargets ; cIndex++ ) + { + ALERT ( at_console, "%s %f\n", STRING(m_iTargetName[cIndex]), m_flTargetDelay[cIndex] ); + } +} +#endif + +//*********************************************************** + + +// +// Render parameters trigger +// +// This entity will copy its render parameters (renderfx, rendermode, rendercolor, renderamt) +// to its targets when triggered. +// + + +// Flags to indicate masking off various render parameters that are normally copied to the targets +#define SF_RENDER_MASKFX (1<<0) +#define SF_RENDER_MASKAMT (1<<1) +#define SF_RENDER_MASKMODE (1<<2) +#define SF_RENDER_MASKCOLOR (1<<3) + +class CRenderFxManager : public CBaseEntity +{ +public: + void Spawn( void ); + void Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; + +LINK_ENTITY_TO_CLASS( env_render, CRenderFxManager ); + + +void CRenderFxManager :: Spawn ( void ) +{ + pev->solid = SOLID_NOT; +} + +void CRenderFxManager :: Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (!FStringNull(pev->target)) + { + edict_t* pentTarget = NULL; + while ( 1 ) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->target)); + if (FNullEnt(pentTarget)) + break; + + entvars_t *pevTarget = VARS( pentTarget ); + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKFX ) ) + pevTarget->renderfx = pev->renderfx; + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKAMT ) ) + pevTarget->renderamt = pev->renderamt; + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKMODE ) ) + pevTarget->rendermode = pev->rendermode; + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKCOLOR ) ) + pevTarget->rendercolor = pev->rendercolor; + } + } +} + + + +class CBaseTrigger : public CBaseToggle +{ +public: + void EXPORT TeleportTouch ( CBaseEntity *pOther ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT MultiTouch( CBaseEntity *pOther ); + void EXPORT HurtTouch ( CBaseEntity *pOther ); + void EXPORT EnvTouch ( CBaseEntity *pOther ); + void EXPORT CDAudioTouch ( CBaseEntity *pOther ); + void ActivateMultiTrigger( CBaseEntity *pActivator ); + void EXPORT MultiWaitOver( void ); + void EXPORT CounterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void InitTrigger( void ); + + virtual int ObjectCaps( void ) { return CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +LINK_ENTITY_TO_CLASS( trigger, CBaseTrigger ); + +/* +================ +InitTrigger +================ +*/ +void CBaseTrigger::InitTrigger( ) +{ + // trigger angles are used for one-way touches. An angle of 0 is assumed + // to mean no restrictions, so use a yaw of 360 instead. + if (pev->angles != g_vecZero) + SetMovedir(pev); + pev->solid = SOLID_TRIGGER; + pev->movetype = MOVETYPE_NONE; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world +// if ( CVAR_GET_FLOAT("showtriggers") == 0 ) + SetBits( pev->effects, EF_NODRAW ); +} + + +// +// Cache user-entity-field values until spawn is called. +// + +void CBaseTrigger :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "damage")) + { + pev->dmg = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "count")) + { + m_cTriggersLeft = (int) atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damagetype")) + { + m_bitsDamageInflict = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +class CTriggerHurt : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT RadiationThink( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_hurt, CTriggerHurt ); + +class CTriggerEnvHurt : public CBaseTrigger +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_env_hurt, CTriggerEnvHurt ); + + +// +// trigger_monsterjump +// +class CTriggerMonsterJump : public CBaseTrigger +{ +public: + void Spawn( void ); + void Touch( CBaseEntity *pOther ); + void Think( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_monsterjump, CTriggerMonsterJump ); + + +void CTriggerMonsterJump :: Spawn ( void ) +{ + SetMovedir ( pev ); + + InitTrigger (); + + pev->nextthink = 0; + pev->speed = 200; + m_flHeight = 150; + + if ( !FStringNull ( pev->targetname ) ) + {// if targetted, spawn turned off + pev->solid = SOLID_NOT; + UTIL_SetOrigin( pev, pev->origin ); // Unlink from trigger list + SetUse( &CBaseTrigger::ToggleUse ); + } +} + + +void CTriggerMonsterJump :: Think( void ) +{ + pev->solid = SOLID_NOT;// kill the trigger for now !!!UNDONE + UTIL_SetOrigin( pev, pev->origin ); // Unlink from trigger list + SetThink( NULL ); +} + +void CTriggerMonsterJump :: Touch( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + 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->nextthink = gpGlobals->time; +} + + +//===================================== +// +// trigger_cdaudio - starts/stops cd audio tracks +// +class CTriggerCDAudio : public CBaseTrigger +{ +public: + void Spawn( void ); + + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void PlayTrack( void ); + void Touch ( CBaseEntity *pOther ); +}; + +LINK_ENTITY_TO_CLASS( trigger_cdaudio, CTriggerCDAudio ); + +// +// Changes tracks or stops CD when player touches +// +// !!!HACK - overloaded HEALTH to avoid adding new field +void CTriggerCDAudio :: Touch ( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + {// only clients may trigger these events + return; + } + + PlayTrack(); +} + +void CTriggerCDAudio :: Spawn( void ) +{ + InitTrigger(); +} + +void CTriggerCDAudio::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + PlayTrack(); +} + +void PlayCDTrack( int iTrack ) +{ + edict_t *pClient; + + // manually find the single player. + pClient = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + // Can't play if the client is not connected! + if ( !pClient ) + return; + + if ( iTrack < -1 || iTrack > 30 ) + { + ALERT ( at_console, "TriggerCDAudio - Track %d out of range\n" ); + return; + } + + if ( iTrack == -1 ) + { + CLIENT_COMMAND ( pClient, "cd pause\n"); + } + else + { + char string [ 64 ]; + + sprintf( string, "cd play %3d\n", iTrack ); + CLIENT_COMMAND ( pClient, string); + } +} + + +// only plays for ONE client, so only use in single play! +void CTriggerCDAudio :: PlayTrack( void ) +{ + //PlayCDTrack( (int)pev->health ); + + SetTouch( NULL ); + UTIL_Remove( this ); +} + + +// This plays a CD track when fired or when the player enters it's radius +class CTargetCDAudio : public CPointEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Think( void ); + void Play( void ); +}; + +LINK_ENTITY_TO_CLASS( target_cdaudio, CTargetCDAudio ); + +void CTargetCDAudio :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "radius")) + { + pev->scale = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CTargetCDAudio :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + if ( pev->scale > 0 ) + pev->nextthink = gpGlobals->time + 1.0; +} + +void CTargetCDAudio::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + Play(); +} + +// only plays for ONE client, so only use in single play! +void CTargetCDAudio::Think( void ) +{ + edict_t *pClient; + + // manually find the single player. + pClient = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + // Can't play if the client is not connected! + if ( !pClient ) + return; + + pev->nextthink = gpGlobals->time + 0.5; + + if ( (pClient->v.origin - pev->origin).Length() <= pev->scale ) + Play(); + +} + +void CTargetCDAudio::Play( void ) +{ + //PlayCDTrack( (int)pev->health ); + UTIL_Remove(this); +} + +//===================================== + +/* +========================== +CTriggerEnvHurt +Used to represent Slime or Lava +========================== +*/ + +void CTriggerEnvHurt :: Spawn( void ) +{ + InitTrigger(); + SetTouch ( &CBaseTrigger::EnvTouch ); + + if ( FBitSet (pev->spawnflags, SF_TRIGGER_HURT_START_OFF) )// if flagged to Start Turned Off, make trigger nonsolid. + pev->solid = SOLID_NOT; + + UTIL_SetOrigin( pev, pev->origin ); // Link into the list +} + +// When touched, a hurt trigger does DMG points of damage each half-second +void CBaseTrigger :: EnvTouch ( CBaseEntity *pOther ) +{ + float fldmg = 0.0f; + + if ( !pOther->pev->takedamage ) + return; + + if ( (pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYTOUCH) && !pOther->IsPlayer() ) + { + // this trigger is only allowed to touch clients, and this ain't a client. + return; + } + + if ( (pev->spawnflags & SF_TRIGGER_HURT_NO_CLIENTS) && pOther->IsPlayer() ) + return; + + // HACKHACK -- In multiplayer, players touch this based on packet receipt. + // So the players who send packets later aren't always hurt. Keep track of + // how much time has passed and whether or not you've touched that player + if ( g_pGameRules->IsMultiplayer() ) + { + if ( pev->dmgtime > gpGlobals->time ) + { + if ( gpGlobals->time != pev->pain_finished ) + {// too early to hurt again, and not same frame with a different entity + if ( pOther->IsPlayer() ) + { + int playerMask = 1 << (pOther->entindex() - 1); + + // If I've already touched this player (this time), then bail out + if ( pev->impulse & playerMask ) + return; + + // Mark this player as touched + // BUGBUG - There can be only 32 players! + pev->impulse |= playerMask; + } + else + { + return; + } + } + } + else + { + // New clock, "un-touch" all players + pev->impulse = 0; + if ( pOther->IsPlayer() ) + { + int playerMask = 1 << (pOther->entindex() - 1); + + // Mark this player as touched + // BUGBUG - There can be only 32 players! + pev->impulse |= playerMask; + } + } + } + else // Original code -- single player + { + if ( pev->dmgtime > gpGlobals->time && gpGlobals->time != pev->pain_finished ) + {// too early to hurt again, and not same frame with a different entity + return; + } + } + + + //We are in slime + if ( m_bitsDamageInflict & DMG_ACID ) + { + pev->dmg = 4; //Default damage of 4 + fldmg = (float)( pev->dmg * pOther->pev->waterlevel ); // pev->damage plus our current waterlevel + + pev->dmgtime = gpGlobals->time + 1.0; //Next damage in 1 second + } + //We are in lava + else if ( m_bitsDamageInflict & DMG_BURN ) + { + pev->dmg = 10; //Default damage of 10 + fldmg = (float)( pev->dmg * pOther->pev->waterlevel ); // pev->damage plus our current waterlevel + + if ( pOther->IsPlayer() ) + { + if ( ( (CBasePlayer *)pOther )->m_iQuakeItems & IT_SUIT) // Wearing the suit slows down the next damage time + pev->dmgtime = gpGlobals->time + 1.0; + else + pev->dmgtime = gpGlobals->time + 0.2; + } + else + pev->dmgtime = gpGlobals->time + 0.2; + } + + if ( fldmg < 0 ) + pOther->TakeHealth( -fldmg, m_bitsDamageInflict ); + else + pOther->TakeDamage( pev, pev, fldmg, m_bitsDamageInflict ); + + // Store pain time so we can get all of the other entities on this frame + pev->pain_finished = gpGlobals->time; + + + if ( pev->target ) + { + // trigger has a target it wants to fire. + if ( pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYFIRE ) + { + // if the toucher isn't a client, don't fire the target! + if ( !pOther->IsPlayer() ) + { + return; + } + } + + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); + if ( pev->spawnflags & SF_TRIGGER_HURT_TARGETONCE ) + pev->target = 0; + } +} + +// trigger_hurt - hurts anything that touches it. if the trigger has a targetname, firing it will toggle state +// +//int gfToggleState = 0; // used to determine when all radiation trigger hurts have called 'RadiationThink' +void CTriggerHurt :: Spawn( void ) +{ + InitTrigger(); + SetTouch ( &CBaseTrigger::HurtTouch ); + + if ( !FStringNull ( pev->targetname ) ) + { + SetUse ( &CBaseTrigger::ToggleUse ); + } + else + { + SetUse ( NULL ); + } + + if (m_bitsDamageInflict & DMG_RADIATION) + { + SetThink ( &CTriggerHurt::RadiationThink ); + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(0.0, 0.5); + } + + if ( FBitSet (pev->spawnflags, SF_TRIGGER_HURT_START_OFF) )// if flagged to Start Turned Off, make trigger nonsolid. + pev->solid = SOLID_NOT; + + UTIL_SetOrigin( pev, pev->origin ); // Link into the list +} + +// trigger hurt that causes radiation will do a radius +// check and set the player's geiger counter level +// according to distance from center of trigger + +void CTriggerHurt :: RadiationThink( void ) +{ + + edict_t *pentPlayer; + CBasePlayer *pPlayer = NULL; + float flRange; + entvars_t *pevTarget; + Vector vecSpot1; + Vector vecSpot2; + Vector vecRange; + Vector origin; + Vector view_ofs; + + // check to see if a player is in pvs + // if not, continue + + // set origin to center of trigger so that this check works + origin = pev->origin; + view_ofs = pev->view_ofs; + + pev->origin = (pev->absmin + pev->absmax) * 0.5; + pev->view_ofs = pev->view_ofs * 0.0; + + pentPlayer = FIND_CLIENT_IN_PVS(edict()); + + pev->origin = origin; + pev->view_ofs = view_ofs; + + // reset origin + + if (!FNullEnt(pentPlayer)) + { + + pPlayer = GetClassPtr( (CBasePlayer *)VARS(pentPlayer)); + + pevTarget = VARS(pentPlayer); + + // get range to player; + + vecSpot1 = (pev->absmin + pev->absmax) * 0.5; + vecSpot2 = (pevTarget->absmin + pevTarget->absmax) * 0.5; + + 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; +} + +// +// ToggleUse - If this is the USE function for a trigger, its state will toggle every time it's fired +// +void CBaseTrigger :: ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (pev->solid == SOLID_NOT) + {// if the trigger is off, turn it on + pev->solid = SOLID_TRIGGER; + + // Force retouch + gpGlobals->force_retouch++; + } + else + {// turn the trigger off + pev->solid = SOLID_NOT; + } + UTIL_SetOrigin( pev, pev->origin ); +} + +// When touched, a hurt trigger does DMG points of damage each half-second +void CBaseTrigger :: HurtTouch ( CBaseEntity *pOther ) +{ + float fldmg; + + if ( !pOther->pev->takedamage ) + return; + + if ( (pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYTOUCH) && !pOther->IsPlayer() ) + { + // this trigger is only allowed to touch clients, and this ain't a client. + return; + } + + if ( (pev->spawnflags & SF_TRIGGER_HURT_NO_CLIENTS) && pOther->IsPlayer() ) + return; + + // HACKHACK -- In multiplayer, players touch this based on packet receipt. + // So the players who send packets later aren't always hurt. Keep track of + // how much time has passed and whether or not you've touched that player + if ( g_pGameRules->IsMultiplayer() ) + { + if ( pev->dmgtime > gpGlobals->time ) + { + if ( gpGlobals->time != pev->pain_finished ) + {// too early to hurt again, and not same frame with a different entity + if ( pOther->IsPlayer() ) + { + int playerMask = 1 << (pOther->entindex() - 1); + + // If I've already touched this player (this time), then bail out + if ( pev->impulse & playerMask ) + return; + + // Mark this player as touched + // BUGBUG - There can be only 32 players! + pev->impulse |= playerMask; + } + else + { + return; + } + } + } + else + { + // New clock, "un-touch" all players + pev->impulse = 0; + if ( pOther->IsPlayer() ) + { + int playerMask = 1 << (pOther->entindex() - 1); + + // Mark this player as touched + // BUGBUG - There can be only 32 players! + pev->impulse |= playerMask; + } + } + } + else // Original code -- single player + { + if ( pev->dmgtime > gpGlobals->time && gpGlobals->time != pev->pain_finished ) + {// too early to hurt again, and not same frame with a different entity + return; + } + } + + + + // If this is time_based damage (poison, radiation), override the pev->dmg with a + // default for the given damage type. Monsters only take time-based damage + // while touching the trigger. Player continues taking damage for a while after + // leaving the trigger + + fldmg = pev->dmg * 0.5; // 0.5 seconds worth of damage, pev->dmg is damage/second + + + // JAY: Cut this because it wasn't fully realized. Damage is simpler now. +#if 0 + switch (m_bitsDamageInflict) + { + default: break; + case DMG_POISON: fldmg = POISON_DAMAGE/4; break; + case DMG_NERVEGAS: fldmg = NERVEGAS_DAMAGE/4; break; + case DMG_RADIATION: fldmg = RADIATION_DAMAGE/4; break; + case DMG_PARALYZE: fldmg = PARALYZE_DAMAGE/4; break; // UNDONE: cut this? should slow movement to 50% + case DMG_ACID: fldmg = ACID_DAMAGE/4; break; + case DMG_SLOWBURN: fldmg = SLOWBURN_DAMAGE/4; break; + case DMG_SLOWFREEZE: fldmg = SLOWFREEZE_DAMAGE/4; break; + } +#endif + + if ( fldmg < 0 ) + pOther->TakeHealth( -fldmg, m_bitsDamageInflict ); + else + pOther->TakeDamage( pev, pev, fldmg, m_bitsDamageInflict ); + + // Store pain time so we can get all of the other entities on this frame + pev->pain_finished = gpGlobals->time; + + // Apply damage every half second + pev->dmgtime = gpGlobals->time + 0.5;// half second delay until this trigger can hurt toucher again + + + + if ( pev->target ) + { + // trigger has a target it wants to fire. + if ( pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYFIRE ) + { + // if the toucher isn't a client, don't fire the target! + if ( !pOther->IsPlayer() ) + { + return; + } + } + + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); + if ( pev->spawnflags & SF_TRIGGER_HURT_TARGETONCE ) + pev->target = 0; + } +} + + +/*QUAKED trigger_multiple (.5 .5 .5) ? notouch +Variable sized repeatable trigger. Must be targeted at one or more entities. +If "health" is set, the trigger must be killed to activate each time. +If "delay" is set, the trigger waits some time after activating before firing. +"wait" : Seconds between triggerings. (.2 default) +If notouch is set, the trigger is only fired by other entities, not by touching. +NOTOUCH has been obsoleted by trigger_relay! +sounds +1) secret +2) beep beep +3) large switch +4) +NEW +if a trigger has a NETNAME, that NETNAME will become the TARGET of the triggered object. +*/ +class CTriggerMultiple : public CBaseTrigger +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_multiple, CTriggerMultiple ); + + +void CTriggerMultiple :: Spawn( void ) +{ + if (m_flWait == 0) + m_flWait = 0.2; + + InitTrigger(); + + ASSERTSZ(pev->health == 0, "trigger_multiple with health"); +// UTIL_SetOrigin(pev, pev->origin); +// SET_MODEL( ENT(pev), STRING(pev->model) ); +// if (pev->health > 0) +// { +// if (FBitSet(pev->spawnflags, SPAWNFLAG_NOTOUCH)) +// ALERT(at_error, "trigger_multiple spawn: health and notouch don't make sense"); +// pev->max_health = pev->health; +//UNDONE: where to get pfnDie from? +// pev->pfnDie = multi_killed; +// pev->takedamage = DAMAGE_YES; +// pev->solid = SOLID_BBOX; +// UTIL_SetOrigin(pev, pev->origin); // make sure it links into the world +// } +// else + { + SetTouch( &CBaseTrigger::MultiTouch ); + } + } + + +/*QUAKED trigger_once (.5 .5 .5) ? notouch +Variable sized trigger. Triggers once, then removes itself. You must set the key "target" to the name of another object in the level that has a matching +"targetname". If "health" is set, the trigger must be killed to activate. +If notouch is set, the trigger is only fired by other entities, not by touching. +if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired. +if "angle" is set, the trigger will only fire when someone is facing the direction of the angle. Use "360" for an angle of 0. +sounds +1) secret +2) beep beep +3) large switch +4) +*/ +class CTriggerOnce : public CTriggerMultiple +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_once, CTriggerOnce ); +void CTriggerOnce::Spawn( void ) +{ + m_flWait = -1; + + CTriggerMultiple :: Spawn(); +} + + + +void CBaseTrigger :: MultiTouch( CBaseEntity *pOther ) +{ + entvars_t *pevToucher; + + pevToucher = pOther->pev; + + // Only touch clients, monsters, or pushables (depending on flags) + if ( ((pevToucher->flags & FL_CLIENT) && !(pev->spawnflags & SF_TRIGGER_NOCLIENTS)) || + ((pevToucher->flags & FL_MONSTER) && (pev->spawnflags & SF_TRIGGER_ALLOWMONSTERS)) || + (pev->spawnflags & SF_TRIGGER_PUSHABLES) && FClassnameIs(pevToucher,"func_pushable") ) + { + +#if 0 + // if the trigger has an angles field, check player's facing direction + if (pev->movedir != g_vecZero) + { + UTIL_MakeVectors( pevToucher->angles ); + if ( DotProduct( gpGlobals->v_forward, pev->movedir ) < 0 ) + return; // not facing the right way + } +#endif + + ActivateMultiTrigger( pOther ); + } +} + + +// +// the trigger was just touched/killed/used +// self.enemy should be set to the activator so it can be held through a delay +// so wait for the delay time before firing +// +void CBaseTrigger :: ActivateMultiTrigger( CBaseEntity *pActivator ) +{ + if (pev->nextthink > gpGlobals->time) + return; // still waiting for reset time + + if (!UTIL_IsMasterTriggered(m_sMaster,pActivator)) + return; + + if (FClassnameIs(pev, "trigger_secret")) + { + if ( pev->enemy == NULL || !FClassnameIs(pev->enemy, "player")) + return; + gpGlobals->found_secrets++; + } + + if (!FStringNull(pev->noise)) + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + +// don't trigger again until reset +// pev->takedamage = DAMAGE_NO; + + m_hActivator = pActivator; + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + + if ( pev->message && pActivator->IsPlayer() ) + { + UTIL_ShowMessage( STRING(pev->message), pActivator ); +// CLIENT_PRINTF( ENT( pActivator->pev ), print_center, STRING(pev->message) ); + } + + if (m_flWait > 0) + { + SetThink( &CBaseTrigger::MultiWaitOver ); + pev->nextthink = gpGlobals->time + m_flWait; + } + else + { + // we can't just remove (self) here, because this is a touch function + // called while C code is looping through area links... + SetTouch( NULL ); + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CBaseEntity::SUB_Remove ); + } +} + + +// the wait time has passed, so set back up for another activation +void CBaseTrigger :: MultiWaitOver( void ) +{ +// if (pev->max_health) +// { +// pev->health = pev->max_health; +// pev->takedamage = DAMAGE_YES; +// pev->solid = SOLID_BBOX; +// } + SetThink( NULL ); +} + + +// ========================= COUNTING TRIGGER ===================================== + +// +// GLOBALS ASSUMED SET: g_eoActivator +// +void CBaseTrigger::CounterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_cTriggersLeft--; + m_hActivator = pActivator; + + if (m_cTriggersLeft < 0) + return; + + BOOL fTellActivator = + (m_hActivator != 0) && + FClassnameIs(m_hActivator->pev, "player") && + !FBitSet(pev->spawnflags, SPAWNFLAG_NOMESSAGE); + if (m_cTriggersLeft != 0) + { + if (fTellActivator) + { + // UNDONE: I don't think we want these Quakesque messages + switch (m_cTriggersLeft) + { + case 1: ALERT(at_console, "Only 1 more to go..."); break; + case 2: ALERT(at_console, "Only 2 more to go..."); break; + case 3: ALERT(at_console, "Only 3 more to go..."); break; + default: ALERT(at_console, "There are more to go..."); break; + } + } + return; + } + + // !!!UNDONE: I don't think we want these Quakesque messages + if (fTellActivator) + ALERT(at_console, "Sequence completed!"); + + ActivateMultiTrigger( m_hActivator ); +} + + +/*QUAKED trigger_counter (.5 .5 .5) ? nomessage +Acts as an intermediary for an action that takes multiple inputs. +If nomessage is not set, it will print "1 more.. " etc when triggered and +"sequence complete" when finished. After the counter has been triggered "cTriggersLeft" +times (default 2), it will fire all of it's targets and remove itself. +*/ +class CTriggerCounter : public CBaseTrigger +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS( trigger_counter, CTriggerCounter ); + +void CTriggerCounter :: Spawn( void ) +{ + // By making the flWait be -1, this counter-trigger will disappear after it's activated + // (but of course it needs cTriggersLeft "uses" before that happens). + m_flWait = -1; + + if (m_cTriggersLeft == 0) + m_cTriggersLeft = 2; + SetUse( &CBaseTrigger::CounterUse ); +} + +// ====================== TRIGGER_CHANGELEVEL ================================ + +class CTriggerVolume : public CPointEntity // Derive from point entity so this doesn't move across levels +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_transition, CTriggerVolume ); + +// Define space that travels across a level transition +void CTriggerVolume :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->model = 0; + pev->modelindex = 0; +} + + +// Fires a target after level transition and then dies +class CFireAndDie : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void Think( void ); + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() | FCAP_FORCE_TRANSITION; } // Always go across transitions +}; +LINK_ENTITY_TO_CLASS( fireanddie, CFireAndDie ); + +void CFireAndDie::Spawn( void ) +{ + pev->classname = MAKE_STRING("fireanddie"); + // Don't call Precache() - it should be called on restore +} + + +void CFireAndDie::Precache( void ) +{ + // This gets called on restore + pev->nextthink = gpGlobals->time + m_flDelay; +} + + +void CFireAndDie::Think( void ) +{ + SUB_UseTargets( this, USE_TOGGLE, 0 ); + UTIL_Remove( this ); +} + + +#define SF_CHANGELEVEL_USEONLY 0x0002 +class CChangeLevel : public CBaseTrigger +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT UseChangeLevel ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT TriggerChangeLevel( void ); + void EXPORT ExecuteChangeLevel( void ); + void EXPORT TouchChangeLevel( CBaseEntity *pOther ); + void ChangeLevelNow( CBaseEntity *pActivator ); + + static edict_t *FindLandmark( const char *pLandmarkName ); + static int ChangeList( LEVELLIST *pLevelList, int maxList ); + static int AddTransitionToList( LEVELLIST *pLevelList, int listCount, const char *pMapName, const char *pLandmarkName, edict_t *pentLandmark ); + static int InTransitionVolume( CBaseEntity *pEntity, char *pVolumeName ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + char m_szMapName[cchMapNameMost]; // trigger_changelevel only: next map + char m_szLandmarkName[cchMapNameMost]; // trigger_changelevel only: landmark on next map + int m_changeTarget; + float m_changeTargetDelay; +}; +LINK_ENTITY_TO_CLASS( trigger_changelevel, CChangeLevel ); + +// Global Savedata for changelevel trigger +TYPEDESCRIPTION CChangeLevel::m_SaveData[] = +{ + DEFINE_ARRAY( CChangeLevel, m_szMapName, FIELD_CHARACTER, cchMapNameMost ), + DEFINE_ARRAY( CChangeLevel, m_szLandmarkName, FIELD_CHARACTER, cchMapNameMost ), + DEFINE_FIELD( CChangeLevel, m_changeTarget, FIELD_STRING ), + DEFINE_FIELD( CChangeLevel, m_changeTargetDelay, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE(CChangeLevel,CBaseTrigger); + +// +// Cache user-entity-field values until spawn is called. +// + +void CChangeLevel :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "map")) + { + if (strlen(pkvd->szValue) >= cchMapNameMost) + ALERT( at_error, "Map name '%s' too long (32 chars)\n", pkvd->szValue ); + strcpy(m_szMapName, pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "landmark")) + { + if (strlen(pkvd->szValue) >= cchMapNameMost) + ALERT( at_error, "Landmark name '%s' too long (32 chars)\n", pkvd->szValue ); + strcpy(m_szLandmarkName, pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "changetarget")) + { + m_changeTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "changedelay")) + { + m_changeTargetDelay = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseTrigger::KeyValue( pkvd ); +} + + +/*QUAKED trigger_changelevel (0.5 0.5 0.5) ? NO_INTERMISSION +When the player touches this, he gets sent to the map listed in the "map" variable. Unless the NO_INTERMISSION flag is set, the view will go to the info_intermission spot and display stats. +*/ + +void CChangeLevel :: Spawn( void ) +{ + if ( FStrEq( m_szMapName, "" ) ) + ALERT( at_console, "a trigger_changelevel doesn't have a map" ); + + if ( FStrEq( m_szLandmarkName, "" ) ) + ALERT( at_console, "trigger_changelevel to %s doesn't have a landmark", m_szMapName ); + + if (!FStringNull ( pev->targetname ) ) + { + SetUse ( &CChangeLevel::UseChangeLevel ); + } + InitTrigger(); + if ( !(pev->spawnflags & SF_CHANGELEVEL_USEONLY) ) + SetTouch( &CChangeLevel::TouchChangeLevel ); +// ALERT( at_console, "TRANSITION: %s (%s)\n", m_szMapName, m_szLandmarkName ); +} + + +void CChangeLevel :: ExecuteChangeLevel( void ) +{ + MESSAGE_BEGIN( MSG_ALL, SVC_CDTRACK ); + WRITE_BYTE( 3 ); + WRITE_BYTE( 3 ); + MESSAGE_END(); + + MESSAGE_BEGIN(MSG_ALL, SVC_INTERMISSION); + MESSAGE_END(); +} + + +FILE_GLOBAL char st_szNextMap[cchMapNameMost]; +FILE_GLOBAL char st_szNextSpot[cchMapNameMost]; + +edict_t *CChangeLevel :: FindLandmark( const char *pLandmarkName ) +{ + edict_t *pentLandmark; + + pentLandmark = FIND_ENTITY_BY_STRING( NULL, "targetname", pLandmarkName ); + while ( !FNullEnt( pentLandmark ) ) + { + // Found the landmark + if ( FClassnameIs( pentLandmark, "info_landmark" ) ) + return pentLandmark; + else + pentLandmark = FIND_ENTITY_BY_STRING( pentLandmark, "targetname", pLandmarkName ); + } + ALERT( at_error, "Can't find landmark %s\n", pLandmarkName ); + return NULL; +} + + +//========================================================= +// CChangeLevel :: Use - allows level transitions to be +// triggered by buttons, etc. +// +//========================================================= +void CChangeLevel :: UseChangeLevel ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + ChangeLevelNow( pActivator ); +} + +void CChangeLevel :: ChangeLevelNow( CBaseEntity *pActivator ) +{ + edict_t *pentLandmark; + + ASSERT(!FStrEq(m_szMapName, "")); + + // Don't work in deathmatch + if ( g_pGameRules->IsDeathmatch() ) + return; + + // Some people are firing these multiple times in a frame, disable + if ( gpGlobals->time == pev->dmgtime ) + return; + + pev->dmgtime = gpGlobals->time; + + + CBaseEntity *pPlayer = CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) ); + if ( !InTransitionVolume( pPlayer, m_szLandmarkName ) ) + { + ALERT( at_aiconsole, "Player isn't in the transition volume %s, aborting\n", m_szLandmarkName ); + return; + } + + // Create an entity to fire the changetarget + if ( m_changeTarget ) + { + CFireAndDie *pFireAndDie = GetClassPtr( (CFireAndDie *)NULL ); + if ( pFireAndDie ) + { + // Set target and delay + pFireAndDie->pev->target = m_changeTarget; + pFireAndDie->m_flDelay = m_changeTargetDelay; + pFireAndDie->pev->origin = pPlayer->pev->origin; + // Call spawn + DispatchSpawn( pFireAndDie->edict() ); + } + } + // This object will get removed in the call to CHANGE_LEVEL, copy the params into "safe" memory + strcpy(st_szNextMap, m_szMapName); + + m_hActivator = pActivator; + SUB_UseTargets( pActivator, USE_TOGGLE, 0 ); + st_szNextSpot[0] = 0; // Init landmark to NULL + + // look for a landmark entity + pentLandmark = FindLandmark( m_szLandmarkName ); + if ( !FNullEnt( pentLandmark ) ) + { + strcpy(st_szNextSpot, m_szLandmarkName); + gpGlobals->vecLandmarkOffset = VARS(pentLandmark)->origin; + } +// ALERT( at_console, "Level touches %d levels\n", ChangeList( levels, 16 ) ); + ALERT( at_console, "CHANGE LEVEL: %s %s\n", st_szNextMap, st_szNextSpot ); + CHANGE_LEVEL( st_szNextMap, st_szNextSpot ); +} + +// +// GLOBALS ASSUMED SET: st_szNextMap +// +void CChangeLevel :: TouchChangeLevel( CBaseEntity *pOther ) +{ + if (!FClassnameIs(pOther->pev, "player")) + return; + + ChangeLevelNow( pOther ); +} + + +// Add a transition to the list, but ignore duplicates +// (a designer may have placed multiple trigger_changelevels with the same landmark) +int CChangeLevel::AddTransitionToList( LEVELLIST *pLevelList, int listCount, const char *pMapName, const char *pLandmarkName, edict_t *pentLandmark ) +{ + int i; + + if ( !pLevelList || !pMapName || !pLandmarkName || !pentLandmark ) + return 0; + + for ( i = 0; i < listCount; i++ ) + { + if ( pLevelList[i].pentLandmark == pentLandmark && strcmp( pLevelList[i].mapName, pMapName ) == 0 ) + return 0; + } + strcpy( pLevelList[listCount].mapName, pMapName ); + strcpy( pLevelList[listCount].landmarkName, pLandmarkName ); + pLevelList[listCount].pentLandmark = pentLandmark; + pLevelList[listCount].vecLandmarkOrigin = VARS(pentLandmark)->origin; + + return 1; +} + +int BuildChangeList( LEVELLIST *pLevelList, int maxList ) +{ + return CChangeLevel::ChangeList( pLevelList, maxList ); +} + + +int CChangeLevel::InTransitionVolume( CBaseEntity *pEntity, char *pVolumeName ) +{ + edict_t *pentVolume; + + + if ( pEntity->ObjectCaps() & FCAP_FORCE_TRANSITION ) + return 1; + + // If you're following another entity, follow it through the transition (weapons follow the player) + if ( pEntity->pev->movetype == MOVETYPE_FOLLOW ) + { + if ( pEntity->pev->aiment != NULL ) + pEntity = CBaseEntity::Instance( pEntity->pev->aiment ); + } + + int inVolume = 1; // Unless we find a trigger_transition, everything is in the volume + + pentVolume = FIND_ENTITY_BY_TARGETNAME( NULL, pVolumeName ); + while ( !FNullEnt( pentVolume ) ) + { + CBaseEntity *pVolume = CBaseEntity::Instance( pentVolume ); + + if ( pVolume && FClassnameIs( pVolume->pev, "trigger_transition" ) ) + { + if ( pVolume->Intersects( pEntity ) ) // It touches one, it's in the volume + return 1; + else + inVolume = 0; // Found a trigger_transition, but I don't intersect it -- if I don't find another, don't go! + } + pentVolume = FIND_ENTITY_BY_TARGETNAME( pentVolume, pVolumeName ); + } + + return inVolume; +} + + +// We can only ever move 512 entities across a transition +#define MAX_ENTITY 512 + +// This has grown into a complicated beast +// Can we make this more elegant? +// This builds the list of all transitions on this level and which entities are in their PVS's and can / should +// be moved across. +int CChangeLevel::ChangeList( LEVELLIST *pLevelList, int maxList ) +{ + edict_t *pentChangelevel, *pentLandmark; + int i, count; + + count = 0; + + // Find all of the possible level changes on this BSP + pentChangelevel = FIND_ENTITY_BY_STRING( NULL, "classname", "trigger_changelevel" ); + if ( FNullEnt( pentChangelevel ) ) + return 0; + while ( !FNullEnt( pentChangelevel ) ) + { + CChangeLevel *pTrigger; + + pTrigger = GetClassPtr((CChangeLevel *)VARS(pentChangelevel)); + if ( pTrigger ) + { + // Find the corresponding landmark + pentLandmark = FindLandmark( pTrigger->m_szLandmarkName ); + if ( pentLandmark ) + { + // Build a list of unique transitions + if ( AddTransitionToList( pLevelList, count, pTrigger->m_szMapName, pTrigger->m_szLandmarkName, pentLandmark ) ) + { + count++; + if ( count >= maxList ) // FULL!! + break; + } + } + } + pentChangelevel = FIND_ENTITY_BY_STRING( pentChangelevel, "classname", "trigger_changelevel" ); + } + + if ( gpGlobals->pSaveData && ((SAVERESTOREDATA *)gpGlobals->pSaveData)->pTable ) + { + CSave saveHelper( (SAVERESTOREDATA *)gpGlobals->pSaveData ); + + for ( i = 0; i < count; i++ ) + { + int j, entityCount = 0; + CBaseEntity *pEntList[ MAX_ENTITY ]; + int entityFlags[ MAX_ENTITY ]; + + // Follow the linked list of entities in the PVS of the transition landmark + edict_t *pent = UTIL_EntitiesInPVS( pLevelList[i].pentLandmark ); + + // Build a list of valid entities in this linked list (we're going to use pent->v.chain again) + while ( !FNullEnt( pent ) ) + { + CBaseEntity *pEntity = CBaseEntity::Instance(pent); + if ( pEntity ) + { +// ALERT( at_console, "Trying %s\n", STRING(pEntity->pev->classname) ); + int caps = pEntity->ObjectCaps(); + if ( !(caps & FCAP_DONT_SAVE) ) + { + int flags = 0; + + // If this entity can be moved or is global, mark it + if ( caps & FCAP_ACROSS_TRANSITION ) + flags |= FENTTABLE_MOVEABLE; + if ( pEntity->pev->globalname && !pEntity->IsDormant() ) + flags |= FENTTABLE_GLOBAL; + if ( flags ) + { + pEntList[ entityCount ] = pEntity; + entityFlags[ entityCount ] = flags; + entityCount++; + if ( entityCount > MAX_ENTITY ) + ALERT( at_error, "Too many entities across a transition!" ); + } +// else +// ALERT( at_console, "Failed %s\n", STRING(pEntity->pev->classname) ); + } +// else +// ALERT( at_console, "DON'T SAVE %s\n", STRING(pEntity->pev->classname) ); + } + pent = pent->v.chain; + } + + for ( j = 0; j < entityCount; j++ ) + { + // Check to make sure the entity isn't screened out by a trigger_transition + if ( entityFlags[j] && InTransitionVolume( pEntList[j], pLevelList[i].landmarkName ) ) + { + // Mark entity table with 1<pev->classname) ); + + } + } + } + + return count; +} + +/* +go to the next level for deathmatch +only called if a time or frag limit has expired +*/ +void NextLevel( void ) +{ + edict_t* pent; + CChangeLevel *pChange; + + // find a trigger_changelevel + pent = FIND_ENTITY_BY_CLASSNAME(NULL, "trigger_changelevel"); + + // go back to start if no trigger_changelevel + if (FNullEnt(pent)) + { + gpGlobals->mapname = ALLOC_STRING("start"); + pChange = GetClassPtr( (CChangeLevel *)NULL ); + strcpy(pChange->m_szMapName, "start"); + } + else + pChange = GetClassPtr( (CChangeLevel *)VARS(pent)); + + strcpy(st_szNextMap, pChange->m_szMapName); + g_fGameOver = TRUE; + + if (pChange->pev->nextthink < gpGlobals->time) + { + pChange->SetThink( &CChangeLevel::ExecuteChangeLevel ); + pChange->pev->nextthink = gpGlobals->time + 0.1; + } +} + + +// ============================== LADDER ======================================= + +class CLadder : public CBaseTrigger +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS( func_ladder, CLadder ); + + +void CLadder :: KeyValue( KeyValueData *pkvd ) +{ + CBaseTrigger::KeyValue( pkvd ); +} + + +//========================================================= +// func_ladder - makes an area vertically negotiable +//========================================================= +void CLadder :: Precache( void ) +{ + // Do all of this in here because we need to 'convert' old saved games + pev->solid = SOLID_NOT; + pev->skin = CONTENTS_LADDER; + if ( CVAR_GET_FLOAT("showtriggers") == 0 ) + { + pev->rendermode = kRenderTransTexture; + pev->renderamt = 0; + } + pev->effects &= ~EF_NODRAW; +} + + +void CLadder :: Spawn( void ) +{ + Precache(); + + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->movetype = MOVETYPE_PUSH; +} + + +// ========================== A TRIGGER THAT PUSHES YOU =============================== + +class CTriggerPush : public CBaseTrigger +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Touch( CBaseEntity *pOther ); +}; +LINK_ENTITY_TO_CLASS( trigger_push, CTriggerPush ); + + +void CTriggerPush :: KeyValue( KeyValueData *pkvd ) +{ + CBaseTrigger::KeyValue( pkvd ); +} + + +/*QUAKED trigger_push (.5 .5 .5) ? TRIG_PUSH_ONCE +Pushes the player +*/ + +void CTriggerPush :: Spawn( ) +{ + if ( pev->angles == g_vecZero ) + pev->angles.y = 360; + InitTrigger(); + + if (pev->speed == 0) + pev->speed = 100; + + if ( FBitSet (pev->spawnflags, SF_TRIGGER_PUSH_START_OFF) )// if flagged to Start Turned Off, make trigger nonsolid. + pev->solid = SOLID_NOT; + + SetUse( &CBaseTrigger::ToggleUse ); + + UTIL_SetOrigin( pev, pev->origin ); // Link into the list +} + + +void CTriggerPush :: Touch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + + // UNDONE: Is there a better way than health to detect things that have physics? (clients/monsters) + switch( pevToucher->movetype ) + { + case MOVETYPE_NONE: + case MOVETYPE_PUSH: + case MOVETYPE_NOCLIP: + case MOVETYPE_FOLLOW: + return; + } + + //Only players + if ( !pOther->IsPlayer() ) + return; + + if ( pevToucher->solid != SOLID_NOT && pevToucher->solid != SOLID_BSP ) + { + // Instant trigger, just transfer velocity and remove + if (FBitSet(pev->spawnflags, SF_TRIG_PUSH_ONCE)) + { + pevToucher->velocity = pevToucher->velocity + (pev->speed * pev->movedir); + if ( pevToucher->velocity.z > 0 ) + pevToucher->flags &= ~FL_ONGROUND; + UTIL_Remove( this ); + } + else + { // Push field, transfer to base velocity + Vector vecPush = (pev->speed * pev->movedir); + if ( pevToucher->flags & FL_BASEVELOCITY ) + vecPush = vecPush + pevToucher->basevelocity; + + pevToucher->basevelocity = vecPush; + + pevToucher->flags |= FL_BASEVELOCITY; +// ALERT( at_console, "Vel %f, base %f\n", pevToucher->velocity.z, pevToucher->basevelocity.z ); + } + } +} + +//======================================================================================== +// TELEPORT TRIGGERS +//======================================================================================== + +//----------------------------------------------------------------------------- +// Purpose: Teleport Death entity. Kills anything it touches +//----------------------------------------------------------------------------- +class CTeleDeath : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT DeathTouch( CBaseEntity *pOther ); +}; +LINK_ENTITY_TO_CLASS( teledeath, CTeleDeath ); + +void CTeleDeath::Spawn( void ) +{ + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_TRIGGER; + pev->angles = g_vecZero; + + // Owner is the player who's spawned this + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + if (!pOwner) + return; + + UTIL_SetSize( pev, pOwner->pev->mins - Vector(1,1,1), pOwner->pev->maxs + Vector(1,1,1) ); + UTIL_SetOrigin( pev, pev->origin ); + + SetTouch( &CTeleDeath::DeathTouch ); + pev->nextthink = gpGlobals->time + 0.2; + SetThink( &CBaseEntity::SUB_Remove ); + + // Touch still players + gpGlobals->force_retouch = 2; +} + +void CTeleDeath::DeathTouch( CBaseEntity *pOther ) +{ + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + + if ( pOther == pOwner ) + return; + + // frag anyone who teleports in on top of an invincible player + if ( pOther->IsPlayer() ) + { + if ( ((CBasePlayer*)pOther)->m_flInvincibleFinished > gpGlobals->time ) + { + // Note: This did not work in Quake. Fixed in QUAKECLASSIC. + if ( pOwner->IsPlayer() ) + { + g_szDeathType = "teledeath2"; + pOwner->TakeDamage( pOwner->pev, pOwner->pev, 1000, DMG_GENERIC | DMG_ALWAYSGIB); + return; + } + } + } + + if ( pOther->pev->health ) + { + g_szDeathType = "teledeath"; + pOther->TakeDamage( pOther->pev, pOwner->pev, 1000, DMG_GENERIC | DMG_ALWAYSGIB); + } +} + +//====================================== +// teleport trigger +// +// QUAKECLASSIC: Different bitflags +#define TELE_PLAYER_ONLY 1 +#define TELE_SILENT 2 + +//----------------------------------------------------------------------------- +// Purpose: Spawn a teleport fog at the vector specified +//----------------------------------------------------------------------------- +void CBaseEntity::Spawn_Telefog( Vector vecOrg, CBaseEntity *pOther ) +{ + //Moved to the client + PLAYBACK_EVENT_FULL ( FEV_GLOBAL | FEV_NOTHOST, pOther->edict(), g_sTeleport, 0.0, (float *)&vecOrg, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0); +} + +//----------------------------------------------------------------------------- +// Purpose: Kill anything at the teleport destination +//----------------------------------------------------------------------------- +void CBaseTrigger :: TeleportTouch( CBaseEntity *pOther ) +{ + // no clients allowed? + if ( ( pev->spawnflags & TELE_PLAYER_ONLY ) ) + { + if ( pOther->IsPlayer() ) + return; + } + + // only teleport living creatures + if (pOther->pev->health <= 0 || pOther->pev->solid != SOLID_SLIDEBOX) + return; + + SUB_UseTargets( this, USE_TOGGLE, 0 ); + + // put a tfog where the player was + Spawn_Telefog(pOther->pev->origin, pOther ); + + edict_t *pentTarget = NULL; + pentTarget = FIND_ENTITY_BY_TARGETNAME( pentTarget, STRING(pev->target) ); + if (FNullEnt(pentTarget)) + return; + + // spawn a tfog flash in front of the destination + CBaseEntity *pTarget = CBaseEntity::Instance( pentTarget ); + UTIL_MakeVectors(pTarget->m_vecTeleAngles); + Vector vecNewOrg = pTarget->pev->origin + (gpGlobals->v_forward * 32); + pTarget->Spawn_Telefog( vecNewOrg, pOther ); + + // Spawn teledeath at destination + CTeleDeath *pDeath = GetClassPtr( (CTeleDeath *)NULL ); + pDeath->pev->owner = pOther->edict(); + pDeath->pev->origin = pTarget->pev->origin; + pDeath->Spawn(); + + // May be killed by teleporting onto an invulnerable player + if (!pOther->pev->health) + { + pOther->pev->origin = pTarget->pev->origin; + pOther->pev->velocity = (gpGlobals->v_forward * pOther->pev->velocity.x) + (gpGlobals->v_forward * pOther->pev->velocity.y); + return; + } + + // Move the player + UTIL_SetOrigin( pOther->pev, pTarget->pev->origin ); + pOther->pev->angles = pTarget->m_vecTeleAngles; + + if (pOther->IsPlayer()) + { + // pOther->pev->fixangle = 1; // turn this way immediately + + //Err, why is this here? + pOther->pev->fuser4 = gpGlobals->time + 0.7; + pOther->pev->velocity = gpGlobals->v_forward * 300; + } + pOther->pev->flags &= ~FL_ONGROUND; +} + + +class CTriggerTeleport : public CBaseTrigger +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS( trigger_teleport, CTriggerTeleport ); + +void CTriggerTeleport :: Spawn( void ) +{ + InitTrigger(); + + SetTouch( &CBaseTrigger::TeleportTouch ); + + g_vecTeleMins[ g_iTeleNum ] = pev->absmin; + g_vecTeleMaxs[ g_iTeleNum ] = pev->absmax; + + g_iTeleNum++; + + if (!(pev->spawnflags & TELE_SILENT)) + { + PRECACHE_SOUND("ambience/hum1.wav"); + UTIL_EmitAmbientSound(ENT(pev), (pev->mins + pev->maxs) * 0.5, "ambience/hum1.wav", 0.5, ATTN_STATIC, 0, 100); + } +} + + +class CTriggerTeleportDest : public CBaseTrigger +{ +public: + void Spawn( void ); +}; + +void CTriggerTeleportDest :: Spawn( void ) +{ + m_vecTeleAngles = pev->angles; + pev->angles = g_vecZero; + pev->origin.z += 27; + UTIL_SetOrigin( pev, pev->origin ); +} + +LINK_ENTITY_TO_CLASS( info_teleport_destination, CTriggerTeleportDest ); + +//========================================================================== +class CTriggerSave : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT SaveTouch( CBaseEntity *pOther ); +}; +LINK_ENTITY_TO_CLASS( trigger_autosave, CTriggerSave ); + +void CTriggerSave::Spawn( void ) +{ + if ( g_pGameRules->IsDeathmatch() ) + { + REMOVE_ENTITY( ENT(pev) ); + return; + } + + InitTrigger(); + SetTouch( &CTriggerSave::SaveTouch ); +} + +void CTriggerSave::SaveTouch( CBaseEntity *pOther ) +{ + if ( !UTIL_IsMasterTriggered( m_sMaster, pOther ) ) + return; + + // Only save on clients + if ( !pOther->IsPlayer() ) + return; + + SetTouch( NULL ); + UTIL_Remove( this ); + SERVER_COMMAND( "autosave\n" ); +} + +#define SF_ENDSECTION_USEONLY 0x0001 + +class CTriggerEndSection : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT EndSectionTouch( CBaseEntity *pOther ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT EndSectionUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; +LINK_ENTITY_TO_CLASS( trigger_endsection, CTriggerEndSection ); + + +void CTriggerEndSection::EndSectionUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Only save on clients + if ( !pActivator->IsNetClient() ) + return; + + SetUse( NULL ); + + if ( pev->message ) + { + g_engfuncs.pfnEndSection(STRING(pev->message)); + } + UTIL_Remove( this ); +} + +void CTriggerEndSection::Spawn( void ) +{ + if ( g_pGameRules->IsDeathmatch() ) + { + REMOVE_ENTITY( ENT(pev) ); + return; + } + + InitTrigger(); + + SetUse ( &CTriggerEndSection::EndSectionUse ); + // If it is a "use only" trigger, then don't set the touch function. + if ( ! (pev->spawnflags & SF_ENDSECTION_USEONLY) ) + SetTouch( &CTriggerEndSection::EndSectionTouch ); +} + +void CTriggerEndSection::EndSectionTouch( CBaseEntity *pOther ) +{ + // Only save on clients + if ( !pOther->IsNetClient() ) + return; + + SetTouch( NULL ); + + if (pev->message) + { + g_engfuncs.pfnEndSection(STRING(pev->message)); + } + UTIL_Remove( this ); +} + +void CTriggerEndSection :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "section")) + { +// m_iszSectionName = ALLOC_STRING( pkvd->szValue ); + // Store this in message so we don't have to write save/restore for this ent + pev->message = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseTrigger::KeyValue( pkvd ); +} + + +class CTriggerGravity : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT GravityTouch( CBaseEntity *pOther ); +}; +LINK_ENTITY_TO_CLASS( trigger_gravity, CTriggerGravity ); + +void CTriggerGravity::Spawn( void ) +{ + InitTrigger(); + SetTouch( &CTriggerGravity::GravityTouch ); +} + +void CTriggerGravity::GravityTouch( CBaseEntity *pOther ) +{ + // Only save on clients + if ( !pOther->IsPlayer() ) + return; + + pOther->pev->gravity = pev->gravity; +} + + + + + + + +// this is a really bad idea. +class CTriggerChangeTarget : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_iszNewTarget; +}; +LINK_ENTITY_TO_CLASS( trigger_changetarget, CTriggerChangeTarget ); + +TYPEDESCRIPTION CTriggerChangeTarget::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerChangeTarget, m_iszNewTarget, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerChangeTarget,CBaseDelay); + +void CTriggerChangeTarget::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszNewTarget")) + { + m_iszNewTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + +void CTriggerChangeTarget::Spawn( void ) +{ +} + + +void CTriggerChangeTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBaseEntity *pTarget = UTIL_FindEntityByString( NULL, "targetname", STRING( pev->target ) ); + + if (pTarget) + { + pTarget->pev->target = m_iszNewTarget; + CBaseMonster *pMonster = pTarget->MyMonsterPointer( ); + if (pMonster) + { + pMonster->m_pGoalEnt = NULL; + } + } +} + + + + +#define SF_CAMERA_PLAYER_POSITION 1 +#define SF_CAMERA_PLAYER_TARGET 2 +#define SF_CAMERA_PLAYER_TAKECONTROL 4 + +class CTriggerCamera : public CBaseDelay +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT FollowTarget( void ); + void Move(void); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + static TYPEDESCRIPTION m_SaveData[]; + + EHANDLE m_hPlayer; + EHANDLE m_hTarget; + CBaseEntity *m_pentPath; + int m_sPath; + float m_flWait; + float m_flReturnTime; + float m_flStopTime; + float m_moveDistance; + float m_targetSpeed; + float m_initialSpeed; + float m_acceleration; + float m_deceleration; + int m_state; + +}; +LINK_ENTITY_TO_CLASS( trigger_camera, CTriggerCamera ); + +// Global Savedata for changelevel friction modifier +TYPEDESCRIPTION CTriggerCamera::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerCamera, m_hPlayer, FIELD_EHANDLE ), + DEFINE_FIELD( CTriggerCamera, m_hTarget, FIELD_EHANDLE ), + DEFINE_FIELD( CTriggerCamera, m_pentPath, FIELD_CLASSPTR ), + DEFINE_FIELD( CTriggerCamera, m_sPath, FIELD_STRING ), + DEFINE_FIELD( CTriggerCamera, m_flWait, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_flReturnTime, FIELD_TIME ), + DEFINE_FIELD( CTriggerCamera, m_flStopTime, FIELD_TIME ), + DEFINE_FIELD( CTriggerCamera, m_moveDistance, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_targetSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_initialSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_acceleration, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_deceleration, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_state, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerCamera,CBaseDelay); + +void CTriggerCamera::Spawn( void ) +{ + pev->movetype = MOVETYPE_NOCLIP; + pev->solid = SOLID_NOT; // Remove model & collisions + pev->renderamt = 0; // The engine won't draw this model if this is set to 0 and blending is on + pev->rendermode = kRenderTransTexture; + + m_initialSpeed = pev->speed; + if ( m_acceleration == 0 ) + m_acceleration = 500; + if ( m_deceleration == 0 ) + m_deceleration = 500; +} + + +void CTriggerCamera :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "moveto")) + { + m_sPath = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "acceleration")) + { + m_acceleration = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "deceleration")) + { + m_deceleration = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + + + +void CTriggerCamera::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_state ) ) + return; + + // Toggle state + m_state = !m_state; + if (m_state == 0) + { + m_flReturnTime = gpGlobals->time; + return; + } + if ( !pActivator || !pActivator->IsPlayer() ) + { + pActivator = CBaseEntity::Instance(g_engfuncs.pfnPEntityOfEntIndex( 1 )); + } + + m_hPlayer = pActivator; + + m_flReturnTime = gpGlobals->time + m_flWait; + pev->speed = m_initialSpeed; + m_targetSpeed = m_initialSpeed; + + if (FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_TARGET ) ) + { + m_hTarget = m_hPlayer; + } + else + { + m_hTarget = GetNextTarget(); + } + + // Nothing to look at! + if ( m_hTarget == 0 ) + { + return; + } + + + if (FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_TAKECONTROL ) ) + { + ((CBasePlayer *)pActivator)->EnableControl(FALSE); + } + + if ( m_sPath ) + { + m_pentPath = Instance( FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(m_sPath)) ); + } + else + { + m_pentPath = NULL; + } + + m_flStopTime = gpGlobals->time; + if ( m_pentPath ) + { + if ( m_pentPath->pev->speed != 0 ) + m_targetSpeed = m_pentPath->pev->speed; + + m_flStopTime += m_pentPath->GetDelay(); + } + + // copy over player information + if (FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_POSITION ) ) + { + UTIL_SetOrigin( pev, pActivator->pev->origin + pActivator->pev->view_ofs ); + pev->angles.x = -pActivator->pev->angles.x; + pev->angles.y = pActivator->pev->angles.y; + pev->angles.z = 0; + pev->velocity = pActivator->pev->velocity; + } + else + { + pev->velocity = Vector( 0, 0, 0 ); + } + + SET_VIEW( pActivator->edict(), edict() ); + + SET_MODEL(ENT(pev), STRING(pActivator->pev->model) ); + + // follow the player down + SetThink( &CTriggerCamera::FollowTarget ); + pev->nextthink = gpGlobals->time; + + m_moveDistance = 0; + Move(); +} + + +void CTriggerCamera::FollowTarget( ) +{ + if (m_hPlayer == 0) + return; + + if (m_hTarget == 0 || m_flReturnTime < gpGlobals->time) + { + if (m_hPlayer->IsAlive( )) + { + SET_VIEW( m_hPlayer->edict(), m_hPlayer->edict() ); + ((CBasePlayer *)((CBaseEntity *)m_hPlayer))->EnableControl(TRUE); + } + SUB_UseTargets( this, USE_TOGGLE, 0 ); + pev->avelocity = Vector( 0, 0, 0 ); + m_state = 0; + return; + } + + Vector vecGoal = UTIL_VecToAngles( m_hTarget->pev->origin - pev->origin ); + vecGoal.x = -vecGoal.x; + + if (pev->angles.y > 360) + pev->angles.y -= 360; + + if (pev->angles.y < 0) + pev->angles.y += 360; + + float dx = vecGoal.x - pev->angles.x; + float dy = vecGoal.y - pev->angles.y; + + if (dx < -180) + dx += 360; + if (dx > 180) + dx = dx - 360; + + if (dy < -180) + dy += 360; + if (dy > 180) + dy = dy - 360; + + pev->avelocity.x = dx * 40 * gpGlobals->frametime; + pev->avelocity.y = dy * 40 * gpGlobals->frametime; + + + if (!(FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_TAKECONTROL))) + { + pev->velocity = pev->velocity * 0.8; + if (pev->velocity.Length( ) < 10.0) + pev->velocity = g_vecZero; + } + + pev->nextthink = gpGlobals->time; + + Move(); +} + +void CTriggerCamera::Move() +{ + // Not moving on a path, return + if (!m_pentPath) + return; + + // Subtract movement from the previous frame + m_moveDistance -= pev->speed * gpGlobals->frametime; + + // Have we moved enough to reach the target? + if ( m_moveDistance <= 0 ) + { + // Fire the passtarget if there is one + if ( m_pentPath->pev->message ) + { + FireTargets( STRING(m_pentPath->pev->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( m_pentPath->pev->spawnflags, SF_CORNER_FIREONCE ) ) + m_pentPath->pev->message = 0; + } + // Time to go to the next target + m_pentPath = m_pentPath->GetNextTarget(); + + // Set up next corner + if ( !m_pentPath ) + { + pev->velocity = g_vecZero; + } + else + { + if ( m_pentPath->pev->speed != 0 ) + m_targetSpeed = m_pentPath->pev->speed; + + Vector delta = m_pentPath->pev->origin - pev->origin; + m_moveDistance = delta.Length(); + pev->movedir = delta.Normalize(); + m_flStopTime = gpGlobals->time + m_pentPath->GetDelay(); + } + } + + if ( m_flStopTime > gpGlobals->time ) + pev->speed = UTIL_Approach( 0, pev->speed, m_deceleration * gpGlobals->frametime ); + else + pev->speed = UTIL_Approach( m_targetSpeed, pev->speed, m_acceleration * gpGlobals->frametime ); + + float fraction = 2 * gpGlobals->frametime; + pev->velocity = ((pev->movedir * pev->speed) * fraction) + (pev->velocity * (1-fraction)); +} + +void CClientFog :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "startdist")) + { + m_iStartDist = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "enddist")) + { + m_iEndDist = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CClientFog :: Spawn ( void ) +{ + pev->movetype = MOVETYPE_NOCLIP; + pev->solid = SOLID_NOT; // Remove model & collisions + pev->renderamt = 0; // The engine won't draw this model if this is set to 0 and blending is on + pev->rendermode = kRenderTransTexture; +} + +LINK_ENTITY_TO_CLASS( env_fog, CClientFog ); diff --git a/hlsdk/dmc/dlls/util.cpp b/hlsdk/dmc/dlls/util.cpp new file mode 100644 index 00000000..442bd76f --- /dev/null +++ b/hlsdk/dmc/dlls/util.cpp @@ -0,0 +1,2715 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== util.cpp ======================================================== + + Utility code. Really not optional after all. + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include +#include "../engine/shake.h" +#include "decals.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" + +/* +===================== +UTIL_WeaponTimeBase + +Time basis for weapons ( zero based of predicting client weapons ) +===================== +*/ +float UTIL_WeaponTimeBase( void ) +{ + return 0.0; +} + +static unsigned int glSeed = 0; + +unsigned int seed_table[ 256 ] = +{ + 28985, 27138, 26457, 9451, 17764, 10909, 28790, 8716, 6361, 4853, 17798, 21977, 19643, 20662, 10834, 20103, + 27067, 28634, 18623, 25849, 8576, 26234, 23887, 18228, 32587, 4836, 3306, 1811, 3035, 24559, 18399, 315, + 26766, 907, 24102, 12370, 9674, 2972, 10472, 16492, 22683, 11529, 27968, 30406, 13213, 2319, 23620, 16823, + 10013, 23772, 21567, 1251, 19579, 20313, 18241, 30130, 8402, 20807, 27354, 7169, 21211, 17293, 5410, 19223, + 10255, 22480, 27388, 9946, 15628, 24389, 17308, 2370, 9530, 31683, 25927, 23567, 11694, 26397, 32602, 15031, + 18255, 17582, 1422, 28835, 23607, 12597, 20602, 10138, 5212, 1252, 10074, 23166, 19823, 31667, 5902, 24630, + 18948, 14330, 14950, 8939, 23540, 21311, 22428, 22391, 3583, 29004, 30498, 18714, 4278, 2437, 22430, 3439, + 28313, 23161, 25396, 13471, 19324, 15287, 2563, 18901, 13103, 16867, 9714, 14322, 15197, 26889, 19372, 26241, + 31925, 14640, 11497, 8941, 10056, 6451, 28656, 10737, 13874, 17356, 8281, 25937, 1661, 4850, 7448, 12744, + 21826, 5477, 10167, 16705, 26897, 8839, 30947, 27978, 27283, 24685, 32298, 3525, 12398, 28726, 9475, 10208, + 617, 13467, 22287, 2376, 6097, 26312, 2974, 9114, 21787, 28010, 4725, 15387, 3274, 10762, 31695, 17320, + 18324, 12441, 16801, 27376, 22464, 7500, 5666, 18144, 15314, 31914, 31627, 6495, 5226, 31203, 2331, 4668, + 12650, 18275, 351, 7268, 31319, 30119, 7600, 2905, 13826, 11343, 13053, 15583, 30055, 31093, 5067, 761, + 9685, 11070, 21369, 27155, 3663, 26542, 20169, 12161, 15411, 30401, 7580, 31784, 8985, 29367, 20989, 14203, + 29694, 21167, 10337, 1706, 28578, 887, 3373, 19477, 14382, 675, 7033, 15111, 26138, 12252, 30996, 21409, + 25678, 18555, 13256, 23316, 22407, 16727, 991, 9236, 5373, 29402, 6117, 15241, 27715, 19291, 19888, 19847 +}; + +unsigned int U_Random( void ) +{ + glSeed *= 69069; + glSeed += seed_table[ glSeed & 0xff ]; + + return ( ++glSeed & 0x0fffffff ); +} + +void U_Srand( unsigned int seed ) +{ + glSeed = seed_table[ seed & 0xff ]; +} + +/* +===================== +UTIL_SharedRandomLong +===================== +*/ +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ) +{ + + unsigned int range; + + U_Srand( (int)seed + low + high ); + + range = high - low + 1; + if ( !(range - 1) ) + { + return low; + } + else + { + int offset; + int rnum; + + rnum = U_Random(); + + offset = rnum % range; + + return (low + offset); + } +} + +/* +===================== +UTIL_SharedRandomFloat +===================== +*/ +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ) +{ + // + unsigned int range; + + U_Srand( (int)seed + *(int *)&low + *(int *)&high ); + + U_Random(); + U_Random(); + + range = static_cast(high - low); + if ( !range ) + { + return low; + } + else + { + int tensixrand; + float offset; + + tensixrand = U_Random() & 65535; + + offset = (float)tensixrand / 65536.0; + + return (low + offset * range ); + } +} + +void UTIL_ParametricRocket( entvars_t *pev, Vector vecOrigin, Vector vecAngles, edict_t *owner ) +{ + pev->startpos = vecOrigin; + // Trace out line to end pos + TraceResult tr; + UTIL_MakeVectors( vecAngles ); + UTIL_TraceLine( pev->startpos, pev->startpos + gpGlobals->v_forward * 8192, ignore_monsters, owner, &tr); + pev->endpos = tr.vecEndPos; + + // Now compute how long it will take based on current velocity + Vector vecTravel = pev->endpos - pev->startpos; + float travelTime = 0.0; + if ( pev->velocity.Length() > 0 ) + { + travelTime = vecTravel.Length() / pev->velocity.Length(); + } + pev->starttime = gpGlobals->time; + pev->impacttime = gpGlobals->time + travelTime; +} + +void UTIL_ClientProjectile( const Vector &vecOrigin, const Vector &vecVelocity, short sModelIndex, int iOwnerIndex, int iLife ) +{ + // we'd like to just send this projectile to a person in the shooter's PAS. However + // the projectile won't be sent to a player outside of water if shot from inside water + // and vice-versa, so we do a trace here to figure out if the trace starts or stops in water. + // if it crosses contents, we'll just broadcast the projectile. Otherwise, just send to PVS + // of the trace's endpoint. + BOOL fBroadcast; + + fBroadcast = FALSE; // assume we're just gonna send this message to PVS. + + TraceResult tr; + Vector vecTraceDir; + + vecTraceDir = vecVelocity.Normalize(); + + UTIL_TraceLine( vecOrigin, vecOrigin + vecTraceDir * 4096, ignore_monsters, ENT(iOwnerIndex), &tr ); + + if ( UTIL_PointContents( vecOrigin ) != UTIL_PointContents( tr.vecEndPos ) ) + { + fBroadcast = TRUE; + } + + if ( fBroadcast ) + { + // The projectile is going to cross content types + // (which will block PVS/PAS). Send to every client + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + } + else + { + // just the PVS of where the projectile will hit. + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, tr.vecEndPos ); + } + WRITE_BYTE( TE_PROJECTILE ); + WRITE_COORD( vecOrigin.x ); + WRITE_COORD( vecOrigin.y ); + WRITE_COORD( vecOrigin.z ); + + WRITE_COORD( vecVelocity.x ); + WRITE_COORD( vecVelocity.y ); + WRITE_COORD( vecVelocity.z ); + + WRITE_SHORT( sModelIndex ); + WRITE_BYTE ( iLife ); + WRITE_BYTE ( (BYTE)iOwnerIndex ); + MESSAGE_END(); +} + +int g_groupmask = 0; +int g_groupop = 0; + +// Normal overrides +void UTIL_SetGroupTrace( int groupmask, int op ) +{ + g_groupmask = groupmask; + g_groupop = op; + + ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); +} + +void UTIL_UnsetGroupTrace( void ) +{ + g_groupmask = 0; + g_groupop = 0; + + ENGINE_SETGROUPMASK( 0, 0 ); +} + +// Smart version, it'll clean itself up when it pops off stack +UTIL_GroupTrace::UTIL_GroupTrace( int groupmask, int op ) +{ + m_oldgroupmask = g_groupmask; + m_oldgroupop = g_groupop; + + g_groupmask = groupmask; + g_groupop = op; + + ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); +} + +UTIL_GroupTrace::~UTIL_GroupTrace( void ) +{ + g_groupmask = m_oldgroupmask; + g_groupop = m_oldgroupop; + + ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); +} + +TYPEDESCRIPTION gEntvarsDescription[] = +{ + DEFINE_ENTITY_FIELD( classname, FIELD_STRING ), + DEFINE_ENTITY_GLOBAL_FIELD( globalname, FIELD_STRING ), + + DEFINE_ENTITY_FIELD( origin, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_FIELD( oldorigin, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_FIELD( velocity, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( basevelocity, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( movedir, FIELD_VECTOR ), + + DEFINE_ENTITY_FIELD( angles, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( avelocity, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( punchangle, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( v_angle, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( fixangle, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( idealpitch, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( pitch_speed, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( ideal_yaw, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( yaw_speed, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( modelindex, FIELD_INTEGER ), + DEFINE_ENTITY_GLOBAL_FIELD( model, FIELD_MODELNAME ), + + DEFINE_ENTITY_FIELD( viewmodel, FIELD_MODELNAME ), + DEFINE_ENTITY_FIELD( weaponmodel, FIELD_MODELNAME ), + + DEFINE_ENTITY_FIELD( absmin, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_FIELD( absmax, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_GLOBAL_FIELD( mins, FIELD_VECTOR ), + DEFINE_ENTITY_GLOBAL_FIELD( maxs, FIELD_VECTOR ), + DEFINE_ENTITY_GLOBAL_FIELD( size, FIELD_VECTOR ), + + DEFINE_ENTITY_FIELD( ltime, FIELD_TIME ), + DEFINE_ENTITY_FIELD( nextthink, FIELD_TIME ), + + DEFINE_ENTITY_FIELD( solid, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( movetype, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( skin, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( body, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( effects, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( gravity, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( friction, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( light_level, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( frame, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( scale, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( sequence, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( animtime, FIELD_TIME ), + DEFINE_ENTITY_FIELD( framerate, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( controller, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( blending, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( rendermode, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( renderamt, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( rendercolor, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( renderfx, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( health, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( frags, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( weapons, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( takedamage, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( deadflag, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( view_ofs, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( button, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( impulse, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( chain, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( dmg_inflictor, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( enemy, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( aiment, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( owner, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( groundentity, FIELD_EDICT ), + + DEFINE_ENTITY_FIELD( spawnflags, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( flags, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( colormap, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( team, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( max_health, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( teleport_time, FIELD_TIME ), + DEFINE_ENTITY_FIELD( armortype, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( armorvalue, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( waterlevel, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( watertype, FIELD_INTEGER ), + + // Having these fields be local to the individual levels makes it easier to test those levels individually. + DEFINE_ENTITY_GLOBAL_FIELD( target, FIELD_STRING ), + DEFINE_ENTITY_GLOBAL_FIELD( targetname, FIELD_STRING ), + DEFINE_ENTITY_FIELD( netname, FIELD_STRING ), + DEFINE_ENTITY_FIELD( message, FIELD_STRING ), + + DEFINE_ENTITY_FIELD( dmg_take, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( dmg_save, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( dmg, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( dmgtime, FIELD_TIME ), + + DEFINE_ENTITY_FIELD( noise, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( noise1, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( noise2, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( noise3, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( speed, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( air_finished, FIELD_TIME ), + DEFINE_ENTITY_FIELD( pain_finished, FIELD_TIME ), + DEFINE_ENTITY_FIELD( radsuit_finished, FIELD_TIME ), +}; + +#define ENTVARS_COUNT (sizeof(gEntvarsDescription)/sizeof(gEntvarsDescription[0])) + + +#ifdef DEBUG +edict_t *DBG_EntOfVars( const entvars_t *pev ) +{ + if (pev->pContainingEntity != NULL) + return pev->pContainingEntity; + ALERT(at_console, "entvars_t pContainingEntity is NULL, calling into engine"); + edict_t* pent = (*g_engfuncs.pfnFindEntityByVars)((entvars_t*)pev); + if (pent == NULL) + ALERT(at_console, "DAMN! Even the engine couldn't FindEntityByVars!"); + ((entvars_t *)pev)->pContainingEntity = pent; + return pent; +} +#endif //DEBUG + + +#ifdef DEBUG + void +DBG_AssertFunction( + BOOL fExpr, + const char* szExpr, + const char* szFile, + int szLine, + const char* szMessage) + { + if (fExpr) + return; + char szOut[512]; + if (szMessage != NULL) + sprintf(szOut, "ASSERT FAILED:\n %s \n(%s@%d)\n%s", szExpr, szFile, szLine, szMessage); + else + sprintf(szOut, "ASSERT FAILED:\n %s \n(%s@%d)", szExpr, szFile, szLine); + ALERT(at_console, szOut); + } +#endif // DEBUG + +BOOL UTIL_GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) +{ + return g_pGameRules->GetNextBestWeapon( pPlayer, pCurrentWeapon ); +} + +// ripped this out of the engine +float UTIL_AngleMod(float a) +{ + if (a < 0) + { + a = a + 360 * ((int)(a / 360) + 1); + } + else if (a >= 360) + { + a = a - 360 * ((int)(a / 360)); + } + // a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); + return a; +} + +float UTIL_AngleDiff( float destAngle, float srcAngle ) +{ + float delta; + + delta = destAngle - srcAngle; + if ( destAngle > srcAngle ) + { + if ( delta >= 180 ) + delta -= 360; + } + else + { + if ( delta <= -180 ) + delta += 360; + } + return delta; +} + + +void EffectPrint( CBasePlayer *pPlayer, int color, int effect, int channel, char *text ) +{ + hudtextparms_t m_TesParms; + + memset(&m_TesParms, 0, sizeof(m_TesParms)); + + if (channel == WIN_MSG) + { + m_TesParms.x = -1; + m_TesParms.y = 0.4; + } + + if (channel == NOTIFY) + { + m_TesParms.x = -1; + m_TesParms.y = 0.9; + } + + else if (channel == INFO) + { + m_TesParms.x = 0; + m_TesParms.y = 0.85; + } + else if (channel == LEADER_HIT) + { + m_TesParms.x = -1; + m_TesParms.y = 1.0; + } + else if (channel == CRITICAL) + { + m_TesParms.x = -1; + m_TesParms.y = 0.7; + } + + else if (channel == MISC_SHIT) + { + m_TesParms.x = -1; + m_TesParms.y = 0.25; + } + else if (channel == CHASECAM) + { + m_TesParms.x = 0.0; + m_TesParms.y = 0.3; + } + else if (channel == CHASECAM_TARGET) + { + m_TesParms.x = 0.09; + m_TesParms.y = 0.3; + } + + m_TesParms.channel = channel; + + /* + effect = F_IN_OUT - FADE IN / FADE OUT + effect = CREDITS - CREDITS + effect = SCANOUT - SCAN OUT + */ + + m_TesParms.effect = effect; + + + if (color == BLUE) // Blue Color + { + m_TesParms.r1 = 0; + m_TesParms.g1 = 0; + m_TesParms.b1 = 255; + m_TesParms.a1 = 0; + } + else if (color == RED) // Red Color + { + m_TesParms.r1 = 255; + m_TesParms.g1 = 0; + m_TesParms.b1 = 0; + m_TesParms.a1 = 0; + } + else if (color == WHITE) //Whitey Color + { + m_TesParms.r1 = 255; + m_TesParms.g1 = 255; + m_TesParms.b1 = 200; + } + + //This is the second color in the effect, this is white for now... + m_TesParms.r2 = 255; + m_TesParms.g2 = 255; + m_TesParms.b2 = 255; + m_TesParms.a2 = 0; + + //Fade In Time + if (effect == SCANOUT) + m_TesParms.fadeinTime = 0.01; + else + m_TesParms.fadeinTime = 0.3; + + //Fade Out Time + + m_TesParms.fadeoutTime = 0.3; + + //Time the Effect is going to be up + if (effect == F_IN_OUT) + m_TesParms.holdTime = 1.5; + + else + m_TesParms.holdTime = 3.5; + + //Time the effect is aplied (?) + m_TesParms.fxTime = 0.25; + + if (pPlayer == NULL) + UTIL_HudMessageAll( m_TesParms, text ); + else + UTIL_HudMessage(pPlayer, m_TesParms, text ); + + +} + +Vector UTIL_VecToAngles( const Vector &vec ) +{ + float rgflVecOut[3]; + VEC_TO_ANGLES(vec, rgflVecOut); + return Vector(rgflVecOut); +} + +// float UTIL_MoveToOrigin( edict_t *pent, const Vector vecGoal, float flDist, int iMoveType ) +void UTIL_MoveToOrigin( edict_t *pent, const Vector &vecGoal, float flDist, int iMoveType ) +{ + float rgfl[3]; + vecGoal.CopyToArray(rgfl); +// return MOVE_TO_ORIGIN ( pent, rgfl, flDist, iMoveType ); + MOVE_TO_ORIGIN ( pent, rgfl, flDist, iMoveType ); +} + + +int UTIL_EntitiesInBox( CBaseEntity **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + int count; + + count = 0; + + if ( !pEdict ) + return count; + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + if ( pEdict->free ) // Not in use + continue; + + if ( flagMask && !(pEdict->v.flags & flagMask) ) // Does it meet the criteria? + continue; + + if ( mins.x > pEdict->v.absmax.x || + mins.y > pEdict->v.absmax.y || + mins.z > pEdict->v.absmax.z || + maxs.x < pEdict->v.absmin.x || + maxs.y < pEdict->v.absmin.y || + maxs.z < pEdict->v.absmin.z ) + continue; + + pEntity = CBaseEntity::Instance(pEdict); + if ( !pEntity ) + continue; + + pList[ count ] = pEntity; + count++; + + if ( count >= listMax ) + return count; + } + + return count; +} + + +int UTIL_MonstersInSphere( CBaseEntity **pList, int listMax, const Vector ¢er, float radius ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + int count; + float distance, delta; + + count = 0; + float radiusSquared = radius * radius; + + if ( !pEdict ) + return count; + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + if ( pEdict->free ) // Not in use + continue; + + if ( !(pEdict->v.flags & (FL_CLIENT|FL_MONSTER)) ) // Not a client/monster ? + continue; + + // Use origin for X & Y since they are centered for all monsters + // Now X + delta = center.x - pEdict->v.origin.x;//(pEdict->v.absmin.x + pEdict->v.absmax.x)*0.5; + delta *= delta; + + if ( delta > radiusSquared ) + continue; + distance = delta; + + // Now Y + delta = center.y - pEdict->v.origin.y;//(pEdict->v.absmin.y + pEdict->v.absmax.y)*0.5; + delta *= delta; + + distance += delta; + if ( distance > radiusSquared ) + continue; + + // Now Z + delta = center.z - (pEdict->v.absmin.z + pEdict->v.absmax.z)*0.5; + delta *= delta; + + distance += delta; + if ( distance > radiusSquared ) + continue; + + pEntity = CBaseEntity::Instance(pEdict); + if ( !pEntity ) + continue; + + pList[ count ] = pEntity; + count++; + + if ( count >= listMax ) + return count; + } + + + return count; +} + + +CBaseEntity *UTIL_FindEntityInSphere( CBaseEntity *pStartEntity, const Vector &vecCenter, float flRadius ) +{ + edict_t *pentEntity; + + if (pStartEntity) + pentEntity = pStartEntity->edict(); + else + pentEntity = NULL; + + pentEntity = FIND_ENTITY_IN_SPHERE( pentEntity, vecCenter, flRadius); + + if (!FNullEnt(pentEntity)) + return CBaseEntity::Instance(pentEntity); + return NULL; +} + + +CBaseEntity *UTIL_FindEntityByString( CBaseEntity *pStartEntity, const char *szKeyword, const char *szValue ) +{ + edict_t *pentEntity; + + if (pStartEntity) + pentEntity = pStartEntity->edict(); + else + pentEntity = NULL; + + pentEntity = FIND_ENTITY_BY_STRING( pentEntity, szKeyword, szValue ); + + if (!FNullEnt(pentEntity)) + return CBaseEntity::Instance(pentEntity); + return NULL; +} + +CBaseEntity *UTIL_FindEntityByClassname( CBaseEntity *pStartEntity, const char *szName ) +{ + return UTIL_FindEntityByString( pStartEntity, "classname", szName ); +} + +CBaseEntity *UTIL_FindEntityByTargetname( CBaseEntity *pStartEntity, const char *szName ) +{ + return UTIL_FindEntityByString( pStartEntity, "targetname", szName ); +} + + +CBaseEntity *UTIL_FindEntityGeneric( const char *szWhatever, Vector &vecSrc, float flRadius ) +{ + CBaseEntity *pEntity = NULL; + + pEntity = UTIL_FindEntityByTargetname( NULL, szWhatever ); + if (pEntity) + return pEntity; + + CBaseEntity *pSearch = NULL; + float flMaxDist2 = flRadius * flRadius; + while ((pSearch = UTIL_FindEntityByClassname( pSearch, szWhatever )) != NULL) + { + float flDist2 = (pSearch->pev->origin - vecSrc).Length(); + flDist2 = flDist2 * flDist2; + if (flMaxDist2 > flDist2) + { + pEntity = pSearch; + flMaxDist2 = flDist2; + } + } + return pEntity; +} + + +// returns a CBaseEntity pointer to a player by index. Only returns if the player is spawned and connected +// otherwise returns NULL +// Index is 1 based +CBaseEntity *UTIL_PlayerByIndex( int playerIndex ) +{ + CBaseEntity *pPlayer = NULL; + + if ( playerIndex > 0 && playerIndex <= gpGlobals->maxClients ) + { + edict_t *pPlayerEdict = INDEXENT( playerIndex ); + if ( pPlayerEdict && !pPlayerEdict->free ) + { + pPlayer = CBaseEntity::Instance( pPlayerEdict ); + } + } + + return pPlayer; +} + + +void UTIL_MakeVectors( const Vector &vecAngles ) +{ + MAKE_VECTORS( vecAngles ); +} + + +void UTIL_MakeAimVectors( const Vector &vecAngles ) +{ + float rgflVec[3]; + vecAngles.CopyToArray(rgflVec); + rgflVec[0] = -rgflVec[0]; + MAKE_VECTORS(rgflVec); +} + + +#define SWAP(a,b,temp) ((temp)=(a),(a)=(b),(b)=(temp)) + +void UTIL_MakeInvVectors( const Vector &vec, globalvars_t *pgv ) +{ + MAKE_VECTORS(vec); + + float tmp; + pgv->v_right = pgv->v_right * -1; + + SWAP(pgv->v_forward.y, pgv->v_right.x, tmp); + SWAP(pgv->v_forward.z, pgv->v_up.x, tmp); + SWAP(pgv->v_right.z, pgv->v_up.y, tmp); +} + + +void UTIL_EmitAmbientSound( edict_t *entity, const Vector &vecOrigin, const char *samp, float vol, float attenuation, int fFlags, int pitch ) +{ + float rgfl[3]; + vecOrigin.CopyToArray(rgfl); + + if (samp && *samp == '!') + { + char name[32]; + if (SENTENCEG_Lookup(samp, name) >= 0) + EMIT_AMBIENT_SOUND(entity, rgfl, name, vol, attenuation, fFlags, pitch); + } + else + EMIT_AMBIENT_SOUND(entity, rgfl, samp, vol, attenuation, fFlags, pitch); +} + +static unsigned short FixedUnsigned16( float value, float scale ) +{ + int output; + + output = static_cast(value * scale); + if ( output < 0 ) + output = 0; + if ( output > 0xFFFF ) + output = 0xFFFF; + + return (unsigned short)output; +} + +static short FixedSigned16( float value, float scale ) +{ + int output; + + output = static_cast(value * scale); + + if ( output > 32767 ) + output = 32767; + + if ( output < -32768 ) + output = -32768; + + return (short)output; +} + +// Shake the screen of all clients within radius +// radius == 0, shake all clients +// UNDONE: Allow caller to shake clients not ONGROUND? +// UNDONE: Fix falloff model (disabled)? +// UNDONE: Affect user controls? +void UTIL_ScreenShake( const Vector ¢er, float amplitude, float frequency, float duration, float radius ) +{ + int i; + float localAmplitude; + ScreenShake shake; + + shake.duration = FixedUnsigned16( duration, 1<<12 ); // 4.12 fixed + shake.frequency = FixedUnsigned16( frequency, 1<<8 ); // 8.8 fixed + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + if ( !pPlayer || !(pPlayer->pev->flags & FL_ONGROUND) ) // Don't shake if not onground + continue; + + localAmplitude = 0; + + if ( radius <= 0 ) + localAmplitude = amplitude; + else + { + Vector delta = center - pPlayer->pev->origin; + float distance = delta.Length(); + + // Had to get rid of this falloff - it didn't work well + if ( distance < radius ) + localAmplitude = amplitude;//radius - distance; + } + if ( localAmplitude ) + { + shake.amplitude = FixedUnsigned16( localAmplitude, 1<<12 ); // 4.12 fixed + + MESSAGE_BEGIN( MSG_ONE, gmsgShake, NULL, pPlayer->edict() ); // use the magic #1 for "one client" + + WRITE_SHORT( shake.amplitude ); // shake amount + WRITE_SHORT( shake.duration ); // shake lasts this long + WRITE_SHORT( shake.frequency ); // shake noise frequency + + MESSAGE_END(); + } + } +} + + + +void UTIL_ScreenShakeAll( const Vector ¢er, float amplitude, float frequency, float duration ) +{ + UTIL_ScreenShake( center, amplitude, frequency, duration, 0 ); +} + + +void UTIL_ScreenFadeBuild( ScreenFade &fade, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) +{ + fade.duration = FixedUnsigned16( fadeTime, 1<<12 ); // 4.12 fixed + fade.holdTime = FixedUnsigned16( fadeHold, 1<<12 ); // 4.12 fixed + fade.r = (int)color.x; + fade.g = (int)color.y; + fade.b = (int)color.z; + fade.a = alpha; + fade.fadeFlags = flags; +} + + +void UTIL_ScreenFadeWrite( const ScreenFade &fade, CBaseEntity *pEntity ) +{ + if ( !pEntity || !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, gmsgFade, NULL, pEntity->edict() ); // use the magic #1 for "one client" + + WRITE_SHORT( fade.duration ); // fade lasts this long + WRITE_SHORT( fade.holdTime ); // fade lasts this long + WRITE_SHORT( fade.fadeFlags ); // fade type (in / out) + WRITE_BYTE( fade.r ); // fade red + WRITE_BYTE( fade.g ); // fade green + WRITE_BYTE( fade.b ); // fade blue + WRITE_BYTE( fade.a ); // fade blue + + MESSAGE_END(); +} + + +void UTIL_ScreenFadeAll( const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) +{ + int i; + ScreenFade fade; + + + UTIL_ScreenFadeBuild( fade, color, fadeTime, fadeHold, alpha, flags ); + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + UTIL_ScreenFadeWrite( fade, pPlayer ); + } +} + + +void UTIL_ScreenFade( CBaseEntity *pEntity, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) +{ + ScreenFade fade; + + UTIL_ScreenFadeBuild( fade, color, fadeTime, fadeHold, alpha, flags ); + UTIL_ScreenFadeWrite( fade, pEntity ); +} + + +void UTIL_HudMessage( CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage ) +{ + if ( !pEntity || !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, SVC_TEMPENTITY, NULL, pEntity->edict() ); + WRITE_BYTE( TE_TEXTMESSAGE ); + WRITE_BYTE( textparms.channel & 0xFF ); + + WRITE_SHORT( FixedSigned16( textparms.x, 1<<13 ) ); + WRITE_SHORT( FixedSigned16( textparms.y, 1<<13 ) ); + WRITE_BYTE( textparms.effect ); + + WRITE_BYTE( textparms.r1 ); + WRITE_BYTE( textparms.g1 ); + WRITE_BYTE( textparms.b1 ); + WRITE_BYTE( textparms.a1 ); + + WRITE_BYTE( textparms.r2 ); + WRITE_BYTE( textparms.g2 ); + WRITE_BYTE( textparms.b2 ); + WRITE_BYTE( textparms.a2 ); + + WRITE_SHORT( FixedUnsigned16( textparms.fadeinTime, 1<<8 ) ); + WRITE_SHORT( FixedUnsigned16( textparms.fadeoutTime, 1<<8 ) ); + WRITE_SHORT( FixedUnsigned16( textparms.holdTime, 1<<8 ) ); + + if ( textparms.effect == 2 ) + WRITE_SHORT( FixedUnsigned16( textparms.fxTime, 1<<8 ) ); + + if ( strlen( pMessage ) < 512 ) + { + WRITE_STRING( pMessage ); + } + else + { + char tmp[512]; + strncpy( tmp, pMessage, 511 ); + tmp[511] = 0; + WRITE_STRING( tmp ); + } + MESSAGE_END(); +} + +void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage ) +{ + int i; + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + UTIL_HudMessage( pPlayer, textparms, pMessage ); + } +} + + +extern int gmsgTextMsg, gmsgSayText; +void UTIL_ClientPrintAll( int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) +{ + MESSAGE_BEGIN( MSG_ALL, gmsgTextMsg ); + WRITE_BYTE( msg_dest ); + WRITE_STRING( msg_name ); + + if ( param1 ) + WRITE_STRING( param1 ); + if ( param2 ) + WRITE_STRING( param2 ); + if ( param3 ) + WRITE_STRING( param3 ); + if ( param4 ) + WRITE_STRING( param4 ); + + MESSAGE_END(); +} + +void ClientPrint( entvars_t *client, int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) +{ + MESSAGE_BEGIN( MSG_ONE, gmsgTextMsg, NULL, client ); + WRITE_BYTE( msg_dest ); + WRITE_STRING( msg_name ); + + if ( param1 ) + WRITE_STRING( param1 ); + if ( param2 ) + WRITE_STRING( param2 ); + if ( param3 ) + WRITE_STRING( param3 ); + if ( param4 ) + WRITE_STRING( param4 ); + + MESSAGE_END(); +} + +void UTIL_SayText( const char *pText, CBaseEntity *pEntity ) +{ + if ( !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, pEntity->edict() ); + WRITE_BYTE( pEntity->entindex() ); + WRITE_STRING( pText ); + MESSAGE_END(); +} + +void UTIL_SayTextAll( const char *pText, CBaseEntity *pEntity ) +{ + MESSAGE_BEGIN( MSG_ALL, gmsgSayText, NULL ); + WRITE_BYTE( pEntity->entindex() ); + WRITE_STRING( pText ); + MESSAGE_END(); +} + + +char *UTIL_dtos1( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +char *UTIL_dtos2( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +char *UTIL_dtos3( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +char *UTIL_dtos4( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +void UTIL_ShowMessage( const char *pString, CBaseEntity *pEntity ) +{ + if ( !pEntity || !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, gmsgHudText, NULL, pEntity->edict() ); + WRITE_STRING( pString ); + MESSAGE_END(); +} + + +void UTIL_ShowMessageAll( const char *pString ) +{ + int i; + + // loop through all players + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + UTIL_ShowMessage( pString, pPlayer ); + } +} + +// Overloaded to add IGNORE_GLASS +void UTIL_TraceLine( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, IGNORE_GLASS ignoreGlass, edict_t *pentIgnore, TraceResult *ptr ) +{ + TRACE_LINE( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE) | (ignoreGlass?0x100:0), pentIgnore, ptr ); +} + + +void UTIL_TraceLine( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, edict_t *pentIgnore, TraceResult *ptr ) +{ + TRACE_LINE( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE), pentIgnore, ptr ); +} + + +void UTIL_TraceHull( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, int hullNumber, edict_t *pentIgnore, TraceResult *ptr ) +{ + TRACE_HULL( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE), hullNumber, pentIgnore, ptr ); +} + +void UTIL_TraceModel( const Vector &vecStart, const Vector &vecEnd, int hullNumber, edict_t *pentModel, TraceResult *ptr ) +{ + g_engfuncs.pfnTraceModel( vecStart, vecEnd, hullNumber, pentModel, ptr ); +} + + +TraceResult UTIL_GetGlobalTrace( ) +{ + TraceResult tr; + + tr.fAllSolid = static_cast(gpGlobals->trace_allsolid); + tr.fStartSolid = static_cast(gpGlobals->trace_startsolid); + tr.fInOpen = static_cast(gpGlobals->trace_inopen); + tr.fInWater = static_cast(gpGlobals->trace_inwater); + tr.flFraction = gpGlobals->trace_fraction; + tr.flPlaneDist = gpGlobals->trace_plane_dist; + tr.pHit = gpGlobals->trace_ent; + tr.vecEndPos = gpGlobals->trace_endpos; + tr.vecPlaneNormal = gpGlobals->trace_plane_normal; + tr.iHitgroup = gpGlobals->trace_hitgroup; + return tr; +} + + +void UTIL_SetSize( entvars_t *pev, const Vector &vecMin, const Vector &vecMax ) +{ + SET_SIZE( ENT(pev), vecMin, vecMax ); +} + + +float UTIL_VecToYaw( const Vector &vec ) +{ + return VEC_TO_YAW(vec); +} + + +void UTIL_SetOrigin( entvars_t *pev, const Vector &vecOrigin ) +{ + SET_ORIGIN(ENT(pev), vecOrigin ); +} + +void UTIL_ParticleEffect( const Vector &vecOrigin, const Vector &vecDirection, ULONG ulColor, ULONG ulCount ) +{ + PARTICLE_EFFECT( vecOrigin, vecDirection, (float)ulColor, (float)ulCount ); +} + + +float UTIL_Approach( float target, float value, float speed ) +{ + float delta = target - value; + + if ( delta > speed ) + value += speed; + else if ( delta < -speed ) + value -= speed; + else + value = target; + + return value; +} + + +float UTIL_ApproachAngle( float target, float value, float speed ) +{ + target = UTIL_AngleMod( target ); + value = UTIL_AngleMod( target ); + + float delta = target - value; + + // Speed is assumed to be positive + if ( speed < 0 ) + speed = -speed; + + if ( delta < -180 ) + delta += 360; + else if ( delta > 180 ) + delta -= 360; + + if ( delta > speed ) + value += speed; + else if ( delta < -speed ) + value -= speed; + else + value = target; + + return value; +} + + +float UTIL_AngleDistance( float next, float cur ) +{ + float delta = next - cur; + + if ( delta < -180 ) + delta += 360; + else if ( delta > 180 ) + delta -= 360; + + return delta; +} + + +float UTIL_SplineFraction( float value, float scale ) +{ + value = scale * value; + float valueSquared = value * value; + + // Nice little ease-in, ease-out spline-like curve + return 3 * valueSquared - 2 * valueSquared * value; +} + + +char* UTIL_VarArgs( char *format, ... ) +{ + va_list argptr; + static char string[1024]; + + va_start (argptr, format); + vsprintf (string, format,argptr); + va_end (argptr); + + return string; +} + +Vector UTIL_GetAimVector( edict_t *pent, float flSpeed ) +{ + Vector tmp; + GET_AIM_VECTOR(pent, flSpeed, tmp); + return tmp; +} + +int UTIL_IsMasterTriggered(string_t sMaster, CBaseEntity *pActivator) +{ + if (sMaster) + { + edict_t *pentTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(sMaster)); + + if ( !FNullEnt(pentTarget) ) + { + CBaseEntity *pMaster = CBaseEntity::Instance(pentTarget); + if ( pMaster && (pMaster->ObjectCaps() & FCAP_MASTER) ) + return pMaster->IsTriggered( pActivator ); + } + + ALERT(at_console, "Master was null or not a master!\n"); + } + + // if this isn't a master entity, just say yes. + return 1; +} + +BOOL UTIL_ShouldShowBlood( int color ) +{ + if ( color != DONT_BLEED ) + { + if ( color == BLOOD_COLOR_RED ) + { + if ( CVAR_GET_FLOAT("violence_hblood") != 0 ) + return TRUE; + } + else + { + if ( CVAR_GET_FLOAT("violence_ablood") != 0 ) + return TRUE; + } + } + return FALSE; +} + +int UTIL_PointContents( const Vector &vec ) +{ + return POINT_CONTENTS(vec); +} + +void UTIL_BloodStream( const Vector &origin, const Vector &direction, int color, int amount ) +{ + if ( !UTIL_ShouldShowBlood( color ) ) + return; + + if ( g_Language == LANGUAGE_GERMAN && color == BLOOD_COLOR_RED ) + color = 0; + + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, origin ); + WRITE_BYTE( TE_BLOODSTREAM ); + WRITE_COORD( origin.x ); + WRITE_COORD( origin.y ); + WRITE_COORD( origin.z ); + WRITE_COORD( direction.x ); + WRITE_COORD( direction.y ); + WRITE_COORD( direction.z ); + WRITE_BYTE( color ); + WRITE_BYTE( min( amount, 255 ) ); + MESSAGE_END(); +} + +void UTIL_BloodDrips( const Vector &origin, const Vector &direction, int color, int amount ) +{ + if ( !UTIL_ShouldShowBlood( color ) ) + return; + + if ( color == DONT_BLEED || amount == 0 ) + return; + + if ( g_Language == LANGUAGE_GERMAN && color == BLOOD_COLOR_RED ) + color = 0; + + if ( g_pGameRules->IsMultiplayer() ) + { + // scale up blood effect in multiplayer for better visibility + amount *= 2; + } + + if ( amount > 255 ) + amount = 255; + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, origin ); + WRITE_BYTE( TE_BLOODSPRITE ); + WRITE_COORD( origin.x); // pos + WRITE_COORD( origin.y); + WRITE_COORD( origin.z); + WRITE_SHORT( g_sModelIndexBloodSpray ); // initial sprite model + 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(); +} + +Vector UTIL_RandomBloodVector( void ) +{ + Vector direction; + + direction.x = RANDOM_FLOAT ( -1, 1 ); + direction.y = RANDOM_FLOAT ( -1, 1 ); + direction.z = RANDOM_FLOAT ( 0, 1 ); + + return direction; +} + + +void UTIL_BloodDecalTrace( TraceResult *pTrace, int bloodColor ) +{ + if ( UTIL_ShouldShowBlood( bloodColor ) ) + { + if ( bloodColor == BLOOD_COLOR_RED ) + UTIL_DecalTrace( pTrace, DECAL_BLOOD1 + RANDOM_LONG(0,5) ); + else + UTIL_DecalTrace( pTrace, DECAL_YBLOOD1 + RANDOM_LONG(0,5) ); + } +} + + +void UTIL_DecalTrace( TraceResult *pTrace, int decalNumber ) +{ + short entityIndex; + int index; + int message; + + if ( decalNumber < 0 ) + return; + + index = gDecals[ decalNumber ].index; + + if ( index < 0 ) + return; + + if (pTrace->flFraction == 1.0) + return; + + // Only decal BSP models + if ( pTrace->pHit ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( pTrace->pHit ); + if ( pEntity && !pEntity->IsBSPModel() ) + return; + entityIndex = ENTINDEX( pTrace->pHit ); + } + else + entityIndex = 0; + + message = TE_DECAL; + if ( entityIndex != 0 ) + { + if ( index > 255 ) + { + message = TE_DECALHIGH; + index -= 256; + } + } + else + { + message = TE_WORLDDECAL; + if ( index > 255 ) + { + message = TE_WORLDDECALHIGH; + index -= 256; + } + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( message ); + WRITE_COORD( pTrace->vecEndPos.x ); + WRITE_COORD( pTrace->vecEndPos.y ); + WRITE_COORD( pTrace->vecEndPos.z ); + WRITE_BYTE( index ); + if ( entityIndex ) + WRITE_SHORT( entityIndex ); + MESSAGE_END(); +} + +/* +============== +UTIL_PlayerDecalTrace + +A player is trying to apply his custom decal for the spray can. +Tell connected clients to display it, or use the default spray can decal +if the custom can't be loaded. +============== +*/ +void UTIL_PlayerDecalTrace( TraceResult *pTrace, int playernum, int decalNumber, BOOL bIsCustom ) +{ + int index; + + if (!bIsCustom) + { + if ( decalNumber < 0 ) + return; + + index = gDecals[ decalNumber ].index; + if ( index < 0 ) + return; + } + else + index = decalNumber; + + if (pTrace->flFraction == 1.0) + return; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_PLAYERDECAL ); + WRITE_BYTE ( playernum ); + WRITE_COORD( pTrace->vecEndPos.x ); + WRITE_COORD( pTrace->vecEndPos.y ); + WRITE_COORD( pTrace->vecEndPos.z ); + WRITE_SHORT( (short)ENTINDEX(pTrace->pHit) ); + WRITE_BYTE( index ); + MESSAGE_END(); +} + +void UTIL_GunshotDecalTrace( TraceResult *pTrace, int decalNumber ) +{ + if ( decalNumber < 0 ) + return; + + int index = gDecals[ decalNumber ].index; + if ( index < 0 ) + return; + + if (pTrace->flFraction == 1.0) + return; + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pTrace->vecEndPos ); + WRITE_BYTE( TE_GUNSHOTDECAL ); + WRITE_COORD( pTrace->vecEndPos.x ); + WRITE_COORD( pTrace->vecEndPos.y ); + WRITE_COORD( pTrace->vecEndPos.z ); + WRITE_SHORT( (short)ENTINDEX(pTrace->pHit) ); + WRITE_BYTE( index ); + MESSAGE_END(); +} + + +void UTIL_Sparks( const Vector &position ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, position ); + WRITE_BYTE( TE_SPARKS ); + WRITE_COORD( position.x ); + WRITE_COORD( position.y ); + WRITE_COORD( position.z ); + MESSAGE_END(); +} + + +void UTIL_Ricochet( const Vector &position, float scale ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, position ); + WRITE_BYTE( TE_ARMOR_RICOCHET ); + WRITE_COORD( position.x ); + WRITE_COORD( position.y ); + WRITE_COORD( position.z ); + WRITE_BYTE( (int)(scale*10) ); + MESSAGE_END(); +} + + +BOOL UTIL_TeamsMatch( const char *pTeamName1, const char *pTeamName2 ) +{ + // Everyone matches unless it's teamplay + if ( !g_pGameRules->IsTeamplay() ) + return TRUE; + + // Both on a team? + if ( *pTeamName1 != 0 && *pTeamName2 != 0 ) + { + if ( !stricmp( pTeamName1, pTeamName2 ) ) // Same Team? + return TRUE; + } + + return FALSE; +} + + +void UTIL_StringToVector( float *pVector, const char *pString ) +{ + char *pstr, *pfront, tempString[128]; + int j; + + strcpy( tempString, pString ); + pstr = pfront = tempString; + + for ( j = 0; j < 3; j++ ) // lifted from pr_edict.c + { + pVector[j] = atof( pfront ); + + while ( *pstr && *pstr != ' ' ) + pstr++; + if (!*pstr) + break; + pstr++; + pfront = pstr; + } + if (j < 2) + { + /* + ALERT( at_error, "Bad field in entity!! %s:%s == \"%s\"\n", + pkvd->szClassName, pkvd->szKeyName, pkvd->szValue ); + */ + for (j = j+1;j < 3; j++) + pVector[j] = 0; + } +} + + +void UTIL_StringToIntArray( int *pVector, int count, const char *pString ) +{ + char *pstr, *pfront, tempString[128]; + int j; + + strcpy( tempString, pString ); + pstr = pfront = tempString; + + for ( j = 0; j < count; j++ ) // lifted from pr_edict.c + { + pVector[j] = atoi( pfront ); + + while ( *pstr && *pstr != ' ' ) + pstr++; + if (!*pstr) + break; + pstr++; + pfront = pstr; + } + + for ( j++; j < count; j++ ) + { + pVector[j] = 0; + } +} + +Vector UTIL_ClampVectorToBox( const Vector &input, const Vector &clampSize ) +{ + Vector sourceVector = input; + + if ( sourceVector.x > clampSize.x ) + sourceVector.x -= clampSize.x; + else if ( sourceVector.x < -clampSize.x ) + sourceVector.x += clampSize.x; + else + sourceVector.x = 0; + + if ( sourceVector.y > clampSize.y ) + sourceVector.y -= clampSize.y; + else if ( sourceVector.y < -clampSize.y ) + sourceVector.y += clampSize.y; + else + sourceVector.y = 0; + + if ( sourceVector.z > clampSize.z ) + sourceVector.z -= clampSize.z; + else if ( sourceVector.z < -clampSize.z ) + sourceVector.z += clampSize.z; + else + sourceVector.z = 0; + + return sourceVector.Normalize(); +} + + +float UTIL_WaterLevel( const Vector &position, float minz, float maxz ) +{ + Vector midUp = position; + midUp.z = minz; + + if (UTIL_PointContents(midUp) != CONTENTS_WATER) + return minz; + + midUp.z = maxz; + if (UTIL_PointContents(midUp) == CONTENTS_WATER) + return maxz; + + float diff = maxz - minz; + while (diff > 1.0) + { + midUp.z = minz + diff/2.0; + if (UTIL_PointContents(midUp) == CONTENTS_WATER) + { + minz = midUp.z; + } + else + { + maxz = midUp.z; + } + diff = maxz - minz; + } + + return midUp.z; +} + + +extern DLL_GLOBAL short g_sModelIndexBubbles;// holds the index for the bubbles model + +void UTIL_Bubbles( Vector mins, Vector maxs, int count ) +{ + Vector mid = (mins + maxs) * 0.5; + + float flHeight = UTIL_WaterLevel( mid, mid.z, mid.z + 1024 ); + flHeight = flHeight - mins.z; + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, mid ); + WRITE_BYTE( TE_BUBBLES ); + WRITE_COORD( mins.x ); // mins + WRITE_COORD( mins.y ); + WRITE_COORD( mins.z ); + WRITE_COORD( maxs.x ); // maxz + WRITE_COORD( maxs.y ); + WRITE_COORD( maxs.z ); + WRITE_COORD( flHeight ); // height + WRITE_SHORT( g_sModelIndexBubbles ); + WRITE_BYTE( count ); // count + WRITE_COORD( 8 ); // speed + MESSAGE_END(); +} + +void UTIL_BubbleTrail( Vector from, Vector to, int count ) +{ + float flHeight = UTIL_WaterLevel( from, from.z, from.z + 256 ); + flHeight = flHeight - from.z; + + // Return if the start point isn't in water + if (flHeight < 8) + return; + + flHeight = UTIL_WaterLevel( to, to.z, to.z + 256 ); + flHeight = flHeight - to.z; + if (flHeight < 8) + return; + + // UNDONE: do a ploink sound + flHeight = flHeight + to.z - from.z; + + if (count > 255) + count = 255; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BUBBLETRAIL ); + WRITE_COORD( from.x ); // mins + WRITE_COORD( from.y ); + WRITE_COORD( from.z ); + WRITE_COORD( to.x ); // maxz + WRITE_COORD( to.y ); + WRITE_COORD( to.z ); + WRITE_COORD( flHeight ); // height + WRITE_SHORT( g_sModelIndexBubbles ); + WRITE_BYTE( count ); // count + WRITE_COORD( 8 ); // speed + MESSAGE_END(); +} + + +void UTIL_Remove( CBaseEntity *pEntity ) +{ + if ( !pEntity ) + return; + + pEntity->UpdateOnRemove(); + pEntity->pev->flags |= FL_KILLME; + pEntity->pev->targetname = 0; +} + + +BOOL UTIL_IsValidEntity( edict_t *pent ) +{ + if ( !pent || pent->free || (pent->v.flags & FL_KILLME) ) + return FALSE; + return TRUE; +} + + +void UTIL_PrecacheOther( const char *szClassname ) +{ + edict_t *pent; + + pent = CREATE_NAMED_ENTITY( MAKE_STRING( szClassname ) ); + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in UTIL_PrecacheOther\n" ); + return; + } + + CBaseEntity *pEntity = CBaseEntity::Instance (VARS( pent )); + if (pEntity) + pEntity->Precache( ); + REMOVE_ENTITY(pent); +} + +//========================================================= +// UTIL_LogPrintf - Prints a logged message to console. +// Preceded by LOG: ( timestamp ) < message > +//========================================================= +void UTIL_LogPrintf( char *fmt, ... ) +{ + va_list argptr; + static char string[1024]; + + va_start ( argptr, fmt ); + vsprintf ( string, fmt, argptr ); + va_end ( argptr ); + + // Print to server console + ALERT( at_logged, "%s", string ); +} + +//========================================================= +// UTIL_DotPoints - returns the dot product of a line from +// src to check and vecdir. +//========================================================= +float UTIL_DotPoints ( const Vector &vecSrc, const Vector &vecCheck, const Vector &vecDir ) +{ + Vector2D vec2LOS; + + vec2LOS = ( vecCheck - vecSrc ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + return DotProduct (vec2LOS , ( vecDir.Make2D() ) ); +} + + +//========================================================= +// UTIL_StripToken - for redundant keynames +//========================================================= +void UTIL_StripToken( const char *pKey, char *pDest ) +{ + int i = 0; + + while ( pKey[i] && pKey[i] != '#' ) + { + pDest[i] = pKey[i]; + i++; + } + pDest[i] = 0; +} + + +// -------------------------------------------------------------- +// +// CSave +// +// -------------------------------------------------------------- +static int gSizes[FIELD_TYPECOUNT] = +{ + sizeof(float), // FIELD_FLOAT + sizeof(int), // FIELD_STRING + sizeof(int), // FIELD_ENTITY + sizeof(int), // FIELD_CLASSPTR + sizeof(int), // FIELD_EHANDLE + sizeof(int), // FIELD_entvars_t + sizeof(int), // FIELD_EDICT + sizeof(float)*3, // FIELD_VECTOR + sizeof(float)*3, // FIELD_POSITION_VECTOR + sizeof(int *), // FIELD_POINTER + sizeof(int), // FIELD_INTEGER + sizeof(int *), // FIELD_FUNCTION + sizeof(int), // FIELD_BOOLEAN + sizeof(short), // FIELD_SHORT + sizeof(char), // FIELD_CHARACTER + sizeof(float), // FIELD_TIME + sizeof(int), // FIELD_MODELNAME + sizeof(int), // FIELD_SOUNDNAME +}; + + +// Base class includes common SAVERESTOREDATA pointer, and manages the entity table +CSaveRestoreBuffer :: CSaveRestoreBuffer( void ) +{ + m_pdata = NULL; +} + + +CSaveRestoreBuffer :: CSaveRestoreBuffer( SAVERESTOREDATA *pdata ) +{ + m_pdata = pdata; +} + + +CSaveRestoreBuffer :: ~CSaveRestoreBuffer( void ) +{ +} + +int CSaveRestoreBuffer :: EntityIndex( CBaseEntity *pEntity ) +{ + if ( pEntity == NULL ) + return -1; + return EntityIndex( pEntity->pev ); +} + + +int CSaveRestoreBuffer :: EntityIndex( entvars_t *pevLookup ) +{ + if ( pevLookup == NULL ) + return -1; + return EntityIndex( ENT( pevLookup ) ); +} + +int CSaveRestoreBuffer :: EntityIndex( EOFFSET eoLookup ) +{ + return EntityIndex( ENT( eoLookup ) ); +} + + +int CSaveRestoreBuffer :: EntityIndex( edict_t *pentLookup ) +{ + if ( !m_pdata || pentLookup == NULL ) + return -1; + + int i; + ENTITYTABLE *pTable; + + for ( i = 0; i < m_pdata->tableCount; i++ ) + { + pTable = m_pdata->pTable + i; + if ( pTable->pent == pentLookup ) + return i; + } + return -1; +} + + +edict_t *CSaveRestoreBuffer :: EntityFromIndex( int entityIndex ) +{ + if ( !m_pdata || entityIndex < 0 ) + return NULL; + + int i; + ENTITYTABLE *pTable; + + for ( i = 0; i < m_pdata->tableCount; i++ ) + { + pTable = m_pdata->pTable + i; + if ( pTable->id == entityIndex ) + return pTable->pent; + } + return NULL; +} + + +int CSaveRestoreBuffer :: EntityFlagsSet( int entityIndex, int flags ) +{ + if ( !m_pdata || entityIndex < 0 ) + return 0; + if ( entityIndex > m_pdata->tableCount ) + return 0; + + m_pdata->pTable[ entityIndex ].flags |= flags; + + return m_pdata->pTable[ entityIndex ].flags; +} + + +void CSaveRestoreBuffer :: BufferRewind( int size ) +{ + if ( !m_pdata ) + return; + + if ( m_pdata->size < size ) + size = m_pdata->size; + + m_pdata->pCurrentData -= size; + m_pdata->size -= size; +} + +#ifndef _WIN32 +extern "C" { +unsigned _rotr ( unsigned val, int shift) +{ + register unsigned lobit; /* non-zero means lo bit set */ + register unsigned num = val; /* number to rotate */ + + shift &= 0x1f; /* modulo 32 -- this will also make + negative shifts work */ + + while (shift--) { + lobit = num & 1; /* get high bit */ + num >>= 1; /* shift right one bit */ + if (lobit) + num |= 0x80000000; /* set hi bit if lo bit was set */ + } + + return num; +} +} +#endif + +unsigned int CSaveRestoreBuffer :: HashString( const char *pszToken ) +{ + unsigned int hash = 0; + + while ( *pszToken ) + hash = _rotr( hash, 4 ) ^ *pszToken++; + + return hash; +} + +unsigned short CSaveRestoreBuffer :: TokenHash( const char *pszToken ) +{ + unsigned short hash = (unsigned short)(HashString( pszToken ) % (unsigned)m_pdata->tokenCount ); + +#if _DEBUG + static int tokensparsed = 0; + tokensparsed++; + if ( !m_pdata->tokenCount || !m_pdata->pTokens ) + ALERT( at_error, "No token table array in TokenHash()!" ); +#endif + + for ( int i=0; itokenCount; i++ ) + { +#if _DEBUG + static qboolean beentheredonethat = FALSE; + if ( i > 50 && !beentheredonethat ) + { + beentheredonethat = TRUE; + ALERT( at_error, "CSaveRestoreBuffer :: TokenHash() is getting too full!" ); + } +#endif + + int index = hash + i; + if ( index >= m_pdata->tokenCount ) + index -= m_pdata->tokenCount; + + if ( !m_pdata->pTokens[index] || strcmp( pszToken, m_pdata->pTokens[index] ) == 0 ) + { + m_pdata->pTokens[index] = (char *)pszToken; + return index; + } + } + + // Token hash table full!!! + // [Consider doing overflow table(s) after the main table & limiting linear hash table search] + ALERT( at_error, "CSaveRestoreBuffer :: TokenHash() is COMPLETELY FULL!" ); + return 0; +} + +void CSave :: WriteData( const char *pname, int size, const char *pdata ) +{ + BufferField( pname, size, pdata ); +} + + +void CSave :: WriteShort( const char *pname, const short *data, int count ) +{ + BufferField( pname, sizeof(short) * count, (const char *)data ); +} + + +void CSave :: WriteInt( const char *pname, const int *data, int count ) +{ + BufferField( pname, sizeof(int) * count, (const char *)data ); +} + + +void CSave :: WriteFloat( const char *pname, const float *data, int count ) +{ + BufferField( pname, sizeof(float) * count, (const char *)data ); +} + + +void CSave :: WriteTime( const char *pname, const float *data, int count ) +{ + int i; + Vector tmp, input; + + BufferHeader( pname, sizeof(float) * count ); + for ( i = 0; i < count; i++ ) + { + float tmp = data[0]; + + // Always encode time as a delta from the current time so it can be re-based if loaded in a new level + // Times of 0 are never written to the file, so they will be restored as 0, not a relative time + if ( m_pdata ) + tmp -= m_pdata->time; + + BufferData( (const char *)&tmp, sizeof(float) ); + data ++; + } +} + + +void CSave :: WriteString( const char *pname, const char *pdata ) +{ +#ifdef TOKENIZE + short token = (short)TokenHash( pdata ); + WriteShort( pname, &token, 1 ); +#else + BufferField( pname, strlen(pdata) + 1, pdata ); +#endif +} + + +void CSave :: WriteString( const char *pname, const int *stringId, int count ) +{ + int i, size; + +#ifdef TOKENIZE + short token = (short)TokenHash( STRING( *stringId ) ); + WriteShort( pname, &token, 1 ); +#else +#if 0 + if ( count != 1 ) + ALERT( at_error, "No string arrays!\n" ); + WriteString( pname, (char *)STRING(*stringId) ); +#endif + + size = 0; + for ( i = 0; i < count; i++ ) + size += strlen( STRING( stringId[i] ) ) + 1; + + BufferHeader( pname, size ); + for ( i = 0; i < count; i++ ) + { + const char *pString = STRING(stringId[i]); + BufferData( pString, strlen(pString)+1 ); + } +#endif +} + + +void CSave :: WriteVector( const char *pname, const Vector &value ) +{ + WriteVector( pname, &value.x, 1 ); +} + + +void CSave :: WriteVector( const char *pname, const float *value, int count ) +{ + BufferHeader( pname, sizeof(float) * 3 * count ); + BufferData( (const char *)value, sizeof(float) * 3 * count ); +} + + + +void CSave :: WritePositionVector( const char *pname, const Vector &value ) +{ + + if ( m_pdata && m_pdata->fUseLandmark ) + { + Vector tmp = value - m_pdata->vecLandmarkOffset; + WriteVector( pname, tmp ); + } + + WriteVector( pname, value ); +} + + +void CSave :: WritePositionVector( const char *pname, const float *value, int count ) +{ + int i; + Vector tmp, input; + + BufferHeader( pname, sizeof(float) * 3 * count ); + for ( i = 0; i < count; i++ ) + { + Vector tmp( value[0], value[1], value[2] ); + + if ( m_pdata && m_pdata->fUseLandmark ) + tmp = tmp - m_pdata->vecLandmarkOffset; + + BufferData( (const char *)&tmp.x, sizeof(float) * 3 ); + value += 3; + } +} + + +void CSave :: WriteFunction( const char *pname, const int *data, int count ) +{ + const char *functionName; + + functionName = NAME_FOR_FUNCTION( *data ); + if ( functionName ) + BufferField( pname, strlen(functionName) + 1, functionName ); + else + ALERT( at_error, "Invalid function pointer in entity!" ); +} + + +void EntvarsKeyvalue( entvars_t *pev, KeyValueData *pkvd ) +{ + int i; + TYPEDESCRIPTION *pField; + + for ( i = 0; i < static_cast(ENTVARS_COUNT); i++ ) + { + pField = &gEntvarsDescription[i]; + + if ( !stricmp( pField->fieldName, pkvd->szKeyName ) ) + { + switch( pField->fieldType ) + { + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + case FIELD_STRING: + (*(int *)((char *)pev + pField->fieldOffset)) = ALLOC_STRING( pkvd->szValue ); + break; + + case FIELD_TIME: + case FIELD_FLOAT: + (*(float *)((char *)pev + pField->fieldOffset)) = atof( pkvd->szValue ); + break; + + case FIELD_INTEGER: + (*(int *)((char *)pev + pField->fieldOffset)) = atoi( pkvd->szValue ); + break; + + case FIELD_POSITION_VECTOR: + case FIELD_VECTOR: + UTIL_StringToVector( (float *)((char *)pev + pField->fieldOffset), pkvd->szValue ); + break; + + default: + case FIELD_EVARS: + case FIELD_CLASSPTR: + case FIELD_EDICT: + case FIELD_ENTITY: + case FIELD_POINTER: + ALERT( at_error, "Bad field in entity!!\n" ); + break; + } + pkvd->fHandled = TRUE; + return; + } + } +} + + + +int CSave :: WriteEntVars( const char *pname, entvars_t *pev ) +{ + return WriteFields( pname, pev, gEntvarsDescription, ENTVARS_COUNT ); +} + + + +int CSave :: WriteFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + int i, j, actualCount, emptyCount; + TYPEDESCRIPTION *pTest; + int entityArray[MAX_ENTITYARRAY]; + + // Precalculate the number of empty fields + emptyCount = 0; + for ( i = 0; i < fieldCount; i++ ) + { + void *pOutputData; + pOutputData = ((char *)pBaseData + pFields[i].fieldOffset ); + if ( DataEmpty( (const char *)pOutputData, pFields[i].fieldSize * gSizes[pFields[i].fieldType] ) ) + emptyCount++; + } + + // Empty fields will not be written, write out the actual number of fields to be written + actualCount = fieldCount - emptyCount; + WriteInt( pname, &actualCount, 1 ); + + for ( i = 0; i < fieldCount; i++ ) + { + void *pOutputData; + pTest = &pFields[ i ]; + pOutputData = ((char *)pBaseData + pTest->fieldOffset ); + + // UNDONE: Must we do this twice? + if ( DataEmpty( (const char *)pOutputData, pTest->fieldSize * gSizes[pTest->fieldType] ) ) + continue; + + switch( pTest->fieldType ) + { + case FIELD_FLOAT: + WriteFloat( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + case FIELD_TIME: + WriteTime( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + case FIELD_STRING: + WriteString( pTest->fieldName, (int *)pOutputData, pTest->fieldSize ); + break; + case FIELD_CLASSPTR: + case FIELD_EVARS: + case FIELD_EDICT: + case FIELD_ENTITY: + case FIELD_EHANDLE: + if ( pTest->fieldSize > MAX_ENTITYARRAY ) + ALERT( at_error, "Can't save more than %d entities in an array!!!\n", MAX_ENTITYARRAY ); + for ( j = 0; j < pTest->fieldSize; j++ ) + { + switch( pTest->fieldType ) + { + case FIELD_EVARS: + entityArray[j] = EntityIndex( ((entvars_t **)pOutputData)[j] ); + break; + case FIELD_CLASSPTR: + entityArray[j] = EntityIndex( ((CBaseEntity **)pOutputData)[j] ); + break; + case FIELD_EDICT: + entityArray[j] = EntityIndex( ((edict_t **)pOutputData)[j] ); + break; + case FIELD_ENTITY: + entityArray[j] = EntityIndex( ((EOFFSET *)pOutputData)[j] ); + break; + case FIELD_EHANDLE: + entityArray[j] = EntityIndex( (CBaseEntity *)(((EHANDLE *)pOutputData)[j]) ); + break; + default: + break; + } + } + WriteInt( pTest->fieldName, entityArray, pTest->fieldSize ); + break; + case FIELD_POSITION_VECTOR: + WritePositionVector( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + case FIELD_VECTOR: + WriteVector( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + + case FIELD_BOOLEAN: + case FIELD_INTEGER: + WriteInt( pTest->fieldName, (int *)pOutputData, pTest->fieldSize ); + break; + + case FIELD_SHORT: + WriteData( pTest->fieldName, 2 * pTest->fieldSize, ((char *)pOutputData) ); + break; + + case FIELD_CHARACTER: + WriteData( pTest->fieldName, pTest->fieldSize, ((char *)pOutputData) ); + break; + + // For now, just write the address out, we're not going to change memory while doing this yet! + case FIELD_POINTER: + WriteInt( pTest->fieldName, (int *)(char *)pOutputData, pTest->fieldSize ); + break; + + case FIELD_FUNCTION: + WriteFunction( pTest->fieldName, (int *)(char *)pOutputData, pTest->fieldSize ); + break; + default: + ALERT( at_error, "Bad field type\n" ); + } + } + + return 1; +} + + +void CSave :: BufferString( char *pdata, int len ) +{ + char c = 0; + + BufferData( pdata, len ); // Write the string + BufferData( &c, 1 ); // Write a null terminator +} + + +int CSave :: DataEmpty( const char *pdata, int size ) +{ + for ( int i = 0; i < size; i++ ) + { + if ( pdata[i] ) + return 0; + } + return 1; +} + + +void CSave :: BufferField( const char *pname, int size, const char *pdata ) +{ + BufferHeader( pname, size ); + BufferData( pdata, size ); +} + + +void CSave :: BufferHeader( const char *pname, int size ) +{ + short hashvalue = TokenHash( pname ); + if ( size > 1<<(sizeof(short)*8) ) + ALERT( at_error, "CSave :: BufferHeader() size parameter exceeds 'short'!" ); + BufferData( (const char *)&size, sizeof(short) ); + BufferData( (const char *)&hashvalue, sizeof(short) ); +} + + +void CSave :: BufferData( const char *pdata, int size ) +{ + if ( !m_pdata ) + return; + + if ( m_pdata->size + size > m_pdata->bufferSize ) + { + ALERT( at_error, "Save/Restore overflow!" ); + m_pdata->size = m_pdata->bufferSize; + return; + } + + memcpy( m_pdata->pCurrentData, pdata, size ); + m_pdata->pCurrentData += size; + m_pdata->size += size; +} + + + +// -------------------------------------------------------------- +// +// CRestore +// +// -------------------------------------------------------------- + +int CRestore::ReadField( void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount, int startField, int size, char *pName, void *pData ) +{ + int i, j, stringCount, fieldNumber, entityIndex; + TYPEDESCRIPTION *pTest; + float time, timeData; + Vector position; + edict_t *pent; + char *pString; + + time = 0; + position = Vector(0,0,0); + + if ( m_pdata ) + { + time = m_pdata->time; + if ( m_pdata->fUseLandmark ) + position = m_pdata->vecLandmarkOffset; + } + + for ( i = 0; i < fieldCount; i++ ) + { + fieldNumber = (i+startField)%fieldCount; + pTest = &pFields[ fieldNumber ]; + if ( !stricmp( pTest->fieldName, pName ) ) + { + if ( !m_global || !(pTest->flags & FTYPEDESC_GLOBAL) ) + { + for ( j = 0; j < pTest->fieldSize; j++ ) + { + void *pOutputData = ((char *)pBaseData + pTest->fieldOffset + (j*gSizes[pTest->fieldType]) ); + void *pInputData = (char *)pData + j * gSizes[pTest->fieldType]; + + switch( pTest->fieldType ) + { + case FIELD_TIME: + timeData = *(float *)pInputData; + // Re-base time variables + timeData += time; + *((float *)pOutputData) = timeData; + break; + case FIELD_FLOAT: + *((float *)pOutputData) = *(float *)pInputData; + break; + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + case FIELD_STRING: + // Skip over j strings + pString = (char *)pData; + for ( stringCount = 0; stringCount < j; stringCount++ ) + { + while (*pString) + pString++; + pString++; + } + pInputData = pString; + if ( strlen( (char *)pInputData ) == 0 ) + *((int *)pOutputData) = 0; + else + { + int string; + + string = ALLOC_STRING( (char *)pInputData ); + + *((int *)pOutputData) = string; + + if ( !FStringNull( string ) && m_precache ) + { + if ( pTest->fieldType == FIELD_MODELNAME ) + PRECACHE_MODEL( (char *)STRING( string ) ); + else if ( pTest->fieldType == FIELD_SOUNDNAME ) + PRECACHE_SOUND( (char *)STRING( string ) ); + } + } + break; + case FIELD_EVARS: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((entvars_t **)pOutputData) = VARS(pent); + else + *((entvars_t **)pOutputData) = NULL; + break; + case FIELD_CLASSPTR: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((CBaseEntity **)pOutputData) = CBaseEntity::Instance(pent); + else + *((CBaseEntity **)pOutputData) = NULL; + break; + case FIELD_EDICT: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + *((edict_t **)pOutputData) = pent; + break; + case FIELD_EHANDLE: + // Input and Output sizes are different! + pOutputData = (char *)pOutputData + j*(sizeof(EHANDLE) - gSizes[pTest->fieldType]); + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((EHANDLE *)pOutputData) = CBaseEntity::Instance(pent); + else + *((EHANDLE *)pOutputData) = NULL; + break; + case FIELD_ENTITY: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((EOFFSET *)pOutputData) = OFFSET(pent); + else + *((EOFFSET *)pOutputData) = 0; + break; + case FIELD_VECTOR: + ((float *)pOutputData)[0] = ((float *)pInputData)[0]; + ((float *)pOutputData)[1] = ((float *)pInputData)[1]; + ((float *)pOutputData)[2] = ((float *)pInputData)[2]; + break; + case FIELD_POSITION_VECTOR: + ((float *)pOutputData)[0] = ((float *)pInputData)[0] + position.x; + ((float *)pOutputData)[1] = ((float *)pInputData)[1] + position.y; + ((float *)pOutputData)[2] = ((float *)pInputData)[2] + position.z; + break; + + case FIELD_BOOLEAN: + case FIELD_INTEGER: + *((int *)pOutputData) = *( int *)pInputData; + break; + + case FIELD_SHORT: + *((short *)pOutputData) = *( short *)pInputData; + break; + + case FIELD_CHARACTER: + *((char *)pOutputData) = *( char *)pInputData; + break; + + case FIELD_POINTER: + *((int *)pOutputData) = *( int *)pInputData; + break; + case FIELD_FUNCTION: + if ( strlen( (char *)pInputData ) == 0 ) + *((int *)pOutputData) = 0; + else + *((int *)pOutputData) = FUNCTION_FROM_NAME( (char *)pInputData ); + break; + + default: + ALERT( at_error, "Bad field type\n" ); + } + } + } +#if 0 + else + { + ALERT( at_console, "Skipping global field %s\n", pName ); + } +#endif + return fieldNumber; + } + } + + return -1; +} + + +int CRestore::ReadEntVars( const char *pname, entvars_t *pev ) +{ + return ReadFields( pname, pev, gEntvarsDescription, ENTVARS_COUNT ); +} + + +int CRestore::ReadFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + unsigned short i, token; + int lastField, fileCount; + HEADER header; + + i = ReadShort(); + ASSERT( i == sizeof(int) ); // First entry should be an int + + token = ReadShort(); + + // Check the struct name + if ( token != TokenHash(pname) ) // Field Set marker + { +// ALERT( at_error, "Expected %s found %s!\n", pname, BufferPointer() ); + BufferRewind( 2*sizeof(short) ); + return 0; + } + + // Skip over the struct name + fileCount = ReadInt(); // Read field count + + lastField = 0; // Make searches faster, most data is read/written in the same order + + // Clear out base data + for ( i = 0; i < fieldCount; i++ ) + { + // Don't clear global fields + if ( !m_global || !(pFields[i].flags & FTYPEDESC_GLOBAL) ) + memset( ((char *)pBaseData + pFields[i].fieldOffset), 0, pFields[i].fieldSize * gSizes[pFields[i].fieldType] ); + } + + for ( i = 0; i < fileCount; i++ ) + { + BufferReadHeader( &header ); + lastField = ReadField( pBaseData, pFields, fieldCount, lastField, header.size, m_pdata->pTokens[header.token], header.pData ); + lastField++; + } + + return 1; +} + + +void CRestore::BufferReadHeader( HEADER *pheader ) +{ + ASSERT( pheader!=NULL ); + pheader->size = ReadShort(); // Read field size + pheader->token = ReadShort(); // Read field name token + pheader->pData = BufferPointer(); // Field Data is next + BufferSkipBytes( pheader->size ); // Advance to next field +} + + +short CRestore::ReadShort( void ) +{ + short tmp = 0; + + BufferReadBytes( (char *)&tmp, sizeof(short) ); + + return tmp; +} + +int CRestore::ReadInt( void ) +{ + int tmp = 0; + + BufferReadBytes( (char *)&tmp, sizeof(int) ); + + return tmp; +} + +int CRestore::ReadNamedInt( const char *pName ) +{ + HEADER header; + + BufferReadHeader( &header ); + return ((int *)header.pData)[0]; +} + +char *CRestore::ReadNamedString( const char *pName ) +{ + HEADER header; + + BufferReadHeader( &header ); +#ifdef TOKENIZE + return (char *)(m_pdata->pTokens[*(short *)header.pData]); +#else + return (char *)header.pData; +#endif +} + + +char *CRestore::BufferPointer( void ) +{ + if ( !m_pdata ) + return NULL; + + return m_pdata->pCurrentData; +} + +void CRestore::BufferReadBytes( char *pOutput, int size ) +{ + ASSERT( m_pdata !=NULL ); + + if ( !m_pdata || Empty() ) + return; + + if ( (m_pdata->size + size) > m_pdata->bufferSize ) + { + ALERT( at_error, "Restore overflow!" ); + m_pdata->size = m_pdata->bufferSize; + return; + } + + if ( pOutput ) + memcpy( pOutput, m_pdata->pCurrentData, size ); + m_pdata->pCurrentData += size; + m_pdata->size += size; +} + + +void CRestore::BufferSkipBytes( int bytes ) +{ + BufferReadBytes( NULL, bytes ); +} + +int CRestore::BufferSkipZString( void ) +{ + char *pszSearch; + int len; + + if ( !m_pdata ) + return 0; + + int maxLen = m_pdata->bufferSize - m_pdata->size; + + len = 0; + pszSearch = m_pdata->pCurrentData; + while ( *pszSearch++ && len < maxLen ) + len++; + + len++; + + BufferSkipBytes( len ); + + return len; +} + +int CRestore::BufferCheckZString( const char *string ) +{ + if ( !m_pdata ) + return 0; + + int maxLen = m_pdata->bufferSize - m_pdata->size; + int len = strlen( string ); + if ( len <= maxLen ) + { + if ( !strncmp( string, m_pdata->pCurrentData, len ) ) + return 1; + } + return 0; +} diff --git a/hlsdk/dmc/dlls/util.h b/hlsdk/dmc/dlls/util.h new file mode 100644 index 00000000..5a2264bb --- /dev/null +++ b/hlsdk/dmc/dlls/util.h @@ -0,0 +1,599 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Misc utility code +// + +#include + +#ifndef ACTIVITY_H +#include "activity.h" +#endif + +#ifndef ENGINECALLBACK_H +#include "enginecallback.h" +#endif +inline void MESSAGE_BEGIN( int msg_dest, int msg_type, const float *pOrigin, entvars_t *ent ); // implementation later in this file + +extern globalvars_t *gpGlobals; + +// Use this instead of ALLOC_STRING on constant strings +#define STRING(offset) (const char *)(gpGlobals->pStringBase + (int)offset) +#define MAKE_STRING(str) ((int)str - (int)STRING(0)) + +inline edict_t *FIND_ENTITY_BY_CLASSNAME(edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING(entStart, "classname", pszName); +} + +inline edict_t *FIND_ENTITY_BY_TARGETNAME(edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING(entStart, "targetname", pszName); +} + +// for doing a reverse lookup. Say you have a door, and want to find its button. +inline edict_t *FIND_ENTITY_BY_TARGET(edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING(entStart, "target", pszName); +} + +// Keeps clutter down a bit, when writing key-value pairs +#define WRITEKEY_INT(pf, szKeyName, iKeyValue) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%d\"\n", szKeyName, iKeyValue) +#define WRITEKEY_FLOAT(pf, szKeyName, flKeyValue) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%f\"\n", szKeyName, flKeyValue) +#define WRITEKEY_STRING(pf, szKeyName, szKeyValue) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%s\"\n", szKeyName, szKeyValue) +#define WRITEKEY_VECTOR(pf, szKeyName, flX, flY, flZ) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%f %f %f\"\n", szKeyName, flX, flY, flZ) + +// Keeps clutter down a bit, when using a float as a bit-vector +#define SetBits(flBitVector, bits) ((flBitVector) = (int)(flBitVector) | (bits)) +#define ClearBits(flBitVector, bits) ((flBitVector) = (int)(flBitVector) & ~(bits)) +#define FBitSet(flBitVector, bit) ((int)(flBitVector) & (bit)) + +// Makes these more explicit, and easier to find +#define FILE_GLOBAL static +#define DLL_GLOBAL + +// Until we figure out why "const" gives the compiler problems, we'll just have to use +// this bogus "empty" define to mark things as constant. +#define CONSTANT + +// More explicit than "int" +typedef int EOFFSET; + +// In case it's not alread defined +typedef int BOOL; + +// In case this ever changes +#define M_PI 3.14159265358979323846 + +// Keeps clutter down a bit, when declaring external entity/global method prototypes +#define DECLARE_GLOBAL_METHOD(MethodName) \ + extern void DLLEXPORT MethodName( void ) +#define GLOBAL_METHOD(funcname) void DLLEXPORT funcname(void) + +// This is the glue that hooks .MAP entity class names to our CPP classes +// The _declspec forces them to be exported by name so we can do a lookup with GetProcAddress() +// The function is used to intialize / allocate the object for the entity +#ifdef _WIN32 +#define LINK_ENTITY_TO_CLASS(mapClassName,DLLClassName) \ + extern "C" _declspec( dllexport ) void mapClassName( entvars_t *pev ); \ + void mapClassName( entvars_t *pev ) { GetClassPtr( (DLLClassName *)pev ); } +#else +#define LINK_ENTITY_TO_CLASS(mapClassName,DLLClassName) \ + extern "C" void mapClassName( entvars_t *pev ); \ + void mapClassName( entvars_t *pev ) { GetClassPtr( (DLLClassName *)pev ); } +#endif + + +// +// Conversion among the three types of "entity", including identity-conversions. +// +#ifdef DEBUG + extern edict_t *DBG_EntOfVars(const entvars_t *pev); + inline edict_t *ENT(const entvars_t *pev) { return DBG_EntOfVars(pev); } +#else + inline edict_t *ENT(const entvars_t *pev) { return pev->pContainingEntity; } +#endif +inline edict_t *ENT(edict_t *pent) { return pent; } +inline edict_t *ENT(EOFFSET eoffset) { return (*g_engfuncs.pfnPEntityOfEntOffset)(eoffset); } +inline EOFFSET OFFSET(EOFFSET eoffset) { return eoffset; } +inline EOFFSET OFFSET(const edict_t *pent) +{ +#ifdef _DEBUG + if ( !pent ) + ALERT( at_error, "Bad ent in OFFSET()\n" ); +#endif + return (*g_engfuncs.pfnEntOffsetOfPEntity)(pent); +} +inline EOFFSET OFFSET(entvars_t *pev) +{ +#ifdef _DEBUG + if ( !pev ) + ALERT( at_error, "Bad pev in OFFSET()\n" ); +#endif + return OFFSET(ENT(pev)); +} +inline entvars_t *VARS(entvars_t *pev) { return pev; } + +inline entvars_t *VARS(edict_t *pent) +{ + if ( !pent ) + return NULL; + + return &pent->v; +} + +inline entvars_t* VARS(EOFFSET eoffset) { return VARS(ENT(eoffset)); } +inline int ENTINDEX(edict_t *pEdict) { return (*g_engfuncs.pfnIndexOfEdict)(pEdict); } +inline edict_t* INDEXENT( int iEdictNum ) { return (*g_engfuncs.pfnPEntityOfEntIndex)(iEdictNum); } +inline void MESSAGE_BEGIN( int msg_dest, int msg_type, const float *pOrigin, entvars_t *ent ) { + (*g_engfuncs.pfnMessageBegin)(msg_dest, msg_type, pOrigin, ENT(ent)); +} + +// Testing the three types of "entity" for nullity +#define eoNullEntity 0 +inline BOOL FNullEnt(EOFFSET eoffset) { return eoffset == 0; } +inline BOOL FNullEnt(const edict_t* pent) { return pent == NULL || FNullEnt(OFFSET(pent)); } +inline BOOL FNullEnt(entvars_t* pev) { return pev == NULL || FNullEnt(OFFSET(pev)); } + +// Testing strings for nullity +#define iStringNull 0 +inline BOOL FStringNull(int iString) { return iString == iStringNull; } + +#define cchMapNameMost 32 + +// Dot products for view cone checking +#define VIEW_FIELD_FULL (float)-1.0 // +-180 degrees +#define VIEW_FIELD_WIDE (float)-0.7 // +-135 degrees 0.1 // +-85 degrees, used for full FOV checks +#define VIEW_FIELD_NARROW (float)0.7 // +-45 degrees, more narrow check used to set up ranged attacks +#define VIEW_FIELD_ULTRA_NARROW (float)0.9 // +-25 degrees, more narrow check used to set up ranged attacks + +// All monsters need this data +#define DONT_BLEED -1 +#define BLOOD_COLOR_RED (BYTE)247 +#define BLOOD_COLOR_YELLOW (BYTE)195 +#define BLOOD_COLOR_GREEN BLOOD_COLOR_YELLOW + +typedef enum +{ + + MONSTERSTATE_NONE = 0, + MONSTERSTATE_IDLE, + MONSTERSTATE_COMBAT, + MONSTERSTATE_ALERT, + MONSTERSTATE_HUNT, + MONSTERSTATE_PRONE, + MONSTERSTATE_SCRIPT, + MONSTERSTATE_PLAYDEAD, + MONSTERSTATE_DEAD + +} MONSTERSTATE; + + + +// Things that toggle (buttons/triggers/doors) need this +typedef enum + { + TS_AT_TOP, + TS_AT_BOTTOM, + TS_GOING_UP, + TS_GOING_DOWN + } TOGGLE_STATE; + +// Misc useful +inline BOOL FStrEq(const char*sz1, const char*sz2) + { return (strcmp(sz1, sz2) == 0); } +inline BOOL FClassnameIs(edict_t* pent, const char* szClassname) + { return FStrEq(STRING(VARS(pent)->classname), szClassname); } +inline BOOL FClassnameIs(entvars_t* pev, const char* szClassname) + { return FStrEq(STRING(pev->classname), szClassname); } + +class CBaseEntity; + +// Misc. Prototypes +extern void UTIL_SetSize (entvars_t* pev, const Vector &vecMin, const Vector &vecMax); +extern float UTIL_VecToYaw (const Vector &vec); +extern Vector UTIL_VecToAngles (const Vector &vec); +extern float UTIL_AngleMod (float a); +extern float UTIL_AngleDiff ( float destAngle, float srcAngle ); + +extern CBaseEntity *UTIL_FindEntityInSphere(CBaseEntity *pStartEntity, const Vector &vecCenter, float flRadius); +extern CBaseEntity *UTIL_FindEntityByString(CBaseEntity *pStartEntity, const char *szKeyword, const char *szValue ); +extern CBaseEntity *UTIL_FindEntityByClassname(CBaseEntity *pStartEntity, const char *szName ); +extern CBaseEntity *UTIL_FindEntityByTargetname(CBaseEntity *pStartEntity, const char *szName ); +extern CBaseEntity *UTIL_FindEntityGeneric(const char *szName, Vector &vecSrc, float flRadius ); + +// returns a CBaseEntity pointer to a player by index. Only returns if the player is spawned and connected +// otherwise returns NULL +// Index is 1 based +extern CBaseEntity *UTIL_PlayerByIndex( int playerIndex ); + +#define UTIL_EntitiesInPVS(pent) (*g_engfuncs.pfnEntitiesInPVS)(pent) +extern void UTIL_MakeVectors (const Vector &vecAngles); + +// Pass in an array of pointers and an array size, it fills the array and returns the number inserted +extern int UTIL_MonstersInSphere( CBaseEntity **pList, int listMax, const Vector ¢er, float radius ); +extern int UTIL_EntitiesInBox( CBaseEntity **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask ); + +inline void UTIL_MakeVectorsPrivate( const Vector &vecAngles, float *p_vForward, float *p_vRight, float *p_vUp ) +{ + g_engfuncs.pfnAngleVectors( vecAngles, p_vForward, p_vRight, p_vUp ); +} + +extern void UTIL_MakeAimVectors ( const Vector &vecAngles ); // like MakeVectors, but assumes pitch isn't inverted +extern void UTIL_MakeInvVectors ( const Vector &vec, globalvars_t *pgv ); + +extern void UTIL_SetOrigin ( entvars_t* pev, const Vector &vecOrigin ); +extern void UTIL_EmitAmbientSound ( edict_t *entity, const Vector &vecOrigin, const char *samp, float vol, float attenuation, int fFlags, int pitch ); +extern void UTIL_ParticleEffect ( const Vector &vecOrigin, const Vector &vecDirection, ULONG ulColor, ULONG ulCount ); +extern void UTIL_ScreenShake ( const Vector ¢er, float amplitude, float frequency, float duration, float radius ); +extern void UTIL_ScreenShakeAll ( const Vector ¢er, float amplitude, float frequency, float duration ); +extern void UTIL_ShowMessage ( const char *pString, CBaseEntity *pPlayer ); +extern void UTIL_ShowMessageAll ( const char *pString ); +extern void UTIL_ScreenFadeAll ( const Vector &color, float fadeTime, float holdTime, int alpha, int flags ); +extern void UTIL_ScreenFade ( CBaseEntity *pEntity, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ); + +typedef enum { ignore_monsters=1, dont_ignore_monsters=0, missile=2 } IGNORE_MONSTERS; +typedef enum { ignore_glass=1, dont_ignore_glass=0 } IGNORE_GLASS; +extern void UTIL_TraceLine (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, edict_t *pentIgnore, TraceResult *ptr); +extern void UTIL_TraceLine (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, IGNORE_GLASS ignoreGlass, edict_t *pentIgnore, TraceResult *ptr); +typedef enum { point_hull=0, human_hull=1, large_hull=2, head_hull=3 }; +extern void UTIL_TraceHull (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, int hullNumber, edict_t *pentIgnore, TraceResult *ptr); +extern TraceResult UTIL_GetGlobalTrace (void); +extern void UTIL_TraceModel (const Vector &vecStart, const Vector &vecEnd, int hullNumber, edict_t *pentModel, TraceResult *ptr); +extern Vector UTIL_GetAimVector (edict_t* pent, float flSpeed); +extern int UTIL_PointContents (const Vector &vec); + +extern int UTIL_IsMasterTriggered (string_t sMaster, CBaseEntity *pActivator); +extern void UTIL_BloodStream( const Vector &origin, const Vector &direction, int color, int amount ); +extern void UTIL_BloodDrips( const Vector &origin, const Vector &direction, int color, int amount ); +extern Vector UTIL_RandomBloodVector( void ); +extern BOOL UTIL_ShouldShowBlood( int bloodColor ); +extern void UTIL_BloodDecalTrace( TraceResult *pTrace, int bloodColor ); +extern void UTIL_DecalTrace( TraceResult *pTrace, int decalNumber ); +extern void UTIL_PlayerDecalTrace( TraceResult *pTrace, int playernum, int decalNumber, BOOL bIsCustom ); +extern void UTIL_GunshotDecalTrace( TraceResult *pTrace, int decalNumber ); +extern void UTIL_Sparks( const Vector &position ); +extern void UTIL_Ricochet( const Vector &position, float scale ); +extern void UTIL_StringToVector( float *pVector, const char *pString ); +extern void UTIL_StringToIntArray( int *pVector, int count, const char *pString ); +extern Vector UTIL_ClampVectorToBox( const Vector &input, const Vector &clampSize ); +extern float UTIL_Approach( float target, float value, float speed ); +extern float UTIL_ApproachAngle( float target, float value, float speed ); +extern float UTIL_AngleDistance( float next, float cur ); + +extern char *UTIL_VarArgs( char *format, ... ); +extern void UTIL_Remove( CBaseEntity *pEntity ); +extern BOOL UTIL_IsValidEntity( edict_t *pent ); +extern BOOL UTIL_TeamsMatch( const char *pTeamName1, const char *pTeamName2 ); + +// create a TENT projectile on all clients +extern void UTIL_ClientProjectile( const Vector &vecOrigin, const Vector &vecVelocity, short sModelIndex, int iOwnerIndex, int iLife ); + +// Use for ease-in, ease-out style interpolation (accel/decel) +extern float UTIL_SplineFraction( float value, float scale ); + +// Search for water transition along a vertical line +extern float UTIL_WaterLevel( const Vector &position, float minz, float maxz ); +extern void UTIL_Bubbles( Vector mins, Vector maxs, int count ); +extern void UTIL_BubbleTrail( Vector from, Vector to, int count ); + +// allows precacheing of other entities +extern void UTIL_PrecacheOther( const char *szClassname ); + +// prints a message to each client +extern void UTIL_ClientPrintAll( int msg_dest, const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ); +inline void UTIL_CenterPrintAll( const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ) +{ + UTIL_ClientPrintAll( HUD_PRINTCENTER, msg_name, param1, param2, param3, param4 ); +} + +class CBasePlayerItem; +class CBasePlayer; +extern BOOL UTIL_GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ); + +// prints messages through the HUD +extern void ClientPrint( entvars_t *client, int msg_dest, const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ); + +// prints a message to the HUD say (chat) +extern void UTIL_SayText( const char *pText, CBaseEntity *pEntity ); +extern void UTIL_SayTextAll( const char *pText, CBaseEntity *pEntity ); + + +typedef struct hudtextparms_s +{ + float x; + float y; + int effect; + byte r1, g1, b1, a1; + byte r2, g2, b2, a2; + float fadeinTime; + float fadeoutTime; + float holdTime; + float fxTime; + int channel; +} hudtextparms_t; + +// prints as transparent 'title' to the HUD +extern void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage ); +extern void UTIL_HudMessage( CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage ); + +// for handy use with ClientPrint params +extern char *UTIL_dtos1( int d ); +extern char *UTIL_dtos2( int d ); +extern char *UTIL_dtos3( int d ); +extern char *UTIL_dtos4( int d ); + +// Writes message to console with timestamp and FragLog header. +extern void UTIL_LogPrintf( char *fmt, ... ); + +// Sorta like FInViewCone, but for nonmonsters. +extern float UTIL_DotPoints ( const Vector &vecSrc, const Vector &vecCheck, const Vector &vecDir ); + +extern void UTIL_StripToken( const char *pKey, char *pDest );// for redundant keynames + +// Misc functions +extern void SetMovedir(entvars_t* pev); +extern Vector VecBModelOrigin( entvars_t* pevBModel ); +extern int BuildChangeList( LEVELLIST *pLevelList, int maxList ); + +// +// How did I ever live without ASSERT? +// +#ifdef DEBUG +void DBG_AssertFunction(BOOL fExpr, const char* szExpr, const char* szFile, int szLine, const char* szMessage); +#define ASSERT(f) DBG_AssertFunction(f, #f, __FILE__, __LINE__, NULL) +#define ASSERTSZ(f, sz) DBG_AssertFunction(f, #f, __FILE__, __LINE__, sz) +#else // !DEBUG +#define ASSERT(f) +#define ASSERTSZ(f, sz) +#endif // !DEBUG + + +extern DLL_GLOBAL const Vector g_vecZero; + +// +// Constants that were used only by QC (maybe not used at all now) +// +// Un-comment only as needed +// +#define LANGUAGE_ENGLISH 0 +#define LANGUAGE_GERMAN 1 +#define LANGUAGE_FRENCH 2 +#define LANGUAGE_BRITISH 3 + +extern DLL_GLOBAL int g_Language; + +#define AMBIENT_SOUND_STATIC 0 // medium radius attenuation +#define AMBIENT_SOUND_EVERYWHERE 1 +#define AMBIENT_SOUND_SMALLRADIUS 2 +#define AMBIENT_SOUND_MEDIUMRADIUS 4 +#define AMBIENT_SOUND_LARGERADIUS 8 +#define AMBIENT_SOUND_START_SILENT 16 +#define AMBIENT_SOUND_NOT_LOOPING 32 + +#define SPEAKER_START_SILENT 1 // wait for trigger 'on' to start announcements + +#define SND_SPAWNING (1<<8) // duplicated in protocol.h we're spawing, used in some cases for ambients +#define SND_STOP (1<<5) // duplicated in protocol.h stop sound +#define SND_CHANGE_VOL (1<<6) // duplicated in protocol.h change sound vol +#define SND_CHANGE_PITCH (1<<7) // duplicated in protocol.h change sound pitch + +#define LFO_SQUARE 1 +#define LFO_TRIANGLE 2 +#define LFO_RANDOM 3 + +// func_rotating +#define SF_BRUSH_ROTATE_Y_AXIS 0 +#define SF_BRUSH_ROTATE_INSTANT 1 +#define SF_BRUSH_ROTATE_BACKWARDS 2 +#define SF_BRUSH_ROTATE_Z_AXIS 4 +#define SF_BRUSH_ROTATE_X_AXIS 8 +#define SF_PENDULUM_AUTO_RETURN 16 +#define SF_PENDULUM_PASSABLE 32 + + +#define SF_BRUSH_ROTATE_SMALLRADIUS 128 +#define SF_BRUSH_ROTATE_MEDIUMRADIUS 256 +#define SF_BRUSH_ROTATE_LARGERADIUS 512 + +#define PUSH_BLOCK_ONLY_X 1 +#define PUSH_BLOCK_ONLY_Y 2 + +// QUAKECLASSIC: Different player sizes +//#define VEC_HULL_MIN Vector(-16, -16, -36) +//#define VEC_HULL_MAX Vector( 16, 16, 36) +#define VEC_HULL_MIN Vector(-16, -16, -24) +#define VEC_HULL_MAX Vector(16, 16, 32) + +#define VEC_HUMAN_HULL_MIN Vector( -16, -16, 0 ) +#define VEC_HUMAN_HULL_MAX Vector( 16, 16, 72 ) +#define VEC_HUMAN_HULL_DUCK Vector( 16, 16, 36 ) + +#define VEC_VIEW Vector( 0, 0, 18 ) + +#define VEC_DUCK_HULL_MIN Vector(-16, -16, -18 ) +#define VEC_DUCK_HULL_MAX Vector( 16, 16, 18) +#define VEC_DUCK_VIEW Vector( 0, 0, 12 ) + +#define SVC_TEMPENTITY 23 +#define SVC_INTERMISSION 30 +#define SVC_CDTRACK 32 +#define SVC_WEAPONANIM 35 +#define SVC_ROOMTYPE 37 +#define SVC_ADDANGLE 38 // [vec3] add this angle to the view angle +#define SVC_NEWUSERMSG 39 +#define SVC_DIRECTOR 51 + + +// triggers +#define SF_TRIGGER_ALLOWMONSTERS 1// monsters allowed to fire this trigger +#define SF_TRIGGER_NOCLIENTS 2// players not allowed to fire this trigger +#define SF_TRIGGER_PUSHABLES 4// only pushables can fire this trigger + +// func breakable +#define SF_BREAK_TRIGGER_ONLY 1// may only be broken by trigger +#define SF_BREAK_TOUCH 2// can be 'crashed through' by running player (plate glass) +#define SF_BREAK_PRESSURE 4// can be broken by a player standing on it +#define SF_BREAK_CROWBAR 256// instant break if hit with crowbar + +// func_pushable (it's also func_breakable, so don't collide with those flags) +#define SF_PUSH_BREAKABLE 128 + +#define SF_LIGHT_START_OFF 1 + +#define SPAWNFLAG_NOMESSAGE 1 +#define SPAWNFLAG_NOTOUCH 1 +#define SPAWNFLAG_DROIDONLY 4 + +#define SPAWNFLAG_USEONLY 1 // can't be touched, must be used (buttons) + +#define TELE_PLAYER_ONLY 1 +#define TELE_SILENT 2 + +#define SF_TRIG_PUSH_ONCE 1 + + +// Sound Utilities + +// sentence groups +#define CBSENTENCENAME_MAX 16 +#define CVOXFILESENTENCEMAX 1536 // max number of sentences in game. NOTE: this must match + // CVOXFILESENTENCEMAX in engine\sound.h!!! + +extern char gszallsentencenames[CVOXFILESENTENCEMAX][CBSENTENCENAME_MAX]; +extern int gcallsentences; + +int USENTENCEG_Pick(int isentenceg, char *szfound); +int USENTENCEG_PickSequential(int isentenceg, char *szfound, int ipick, int freset); +void USENTENCEG_InitLRU(unsigned char *plru, int count); + +void SENTENCEG_Init(); +void SENTENCEG_Stop(edict_t *entity, int isentenceg, int ipick); +int SENTENCEG_PlayRndI(edict_t *entity, int isentenceg, float volume, float attenuation, int flags, int pitch); +int SENTENCEG_PlayRndSz(edict_t *entity, const char *szrootname, float volume, float attenuation, int flags, int pitch); +int SENTENCEG_PlaySequentialSz(edict_t *entity, const char *szrootname, float volume, float attenuation, int flags, int pitch, int ipick, int freset); +int SENTENCEG_GetIndex(const char *szrootname); +int SENTENCEG_Lookup(const char *sample, char *sentencenum); + +void TEXTURETYPE_Init(); +char TEXTURETYPE_Find(char *name); +float TEXTURETYPE_PlaySound(TraceResult *ptr, Vector vecSrc, Vector vecEnd, int iBulletType); + +#define CBTEXTURENAMEMAX 13 // only load first n chars of name + +#define CHAR_TEX_CONCRETE 'C' // texture types +#define CHAR_TEX_METAL 'M' +#define CHAR_TEX_DIRT 'D' +#define CHAR_TEX_VENT 'V' +#define CHAR_TEX_GRATE 'G' +#define CHAR_TEX_TILE 'T' +#define CHAR_TEX_SLOSH 'S' +#define CHAR_TEX_WOOD 'W' +#define CHAR_TEX_COMPUTER 'P' +#define CHAR_TEX_GLASS 'Y' +#define CHAR_TEX_FLESH 'F' + +// NOTE: use EMIT_SOUND_DYN to set the pitch of a sound. Pitch of 100 +// is no pitch shift. Pitch > 100 up to 255 is a higher pitch, pitch < 100 +// down to 1 is a lower pitch. 150 to 70 is the realistic range. +// EMIT_SOUND_DYN with pitch != 100 should be used sparingly, as it's not quite as +// fast as EMIT_SOUND (the pitchshift mixer is not native coded). + +void EMIT_SOUND_DYN(edict_t *entity, int channel, const char *sample, float volume, float attenuation, + int flags, int pitch); + + +inline void EMIT_SOUND(edict_t *entity, int channel, const char *sample, float volume, float attenuation) +{ + EMIT_SOUND_DYN(entity, channel, sample, volume, attenuation, 0, PITCH_NORM); +} + +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 < static_cast(ARRAYSIZE( a )); i++ ) PRECACHE_SOUND((char *) a [i]); } + +#define EMIT_SOUND_ARRAY_DYN( chan, array ) \ + EMIT_SOUND_DYN ( ENT(pev), chan , array [ RANDOM_LONG(0,ARRAYSIZE( array )-1) ], 1.0, ATTN_NORM, 0, RANDOM_LONG(95,105) ); + +#define RANDOM_SOUND_ARRAY( array ) (array) [ RANDOM_LONG(0,ARRAYSIZE( (array) )-1) ] + + +#define PLAYBACK_EVENT( flags, who, index ) PLAYBACK_EVENT_FULL( flags, who, index, 0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0 ); +#define PLAYBACK_EVENT_DELAY( flags, who, index, delay ) PLAYBACK_EVENT_FULL( flags, who, index, delay, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0 ); + +#define GROUP_OP_AND 0 +#define GROUP_OP_NAND 1 + +extern int g_groupmask; +extern int g_groupop; + +class UTIL_GroupTrace +{ +public: + UTIL_GroupTrace( int groupmask, int op ); + ~UTIL_GroupTrace( void ); + +private: + int m_oldgroupmask, m_oldgroupop; +}; + +void UTIL_SetGroupTrace( int groupmask, int op ); +void UTIL_UnsetGroupTrace( void ); + +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ); +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ); + +float UTIL_WeaponTimeBase( void ); + +typedef enum { + RED = 1, + BLUE, + GREEN, + YELLOW, + WHITE +} printcolor_t; + +typedef enum { + F_IN_OUT, + CREDITS, + SCANOUT +} printeffect_t; + +typedef enum { + + WIN_MSG, + CRITICAL, + INFO, + LEADER_HIT, + MISC_SHIT, + CHASECAM, + CHASECAM_TARGET, + NOTIFY, + +} effectchannel_t; + +extern void EffectPrint( CBasePlayer *pPlayer, int color, int effect, int channel, char *text ); diff --git a/hlsdk/dmc/dlls/vector.h b/hlsdk/dmc/dlls/vector.h new file mode 100644 index 00000000..08148de6 --- /dev/null +++ b/hlsdk/dmc/dlls/vector.h @@ -0,0 +1,111 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef VECTOR_H +#define VECTOR_H + +//========================================================= +// 2DVector - used for many pathfinding and many other +// operations that are treated as planar rather than 3d. +//========================================================= +class Vector2D +{ +public: + inline Vector2D(void): x(0.0), y(0.0) { } + inline Vector2D(float X, float Y): x(0.0), y(0.0) { x = X; y = Y; } + inline Vector2D operator+(const Vector2D& v) const { return Vector2D(x+v.x, y+v.y); } + inline Vector2D operator-(const Vector2D& v) const { return Vector2D(x-v.x, y-v.y); } + inline Vector2D operator*(float fl) const { return Vector2D(x*fl, y*fl); } + inline Vector2D operator/(float fl) const { return Vector2D(x/fl, y/fl); } + + inline float Length(void) const { return sqrt(x*x + y*y ); } + + inline Vector2D Normalize ( void ) const + { + + float flLen = Length(); + if ( flLen == 0 ) + { + return Vector2D( 0, 0 ); + } + else + { + flLen = 1 / flLen; + return Vector2D( x * flLen, y * flLen ); + } + } + + vec_t x, y; +}; + +inline float DotProduct(const Vector2D& a, const Vector2D& b) { return( a.x*b.x + a.y*b.y ); } +inline Vector2D operator*(float fl, const Vector2D& v) { return v * fl; } + +//========================================================= +// 3D Vector +//========================================================= +class Vector // same data-layout as engine's vec3_t, +{ // which is a vec_t[3] +public: + // Construction/destruction + inline Vector(void): x(0.0), y(0.0), z(0.0) { } + inline Vector(float X, float Y, float Z): x(0.0), y(0.0), z(0.0) { x = X; y = Y; z = Z; } + //inline Vector(double X, double Y, double Z) { x = (float)X; y = (float)Y; z = (float)Z; } + //inline Vector(int X, int Y, int Z) { x = (float)X; y = (float)Y; z = (float)Z; } + inline Vector(const Vector& v): x(0.0), y(0.0), z(0.0) { x = v.x; y = v.y; z = v.z; } + inline Vector(float rgfl[3]): x(0.0), y(0.0), z(0.0) { x = rgfl[0]; y = rgfl[1]; z = rgfl[2]; } + + // Operators + inline Vector operator-(void) const { return Vector(-x,-y,-z); } + inline int operator==(const Vector& v) const { return x==v.x && y==v.y && z==v.z; } + inline int operator!=(const Vector& v) const { return !(*this==v); } + inline Vector operator+(const Vector& v) const { return Vector(x+v.x, y+v.y, z+v.z); } + inline Vector operator-(const Vector& v) const { return Vector(x-v.x, y-v.y, z-v.z); } + inline Vector operator*(float fl) const { return Vector(x*fl, y*fl, z*fl); } + inline Vector operator/(float fl) const { return Vector(x/fl, y/fl, z/fl); } + + // Methods + inline void CopyToArray(float* rgfl) const { rgfl[0] = x, rgfl[1] = y, rgfl[2] = z; } + inline float Length(void) const { return sqrt(x*x + y*y + z*z); } + operator float *() { return &x; } // Vectors will now automatically convert to float * when needed + operator const float *() const { return &x; } // Vectors will now automatically convert to float * when needed + inline Vector Normalize(void) const + { + float flLen = Length(); + if (flLen == 0) return Vector(0,0,1); // ???? + flLen = 1 / flLen; + return Vector(x * flLen, y * flLen, z * flLen); + } + + inline Vector2D Make2D ( void ) const + { + Vector2D Vec2; + + Vec2.x = x; + Vec2.y = y; + + return Vec2; + } + inline float Length2D(void) const { return sqrt(x*x + y*y); } + + // Members + vec_t x, y, z; +}; +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 ); } + + + +#endif diff --git a/hlsdk/dmc/dlls/weapons.cpp b/hlsdk/dmc/dlls/weapons.cpp new file mode 100644 index 00000000..8b577800 --- /dev/null +++ b/hlsdk/dmc/dlls/weapons.cpp @@ -0,0 +1,1417 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== weapons.cpp ======================================================== + + functions governing the selection/use of weapons for players + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "decals.h" +#include "gamerules.h" +#include "quake_gun.h" + +extern CGraph WorldGraph; +extern int gEvilImpulse101; + + +#define NOT_USED 255 + +DLL_GLOBAL short g_sModelIndexLaser;// holds the index for the laser beam +DLL_GLOBAL const char *g_pModelNameLaser = "sprites/laserbeam.spr"; +DLL_GLOBAL short g_sModelIndexLaserDot;// holds the index for the laser beam dot +DLL_GLOBAL short g_sModelIndexFireball;// holds the index for the fireball +DLL_GLOBAL short g_sModelIndexSmoke;// holds the index for the smoke cloud +DLL_GLOBAL short g_sModelIndexWExplosion;// holds the index for the underwater explosion +DLL_GLOBAL short g_sModelIndexBubbles;// holds the index for the bubbles model +DLL_GLOBAL short g_sModelIndexBloodDrop;// holds the sprite index for the initial blood +DLL_GLOBAL short g_sModelIndexBloodSpray;// holds the sprite index for splattered blood + +ItemInfo CBasePlayerItem::ItemInfoArray[MAX_WEAPONS]; +AmmoInfo CBasePlayerItem::AmmoInfoArray[MAX_AMMO_SLOTS]; + +extern int gmsgCurWeapon; + +MULTIDAMAGE gMultiDamage; + +#define TRACER_FREQ 4 // Tracers fire every fourth bullet + + +//========================================================= +// MaxAmmoCarry - pass in a name and this function will tell +// you the maximum amount of that type of ammunition that a +// player can carry. +//========================================================= +int MaxAmmoCarry( int iszName ) +{ + for ( int i = 0; i < MAX_WEAPONS; i++ ) + { + if ( CBasePlayerItem::ItemInfoArray[i].pszAmmo1 && !strcmp( STRING(iszName), CBasePlayerItem::ItemInfoArray[i].pszAmmo1 ) ) + return CBasePlayerItem::ItemInfoArray[i].iMaxAmmo1; + if ( CBasePlayerItem::ItemInfoArray[i].pszAmmo2 && !strcmp( STRING(iszName), CBasePlayerItem::ItemInfoArray[i].pszAmmo2 ) ) + return CBasePlayerItem::ItemInfoArray[i].iMaxAmmo2; + } + + ALERT( at_console, "MaxAmmoCarry() doesn't recognize '%s'!\n", STRING( iszName ) ); + return -1; +} + + +/* +============================================================================== + +MULTI-DAMAGE + +Collects multiple small damages into a single damage + +============================================================================== +*/ + +// +// ClearMultiDamage - resets the global multi damage accumulator +// +void ClearMultiDamage(void) +{ + gMultiDamage.pEntity = NULL; + gMultiDamage.amount = 0; + gMultiDamage.type = 0; +} + + +// +// ApplyMultiDamage - inflicts contents of global multi damage register on gMultiDamage.pEntity +// +// GLOBALS USED: +// gMultiDamage + +void ApplyMultiDamage(entvars_t *pevInflictor, entvars_t *pevAttacker ) +{ + Vector vecSpot1;//where blood comes from + Vector vecDir;//direction blood should go + TraceResult tr; + + if ( !gMultiDamage.pEntity ) + return; + + gMultiDamage.pEntity->TakeDamage(pevInflictor, pevAttacker, gMultiDamage.amount, gMultiDamage.type ); +} + + +// GLOBALS USED: +// gMultiDamage + +void AddMultiDamage( entvars_t *pevInflictor, CBaseEntity *pEntity, float flDamage, int bitsDamageType) +{ + if ( !pEntity ) + return; + + gMultiDamage.type |= bitsDamageType; + + if ( pEntity != gMultiDamage.pEntity ) + { + ApplyMultiDamage(pevInflictor,pevInflictor); // UNDONE: wrong attacker! + gMultiDamage.pEntity = pEntity; + gMultiDamage.amount = 0; + } + + gMultiDamage.amount += flDamage; +} + +/* +================ +SpawnBlood +================ +*/ +void SpawnBlood(Vector vecSpot, int bloodColor, float flDamage) +{ + UTIL_BloodDrips( vecSpot, g_vecAttackDir, bloodColor, (int)flDamage ); +} + + +int DamageDecal( CBaseEntity *pEntity, int bitsDamageType ) +{ + if ( !pEntity ) + return (DECAL_GUNSHOT1 + RANDOM_LONG(0,4)); + + return pEntity->DamageDecal( bitsDamageType ); +} + +void DecalGunshot( TraceResult *pTrace, int iBulletType ) +{ + // Is the entity valid + if ( !UTIL_IsValidEntity( pTrace->pHit ) ) + return; + + if ( VARS(pTrace->pHit)->solid == SOLID_BSP || VARS(pTrace->pHit)->movetype == MOVETYPE_PUSHSTEP ) + { + CBaseEntity *pEntity = NULL; + // Decal the wall with a gunshot + if ( !FNullEnt(pTrace->pHit) ) + pEntity = CBaseEntity::Instance(pTrace->pHit); + + switch( iBulletType ) + { + 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; + } + } +} + + + +// +// EjectBrass - tosses a brass shell from passed origin at passed velocity +// +void EjectBrass ( const Vector &vecOrigin, const Vector &vecVelocity, float rotation, int model, int soundtype ) +{ + // FIX: when the player shoots, their gun isn't in the same position as it is on the model other players see. + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecOrigin ); + WRITE_BYTE( TE_MODEL); + WRITE_COORD( vecOrigin.x); + WRITE_COORD( vecOrigin.y); + WRITE_COORD( vecOrigin.z); + WRITE_COORD( vecVelocity.x); + WRITE_COORD( vecVelocity.y); + WRITE_COORD( vecVelocity.z); + WRITE_ANGLE( rotation ); + WRITE_SHORT( model ); + WRITE_BYTE ( soundtype); + WRITE_BYTE ( 25 );// 2.5 seconds + MESSAGE_END(); +} + + +#if 0 +// UNDONE: This is no longer used? +void ExplodeModel( const Vector &vecOrigin, float speed, int model, int count ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecOrigin ); + WRITE_BYTE ( TE_EXPLODEMODEL ); + WRITE_COORD( vecOrigin.x ); + WRITE_COORD( vecOrigin.y ); + WRITE_COORD( vecOrigin.z ); + WRITE_COORD( speed ); + WRITE_SHORT( model ); + WRITE_SHORT( count ); + WRITE_BYTE ( 15 );// 1.5 seconds + MESSAGE_END(); +} +#endif + + +int giAmmoIndex = 0; + +// Precaches the ammo and queues the ammo info for sending to clients +void AddAmmoNameToAmmoRegistry( const char *szAmmoname ) +{ + // make sure it's not already in the registry + for ( int i = 0; i < MAX_AMMO_SLOTS; i++ ) + { + if ( !CBasePlayerItem::AmmoInfoArray[i].pszName) + continue; + + if ( stricmp( CBasePlayerItem::AmmoInfoArray[i].pszName, szAmmoname ) == 0 ) + return; // ammo already in registry, just quite + } + + + giAmmoIndex++; + ASSERT( giAmmoIndex < MAX_AMMO_SLOTS ); + if ( giAmmoIndex >= MAX_AMMO_SLOTS ) + giAmmoIndex = 0; + + CBasePlayerItem::AmmoInfoArray[giAmmoIndex].pszName = szAmmoname; + CBasePlayerItem::AmmoInfoArray[giAmmoIndex].iId = giAmmoIndex; // yes, this info is redundant +} + + +// Precaches the weapon and queues the weapon info for sending to clients +void UTIL_PrecacheOtherWeapon( const char *szClassname ) +{ + edict_t *pent; + + pent = CREATE_NAMED_ENTITY( MAKE_STRING( szClassname ) ); + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in UTIL_PrecacheOtherWeapon\n" ); + return; + } + + CBaseEntity *pEntity = CBaseEntity::Instance (VARS( pent )); + + if (pEntity) + { + ItemInfo II; + pEntity->Precache( ); + memset( &II, 0, sizeof II ); + if ( ((CBasePlayerItem*)pEntity)->GetItemInfo( &II ) ) + { + CBasePlayerItem::ItemInfoArray[II.iId] = II; + + if ( II.pszAmmo1 && *II.pszAmmo1 ) + { + AddAmmoNameToAmmoRegistry( II.pszAmmo1 ); + } + + if ( II.pszAmmo2 && *II.pszAmmo2 ) + { + AddAmmoNameToAmmoRegistry( II.pszAmmo2 ); + } + + memset( &II, 0, sizeof II ); + } + } + + REMOVE_ENTITY(pent); +} + +// called by worldspawn +void W_Precache(void) +{ + memset( CBasePlayerItem::ItemInfoArray, 0, sizeof(CBasePlayerItem::ItemInfoArray) ); + memset( CBasePlayerItem::AmmoInfoArray, 0, sizeof(CBasePlayerItem::AmmoInfoArray) ); + giAmmoIndex = 0; + + // quake gun + UTIL_PrecacheOtherWeapon( "weapon_quakegun" ); + AddAmmoNameToAmmoRegistry( "shells" ); + AddAmmoNameToAmmoRegistry( "nails" ); + AddAmmoNameToAmmoRegistry( "rockets" ); + AddAmmoNameToAmmoRegistry( "cells" ); + + // global sprites + g_sModelIndexFireball = PRECACHE_MODEL ("sprites/zerogxplode.spr");// fireball + g_sModelIndexWExplosion = PRECACHE_MODEL ("sprites/WXplo1.spr");// underwater fireball + g_sModelIndexSmoke = PRECACHE_MODEL ("sprites/steam1.spr");// smoke + g_sModelIndexBubbles = PRECACHE_MODEL ("sprites/bubble.spr");//bubbles + g_sModelIndexBloodSpray = PRECACHE_MODEL ("sprites/bloodspray.spr"); // initial blood + g_sModelIndexBloodDrop = PRECACHE_MODEL ("sprites/blood.spr"); // splattered blood + + // used by explosions + PRECACHE_SOUND ("weapons/debris1.wav");// explosion aftermaths + PRECACHE_SOUND ("weapons/debris2.wav");// explosion aftermaths + PRECACHE_SOUND ("weapons/debris3.wav");// explosion aftermaths + + PRECACHE_SOUND ("weapons/grenade_hit1.wav");//grenade + PRECACHE_SOUND ("weapons/grenade_hit2.wav");//grenade + PRECACHE_SOUND ("weapons/grenade_hit3.wav");//grenade + + PRECACHE_SOUND ("weapons/bullet_hit1.wav"); // hit by bullet + PRECACHE_SOUND ("weapons/bullet_hit2.wav"); // hit by bullet + + PRECACHE_SOUND ("items/weapondrop1.wav");// weapon falls to the ground + + PRECACHE_EVENT( 1, "events/shotgun1.sc" ); + PRECACHE_EVENT( 1, "events/shotgun2.sc" ); + PRECACHE_EVENT( 1, "events/axe.sc" ); + PRECACHE_EVENT( 1, "events/axeswing.sc" ); + PRECACHE_EVENT( 1, "events/rocket.sc" ); + PRECACHE_EVENT( 1, "events/grenade.sc" ); + PRECACHE_EVENT( 1, "events/lightning.sc" ); + PRECACHE_EVENT( 1, "events/spike.sc" ); + PRECACHE_EVENT( 1, "events/superspike.sc" ); +} + + + + +TYPEDESCRIPTION CBasePlayerItem::m_SaveData[] = +{ + DEFINE_FIELD( CBasePlayerItem, m_pPlayer, FIELD_CLASSPTR ), + DEFINE_FIELD( CBasePlayerItem, m_pNext, FIELD_CLASSPTR ), + //DEFINE_FIELD( CBasePlayerItem, m_fKnown, FIELD_INTEGER ),Reset to zero on load + DEFINE_FIELD( CBasePlayerItem, m_iId, FIELD_INTEGER ), + // DEFINE_FIELD( CBasePlayerItem, m_iIdPrimary, FIELD_INTEGER ), + // DEFINE_FIELD( CBasePlayerItem, m_iIdSecondary, FIELD_INTEGER ), +}; +IMPLEMENT_SAVERESTORE( CBasePlayerItem, CBaseAnimating ); + + +TYPEDESCRIPTION CBasePlayerWeapon::m_SaveData[] = +{ + DEFINE_FIELD( CBasePlayerWeapon, m_flNextPrimaryAttack, FIELD_TIME ), + DEFINE_FIELD( CBasePlayerWeapon, m_flNextSecondaryAttack, FIELD_TIME ), + DEFINE_FIELD( CBasePlayerWeapon, m_flTimeWeaponIdle, FIELD_TIME ), + DEFINE_FIELD( CBasePlayerWeapon, m_iPrimaryAmmoType, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerWeapon, m_iSecondaryAmmoType, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerWeapon, m_iClip, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerWeapon, m_iDefaultAmmo, FIELD_INTEGER ), +// DEFINE_FIELD( CBasePlayerWeapon, m_iClientClip, FIELD_INTEGER ) , reset to zero on load so hud gets updated correctly +// DEFINE_FIELD( CBasePlayerWeapon, m_iClientWeaponState, FIELD_INTEGER ), reset to zero on load so hud gets updated correctly +}; + +IMPLEMENT_SAVERESTORE( CBasePlayerWeapon, CBasePlayerItem ); + + +void CBasePlayerItem :: SetObjectCollisionBox( void ) +{ + pev->absmin = pev->origin + Vector(-24, -24, 0); + pev->absmax = pev->origin + Vector(24, 24, 16); +} + + +//========================================================= +// Sets up movetype, size, solidtype for a new weapon. +//========================================================= +void CBasePlayerItem :: FallInit( void ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_BBOX; + + UTIL_SetOrigin( pev, pev->origin ); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0) );//pointsize until it lands on the ground. + + SetTouch( &CBasePlayerItem::DefaultTouch ); + SetThink( &CBasePlayerItem::FallThink ); + + pev->nextthink = gpGlobals->time + 0.1; +} + +//========================================================= +// FallThink - Items that have just spawned run this think +// to catch them when they hit the ground. Once we're sure +// that the object is grounded, we change its solid type +// to trigger and set it in a large box that helps the +// player get it. +//========================================================= +void CBasePlayerItem::FallThink ( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + + + if ( pev->flags & FL_ONGROUND ) + { + // clatter if we have an owner (i.e., dropped by someone) + // don't clatter if the gun is waiting to respawn (if it's waiting, it is invisible!) + if ( !FNullEnt( pev->owner ) ) + { + int pitch = 95 + RANDOM_LONG(0,29); + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "items/weapondrop1.wav", 1, ATTN_NORM, 0, pitch); + } + + // lie flat + pev->angles.x = 0; + pev->angles.z = 0; + + Materialize(); + } +} + +//========================================================= +// Materialize - make a CBasePlayerItem visible and tangible +//========================================================= +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; + } + + pev->solid = SOLID_TRIGGER; + + UTIL_SetOrigin( pev, pev->origin );// link into world. + SetTouch (&CBasePlayerItem::DefaultTouch); + SetThink (NULL); + +} + +//========================================================= +// AttemptToMaterialize - the item is trying to rematerialize, +// should it do so now or wait longer? +//========================================================= +void CBasePlayerItem::AttemptToMaterialize( void ) +{ + float time = g_pGameRules->FlWeaponTryRespawn( this ); + + if ( time == 0 ) + { + Materialize(); + return; + } + + pev->nextthink = gpGlobals->time + time; +} + +//========================================================= +// CheckRespawn - a player is taking this weapon, should +// it respawn? +//========================================================= +void CBasePlayerItem :: CheckRespawn ( void ) +{ + switch ( g_pGameRules->WeaponShouldRespawn( this ) ) + { + case GR_WEAPON_RESPAWN_YES: + Respawn(); + break; + case GR_WEAPON_RESPAWN_NO: + return; + break; + } +} + +//========================================================= +// Respawn- this item is already in the world, but it is +// invisible and intangible. Make it visible and tangible. +//========================================================= +CBaseEntity* CBasePlayerItem::Respawn( void ) +{ + // make a copy of this weapon that is invisible and inaccessible to players (no touch function). The weapon spawn/respawn code + // will decide when to make the weapon visible and touchable. + CBaseEntity *pNewWeapon = CBaseEntity::Create( (char *)STRING( pev->classname ), g_pGameRules->VecWeaponRespawnSpot( this ), pev->angles, pev->owner ); + + if ( pNewWeapon ) + { + pNewWeapon->pev->effects |= EF_NODRAW;// invisible for now + pNewWeapon->SetTouch( NULL );// no touch + pNewWeapon->SetThink( &CBasePlayerItem::AttemptToMaterialize ); + + DROP_TO_FLOOR ( ENT(pev) ); + + // not a typo! We want to know when the weapon the player just picked up should respawn! This new entity we created is the replacement, + // but when it should respawn is based on conditions belonging to the weapon that was taken. + pNewWeapon->pev->nextthink = g_pGameRules->FlWeaponRespawnTime( this ); + } + else + { + ALERT ( at_console, "Respawn failed to create %s!\n", STRING( pev->classname ) ); + } + + return pNewWeapon; +} + +void CBasePlayerItem::DefaultTouch( CBaseEntity *pOther ) +{ + // if it's not a player, ignore + if ( !pOther->IsPlayer() ) + return; + + CBasePlayer *pPlayer = (CBasePlayer *)pOther; + + // can I have this? + if ( !g_pGameRules->CanHavePlayerItem( pPlayer, this ) ) + { + + if ( gEvilImpulse101 || FClassnameIs( pev, "weapon_quakegun" ) ) + { + UTIL_Remove( this ); + } + return; + } + + if (pOther->AddPlayerItem( this )) + { + AttachToPlayer( pPlayer ); + EMIT_SOUND(ENT(pPlayer->pev), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM); + } + + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); // UNDONE: when should this happen? +} + +BOOL CanAttack( float attack_time, float curtime, BOOL isPredicted ) +{ + if ( !isPredicted ) + { + return ( attack_time <= curtime ) ? TRUE : FALSE; + } + else + { + return ( attack_time <= 0.0 ) ? TRUE : FALSE; + } +} + +void CBasePlayerWeapon::ItemPostFrame( void ) +{ + if ((m_fInReload) && ( m_pPlayer->m_flNextAttack <= UTIL_WeaponTimeBase() ) ) + { + // 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; + + m_fInReload = FALSE; + } + + if ((m_pPlayer->pev->button & IN_ATTACK2) && CanAttack( m_flNextSecondaryAttack, gpGlobals->time, UseDecrement() ) ) + { + 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) && CanAttack( m_flNextPrimaryAttack, gpGlobals->time, UseDecrement() ) ) + { + 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 + if ( !m_bPlayedIdleAnim ) + { + m_bPlayedIdleAnim = TRUE; + SendWeaponAnim( 0, 1 ); + + if ( m_pPlayer->m_iQuakeWeapon == IT_LIGHTNING ) + { + PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), m_pPlayer->m_usLightning, 0, (float *)&m_pPlayer->pev->origin, (float *)&m_pPlayer->pev->angles, 0.0, 0.0, 0, 1, 0, 0 ); + + if ( m_pPlayer->m_pActiveItem ) + ((CQuakeGun*)m_pPlayer->m_pActiveItem)->DestroyEffect(); + } + } + + WeaponIdle( ); + return; + } + + // catch all + if ( ShouldWeaponIdle() ) + { + WeaponIdle(); + } +} + +void CBasePlayerItem::DestroyItem( void ) +{ + if ( m_pPlayer ) + { + // if attached to a player, remove. + m_pPlayer->RemovePlayerItem( this ); + } + + Kill( ); +} + +int CBasePlayerItem::AddToPlayer( CBasePlayer *pPlayer ) +{ + m_pPlayer = pPlayer; + + return TRUE; +} + +void CBasePlayerItem::Drop( void ) +{ + SetTouch( NULL ); + SetThink(&CBaseEntity::SUB_Remove); + pev->nextthink = gpGlobals->time + .1; +} + +void CBasePlayerItem::Kill( void ) +{ + SetTouch( NULL ); + SetThink(&CBaseEntity::SUB_Remove); + pev->nextthink = gpGlobals->time + .1; +} + +void CBasePlayerItem::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->pev->viewmodel = 0; + m_pPlayer->pev->weaponmodel = 0; +} + +void CBasePlayerItem::AttachToPlayer ( CBasePlayer *pPlayer ) +{ + pev->movetype = MOVETYPE_FOLLOW; + pev->solid = SOLID_NOT; + 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->owner = pPlayer->edict(); + pev->nextthink = gpGlobals->time + .1; + SetTouch( NULL ); +} + +// CALLED THROUGH the newly-touched weapon's instance. The existing player weapon is pOriginal +int CBasePlayerWeapon::AddDuplicate( CBasePlayerItem *pOriginal ) +{ + if ( m_iDefaultAmmo ) + { + return ExtractAmmo( (CBasePlayerWeapon *)pOriginal ); + } + else + { + // a dead player dropped this. + return ExtractClipAmmo( (CBasePlayerWeapon *)pOriginal ); + } +} + + +int CBasePlayerWeapon::AddToPlayer( CBasePlayer *pPlayer ) +{ + int bResult = CBasePlayerItem::AddToPlayer( pPlayer ); + + pPlayer->pev->weapons |= (1<GetAmmoIndex( pszAmmo1() ); + m_iSecondaryAmmoType = pPlayer->GetAmmoIndex( pszAmmo2() ); + } + + + if (bResult) + return AddWeapon( ); + return FALSE; +} + +int CBasePlayerWeapon::UpdateClientData( CBasePlayer *pPlayer ) +{ + BOOL bSend = FALSE; + int state = 0; + if ( pPlayer->m_pActiveItem == this ) + { + if ( pPlayer->m_fOnTarget ) + state = WEAPON_IS_ONTARGET; + else + state = 1; + } + + // Forcing send of all data! + if ( !pPlayer->m_fWeapon ) + bSend = TRUE; + + // See if the Quake Gun has changed "weapons" + if ( pPlayer->m_iQuakeWeapon != pPlayer->m_iClientQuakeWeapon ) + bSend = TRUE; + + // QUAKECLASSIC + m_iClip = 0; + + int iId = 0; + + switch ( pPlayer->m_iQuakeWeapon ) + { + case IT_AXE: iId = IT_AXE; break; + case IT_SHOTGUN: iId = IT_SHOTGUN; break; + case IT_SUPER_SHOTGUN: iId = IT_SUPER_SHOTGUN; break; + case IT_NAILGUN: iId = IT_NAILGUN; break; + case IT_SUPER_NAILGUN: iId = IT_SUPER_NAILGUN; break; + case IT_GRENADE_LAUNCHER: iId = IT_GRENADE_LAUNCHER; break; + case IT_ROCKET_LAUNCHER: iId = IT_ROCKET_LAUNCHER; break; + case IT_LIGHTNING: iId = IT_LIGHTNING; break; + } + + if ( bSend ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pPlayer->pev ); + WRITE_BYTE( state ); + WRITE_BYTE( iId ); + WRITE_BYTE( m_iClip ); + MESSAGE_END(); + + m_iClientClip = m_iClip; + m_iClientWeaponState = state; + pPlayer->m_fWeapon = TRUE; + } + + if ( m_pNext ) + m_pNext->UpdateClientData( pPlayer ); + + return 1; +} + + +void CBasePlayerWeapon::SendWeaponAnim( int iAnim, int skiplocal ) +{ + m_pPlayer->pev->weaponanim = iAnim; + + if ( skiplocal && ENGINE_CANSKIP( m_pPlayer->edict() ) ) + return; + + MESSAGE_BEGIN( MSG_ONE, SVC_WEAPONANIM, NULL, m_pPlayer->pev ); + WRITE_BYTE( iAnim ); // sequence number + WRITE_BYTE( pev->body ); // weaponmodel bodygroup. + MESSAGE_END(); +} + +BOOL CBasePlayerWeapon :: AddPrimaryAmmo( int iCount, char *szName, int iMaxClip, int iMaxCarry ) +{ + int iIdAmmo; + + if (iMaxClip < 1) + { + m_iClip = -1; + iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName, iMaxCarry ); + } + else if (m_iClip == 0) + { + int i; + i = min( m_iClip + iCount, iMaxClip ) - m_iClip; + m_iClip += i; + iIdAmmo = m_pPlayer->GiveAmmo( iCount - i, szName, iMaxCarry ); + } + else + { + iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName, iMaxCarry ); + } + + // m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] = iMaxCarry; // hack for testing + + if (iIdAmmo > 0) + { + m_iPrimaryAmmoType = iIdAmmo; + if (m_pPlayer->HasPlayerItem( this ) ) + { + // 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); + } + } + + return iIdAmmo > 0 ? TRUE : FALSE; +} + + +BOOL CBasePlayerWeapon :: AddSecondaryAmmo( int iCount, char *szName, int iMax ) +{ + int iIdAmmo; + + iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName, iMax ); + + //m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] = iMax; // hack for testing + + if (iIdAmmo > 0) + { + m_iSecondaryAmmoType = iIdAmmo; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + } + return iIdAmmo > 0 ? TRUE : FALSE; +} + +//========================================================= +// IsUseable - this function determines whether or not a +// weapon is useable by the player in its current state. +// (does it have ammo loaded? do I have any ammo for the +// weapon?, etc) +//========================================================= +BOOL CBasePlayerWeapon :: IsUseable( void ) +{ + if ( m_iClip <= 0 ) + { + if ( m_pPlayer->m_rgAmmo[ PrimaryAmmoIndex() ] <= 0 && iMaxAmmo1() != -1 ) + { + // clip is empty (or nonexistant) and the player has no more ammo of this type. + return FALSE; + } + } + + return TRUE; +} + +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; +} + +BOOL CBasePlayerWeapon :: DefaultDeploy( char *szViewModel, char *szWeaponModel, int iAnim, char *szAnimExt, int skiplocal /* = 0 */ ) +{ + if (!CanDeploy( )) + return FALSE; + + m_bPlayedIdleAnim = FALSE; + + m_pPlayer->pev->viewmodel = MAKE_STRING(szViewModel); + m_pPlayer->pev->weaponmodel = MAKE_STRING(szWeaponModel); + strcpy( m_pPlayer->m_szAnimExtention, szAnimExt ); + SendWeaponAnim( iAnim, skiplocal ); + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.0; + + return TRUE; +} + + +BOOL CBasePlayerWeapon :: DefaultReload( int iClipSize, int iAnim, float fDelay ) +{ + 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 ); + + m_fInReload = TRUE; + + m_flTimeWeaponIdle = gpGlobals->time + 3; + return TRUE; +} + +BOOL CBasePlayerWeapon :: PlayEmptySound( void ) +{ + if (m_iPlayEmptySound) + { + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/357_cock1.wav", 0.8, ATTN_NORM); + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +void CBasePlayerWeapon :: ResetEmptySound( void ) +{ + m_iPlayEmptySound = 1; +} + +//========================================================= +//========================================================= +int CBasePlayerWeapon::PrimaryAmmoIndex( void ) +{ + return m_iPrimaryAmmoType; +} + +//========================================================= +//========================================================= +int CBasePlayerWeapon::SecondaryAmmoIndex( void ) +{ + return -1; +} + +void CBasePlayerWeapon::Holster( int skiplocal /* = 0 */ ) +{ + m_fInReload = FALSE; // cancel any reload in progress. + m_pPlayer->pev->viewmodel = 0; + m_pPlayer->pev->weaponmodel = 0; +} + +void CBasePlayerAmmo::Spawn( void ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 16)); + UTIL_SetOrigin( pev, pev->origin ); + + SetTouch( &CBasePlayerAmmo::DefaultTouch ); +} + +CBaseEntity* CBasePlayerAmmo::Respawn( void ) +{ + pev->effects |= EF_NODRAW; + SetTouch( NULL ); + + UTIL_SetOrigin( pev, g_pGameRules->VecAmmoRespawnSpot( this ) );// move to wherever I'm supposed to repawn. + + SetThink( &CBasePlayerAmmo::Materialize ); + pev->nextthink = g_pGameRules->FlAmmoRespawnTime( this ); + + return this; +} + +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; + } + + SetTouch( &CBasePlayerAmmo::DefaultTouch ); +} + +void CBasePlayerAmmo :: DefaultTouch( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + { + return; + } + + if (AddAmmo( pOther )) + { + if ( g_pGameRules->AmmoShouldRespawn( this ) == GR_AMMO_RESPAWN_YES ) + { + Respawn(); + } + else + { + SetTouch( NULL ); + SetThink(&CBaseEntity::SUB_Remove); + pev->nextthink = gpGlobals->time + .1; + } + } + else if (gEvilImpulse101) + { + // evil impulse 101 hack, kill always + SetTouch( NULL ); + SetThink(&CBaseEntity::SUB_Remove); + pev->nextthink = gpGlobals->time + .1; + } +} + +//========================================================= +// called by the new item with the existing item as parameter +// +// if we call ExtractAmmo(), it's because the player is picking up this type of weapon for +// the first time. If it is spawned by the world, m_iDefaultAmmo will have a default ammo amount in it. +// if this is a weapon dropped by a dying player, has 0 m_iDefaultAmmo, which means only the ammo in +// the weapon clip comes along. +//========================================================= +int CBasePlayerWeapon::ExtractAmmo( CBasePlayerWeapon *pWeapon ) +{ + int iReturn = 0; + + if ( pszAmmo1() != NULL ) + { + // blindly call with m_iDefaultAmmo. It's either going to be a value or zero. If it is zero, + // we only get the ammo in the weapon's clip, which is what we want. + iReturn = pWeapon->AddPrimaryAmmo( m_iDefaultAmmo, (char *)pszAmmo1(), iMaxClip(), iMaxAmmo1() ); + m_iDefaultAmmo = 0; + } + + if ( pszAmmo2() != NULL ) + { + iReturn = pWeapon->AddSecondaryAmmo( 0, (char *)pszAmmo2(), iMaxAmmo2() ); + } + + return iReturn; +} + +//========================================================= +// called by the new item's class with the existing item as parameter +//========================================================= +int CBasePlayerWeapon::ExtractClipAmmo( CBasePlayerWeapon *pWeapon ) +{ + int iAmmo; + + if ( m_iClip == WEAPON_NOCLIP ) + { + iAmmo = 0;// guns with no clips always come empty if they are second-hand + } + else + { + iAmmo = m_iClip; + } + + return pWeapon->m_pPlayer->GiveAmmo( iAmmo, (char *)pszAmmo1(), iMaxAmmo1() ); // , &m_iPrimaryAmmoType +} + +//========================================================= +// RetireWeapon - no more ammo for this gun, put it away. +//========================================================= +void CBasePlayerWeapon::RetireWeapon( void ) +{ + // first, no viewmodel at all. + m_pPlayer->pev->viewmodel = iStringNull; + m_pPlayer->pev->weaponmodel = iStringNull; + //m_pPlayer->pev->viewmodelindex = NULL; + + g_pGameRules->GetNextBestWeapon( m_pPlayer, this ); +} + +//********************************************************* +// weaponbox code: +//********************************************************* + +LINK_ENTITY_TO_CLASS( weaponbox, CWeaponBox ); + +TYPEDESCRIPTION CWeaponBox::m_SaveData[] = +{ + DEFINE_ARRAY( CWeaponBox, m_rgAmmo, FIELD_INTEGER, MAX_AMMO_SLOTS ), + DEFINE_ARRAY( CWeaponBox, m_rgiszAmmo, FIELD_STRING, MAX_AMMO_SLOTS ), + DEFINE_ARRAY( CWeaponBox, m_rgpPlayerItems, FIELD_CLASSPTR, MAX_ITEM_TYPES ), + DEFINE_FIELD( CWeaponBox, m_cAmmoTypes, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CWeaponBox, CBaseEntity ); + +//========================================================= +// +//========================================================= +void CWeaponBox::Precache( void ) +{ + PRECACHE_MODEL("models/w_weaponbox.mdl"); +} + +//========================================================= +//========================================================= +void CWeaponBox :: KeyValue( KeyValueData *pkvd ) +{ + if ( m_cAmmoTypes < MAX_AMMO_SLOTS ) + { + PackAmmo( ALLOC_STRING(pkvd->szKeyName), atoi(pkvd->szValue) ); + m_cAmmoTypes++;// count this new ammo type. + + pkvd->fHandled = TRUE; + } + else + { + ALERT ( at_console, "WeaponBox too full! only %d ammotypes allowed\n", MAX_AMMO_SLOTS ); + } +} + +//========================================================= +// CWeaponBox - Spawn +//========================================================= +void CWeaponBox::Spawn( void ) +{ + Precache( ); + + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + + UTIL_SetSize( pev, g_vecZero, g_vecZero ); + + SET_MODEL( ENT(pev), "models/w_weaponbox.mdl"); +} + +//========================================================= +// CWeaponBox - Kill - the think function that removes the +// box from the world. +//========================================================= +void CWeaponBox::Kill( void ) +{ + CBasePlayerItem *pWeapon; + int i; + + // destroy the weapons + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + pWeapon = m_rgpPlayerItems[ i ]; + + while ( pWeapon ) + { + pWeapon->SetThink(&CBaseEntity::SUB_Remove); + pWeapon->pev->nextthink = gpGlobals->time + 0.1; + pWeapon = pWeapon->m_pNext; + } + } + + // remove the box + UTIL_Remove( this ); +} + +//========================================================= +// CWeaponBox - Touch: try to add my contents to the toucher +// if the toucher is a player. +//========================================================= +void CWeaponBox::Touch( CBaseEntity *pOther ) +{ + if ( !(pev->flags & FL_ONGROUND ) ) + { + return; + } + + if ( !pOther->IsPlayer() ) + { + // only players may touch a weaponbox. + return; + } + + if ( !pOther->IsAlive() ) + { + // no dead guys. + return; + } + + CBasePlayer *pPlayer = (CBasePlayer *)pOther; + int i; + +// dole out ammo + for ( i = 0 ; i < MAX_AMMO_SLOTS ; i++ ) + { + if ( !FStringNull( m_rgiszAmmo[ i ] ) ) + { + // there's some ammo of this type. + pPlayer->GiveAmmo( m_rgAmmo[ i ], (char *)STRING( m_rgiszAmmo[ i ] ), MaxAmmoCarry( m_rgiszAmmo[ i ] ) ); + + //ALERT ( at_console, "Gave %d rounds of %s\n", m_rgAmmo[i], STRING(m_rgiszAmmo[i]) ); + + // now empty the ammo from the weaponbox since we just gave it to the player + m_rgiszAmmo[ i ] = iStringNull; + m_rgAmmo[ i ] = 0; + } + } + +// go through my weapons and try to give the usable ones to the player. +// it's important the the player be given ammo first, so the weapons code doesn't refuse +// to deploy a better weapon that the player may pick up because he has no ammo for it. + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + CBasePlayerItem *pItem; + + // have at least one weapon in this slot + while ( m_rgpPlayerItems[ i ] ) + { + //ALERT ( at_console, "trying to give %s\n", STRING( m_rgpPlayerItems[ i ]->pev->classname ) ); + + pItem = m_rgpPlayerItems[ i ]; + m_rgpPlayerItems[ i ] = m_rgpPlayerItems[ i ]->m_pNext;// unlink this weapon from the box + + if ( pPlayer->AddPlayerItem( pItem ) ) + { + pItem->AttachToPlayer( pPlayer ); + } + } + } + } + + EMIT_SOUND( pOther->edict(), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM ); + SetTouch(NULL); + UTIL_Remove(this); +} + +//========================================================= +// CWeaponBox - PackWeapon: Add this weapon to the box +//========================================================= +BOOL CWeaponBox::PackWeapon( CBasePlayerItem *pWeapon ) +{ + // is one of these weapons already packed in this box? + if ( HasWeapon( pWeapon ) ) + { + return FALSE;// box can only hold one of each weapon type + } + + if ( pWeapon->m_pPlayer ) + { + if ( !pWeapon->m_pPlayer->RemovePlayerItem( pWeapon ) ) + { + // failed to unhook the weapon from the player! + return FALSE; + } + } + + int iWeaponSlot = pWeapon->iItemSlot(); + + if ( m_rgpPlayerItems[ iWeaponSlot ] ) + { + // there's already one weapon in this slot, so link this into the slot's column + pWeapon->m_pNext = m_rgpPlayerItems[ iWeaponSlot ]; + m_rgpPlayerItems[ iWeaponSlot ] = pWeapon; + } + else + { + // first weapon we have for this slot + m_rgpPlayerItems[ iWeaponSlot ] = pWeapon; + pWeapon->m_pNext = NULL; + } + + pWeapon->pev->spawnflags |= SF_NORESPAWN;// never respawn + pWeapon->pev->movetype = MOVETYPE_NONE; + pWeapon->pev->solid = SOLID_NOT; + pWeapon->pev->effects = EF_NODRAW; + pWeapon->pev->modelindex = 0; + pWeapon->pev->model = iStringNull; + pWeapon->pev->owner = edict(); + pWeapon->SetThink( NULL );// crowbar may be trying to swing again, etc. + pWeapon->SetTouch( NULL ); + pWeapon->m_pPlayer = NULL; + + //ALERT ( at_console, "packed %s\n", STRING(pWeapon->pev->classname) ); + + return TRUE; +} + +//========================================================= +// CWeaponBox - PackAmmo +//========================================================= +BOOL CWeaponBox::PackAmmo( int iszName, int iCount ) +{ + int iMaxCarry; + + if ( FStringNull( iszName ) ) + { + // error here + ALERT ( at_console, "NULL String in PackAmmo!\n" ); + return FALSE; + } + + iMaxCarry = MaxAmmoCarry( iszName ); + + if ( iMaxCarry != -1 && iCount > 0 ) + { + //ALERT ( at_console, "Packed %d rounds of %s\n", iCount, STRING(iszName) ); + GiveAmmo( iCount, (char *)STRING( iszName ), iMaxCarry ); + return TRUE; + } + + return FALSE; +} + +//========================================================= +// CWeaponBox - GiveAmmo +//========================================================= +int CWeaponBox::GiveAmmo( int iCount, char *szName, int iMax, int *pIndex/* = NULL*/ ) +{ + int i; + + for (i = 1; i < MAX_AMMO_SLOTS && !FStringNull( m_rgiszAmmo[i] ); i++) + { + if (stricmp( szName, STRING( m_rgiszAmmo[i])) == 0) + { + if (pIndex) + *pIndex = i; + + int iAdd = min( iCount, iMax - m_rgAmmo[i]); + if (iCount == 0 || iAdd > 0) + { + m_rgAmmo[i] += iAdd; + + return i; + } + return -1; + } + } + if (i < MAX_AMMO_SLOTS) + { + if (pIndex) + *pIndex = i; + + m_rgiszAmmo[i] = MAKE_STRING( szName ); + m_rgAmmo[i] = iCount; + + return i; + } + ALERT( at_console, "out of named ammo slots\n"); + return i; +} + +//========================================================= +// CWeaponBox::HasWeapon - is a weapon of this type already +// packed in this box? +//========================================================= +BOOL CWeaponBox::HasWeapon( CBasePlayerItem *pCheckItem ) +{ + CBasePlayerItem *pItem = m_rgpPlayerItems[pCheckItem->iItemSlot()]; + + while (pItem) + { + if (FClassnameIs( pItem->pev, STRING( pCheckItem->pev->classname) )) + { + return TRUE; + } + pItem = pItem->m_pNext; + } + + return FALSE; +} + +//========================================================= +// CWeaponBox::IsEmpty - is there anything in this box? +//========================================================= +BOOL CWeaponBox::IsEmpty( void ) +{ + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + return FALSE; + } + } + + for ( i = 0 ; i < MAX_AMMO_SLOTS ; i++ ) + { + if ( !FStringNull( m_rgiszAmmo[ i ] ) ) + { + // still have a bit of this type of ammo + return FALSE; + } + } + + return TRUE; +} + +//========================================================= +//========================================================= +void CWeaponBox::SetObjectCollisionBox( void ) +{ + pev->absmin = pev->origin + Vector(-16, -16, 0); + pev->absmax = pev->origin + Vector(16, 16, 16); +} + +void CBasePlayerWeapon::PrintState( void ) +{ +} + + diff --git a/hlsdk/dmc/dlls/weapons.h b/hlsdk/dmc/dlls/weapons.h new file mode 100644 index 00000000..a3d19d17 --- /dev/null +++ b/hlsdk/dmc/dlls/weapons.h @@ -0,0 +1,492 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef WEAPONS_H +#define WEAPONS_H + + +class CBasePlayer; +extern int gmsgWeapPickup; + +// Contact Grenade / Timed grenade / Satchel Charge +class CGrenade : public CBaseMonster +{ +public: + void Spawn( void ); + + typedef enum { SATCHEL_DETONATE = 0, SATCHEL_RELEASE } SATCHELCODE; + + static CGrenade *ShootTimed( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time ); + static CGrenade *ShootContact( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ); + static CGrenade *ShootSatchelCharge( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ); + static void UseSatchelCharges( entvars_t *pevOwner, SATCHELCODE code ); + + void Explode( Vector vecSrc, Vector vecAim ); + void Explode( TraceResult *pTrace, int bitsDamageType ); + void EXPORT Smoke( void ); + + void EXPORT BounceTouch( CBaseEntity *pOther ); + void EXPORT SlideTouch( CBaseEntity *pOther ); + void EXPORT ExplodeTouch( CBaseEntity *pOther ); + void EXPORT DangerSoundThink( void ); + void EXPORT PreDetonate( void ); + void EXPORT Detonate( void ); + void EXPORT DetonateUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT TumbleThink( void ); + + virtual void BounceSound( void ); + virtual int BloodColor( void ) { return DONT_BLEED; } + virtual void Killed( entvars_t *pevAttacker, int iGib ); + + BOOL m_fRegisteredSound;// whether or not this grenade has issued its DANGER sound to the world sound list yet. +}; + + +// 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<skin < 0 || (gpGlobals->deathmatch && FBitSet( pev->spawnflags, SF_DECAL_NOTINDEATHMATCH )) ) + { + REMOVE_ENTITY(ENT(pev)); + return; + } + + if ( FStringNull ( pev->targetname ) ) + { + SetThink( &CDecal::StaticDecal ); + // if there's no targetname, the decal will spray itself on as soon as the world is done spawning. + pev->nextthink = gpGlobals->time; + } + else + { + // if there IS a targetname, the decal sprays itself on when it is triggered. + SetThink ( &CBaseEntity::SUB_DoNothing ); + SetUse(&CDecal::TriggerDecal); + } +} + +void CDecal :: TriggerDecal ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // this is set up as a USE function for infodecals that have targetnames, so that the + // decal doesn't get applied until it is fired. (usually by a scripted sequence) + TraceResult trace; + int entityIndex; + + UTIL_TraceLine( pev->origin - Vector(5,5,5), pev->origin + Vector(5,5,5), ignore_monsters, ENT(pev), &trace ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY); + WRITE_BYTE( TE_BSPDECAL ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( (int)pev->skin ); + entityIndex = (short)ENTINDEX(trace.pHit); + WRITE_SHORT( entityIndex ); + if ( entityIndex ) + WRITE_SHORT( (int)VARS(trace.pHit)->modelindex ); + MESSAGE_END(); + + SetThink( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CDecal :: StaticDecal( void ) +{ + TraceResult trace; + int entityIndex, modelIndex; + + UTIL_TraceLine( pev->origin - Vector(5,5,5), pev->origin + Vector(5,5,5), ignore_monsters, ENT(pev), &trace ); + + entityIndex = (short)ENTINDEX(trace.pHit); + if ( entityIndex ) + modelIndex = (int)VARS(trace.pHit)->modelindex; + else + modelIndex = 0; + + g_engfuncs.pfnStaticDecal( pev->origin, (int)pev->skin, entityIndex, modelIndex ); + + SUB_Remove(); +} + + +void CDecal :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "texture")) + { + pev->skin = DECAL_INDEX( pkvd->szValue ); + + // Found + if ( pev->skin >= 0 ) + return; + ALERT( at_console, "Can't find decal %s\n", pkvd->szValue ); + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +// Body queue class here.... It's really just CBaseEntity +class CCorpse : public CBaseEntity +{ + virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +}; + +LINK_ENTITY_TO_CLASS( bodyque, CCorpse ); + +static void InitBodyQue(void) +{ + string_t istrClassname = MAKE_STRING("bodyque"); + + g_pBodyQueueHead = CREATE_NAMED_ENTITY( istrClassname ); + entvars_t *pev = VARS(g_pBodyQueueHead); + + // Reserve 3 more slots for dead bodies + for ( int i = 0; i < 3; i++ ) + { + pev->owner = CREATE_NAMED_ENTITY( istrClassname ); + pev = VARS(pev->owner); + } + + pev->owner = g_pBodyQueueHead; +} + + +// +// make a body que entry for the given ent so the ent can be respawned elsewhere +// +// GLOBALS ASSUMED SET: g_eoBodyQueueHead +// +void CopyToBodyQue(entvars_t *pev) +{ + if (pev->effects & EF_NODRAW) + return; + + entvars_t *pevHead = VARS(g_pBodyQueueHead); + + pevHead->angles = pev->angles; + pevHead->model = pev->model; + pevHead->modelindex = pev->modelindex; + pevHead->frame = pev->frame; + pevHead->colormap = pev->colormap; + pevHead->movetype = MOVETYPE_TOSS; + pevHead->velocity = pev->velocity; + pevHead->flags = 0; + pevHead->deadflag = pev->deadflag; + pevHead->renderfx = kRenderFxDeadPlayer; + pevHead->renderamt = ENTINDEX( ENT( pev ) ); + + pevHead->effects = pev->effects | EF_NOINTERP; + //pevHead->goalstarttime = pev->goalstarttime; + //pevHead->goalframe = pev->goalframe; + //pevHead->goalendtime = pev->goalendtime ; + + pevHead->sequence = pev->sequence; + pevHead->animtime = pev->animtime; + + UTIL_SetOrigin(pevHead, pev->origin); + UTIL_SetSize(pevHead, pev->mins, pev->maxs); + g_pBodyQueueHead = pevHead->owner; +} + + +CGlobalState::CGlobalState( void ) +{ + Reset(); +} + +void CGlobalState::Reset( void ) +{ + m_pList = NULL; + m_listCount = 0; +} + +globalentity_t *CGlobalState :: Find( string_t globalname ) +{ + if ( !globalname ) + return NULL; + + globalentity_t *pTest; + const char *pEntityName = STRING(globalname); + + + pTest = m_pList; + while ( pTest ) + { + if ( FStrEq( pEntityName, pTest->name ) ) + break; + + pTest = pTest->pNext; + } + + return pTest; +} + + +// This is available all the time now on impulse 104, remove later +//#ifdef _DEBUG +void CGlobalState :: DumpGlobals( void ) +{ + static char *estates[] = { "Off", "On", "Dead" }; + globalentity_t *pTest; + + ALERT( at_console, "-- Globals --\n" ); + pTest = m_pList; + while ( pTest ) + { + ALERT( at_console, "%s: %s (%s)\n", pTest->name, pTest->levelName, estates[pTest->state] ); + pTest = pTest->pNext; + } +} +//#endif + + +void CGlobalState :: EntityAdd( string_t globalname, string_t mapName, GLOBALESTATE state ) +{ + ASSERT( !Find(globalname) ); + + globalentity_t *pNewEntity = (globalentity_t *)calloc( sizeof( globalentity_t ), 1 ); + ASSERT( pNewEntity != NULL ); + pNewEntity->pNext = m_pList; + m_pList = pNewEntity; + strcpy( pNewEntity->name, STRING( globalname ) ); + strcpy( pNewEntity->levelName, STRING(mapName) ); + pNewEntity->state = state; + m_listCount++; +} + + +void CGlobalState :: EntitySetState( string_t globalname, GLOBALESTATE state ) +{ + globalentity_t *pEnt = Find( globalname ); + + if ( pEnt ) + pEnt->state = state; +} + + +const globalentity_t *CGlobalState :: EntityFromTable( string_t globalname ) +{ + globalentity_t *pEnt = Find( globalname ); + + return pEnt; +} + + +GLOBALESTATE CGlobalState :: EntityGetState( string_t globalname ) +{ + globalentity_t *pEnt = Find( globalname ); + if ( pEnt ) + return pEnt->state; + + return GLOBAL_OFF; +} + + +// Global Savedata for Delay +TYPEDESCRIPTION CGlobalState::m_SaveData[] = +{ + DEFINE_FIELD( CGlobalState, m_listCount, FIELD_INTEGER ), +}; + +// Global Savedata for Delay +TYPEDESCRIPTION gGlobalEntitySaveData[] = +{ + DEFINE_ARRAY( globalentity_t, name, FIELD_CHARACTER, 64 ), + DEFINE_ARRAY( globalentity_t, levelName, FIELD_CHARACTER, 32 ), + DEFINE_FIELD( globalentity_t, state, FIELD_INTEGER ), +}; + + +int CGlobalState::Save( CSave &save ) +{ + int i; + globalentity_t *pEntity; + + if ( !save.WriteFields( "GLOBAL", this, m_SaveData, ARRAYSIZE(m_SaveData) ) ) + return 0; + + pEntity = m_pList; + for ( i = 0; i < m_listCount && pEntity; i++ ) + { + if ( !save.WriteFields( "GENT", pEntity, gGlobalEntitySaveData, ARRAYSIZE(gGlobalEntitySaveData) ) ) + return 0; + + pEntity = pEntity->pNext; + } + + return 1; +} + +int CGlobalState::Restore( CRestore &restore ) +{ + int i, listCount; + globalentity_t tmpEntity; + + + ClearStates(); + if ( !restore.ReadFields( "GLOBAL", this, m_SaveData, ARRAYSIZE(m_SaveData) ) ) + return 0; + + listCount = m_listCount; // Get new list count + m_listCount = 0; // Clear loaded data + + for ( i = 0; i < listCount; i++ ) + { + if ( !restore.ReadFields( "GENT", &tmpEntity, gGlobalEntitySaveData, ARRAYSIZE(gGlobalEntitySaveData) ) ) + return 0; + EntityAdd( MAKE_STRING(tmpEntity.name), MAKE_STRING(tmpEntity.levelName), tmpEntity.state ); + } + return 1; +} + +void CGlobalState::EntityUpdate( string_t globalname, string_t mapname ) +{ + globalentity_t *pEnt = Find( globalname ); + + if ( pEnt ) + strcpy( pEnt->levelName, STRING(mapname) ); +} + + +void CGlobalState::ClearStates( void ) +{ + globalentity_t *pFree = m_pList; + while ( pFree ) + { + globalentity_t *pNext = pFree->pNext; + free( pFree ); + pFree = pNext; + } + Reset(); +} + + +void SaveGlobalState( SAVERESTOREDATA *pSaveData ) +{ + CSave saveHelper( pSaveData ); + gGlobalState.Save( saveHelper ); +} + + +void RestoreGlobalState( SAVERESTOREDATA *pSaveData ) +{ + CRestore restoreHelper( pSaveData ); + gGlobalState.Restore( restoreHelper ); +} + + +void ResetGlobalState( void ) +{ + gGlobalState.ClearStates(); + gInitHUD = TRUE; // Init the HUD on a new game / load game +} + +// moved CWorld class definition to cbase.h +//======================= +// CWorld +// +// This spawns first when each level begins. +//======================= + +LINK_ENTITY_TO_CLASS( worldspawn, CWorld ); + +#define SF_WORLD_DARK 0x0001 // Fade from black at startup +#define SF_WORLD_TITLE 0x0002 // Display game title at startup +#define SF_WORLD_FORCETEAM 0x0004 // Force teams + +extern DLL_GLOBAL BOOL g_fGameOver; +float g_flWeaponCheat; + +void CWorld :: Spawn( void ) +{ + g_fGameOver = FALSE; + Precache( ); + g_flWeaponCheat = CVAR_GET_FLOAT( "sv_cheats" ); // Is the impulse 101 command allowed? +} + +void CWorld :: Precache( void ) +{ + g_pLastSpawn = NULL; + +#if 1 + CVAR_SET_STRING("sv_gravity", "800"); // 67ft/sec + CVAR_SET_STRING("sv_stepsize", "18"); +#else + CVAR_SET_STRING("sv_gravity", "384"); // 32ft/sec + CVAR_SET_STRING("sv_stepsize", "24"); +#endif + + CVAR_SET_STRING("room_type", "0");// clear DSP + + // QUAKECLASSIC + // Set various physics cvars to Quake's values + CVAR_SET_STRING("sv_friction", "4"); + CVAR_SET_STRING("sv_maxspeed", "400"); + CVAR_SET_STRING("sv_airaccelerate", "0.7"); + + // Set up game rules + if (g_pGameRules) + { + delete g_pGameRules; + } + + g_pGameRules = InstallGameRules( ); + + //!!!UNDONE why is there so much Spawn code in the Precache function? I'll just keep it here + InitBodyQue(); + +// init sentence group playback stuff from sentences.txt. +// ok to call this multiple times, calls after first are ignored. + + SENTENCEG_Init(); + +// init texture type array from materials.txt + + TEXTURETYPE_Init(); + + +// the area based ambient sounds MUST be the first precache_sounds + +// player precaches + W_Precache (); // get weapon precaches + + ClientPrecache(); + + // QUAKECLASSIC + QuakeClassicPrecache(); + +// 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) + PRECACHE_SOUND( "common/bodydrop4.wav" ); + + g_Language = (int)CVAR_GET_FLOAT( "sv_language" ); + if ( g_Language == LANGUAGE_GERMAN ) + { + PRECACHE_MODEL( "models/germangibs.mdl" ); + } + else + { + PRECACHE_MODEL( "models/hgibs.mdl" ); + 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"); + + // 1 FLICKER (first variety) + LIGHT_STYLE(1, "mmnmmommommnonmmonqnmmo"); + + // 2 SLOW STRONG PULSE + LIGHT_STYLE(2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"); + + // 3 CANDLE (first variety) + LIGHT_STYLE(3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg"); + + // 4 FAST STROBE + LIGHT_STYLE(4, "mamamamamama"); + + // 5 GENTLE PULSE 1 + LIGHT_STYLE(5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj"); + + // 6 FLICKER (second variety) + LIGHT_STYLE(6, "nmonqnmomnmomomno"); + + // 7 CANDLE (second variety) + LIGHT_STYLE(7, "mmmaaaabcdefgmmmmaaaammmaamm"); + + // 8 CANDLE (third variety) + LIGHT_STYLE(8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa"); + + // 9 SLOW STROBE (fourth variety) + LIGHT_STYLE(9, "aaaaaaaazzzzzzzz"); + + // 10 FLUORESCENT FLICKER + LIGHT_STYLE(10, "mmamammmmammamamaaamammma"); + + // 11 SLOW PULSE NOT FADE TO BLACK + LIGHT_STYLE(11, "abcdefghijklmnopqrrqponmlkjihgfedcba"); + + // 12 UNDERWATER LIGHT MUTATION + // this light only distorts the lightmap - no contribution + // is made to the brightness of affected surfaces + LIGHT_STYLE(12, "mmnnmmnnnmmnn"); + + // styles 32-62 are assigned by the light program for switchable lights + + // 63 testing + LIGHT_STYLE(63, "a"); + + for ( int i = 0; i < static_cast(ARRAYSIZE(gDecals)); i++ ) + gDecals[i].index = DECAL_INDEX( gDecals[i].name ); + +// init the WorldGraph. + WorldGraph.InitGraph(); + +// make sure the .NOD file is newer than the .BSP file. + if ( !WorldGraph.CheckNODFile ( ( char * )STRING( gpGlobals->mapname ) ) ) + {// NOD file is not present, or is older than the BSP file. + WorldGraph.AllocNodes (); + } + else + {// Load the node graph for this level + if ( !WorldGraph.FLoadGraph ( (char *)STRING( gpGlobals->mapname ) ) ) + {// couldn't load, so alloc and prepare to build a graph. + ALERT ( at_console, "*Error opening .NOD file\n" ); + WorldGraph.AllocNodes (); + } + else + { + ALERT ( at_console, "\n*Graph Loaded!\n" ); + } + } + + if ( pev->speed > 0 ) + CVAR_SET_FLOAT( "sv_zmax", pev->speed ); + else + CVAR_SET_FLOAT( "sv_zmax", 4096 ); + + // QUAKECLASSIC: No Fades + /* + if ( pev->netname ) + { + ALERT( at_aiconsole, "Chapter title: %s\n", STRING(pev->netname) ); + CBaseEntity *pEntity = CBaseEntity::Create( "env_message", g_vecZero, g_vecZero, NULL ); + if ( pEntity ) + { + pEntity->SetThink( SUB_CallUseToggle ); + pEntity->pev->message = pev->netname; + pev->netname = 0; + pEntity->pev->nextthink = gpGlobals->time + 0.3; + pEntity->pev->spawnflags = SF_MESSAGE_ONCE; + } + } + */ + + // QUAKECLASSIC: No Darkness + /* + if ( pev->spawnflags & SF_WORLD_DARK ) + CVAR_SET_FLOAT( "v_dark", 1.0 ); + else + CVAR_SET_FLOAT( "v_dark", 0.0 ); + */ + + if ( pev->spawnflags & SF_WORLD_TITLE ) + gDisplayTitle = TRUE; // display the game title if this key is set + else + gDisplayTitle = FALSE; + + if ( pev->spawnflags & SF_WORLD_FORCETEAM ) + { + CVAR_SET_FLOAT( "mp_defaultteam", 1 ); + } + else + { + CVAR_SET_FLOAT( "mp_defaultteam", 0 ); + } +} + + +// +// Just to ignore the "wad" field. +// +void CWorld :: KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "skyname") ) + { + // Sent over net now. + CVAR_SET_STRING( "sv_skyname", pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "sounds") ) + { + gpGlobals->cdAudioTrack = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "WaveHeight") ) + { + // Sent over net now. + pev->scale = atof(pkvd->szValue) * (1.0/8.0); + pkvd->fHandled = TRUE; + CVAR_SET_FLOAT( "sv_wateramp", pev->scale ); + } + else if ( FStrEq(pkvd->szKeyName, "MaxRange") ) + { + pev->speed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "chaptertitle") ) + { + pev->netname = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "startdark") ) + { + // UNDONE: This is a gross hack!!! The CVAR is NOT sent over the client/sever link + // but it will work for single player + int flag = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + if ( flag ) + pev->spawnflags |= SF_WORLD_DARK; + } + else if ( FStrEq(pkvd->szKeyName, "newunit") ) + { + // Single player only. Clear save directory if set + if ( atoi(pkvd->szValue) ) + CVAR_SET_FLOAT( "sv_newunit", 1 ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "gametitle") ) + { + if ( atoi(pkvd->szValue) ) + pev->spawnflags |= SF_WORLD_TITLE; + + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "mapteams") ) + { + pev->team = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "defaultteam") ) + { + if ( atoi(pkvd->szValue) ) + { + pev->spawnflags |= SF_WORLD_FORCETEAM; + } + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} diff --git a/hlsdk/dmc/pm_shared/pm_debug.c b/hlsdk/dmc/pm_shared/pm_debug.c new file mode 100644 index 00000000..c485b438 --- /dev/null +++ b/hlsdk/dmc/pm_shared/pm_debug.c @@ -0,0 +1,306 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include "mathlib.h" +#include "const.h" +#include "usercmd.h" +#include "pm_defs.h" +#include "pm_shared.h" +#include "pm_movevars.h" +#include "pm_debug.h" + +#include + +#ifdef _MSC_VER +#pragma warning(disable : 4244) +#pragma warning(disable : 4305) +#endif + +extern playermove_t *pmove; + +// Expand debugging BBOX particle hulls by this many units. +#define BOX_GAP 0.0f + +static int PM_boxpnt[6][4] = +{ + { 0, 4, 6, 2 }, // +X + { 0, 1, 5, 4 }, // +Y + { 0, 2, 3, 1 }, // +Z + { 7, 5, 1, 3 }, // -X + { 7, 3, 2, 6 }, // -Y + { 7, 6, 4, 5 }, // -Z +}; + +void PM_ShowClipBox( void ) +{ +#if defined( _DEBUG ) + if ( !pmove->runfuncs ) + return; + + // More debugging, draw the particle bbox for player and for the entity we are looking directly at. + // aslo prints entity info to the console overlay. + if ( !pmove->server ) + return; + + // Draw entity in center of view + // Also draws the normal to the clip plane that intersects our movement ray. Leaves a particle + // trail at the intersection point. + PM_ViewEntity(); + + // Show our BBOX in particles. + //PM_DrawBBox( pmove->player_mins[pmove->usehull], pmove->player_maxs[pmove->usehull], pmove->origin, 132, 0.1 ); +/* + { + int i; + for ( i = 0; i < pmove->numphysent; i++ ) + { + if ( pmove->physents[ i ].info >= 1 && pmove->physents[ i ].info <= 4 ) + { + PM_DrawBBox( pmove->player_mins[pmove->usehull], pmove->player_maxs[pmove->usehull], pmove->physents[i].origin, 132, 0.1 ); + } + } + } +*/ +#endif +} + +/* +=============== +PM_ParticleLine(vec3_t start, vec3_t end, int color, float life) + +================ +*/ +void PM_ParticleLine(vec3_t start, vec3_t end, int pcolor, float life, float vert) +{ + float linestep = 2.0f; + float curdist; + float len; + vec3_t curpos; + vec3_t diff; + int i; + // Determine distance; + + VectorSubtract(end, start, diff); + + len = VectorNormalize(diff); + + curdist = 0; + while (curdist <= len) + { + for (i = 0; i < 3; i++) + curpos[i] = start[i] + curdist * diff[i]; + + pmove->PM_Particle( curpos, pcolor, life, 0, vert); + curdist += linestep; + } + +} + +/* +================ +PM_DrawRectangle(vec3_t tl, vec3_t br) + +================ +*/ +void PM_DrawRectangle(vec3_t tl, vec3_t bl, vec3_t tr, vec3_t br, int pcolor, float life) +{ + PM_ParticleLine(tl, bl, pcolor, life, 0); + PM_ParticleLine(bl, br, pcolor, life, 0); + PM_ParticleLine(br, tr, pcolor, life, 0); + PM_ParticleLine(tr, tl, pcolor, life, 0); +} + +/* +================ +PM_DrawPhysEntBBox(int num) + +================ +*/ +void PM_DrawPhysEntBBox(int num, int pcolor, float life) +{ + physent_t *pe; + vec3_t org; + int j; + vec3_t tmp; + vec3_t p[8]; + float gap = BOX_GAP; + vec3_t modelmins, modelmaxs; + + if (num >= pmove->numphysent || + num <= 0) + return; + + pe = &pmove->physents[num]; + + if (pe->model) + { + VectorCopy(pe->origin, org); + + pmove->PM_GetModelBounds( pe->model, modelmins, modelmaxs ); + for (j = 0; j < 8; j++) + { + tmp[0] = (j & 1) ? modelmins[0] - gap : modelmaxs[0] + gap; + tmp[1] = (j & 2) ? modelmins[1] - gap : modelmaxs[1] + gap; + tmp[2] = (j & 4) ? modelmins[2] - gap : modelmaxs[2] + gap; + + VectorCopy(tmp, p[j]); + } + + // If the bbox should be rotated, do that + if (pe->angles[0] || pe->angles[1] || pe->angles[2]) + { + vec3_t forward, right, up; + + AngleVectorsTranspose(pe->angles, forward, right, up); + for (j = 0; j < 8; j++) + { + VectorCopy(p[j], tmp); + p[j][0] = DotProduct ( tmp, forward ); + p[j][1] = DotProduct ( tmp, right ); + p[j][2] = DotProduct ( tmp, up ); + } + } + + // Offset by entity origin, if any. + for (j = 0; j < 8; j++) + VectorAdd(p[j], org, p[j]); + + for (j = 0; j < 6; j++) + { + PM_DrawRectangle( + p[PM_boxpnt[j][1]], + p[PM_boxpnt[j][0]], + p[PM_boxpnt[j][2]], + p[PM_boxpnt[j][3]], + pcolor, life); + } + } + else + { + for (j = 0; j < 8; j++) + { + tmp[0] = (j & 1) ? pe->mins[0] : pe->maxs[0]; + tmp[1] = (j & 2) ? pe->mins[1] : pe->maxs[1]; + tmp[2] = (j & 4) ? pe->mins[2] : pe->maxs[2]; + + VectorAdd(tmp, pe->origin, tmp); + VectorCopy(tmp, p[j]); + } + + for (j = 0; j < 6; j++) + { + PM_DrawRectangle( + p[PM_boxpnt[j][1]], + p[PM_boxpnt[j][0]], + p[PM_boxpnt[j][2]], + p[PM_boxpnt[j][3]], + pcolor, life); + } + + } +} + +/* +================ +PM_DrawBBox(vec3_t mins, vec3_t maxs, vec3_t origin, int pcolor, float life) + +================ +*/ +void PM_DrawBBox(vec3_t mins, vec3_t maxs, vec3_t origin, int pcolor, float life) +{ + int j; + + vec3_t tmp; + vec3_t p[8]; + float gap = BOX_GAP; + + for (j = 0; j < 8; j++) + { + tmp[0] = (j & 1) ? mins[0] - gap : maxs[0] + gap; + tmp[1] = (j & 2) ? mins[1] - gap : maxs[1] + gap ; + tmp[2] = (j & 4) ? mins[2] - gap : maxs[2] + gap ; + + VectorAdd(tmp, origin, tmp); + VectorCopy(tmp, p[j]); + } + + for (j = 0; j < 6; j++) + { + PM_DrawRectangle( + p[PM_boxpnt[j][1]], + p[PM_boxpnt[j][0]], + p[PM_boxpnt[j][2]], + p[PM_boxpnt[j][3]], + pcolor, life); + } +} + + +#ifndef DEDICATED + +/* +================ +PM_ViewEntity + +Shows a particle trail from player to entity in crosshair. +Shows particles at that entities bbox + +Tries to shoot a ray out by about 128 units. +================ +*/ +void PM_ViewEntity( void ) +{ + vec3_t forward, right, up; + float raydist = 256.0f; + vec3_t origin; + vec3_t end; + int i; + pmtrace_t trace; + int pcolor = 77; + float fup; + +#if 0 + if ( !pm_showclip.value ) + return; +#endif + + AngleVectors (pmove->angles, forward, right, up); // Determine movement angles + + VectorCopy( pmove->origin, origin); + + fup = 0.5*( pmove->player_mins[pmove->usehull][2] + pmove->player_maxs[pmove->usehull][2] ); + fup += pmove->view_ofs[2]; + fup -= 4; + + for (i = 0; i < 3; i++) + { + end[i] = origin[i] + raydist * forward[i]; + } + + trace = pmove->PM_PlayerTrace( origin, end, PM_STUDIO_BOX, -1 ); + + if (trace.ent > 0) // Not the world + { + pcolor = 111; + } + + // Draw the hull or bbox. + if (trace.ent > 0) + { + PM_DrawPhysEntBBox(trace.ent, pcolor, 0.3f); + } +} + +#endif diff --git a/hlsdk/dmc/pm_shared/pm_debug.h b/hlsdk/dmc/pm_shared/pm_debug.h new file mode 100644 index 00000000..a506d37e --- /dev/null +++ b/hlsdk/dmc/pm_shared/pm_debug.h @@ -0,0 +1,28 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PM_DEBUG_H +#define PM_DEBUG_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +void PM_ViewEntity( void ); +void PM_DrawBBox(vec3_t mins, vec3_t maxs, vec3_t origin, int pcolor, float life); +void PM_ParticleLine(vec3_t start, vec3_t end, int pcolor, float life, float vert); +void PM_ShowClipBox( void ); + +#endif // PMOVEDBG_H diff --git a/hlsdk/dmc/pm_shared/pm_defs.h b/hlsdk/dmc/pm_shared/pm_defs.h new file mode 100644 index 00000000..43db4528 --- /dev/null +++ b/hlsdk/dmc/pm_shared/pm_defs.h @@ -0,0 +1,265 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// pm_defs.h +#if !defined( PM_DEFSH ) +#define PM_DEFSH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +#define MAX_PHYSENTS 600 // Must have room for all entities in the world. +#define MAX_MOVEENTS 64 +#define MAX_CLIP_PLANES 5 + +#define PM_NORMAL 0x00000000 +#define PM_STUDIO_IGNORE 0x00000001 // Skip studio models +#define PM_STUDIO_BOX 0x00000002 // Use boxes for non-complex studio models (even in traceline) +#define PM_GLASS_IGNORE 0x00000004 // Ignore entities with non-normal rendermode +#define PM_WORLD_ONLY 0x00000008 // Only trace against the world + +// Values for flags parameter of PM_TraceLine +#define PM_TRACELINE_ANYVISIBLE 0 +#define PM_TRACELINE_PHYSENTSONLY 1 + +#if defined _MSC_VER && _MSC_VER >= 1400 + #ifndef _CRT_SECURE_NO_DEPRECATE + #define _CRT_SECURE_NO_DEPRECATE + #endif + + #pragma warning(disable: 4996) // deprecated functions +#endif + +#include "pm_info.h" + +// PM_PlayerTrace results. +#include "pmtrace.h" + +#if !defined ( USERCMD_H ) +#include "usercmd.h" +#endif + + +// physent_t +typedef struct physent_s +{ + char name[32]; // Name of model, or "player" or "world". + int player; + vec3_t origin; // Model's origin in world coordinates. + struct model_s *model; // only for bsp models + struct model_s *studiomodel; // SOLID_BBOX, but studio clip intersections. + vec3_t mins, maxs; // only for non-bsp models + int info; // For client or server to use to identify (index into edicts or cl_entities) + vec3_t angles; // rotated entities need this info for hull testing to work. + + int solid; // Triggers and func_door type WATER brushes are SOLID_NOT + int skin; // BSP Contents for such things like fun_door water brushes. + int rendermode; // So we can ignore glass + + // Complex collision detection. + float frame; + int sequence; + byte controller[4]; + byte blending[2]; + + int movetype; + int takedamage; + int blooddecal; + int team; + int classnumber; + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; +} physent_t; + +#ifndef _DCLIPNODE_DEFINED_ +#define _DCLIPNODE_DEFINED_ +typedef struct +{ + int planenum; + short children[2]; // negative numbers are contents +} dclipnode_t; +#endif + +// plane_t structure +#ifndef _MPLANE_DEFINED_ +#define _MPLANE_DEFINED_ +typedef struct mplane_s +{ + vec3_t normal; // surface normal + float dist; // closest appoach to origin + byte type; // for texture axis selection and fast side tests + byte signbits; // signx + signy<<1 + signz<<1 + byte pad[2]; +} mplane_t; +#endif + +#ifndef _HULL_DEFINED_ +#define _HULL_DEFINED_ +typedef struct hull_s +{ + dclipnode_t *clipnodes; + mplane_t *planes; + int firstclipnode; + int lastclipnode; + vec3_t clip_mins; + vec3_t clip_maxs; +} hull_t; +#endif + + +typedef struct playermove_s +{ + int player_index; // So we don't try to run the PM_CheckStuck nudging too quickly. + qboolean server; // For debugging, are we running physics code on server side? + + qboolean multiplayer; // 1 == multiplayer server + float time; // realtime on host, for reckoning duck timing + float frametime; // Duration of this frame + + vec3_t forward, right, up; // Vectors for angles + // player state + vec3_t origin; // Movement origin. + vec3_t angles; // Movement view angles. + vec3_t oldangles; // Angles before movement view angles were looked at. + vec3_t velocity; // Current movement direction. + vec3_t movedir; // For waterjumping, a forced forward velocity so we can fly over lip of ledge. + vec3_t basevelocity; // Velocity of the conveyor we are standing, e.g. + + // For ducking/dead + vec3_t view_ofs; // Our eye position. + float flDuckTime; // Time we started duck + qboolean bInDuck; // In process of ducking or ducked already? + + // For walking/falling + int flTimeStepSound; // Next time we can play a step sound + int iStepLeft; + + float flFallVelocity; + vec3_t punchangle; + + float flSwimTime; + + float flNextPrimaryAttack; + + int effects; // MUZZLE FLASH, e.g. + + int flags; // FL_ONGROUND, FL_DUCKING, etc. + int usehull; // 0 = regular player hull, 1 = ducked player hull, 2 = point hull + float gravity; // Our current gravity and friction. + float friction; + int oldbuttons; // Buttons last usercmd + float waterjumptime; // Amount of time left in jumping out of water cycle. + qboolean dead; // Are we a dead player? + int deadflag; + int spectator; // Should we use spectator physics model? + int movetype; // Our movement type, NOCLIP, WALK, FLY + + int onground; + int waterlevel; + int watertype; + int oldwaterlevel; + + char sztexturename[256]; + char chtexturetype; + + float maxspeed; + float clientmaxspeed; // Player specific maxspeed + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; + // world state + // Number of entities to clip against. + int numphysent; + physent_t physents[MAX_PHYSENTS]; + // Number of momvement entities (ladders) + int nummoveent; + // just a list of ladders + physent_t moveents[MAX_MOVEENTS]; + + // All things being rendered, for tracing against things you don't actually collide with + int numvisent; + physent_t visents[ MAX_PHYSENTS ]; + + // input to run through physics. + usercmd_t cmd; + + // Trace results for objects we collided with. + int numtouch; + pmtrace_t touchindex[MAX_PHYSENTS]; + + char physinfo[ MAX_PHYSINFO_STRING ]; // Physics info string + + struct movevars_s *movevars; + vec3_t player_mins[4]; + vec3_t player_maxs[4]; + + // Common functions + const char *(*PM_Info_ValueForKey) ( const char *s, const char *key ); + void (*PM_Particle)( vec3_t origin, int color, float life, int zpos, int zvel); + int (*PM_TestPlayerPosition) (vec3_t pos, pmtrace_t *ptrace ); + void (*Con_NPrintf)( int idx, char *fmt, ... ); + void (*Con_DPrintf)( char *fmt, ... ); + void (*Con_Printf)( char *fmt, ... ); + double (*Sys_FloatTime)( void ); + void (*PM_StuckTouch)( int hitent, pmtrace_t *ptraceresult ); + int (*PM_PointContents) (vec3_t p, int *truecontents /*filled in if this is non-null*/ ); + int (*PM_TruePointContents) (vec3_t p); + int (*PM_HullPointContents) ( struct hull_s *hull, int num, vec3_t p); + pmtrace_t (*PM_PlayerTrace) (vec3_t start, vec3_t end, int traceFlags, int ignore_pe ); + struct pmtrace_s *(*PM_TraceLine)( float *start, float *end, int flags, int usehulll, int ignore_pe ); + long (*RandomLong)( long lLow, long lHigh ); + float (*RandomFloat)( float flLow, float flHigh ); + int (*PM_GetModelType)( struct model_s *mod ); + void (*PM_GetModelBounds)( struct model_s *mod, vec3_t mins, vec3_t maxs ); + void *(*PM_HullForBsp)( physent_t *pe, vec3_t offset ); + float (*PM_TraceModel)( physent_t *pEnt, vec3_t start, vec3_t end, trace_t *trace ); + int (*COM_FileSize)(char *filename); + byte *(*COM_LoadFile) (char *path, int usehunk, int *pLength); + void (*COM_FreeFile) ( void *buffer ); + char *(*memfgets)( byte *pMemFile, int fileSize, int *pFilePos, char *pBuffer, int bufferSize ); + + // Functions + // Run functions for this frame? + qboolean runfuncs; + void (*PM_PlaySound) ( int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch ); + const char *(*PM_TraceTexture) ( int ground, vec3_t vstart, vec3_t vend ); + void (*PM_PlaybackEventFull) ( int flags, int clientindex, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); +} playermove_t; + +#endif diff --git a/hlsdk/dmc/pm_shared/pm_info.h b/hlsdk/dmc/pm_shared/pm_info.h new file mode 100644 index 00000000..fa3cc192 --- /dev/null +++ b/hlsdk/dmc/pm_shared/pm_info.h @@ -0,0 +1,26 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Physics info string definition +#if !defined( PM_INFOH ) +#define PM_INFOH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +#define MAX_PHYSINFO_STRING 256 + +#endif // PM_INFOH diff --git a/hlsdk/dmc/pm_shared/pm_materials.h b/hlsdk/dmc/pm_shared/pm_materials.h new file mode 100644 index 00000000..41407817 --- /dev/null +++ b/hlsdk/dmc/pm_shared/pm_materials.h @@ -0,0 +1,37 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( PM_MATERIALSH ) +#define PM_MATERIALSH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +#define CBTEXTURENAMEMAX 13 // only load first n chars of name + +#define CHAR_TEX_CONCRETE 'C' // texture types +#define CHAR_TEX_METAL 'M' +#define CHAR_TEX_DIRT 'D' +#define CHAR_TEX_VENT 'V' +#define CHAR_TEX_GRATE 'G' +#define CHAR_TEX_TILE 'T' +#define CHAR_TEX_SLOSH 'S' +#define CHAR_TEX_WOOD 'W' +#define CHAR_TEX_COMPUTER 'P' +#define CHAR_TEX_GLASS 'Y' +#define CHAR_TEX_FLESH 'F' + +#endif // !PM_MATERIALSH diff --git a/hlsdk/dmc/pm_shared/pm_math.c b/hlsdk/dmc/pm_shared/pm_math.c new file mode 100644 index 00000000..bc892a6e --- /dev/null +++ b/hlsdk/dmc/pm_shared/pm_math.c @@ -0,0 +1,423 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// pm_math.c -- math primitives + +#include "mathlib.h" +#include "const.h" +#include + +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 + +#ifdef _MSC_VER +#pragma warning(disable : 4244) +#endif + +vec3_t vec3_origin = {0,0,0}; +int nanmask = 255<<23; + +float anglemod(float a) +{ + a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); + return a; +} + +void AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + 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; + } +} + +void AngleVectorsTranspose (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + 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] = (sr*sp*cy+cr*-sy); + forward[2] = (cr*sp*cy+-sr*-sy); + } + if (right) + { + right[0] = cp*sy; + right[1] = (sr*sp*sy+cr*cy); + right[2] = (cr*sp*sy+-sr*cy); + } + if (up) + { + up[0] = -sp; + up[1] = sr*cp; + up[2] = cr*cp; + } +} + + +void AngleMatrix (const vec3_t angles, float (*matrix)[4] ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + 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); + + // matrix = (YAW * PITCH) * ROLL + matrix[0][0] = cp*cy; + matrix[1][0] = cp*sy; + matrix[2][0] = -sp; + matrix[0][1] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[2][1] = sr*cp; + matrix[0][2] = (cr*sp*cy+-sr*-sy); + matrix[1][2] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.0; + matrix[1][3] = 0.0; + matrix[2][3] = 0.0; +} + +void AngleIMatrix (const vec3_t angles, float matrix[3][4] ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + 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); + + // matrix = (YAW * PITCH) * ROLL + matrix[0][0] = cp*cy; + matrix[0][1] = cp*sy; + matrix[0][2] = -sp; + matrix[1][0] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[1][2] = sr*cp; + matrix[2][0] = (cr*sp*cy+-sr*-sy); + matrix[2][1] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.0; + matrix[1][3] = 0.0; + matrix[2][3] = 0.0; +} + +void NormalizeAngles( float *angles ) +{ + int i; + // Normalize angles + for ( i = 0; i < 3; i++ ) + { + if ( angles[i] > 180.0 ) + { + angles[i] -= 360.0; + } + else if ( angles[i] < -180.0 ) + { + angles[i] += 360.0; + } + } +} + +/* +=================== +InterpolateAngles + +Interpolate Euler angles. +FIXME: Use Quaternions to avoid discontinuities +Frac is 0.0 to 1.0 ( i.e., should probably be clamped, but doesn't have to be ) +=================== +*/ +void InterpolateAngles( float *start, float *end, float *output, float frac ) +{ + int i; + float ang1, ang2; + float d; + + NormalizeAngles( start ); + NormalizeAngles( end ); + + for ( i = 0 ; i < 3 ; i++ ) + { + ang1 = start[i]; + ang2 = end[i]; + + d = ang2 - ang1; + if ( d > 180 ) + { + d -= 360; + } + else if ( d < -180 ) + { + d += 360; + } + + output[i] = ang1 + d * frac; + } + + NormalizeAngles( output ); +} + + +/* +=================== +AngleBetweenVectors + +=================== +*/ +float AngleBetweenVectors( const vec3_t v1, const vec3_t v2 ) +{ + float angle; + float l1 = Length( v1 ); + float l2 = Length( v2 ); + + if ( !l1 || !l2 ) + return 0.0f; + + angle = acos( DotProduct( v1, v2 ) ) / (l1*l2); + angle = ( angle * 180.0f ) / M_PI; + + return angle; +} + +void VectorTransform (const vec3_t in1, float in2[3][4], vec3_t out) +{ + out[0] = DotProduct(in1, in2[0]) + in2[0][3]; + out[1] = DotProduct(in1, in2[1]) + in2[1][3]; + out[2] = DotProduct(in1, in2[2]) + in2[2][3]; +} + + +int VectorCompare (const vec3_t v1, const vec3_t v2) +{ + int i; + + for (i=0 ; i<3 ; i++) + if (v1[i] != v2[i]) + return 0; + + return 1; +} + +void VectorMA (const vec3_t veca, float scale, const vec3_t vecb, vec3_t vecc) +{ + vecc[0] = veca[0] + scale*vecb[0]; + vecc[1] = veca[1] + scale*vecb[1]; + vecc[2] = veca[2] + scale*vecb[2]; +} + + +vec_t _DotProduct (vec3_t v1, vec3_t v2) +{ + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} + +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]-vecb[0]; + out[1] = veca[1]-vecb[1]; + out[2] = veca[2]-vecb[2]; +} + +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]+vecb[0]; + out[1] = veca[1]+vecb[1]; + out[2] = veca[2]+vecb[2]; +} + +void _VectorCopy (vec3_t in, vec3_t out) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +void CrossProduct (const vec3_t v1, const vec3_t v2, vec3_t cross) +{ + cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; + cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; + cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} + +double sqrt(double x); + +float Length(const vec3_t v) +{ + int i; + float length = 0.0f; + + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = sqrt (length); // FIXME + + return length; +} + +float Distance(const vec3_t v1, const vec3_t v2) +{ + vec3_t d; + VectorSubtract(v2,v1,d); + return Length(d); +} + +float VectorNormalize (vec3_t v) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; + +} + +void VectorInverse (vec3_t v) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void VectorScale (const vec3_t in, vec_t scale, vec3_t out) +{ + out[0] = in[0]*scale; + out[1] = in[1]*scale; + out[2] = in[2]*scale; +} + + +int Q_log2(int val) +{ + int answer=0; + while (val>>=1) + answer++; + return answer; +} + +void VectorMatrix( vec3_t forward, vec3_t right, vec3_t up) +{ + vec3_t tmp; + + if (forward[0] == 0 && forward[1] == 0) + { + right[0] = 1; + right[1] = 0; + right[2] = 0; + up[0] = -forward[2]; + up[1] = 0; + up[2] = 0; + return; + } + + tmp[0] = 0; tmp[1] = 0; tmp[2] = 1.0; + CrossProduct( forward, tmp, right ); + VectorNormalize( right ); + CrossProduct( right, forward, up ); + VectorNormalize( up ); +} + + +void VectorAngles( const vec3_t forward, vec3_t 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; +} diff --git a/hlsdk/dmc/pm_shared/pm_movevars.h b/hlsdk/dmc/pm_shared/pm_movevars.h new file mode 100644 index 00000000..66c99ee7 --- /dev/null +++ b/hlsdk/dmc/pm_shared/pm_movevars.h @@ -0,0 +1,47 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// pm_movevars.h +#if !defined( PM_MOVEVARSH ) +#define PM_MOVEVARSH + +// movevars_t // Physics variables. +typedef struct movevars_s movevars_t; + +struct movevars_s +{ + float gravity; // Gravity for map + float stopspeed; // Deceleration when not moving + float maxspeed; // Max allowed speed + float spectatormaxspeed; + float accelerate; // Acceleration factor + float airaccelerate; // Same for when in open air + float wateraccelerate; // Same for when in water + float friction; + float edgefriction; // Extra friction near dropofs + float waterfriction; // Less in water + float entgravity; // 1.0 + float bounce; // Wall bounce value. 1.0 + float stepsize; // sv_stepsize; + float maxvelocity; // maximum server velocity. + float zmax; // Max z-buffer range (for GL) + float waveHeight; // Water wave height (for GL) + qboolean footsteps; // Play footstep sounds + char skyName[32]; // Name of the sky map + float rollangle; + float rollspeed; + float skycolor_r; // Sky color + float skycolor_g; // + float skycolor_b; // + float skyvec_x; // Sky vector + float skyvec_y; // + float skyvec_z; // +}; + +extern movevars_t movevars; + +#endif diff --git a/hlsdk/dmc/pm_shared/pm_shared.c b/hlsdk/dmc/pm_shared/pm_shared.c new file mode 100644 index 00000000..7ede7cfc --- /dev/null +++ b/hlsdk/dmc/pm_shared/pm_shared.c @@ -0,0 +1,2772 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include +#include "mathlib.h" +#include "const.h" +#include "usercmd.h" +#include "pm_defs.h" +#include "pm_shared.h" +#include "pm_movevars.h" +#include "pm_debug.h" +#include // NULL +#include // sqrt +#include // strcpy +#include // atoi +#include // isspace + +#ifdef CLIENT_DLL + // Spectator Mode + int iJumpSpectator; + float vJumpOrigin[3]; + float vJumpAngles[3]; +#endif + +static int pm_shared_initialized = 0; + +#ifdef _MSC_VER +#pragma warning( disable : 4305 ) +#endif + +typedef enum {mod_brush, mod_sprite, mod_alias, mod_studio} modtype_t; + +playermove_t *pmove = NULL; + +// Ducking time +#define TIME_TO_DUCK 0.4 +#define VEC_DUCK_HULL_MIN -18 +#define VEC_DUCK_HULL_MAX 18 +#define VEC_DUCK_VIEW 12 +#define PM_DEAD_VIEWHEIGHT -8 +#define MAX_CLIMB_SPEED 200 +#define STUCK_MOVEUP 1 +#define STUCK_MOVEDOWN -1 +#define VEC_HULL_MIN -36 +#define VEC_HULL_MAX 36 +#define VEC_VIEW 28 +#define STOP_EPSILON 0.1 + +#define CTEXTURESMAX 512 // max number of textures loaded +#define CBTEXTURENAMEMAX 13 // only load first n chars of name + +#define CHAR_TEX_CONCRETE 'C' // texture types +#define CHAR_TEX_METAL 'M' +#define CHAR_TEX_DIRT 'D' +#define CHAR_TEX_VENT 'V' +#define CHAR_TEX_GRATE 'G' +#define CHAR_TEX_TILE 'T' +#define CHAR_TEX_SLOSH 'S' +#define CHAR_TEX_WOOD 'W' +#define CHAR_TEX_COMPUTER 'P' +#define CHAR_TEX_GLASS 'Y' +#define CHAR_TEX_FLESH 'F' + +#define STEP_CONCRETE 0 // default step sound +#define STEP_METAL 1 // metal floor +#define STEP_DIRT 2 // dirt, sand, rock +#define STEP_VENT 3 // ventillation duct +#define STEP_GRATE 4 // metal grating +#define STEP_TILE 5 // floor tiles +#define STEP_SLOSH 6 // shallow liquid puddle +#define STEP_WADE 7 // wading in liquid +#define STEP_LADDER 8 // climbing ladder + +#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_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_LONGJUMP_SPEED 350 // how fast we longjump + +// double to float warning +#ifdef _MSC_VER +#pragma warning(disable : 4244) +#endif +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#define min(a, b) (((a) < (b)) ? (a) : (b)) +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 + +#define MAX_CLIENTS 32 + +#define CONTENTS_CURRENT_0 -9 +#define CONTENTS_CURRENT_90 -10 +#define CONTENTS_CURRENT_180 -11 +#define CONTENTS_CURRENT_270 -12 +#define CONTENTS_CURRENT_UP -13 +#define CONTENTS_CURRENT_DOWN -14 + +#define CONTENTS_TRANSLUCENT -15 + +static vec3_t rgv3tStuckTable[54]; +static int rgStuckLast[MAX_CLIENTS][2]; + +// Texture names +static int gcTextures = 0; +static char grgszTextureName[CTEXTURESMAX][CBTEXTURENAMEMAX]; +static char grgchTextureType[CTEXTURESMAX]; + +int g_onladder = 0; + +void PM_SwapTextures( int i, int j ) +{ + char chTemp; + char szTemp[ CBTEXTURENAMEMAX ]; + + strcpy( szTemp, grgszTextureName[ i ] ); + chTemp = grgchTextureType[ i ]; + + strcpy( grgszTextureName[ i ], grgszTextureName[ j ] ); + grgchTextureType[ i ] = grgchTextureType[ j ]; + + strcpy( grgszTextureName[ j ], szTemp ); + grgchTextureType[ j ] = chTemp; +} + +void PM_SortTextures( void ) +{ + // Bubble sort, yuck, but this only occurs at startup and it's only 512 elements... + // + int i, j; + + for ( i = 0 ; i < gcTextures; i++ ) + { + for ( j = i + 1; j < gcTextures; j++ ) + { + if ( stricmp( grgszTextureName[ i ], grgszTextureName[ j ] ) > 0 ) + { + // Swap + // + PM_SwapTextures( i, j ); + } + } + } +} + +void PM_InitTextureTypes() +{ + char buffer[512]; + int i, j; + byte *pMemFile; + int fileSize, filePos; + static qboolean bTextureTypeInit = false; + + if ( bTextureTypeInit ) + 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 ); + if ( !pMemFile ) + return; + + filePos = 0; + // for each line in the file... + while ( pmove->memfgets( pMemFile, fileSize, &filePos, buffer, 511 ) != NULL && (gcTextures < CTEXTURESMAX) ) + { + // skip whitespace + i = 0; + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // skip comment lines + if (buffer[i] == '/' || !isalpha(buffer[i])) + continue; + + // get texture type + grgchTextureType[gcTextures] = toupper(buffer[i++]); + + // skip whitespace + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // get sentence name + j = i; + while (buffer[j] && !isspace(buffer[j])) + j++; + + if (!buffer[j]) + continue; + + // null-terminate name and save in sentences array + j = min (j, CBTEXTURENAMEMAX-1+i); + buffer[j] = 0; + 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 ) +{ + int left, right, pivot; + int val; + + assert( pm_shared_initialized ); + + left = 0; + right = gcTextures - 1; + + while ( left <= right ) + { + pivot = ( left + right ) / 2; + + val = strnicmp( name, grgszTextureName[ pivot ], CBTEXTURENAMEMAX-1 ); + if ( val == 0 ) + { + return grgchTextureType[ pivot ]; + } + else if ( val > 0 ) + { + left = pivot + 1; + } + else if ( val < 0 ) + { + right = pivot - 1; + } + } + + return CHAR_TEX_CONCRETE; +} +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; + } +} + +/* +==================== +PM_CatagorizeTextureType + +Determine texture info for the texture we are standing on. +==================== +*/ +void PM_CatagorizeTextureType( void ) +{ + vec3_t start, end; + const char *pTextureName; + + VectorCopy( pmove->origin, start ); + VectorCopy( pmove->origin, end ); + + // Straight down + end[2] -= 64; + + // Fill in default values, just in case. + pmove->sztexturename[0] = '\0'; + pmove->chtexturetype = CHAR_TEX_CONCRETE; + + pTextureName = pmove->PM_TraceTexture( pmove->onground, start, end ); + if ( !pTextureName ) + return; + + // strip leading '-0' or '+0~' or '{' or '!' + if (*pTextureName == '-' || *pTextureName == '+') + pTextureName += 2; + + if (*pTextureName == '{' || *pTextureName == '!' || *pTextureName == '~' || *pTextureName == ' ') + pTextureName++; + // '}}' + + strcpy( pmove->sztexturename, pTextureName); + pmove->sztexturename[ CBTEXTURENAMEMAX - 1 ] = 0; + + // get texture type + pmove->chtexturetype = PM_FindTextureType( pmove->sztexturename ); +} + +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; + + if ( pmove->flTimeStepSound > 0 ) + return; + + if ( pmove->flags & FL_FROZEN ) + return; + + 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!!!! + 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; + } + + // 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 ) ) + { + fWalking = speed < velrun; + + VectorCopy( pmove->origin, center ); + VectorCopy( pmove->origin, knee ); + VectorCopy( pmove->origin, feet ); + + height = pmove->player_maxs[ pmove->usehull ][ 2 ] - pmove->player_mins[ pmove->usehull ][ 2 ]; + + knee[2] = pmove->origin[2] - 0.3 * height; + feet[2] = pmove->origin[2] - 0.5 * height; + + if ( pmove->PM_PointContents ( knee, NULL ) == CONTENTS_WATER ) + { + step = STEP_WADE; + fvol = 0.65; + 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; + } + else + { + // find texture under player, if different from current texture, + // get material type + step = PM_MapTextureTypeStepType( pmove->chtexturetype ); + + switch ( pmove->chtexturetype ) + { + default: + case CHAR_TEX_CONCRETE: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_METAL: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_DIRT: + fvol = fWalking ? 0.25 : 0.55; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_VENT: + fvol = fWalking ? 0.4 : 0.7; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_GRATE: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_TILE: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_SLOSH: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + } + } + + pmove->flTimeStepSound += flduck; // slower step time if ducking + } +} + +/* +================ +PM_AddToTouched + +Add's the trace result to touch list, if contact is not already in list. +================ +*/ +qboolean PM_AddToTouched(pmtrace_t tr, vec3_t impactvelocity) +{ + int i; + + for (i = 0; i < pmove->numtouch; i++) + { + if (pmove->touchindex[i].ent == tr.ent) + break; + } + if (i != pmove->numtouch) // Already in list. + return false; + + VectorCopy( impactvelocity, tr.deltavelocity ); + + if (pmove->numtouch >= MAX_PHYSENTS) + pmove->Con_DPrintf("Too many entities were touched!\n"); + + pmove->touchindex[pmove->numtouch++] = tr; + return true; +} + +/* +================ +PM_CheckVelocity + +See if the player has a bogus velocity value. +================ +*/ +void PM_CheckVelocity () +{ + int i; + +// +// bound velocity +// + for (i=0 ; i<3 ; i++) + { + // See if it's bogus. + if (IS_NAN(pmove->velocity[i])) + { + pmove->Con_Printf ("PM Got a NaN velocity %i\n", i); + pmove->velocity[i] = 0; + } + if (IS_NAN(pmove->origin[i])) + { + pmove->Con_Printf ("PM Got a NaN origin on %i\n", i); + pmove->origin[i] = 0; + } + + // Bound it. + if (pmove->velocity[i] > pmove->movevars->maxvelocity) + { + pmove->Con_DPrintf ("PM Got a velocity too high on %i\n", i); + pmove->velocity[i] = pmove->movevars->maxvelocity; + } + else if (pmove->velocity[i] < -pmove->movevars->maxvelocity) + { + pmove->Con_DPrintf ("PM Got a velocity too low on %i\n", i); + pmove->velocity[i] = -pmove->movevars->maxvelocity; + } + } +} + +/* +================== +PM_ClipVelocity + +Slide off of the impacting object +returns the blocked flags: +0x01 == floor +0x02 == step / wall +================== +*/ +int PM_ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce) +{ + float backoff; + float change; + float angle; + int i, blocked; + + angle = normal[ 2 ]; + + blocked = 0x00; // Assume unblocked. + if (angle > 0) // If the plane that is blocking us has a positive z component, then assume it's a floor. + blocked |= 0x01; // + if (!angle) // If the plane has no Z, it is vertical (wall/step) + blocked |= 0x02; // + + // Determine how far along plane to slide based on incoming direction. + // Scale by overbounce factor. + backoff = DotProduct (in, normal) * overbounce; + + for (i=0 ; i<3 ; i++) + { + change = normal[i]*backoff; + out[i] = in[i] - change; + // If out velocity is too small, zero it out. + if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) + out[i] = 0; + } + + // Return blocking flags. + return blocked; +} + +void PM_AddCorrectGravity () +{ + float ent_gravity; + + if ( pmove->waterjumptime ) + return; + + if (pmove->gravity) + ent_gravity = pmove->gravity; + else + ent_gravity = 1.0; + + // Add gravity so they'll be in the correct position during movement + // yes, this 0.5 looks wrong, but it's not. + pmove->velocity[2] -= (ent_gravity * pmove->movevars->gravity * 0.5 * pmove->frametime ); + pmove->velocity[2] += pmove->basevelocity[2] * pmove->frametime; + pmove->basevelocity[2] = 0; + + PM_CheckVelocity(); +} + + +void PM_FixupGravityVelocity () +{ + float ent_gravity; + + if ( pmove->waterjumptime ) + return; + + if (pmove->gravity) + ent_gravity = pmove->gravity; + else + ent_gravity = 1.0; + + // Get the correct velocity for the end of the dt + pmove->velocity[2] -= (ent_gravity * pmove->movevars->gravity * pmove->frametime * 0.5 ); + + PM_CheckVelocity(); +} + +/* +============ +PM_FlyMove + +The basic solid body movement clip that slides along multiple planes +============ +*/ +int PM_FlyMove (void) +{ + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity, original_velocity; + vec3_t new_velocity; + int i, j; + pmtrace_t trace; + vec3_t end; + float time_left, allFraction; + int blocked; + + numbumps = 4; // Bump up to four times + + blocked = 0; // Assume not blocked + numplanes = 0; // and not sliding along any planes + VectorCopy (pmove->velocity, original_velocity); // Store original velocity + VectorCopy (pmove->velocity, primal_velocity); + + allFraction = 0; + time_left = pmove->frametime; // Total time for this movement operation. + + for (bumpcount=0 ; bumpcountvelocity[0] && !pmove->velocity[1] && !pmove->velocity[2]) + break; + + // Assume we can move all the way from the current origin to the + // end point. + for (i=0 ; i<3 ; i++) + end[i] = pmove->origin[i] + time_left * pmove->velocity[i]; + + // See if we can make it from origin to end point. + trace = pmove->PM_PlayerTrace (pmove->origin, end, PM_NORMAL, -1 ); + + allFraction += trace.fraction; + // If we started in a solid object, or we were in solid space + // the whole way, zero out our velocity and return that we + // are blocked by floor and wall. + if (trace.allsolid) + { // entity is trapped in another solid + VectorCopy (vec3_origin, pmove->velocity); + //Con_DPrintf("Trapped 4\n"); + return 4; + } + + // If we moved some portion of the total distance, then + // copy the end position into the pmove->origin and + // zero the plane counter. + if (trace.fraction > 0) + { // actually covered some distance + VectorCopy (trace.endpos, pmove->origin); + VectorCopy (pmove->velocity, original_velocity); + numplanes = 0; + } + + // If we covered the entire distance, we are done + // and can return. + if (trace.fraction == 1) + break; // moved the entire distance + + //if (!trace.ent) + // Sys_Error ("PM_PlayerTrace: !trace.ent"); + + // Save entity that blocked us (since fraction was < 1.0) + // for contact + // Add it if it's not already in the list!!! + PM_AddToTouched(trace, pmove->velocity); + + // If the plane we hit has a high z component in the normal, then + // it's probably a floor + if (trace.plane.normal[2] > 0.7) + { + blocked |= 1; // floor + } + // If the plane has a zero z component in the normal, then it's a + // step or wall + if (!trace.plane.normal[2]) + { + blocked |= 2; // step / wall + //Con_DPrintf("Blocked by %i\n", trace.ent); + } + + // Reduce amount of pmove->frametime left by total time left * fraction + // that we covered. + time_left -= time_left * trace.fraction; + + // Did we run out of planes to clip against? + if (numplanes >= MAX_CLIP_PLANES) + { // this shouldn't really happen + // Stop our movement if so. + VectorCopy (vec3_origin, pmove->velocity); + //Con_DPrintf("Too many planes 4\n"); + + break; + } + + // Set up next clipping plane + VectorCopy (trace.plane.normal, planes[numplanes]); + numplanes++; +// + +// modify original_velocity so it parallels all of the clip planes +// + if ( pmove->movetype == MOVETYPE_WALK && + ((pmove->onground == -1) || (pmove->friction != 1)) ) // relfect player velocity + { + for ( i = 0; i < numplanes; i++ ) + { + if ( planes[i][2] > 0.7 ) + {// floor or slope + PM_ClipVelocity( original_velocity, planes[i], new_velocity, 1 ); + VectorCopy( new_velocity, original_velocity ); + } + else + PM_ClipVelocity( original_velocity, planes[i], new_velocity, 1.0 + pmove->movevars->bounce * (1-pmove->friction) ); + } + + VectorCopy( new_velocity, pmove->velocity ); + VectorCopy( new_velocity, original_velocity ); + } + else + { + for (i=0 ; ivelocity, + 1); + for (j=0 ; jvelocity, planes[j]) < 0) + break; // not ok + } + if (j == numplanes) // Didn't have to clip, so we're ok + break; + } + + // Did we go all the way through plane set + if (i != numplanes) + { // go along this plane + // pmove->velocity is set in clipping call, no need to set again. + ; + } + else + { // go along the crease + if (numplanes != 2) + { + //Con_Printf ("clip velocity, numplanes == %i\n",numplanes); + VectorCopy (vec3_origin, pmove->velocity); + //Con_DPrintf("Trapped 4\n"); + + break; + } + CrossProduct (planes[0], planes[1], dir); + d = DotProduct (dir, pmove->velocity); + VectorScale (dir, d, pmove->velocity ); + } + + // + // if original velocity is against the original velocity, stop dead + // to avoid tiny occilations in sloping corners + // + if (DotProduct (pmove->velocity, primal_velocity) <= 0) + { + //Con_DPrintf("Back\n"); + VectorCopy (vec3_origin, pmove->velocity); + break; + } + } + } + + if ( allFraction == 0 ) + { + VectorCopy (vec3_origin, pmove->velocity); + //Con_DPrintf( "Don't stick\n" ); + } + + return blocked; +} + +/* +============== +PM_Accelerate +============== +*/ +void PM_Accelerate (vec3_t wishdir, float wishspeed, float accel) +{ + int i; + float addspeed, accelspeed, currentspeed; + + // Dead player's don't accelerate + if (pmove->dead) + return; + + // If waterjumping, don't accelerate + if (pmove->waterjumptime) + return; + + // See if we are changing direction a bit + currentspeed = DotProduct (pmove->velocity, wishdir); + + // Reduce wishspeed by the amount of veer. + addspeed = wishspeed - currentspeed; + + // If not going to add any speed, done. + if (addspeed <= 0) + return; + + // Determine amount of accleration. + accelspeed = accel * pmove->frametime * wishspeed * pmove->friction; + + // Cap at addspeed + if (accelspeed > addspeed) + accelspeed = addspeed; + + // Adjust velocity. + for (i=0 ; i<3 ; i++) + { + pmove->velocity[i] += accelspeed * wishdir[i]; + } +} + +/* +===================== +PM_WalkMove + +Only used by players. Moves along the ground when player is a MOVETYPE_WALK. +====================== +*/ +void PM_WalkMove () +{ + int clip; + int oldonground; + int i; + + vec3_t wishvel; + float spd; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + + vec3_t dest, start; + vec3_t original, originalvel; + vec3_t down, downvel; + float downdist, updist; + + pmtrace_t trace; + + // Copy movement amounts + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + // Zero out z components of movement vectors + pmove->forward[2] = 0; + pmove->right[2] = 0; + + VectorNormalize (pmove->forward); // Normalize remainder of vectors. + VectorNormalize (pmove->right); // + + for (i=0 ; i<2 ; i++) // Determine x and y parts of velocity + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + + wishvel[2] = 0; // Zero out z part of velocity + + VectorCopy (wishvel, wishdir); // Determine maginitude of speed of move + wishspeed = VectorNormalize(wishdir); + +// +// Clamp to server defined max speed +// + if (wishspeed > pmove->maxspeed) + { + VectorScale (wishvel, pmove->maxspeed/wishspeed, wishvel); + wishspeed = pmove->maxspeed; + } + + // Set pmove velocity + pmove->velocity[2] = 0; + PM_Accelerate (wishdir, wishspeed, pmove->movevars->accelerate); + pmove->velocity[2] = 0; + + // Add in any base velocity to the current velocity. + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity ); + + spd = Length( pmove->velocity ); + + if (spd < 1.0f) + { + VectorClear( pmove->velocity ); + return; + } + + // If we are not moving, do nothing + //if (!pmove->velocity[0] && !pmove->velocity[1] && !pmove->velocity[2]) + // return; + + oldonground = pmove->onground; + +// first try just moving to the destination + dest[0] = pmove->origin[0] + pmove->velocity[0]*pmove->frametime; + dest[1] = pmove->origin[1] + pmove->velocity[1]*pmove->frametime; + dest[2] = pmove->origin[2]; + + // first try moving directly to the next spot + VectorCopy (dest, start); + trace = pmove->PM_PlayerTrace (pmove->origin, dest, PM_NORMAL, -1 ); + // If we made it all the way, then copy trace end + // as new player position. + if (trace.fraction == 1) + { + VectorCopy (trace.endpos, pmove->origin); + return; + } + + if (oldonground == -1 && // Don't walk up stairs if not on ground. + pmove->waterlevel == 0) + return; + + if (pmove->waterjumptime) // If we are jumping out of water, don't do anything more. + return; + + // Try sliding forward both on ground and up 16 pixels + // take the move that goes farthest + VectorCopy (pmove->origin, original); // Save out original pos & + VectorCopy (pmove->velocity, originalvel); // velocity. + + // Slide move + clip = PM_FlyMove (); + + // Copy the results out + VectorCopy (pmove->origin , down); + VectorCopy (pmove->velocity, downvel); + + // Reset original values. + VectorCopy (original, pmove->origin); + + VectorCopy (originalvel, pmove->velocity); + + // Start out up one stair height + VectorCopy (pmove->origin, dest); + dest[2] += pmove->movevars->stepsize; + + trace = pmove->PM_PlayerTrace (pmove->origin, dest, PM_NORMAL, -1 ); + // If we started okay and made it part of the way at least, + // copy the results to the movement start position and then + // run another move try. + if (!trace.startsolid && !trace.allsolid) + { + VectorCopy (trace.endpos, pmove->origin); + } + +// slide move the rest of the way. + clip = PM_FlyMove (); + +// Now try going back down from the end point +// press down the stepheight + VectorCopy (pmove->origin, dest); + dest[2] -= pmove->movevars->stepsize; + + trace = pmove->PM_PlayerTrace (pmove->origin, dest, PM_NORMAL, -1 ); + + // If we are not on the ground any more then + // use the original movement attempt + if ( trace.plane.normal[2] < 0.7) + goto usedown; + // If the trace ended up in empty space, copy the end + // over to the origin. + if (!trace.startsolid && !trace.allsolid) + { + VectorCopy (trace.endpos, pmove->origin); + } + // Copy this origion to up. + VectorCopy (pmove->origin, pmove->up); + + // decide which one went farther + downdist = (down[0] - original[0])*(down[0] - original[0]) + + (down[1] - original[1])*(down[1] - original[1]); + updist = (pmove->up[0] - original[0])*(pmove->up[0] - original[0]) + + (pmove->up[1] - original[1])*(pmove->up[1] - original[1]); + + if (downdist > updist) + { +usedown: + VectorCopy (down , pmove->origin); + VectorCopy (downvel, pmove->velocity); + } else // copy z value from slide move + pmove->velocity[2] = downvel[2]; + +} + +/* +================== +PM_Friction + +Handles both ground friction and water friction +================== +*/ +void PM_Friction (void) +{ + float *vel; + float speed, newspeed, control; + float friction; + float drop; + vec3_t newvel; + + // If we are in water jump cycle, don't apply friction + if (pmove->waterjumptime) + return; + + // Get velocity + vel = pmove->velocity; + + // Calculate speed + speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1] + vel[2]*vel[2]); + + // If too slow, return + if (speed < 0.1f) + { + return; + } + + drop = 0; + +// apply ground friction + if (pmove->onground != -1) // On an entity that is the ground + { + vec3_t start, stop; + pmtrace_t trace; + + start[0] = stop[0] = pmove->origin[0] + vel[0]/speed*16; + start[1] = stop[1] = pmove->origin[1] + vel[1]/speed*16; + start[2] = pmove->origin[2] + pmove->player_mins[pmove->usehull][2]; + stop[2] = start[2] - 34; + + trace = pmove->PM_PlayerTrace (start, stop, PM_NORMAL, -1 ); + + if (trace.fraction == 1.0) + friction = pmove->movevars->friction*pmove->movevars->edgefriction; + else + friction = pmove->movevars->friction; + + // Grab friction value. + //friction = pmove->movevars->friction; + + friction *= pmove->friction; // player friction? + + // Bleed off some speed, but if we have less than the bleed + // threshhold, bleed the theshold amount. + control = (speed < pmove->movevars->stopspeed) ? + pmove->movevars->stopspeed : speed; + // Add the amount to t'he drop amount. + drop += control*friction*pmove->frametime; + } + +// apply water friction +// if (pmove->waterlevel) +// drop += speed * pmove->movevars->waterfriction * waterlevel * pmove->frametime; + +// scale the velocity + newspeed = speed - drop; + if (newspeed < 0) + newspeed = 0; + + // Determine proportion of old speed we are using. + newspeed /= speed; + + // Adjust velocity according to proportion. + newvel[0] = vel[0] * newspeed; + newvel[1] = vel[1] * newspeed; + newvel[2] = vel[2] * newspeed; + + VectorCopy( newvel, pmove->velocity ); +} + +void PM_AirAccelerate (vec3_t wishdir, float wishspeed, float accel) +{ + int i; + float addspeed, accelspeed, currentspeed, wishspd = wishspeed; + + if (pmove->dead) + return; + if (pmove->waterjumptime) + return; + + // Cap speed + //wishspd = VectorNormalize (pmove->wishveloc); + + if (wishspd > 30) + wishspd = 30; + // Determine veer amount + currentspeed = DotProduct (pmove->velocity, wishdir); + // See how much to add + addspeed = wishspd - currentspeed; + // If not adding any, done. + if (addspeed <= 0) + return; + // Determine acceleration speed after acceleration + + // QUAKECLASSIC: accelspeed = accel * wishspeed * pmove->frametime * pmove->friction; + accelspeed = accel * wishspeed * pmove->frametime; + + // Cap it + if (accelspeed > addspeed) + accelspeed = addspeed; + + // Adjust pmove vel. + for (i=0 ; i<3 ; i++) + { + pmove->velocity[i] += accelspeed*wishdir[i]; + } +} + +/* +=================== +PM_WaterMove + +=================== +*/ +void PM_WaterMove (void) +{ + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + vec3_t start, dest; + vec3_t temp; + pmtrace_t trace; + + float speed, newspeed, addspeed, accelspeed; + +// +// user intentions +// + for (i=0 ; i<3 ; i++) + wishvel[i] = pmove->forward[i]*pmove->cmd.forwardmove + pmove->right[i]*pmove->cmd.sidemove; + + // Sinking after no other movement occurs + if (!pmove->cmd.forwardmove && !pmove->cmd.sidemove && !pmove->cmd.upmove) + wishvel[2] -= 60; // drift towards bottom + else // Go straight up by upmove amount. + wishvel[2] += pmove->cmd.upmove; + + // Copy it over and determine speed + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + // Cap speed. + if (wishspeed > pmove->maxspeed) + { + VectorScale (wishvel, pmove->maxspeed/wishspeed, wishvel); + wishspeed = pmove->maxspeed; + } + // Slow us down a bit. + // QUAKECLASSIC: wishspeed *= 0.8; + wishspeed *= 0.7; + + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity); +// Water friction + VectorCopy(pmove->velocity, temp); + speed = VectorNormalize(temp); + if (speed) + { + newspeed = speed - pmove->frametime * speed * pmove->movevars->friction * pmove->friction; + + if (newspeed < 0) + newspeed = 0; + VectorScale (pmove->velocity, newspeed/speed, pmove->velocity); + } + else + newspeed = 0; + +// +// water acceleration +// + if ( wishspeed < 0.1f ) + { + return; + } + + addspeed = wishspeed - newspeed; + if (addspeed > 0) + { + + VectorNormalize(wishvel); + accelspeed = pmove->movevars->accelerate * wishspeed * pmove->frametime * pmove->friction; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i = 0; i < 3; i++) + pmove->velocity[i] += accelspeed * wishvel[i]; + } + +// Now move +// assume it is a stair or a slope, so press down from stepheight above + VectorMA (pmove->origin, pmove->frametime, pmove->velocity, dest); + VectorCopy (dest, start); + start[2] += pmove->movevars->stepsize + 1; + trace = pmove->PM_PlayerTrace (start, dest, PM_NORMAL, -1 ); + if (!trace.startsolid && !trace.allsolid) // FIXME: check steep slope? + { // walked up the step, so just keep result and exit + VectorCopy (trace.endpos, pmove->origin); + return; + } + + // Try moving straight along out normal path. + PM_FlyMove (); +} + + +/* +=================== +PM_AirMove + +=================== +*/ +void PM_AirMove (void) +{ + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + + // Copy movement amounts + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + // Zero out z components of movement vectors + pmove->forward[2] = 0; + pmove->right[2] = 0; + // Renormalize + VectorNormalize (pmove->forward); + VectorNormalize (pmove->right); + + // Determine x and y parts of velocity + for (i=0 ; i<2 ; i++) + { + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + } + // Zero out z part of velocity + wishvel[2] = 0; + + // Determine maginitude of speed of move + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + // Clamp to server defined max speed + if (wishspeed > pmove->maxspeed) + { + VectorScale (wishvel, pmove->maxspeed/wishspeed, wishvel); + wishspeed = pmove->maxspeed; + } + + // QUAKECLASSIC: PM_AirAccelerate (wishdir, wishspeed, pmove->movevars->airaccelerate); + PM_AirAccelerate (wishdir, wishspeed, pmove->movevars->accelerate); + + // Add in any base velocity to the current velocity. + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity ); + + PM_FlyMove (); +} + +qboolean PM_InWater( void ) +{ + return ( pmove->waterlevel > 1 ); +} + +qboolean PM_CheckWater () +{ + vec3_t point; + int cont; + int truecont; + float height; + float heightover2; + + // Pick a spot just above the players feet. + point[0] = pmove->origin[0] + (pmove->player_mins[pmove->usehull][0] + pmove->player_maxs[pmove->usehull][0]) * 0.5; + point[1] = pmove->origin[1] + (pmove->player_mins[pmove->usehull][1] + pmove->player_maxs[pmove->usehull][1]) * 0.5; + point[2] = pmove->origin[2] + pmove->player_mins[pmove->usehull][2] + 1; + + // Assume that we are not in water at all. + pmove->waterlevel = 0; + pmove->watertype = CONTENTS_EMPTY; + + // Grab point contents. + cont = pmove->PM_PointContents (point, &truecont ); + // Are we under water? (not solid and not empty?) + if (cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) + { + // Set water type + pmove->watertype = cont; + + // We are at least at level one + pmove->waterlevel = 1; + + height = (pmove->player_mins[pmove->usehull][2] + pmove->player_maxs[pmove->usehull][2]); + heightover2 = height * 0.5; + + // Now check a point that is at the player hull midpoint. + point[2] = pmove->origin[2] + heightover2; + cont = pmove->PM_PointContents (point, NULL ); + // If that point is also under water... + if (cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) + { + // Set a higher water level. + pmove->waterlevel = 2; + + // Now check the eye position. (view_ofs is relative to the origin) + point[2] = pmove->origin[2] + pmove->view_ofs[2]; + + cont = pmove->PM_PointContents (point, NULL ); + if (cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) + pmove->waterlevel = 3; // In over our eyes + } + + // Adjust velocity based on water current, if any. + if ( ( truecont <= CONTENTS_CURRENT_0 ) && + ( truecont >= CONTENTS_CURRENT_DOWN ) ) + { + // The deeper we are, the stronger the current. + static vec3_t current_table[] = + { + {1, 0, 0}, {0, 1, 0}, {-1, 0, 0}, + {0, -1, 0}, {0, 0, 1}, {0, 0, -1} + }; + + VectorMA (pmove->basevelocity, 50.0*pmove->waterlevel, current_table[CONTENTS_CURRENT_0 - truecont], pmove->basevelocity); + } + } + + return pmove->waterlevel > 1; +} + +/* +============= +PM_CatagorizePosition +============= +*/ +void PM_CatagorizePosition (void) +{ + vec3_t point; + pmtrace_t tr; + +// if the player hull point one unit down is solid, the player +// is on ground + +// see if standing on something solid + + // Doing this before we move may introduce a potential latency in water detection, but + // doing it after can get us stuck on the bottom in water if the amount we move up + // is less than the 1 pixel 'threshold' we're about to snap to. Also, we'll call + // this several times per frame, so we really need to avoid sticking to the bottom of + // water on each call, and the converse case will correct itself if called twice. + PM_CheckWater(); + + point[0] = pmove->origin[0]; + point[1] = pmove->origin[1]; + point[2] = pmove->origin[2] - 2; + + if (pmove->velocity[2] > 180) // Shooting up really fast. Definitely not on ground. + { + pmove->onground = -1; + } + else + { + // Try and move down. + tr = pmove->PM_PlayerTrace (pmove->origin, point, PM_NORMAL, -1 ); + // If we hit a steep plane, we are not on ground + if ( tr.plane.normal[2] < 0.7) + pmove->onground = -1; // too steep + else + pmove->onground = tr.ent; // Otherwise, point to index of ent under us. + + // If we are on something... + if (pmove->onground != -1) + { + // Then we are not in water jump sequence + pmove->waterjumptime = 0; + // If we could make the move, drop us down that 1 pixel + // QUAKECLASSIC: + //if (!tr.startsolid && !tr.allsolid) + if ( pmove->waterlevel < 2 && !tr.startsolid && !tr.allsolid ) + VectorCopy (tr.endpos, pmove->origin); + } + + // Standing on an entity other than the world + if (tr.ent > 0) // So signal that we are touching something. + { + PM_AddToTouched(tr, pmove->velocity); + } + } +} + +/* +================= +PM_GetRandomStuckOffsets + +When a player is stuck, it's costly to try and unstick them +Grab a test offset for the player based on a passed in index +================= +*/ +int PM_GetRandomStuckOffsets(int nIndex, int server, vec3_t offset) +{ + // Last time we did a full + int idx; + idx = rgStuckLast[nIndex][server]++; + + VectorCopy(rgv3tStuckTable[idx % 54], offset); + + return (idx % 54); +} + +void PM_ResetStuckOffsets(int nIndex, int server) +{ + rgStuckLast[nIndex][server] = 0; +} + +/* +================= +NudgePosition + +If pmove->origin is in a solid position, +try nudging slightly on all axis to +allow for the cut precision of the net coordinates +================= +*/ +#define PM_CHECKSTUCK_MINTIME 0.05 // Don't check again too quickly. + +int PM_CheckStuck (void) +{ + vec3_t base; + vec3_t offset; + vec3_t test; + int hitent; + int idx; + float fTime; + int i; + pmtrace_t traceresult; + + static float rgStuckCheckTime[MAX_CLIENTS][2]; // Last time we did a full + + // If position is okay, exit + hitent = pmove->PM_TestPlayerPosition (pmove->origin, &traceresult ); + if (hitent == -1 ) + { + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + return 0; + } + + VectorCopy (pmove->origin, base); + + // + // Deal with precision error in network. + // + if (!pmove->server) + { + // World or BSP model + if ( ( hitent == 0 ) || + ( pmove->physents[hitent].model != NULL ) ) + { + int nReps = 0; + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + do + { + i = PM_GetRandomStuckOffsets(pmove->player_index, pmove->server, offset); + + VectorAdd(base, offset, test); + if (pmove->PM_TestPlayerPosition (test, &traceresult ) == -1) + { + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + + VectorCopy ( test, pmove->origin ); + return 0; + } + nReps++; + } while (nReps < 54); + } + } + + // Only an issue on the client. + + if (pmove->server) + idx = 0; + else + idx = 1; + + fTime = pmove->Sys_FloatTime(); + // Too soon? + if (rgStuckCheckTime[pmove->player_index][idx] >= + ( fTime - PM_CHECKSTUCK_MINTIME ) ) + { + return 1; + } + rgStuckCheckTime[pmove->player_index][idx] = fTime; + + pmove->PM_StuckTouch( hitent, &traceresult ); + + i = PM_GetRandomStuckOffsets(pmove->player_index, pmove->server, offset); + + VectorAdd(base, offset, test); + if ( ( hitent = pmove->PM_TestPlayerPosition ( test, NULL ) ) == -1 ) + { + //Con_DPrintf("Nudged\n"); + + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + + if (i >= 27) + VectorCopy ( test, pmove->origin ); + + return 0; + } + + // If player is flailing while stuck in another player ( should never happen ), then see + // if we can't "unstick" them forceably. + if ( pmove->cmd.buttons & ( IN_JUMP | IN_DUCK | IN_ATTACK ) && ( pmove->physents[ hitent ].player != 0 ) ) + { + float x, y, z; + float xystep = 8.0; + float zstep = 18.0; + float xyminmax = xystep; + float zminmax = 4 * zstep; + + for ( z = 0; z <= zminmax; z += zstep ) + { + for ( x = -xyminmax; x <= xyminmax; x += xystep ) + { + for ( y = -xyminmax; y <= xyminmax; y += xystep ) + { + VectorCopy( base, test ); + test[0] += x; + test[1] += y; + test[2] += z; + + if ( pmove->PM_TestPlayerPosition ( test, NULL ) == -1 ) + { + VectorCopy( test, pmove->origin ); + return 0; + } + } + } + } + } + + //VectorCopy (base, pmove->origin); + + return 1; +} + +/* +=============== +PM_SpectatorMove +=============== +*/ +void PM_SpectatorMove (void) +{ + float speed, drop, friction, control, newspeed; + //float accel; + float currentspeed, addspeed, accelspeed; + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + // this routine keeps track of the spectators psoition + // there a two different main move types : track player or moce freely (OBS_ROAMING) + // doesn't need excate track position, only to generate PVS, so just copy + // targets position and real view position is calculated on client (saves server CPU) + +#ifdef CLIENT_DLL + // execute all jumps + if ( iJumpSpectator ) + { + VectorCopy( vJumpOrigin, pmove->origin ); + VectorCopy( vJumpAngles, pmove->angles ); + VectorCopy( vec3_origin, pmove->velocity ); + iJumpSpectator = 0; + return; + } +#endif + + if ( pmove->iuser1 == OBS_ROAMING) + { + // Move around in normal spectator method + + speed = Length (pmove->velocity); + if (speed < 1) + { + VectorCopy (vec3_origin, pmove->velocity) + } + else + { + drop = 0; + + friction = pmove->movevars->friction*1.5; // extra friction + control = speed < pmove->movevars->stopspeed ? pmove->movevars->stopspeed : speed; + drop += control*friction*pmove->frametime; + + // scale the velocity + newspeed = speed - drop; + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + + VectorScale (pmove->velocity, newspeed, pmove->velocity); + } + + // accelerate + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + VectorNormalize (pmove->forward); + VectorNormalize (pmove->right); + + for (i=0 ; i<3 ; i++) + { + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + } + wishvel[2] += pmove->cmd.upmove; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + // + // clamp to server defined max speed + // + if (wishspeed > pmove->movevars->spectatormaxspeed) + { + VectorScale (wishvel, pmove->movevars->spectatormaxspeed/wishspeed, wishvel); + wishspeed = pmove->movevars->spectatormaxspeed; + } + + currentspeed = DotProduct(pmove->velocity, wishdir); + addspeed = wishspeed - currentspeed; + if (addspeed <= 0) + return; + + accelspeed = pmove->movevars->accelerate*pmove->frametime*wishspeed; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i=0 ; i<3 ; i++) + pmove->velocity[i] += accelspeed*wishdir[i]; + + // move + VectorMA (pmove->origin, pmove->frametime, pmove->velocity, pmove->origin); + } + else + { + // all other modes just track some kind of target, so spectator PVS = target PVS + + int target; + + // Find the client this player's targeting + for (target = 0; target < pmove->numphysent; target++) + { + if ( pmove->physents[target].info == pmove->iuser2 ) + break; + } + + if (target == pmove->numphysent) + return; + + // use targets position as own origin for PVS + VectorCopy( pmove->physents[target].angles, pmove->angles ); + VectorCopy( pmove->physents[target].origin, pmove->origin ); + + // no velocity + VectorCopy( vec3_origin, pmove->velocity ); + } +} + +/* +================== +PM_SplineFraction + +Use for ease-in, ease-out style interpolation (accel/decel) +Used by ducking code. +================== +*/ +float PM_SplineFraction( float value, float scale ) +{ + float valueSquared; + + value = scale * value; + valueSquared = value * value; + + // Nice little ease-in, ease-out spline-like curve + return 3 * valueSquared - 2 * valueSquared * value; +} + +void PM_FixPlayerCrouchStuck( int direction ) +{ + int hitent; + int i; + vec3_t test; + + hitent = pmove->PM_TestPlayerPosition ( pmove->origin, NULL ); + if (hitent == -1 ) + return; + + VectorCopy( pmove->origin, test ); + for ( i = 0; i < 36; i++ ) + { + pmove->origin[2] += direction; + hitent = pmove->PM_TestPlayerPosition ( pmove->origin, NULL ); + if (hitent == -1 ) + return; + } + + VectorCopy( test, pmove->origin ); // Failed +} + +void PM_WaterJump (void) +{ + if ( pmove->waterjumptime > 10000 ) + { + pmove->waterjumptime = 10000; + } + + if ( !pmove->waterjumptime ) + return; + + pmove->waterjumptime -= pmove->cmd.msec; + if ( pmove->waterjumptime < 0 || + !pmove->waterlevel ) + { + pmove->waterjumptime = 0; + pmove->flags &= ~FL_WATERJUMP; + } + + pmove->velocity[0] = pmove->movedir[0]; + pmove->velocity[1] = pmove->movedir[1]; +} + +/* +============ +PM_AddGravity + +============ +*/ +void PM_AddGravity () +{ + float ent_gravity; + + if (pmove->gravity) + ent_gravity = pmove->gravity; + else + ent_gravity = 1.0; + + // Add gravity incorrectly + pmove->velocity[2] -= (ent_gravity * pmove->movevars->gravity * pmove->frametime ); + pmove->velocity[2] += pmove->basevelocity[2] * pmove->frametime; + pmove->basevelocity[2] = 0; + PM_CheckVelocity(); +} +/* +============ +PM_PushEntity + +Does not change the entities velocity at all +============ +*/ +pmtrace_t PM_PushEntity (vec3_t push) +{ + pmtrace_t trace; + vec3_t end; + + VectorAdd (pmove->origin, push, end); + + trace = pmove->PM_PlayerTrace (pmove->origin, end, PM_NORMAL, -1 ); + + VectorCopy (trace.endpos, pmove->origin); + + // So we can run impact function afterwards. + if (trace.fraction < 1.0 && + !trace.allsolid) + { + PM_AddToTouched(trace, pmove->velocity); + } + + return trace; +} + +/* +============ +PM_Physics_Toss() + +Dead player flying through air., e.g. +============ +*/ +void PM_Physics_Toss() +{ + pmtrace_t trace; + vec3_t move; + float backoff; + + PM_CheckWater(); + + if (pmove->velocity[2] > 0) + pmove->onground = -1; + + // If on ground and not moving, return. + if ( pmove->onground != -1 ) + { + if (VectorCompare(pmove->basevelocity, vec3_origin) && + VectorCompare(pmove->velocity, vec3_origin)) + return; + } + + PM_CheckVelocity (); + +// add gravity + if ( pmove->movetype != MOVETYPE_FLY && + pmove->movetype != MOVETYPE_BOUNCEMISSILE && + pmove->movetype != MOVETYPE_FLYMISSILE ) + PM_AddGravity (); + +// move origin + // Base velocity is not properly accounted for since this entity will move again after the bounce without + // taking it into account + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity); + + PM_CheckVelocity(); + VectorScale (pmove->velocity, pmove->frametime, move); + VectorSubtract (pmove->velocity, pmove->basevelocity, pmove->velocity); + + trace = PM_PushEntity (move); // Should this clear basevelocity + + PM_CheckVelocity(); + + if (trace.allsolid) + { + // entity is trapped in another solid + pmove->onground = trace.ent; + VectorCopy (vec3_origin, pmove->velocity); + return; + } + + if (trace.fraction == 1) + { + PM_CheckWater(); + return; + } + + + if (pmove->movetype == MOVETYPE_BOUNCE) + // QUAKECLASSIC: backoff = 2.0 - pmove->friction; + backoff = 1.5; + // QUAKECLASSIC: else if (pmove->movetype == MOVETYPE_BOUNCEMISSILE) + // QUAKECLASSIC: backoff = 2.0; + else + backoff = 1; + + PM_ClipVelocity (pmove->velocity, trace.plane.normal, pmove->velocity, backoff); + + // stop if on ground + if (trace.plane.normal[2] > 0.7) + { + float vel; + vec3_t base; + + VectorClear( base ); + + // QUAKECLASSIC: No Static friction + /* + if (pmove->velocity[2] < pmove->movevars->gravity * pmove->frametime) + { + // we're rolling on the ground, add static friction. + pmove->onground = trace.ent; + pmove->velocity[2] = 0; + } + */ + + vel = DotProduct( pmove->velocity, pmove->velocity ); + + // Con_DPrintf("%f %f: %.0f %.0f %.0f\n", vel, trace.fraction, ent->velocity[0], ent->velocity[1], ent->velocity[2] ); + + if (vel < (30 * 30) || (pmove->movetype != MOVETYPE_BOUNCE && pmove->movetype != MOVETYPE_BOUNCEMISSILE)) + { + pmove->onground = trace.ent; + VectorCopy (vec3_origin, pmove->velocity); + } + } + +// check for in water + PM_CheckWater(); +} + +/* +==================== +PM_NoClip + +==================== +*/ +void PM_NoClip() +{ + int i; + vec3_t wishvel; + float fmove, smove; + + // Copy movement amounts + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + VectorNormalize ( pmove->forward ); + VectorNormalize ( pmove->right ); + + for (i=0 ; i<3 ; i++) // Determine x and y parts of velocity + { + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + } + wishvel[2] += pmove->cmd.upmove; + + VectorMA (pmove->origin, pmove->frametime, wishvel, pmove->origin); + + // Zero out the velocity so that we don't accumulate a huge downward velocity from + // gravity, etc. + VectorClear( pmove->velocity ); + +} + + +// Only allow bunny jumping up to 1.7x server / player maxspeed setting +#define BUNNYJUMP_MAX_SPEED_FACTOR 1.7f + +//----------------------------------------------------------------------------- +// Purpose: Corrects bunny jumping ( where player initiates a bunny jump before other +// movement logic runs, thus making onground == -1 thus making PM_Friction get skipped and +// running PM_AirMove, which doesn't crop velocity to maxspeed like the ground / other +// movement logic does. +//----------------------------------------------------------------------------- +void PM_PreventMegaBunnyJumping( void ) +{ + // Current player speed + float spd; + // If we have to crop, apply this cropping fraction to velocity + float fraction; + // Speed at which bunny jumping is limited + float maxscaledspeed; + + maxscaledspeed = BUNNYJUMP_MAX_SPEED_FACTOR * pmove->maxspeed; + + // Don't divide by zero + if ( maxscaledspeed <= 0.0f ) + return; + + spd = Length( pmove->velocity ); + + if ( spd <= maxscaledspeed ) + return; + + fraction = ( maxscaledspeed / spd ) * 0.65; //Returns the modifier for the velocity + + VectorScale( pmove->velocity, fraction, pmove->velocity ); //Crop it down!. +} + + +/* +============= +PM_Jump +============= +*/ +void PM_Jump (void) +{ + if ( pmove->dead ) + { + pmove->oldbuttons |= IN_JUMP ; // don't jump again until released + return; + } + + if ( pmove->maxspeed == 1 ) + return; + + // See if we are waterjumping. If so, decrement count and return. + if ( pmove->waterjumptime ) + { + pmove->waterjumptime -= pmove->cmd.msec; + if (pmove->waterjumptime < 0) + { + pmove->waterjumptime = 0; + } + return; + } + + // If we are in the water most of the way... + if (pmove->waterlevel >= 2) + { // swimming, not jumping + pmove->onground = -1; + + if (pmove->watertype == CONTENTS_WATER) // We move up a certain amount + pmove->velocity[2] = 100; + else if (pmove->watertype == CONTENTS_SLIME) + pmove->velocity[2] = 80; + else // LAVA + pmove->velocity[2] = 50; + + // play swiming sound + if ( pmove->flSwimTime <= 0 ) + { + // Don't play sound again for 1 second + pmove->flSwimTime = 1000; + switch ( pmove->RandomLong( 0, 3 ) ) + { + case 0: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 1: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 2: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 3: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + } + } + + return; + } + + // No more effect + if ( pmove->onground == -1 ) + { + // Flag that we jumped. + // HACK HACK HACK + // Remove this when the game .dll no longer does physics code!!!! + pmove->oldbuttons |= IN_JUMP; // don't jump again until released + return; // in air, so no effect + } + + if ( pmove->oldbuttons & IN_JUMP ) + return; // don't pogo stick + + // In the air now. + pmove->onground = -1; + + PM_PreventMegaBunnyJumping(); + + pmove->PM_PlaySound( CHAN_BODY, "player/plyrjmp8.wav", 0.5, ATTN_NORM, 0, PITCH_NORM ); + + pmove->velocity[2] += 295; + + // Decay it for simulation + PM_FixupGravityVelocity(); + + // Flag that we jumped. + pmove->oldbuttons |= IN_JUMP; // don't jump again until released +} + +/* +============= +PM_CheckWaterJump +============= +*/ +#define WJ_HEIGHT 8 +void PM_CheckWaterJump (void) +{ + vec3_t vecStart, vecEnd; + vec3_t flatforward; + vec3_t flatvelocity; + float curspeed; + pmtrace_t tr; + int savehull; + + // Already water jumping. + if ( pmove->waterjumptime ) + return; + + // Don't hop out if we just jumped in + if ( pmove->velocity[2] < -180 ) + return; // only hop out if we are moving up + + // See if we are backing up + flatvelocity[0] = pmove->velocity[0]; + flatvelocity[1] = pmove->velocity[1]; + flatvelocity[2] = 0; + + // Must be moving + curspeed = VectorNormalize( flatvelocity ); + + // see if near an edge + flatforward[0] = pmove->forward[0]; + flatforward[1] = pmove->forward[1]; + flatforward[2] = 0; + VectorNormalize (flatforward); + + // Are we backing into water from steps or something? If so, don't pop forward + if ( curspeed != 0.0 && ( DotProduct( flatvelocity, flatforward ) < 0.0 ) ) + return; + + VectorCopy( pmove->origin, vecStart ); + vecStart[2] += WJ_HEIGHT; + + VectorMA ( vecStart, 24, flatforward, vecEnd ); + + // Trace, this trace should use the point sized collision hull + savehull = pmove->usehull; + pmove->usehull = 2; + tr = pmove->PM_PlayerTrace( vecStart, vecEnd, PM_NORMAL, -1 ); + if ( tr.fraction < 1.0 && fabs( tr.plane.normal[2] ) < 0.1f ) // Facing a near vertical wall? + { + vecStart[2] += pmove->player_maxs[ savehull ][2] - WJ_HEIGHT; + VectorMA( vecStart, 24, flatforward, vecEnd ); + VectorMA( vec3_origin, -50, tr.plane.normal, pmove->movedir ); + + tr = pmove->PM_PlayerTrace( vecStart, vecEnd, PM_NORMAL, -1 ); + if ( tr.fraction == 1.0 ) + { + pmove->waterjumptime = 2000; + pmove->velocity[2] = 225; + pmove->oldbuttons |= IN_JUMP; + pmove->flags |= FL_WATERJUMP; + } + } + + // Reset the collision hull + pmove->usehull = savehull; +} + +void PM_CheckFalling( void ) +{ + if ( pmove->onground != -1 && + !pmove->dead && + pmove->flFallVelocity >= PLAYER_FALL_PUNCH_THRESHHOLD ) + { + float fvol = 0.5; + + if ( pmove->waterlevel > 0 ) + { + } + else if ( pmove->flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED ) + { + pmove->PM_PlaySound( CHAN_VOICE, "player/pl_fallpain3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + fvol = 1.0; + } + else if ( pmove->flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED / 2 ) + { + fvol = 0.85; + } + else if ( pmove->flFallVelocity < PLAYER_MIN_BOUNCE_SPEED ) + { + fvol = 0; + } + + if ( fvol > 0.0 ) + { + // Play landing step right away + pmove->flTimeStepSound = 0; + + PM_UpdateStepSound(); + + // Knock the screen around a little bit, temporary effect + pmove->punchangle[ 2 ] = pmove->flFallVelocity * 0.013; // punch z axis + + if ( pmove->punchangle[ 0 ] > 8 ) + { + pmove->punchangle[ 0 ] = 8; + } + } + } + + if ( pmove->onground != -1 ) + { + pmove->flFallVelocity = 0; + } +} + +/* +================= +PM_PlayWaterSounds + +================= +*/ +void PM_PlayWaterSounds( void ) +{ + // Did we enter or leave water? + if ( ( pmove->oldwaterlevel == 0 && pmove->waterlevel != 0 ) || + ( pmove->oldwaterlevel != 0 && pmove->waterlevel == 0 ) ) + { + switch ( pmove->RandomLong(0,3) ) + { + case 0: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 1: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 2: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 3: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + } + } +} + +/* +=============== +PM_CalcRoll + +=============== +*/ +float PM_CalcRoll (vec3_t angles, vec3_t velocity, float rollangle, float rollspeed ) +{ + float sign; + float side; + float value; + vec3_t forward, right, up; + + AngleVectors (angles, forward, right, up); + + side = DotProduct (velocity, right); + + sign = side < 0 ? -1 : 1; + + side = fabs(side); + + value = rollangle; + + if (side < rollspeed) + { + side = side * value / rollspeed; + } + else + { + side = value; + } + + return side * sign; +} + +/* +============= +PM_DropPunchAngle + +============= +*/ +void PM_DropPunchAngle ( vec3_t punchangle ) +{ + float len; + + len = VectorNormalize ( punchangle ); + len -= (10.0 + len * 0.5) * pmove->frametime; + len = max( len, 0.0 ); + VectorScale ( punchangle, len, punchangle); +} + +/* +============== +PM_CheckParamters + +============== +*/ +void PM_CheckParamters( void ) +{ + float spd; + float maxspeed; + vec3_t v_angle; + + spd = ( pmove->cmd.forwardmove * pmove->cmd.forwardmove ) + + ( pmove->cmd.sidemove * pmove->cmd.sidemove ) + + ( pmove->cmd.upmove * pmove->cmd.upmove ); + spd = sqrt( spd ); + + maxspeed = pmove->clientmaxspeed; //atof( pmove->PM_Info_ValueForKey( pmove->physinfo, "maxspd" ) ); + if ( maxspeed != 0.0 ) + { + pmove->maxspeed = min( maxspeed, pmove->maxspeed ); + } + + if ( ( spd != 0.0 ) && + ( spd > pmove->maxspeed ) ) + { + float fRatio = pmove->maxspeed / spd; + pmove->cmd.forwardmove *= fRatio; + pmove->cmd.sidemove *= fRatio; + pmove->cmd.upmove *= fRatio; + } + + if ( pmove->flags & FL_FROZEN || + pmove->flags & FL_ONTRAIN || + pmove->dead ) + { + pmove->cmd.forwardmove = 0; + pmove->cmd.sidemove = 0; + pmove->cmd.upmove = 0; + } + + + PM_DropPunchAngle( pmove->punchangle ); + + // Take angles from command. + if ( !pmove->dead ) + { + VectorCopy ( pmove->cmd.viewangles, v_angle ); + VectorAdd( v_angle, pmove->punchangle, v_angle ); + + // Set up view angles. + pmove->angles[ROLL] = PM_CalcRoll ( v_angle, pmove->velocity, pmove->movevars->rollangle, pmove->movevars->rollspeed )*4; + pmove->angles[PITCH] = v_angle[PITCH]; + pmove->angles[YAW] = v_angle[YAW]; + } + else + { + VectorCopy( pmove->oldangles, pmove->angles ); + } + + // Set dead player view_offset + if ( pmove->dead ) + { + pmove->view_ofs[2] = PM_DEAD_VIEWHEIGHT; + } + + // Adjust client view angles to match values used on server. + if (pmove->angles[YAW] > 180.0f) + { + pmove->angles[YAW] -= 360.0f; + } + +} + +void PM_ReduceTimers( void ) +{ + if ( pmove->flTimeStepSound > 0 ) + { + pmove->flTimeStepSound -= pmove->cmd.msec; + if ( pmove->flTimeStepSound < 0 ) + { + pmove->flTimeStepSound = 0; + } + } + if ( pmove->flDuckTime > 0 ) + { + pmove->flDuckTime -= pmove->cmd.msec; + if ( pmove->flDuckTime < 0 ) + { + pmove->flDuckTime = 0; + } + } + if ( pmove->flSwimTime > 0 ) + { + pmove->flSwimTime -= pmove->cmd.msec; + if ( pmove->flSwimTime < 0 ) + { + pmove->flSwimTime = 0; + } + } +} + +/* +============= +PlayerMove + +Returns with origin, angles, and velocity modified in place. + +Numtouch and touchindex[] will be set if any of the physents +were contacted during the move. +============= +*/ +void PM_PlayerMove ( qboolean server ) +{ + physent_t *pLadder = NULL; + + // Are we running server code? + pmove->server = server; + + // Adjust speeds etc. + PM_CheckParamters(); + + // Assume we don't touch anything + pmove->numtouch = 0; + + // # of msec to apply movement + pmove->frametime = pmove->cmd.msec * 0.001; + + PM_ReduceTimers(); + + // Convert view angles to vectors + AngleVectors (pmove->angles, pmove->forward, pmove->right, pmove->up); + + // PM_ShowClipBox(); + + // Special handling for spectator and observers. (iuser1 is set if the player's in observer mode) + if ( pmove->spectator || pmove->iuser1 > 0 ) + { + PM_SpectatorMove(); + PM_CatagorizePosition(); + return; + } + + // Always try and unstick us unless we are in NOCLIP mode + if ( pmove->movetype != MOVETYPE_NOCLIP && pmove->movetype != MOVETYPE_NONE ) + { + if ( PM_CheckStuck() ) + { + return; // Can't move, we're stuck + } + } + + // Now that we are "unstuck", see where we are ( waterlevel and type, pmove->onground ). + PM_CatagorizePosition(); + + // Store off the starting water level + pmove->oldwaterlevel = pmove->waterlevel; + + // If we are not on ground, store off how fast we are moving down + if ( pmove->onground == -1 ) + { + pmove->flFallVelocity = -pmove->velocity[2]; + } + + g_onladder = 0; + + PM_UpdateStepSound(); + + // Don't run ladder code if dead or on a train + if ( !pmove->dead && !(pmove->flags & FL_ONTRAIN) ) + { + if ( pmove->movetype != MOVETYPE_WALK && + pmove->movetype != MOVETYPE_NOCLIP ) + { + // Clear ladder stuff unless player is noclipping + // it will be set immediately again next frame if necessary + pmove->movetype = MOVETYPE_WALK; + } + } + + // Slow down, I'm pulling it! (a box maybe) but only when I'm standing on ground + if ( ( pmove->onground != -1 ) && ( pmove->cmd.buttons & IN_USE) ) + { + VectorScale( pmove->velocity, 0.3, pmove->velocity ); + } + + // Handle movement + switch ( pmove->movetype ) + { + default: + pmove->Con_DPrintf("Bogus pmove player movetype %i on (%i) 0=cl 1=sv\n", pmove->movetype, pmove->server); + break; + + case MOVETYPE_NONE: + break; + + case MOVETYPE_NOCLIP: + PM_NoClip(); + break; + + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + PM_Physics_Toss(); + break; + + case MOVETYPE_FLY: + + PM_CheckWater(); + + // Was jump button pressed? + // If so, set velocity to 270 away from ladder. This is currently wrong. + // Also, set MOVE_TYPE to walk, too. + if ( pmove->cmd.buttons & IN_JUMP ) + { + if ( !pLadder ) + { + PM_Jump (); + } + } + else + { + pmove->oldbuttons &= ~IN_JUMP; + } + + // Perform the move accounting for any base velocity. + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity); + PM_FlyMove (); + VectorSubtract (pmove->velocity, pmove->basevelocity, pmove->velocity); + break; + + case MOVETYPE_WALK: + if ( !PM_InWater() ) + { + PM_AddCorrectGravity(); + } + + // If we are leaping out of the water, just update the counters. + if ( pmove->waterjumptime ) + { + PM_WaterJump(); + PM_FlyMove(); + + // Make sure waterlevel is set correctly + PM_CheckWater(); + return; + } + + // If we are swimming in the water, see if we are nudging against a place we can jump up out + // of, and, if so, start out jump. Otherwise, if we are not moving up, then reset jump timer to 0 + if ( pmove->waterlevel >= 2 ) + { + if ( pmove->waterlevel == 2 ) + { + PM_CheckWaterJump(); + } + + // If we are falling again, then we must not trying to jump out of water any more. + if ( pmove->velocity[2] < 0 && pmove->waterjumptime ) + { + pmove->waterjumptime = 0; + } + + // Was jump button pressed? + if (pmove->cmd.buttons & IN_JUMP) + { + PM_Jump (); + } + else + { + pmove->oldbuttons &= ~IN_JUMP; + } + + // Perform regular water movement + PM_WaterMove(); + + VectorSubtract (pmove->velocity, pmove->basevelocity, pmove->velocity); + + // Get a final position + PM_CatagorizePosition(); + } + else + + // Not underwater + { + // Was jump button pressed? + if ( pmove->cmd.buttons & IN_JUMP ) + { + if ( !pLadder ) + { + PM_Jump (); + } + } + else + { + pmove->oldbuttons &= ~IN_JUMP; + } + + // Fricion is handled before we add in any base velocity. That way, if we are on a conveyor, + // we don't slow when standing still, relative to the conveyor. + if ( pmove->onground != -1 ) + { + pmove->velocity[2] = 0.0; + PM_Friction(); + } + + // Make sure velocity is valid. + PM_CheckVelocity(); + + // Are we on ground now + if ( pmove->onground != -1 ) + { + PM_WalkMove(); + } + else + { + PM_AirMove(); // Take into account movement when in air. + } + + // Set final flags. + PM_CatagorizePosition(); + + // Now pull the base velocity back out. + // Base velocity is set if you are on a moving object, like + // a conveyor (or maybe another monster?) + VectorSubtract (pmove->velocity, pmove->basevelocity, pmove->velocity ); + + // Make sure velocity is valid. + PM_CheckVelocity(); + + // Add any remaining gravitational component. + if ( !PM_InWater() ) + { + PM_FixupGravityVelocity(); + } + + // If we are on ground, no downward velocity. + if ( pmove->onground != -1 ) + { + pmove->velocity[2] = 0; + } + + // See if we landed on the ground with enough force to play + // a landing sound. + PM_CheckFalling(); + } + + // Did we enter or leave the water? + PM_PlayWaterSounds(); + break; + } +} + +void PM_CreateStuckTable( void ) +{ + float x, y, z; + int idx; + int i; + float zi[3]; + + memset(rgv3tStuckTable, 0, 54 * sizeof(vec3_t)); + + idx = 0; + // Little Moves. + x = y = 0; + // Z moves + for (z = -0.125 ; z <= 0.125 ; z += 0.125) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + x = z = 0; + // Y moves + for (y = -0.125 ; y <= 0.125 ; y += 0.125) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + y = z = 0; + // X moves + for (x = -0.125 ; x <= 0.125 ; x += 0.125) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + + // Remaining multi axis nudges. + for ( x = - 0.125; x <= 0.125; x += 0.250 ) + { + for ( y = - 0.125; y <= 0.125; y += 0.250 ) + { + for ( z = - 0.125; z <= 0.125; z += 0.250 ) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + } + } + + // Big Moves. + x = y = 0; + zi[0] = 0.0f; + zi[1] = 1.0f; + zi[2] = 6.0f; + + for (i = 0; i < 3; i++) + { + // Z moves + z = zi[i]; + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + + x = z = 0; + + // Y moves + for (y = -2.0f ; y <= 2.0f ; y += 2.0) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + y = z = 0; + // X moves + for (x = -2.0f ; x <= 2.0f ; x += 2.0f) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + + // Remaining multi axis nudges. + for (i = 0 ; i < 3; i++) + { + z = zi[i]; + + for (x = -2.0f ; x <= 2.0f ; x += 2.0f) + { + for (y = -2.0f ; y <= 2.0f ; y += 2.0) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + } + } +} + + + +/* +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 +invoked by each side as appropriate. There should be no distinction, internally, between server +and client. This will ensure that prediction behaves appropriately. +*/ + +void PM_Move ( struct playermove_s *ppmove, int server ) +{ + assert( pm_shared_initialized ); + + pmove = ppmove; + + pmove->player_mins[ pmove->usehull ][ 2 ] = -24; + pmove->player_maxs[ pmove->usehull ][ 2 ] = 32; + + PM_PlayerMove( ( server != 0 ) ? true : false ); + + if ( pmove->onground != -1 ) + { + pmove->flags |= FL_ONGROUND; + } + else + { + pmove->flags &= ~FL_ONGROUND; + } + + // In single player, reset friction after each movement to FrictionModifier Triggers work still. + if ( !pmove->multiplayer && ( pmove->movetype == MOVETYPE_WALK ) ) + { + pmove->friction = 1.0f; + } +} + +int PM_GetInfo( int ent ) +{ + if ( ent >= 0 && ent <= pmove->numvisent ) + { + return pmove->visents[ ent ].info; + } + return -1; +} + +void PM_Init( struct playermove_s *ppmove ) +{ + assert( !pm_shared_initialized ); + + pmove = ppmove; + + PM_CreateStuckTable(); + PM_InitTextureTypes(); + + pm_shared_initialized = 1; +} diff --git a/hlsdk/dmc/pm_shared/pm_shared.h b/hlsdk/dmc/pm_shared/pm_shared.h new file mode 100644 index 00000000..153c539f --- /dev/null +++ b/hlsdk/dmc/pm_shared/pm_shared.h @@ -0,0 +1,40 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +// +// pm_shared.h +// +#if !defined( PM_SHAREDH ) +#define PM_SHAREDH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +void PM_Init( struct playermove_s *ppmove ); +void PM_Move ( struct playermove_s *ppmove, int server ); +char PM_FindTextureType( char *name ); + +// Spectator Movement modes (stored in pev->iuser1, so the physics code can get at them) +#define OBS_NONE 0 +#define OBS_CHASE_LOCKED 1 +#define OBS_CHASE_FREE 2 +#define OBS_ROAMING 3 +#define OBS_IN_EYE 4 +#define OBS_MAP_FREE 5 +#define OBS_MAP_CHASE 6 + +#endif diff --git a/hlsdk/engine/Sequence.h b/hlsdk/engine/Sequence.h new file mode 100644 index 00000000..eaea2c7e --- /dev/null +++ b/hlsdk/engine/Sequence.h @@ -0,0 +1,204 @@ +//--------------------------------------------------------------------------- +// +// S c r i p t e d S e q u e n c e s +// +//--------------------------------------------------------------------------- +#ifndef _INCLUDE_SEQUENCE_H_ +#define _INCLUDE_SEQUENCE_H_ + + +#ifndef _DEF_BYTE_ +typedef unsigned char byte; +#endif + +//--------------------------------------------------------------------------- +// client_textmessage_t +//--------------------------------------------------------------------------- +#ifndef _CLIENT_TEXTMSG_DEFINED_ +#define _CLIENT_TEXTMSG_DEFINED_ +typedef struct client_textmessage_s +{ + int effect; + byte r1, g1, b1, a1; // 2 colors for effects + byte r2, g2, b2, a2; + float x; + float y; + float fadein; + float fadeout; + float holdtime; + float fxtime; + const char *pName; + const char *pMessage; +} client_textmessage_t; +#endif + + +//-------------------------------------------------------------------------- +// sequenceDefaultBits_e +// +// Enumerated list of possible modifiers for a command. This enumeration +// is used in a bitarray controlling what modifiers are specified for a command. +//--------------------------------------------------------------------------- +enum sequenceModifierBits +{ + SEQUENCE_MODIFIER_EFFECT_BIT = (1 << 1), + SEQUENCE_MODIFIER_POSITION_BIT = (1 << 2), + SEQUENCE_MODIFIER_COLOR_BIT = (1 << 3), + SEQUENCE_MODIFIER_COLOR2_BIT = (1 << 4), + SEQUENCE_MODIFIER_FADEIN_BIT = (1 << 5), + SEQUENCE_MODIFIER_FADEOUT_BIT = (1 << 6), + SEQUENCE_MODIFIER_HOLDTIME_BIT = (1 << 7), + SEQUENCE_MODIFIER_FXTIME_BIT = (1 << 8), + SEQUENCE_MODIFIER_SPEAKER_BIT = (1 << 9), + SEQUENCE_MODIFIER_LISTENER_BIT = (1 << 10), + SEQUENCE_MODIFIER_TEXTCHANNEL_BIT = (1 << 11), +}; +typedef enum sequenceModifierBits sequenceModifierBits_e ; + + +//--------------------------------------------------------------------------- +// sequenceCommandEnum_e +// +// Enumerated sequence command types. +//--------------------------------------------------------------------------- +enum sequenceCommandEnum_ +{ + SEQUENCE_COMMAND_ERROR = -1, + SEQUENCE_COMMAND_PAUSE = 0, + SEQUENCE_COMMAND_FIRETARGETS, + SEQUENCE_COMMAND_KILLTARGETS, + SEQUENCE_COMMAND_TEXT, + SEQUENCE_COMMAND_SOUND, + SEQUENCE_COMMAND_GOSUB, + SEQUENCE_COMMAND_SENTENCE, + SEQUENCE_COMMAND_REPEAT, + SEQUENCE_COMMAND_SETDEFAULTS, + SEQUENCE_COMMAND_MODIFIER, + SEQUENCE_COMMAND_POSTMODIFIER, + SEQUENCE_COMMAND_NOOP, + + SEQUENCE_MODIFIER_EFFECT, + SEQUENCE_MODIFIER_POSITION, + SEQUENCE_MODIFIER_COLOR, + SEQUENCE_MODIFIER_COLOR2, + SEQUENCE_MODIFIER_FADEIN, + SEQUENCE_MODIFIER_FADEOUT, + SEQUENCE_MODIFIER_HOLDTIME, + SEQUENCE_MODIFIER_FXTIME, + SEQUENCE_MODIFIER_SPEAKER, + SEQUENCE_MODIFIER_LISTENER, + SEQUENCE_MODIFIER_TEXTCHANNEL, +}; +typedef enum sequenceCommandEnum_ sequenceCommandEnum_e; + + +//--------------------------------------------------------------------------- +// sequenceCommandType_e +// +// Typeerated sequence command types. +//--------------------------------------------------------------------------- +enum sequenceCommandType_ +{ + SEQUENCE_TYPE_COMMAND, + SEQUENCE_TYPE_MODIFIER, +}; +typedef enum sequenceCommandType_ sequenceCommandType_e; + + +//--------------------------------------------------------------------------- +// sequenceCommandMapping_s +// +// A mapping of a command enumerated-value to its name. +//--------------------------------------------------------------------------- +typedef struct sequenceCommandMapping_ sequenceCommandMapping_s; +struct sequenceCommandMapping_ +{ + sequenceCommandEnum_e commandEnum; + const char* commandName; + sequenceCommandType_e commandType; +}; + + +//--------------------------------------------------------------------------- +// sequenceCommandLine_s +// +// Structure representing a single command (usually 1 line) from a +// .SEQ file entry. +//--------------------------------------------------------------------------- +typedef struct sequenceCommandLine_ sequenceCommandLine_s; +struct sequenceCommandLine_ +{ + int commandType; // Specifies the type of command + client_textmessage_t clientMessage; // Text HUD message struct + char* speakerName; // Targetname of speaking entity + char* listenerName; // Targetname of entity being spoken to + char* soundFileName; // Name of sound file to play + char* sentenceName; // Name of sentences.txt to play + char* fireTargetNames; // List of targetnames to fire + char* killTargetNames; // List of targetnames to remove + float delay; // Seconds 'till next command + int repeatCount; // If nonzero, reset execution pointer to top of block (N times, -1 = infinite) + int textChannel; // Display channel on which text message is sent + int modifierBitField; // Bit field to specify what clientmessage fields are valid + sequenceCommandLine_s* nextCommandLine; // Next command (linked list) +}; + + +//--------------------------------------------------------------------------- +// sequenceEntry_s +// +// Structure representing a single command (usually 1 line) from a +// .SEQ file entry. +//--------------------------------------------------------------------------- +typedef struct sequenceEntry_ sequenceEntry_s; +struct sequenceEntry_ +{ + char* fileName; // Name of sequence file without .SEQ extension + char* entryName; // Name of entry label in file + sequenceCommandLine_s* firstCommand; // Linked list of commands in entry + sequenceEntry_s* nextEntry; // Next loaded entry + qboolean isGlobal; // Is entry retained over level transitions? +}; + + + +//--------------------------------------------------------------------------- +// sentenceEntry_s +// Structure representing a single sentence of a group from a .SEQ +// file entry. Sentences are identical to entries in sentences.txt, but +// can be unique per level and are loaded/unloaded with the level. +//--------------------------------------------------------------------------- +typedef struct sentenceEntry_ sentenceEntry_s; +struct sentenceEntry_ +{ + char* data; // sentence data (ie "We have hostiles" ) + sentenceEntry_s* nextEntry; // Next loaded entry + qboolean isGlobal; // Is entry retained over level transitions? + unsigned int index; // this entry's position in the file. +}; + +//-------------------------------------------------------------------------- +// sentenceGroupEntry_s +// Structure representing a group of sentences found in a .SEQ file. +// A sentence group is defined by all sentences with the same name, ignoring +// the number at the end of the sentence name. Groups enable a sentence +// to be picked at random across a group. +//-------------------------------------------------------------------------- +typedef struct sentenceGroupEntry_ sentenceGroupEntry_s; +struct sentenceGroupEntry_ +{ + char* groupName; // name of the group (ie CT_ALERT ) + unsigned int numSentences; // number of sentences in group + sentenceEntry_s* firstSentence; // head of linked list of sentences in group + sentenceGroupEntry_s* nextEntry; // next loaded group +}; + +//--------------------------------------------------------------------------- +// Function declarations +//--------------------------------------------------------------------------- +sequenceEntry_s* SequenceGet( const char* fileName, const char* entryName ); +void Sequence_ParseFile( const char* fileName, qboolean isGlobal ); +void Sequence_OnLevelLoad( const char* mapName ); +sentenceEntry_s* SequencePickSentence( const char *groupName, int pickMethod, int *picked ); + +#endif /* _INCLUDE_SEQUENCE_H_ */ diff --git a/hlsdk/engine/anorms.h b/hlsdk/engine/anorms.h new file mode 100644 index 00000000..c90f8d6f --- /dev/null +++ b/hlsdk/engine/anorms.h @@ -0,0 +1,177 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +{-0.525731, 0.000000, 0.850651}, +{-0.442863, 0.238856, 0.864188}, +{-0.295242, 0.000000, 0.955423}, +{-0.309017, 0.500000, 0.809017}, +{-0.162460, 0.262866, 0.951056}, +{0.000000, 0.000000, 1.000000}, +{0.000000, 0.850651, 0.525731}, +{-0.147621, 0.716567, 0.681718}, +{0.147621, 0.716567, 0.681718}, +{0.000000, 0.525731, 0.850651}, +{0.309017, 0.500000, 0.809017}, +{0.525731, 0.000000, 0.850651}, +{0.295242, 0.000000, 0.955423}, +{0.442863, 0.238856, 0.864188}, +{0.162460, 0.262866, 0.951056}, +{-0.681718, 0.147621, 0.716567}, +{-0.809017, 0.309017, 0.500000}, +{-0.587785, 0.425325, 0.688191}, +{-0.850651, 0.525731, 0.000000}, +{-0.864188, 0.442863, 0.238856}, +{-0.716567, 0.681718, 0.147621}, +{-0.688191, 0.587785, 0.425325}, +{-0.500000, 0.809017, 0.309017}, +{-0.238856, 0.864188, 0.442863}, +{-0.425325, 0.688191, 0.587785}, +{-0.716567, 0.681718, -0.147621}, +{-0.500000, 0.809017, -0.309017}, +{-0.525731, 0.850651, 0.000000}, +{0.000000, 0.850651, -0.525731}, +{-0.238856, 0.864188, -0.442863}, +{0.000000, 0.955423, -0.295242}, +{-0.262866, 0.951056, -0.162460}, +{0.000000, 1.000000, 0.000000}, +{0.000000, 0.955423, 0.295242}, +{-0.262866, 0.951056, 0.162460}, +{0.238856, 0.864188, 0.442863}, +{0.262866, 0.951056, 0.162460}, +{0.500000, 0.809017, 0.309017}, +{0.238856, 0.864188, -0.442863}, +{0.262866, 0.951056, -0.162460}, +{0.500000, 0.809017, -0.309017}, +{0.850651, 0.525731, 0.000000}, +{0.716567, 0.681718, 0.147621}, +{0.716567, 0.681718, -0.147621}, +{0.525731, 0.850651, 0.000000}, +{0.425325, 0.688191, 0.587785}, +{0.864188, 0.442863, 0.238856}, +{0.688191, 0.587785, 0.425325}, +{0.809017, 0.309017, 0.500000}, +{0.681718, 0.147621, 0.716567}, +{0.587785, 0.425325, 0.688191}, +{0.955423, 0.295242, 0.000000}, +{1.000000, 0.000000, 0.000000}, +{0.951056, 0.162460, 0.262866}, +{0.850651, -0.525731, 0.000000}, +{0.955423, -0.295242, 0.000000}, +{0.864188, -0.442863, 0.238856}, +{0.951056, -0.162460, 0.262866}, +{0.809017, -0.309017, 0.500000}, +{0.681718, -0.147621, 0.716567}, +{0.850651, 0.000000, 0.525731}, +{0.864188, 0.442863, -0.238856}, +{0.809017, 0.309017, -0.500000}, +{0.951056, 0.162460, -0.262866}, +{0.525731, 0.000000, -0.850651}, +{0.681718, 0.147621, -0.716567}, +{0.681718, -0.147621, -0.716567}, +{0.850651, 0.000000, -0.525731}, +{0.809017, -0.309017, -0.500000}, +{0.864188, -0.442863, -0.238856}, +{0.951056, -0.162460, -0.262866}, +{0.147621, 0.716567, -0.681718}, +{0.309017, 0.500000, -0.809017}, +{0.425325, 0.688191, -0.587785}, +{0.442863, 0.238856, -0.864188}, +{0.587785, 0.425325, -0.688191}, +{0.688191, 0.587785, -0.425325}, +{-0.147621, 0.716567, -0.681718}, +{-0.309017, 0.500000, -0.809017}, +{0.000000, 0.525731, -0.850651}, +{-0.525731, 0.000000, -0.850651}, +{-0.442863, 0.238856, -0.864188}, +{-0.295242, 0.000000, -0.955423}, +{-0.162460, 0.262866, -0.951056}, +{0.000000, 0.000000, -1.000000}, +{0.295242, 0.000000, -0.955423}, +{0.162460, 0.262866, -0.951056}, +{-0.442863, -0.238856, -0.864188}, +{-0.309017, -0.500000, -0.809017}, +{-0.162460, -0.262866, -0.951056}, +{0.000000, -0.850651, -0.525731}, +{-0.147621, -0.716567, -0.681718}, +{0.147621, -0.716567, -0.681718}, +{0.000000, -0.525731, -0.850651}, +{0.309017, -0.500000, -0.809017}, +{0.442863, -0.238856, -0.864188}, +{0.162460, -0.262866, -0.951056}, +{0.238856, -0.864188, -0.442863}, +{0.500000, -0.809017, -0.309017}, +{0.425325, -0.688191, -0.587785}, +{0.716567, -0.681718, -0.147621}, +{0.688191, -0.587785, -0.425325}, +{0.587785, -0.425325, -0.688191}, +{0.000000, -0.955423, -0.295242}, +{0.000000, -1.000000, 0.000000}, +{0.262866, -0.951056, -0.162460}, +{0.000000, -0.850651, 0.525731}, +{0.000000, -0.955423, 0.295242}, +{0.238856, -0.864188, 0.442863}, +{0.262866, -0.951056, 0.162460}, +{0.500000, -0.809017, 0.309017}, +{0.716567, -0.681718, 0.147621}, +{0.525731, -0.850651, 0.000000}, +{-0.238856, -0.864188, -0.442863}, +{-0.500000, -0.809017, -0.309017}, +{-0.262866, -0.951056, -0.162460}, +{-0.850651, -0.525731, 0.000000}, +{-0.716567, -0.681718, -0.147621}, +{-0.716567, -0.681718, 0.147621}, +{-0.525731, -0.850651, 0.000000}, +{-0.500000, -0.809017, 0.309017}, +{-0.238856, -0.864188, 0.442863}, +{-0.262866, -0.951056, 0.162460}, +{-0.864188, -0.442863, 0.238856}, +{-0.809017, -0.309017, 0.500000}, +{-0.688191, -0.587785, 0.425325}, +{-0.681718, -0.147621, 0.716567}, +{-0.442863, -0.238856, 0.864188}, +{-0.587785, -0.425325, 0.688191}, +{-0.309017, -0.500000, 0.809017}, +{-0.147621, -0.716567, 0.681718}, +{-0.425325, -0.688191, 0.587785}, +{-0.162460, -0.262866, 0.951056}, +{0.442863, -0.238856, 0.864188}, +{0.162460, -0.262866, 0.951056}, +{0.309017, -0.500000, 0.809017}, +{0.147621, -0.716567, 0.681718}, +{0.000000, -0.525731, 0.850651}, +{0.425325, -0.688191, 0.587785}, +{0.587785, -0.425325, 0.688191}, +{0.688191, -0.587785, 0.425325}, +{-0.955423, 0.295242, 0.000000}, +{-0.951056, 0.162460, 0.262866}, +{-1.000000, 0.000000, 0.000000}, +{-0.850651, 0.000000, 0.525731}, +{-0.955423, -0.295242, 0.000000}, +{-0.951056, -0.162460, 0.262866}, +{-0.864188, 0.442863, -0.238856}, +{-0.951056, 0.162460, -0.262866}, +{-0.809017, 0.309017, -0.500000}, +{-0.864188, -0.442863, -0.238856}, +{-0.951056, -0.162460, -0.262866}, +{-0.809017, -0.309017, -0.500000}, +{-0.681718, 0.147621, -0.716567}, +{-0.681718, -0.147621, -0.716567}, +{-0.850651, 0.000000, -0.525731}, +{-0.688191, 0.587785, -0.425325}, +{-0.587785, 0.425325, -0.688191}, +{-0.425325, 0.688191, -0.587785}, +{-0.425325, -0.688191, -0.587785}, +{-0.587785, -0.425325, -0.688191}, +{-0.688191, -0.587785, -0.425325}, diff --git a/hlsdk/engine/archtypes.h b/hlsdk/engine/archtypes.h new file mode 100644 index 00000000..ea143ce6 --- /dev/null +++ b/hlsdk/engine/archtypes.h @@ -0,0 +1,41 @@ +// +// Word size dependent definitions +// DAL 1/03 +// +#ifndef ARCHTYPES_H +#define ARCHTYPES_H + +#ifdef __x86_64__ +#define X64BITS +#endif + +#if defined( _WIN32 ) && (! defined( __MINGW32__ )) + +typedef __int16 int16; +typedef unsigned __int16 uint16; +typedef __int32 int32; +typedef unsigned __int32 uint32; +typedef __int64 int64; +typedef unsigned __int64 uint64; +typedef __int32 intp; // intp is an integer that can accomodate a pointer +typedef unsigned __int32 uintp; // (ie, sizeof(intp) >= sizeof(int) && sizeof(intp) >= sizeof(void *) + +#else /* _WIN32 */ + +typedef short int16; +typedef unsigned short uint16; +typedef int int32; +typedef unsigned int uint32; +typedef long long int64; +typedef unsigned long long uint64; +#ifdef X64BITS +typedef long long intp; +typedef unsigned long long uintp; +#else +typedef int intp; +typedef unsigned int uintp; +#endif + +#endif /* else _WIN32 */ + +#endif /* ARCHTYPES_H */ diff --git a/hlsdk/engine/cdll_int.h b/hlsdk/engine/cdll_int.h new file mode 100644 index 00000000..966f03f1 --- /dev/null +++ b/hlsdk/engine/cdll_int.h @@ -0,0 +1,315 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cdll_int.h +// +// 4-23-98 +// JOHN: client dll interface declarations +// + +#ifndef CDLL_INT_H +#define CDLL_INT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "const.h" +#include "archtypes.h" + + +// this file is included by both the engine and the client-dll, +// so make sure engine declarations aren't done twice + +typedef int HSPRITE; // handle to a graphic + +#define SCRINFO_SCREENFLASH 1 +#define SCRINFO_STRETCHED 2 + +typedef struct SCREENINFO_s +{ + int iSize; + int iWidth; + int iHeight; + int iFlags; + int iCharHeight; + short charWidths[256]; +} SCREENINFO; + + +typedef struct client_data_s +{ + // fields that cannot be modified (ie. have no effect if changed) + vec3_t origin; + + // fields that can be changed by the cldll + vec3_t viewangles; + int iWeaponBits; + float fov; // field of view +} client_data_t; + +typedef struct client_sprite_s +{ + char szName[64]; + char szSprite[64]; + int hspr; + int iRes; + wrect_t rc; +} client_sprite_t; + +#ifndef _CLIENT_TEXTMSG_DEFINED_ +#define _CLIENT_TEXTMSG_DEFINED_ +typedef struct client_textmessage_s +{ + int effect; + byte r1, g1, b1, a1; // 2 colors for effects + byte r2, g2, b2, a2; + float x; + float y; + float fadein; + float fadeout; + float holdtime; + float fxtime; + const char *pName; + const char *pMessage; +} client_textmessage_t; +#endif + +typedef struct hud_player_info_s +{ + char *name; + short ping; + byte thisplayer; // TRUE if this is the calling player + + // stuff that's unused at the moment, but should be done + byte spectator; + byte packetloss; + + char *model; + short topcolor; + short bottomcolor; + +} hud_player_info_t; + + +typedef struct cl_enginefuncs_s +{ + // sprite handlers + HSPRITE ( *pfnSPR_Load ) ( const char *szPicName ); + int ( *pfnSPR_Frames ) ( HSPRITE hPic ); + int ( *pfnSPR_Height ) ( HSPRITE hPic, int frame ); + int ( *pfnSPR_Width ) ( HSPRITE hPic, int frame ); + void ( *pfnSPR_Set ) ( HSPRITE hPic, int r, int g, int b ); + void ( *pfnSPR_Draw ) ( int frame, int x, int y, const wrect_t *prc ); + void ( *pfnSPR_DrawHoles ) ( int frame, int x, int y, const wrect_t *prc ); + void ( *pfnSPR_DrawAdditive ) ( int frame, int x, int y, const wrect_t *prc ); + void ( *pfnSPR_EnableScissor ) ( int x, int y, int width, int height ); + void ( *pfnSPR_DisableScissor ) ( void ); + client_sprite_t *( *pfnSPR_GetList ) ( char *psz, int *piCount ); + + // screen handlers + void ( *pfnFillRGBA ) ( int x, int y, int width, int height, int r, int g, int b, int a ); + int ( *pfnGetScreenInfo ) ( SCREENINFO *pscrinfo ); + void ( *pfnSetCrosshair ) ( HSPRITE hspr, wrect_t rc, int r, int g, int b ); + + // cvar handlers + struct cvar_s *( *pfnRegisterVariable ) ( char *szName, char *szValue, int flags ); + float ( *pfnGetCvarFloat ) ( char *szName ); + char* ( *pfnGetCvarString ) ( char *szName ); + + // command handlers + int ( *pfnAddCommand ) ( char *cmd_name, void (*function)(void) ); + int ( *pfnHookUserMsg ) ( char *szMsgName, pfnUserMsgHook pfn ); + int ( *pfnServerCmd ) ( char *szCmdString ); + int ( *pfnClientCmd ) ( char *szCmdString ); + + void ( *pfnGetPlayerInfo ) ( int ent_num, hud_player_info_t *pinfo ); + + // sound handlers + void ( *pfnPlaySoundByName ) ( char *szSound, float volume ); + void ( *pfnPlaySoundByIndex ) ( int iSound, float volume ); + + // vector helpers + void ( *pfnAngleVectors ) ( const float * vecAngles, float * forward, float * right, float * up ); + + // text message system + client_textmessage_t *( *pfnTextMessageGet ) ( const char *pName ); + int ( *pfnDrawCharacter ) ( int x, int y, int number, int r, int g, int b ); + int ( *pfnDrawConsoleString ) ( int x, int y, char *string ); + void ( *pfnDrawSetTextColor ) ( float r, float g, float b ); + void ( *pfnDrawConsoleStringLen )( const char *string, int *length, int *height ); + + void ( *pfnConsolePrint ) ( const char *string ); + void ( *pfnCenterPrint ) ( const char *string ); + + +// Added for user input processing + int ( *GetWindowCenterX ) ( void ); + int ( *GetWindowCenterY ) ( void ); + void ( *GetViewAngles ) ( float * ); + void ( *SetViewAngles ) ( float * ); + int ( *GetMaxClients ) ( void ); + void ( *Cvar_SetValue ) ( char *cvar, float value ); + + int (*Cmd_Argc) (void); + char *( *Cmd_Argv ) ( int arg ); + void ( *Con_Printf ) ( char *fmt, ... ); + void ( *Con_DPrintf ) ( char *fmt, ... ); + void ( *Con_NPrintf ) ( int pos, char *fmt, ... ); + void ( *Con_NXPrintf ) ( struct con_nprint_s *info, char *fmt, ... ); + + const char *( *PhysInfo_ValueForKey ) ( const char *key ); + const char *( *ServerInfo_ValueForKey )( const char *key ); + float ( *GetClientMaxspeed ) ( void ); + int ( *CheckParm ) ( char *parm, char **ppnext ); + void ( *Key_Event ) ( int key, int down ); + void ( *GetMousePosition ) ( int *mx, int *my ); + int ( *IsNoClipping ) ( void ); + + struct cl_entity_s *( *GetLocalPlayer ) ( void ); + struct cl_entity_s *( *GetViewModel ) ( void ); + struct cl_entity_s *( *GetEntityByIndex ) ( int idx ); + + float ( *GetClientTime ) ( void ); + void ( *V_CalcShake ) ( void ); + void ( *V_ApplyShake ) ( float *origin, float *angles, float factor ); + + int ( *PM_PointContents ) ( float *point, int *truecontents ); + int ( *PM_WaterEntity ) ( float *p ); + struct pmtrace_s *( *PM_TraceLine ) ( float *start, float *end, int flags, int usehull, int ignore_pe ); + + struct model_s *( *CL_LoadModel ) ( const char *modelname, int *index ); + int ( *CL_CreateVisibleEntity ) ( int type, struct cl_entity_s *ent ); + + const struct model_s * ( *GetSpritePointer ) ( HSPRITE hSprite ); + void ( *pfnPlaySoundByNameAtLocation ) ( char *szSound, float volume, float *origin ); + + unsigned short ( *pfnPrecacheEvent ) ( int type, const char* psz ); + void ( *pfnPlaybackEvent ) ( int flags, const struct edict_s *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); + void ( *pfnWeaponAnim ) ( int iAnim, int body ); + float ( *pfnRandomFloat ) ( float flLow, float flHigh ); + int32 ( *pfnRandomLong ) ( int32 lLow, int32 lHigh ); + void ( *pfnHookEvent ) ( char *name, void ( *pfnEvent )( struct event_args_s *args ) ); + int (*Con_IsVisible) (); + const char *( *pfnGetGameDirectory ) ( void ); + struct cvar_s *( *pfnGetCvarPointer ) ( const char *szName ); + const char *( *Key_LookupBinding ) ( const char *pBinding ); + const char *( *pfnGetLevelName ) ( void ); + void ( *pfnGetScreenFade ) ( struct screenfade_s *fade ); + void ( *pfnSetScreenFade ) ( struct screenfade_s *fade ); + void *( *VGui_GetPanel ) ( ); + void ( *VGui_ViewportPaintBackground ) (int extents[4]); + + byte* (*COM_LoadFile) ( char *path, int usehunk, int *pLength ); + char* (*COM_ParseFile) ( char *data, char *token ); + void (*COM_FreeFile) ( void *buffer ); + + struct triangleapi_s *pTriAPI; + struct efx_api_s *pEfxAPI; + struct event_api_s *pEventAPI; + struct demo_api_s *pDemoAPI; + struct net_api_s *pNetAPI; + struct IVoiceTweak_s *pVoiceTweak; + + // returns 1 if the client is a spectator only (connected to a proxy), 0 otherwise or 2 if in dev_overview mode + int ( *IsSpectateOnly ) ( void ); + struct model_s *( *LoadMapSprite ) ( const char *filename ); + + // file search functions + void ( *COM_AddAppDirectoryToSearchPath ) ( const char *pszBaseDir, const char *appName ); + int ( *COM_ExpandFilename) ( const char *fileName, char *nameOutBuffer, int nameOutBufferSize ); + + // User info + // playerNum is in the range (1, MaxClients) + // returns NULL if player doesn't exit + // returns "" if no value is set + const char *( *PlayerInfo_ValueForKey )( int playerNum, const char *key ); + void ( *PlayerInfo_SetValueForKey )( const char *key, const char *value ); + + // Gets a unique ID for the specified player. This is the same even if you see the player on a different server. + // iPlayer is an entity index, so client 0 would use iPlayer=1. + // Returns false if there is no player on the server in the specified slot. + qboolean (*GetPlayerUniqueID)(int iPlayer, char playerID[16]); + + // TrackerID access + int (*GetTrackerIDForPlayer)(int playerSlot); + int (*GetPlayerForTrackerID)(int trackerID); + + // Same as pfnServerCmd, but the message goes in the unreliable stream so it can't clog the net stream + // (but it might not get there). + int ( *pfnServerCmdUnreliable )( char *szCmdString ); + + void ( *pfnGetMousePos )( struct tagPOINT *ppt ); + void ( *pfnSetMousePos )( int x, int y ); + void ( *pfnSetMouseEnable )( qboolean fEnable ); +} cl_enginefunc_t; + +#ifndef IN_BUTTONS_H +#include "in_buttons.h" +#endif + +#define CLDLL_INTERFACE_VERSION 7 + +extern void ClientDLL_Init( void ); // from cdll_int.c +extern void ClientDLL_Shutdown( void ); +extern void ClientDLL_HudInit( void ); +extern void ClientDLL_HudVidInit( void ); +extern void ClientDLL_UpdateClientData( void ); +extern void ClientDLL_Frame( double time ); +extern void ClientDLL_HudRedraw( int intermission ); +extern void ClientDLL_MoveClient( struct playermove_s *ppmove ); +extern void ClientDLL_ClientMoveInit( struct playermove_s *ppmove ); +extern char ClientDLL_ClientTextureType( char *name ); + +extern void ClientDLL_CreateMove( float frametime, struct usercmd_s *cmd, int active ); +extern void ClientDLL_ActivateMouse( void ); +extern void ClientDLL_DeactivateMouse( void ); +extern void ClientDLL_MouseEvent( int mstate ); +extern void ClientDLL_ClearStates( void ); +extern int ClientDLL_IsThirdPerson( void ); +extern void ClientDLL_GetCameraOffsets( float *ofs ); +extern int ClientDLL_GraphKeyDown( void ); +extern struct kbutton_s *ClientDLL_FindKey( const char *name ); +extern void ClientDLL_CAM_Think( void ); +extern void ClientDLL_IN_Accumulate( void ); +extern void ClientDLL_CalcRefdef( struct ref_params_s *pparams ); +extern int ClientDLL_AddEntity( int type, struct cl_entity_s *ent ); +extern void ClientDLL_CreateEntities( void ); + +extern void ClientDLL_DrawNormalTriangles( void ); +extern void ClientDLL_DrawTransparentTriangles( void ); +extern void ClientDLL_StudioEvent( const struct mstudioevent_s *event, const struct cl_entity_s *entity ); +extern void ClientDLL_PostRunCmd( struct local_state_s *from, struct local_state_s *to, struct usercmd_s *cmd, int runfuncs, double time, unsigned int random_seed ); +extern void ClientDLL_TxferLocalOverrides( struct entity_state_s *state, const struct clientdata_s *client ); +extern void ClientDLL_ProcessPlayerState( struct entity_state_s *dst, const struct entity_state_s *src ); +extern void ClientDLL_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 ); +extern void ClientDLL_ReadDemoBuffer( int size, unsigned char *buffer ); +extern int ClientDLL_ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); +extern int ClientDLL_GetHullBounds( int hullnumber, float *mins, float *maxs ); + +extern void ClientDLL_VGui_ConsolePrint(const char* text); + +extern int ClientDLL_Key_Event( int down, int keynum, const char *pszCurrentBinding ); +extern void ClientDLL_TempEntUpdate( double ft, double ct, double grav, struct tempent_s **ppFreeTE, struct tempent_s **ppActiveTE, int ( *addTEntity )( struct cl_entity_s *pEntity ), void ( *playTESound )( struct tempent_s *pTemp, float damp ) ); +extern struct cl_entity_s *ClientDLL_GetUserEntity( int index ); +extern void ClientDLL_VoiceStatus(int entindex, qboolean bTalking); +extern void ClientDLL_DirectorMessage( int iSize, void *pbuf ); + + +#ifdef __cplusplus +} +#endif + +#endif // CDLL_INT_H diff --git a/hlsdk/engine/custom.h b/hlsdk/engine/custom.h new file mode 100644 index 00000000..077832ac --- /dev/null +++ b/hlsdk/engine/custom.h @@ -0,0 +1,103 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Customization.h + +#ifndef CUSTOM_H +#define CUSTOM_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +#include "const.h" + +#define MAX_QPATH 64 // Must match value in quakedefs.h + +///////////////// +// Customization +// passed to pfnPlayerCustomization +// For automatic downloading. +typedef enum +{ + t_sound = 0, + t_skin, + t_model, + t_decal, + t_generic, + t_eventscript, + t_world, // Fake type for world, is really t_model +} resourcetype_t; + + +typedef struct +{ + int size; +} _resourceinfo_t; + +typedef struct resourceinfo_s +{ + _resourceinfo_t info[ 8 ]; +} resourceinfo_t; + +#define RES_FATALIFMISSING (1<<0) // Disconnect if we can't get this file. +#define RES_WASMISSING (1<<1) // Do we have the file locally, did we get it ok? +#define RES_CUSTOM (1<<2) // Is this resource one that corresponds to another player's customization + // or is it a server startup resource. +#define RES_REQUESTED (1<<3) // Already requested a download of this one +#define RES_PRECACHED (1<<4) // Already precached + +#include "crc.h" + +typedef struct resource_s +{ + char szFileName[MAX_QPATH]; // File name to download/precache. + resourcetype_t type; // t_sound, t_skin, t_model, t_decal. + int nIndex; // For t_decals + int nDownloadSize; // Size in Bytes if this must be downloaded. + unsigned char ucFlags; + +// For handling client to client resource propagation + unsigned char rgucMD5_hash[16]; // To determine if we already have it. + unsigned char playernum; // Which player index this resource is associated with, if it's a custom resource. + + unsigned char rguc_reserved[ 32 ]; // For future expansion + struct resource_s *pNext; // Next in chain. + struct resource_s *pPrev; +} resource_t; + +typedef struct customization_s +{ + qboolean bInUse; // Is this customization in use; + resource_t resource; // The resource_t for this customization + qboolean bTranslated; // Has the raw data been translated into a useable format? + // (e.g., raw decal .wad make into texture_t *) + int nUserData1; // Customization specific data + int nUserData2; // Customization specific data + void *pInfo; // Buffer that holds the data structure that references the data (e.g., the cachewad_t) + void *pBuffer; // Buffer that holds the data for the customization (the raw .wad data) + struct customization_s *pNext; // Next in chain +} customization_t; + +#define FCUST_FROMHPAK ( 1<<0 ) +#define FCUST_WIPEDATA ( 1<<1 ) +#define FCUST_IGNOREINIT ( 1<<2 ) + +void COM_ClearCustomizationList( struct customization_s *pHead, qboolean bCleanDecals); +qboolean COM_CreateCustomization( struct customization_s *pListHead, struct resource_s *pResource, int playernumber, int flags, + struct customization_s **pCustomization, int *nLumps ); +int COM_SizeofResourceList ( struct resource_s *pList, struct resourceinfo_s *ri ); + +#endif // CUSTOM_H diff --git a/hlsdk/engine/customentity.h b/hlsdk/engine/customentity.h new file mode 100644 index 00000000..2c4eb5eb --- /dev/null +++ b/hlsdk/engine/customentity.h @@ -0,0 +1,38 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef CUSTOMENTITY_H +#define CUSTOMENTITY_H + +// Custom Entities + +// Start/End Entity is encoded as 12 bits of entity index, and 4 bits of attachment (4:12) +#define BEAMENT_ENTITY(x) ((x)&0xFFF) +#define BEAMENT_ATTACHMENT(x) (((x)>>12)&0xF) + +// Beam types, encoded as a byte +enum +{ + BEAM_POINTS = 0, + BEAM_ENTPOINT, + BEAM_ENTS, + BEAM_HOSE, +}; + +#define BEAM_FSINE 0x10 +#define BEAM_FSOLID 0x20 +#define BEAM_FSHADEIN 0x40 +#define BEAM_FSHADEOUT 0x80 + +#endif //CUSTOMENTITY_H diff --git a/hlsdk/engine/edict.h b/hlsdk/engine/edict.h new file mode 100644 index 00000000..ba11fb94 --- /dev/null +++ b/hlsdk/engine/edict.h @@ -0,0 +1,38 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined EDICT_H +#define EDICT_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif +#define MAX_ENT_LEAFS 48 + +#include "progdefs.h" + +struct edict_s +{ + qboolean free; + int serialnumber; + link_t area; // linked to a division node or leaf + + int headnode; // -1 to use normal leaf check + int num_leafs; + short leafnums[MAX_ENT_LEAFS]; + + float freetime; // sv.time when the object was freed + + void* pvPrivateData; // Alloced and freed by engine, used by DLLs + + entvars_t v; // C exported fields from progs + + // other fields from progs come immediately after +}; + +#endif diff --git a/hlsdk/engine/eiface.h b/hlsdk/engine/eiface.h new file mode 100644 index 00000000..7297e032 --- /dev/null +++ b/hlsdk/engine/eiface.h @@ -0,0 +1,530 @@ +/*** +* +* 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. +* 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. +* +****/ +#ifndef EIFACE_H +#define EIFACE_H + +#include "archtypes.h" // DAL + +#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" +#include "cvardef.h" +#include "Sequence.h" +// +// Defines entity interface between engine and DLLs. +// This header file included by engine files and DLL files. +// +// Before including this header, DLLs must: +// include progdefs.h +// This is conveniently done for them in extdll.h +// + +#ifdef _WIN32 +#define DLLEXPORT __stdcall +#else +#define DLLEXPORT /* */ +#endif + +typedef enum + { + at_notice, + at_console, // same as at_notice, but forces a ConPrintf, not a message box + at_aiconsole, // same as at_console, but only shown if developer level is 2! + at_warning, + at_error, + at_logged // Server print to console ( only in multiplayer games ). + } ALERT_TYPE; + +// 4-22-98 JOHN: added for use in pfnClientPrintf +typedef enum + { + print_console, + print_center, + print_chat, + } PRINT_TYPE; + +// For integrity checking of content on clients +typedef enum +{ + force_exactfile, // File on client must exactly match server's file + force_model_samebounds, // For model files only, the geometry must fit in the same bbox + force_model_specifybounds, // For model files only, the geometry must fit in the specified bbox + force_model_specifybounds_if_avail, // For Steam model files only, the geometry must fit in the specified bbox (if the file is available) +} FORCE_TYPE; + +// Returned by TraceLine +typedef struct + { + int fAllSolid; // if true, plane is not valid + int fStartSolid; // if true, the initial point was in a solid area + int fInOpen; + int fInWater; + float flFraction; // time completed, 1.0 = didn't hit anything + vec3_t vecEndPos; // final position + float flPlaneDist; + vec3_t vecPlaneNormal; // surface normal at impact + edict_t *pHit; // entity the surface is on + int iHitgroup; // 0 == generic, non zero is specific body part + } TraceResult; + +// CD audio status +typedef struct +{ + int fPlaying;// is sound playing right now? + int fWasPlaying;// if not, CD is paused if WasPlaying is true. + int fInitialized; + int fEnabled; + int fPlayLooping; + float cdvolume; + //BYTE remap[100]; + int fCDRom; + int fPlayTrack; +} CDStatus; + +#include "../common/crc.h" + + +// Engine hands this to DLLs for functionality callbacks +typedef struct enginefuncs_s +{ + int (*pfnPrecacheModel) (char* s); + int (*pfnPrecacheSound) (char* s); + void (*pfnSetModel) (edict_t *e, const char *m); + int (*pfnModelIndex) (const char *m); + int (*pfnModelFrames) (int modelIndex); + void (*pfnSetSize) (edict_t *e, const float *rgflMin, const float *rgflMax); + void (*pfnChangeLevel) (char* s1, char* s2); + void (*pfnGetSpawnParms) (edict_t *ent); + void (*pfnSaveSpawnParms) (edict_t *ent); + float (*pfnVecToYaw) (const float *rgflVector); + void (*pfnVecToAngles) (const float *rgflVectorIn, float *rgflVectorOut); + void (*pfnMoveToOrigin) (edict_t *ent, const float *pflGoal, float dist, int iMoveType); + void (*pfnChangeYaw) (edict_t* ent); + void (*pfnChangePitch) (edict_t* ent); + edict_t* (*pfnFindEntityByString) (edict_t *pEdictStartSearchAfter, const char *pszField, const char *pszValue); + int (*pfnGetEntityIllum) (edict_t* pEnt); + edict_t* (*pfnFindEntityInSphere) (edict_t *pEdictStartSearchAfter, const float *org, float rad); + edict_t* (*pfnFindClientInPVS) (edict_t *pEdict); + edict_t* (*pfnEntitiesInPVS) (edict_t *pplayer); + void (*pfnMakeVectors) (const float *rgflVector); + void (*pfnAngleVectors) (const float *rgflVector, float *forward, float *right, float *up); + edict_t* (*pfnCreateEntity) (void); + void (*pfnRemoveEntity) (edict_t* e); + edict_t* (*pfnCreateNamedEntity) (int className); + void (*pfnMakeStatic) (edict_t *ent); + int (*pfnEntIsOnFloor) (edict_t *e); + int (*pfnDropToFloor) (edict_t* e); + int (*pfnWalkMove) (edict_t *ent, float yaw, float dist, int iMode); + void (*pfnSetOrigin) (edict_t *e, const float *rgflOrigin); + void (*pfnEmitSound) (edict_t *entity, int channel, const char *sample, /*int*/float volume, float attenuation, int fFlags, int pitch); + void (*pfnEmitAmbientSound) (edict_t *entity, float *pos, const char *samp, float vol, float attenuation, int fFlags, int pitch); + void (*pfnTraceLine) (const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr); + void (*pfnTraceToss) (edict_t* pent, edict_t* pentToIgnore, TraceResult *ptr); + int (*pfnTraceMonsterHull) (edict_t *pEdict, const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr); + void (*pfnTraceHull) (const float *v1, const float *v2, int fNoMonsters, int hullNumber, edict_t *pentToSkip, TraceResult *ptr); + void (*pfnTraceModel) (const float *v1, const float *v2, int hullNumber, edict_t *pent, TraceResult *ptr); + const char *(*pfnTraceTexture) (edict_t *pTextureEntity, const float *v1, const float *v2 ); + void (*pfnTraceSphere) (const float *v1, const float *v2, int fNoMonsters, float radius, edict_t *pentToSkip, TraceResult *ptr); + void (*pfnGetAimVector) (edict_t* ent, float speed, float *rgflReturn); + void (*pfnServerCommand) (char* str); + void (*pfnServerExecute) (void); + void (*pfnClientCommand) (edict_t* pEdict, char* szFmt, ...); + void (*pfnParticleEffect) (const float *org, const float *dir, float color, float count); + void (*pfnLightStyle) (int style, char* val); + int (*pfnDecalIndex) (const char *name); + int (*pfnPointContents) (const float *rgflVector); + void (*pfnMessageBegin) (int msg_dest, int msg_type, const float *pOrigin, edict_t *ed); + void (*pfnMessageEnd) (void); + void (*pfnWriteByte) (int iValue); + void (*pfnWriteChar) (int iValue); + void (*pfnWriteShort) (int iValue); + void (*pfnWriteLong) (int iValue); + void (*pfnWriteAngle) (float flValue); + void (*pfnWriteCoord) (float flValue); + void (*pfnWriteString) (const char *sz); + void (*pfnWriteEntity) (int iValue); + void (*pfnCVarRegister) (cvar_t *pCvar); + float (*pfnCVarGetFloat) (const char *szVarName); + const char* (*pfnCVarGetString) (const char *szVarName); + void (*pfnCVarSetFloat) (const char *szVarName, float flValue); + void (*pfnCVarSetString) (const char *szVarName, const char *szValue); + void (*pfnAlertMessage) (ALERT_TYPE atype, char *szFmt, ...); + void (*pfnEngineFprintf) (void *pfile, char *szFmt, ...); + void* (*pfnPvAllocEntPrivateData) (edict_t *pEdict, int32 cb); + void* (*pfnPvEntPrivateData) (edict_t *pEdict); + void (*pfnFreeEntPrivateData) (edict_t *pEdict); + const char* (*pfnSzFromIndex) (int iString); + int (*pfnAllocString) (const char *szValue); + struct entvars_s* (*pfnGetVarsOfEnt) (edict_t *pEdict); + edict_t* (*pfnPEntityOfEntOffset) (int iEntOffset); + int (*pfnEntOffsetOfPEntity) (const edict_t *pEdict); + int (*pfnIndexOfEdict) (const edict_t *pEdict); + edict_t* (*pfnPEntityOfEntIndex) (int iEntIndex); + edict_t* (*pfnFindEntityByVars) (struct entvars_s* pvars); + void* (*pfnGetModelPtr) (edict_t* pEdict); + int (*pfnRegUserMsg) (const char *pszName, int iSize); + void (*pfnAnimationAutomove) (const edict_t* pEdict, float flTime); + void (*pfnGetBonePosition) (const edict_t* pEdict, int iBone, float *rgflOrigin, float *rgflAngles ); + uint32 (*pfnFunctionFromName) ( const char *pName ); + const char *(*pfnNameForFunction) ( uint32 function ); + void (*pfnClientPrintf) ( edict_t* pEdict, PRINT_TYPE ptype, const char *szMsg ); // JOHN: engine callbacks so game DLL can print messages to individual clients + void (*pfnServerPrint) ( const char *szMsg ); + const char *(*pfnCmd_Args) ( void ); // these 3 added + const char *(*pfnCmd_Argv) ( int argc ); // so game DLL can easily + int (*pfnCmd_Argc) ( void ); // access client 'cmd' strings + void (*pfnGetAttachment) (const edict_t *pEdict, int iAttachment, float *rgflOrigin, float *rgflAngles ); + void (*pfnCRC32_Init) (CRC32_t *pulCRC); + void (*pfnCRC32_ProcessBuffer) (CRC32_t *pulCRC, void *p, int len); + void (*pfnCRC32_ProcessByte) (CRC32_t *pulCRC, unsigned char ch); + CRC32_t (*pfnCRC32_Final) (CRC32_t pulCRC); + int32 (*pfnRandomLong) (int32 lLow, int32 lHigh); + float (*pfnRandomFloat) (float flLow, float flHigh); + void (*pfnSetView) (const edict_t *pClient, const edict_t *pViewent ); + float (*pfnTime) ( void ); + void (*pfnCrosshairAngle) (const edict_t *pClient, float pitch, float yaw); + byte * (*pfnLoadFileForMe) (char *filename, int *pLength); + void (*pfnFreeFile) (void *buffer); + void (*pfnEndSection) (const char *pszSectionName); // trigger_endsection + int (*pfnCompareFileTime) (char *filename1, char *filename2, int *iCompare); + void (*pfnGetGameDir) (char *szGetGameDir); + void (*pfnCvar_RegisterVariable) (cvar_t *variable); + void (*pfnFadeClientVolume) (const edict_t *pEdict, int fadePercent, int fadeOutSeconds, int holdTime, int fadeInSeconds); + void (*pfnSetClientMaxspeed) (const edict_t *pEdict, float fNewMaxspeed); + edict_t * (*pfnCreateFakeClient) (const char *netname); // returns NULL if fake client can't be created + void (*pfnRunPlayerMove) (edict_t *fakeclient, const float *viewangles, float forwardmove, float sidemove, float upmove, unsigned short buttons, byte impulse, byte msec ); + int (*pfnNumberOfEntities) (void); + char* (*pfnGetInfoKeyBuffer) (edict_t *e); // passing in NULL gets the serverinfo + char* (*pfnInfoKeyValue) (char *infobuffer, char *key); + void (*pfnSetKeyValue) (char *infobuffer, char *key, char *value); + void (*pfnSetClientKeyValue) (int clientIndex, char *infobuffer, char *key, char *value); + int (*pfnIsMapValid) (char *filename); + void (*pfnStaticDecal) ( const float *origin, int decalIndex, int entityIndex, int modelIndex ); + int (*pfnPrecacheGeneric) (char* s); + int (*pfnGetPlayerUserId) (edict_t *e ); // returns the server assigned userid for this player. useful for logging frags, etc. returns -1 if the edict couldn't be found in the list of clients + void (*pfnBuildSoundMsg) (edict_t *entity, int channel, const char *sample, /*int*/float volume, float attenuation, int fFlags, int pitch, int msg_dest, int msg_type, const float *pOrigin, edict_t *ed); + int (*pfnIsDedicatedServer) (void);// is this a dedicated server? + cvar_t *(*pfnCVarGetPointer) (const char *szVarName); + unsigned int (*pfnGetPlayerWONId) (edict_t *e); // returns the server assigned WONid for this player. useful for logging frags, etc. returns -1 if the edict couldn't be found in the list of clients + + // YWB 8/1/99 TFF Physics additions + void (*pfnInfo_RemoveKey) ( char *s, const char *key ); + const char *(*pfnGetPhysicsKeyValue) ( const edict_t *pClient, const char *key ); + void (*pfnSetPhysicsKeyValue) ( const edict_t *pClient, const char *key, const char *value ); + const char *(*pfnGetPhysicsInfoString) ( const edict_t *pClient ); + unsigned short (*pfnPrecacheEvent) ( int type, const char*psz ); + void (*pfnPlaybackEvent) ( int flags, const edict_t *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); + + unsigned char *(*pfnSetFatPVS) ( float *org ); + unsigned char *(*pfnSetFatPAS) ( float *org ); + + int (*pfnCheckVisibility ) ( const edict_t *entity, unsigned char *pset ); + + void (*pfnDeltaSetField) ( struct delta_s *pFields, const char *fieldname ); + void (*pfnDeltaUnsetField) ( struct delta_s *pFields, const char *fieldname ); + void (*pfnDeltaAddEncoder) ( char *name, void (*conditionalencode)( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) ); + int (*pfnGetCurrentPlayer) ( void ); + int (*pfnCanSkipPlayer) ( const edict_t *player ); + int (*pfnDeltaFindField) ( struct delta_s *pFields, const char *fieldname ); + void (*pfnDeltaSetFieldByIndex) ( struct delta_s *pFields, int fieldNumber ); + void (*pfnDeltaUnsetFieldByIndex)( struct delta_s *pFields, int fieldNumber ); + + void (*pfnSetGroupMask) ( int mask, int op ); + + int (*pfnCreateInstancedBaseline) ( int classname, struct entity_state_s *baseline ); + void (*pfnCvar_DirectSet) ( struct cvar_s *var, char *value ); + + // Forces the client and server to be running with the same version of the specified file + // ( e.g., a player model ). + // Calling this has no effect in single player + void (*pfnForceUnmodified) ( FORCE_TYPE type, float *mins, float *maxs, const char *filename ); + + void (*pfnGetPlayerStats) ( const edict_t *pClient, int *ping, int *packet_loss ); + + void (*pfnAddServerCommand) ( char *cmd_name, void (*function) (void) ); + + // For voice communications, set which clients hear eachother. + // 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 ); + + // PSV: Added for CZ training map +// const char *(*pfnKeyNameForBinding) ( const char* pBinding ); + + sequenceEntry_s* (*pfnSequenceGet) ( const char* fileName, const char* entryName ); + sentenceEntry_s* (*pfnSequencePickSentence) ( const char* groupName, int pickMethod, int *picked ); + + // LH: Give access to filesize via filesystem + int (*pfnGetFileSize) ( char *filename ); + + unsigned int (*pfnGetApproxWavePlayLen) (const char *filepath); + // MDC: Added for CZ career-mode + int (*pfnIsCareerMatch) ( void ); + + // BGC: return the number of characters of the localized string referenced by using "label" + int (*pfnGetLocalizedStringLength) (const char *label); + + // BGC: added to facilitate persistent storage of tutor message decay values for + // different career game profiles. Also needs to persist regardless of mp.dll being + // destroyed and recreated. + void (*pfnRegisterTutorMessageShown) (int mid); + int (*pfnGetTimesTutorMessageShown) (int mid); + void (*pfnProcessTutorMessageDecayBuffer) (int *buffer, int bufferLength); + void (*pfnConstructTutorMessageDecayBuffer) (int *buffer, int bufferLength); + void (*pfnResetTutorMessageDecayData) ( void ); + void (*pfnQueryClientCvarValue) ( const edict_t *player, const char *cvarName ); + void (*pfnQueryClientCvarValue2) ( const edict_t *player, const char *cvarName, int requestID ); +} enginefuncs_t; + + +// ONLY ADD NEW FUNCTIONS TO THE END OF THIS STRUCT. INTERFACE VERSION IS FROZEN AT 138 + +// Passed to pfnKeyValue +typedef struct KeyValueData_s +{ + char *szClassName; // in: entity classname + char *szKeyName; // in: name of key + char *szValue; // in: value of key + int32 fHandled; // out: DLL sets to true if key-value pair was understood +} KeyValueData; + + +typedef struct +{ + char mapName[ 32 ]; + char landmarkName[ 32 ]; + edict_t *pentLandmark; + vec3_t vecLandmarkOrigin; +} LEVELLIST; +#define MAX_LEVEL_CONNECTIONS 16 // These are encoded in the lower 16bits of ENTITYTABLE->flags + +typedef struct +{ + int id; // Ordinal ID of this entity (used for entity <--> pointer conversions) + edict_t *pent; // Pointer to the in-game entity + + int location; // Offset from the base data of this entity + int size; // Byte size of this entity's data + int flags; // This could be a short -- bit mask of transitions that this entity is in the PVS of + string_t classname; // entity class name + +} ENTITYTABLE; + +#define FENTTABLE_PLAYER 0x80000000 +#define FENTTABLE_REMOVED 0x40000000 +#define FENTTABLE_MOVEABLE 0x20000000 +#define FENTTABLE_GLOBAL 0x10000000 + +typedef struct saverestore_s SAVERESTOREDATA; + +#ifdef _WIN32 +typedef +#endif +struct saverestore_s +{ + char *pBaseData; // Start of all entity save data + char *pCurrentData; // Current buffer pointer for sequential access + int size; // Current data size + int bufferSize; // Total space for data + int tokenSize; // Size of the linear list of tokens + int tokenCount; // Number of elements in the pTokens table + char **pTokens; // Hash table of entity strings (sparse) + int currentIndex; // Holds a global entity table ID + int tableCount; // Number of elements in the entity table + int connectionCount;// Number of elements in the levelList[] + ENTITYTABLE *pTable; // Array of ENTITYTABLE elements (1 for each entity) + LEVELLIST levelList[ MAX_LEVEL_CONNECTIONS ]; // List of connections from this level + + // smooth transition + int fUseLandmark; + char szLandmarkName[20];// landmark we'll spawn near in next level + vec3_t vecLandmarkOffset;// for landmark transitions + float time; + char szCurrentMapName[32]; // To check global entities + +} +#ifdef _WIN32 +SAVERESTOREDATA +#endif +; + +typedef enum _fieldtypes +{ + FIELD_FLOAT = 0, // Any floating point value + FIELD_STRING, // A string ID (return from ALLOC_STRING) + FIELD_ENTITY, // An entity offset (EOFFSET) + FIELD_CLASSPTR, // CBaseEntity * + FIELD_EHANDLE, // Entity handle + FIELD_EVARS, // EVARS * + FIELD_EDICT, // edict_t *, or edict_t * (same thing) + FIELD_VECTOR, // Any vector + FIELD_POSITION_VECTOR, // A world coordinate (these are fixed up across level transitions automagically) + FIELD_POINTER, // Arbitrary data pointer... to be removed, use an array of FIELD_CHARACTER + FIELD_INTEGER, // Any integer or enum + FIELD_FUNCTION, // A class function pointer (Think, Use, etc) + FIELD_BOOLEAN, // boolean, implemented as an int, I may use this as a hint for compression + FIELD_SHORT, // 2 byte integer + FIELD_CHARACTER, // a byte + FIELD_TIME, // a floating point time (these are fixed up automatically too!) + FIELD_MODELNAME, // Engine string that is a model name (needs precache) + FIELD_SOUNDNAME, // Engine string that is a sound name (needs precache) + + FIELD_TYPECOUNT, // MUST BE LAST +} FIELDTYPE; + +#ifndef offsetof +#define offsetof(s,m) (size_t)&(((s *)0)->m) +#endif + +#define _FIELD(type,name,fieldtype,count,flags) { fieldtype, #name, offsetof(type, name), count, flags } +#define DEFINE_FIELD(type,name,fieldtype) _FIELD(type, name, fieldtype, 1, 0) +#define DEFINE_ARRAY(type,name,fieldtype,count) _FIELD(type, name, fieldtype, count, 0) +#define DEFINE_ENTITY_FIELD(name,fieldtype) _FIELD(entvars_t, name, fieldtype, 1, 0 ) +#define DEFINE_ENTITY_GLOBAL_FIELD(name,fieldtype) _FIELD(entvars_t, name, fieldtype, 1, FTYPEDESC_GLOBAL ) +#define DEFINE_GLOBAL_FIELD(type,name,fieldtype) _FIELD(type, name, fieldtype, 1, FTYPEDESC_GLOBAL ) + + +#define FTYPEDESC_GLOBAL 0x0001 // This field is masked for global entity save/restore + +typedef struct +{ + FIELDTYPE fieldType; + char *fieldName; + int fieldOffset; + short fieldSize; + short flags; +} TYPEDESCRIPTION; + +// Fix warning in MSVC8 +#undef ARRAYSIZE +#define ARRAYSIZE(p) (sizeof(p)/sizeof(p[0])) + +typedef struct +{ + // Initialize/shutdown the game (one-time call after loading of game .dll ) + void (*pfnGameInit) ( void ); + int (*pfnSpawn) ( edict_t *pent ); + void (*pfnThink) ( edict_t *pent ); + void (*pfnUse) ( edict_t *pentUsed, edict_t *pentOther ); + void (*pfnTouch) ( edict_t *pentTouched, edict_t *pentOther ); + void (*pfnBlocked) ( edict_t *pentBlocked, edict_t *pentOther ); + void (*pfnKeyValue) ( edict_t *pentKeyvalue, KeyValueData *pkvd ); + void (*pfnSave) ( edict_t *pent, SAVERESTOREDATA *pSaveData ); + int (*pfnRestore) ( edict_t *pent, SAVERESTOREDATA *pSaveData, int globalEntity ); + void (*pfnSetAbsBox) ( edict_t *pent ); + + void (*pfnSaveWriteFields) ( SAVERESTOREDATA *, const char *, void *, TYPEDESCRIPTION *, int ); + void (*pfnSaveReadFields) ( SAVERESTOREDATA *, const char *, void *, TYPEDESCRIPTION *, int ); + + void (*pfnSaveGlobalState) ( SAVERESTOREDATA * ); + void (*pfnRestoreGlobalState) ( SAVERESTOREDATA * ); + void (*pfnResetGlobalState) ( void ); + + qboolean (*pfnClientConnect) ( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); + + void (*pfnClientDisconnect) ( edict_t *pEntity ); + void (*pfnClientKill) ( edict_t *pEntity ); + void (*pfnClientPutInServer) ( edict_t *pEntity ); + void (*pfnClientCommand) ( edict_t *pEntity ); + void (*pfnClientUserInfoChanged)( edict_t *pEntity, char *infobuffer ); + + void (*pfnServerActivate) ( edict_t *pEdictList, int edictCount, int clientMax ); + void (*pfnServerDeactivate) ( void ); + + void (*pfnPlayerPreThink) ( edict_t *pEntity ); + void (*pfnPlayerPostThink) ( edict_t *pEntity ); + + void (*pfnStartFrame) ( void ); + void (*pfnParmsNewLevel) ( void ); + void (*pfnParmsChangeLevel) ( void ); + + // Returns string describing current .dll. E.g., TeamFotrress 2, Half-Life + const char *(*pfnGetGameDescription)( void ); + + // Notify dll about a player customization. + void (*pfnPlayerCustomization) ( edict_t *pEntity, customization_t *pCustom ); + + // Spectator funcs + void (*pfnSpectatorConnect) ( edict_t *pEntity ); + void (*pfnSpectatorDisconnect) ( edict_t *pEntity ); + void (*pfnSpectatorThink) ( edict_t *pEntity ); + + // Notify game .dll that engine is going to shut down. Allows mod authors to set a breakpoint. + void (*pfnSys_Error) ( const char *error_string ); + + void (*pfnPM_Move) ( struct playermove_s *ppmove, qboolean server ); + void (*pfnPM_Init) ( struct playermove_s *ppmove ); + char (*pfnPM_FindTextureType)( char *name ); + void (*pfnSetupVisibility)( struct edict_s *pViewEntity, struct edict_s *pClient, unsigned char **pvs, unsigned char **pas ); + void (*pfnUpdateClientData) ( const struct edict_s *ent, int sendweapons, struct clientdata_s *cd ); + int (*pfnAddToFullPack)( struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet ); + void (*pfnCreateBaseline) ( int player, int eindex, struct entity_state_s *baseline, struct edict_s *entity, int playermodelindex, vec3_t player_mins, vec3_t player_maxs ); + void (*pfnRegisterEncoders) ( void ); + int (*pfnGetWeaponData) ( struct edict_s *player, struct weapon_data_s *info ); + + void (*pfnCmdStart) ( const edict_t *player, const struct usercmd_s *cmd, unsigned int random_seed ); + void (*pfnCmdEnd) ( const edict_t *player ); + + // Return 1 if the packet is valid. Set response_buffer_size if you want to send a response packet. Incoming, it holds the max + // size of the response_buffer, so you must zero it out if you choose not to respond. + int (*pfnConnectionlessPacket ) ( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); + + // Enumerates player hulls. Returns 0 if the hull number doesn't exist, 1 otherwise + int (*pfnGetHullBounds) ( int hullnumber, float *mins, float *maxs ); + + // Create baselines for certain "unplaced" items. + void (*pfnCreateInstancedBaselines) ( void ); + + // One of the pfnForceUnmodified files failed the consistency check for the specified player + // Return 0 to allow the client to continue, 1 to force immediate disconnection ( with an optional disconnect message of up to 256 characters ) + int (*pfnInconsistentFile)( const struct edict_s *player, const char *filename, char *disconnect_message ); + + // The game .dll should return 1 if lag compensation should be allowed ( could also just set + // the sv_unlag cvar. + // Most games right now should return 0, until client-side weapon prediction code is written + // and tested for them. + int (*pfnAllowLagCompensation)( void ); +} DLL_FUNCTIONS; + +extern DLL_FUNCTIONS gEntityInterface; + +// Current version. +#define NEW_DLL_FUNCTIONS_VERSION 1 + +typedef struct +{ + // Called right before the object's memory is freed. + // Calls its destructor. + void (*pfnOnFreeEntPrivateData)(edict_t *pEnt); + void (*pfnGameShutdown)(void); + int (*pfnShouldCollide)( edict_t *pentTouched, edict_t *pentOther ); + void (*pfnCvarValue)( const edict_t *pEnt, const char *value ); + void (*pfnCvarValue2)( const edict_t *pEnt, int requestID, const char *cvarName, const char *value ); +} NEW_DLL_FUNCTIONS; +typedef int (*NEW_DLL_FUNCTIONS_FN)( NEW_DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ); + +// Pointers will be null if the game DLL doesn't support this API. +extern NEW_DLL_FUNCTIONS gNewDLLFunctions; + +typedef int (*APIFUNCTION)( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion ); +typedef int (*APIFUNCTION2)( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ); + +#endif /* EIFACE_H */ diff --git a/hlsdk/engine/keydefs.h b/hlsdk/engine/keydefs.h new file mode 100644 index 00000000..4adeeab6 --- /dev/null +++ b/hlsdk/engine/keydefs.h @@ -0,0 +1,131 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// keydefs.h +#ifndef KEYDEFS_H +#define KEYDEFS_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +// +// these are the key numbers that should be passed to Key_Event +// +#define K_TAB 9 +#define K_ENTER 13 +#define K_ESCAPE 27 +#define K_SPACE 32 + +// normal keys should be passed as lowercased ascii + +#define K_BACKSPACE 127 +#define K_UPARROW 128 +#define K_DOWNARROW 129 +#define K_LEFTARROW 130 +#define K_RIGHTARROW 131 + +#define K_ALT 132 +#define K_CTRL 133 +#define K_SHIFT 134 +#define K_F1 135 +#define K_F2 136 +#define K_F3 137 +#define K_F4 138 +#define K_F5 139 +#define K_F6 140 +#define K_F7 141 +#define K_F8 142 +#define K_F9 143 +#define K_F10 144 +#define K_F11 145 +#define K_F12 146 +#define K_INS 147 +#define K_DEL 148 +#define K_PGDN 149 +#define K_PGUP 150 +#define K_HOME 151 +#define K_END 152 + +#define K_KP_HOME 160 +#define K_KP_UPARROW 161 +#define K_KP_PGUP 162 +#define K_KP_LEFTARROW 163 +#define K_KP_5 164 +#define K_KP_RIGHTARROW 165 +#define K_KP_END 166 +#define K_KP_DOWNARROW 167 +#define K_KP_PGDN 168 +#define K_KP_ENTER 169 +#define K_KP_INS 170 +#define K_KP_DEL 171 +#define K_KP_SLASH 172 +#define K_KP_MINUS 173 +#define K_KP_PLUS 174 +#define K_CAPSLOCK 175 + + +// +// joystick buttons +// +#define K_JOY1 203 +#define K_JOY2 204 +#define K_JOY3 205 +#define K_JOY4 206 + +// +// aux keys are for multi-buttoned joysticks to generate so they can use +// the normal binding process +// +#define K_AUX1 207 +#define K_AUX2 208 +#define K_AUX3 209 +#define K_AUX4 210 +#define K_AUX5 211 +#define K_AUX6 212 +#define K_AUX7 213 +#define K_AUX8 214 +#define K_AUX9 215 +#define K_AUX10 216 +#define K_AUX11 217 +#define K_AUX12 218 +#define K_AUX13 219 +#define K_AUX14 220 +#define K_AUX15 221 +#define K_AUX16 222 +#define K_AUX17 223 +#define K_AUX18 224 +#define K_AUX19 225 +#define K_AUX20 226 +#define K_AUX21 227 +#define K_AUX22 228 +#define K_AUX23 229 +#define K_AUX24 230 +#define K_AUX25 231 +#define K_AUX26 232 +#define K_AUX27 233 +#define K_AUX28 234 +#define K_AUX29 235 +#define K_AUX30 236 +#define K_AUX31 237 +#define K_AUX32 238 +#define K_MWHEELDOWN 239 +#define K_MWHEELUP 240 + +#define K_PAUSE 255 + +// +// mouse buttons generate virtual keys +// +#define K_MOUSE1 241 +#define K_MOUSE2 242 +#define K_MOUSE3 243 +#define K_MOUSE4 244 +#define K_MOUSE5 245 + +#endif // KEYDEFS_H diff --git a/hlsdk/engine/progdefs.h b/hlsdk/engine/progdefs.h new file mode 100644 index 00000000..083b6206 --- /dev/null +++ b/hlsdk/engine/progdefs.h @@ -0,0 +1,226 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PROGDEFS_H +#define PROGDEFS_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +typedef struct +{ + float time; + float frametime; + float force_retouch; + string_t mapname; + string_t startspot; + float deathmatch; + float coop; + float teamplay; + float serverflags; + float found_secrets; + vec3_t v_forward; + vec3_t v_up; + vec3_t v_right; + float trace_allsolid; + float trace_startsolid; + float trace_fraction; + vec3_t trace_endpos; + vec3_t trace_plane_normal; + float trace_plane_dist; + edict_t *trace_ent; + float trace_inopen; + float trace_inwater; + int trace_hitgroup; + int trace_flags; + int msg_entity; + int cdAudioTrack; + int maxClients; + int maxEntities; + const char *pStringBase; + + void *pSaveData; + vec3_t vecLandmarkOffset; +} globalvars_t; + + +typedef struct entvars_s +{ + string_t classname; + string_t globalname; + + vec3_t origin; + vec3_t oldorigin; + vec3_t velocity; + vec3_t basevelocity; + vec3_t clbasevelocity; // Base velocity that was passed in to server physics so + // client can predict conveyors correctly. Server zeroes it, so we need to store here, too. + vec3_t movedir; + + vec3_t angles; // Model angles + vec3_t avelocity; // angle velocity (degrees per second) + vec3_t punchangle; // auto-decaying view angle adjustment + vec3_t v_angle; // Viewing angle (player only) + + // For parametric entities + vec3_t endpos; + vec3_t startpos; + float impacttime; + float starttime; + + int fixangle; // 0:nothing, 1:force view angles, 2:add avelocity + float idealpitch; + float pitch_speed; + float ideal_yaw; + float yaw_speed; + + int modelindex; + string_t model; + + int viewmodel; // player's viewmodel + int weaponmodel; // what other players see + + vec3_t absmin; // BB max translated to world coord + vec3_t absmax; // BB max translated to world coord + vec3_t mins; // local BB min + vec3_t maxs; // local BB max + vec3_t size; // maxs - mins + + float ltime; + float nextthink; + + int movetype; + int solid; + + int skin; + int body; // sub-model selection for studiomodels + int effects; + + float gravity; // % of "normal" gravity + float friction; // inverse elasticity of MOVETYPE_BOUNCE + + int light_level; + + int sequence; // animation sequence + int gaitsequence; // movement animation sequence for player (0 for none) + float frame; // % playback position in animation sequences (0..255) + float animtime; // world time when frame was set + float framerate; // animation playback rate (-8x to 8x) + byte controller[4]; // bone controller setting (0..255) + byte blending[2]; // blending amount between sub-sequences (0..255) + + float scale; // sprite rendering scale (0..255) + + int rendermode; + float renderamt; + vec3_t rendercolor; + int renderfx; + + float health; + float frags; + int weapons; // bit mask for available weapons + float takedamage; + + int deadflag; + vec3_t view_ofs; // eye position + + int button; + int impulse; + + edict_t *chain; // Entity pointer when linked into a linked list + edict_t *dmg_inflictor; + edict_t *enemy; + edict_t *aiment; // entity pointer when MOVETYPE_FOLLOW + edict_t *owner; + edict_t *groundentity; + + int spawnflags; + int flags; + + int colormap; // lowbyte topcolor, highbyte bottomcolor + int team; + + float max_health; + float teleport_time; + float armortype; + float armorvalue; + int waterlevel; + int watertype; + + string_t target; + string_t targetname; + string_t netname; + string_t message; + + float dmg_take; + float dmg_save; + float dmg; + float dmgtime; + + string_t noise; + string_t noise1; + string_t noise2; + string_t noise3; + + float speed; + float air_finished; + float pain_finished; + float radsuit_finished; + + edict_t *pContainingEntity; + + int playerclass; + float maxspeed; + + float fov; + int weaponanim; + + int pushmsec; + + int bInDuck; + int flTimeStepSound; + int flSwimTime; + int flDuckTime; + int iStepLeft; + float flFallVelocity; + + int gamestate; + + int oldbuttons; + + int groupinfo; + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; + edict_t *euser1; + edict_t *euser2; + edict_t *euser3; + edict_t *euser4; +} entvars_t; + + +#endif // PROGDEFS_H diff --git a/hlsdk/engine/progs.h b/hlsdk/engine/progs.h new file mode 100644 index 00000000..1d1f885e --- /dev/null +++ b/hlsdk/engine/progs.h @@ -0,0 +1,82 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PROGS_H +#define PROGS_H + +#include "progdefs.h" + +// 16 simultaneous events, max +#define MAX_EVENT_QUEUE 64 + +#define DEFAULT_EVENT_RESENDS 1 + +#include "event_flags.h" + +typedef struct event_info_s event_info_t; + +#include "event_args.h" + +struct event_info_s +{ + unsigned short index; // 0 implies not in use + + short packet_index; // Use data from state info for entity in delta_packet . -1 implies separate info based on event + // parameter signature + short entity_index; // The edict this event is associated with + + float fire_time; // if non-zero, the time when the event should be fired ( fixed up on the client ) + + event_args_t args; + +// CLIENT ONLY + int flags; // Reliable or not, etc. + +}; + +typedef struct event_state_s event_state_t; + +struct event_state_s +{ + struct event_info_s ei[ MAX_EVENT_QUEUE ]; +}; + +#if !defined( ENTITY_STATEH ) +#include "entity_state.h" +#endif + +#if !defined( EDICT_H ) +#include "edict.h" +#endif + +#define STRUCT_FROM_LINK(l,t,m) ((t *)((byte *)l - (int)&(((t *)0)->m))) +#define EDICT_FROM_AREA(l) STRUCT_FROM_LINK(l,edict_t,area) + +//============================================================================ + +extern char *pr_strings; +extern globalvars_t gGlobalVariables; + +//============================================================================ + +edict_t *ED_Alloc (void); +void ED_Free (edict_t *ed); +void ED_LoadFromFile (char *data); + +edict_t *EDICT_NUM(int n); +int NUM_FOR_EDICT(const edict_t *e); + +#define PROG_TO_EDICT(e) ((edict_t *)((byte *)sv.edicts + e)) + +#endif // PROGS_H diff --git a/hlsdk/engine/shake.h b/hlsdk/engine/shake.h new file mode 100644 index 00000000..8e405db0 --- /dev/null +++ b/hlsdk/engine/shake.h @@ -0,0 +1,55 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef SHAKE_H +#define SHAKE_H + +// Screen / View effects + +// screen shake +extern int gmsgShake; + +// This structure is sent over the net to describe a screen shake event +typedef struct +{ + unsigned short amplitude; // FIXED 4.12 amount of shake + unsigned short duration; // FIXED 4.12 seconds duration + unsigned short frequency; // FIXED 8.8 noise frequency (low frequency is a jerk,high frequency is a rumble) +} ScreenShake; + +extern void V_ApplyShake( float *origin, float *angles, float factor ); +extern void V_CalcShake( void ); +extern int V_ScreenShake( const char *pszName, int iSize, void *pbuf ); +extern int V_ScreenFade( const char *pszName, int iSize, void *pbuf ); + + +// Fade in/out +extern int gmsgFade; + +#define FFADE_IN 0x0000 // Just here so we don't pass 0 into the function +#define FFADE_OUT 0x0001 // Fade out (not in) +#define FFADE_MODULATE 0x0002 // Modulate (don't blend) +#define FFADE_STAYOUT 0x0004 // ignores the duration, stays faded out until new ScreenFade message received + +// This structure is sent over the net to describe a screen fade event +typedef struct +{ + unsigned short duration; // FIXED 4.12 seconds duration + unsigned short holdTime; // FIXED 4.12 seconds duration until reset (fade & hold) + short fadeFlags; // flags + byte r, g, b, a; // fade to color ( max alpha ) +} ScreenFade; + +#endif // SHAKE_H + diff --git a/hlsdk/engine/studio.h b/hlsdk/engine/studio.h new file mode 100644 index 00000000..62d4a986 --- /dev/null +++ b/hlsdk/engine/studio.h @@ -0,0 +1,362 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + + + + +#ifndef _STUDIO_H_ +#define _STUDIO_H_ + +/* +============================================================================== + +STUDIO MODELS + +Studio models are position independent, so the cache manager can move them. +============================================================================== +*/ + + +#define MAXSTUDIOTRIANGLES 20000 // TODO: tune this +#define MAXSTUDIOVERTS 2048 // TODO: tune this +#define MAXSTUDIOSEQUENCES 256 // total animation sequences +#define MAXSTUDIOSKINS 100 // total textures +#define MAXSTUDIOSRCBONES 512 // bones allowed at source movement +#define MAXSTUDIOBONES 128 // total bones actually used +#define MAXSTUDIOMODELS 32 // sub-models per model +#define MAXSTUDIOBODYPARTS 32 +#define MAXSTUDIOGROUPS 16 +#define MAXSTUDIOANIMATIONS 512 // per sequence +#define MAXSTUDIOMESHES 256 +#define MAXSTUDIOEVENTS 1024 +#define MAXSTUDIOPIVOTS 256 +#define MAXSTUDIOCONTROLLERS 8 + +typedef struct +{ + int id; + int version; + + char name[64]; + int length; + + vec3_t eyeposition; // ideal eye position + vec3_t min; // ideal movement hull size + vec3_t max; + + vec3_t bbmin; // clipping bounding box + vec3_t bbmax; + + int flags; + + int numbones; // bones + int boneindex; + + int numbonecontrollers; // bone controllers + int bonecontrollerindex; + + int numhitboxes; // complex bounding boxes + int hitboxindex; + + int numseq; // animation sequences + int seqindex; + + int numseqgroups; // demand loaded sequences + int seqgroupindex; + + int numtextures; // raw textures + int textureindex; + int texturedataindex; + + int numskinref; // replaceable textures + int numskinfamilies; + int skinindex; + + int numbodyparts; + int bodypartindex; + + int numattachments; // queryable attachable points + int attachmentindex; + + int soundtable; + int soundindex; + int soundgroups; + int soundgroupindex; + + int numtransitions; // animation node to animation node transition graph + int transitionindex; +} studiohdr_t; + +// header for demand loaded sequence group data +typedef struct +{ + int id; + int version; + + char name[64]; + int length; +} studioseqhdr_t; + +// bones +typedef struct +{ + char name[32]; // bone name for symbolic links + int parent; // parent bone + int flags; // ?? + int bonecontroller[6]; // bone controller index, -1 == none + float value[6]; // default DoF values + float scale[6]; // scale for delta DoF values +} mstudiobone_t; + + +// bone controllers +typedef struct +{ + int bone; // -1 == 0 + int type; // X, Y, Z, XR, YR, ZR, M + float start; + float end; + int rest; // byte index value at rest + int index; // 0-3 user set controller, 4 mouth +} mstudiobonecontroller_t; + +// intersection boxes +typedef struct +{ + int bone; + int group; // intersection group + vec3_t bbmin; // bounding box + vec3_t bbmax; +} mstudiobbox_t; + +#if !defined( CACHE_USER ) && !defined( QUAKEDEF_H ) +#define CACHE_USER +typedef struct cache_user_s +{ + void *data; +} cache_user_t; +#endif + +// demand loaded sequence groups +typedef struct +{ + char label[32]; // textual name + char name[64]; // file name + cache_user_t cache; // cache index pointer + int data; // hack for group 0 +} mstudioseqgroup_t; + +// sequence descriptions +typedef struct +{ + char label[32]; // sequence label + + float fps; // frames per second + int flags; // looping/non-looping flags + + int activity; + int actweight; + + int numevents; + int eventindex; + + int numframes; // number of frames per sequence + + int numpivots; // number of foot pivots + int pivotindex; + + int motiontype; + int motionbone; + vec3_t linearmovement; + int automoveposindex; + int automoveangleindex; + + vec3_t bbmin; // per sequence bounding box + vec3_t bbmax; + + int numblends; + int animindex; // mstudioanim_t pointer relative to start of sequence group data + // [blend][bone][X, Y, Z, XR, YR, ZR] + + int blendtype[2]; // X, Y, Z, XR, YR, ZR + float blendstart[2]; // starting value + float blendend[2]; // ending value + int blendparent; + + int seqgroup; // sequence group for demand loading + + int entrynode; // transition node at entry + int exitnode; // transition node at exit + int nodeflags; // transition rules + + int nextseq; // auto advancing sequences +} mstudioseqdesc_t; + +// events +#include "studio_event.h" +/* +typedef struct +{ + int frame; + int event; + int type; + char options[64]; +} mstudioevent_t; +*/ + +// pivots +typedef struct +{ + vec3_t org; // pivot point + int start; + int end; +} mstudiopivot_t; + +// attachment +typedef struct +{ + char name[32]; + int type; + int bone; + vec3_t org; // attachment point + vec3_t vectors[3]; +} mstudioattachment_t; + +typedef struct +{ + unsigned short offset[6]; +} mstudioanim_t; + +// animation frames +typedef union +{ + struct { + byte valid; + byte total; + } num; + short value; +} mstudioanimvalue_t; + + + +// body part index +typedef struct +{ + char name[64]; + int nummodels; + int base; + int modelindex; // index into models array +} mstudiobodyparts_t; + + + +// skin info +typedef struct +{ + char name[64]; + int flags; + int width; + int height; + int index; +} mstudiotexture_t; + + +// skin families +// short index[skinfamilies][skinref] + +// studio models +typedef struct +{ + char name[64]; + + int type; + + float boundingradius; + + int nummesh; + int meshindex; + + int numverts; // number of unique vertices + int vertinfoindex; // vertex bone info + int vertindex; // vertex vec3_t + int numnorms; // number of unique surface normals + int norminfoindex; // normal bone info + int normindex; // normal vec3_t + + int numgroups; // deformation groups + int groupindex; +} mstudiomodel_t; + + +// vec3_t boundingbox[model][bone][2]; // complex intersection info + + +// meshes +typedef struct +{ + int numtris; + int triindex; + int skinref; + int numnorms; // per mesh normals + int normindex; // normal vec3_t +} mstudiomesh_t; + +// triangles +#if 0 +typedef struct +{ + short vertindex; // index into vertex array + short normindex; // index into normal array + short s,t; // s,t position on skin +} mstudiotrivert_t; +#endif + +// lighting options +#define STUDIO_NF_FLATSHADE 0x0001 +#define STUDIO_NF_CHROME 0x0002 +#define STUDIO_NF_FULLBRIGHT 0x0004 + +// motion flags +#define STUDIO_X 0x0001 +#define STUDIO_Y 0x0002 +#define STUDIO_Z 0x0004 +#define STUDIO_XR 0x0008 +#define STUDIO_YR 0x0010 +#define STUDIO_ZR 0x0020 +#define STUDIO_LX 0x0040 +#define STUDIO_LY 0x0080 +#define STUDIO_LZ 0x0100 +#define STUDIO_AX 0x0200 +#define STUDIO_AY 0x0400 +#define STUDIO_AZ 0x0800 +#define STUDIO_AXR 0x1000 +#define STUDIO_AYR 0x2000 +#define STUDIO_AZR 0x4000 +#define STUDIO_TYPES 0x7FFF +#define STUDIO_RLOOP 0x8000 // controller that wraps shortest distance + +// sequence flags +#define STUDIO_LOOPING 0x0001 + +// bone flags +#define STUDIO_HAS_NORMALS 0x0001 +#define STUDIO_HAS_VERTICES 0x0002 +#define STUDIO_HAS_BBOX 0x0004 +#define STUDIO_HAS_CHROME 0x0008 // if any of the textures have chrome on them + +#define RAD_TO_STUDIO (32768.0/M_PI) +#define STUDIO_TO_RAD (M_PI/32768.0) + +#endif diff --git a/hlsdk/game_shared/bitvec.h b/hlsdk/game_shared/bitvec.h new file mode 100644 index 00000000..d77bfee7 --- /dev/null +++ b/hlsdk/game_shared/bitvec.h @@ -0,0 +1,181 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef BITVEC_H +#define BITVEC_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + + +#include + + +class CBitVecAccessor +{ +public: + CBitVecAccessor(unsigned long *pDWords, int iBit); + + void operator=(int val); + operator unsigned long(); + +private: + unsigned long *m_pDWords; + int m_iBit; +}; + + +// CBitVec allows you to store a list of bits and do operations on them like they were +// an atomic type. +template +class CBitVec +{ +public: + + CBitVec(); + + // Set all values to the specified value (0 or 1..) + void Init(int val = 0); + + // Access the bits like an array. + CBitVecAccessor operator[](int i); + + // Operations on other bit vectors. + CBitVec& operator=(CBitVec const &other); + bool operator==(CBitVec const &other); + bool operator!=(CBitVec const &other); + + // Get underlying dword representations of the bits. + int GetNumDWords(); + unsigned long GetDWord(int i); + void SetDWord(int i, unsigned long val); + + int GetNumBits(); + +private: + + enum {NUM_DWORDS = NUM_BITS/32 + !!(NUM_BITS & 31)}; + unsigned long m_DWords[NUM_DWORDS]; +}; + + + +// ------------------------------------------------------------------------ // +// CBitVecAccessor inlines. +// ------------------------------------------------------------------------ // + +inline CBitVecAccessor::CBitVecAccessor(unsigned long *pDWords, int iBit) +{ + m_pDWords = pDWords; + m_iBit = iBit; +} + + +inline void CBitVecAccessor::operator=(int val) +{ + if(val) + m_pDWords[m_iBit >> 5] |= (1 << (m_iBit & 31)); + else + m_pDWords[m_iBit >> 5] &= ~(unsigned long)(1 << (m_iBit & 31)); +} + +inline CBitVecAccessor::operator unsigned long() +{ + return m_pDWords[m_iBit >> 5] & (1 << (m_iBit & 31)); +} + + + +// ------------------------------------------------------------------------ // +// CBitVec inlines. +// ------------------------------------------------------------------------ // + +template +inline int CBitVec::GetNumBits() +{ + return NUM_BITS; +} + + +template +inline CBitVec::CBitVec() +{ + for(int i=0; i < NUM_DWORDS; i++) + m_DWords[i] = 0; +} + + +template +inline void CBitVec::Init(int val) +{ + for(int i=0; i < GetNumBits(); i++) + { + (*this)[i] = val; + } +} + + +template +inline CBitVec& CBitVec::operator=(CBitVec const &other) +{ + memcpy(m_DWords, other.m_DWords, sizeof(m_DWords)); + return *this; +} + + +template +inline CBitVecAccessor CBitVec::operator[](int i) +{ + assert(i >= 0 && i < GetNumBits()); + return CBitVecAccessor(m_DWords, i); +} + + +template +inline bool CBitVec::operator==(CBitVec const &other) +{ + for(int i=0; i < NUM_DWORDS; i++) + if(m_DWords[i] != other.m_DWords[i]) + return false; + + return true; +} + + +template +inline bool CBitVec::operator!=(CBitVec const &other) +{ + return !(*this == other); +} + + +template +inline int CBitVec::GetNumDWords() +{ + return NUM_DWORDS; +} + +template +inline unsigned long CBitVec::GetDWord(int i) +{ + assert(i >= 0 && i < NUM_DWORDS); + return m_DWords[i]; +} + + +template +inline void CBitVec::SetDWord(int i, unsigned long val) +{ + assert(i >= 0 && i < NUM_DWORDS); + m_DWords[i] = val; +} + + +#endif // BITVEC_H + diff --git a/hlsdk/game_shared/vgui_checkbutton2.cpp b/hlsdk/game_shared/vgui_checkbutton2.cpp new file mode 100644 index 00000000..511853f8 --- /dev/null +++ b/hlsdk/game_shared/vgui_checkbutton2.cpp @@ -0,0 +1,197 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include +#include "vgui_checkbutton2.h" +#include "vgui_loadtga.h" + + +#define EXTRA_X 5 + + +using namespace vgui; + + + +CCheckButton2::CCheckButton2() : + m_Label(""), + m_pChecked(NULL), + m_pUnchecked(NULL), + m_pHandler(NULL), + m_CheckboxPanel(NULL) +{ + m_bOwnImages = false; + m_bChecked = false; + m_pChecked = m_pUnchecked = NULL; + m_bCheckboxLeft = true; + + m_Label.setParent(this); + m_Label.setFgColor(255,255,255,0); + m_Label.setBgColor(0,0,0,255); // background is not drawn and foreground is white + m_Label.addInputSignal(this); + + m_CheckboxPanel.setParent(this); + m_CheckboxPanel.addInputSignal(this); + + setPaintBackgroundEnabled(false); +} + + +CCheckButton2::~CCheckButton2() +{ + DeleteImages(); +} + + +void CCheckButton2::SetImages(char const *pChecked, char const *pUnchecked) +{ + DeleteImages(); + + m_pChecked = vgui_LoadTGA(pChecked); + m_pUnchecked = vgui_LoadTGA(pUnchecked); + m_bOwnImages = true; + + SetupControls(); +} + + +void CCheckButton2::SetImages(Image *pChecked, Image *pUnchecked) +{ + DeleteImages(); + + m_pChecked = pChecked; + m_pUnchecked = pUnchecked; + m_bOwnImages = false; + + SetupControls(); +} + + +void CCheckButton2::DeleteImages() +{ + if(m_bOwnImages) + { + delete m_pChecked; + delete m_pUnchecked; + } + + m_pChecked = NULL; + m_pUnchecked = NULL; + m_bOwnImages = false; + + SetupControls(); +} + + +void CCheckButton2::SetCheckboxLeft(bool bLeftAlign) +{ + m_bCheckboxLeft = bLeftAlign; + SetupControls(); +} + + +bool CCheckButton2::GetCheckboxLeft() +{ + return m_bCheckboxLeft; +} + + +void CCheckButton2::SetText(char const *pText, ...) +{ + char str[512]; + + va_list marker; + va_start(marker, pText); + _vsnprintf(str, sizeof(str), pText, marker); + va_end(marker); + + m_Label.setText(str); + SetupControls(); +} + + +void CCheckButton2::SetTextColor(int r, int g, int b, int a) +{ + m_Label.setFgColor(r, g, b, a); + repaint(); +} + + +void CCheckButton2::SetHandler(ICheckButton2Handler *pHandler) +{ + m_pHandler = pHandler; +} + + +bool CCheckButton2::IsChecked() +{ + return m_bChecked; +} + + +void CCheckButton2::SetChecked(bool bChecked) +{ + m_bChecked = bChecked; + SetupControls(); +} + + +void CCheckButton2::internalMousePressed(MouseCode code) +{ + m_bChecked = !m_bChecked; + + if(m_pHandler) + m_pHandler->StateChanged(this); + + SetupControls(); +} + + +void CCheckButton2::SetupControls() +{ + // Initialize the checkbutton bitmap. + Image *pBitmap = m_bChecked ? m_pChecked : m_pUnchecked; + + Panel *controls[2] = {&m_CheckboxPanel, &m_Label}; + int controlSizes[2][2]; + + controlSizes[0][0] = controlSizes[0][1] = 0; + if(pBitmap) + pBitmap->getSize(controlSizes[0][0], controlSizes[0][1]); + + m_CheckboxPanel.setImage(pBitmap); + m_CheckboxPanel.setSize(controlSizes[0][0], controlSizes[0][1]); + + + // Get the label's size. + m_Label.getSize(controlSizes[1][0], controlSizes[1][1]); + m_Label.setContentAlignment(Label::a_west); + + + // Position the controls. + int iLeftControl = !m_bCheckboxLeft; + int iBiggestY = controlSizes[0][1] > controlSizes[1][0] ? 0 : 1; + controls[iLeftControl]->setPos(0, (controlSizes[iBiggestY][1] - controlSizes[iLeftControl][1]) / 2); + controls[!iLeftControl]->setPos(controlSizes[iLeftControl][0] + EXTRA_X, (controlSizes[iBiggestY][1] - controlSizes[!iLeftControl][1]) / 2); + + + // Fit this control to the sizes of the subcontrols. + setSize(controlSizes[0][0] + controlSizes[1][0] + EXTRA_X, (controlSizes[0][1] > controlSizes[1][1]) ? controlSizes[0][1] : controlSizes[1][1]); + repaint(); +} + + +void CCheckButton2::mousePressed(MouseCode code, Panel *panel) +{ + internalMousePressed(code); +} + + + + + diff --git a/hlsdk/game_shared/vgui_checkbutton2.h b/hlsdk/game_shared/vgui_checkbutton2.h new file mode 100644 index 00000000..ef10b459 --- /dev/null +++ b/hlsdk/game_shared/vgui_checkbutton2.h @@ -0,0 +1,103 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_CHECKBUTTON2_H +#define VGUI_CHECKBUTTON2_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + + +#include "vgui_label.h" +#include "vgui_imagepanel.h" +#include "vgui_defaultinputsignal.h" + + +namespace vgui +{ + + +class CCheckButton2; + + +class ICheckButton2Handler +{ +public: + virtual void StateChanged(CCheckButton2 *pButton) = 0; +}; + + +// VGUI checkbox class. +// - Provides access to the checkbox images. +// - Provides an easy callback mechanism for state changes. +// - Default background is invisible, and default text color is white. +class CCheckButton2 : public Panel, public CDefaultInputSignal +{ +public: + + CCheckButton2(); + ~CCheckButton2(); + + // Initialize the button with these. + void SetImages(char const *pChecked, char const *pUnchecked); + void SetImages(Image *pChecked, Image *pUnchecked); // If you use this, the button will never delete the images. + void DeleteImages(); + + // The checkbox can be to the left or the right of the text (default is left). + void SetCheckboxLeft(bool bLeftAlign); + bool GetCheckboxLeft(); + + // Set the label text. + void SetText(char const *pText, ...); + void SetTextColor(int r, int g, int b, int a); + + // You can register for change notification here. + void SetHandler(ICheckButton2Handler *pHandler); + + // Get/set the check state. + bool IsChecked(); + void SetChecked(bool bChecked); + + + +// Panel overrides. +public: + + virtual void internalMousePressed(MouseCode code); + + +protected: + + void SetupControls(); + + +// InputSignal overrides. +protected: + virtual void mousePressed(MouseCode code,Panel* panel); + + +public: + ICheckButton2Handler *m_pHandler; + + bool m_bCheckboxLeft; + Label m_Label; + ImagePanel m_CheckboxPanel; + + Image *m_pChecked; + Image *m_pUnchecked; + bool m_bOwnImages; + + bool m_bChecked; +}; + + +} + + +#endif // VGUI_CHECKBUTTON2_H diff --git a/hlsdk/game_shared/vgui_defaultinputsignal.h b/hlsdk/game_shared/vgui_defaultinputsignal.h new file mode 100644 index 00000000..f2067963 --- /dev/null +++ b/hlsdk/game_shared/vgui_defaultinputsignal.h @@ -0,0 +1,41 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_DEFAULTINPUTSIGNAL_H +#define VGUI_DEFAULTINPUTSIGNAL_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + + +#include "vgui_inputsignal.h" + + +namespace vgui +{ + // This class derives from vgui::InputSignal and implements empty defaults for all of its functions. + class CDefaultInputSignal : public vgui::InputSignal + { + public: + 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) {} + 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) {} + }; +} + + +#endif // VGUI_DEFAULTINPUTSIGNAL_H diff --git a/hlsdk/game_shared/vgui_grid.cpp b/hlsdk/game_shared/vgui_grid.cpp new file mode 100644 index 00000000..00e9d699 --- /dev/null +++ b/hlsdk/game_shared/vgui_grid.cpp @@ -0,0 +1,398 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include "vgui_grid.h" + + +using namespace vgui; + + +#define AssertCheck(expr, msg) \ + if(!(expr))\ + {\ + assert(!msg);\ + return 0;\ + } + + + +// ------------------------------------------------------------------------------ // +// CGrid::CGridEntry. +// ------------------------------------------------------------------------------ // + +CGrid::CGridEntry::CGridEntry() +{ + m_pPanel = NULL; + m_bUnderline = false; +} + +CGrid::CGridEntry::~CGridEntry() +{ +} + + +// ------------------------------------------------------------------------------ // +// CGrid. +// ------------------------------------------------------------------------------ // + +CGrid::CGrid() +{ + Clear(); +} + + +CGrid::~CGrid() +{ + Term(); +} + + +bool CGrid::SetDimensions(int xCols, int yRows) +{ + Term(); + + m_GridEntries = new CGridEntry[xCols * yRows]; + m_Widths = new int[xCols*2 + yRows*2]; + m_Heights = m_Widths + xCols; + m_ColOffsets = m_Heights + yRows; + m_RowOffsets = m_ColOffsets + xCols; + + if(!m_GridEntries || !m_Widths) + { + Term(); + return false; + } + + memset(m_Widths, 0, sizeof(int) * (xCols*2 + yRows*2)); + + m_xCols = xCols; + m_yRows = yRows; + return true; +} + + +void CGrid::Term() +{ + delete [] m_GridEntries; + delete [] m_Widths; + Clear(); +} + + +Panel* CGrid::GetEntry(int x, int y) +{ + return GridEntry(x, y)->m_pPanel; +} + + +bool CGrid::SetEntry(int x, int y, Panel *pPanel) +{ + CGridEntry *pEntry = GridEntry(x, y); + if(!pEntry) + return false; + + if(pEntry->m_pPanel) + pEntry->m_pPanel->setParent(NULL); + + pEntry->m_pPanel = pPanel; + if(pPanel) + pPanel->setParent(this); + + m_bDirty = true; + return true; +} + + +int CGrid::GetXSpacing() +{ + return m_xSpacing; +} + + +int CGrid::GetYSpacing() +{ + return m_ySpacing; +} + + +void CGrid::SetSpacing(int xSpacing, int ySpacing) +{ + if(xSpacing != m_xSpacing) + { + m_xSpacing = xSpacing; + CalcColOffsets(0); + m_bDirty = true; + } + + if(ySpacing != m_ySpacing) + { + m_ySpacing = ySpacing; + CalcRowOffsets(0); + m_bDirty = true; + } +} + + +bool CGrid::SetColumnWidth(int iColumn, int width) +{ + AssertCheck(iColumn >= 0 && iColumn < m_xCols, "CGrid::SetColumnWidth : invalid location specified"); + m_Widths[iColumn] = width; + CalcColOffsets(iColumn+1); + m_bDirty = true; + return true; +} + + +bool CGrid::SetRowHeight(int iRow, int height) +{ + AssertCheck(iRow >= 0 && iRow < m_yRows, "CGrid::SetColumnWidth : invalid location specified"); + m_Heights[iRow] = height; + CalcRowOffsets(iRow+1); + m_bDirty = true; + return true; +} + + +int CGrid::GetColumnWidth(int iColumn) +{ + AssertCheck(iColumn >= 0 && iColumn < m_xCols, "CGrid::GetColumnWidth: invalid location specified"); + return m_Widths[iColumn]; +} + + +int CGrid::GetRowHeight(int iRow) +{ + AssertCheck(iRow >= 0 && iRow < m_yRows, "CGrid::GetRowHeight: invalid location specified"); + return m_Heights[iRow]; +} + + +int CGrid::CalcFitColumnWidth(int iColumn) +{ + AssertCheck(iColumn >= 0 && iColumn < m_xCols, "CGrid::CalcFitColumnWidth: invalid location specified"); + + int maxSize = 0; + for(int i=0; i < m_yRows; i++) + { + Panel *pPanel = GridEntry(iColumn, i)->m_pPanel; + if(!pPanel) + continue; + + int w, h; + pPanel->getSize(w,h); + if(w > maxSize) + maxSize = w; + } + + return maxSize; +} + + +int CGrid::CalcFitRowHeight(int iRow) +{ + AssertCheck(iRow >= 0 && iRow < m_yRows, "CGrid::CalcFitRowHeight: invalid location specified"); + + int maxSize = 0; + for(int i=0; i < m_xCols; i++) + { + Panel *pPanel = GridEntry(i, iRow)->m_pPanel; + if(!pPanel) + continue; + + int w, h; + pPanel->getSize(w,h); + if(h > maxSize) + maxSize = h; + } + + return maxSize; +} + + +void CGrid::AutoSetRowHeights() +{ + for(int i=0; i < m_yRows; i++) + SetRowHeight(i, CalcFitRowHeight(i)); +} + + +bool CGrid::GetEntryBox( + int col, int row, int &x, int &y, int &w, int &h) +{ + AssertCheck(col >= 0 && col < m_xCols && row >= 0 && row < m_yRows, "CGrid::GetEntryBox: invalid location specified"); + + x = m_ColOffsets[col]; + w = m_Widths[col]; + + y = m_RowOffsets[row]; + h = m_Heights[row]; + return true; +} + + +bool CGrid::CopyColumnWidths(CGrid *pOther) +{ + if(!pOther || pOther->m_xCols != m_xCols) + return false; + + for(int i=0; i < m_xCols; i++) + m_Widths[i] = pOther->m_Widths[i]; + + CalcColOffsets(0); + m_bDirty = true; + return true; +} + + +void CGrid::RepositionContents() +{ + for(int x=0; x < m_xCols; x++) + { + for(int y=0; y < m_yRows; y++) + { + Panel *pPanel = GridEntry(x,y)->m_pPanel; + if(!pPanel) + continue; + + pPanel->setBounds( + m_ColOffsets[x], + m_RowOffsets[y], + m_Widths[x], + m_Heights[y]); + } + } + + m_bDirty = false; +} + + +int CGrid::CalcDrawHeight() +{ + if(m_yRows > 0) + { + return m_RowOffsets[m_yRows-1] + m_Heights[m_yRows - 1] + m_ySpacing; + } + else + { + return 0; + } +} + + +void CGrid::paint() +{ + if(m_bDirty) + RepositionContents(); + + Panel::paint(); + + // walk the grid looking for underlined rows + int x = 0, y = 0; + for (int row = 0; row < m_yRows; row++) + { + CGridEntry *cell = GridEntry(0, row); + + y += cell->m_pPanel->getTall() + m_ySpacing; + if (cell->m_bUnderline) + { + drawSetColor(cell->m_UnderlineColor[0], cell->m_UnderlineColor[1], cell->m_UnderlineColor[2], cell->m_UnderlineColor[3]); + drawFilledRect(0, y - (cell->m_iUnderlineOffset + 1), getWide(), y - cell->m_iUnderlineOffset); + } + } +} + +void CGrid::paintBackground() +{ + Panel::paintBackground(); +} + +//----------------------------------------------------------------------------- +// Purpose: sets underline color for a particular row +//----------------------------------------------------------------------------- +void CGrid::SetRowUnderline(int row, bool enabled, int offset, int r, int g, int b, int a) +{ + CGridEntry *cell = GridEntry(0, row); + cell->m_bUnderline = enabled; + if (enabled) + { + cell->m_iUnderlineOffset = offset; + cell->m_UnderlineColor[0] = r; + cell->m_UnderlineColor[1] = g; + cell->m_UnderlineColor[2] = b; + cell->m_UnderlineColor[3] = a; + } +} + +void CGrid::Clear() +{ + m_xCols = m_yRows = 0; + m_Widths = NULL; + m_GridEntries = NULL; + m_xSpacing = m_ySpacing = 0; + m_bDirty = false; +} + + +CGrid::CGridEntry* CGrid::GridEntry(int x, int y) +{ + AssertCheck(x >= 0 && x < m_xCols && y >= 0 && y < m_yRows, "CGrid::GridEntry: invalid location specified"); + return &m_GridEntries[y*m_xCols + x]; +} + + +void CGrid::CalcColOffsets(int iStart) +{ + int cur = m_xSpacing; + if(iStart != 0) + cur += m_ColOffsets[iStart-1] + m_Widths[iStart-1]; + + for(int i=iStart; i < m_xCols; i++) + { + m_ColOffsets[i] = cur; + cur += m_Widths[i] + m_xSpacing; + } +} + + +void CGrid::CalcRowOffsets(int iStart) +{ + int cur = m_ySpacing; + if(iStart != 0) + cur += m_RowOffsets[iStart-1]; + + for(int i=iStart; i < m_yRows; i++) + { + m_RowOffsets[i] = cur; + cur += m_Heights[i] + m_ySpacing; + } +} + +bool CGrid::getCellAtPoint(int worldX, int worldY, int &row, int &col) +{ + row = -1; col = -1; + for(int x=0; x < m_xCols; x++) + { + for(int y=0; y < m_yRows; y++) + { + Panel *pPanel = GridEntry(x,y)->m_pPanel; + if (!pPanel) + continue; + + if (pPanel->isWithin(worldX, worldY)) + { + col = x; + row = y; + return true; + } + } + } + + return false; +} + + diff --git a/hlsdk/game_shared/vgui_grid.h b/hlsdk/game_shared/vgui_grid.h new file mode 100644 index 00000000..e9a9bc91 --- /dev/null +++ b/hlsdk/game_shared/vgui_grid.h @@ -0,0 +1,124 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_GRID_H +#define VGUI_GRID_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + + +#include "vgui_panel.h" + + +namespace vgui +{ + +// The grid control simply manages a grid of panels. You can adjust column sizes and spacings and +// configure and fill the panels however you want. +// To use this control, call SetDimensions, SetSpacing and fill the controls. +class CGrid : public Panel +{ +public: + CGrid(); + virtual ~CGrid(); + + bool SetDimensions(int xCols, int yRows); // Set how many columns and rows in the grid. + void Term(); + + Panel* GetEntry(int x, int y); // Get the panel associated with a grid entry. + bool SetEntry(int x, int y, Panel *pPanel); + + int GetXSpacing(); + int GetYSpacing(); + void SetSpacing(int xSpacing, int ySpacing); // Set spacing between rows and columns. + + bool SetColumnWidth(int iColumn, int width); // Set a column's width. + bool SetRowHeight(int iRow, int height); // Set a row's height. + + int GetColumnWidth(int iColumn); + int GetRowHeight(int iRow); + + int CalcFitColumnWidth(int iColumn); // Returns the maximum width of all panels in the column. + int CalcFitRowHeight(int iRow); // Returns the maximum height of all panels in the row. + + int CalcDrawHeight(); // Returns how many pixels high the grid control should be + // for all of its contents to be visible (based on its row heights + // and y spacing). + + void AutoSetRowHeights(); // Just does SetRowHeight(iRow, CalcFitRowHeight(iRow)) for all rows. + + bool GetEntryBox( // Returns the bounding box for the specified entry. + int col, int row, int &x, int &y, int &w, int &h); + + bool CopyColumnWidths(CGrid *pOther); // Copy the column widths from the other grid. Fails if the + // column count is different. + + void RepositionContents(); // Sets the size and position of all the grid entries based + // on current spacings and row/column widths. + // You usually only want to call this while setting up the control + // if you want to get the position or dimensions of the child + // controls. This will set them. + + void SetRowUnderline(int row, bool enabled, int offset, int r, int g, int b, int a); // sets underline color for a particular row + + // returns the true if found, false otherwise + bool getCellAtPoint(int worldX, int worldY, int &row, int &col); + +// Panel overrides. +public: + + virtual void paint(); + virtual void paintBackground(); + +protected: + + class CGridEntry + { + public: + CGridEntry(); + ~CGridEntry(); + + Panel *m_pPanel; + + bool m_bUnderline; + short m_UnderlineColor[4]; + int m_iUnderlineOffset; + }; + + void Clear(); + CGridEntry* GridEntry(int x, int y); + + void CalcColOffsets(int iStart); + void CalcRowOffsets(int iStart); + + +protected: + + bool m_bDirty; // Set when controls will need to be repositioned. + + int m_xCols; + int m_yRows; + + int m_xSpacing; + int m_ySpacing; + + int *m_Widths; + int *m_Heights; + int *m_ColOffsets; + int *m_RowOffsets; + + CGridEntry *m_GridEntries; + +}; + +}; + + +#endif // VGUI_GRID_H diff --git a/hlsdk/game_shared/vgui_helpers.cpp b/hlsdk/game_shared/vgui_helpers.cpp new file mode 100644 index 00000000..fbcfeb5e --- /dev/null +++ b/hlsdk/game_shared/vgui_helpers.cpp @@ -0,0 +1,45 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "vgui_helpers.h" + + +using namespace vgui; + + +void AlignPanel(Panel *pChild, Panel *pParent, int alignment) +{ + int w, h, cw, ch; + pParent->getSize(w, h); + pChild->getSize(cw, ch); + + int xCenter = (w - cw) / 2; + int yCenter = (h - ch) / 2; + + if(alignment == Label::a_west) + pChild->setPos(0, yCenter); + else if(alignment == Label::a_northwest) + pChild->setPos(0,0); + else if(alignment == Label::a_north) + pChild->setPos(xCenter, 0); + else if(alignment == Label::a_northeast) + pChild->setPos(w - cw, 0); + else if(alignment == Label::a_east) + pChild->setPos(w - cw, yCenter); + else if(alignment == Label::a_southeast) + pChild->setPos(w - cw, h - ch); + else if(alignment == Label::a_south) + pChild->setPos(xCenter, h - ch); + else if(alignment == Label::a_southwest) + pChild->setPos(0, h - ch); + else if(alignment == Label::a_center) + pChild->setPos(xCenter, yCenter); +} + + + + diff --git a/hlsdk/game_shared/vgui_helpers.h b/hlsdk/game_shared/vgui_helpers.h new file mode 100644 index 00000000..c454b3dc --- /dev/null +++ b/hlsdk/game_shared/vgui_helpers.h @@ -0,0 +1,33 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_HELPERS_H +#define VGUI_HELPERS_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + + +#include "vgui_panel.h" +#include "vgui_label.h" + + +inline int PanelTop(vgui::Panel *pPanel) {int x,y,w,h; pPanel->getBounds(x,y,w,h); return y;} +inline int PanelLeft(vgui::Panel *pPanel) {int x,y,w,h; pPanel->getBounds(x,y,w,h); return x;} +inline int PanelRight(vgui::Panel *pPanel) {int x,y,w,h; pPanel->getBounds(x,y,w,h); return x+w;} +inline int PanelBottom(vgui::Panel *pPanel) {int x,y,w,h; pPanel->getBounds(x,y,w,h); return y+h;} +inline int PanelWidth(vgui::Panel *pPanel) {int x,y,w,h; pPanel->getBounds(x,y,w,h); return w;} +inline int PanelHeight(vgui::Panel *pPanel) {int x,y,w,h; pPanel->getBounds(x,y,w,h); return h;} + +// Places child at the requested position inside pParent. iAlignment is from Label::Alignment. +void AlignPanel(vgui::Panel *pChild, vgui::Panel *pParent, int alignment); + + +#endif // VGUI_HELPERS_H + diff --git a/hlsdk/game_shared/vgui_listbox.cpp b/hlsdk/game_shared/vgui_listbox.cpp new file mode 100644 index 00000000..972091e0 --- /dev/null +++ b/hlsdk/game_shared/vgui_listbox.cpp @@ -0,0 +1,207 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "vgui_listbox.h" + + + +using namespace vgui; + + +CListBox::CListBox() : Panel(0, 0, 0, 0), + m_ItemsPanel(0,0,0,0), + m_ScrollBar(0, 0, 0, 0, true), + m_Slider(0, 0, 10, 40, true) +{ + m_Signal.m_pListBox = this; + + m_ItemsPanel.setParent(this); + m_ItemsPanel.setBgColor(0,0,0,255); + + m_Slider.setRangeWindow(50); + m_Slider.setRangeWindowEnabled(true); + + m_ScrollBar.setParent(this); + m_ScrollBar.addIntChangeSignal(&m_Signal); + m_ScrollBar.setSlider(&m_Slider); + m_ScrollBar.setButtonPressedScrollValue(1); + + m_Items.m_pNext = m_Items.m_pPrev = &m_Items; + m_ItemOffset = 0; + m_iScrollMax = -1; +} + +CListBox::~CListBox() +{ + Term(); +} + +void CListBox::Init() +{ + Term(); +} + +void CListBox::Term() +{ + m_ItemOffset = 0; + + // Free the LBItems. + LBItem *pNext; + for(LBItem *pItem=m_Items.m_pNext; pItem != &m_Items; pItem=pNext) + { + pItem->m_pPanel->setParent(NULL); // detach the panel from us + pNext = pItem->m_pNext; + delete pItem; + } + m_Items.m_pPrev = m_Items.m_pNext = &m_Items; +} + +void CListBox::AddItem(Panel* panel) +{ + // Add the item. + LBItem *pItem = new LBItem; + if(!pItem) + return; + + pItem->m_pPanel = panel; + pItem->m_pPanel->setParent(&m_ItemsPanel); + + pItem->m_pPrev = m_Items.m_pPrev; + pItem->m_pNext = &m_Items; + pItem->m_pNext->m_pPrev = pItem->m_pPrev->m_pNext = pItem; + + m_ScrollBar.setRange(0, GetScrollMax()); + m_Slider.setRangeWindow(50); + m_Slider.setRangeWindowEnabled(true); + + InternalLayout(); +} + +int CListBox::GetNumItems() +{ + int count=0; + for(LBItem *pItem=m_Items.m_pNext; pItem != &m_Items; pItem=pItem->m_pNext) + ++count; + + return count; +} + +int CListBox::GetItemWidth() +{ + int wide, tall; + m_ItemsPanel.getSize(wide, tall); + return wide; +} + +int CListBox::GetScrollPos() +{ + return m_ItemOffset; +} + +void CListBox::SetScrollPos(int pos) +{ + int maxItems = GetScrollMax(); + if(maxItems < 0) + return; + + m_ItemOffset = (pos < 0) ? 0 : ((pos > maxItems) ? maxItems : pos); + InternalLayout(); +} + +void CListBox::setPos(int x, int y) +{ + Panel::setPos(x, y); + InternalLayout(); +} + +void CListBox::setSize(int wide,int tall) +{ + Panel::setSize(wide,tall); + InternalLayout(); +} + +void CListBox::setPixelScroll(int value) +{ + m_ItemOffset = m_ScrollBar.getValue(); + InternalLayout(); +} + +void CListBox::InternalLayout() +{ + int x, y, wide, tall; + getBounds(x, y, wide, tall); + + // Reposition the main panel and the scrollbar. + m_ItemsPanel.setBounds(0, 0, wide-15, tall); + m_ScrollBar.setBounds(wide-15, 0, 15, tall); + + bool bNeedScrollbar = false; + + // Reposition the items. + int curItem = 0; + int curY = 0; + int maxItem = GetScrollMax(); + for(LBItem *pItem=m_Items.m_pNext; pItem != &m_Items; pItem=pItem->m_pNext) + { + if(curItem < m_ItemOffset) + { + pItem->m_pPanel->setVisible(false); + bNeedScrollbar = true; + } + else if (curItem >= maxItem) + { + // item is past the end of the items we care about + pItem->m_pPanel->setVisible(false); + } + else + { + pItem->m_pPanel->setVisible(true); + + int itemWidth, itemHeight; + pItem->m_pPanel->getSize(itemWidth, itemHeight); + + // Don't change the item's height but change its width to fit the listbox. + pItem->m_pPanel->setBounds(0, curY, wide, itemHeight); + + curY += itemHeight; + + if (curY > tall) + { + bNeedScrollbar = true; + } + } + + ++curItem; + } + + m_ScrollBar.setVisible(bNeedScrollbar); + + repaint(); +} + +void CListBox::paintBackground() +{ +} + +void CListBox::SetScrollRange(int maxScroll) +{ + m_iScrollMax = maxScroll; + m_ScrollBar.setRange(0, maxScroll); + InternalLayout(); +} + +int CListBox::GetScrollMax() +{ + if (m_iScrollMax < 0) + { + return GetNumItems() - 1; + } + + return m_iScrollMax; +} + + diff --git a/hlsdk/game_shared/vgui_listbox.h b/hlsdk/game_shared/vgui_listbox.h new file mode 100644 index 00000000..1d5a6edc --- /dev/null +++ b/hlsdk/game_shared/vgui_listbox.h @@ -0,0 +1,117 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VOICE_LISTBOX_H +#define VOICE_LISTBOX_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + + +#include "VGUI_Panel.h" +#include "VGUI_IntChangeSignal.h" + +#include "VGUI_Slider2.h" +#include "VGUI_ScrollBar2.h" + + +namespace vgui +{ + +// Listbox class used by voice code. Based off of vgui's list panel but with some modifications: +// - This listbox clips its child items to its rectangle. +// - You can access things like the scrollbar and find out the item width. +// - The scrollbar scrolls one element at a time and the range is correct. + +// Note: this listbox does not provide notification when items are +class CListBox : public Panel +{ +public: + + CListBox(); + ~CListBox(); + + void Init(); + void Term(); + + // Add an item to the listbox. This automatically sets the item's parent to the listbox + // and resizes the item's width to fit within the listbox. + void AddItem(Panel *pPanel); + + // Get the number of items currently in the listbox. + int GetNumItems(); + + // Get the width that listbox items will be set to (this changes if you resize the listbox). + int GetItemWidth(); + + // Get/set the scrollbar position (position says which element is at the top of the listbox). + int GetScrollPos(); + void SetScrollPos(int pos); + + // sets the last item the listbox should scroll to + // scroll to GetNumItems() if not set + void SetScrollRange(int maxScroll); + + // returns the maximum value the scrollbar can scroll to + int GetScrollMax(); + +// vgui overrides. +public: + + virtual void setPos(int x, int y); + virtual void setSize(int wide,int tall); + virtual void setPixelScroll(int value); + virtual void paintBackground(); + + +protected: + + class LBItem + { + public: + Panel *m_pPanel; + LBItem *m_pPrev, *m_pNext; + }; + + class ListBoxSignal : public IntChangeSignal + { + public: + void intChanged(int value,Panel* panel) + { + m_pListBox->setPixelScroll(-value); + } + + vgui::CListBox *m_pListBox; + }; + + +protected: + + void InternalLayout(); + + +protected: + + // All the items.. + LBItem m_Items; + + Panel m_ItemsPanel; + + int m_ItemOffset; // where we're scrolled to + Slider2 m_Slider; + ScrollBar2 m_ScrollBar; + ListBoxSignal m_Signal; + + int m_iScrollMax; +}; + +} + + +#endif // VOICE_LISTBOX_H diff --git a/hlsdk/game_shared/vgui_loadtga.cpp b/hlsdk/game_shared/vgui_loadtga.cpp new file mode 100644 index 00000000..d7e1f5e9 --- /dev/null +++ b/hlsdk/game_shared/vgui_loadtga.cpp @@ -0,0 +1,93 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "../cl_dll/wrect.h" +#include "../cl_dll/cl_dll.h" +#include "vgui.h" +#include "vgui_loadtga.h" +#include "vgui_inputstream.h" + + +// ---------------------------------------------------------------------- // +// Helper class for loading tga files. +// ---------------------------------------------------------------------- // +class MemoryInputStream : public vgui::InputStream +{ +public: + MemoryInputStream() + { + m_pData = NULL; + m_DataLen = m_ReadPos = 0; + } + + virtual void seekStart(bool& success) {m_ReadPos=0; success=true;} + virtual void seekRelative(int count,bool& success) {m_ReadPos+=count; success=true;} + virtual void seekEnd(bool& success) {m_ReadPos=m_DataLen; success=true;} + virtual int getAvailable(bool& success) {success=false; return 0;} // This is what vgui does for files... + + virtual uchar readUChar(bool& success) + { + if(m_ReadPos>=0 && m_ReadPos +#include +#include +#include + +using namespace vgui; + + +namespace +{ +class FooDefaultScrollBarIntChangeSignal : public IntChangeSignal +{ +public: + FooDefaultScrollBarIntChangeSignal(ScrollBar2* scrollBar) + { + _scrollBar=scrollBar; + } + virtual void intChanged(int value,Panel* panel) + { + _scrollBar->fireIntChangeSignal(); + } +protected: + ScrollBar2* _scrollBar; +}; + +class FooDefaultButtonSignal : public ActionSignal +{ +public: + ScrollBar2* _scrollBar; + int _buttonIndex; +public: + FooDefaultButtonSignal(ScrollBar2* scrollBar,int buttonIndex) + { + _scrollBar=scrollBar; + _buttonIndex=buttonIndex; + } +public: + virtual void actionPerformed(Panel* panel) + { + _scrollBar->doButtonPressed(_buttonIndex); + } +}; + +} + +//----------------------------------------------------------------------------- +// Purpose: Default scrollbar button +// Draws in new scoreboard style +//----------------------------------------------------------------------------- +class ScrollBarButton : public Button +{ +private: + LineBorder m_Border; + +public: + ScrollBarButton(const char *filename, int x, int y, int wide, int tall) : m_Border(Color(60, 60, 60, 0)), Button("", x, y, wide, tall) + { + Image *image = vgui_LoadTGA(filename); + if (image) + { + image->setColor(Color(140, 140, 140, 0)); + setImage(image); + } + + setBorder(&m_Border); + } + + virtual void paintBackground() + { + int wide,tall; + getPaintSize(wide,tall); + + // fill the background + drawSetColor(0, 0, 0, 0); + drawFilledRect(0, 0, wide, tall); + } +}; + + + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +// Input : x - +// y - +// wide - +// tall - +// vertical - +//----------------------------------------------------------------------------- +ScrollBar2::ScrollBar2(int x,int y,int wide,int tall,bool vertical) : Panel(x,y,wide,tall) +{ + _slider=null; + _button[0]=null; + _button[1]=null; + + if(vertical) + { + setSlider(new Slider2(0,wide-1,wide,(tall-(wide*2))+2,true)); + setButton(new ScrollBarButton("gfx/vgui/arrowup.tga",0,0,wide,wide),0); + setButton(new ScrollBarButton("gfx/vgui/arrowdown.tga",0,tall-wide,wide,wide),1); + } + else + { + // untested code + setSlider(new Slider2(tall,0,wide-(tall*2),tall,false)); + setButton(new ScrollBarButton("gfx/vgui/320_arrowlt.tga",0,0,tall+1,tall+1),0); + setButton(new ScrollBarButton("gfx/vgui/320_arrowrt.tga",wide-tall,0,tall+1,tall+1),1); + } + + setPaintBorderEnabled(true); + setPaintBackgroundEnabled(true); + setPaintEnabled(true); + setButtonPressedScrollValue(15); + + validate(); + } + +void ScrollBar2::setSize(int wide,int tall) +{ + Panel::setSize(wide,tall); + + if(_slider==null) + { + return; + } + + if(_button[0]==null) + { + return; + } + + if(_button[1]==null) + { + return; + } + + getPaintSize(wide,tall); + + if(_slider->isVertical()) + { + _slider->setBounds(0,wide,wide,tall-(wide*2)); + //_slider->setBounds(0,0,wide,tall); + _button[0]->setBounds(0,0,wide,wide); + _button[1]->setBounds(0,tall-wide,wide,wide); + } + else + { + _slider->setBounds(tall,0,wide-(tall*2),tall); + //_slider->setBounds(0,0,wide,tall); + _button[0]->setBounds(0,0,tall,tall); + _button[1]->setBounds((wide-tall),0,tall,tall); + } +} + +void ScrollBar2::performLayout() +{ +} + +void ScrollBar2::setValue(int value) +{ + _slider->setValue(value); +} + +int ScrollBar2::getValue() +{ + return _slider->getValue(); +} + +void ScrollBar2::addIntChangeSignal(IntChangeSignal* s) +{ + _intChangeSignalDar.putElement(s); + _slider->addIntChangeSignal(new FooDefaultScrollBarIntChangeSignal(this)); +} + +void ScrollBar2::setRange(int min,int max) +{ + _slider->setRange(min,max); +} + +void ScrollBar2::fireIntChangeSignal() +{ + for(int i=0;i<_intChangeSignalDar.getCount();i++) + { + _intChangeSignalDar[i]->intChanged(_slider->getValue(),this); + } +} + +bool ScrollBar2::isVertical() +{ + return _slider->isVertical(); +} + +bool ScrollBar2::hasFullRange() +{ + return _slider->hasFullRange(); +} + +//LEAK: new and old slider will leak +void ScrollBar2::setButton(Button* button,int index) +{ + if(_button[index]!=null) + { + removeChild(_button[index]); + } + _button[index]=button; + addChild(_button[index]); + + _button[index]->addActionSignal(new FooDefaultButtonSignal(this,index)); + + validate(); + + //_button[index]->setVisible(false); +} + +Button* ScrollBar2::getButton(int index) +{ + return _button[index]; +} + +//LEAK: new and old slider will leak +void ScrollBar2::setSlider(Slider2 *slider) +{ + if(_slider!=null) + { + removeChild(_slider); + } + _slider=slider; + addChild(_slider); + + _slider->addIntChangeSignal(new FooDefaultScrollBarIntChangeSignal(this)); + + validate(); +} + +Slider2 *ScrollBar2::getSlider() +{ + return _slider; +} + +void ScrollBar2::doButtonPressed(int buttonIndex) +{ + if(buttonIndex==0) + { + _slider->setValue(_slider->getValue()-_buttonPressedScrollValue); + } + else + { + _slider->setValue(_slider->getValue()+_buttonPressedScrollValue); + } + +} + +void ScrollBar2::setButtonPressedScrollValue(int value) +{ + _buttonPressedScrollValue=value; +} + +void ScrollBar2::setRangeWindow(int rangeWindow) +{ + _slider->setRangeWindow(rangeWindow); +} + +void ScrollBar2::setRangeWindowEnabled(bool state) +{ + _slider->setRangeWindowEnabled(state); +} + +void ScrollBar2::validate() +{ + if(_slider!=null) + { + int buttonOffset=0; + + for(int i=0;i<2;i++) + { + if(_button[i]!=null) + { + if(_button[i]->isVisible()) + { + if(_slider->isVertical()) + { + buttonOffset+=_button[i]->getTall(); + } + else + { + buttonOffset+=_button[i]->getWide(); + } + } + } + } + + _slider->setButtonOffset(buttonOffset); + } + + int wide,tall; + getSize(wide,tall); + setSize(wide,tall); +} diff --git a/hlsdk/game_shared/vgui_scrollbar2.h b/hlsdk/game_shared/vgui_scrollbar2.h new file mode 100644 index 00000000..03791ea7 --- /dev/null +++ b/hlsdk/game_shared/vgui_scrollbar2.h @@ -0,0 +1,64 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_SCROLLBAR2_H +#define VGUI_SCROLLBAR2_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +#include +#include +#include + +namespace vgui +{ + +class IntChangeSignal; +class Button; +class Slider2; + +//----------------------------------------------------------------------------- +// Purpose: Hacked up version of the vgui scrollbar +//----------------------------------------------------------------------------- +class VGUIAPI ScrollBar2 : public Panel +{ +public: + ScrollBar2(int x,int y,int wide,int tall,bool vertical); +public: + virtual void setValue(int value); + virtual int getValue(); + virtual void addIntChangeSignal(IntChangeSignal* s); + virtual void setRange(int min,int max); + virtual void setRangeWindow(int rangeWindow); + virtual void setRangeWindowEnabled(bool state); + virtual void setSize(int wide,int tall); + virtual bool isVertical(); + virtual bool hasFullRange(); + virtual void setButton(Button *button,int index); + virtual Button* getButton(int index); + virtual void setSlider(Slider2 *slider); + virtual Slider2 *getSlider(); + virtual void doButtonPressed(int buttonIndex); + virtual void setButtonPressedScrollValue(int value); + virtual void validate(); +public: //bullshit public + virtual void fireIntChangeSignal(); +protected: + virtual void performLayout(); +protected: + Button* _button[2]; + Slider2 *_slider; + Dar _intChangeSignalDar; + int _buttonPressedScrollValue; +}; + +} + +#endif // VGUI_SCROLLBAR2_H diff --git a/hlsdk/game_shared/vgui_slider2.cpp b/hlsdk/game_shared/vgui_slider2.cpp new file mode 100644 index 00000000..843762ed --- /dev/null +++ b/hlsdk/game_shared/vgui_slider2.cpp @@ -0,0 +1,436 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: New version of the slider bar +// +// $NoKeywords: $ +//============================================================================= + +#include "VGUI_Slider2.h" + +#include +#include +#include +#include + +using namespace vgui; + +namespace +{ +class FooDefaultSliderSignal : public InputSignal +{ +private: + Slider2* _slider; +public: + FooDefaultSliderSignal(Slider2* slider) + { + _slider=slider; + } +public: + void cursorMoved(int x,int y,Panel* panel) + { + _slider->privateCursorMoved(x,y,panel); + } + void cursorEntered(Panel* panel){} + void cursorExited(Panel* panel){} + void mouseDoublePressed(MouseCode code,Panel* panel){} + void mousePressed(MouseCode code,Panel* panel) + { + _slider->privateMousePressed(code,panel); + } + void mouseReleased(MouseCode code,Panel* panel) + { + _slider->privateMouseReleased(code,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){} +}; +} + +Slider2::Slider2(int x,int y,int wide,int tall,bool vertical) : Panel(x,y,wide,tall) +{ + _vertical=vertical; + _dragging=false; + _value=0; + _range[0]=0; + _range[1]=299; + _rangeWindow=0; + _rangeWindowEnabled=false; + _buttonOffset=0; + recomputeNobPosFromValue(); + addInputSignal(new FooDefaultSliderSignal(this)); +} + +void Slider2::setSize(int wide,int tall) +{ + Panel::setSize(wide,tall); + recomputeNobPosFromValue(); +} + +bool Slider2::isVertical() +{ + return _vertical; +} + +void Slider2::setValue(int value) +{ + int oldValue=_value; + + if(value<_range[0]) + { + value=_range[0]; + } + + if(value>_range[1]) + { + value=_range[1]; + } + + _value=value; + recomputeNobPosFromValue(); + + if(_value!=oldValue) + { + fireIntChangeSignal(); + } +} + +int Slider2::getValue() +{ + return _value; +} + +void Slider2::recomputeNobPosFromValue() +{ + int wide,tall; + + getPaintSize(wide,tall); + + float fwide=(float)wide; + float ftall=(float)tall; + float frange=(float)(_range[1]-_range[0]); + float fvalue=(float)(_value-_range[0]); + float fper=fvalue/frange; + float frangewindow=(float)(_rangeWindow); + + if(frangewindow<0) + { + frangewindow=0; + } + + if(!_rangeWindowEnabled) + { + frangewindow=frange; + } + + if ( frangewindow > 0 ) + { + if(_vertical) + { + float fnobsize=frangewindow; + float freepixels = ftall - fnobsize; + + float firstpixel = freepixels * fper; + + _nobPos[0]=(int)( firstpixel ); + _nobPos[1]=(int)( firstpixel + fnobsize ); + + if(_nobPos[1]>tall) + { + _nobPos[0]=tall-((int)fnobsize); + _nobPos[1]=tall; + } + } + else + { + float fnobsize=frangewindow; + float freepixels = fwide - fnobsize; + + float firstpixel = freepixels * fper; + + _nobPos[0]=(int)( firstpixel ); + _nobPos[1]=(int)( firstpixel + fnobsize ); + + if(_nobPos[1]>wide) + { + _nobPos[0]=wide-((int)fnobsize); + _nobPos[1]=wide; + } + } + } + + repaint(); +} + +void Slider2::recomputeValueFromNobPos() +{ + int wide,tall; + getPaintSize(wide,tall); + + float fwide=(float)wide; + float ftall=(float)tall; + float frange=(float)(_range[1]-_range[0]); + float fvalue=(float)(_value-_range[0]); + float fnob=(float)_nobPos[0]; + float frangewindow=(float)(_rangeWindow); + + if(frangewindow<0) + { + frangewindow=0; + } + + if(!_rangeWindowEnabled) + { + frangewindow=frange; + } + + if ( frangewindow > 0 ) + { + if(_vertical) + { + float fnobsize=frangewindow; + fvalue=frange*(fnob/(ftall-fnobsize)); + } + else + { + float fnobsize=frangewindow; + fvalue=frange*(fnob/(fwide-fnobsize)); + } + } + // Take care of rounding issues. + _value=(int)(fvalue+_range[0]+0.5); + + // Clamp final result + _value = ( _value < _range[1] ) ? _value : _range[1]; +} + +bool Slider2::hasFullRange() +{ + int wide,tall; + getPaintSize(wide,tall); + + float fwide=(float)wide; + float ftall=(float)tall; + float frange=(float)(_range[1]-_range[0]); + float frangewindow=(float)(_rangeWindow); + + if(frangewindow<0) + { + frangewindow=0; + } + + if(!_rangeWindowEnabled) + { + frangewindow=frange; + } + + if ( frangewindow > 0 ) + { + if(_vertical) + { + if( frangewindow <= ( ftall + _buttonOffset ) ) + { + return true; + } + } + else + { + if( frangewindow <= ( fwide + _buttonOffset ) ) + { + return true; + } + } + } + + return false; +} + +void Slider2::addIntChangeSignal(IntChangeSignal* s) +{ + _intChangeSignalDar.putElement(s); +} + +void Slider2::fireIntChangeSignal() +{ + for(int i=0;i<_intChangeSignalDar.getCount();i++) + { + _intChangeSignalDar[i]->intChanged(getValue(),this); + } +} + +void Slider2::paintBackground() +{ + int wide,tall; + getPaintSize(wide,tall); + + if (_vertical) + { + // background behind slider + drawSetColor(40, 40, 40, 0); + drawFilledRect(0, 0, wide, tall); + + // slider front + drawSetColor(0, 0, 0, 0); + drawFilledRect(0,_nobPos[0],wide,_nobPos[1]); + + // slider border + drawSetColor(60, 60, 60, 0); + drawFilledRect(0,_nobPos[0],wide,_nobPos[0]+1); // top + drawFilledRect(0,_nobPos[1],wide,_nobPos[1]+1); // bottom + drawFilledRect(0,_nobPos[0]+1,1,_nobPos[1]); // left + drawFilledRect(wide-1,_nobPos[0]+1,wide,_nobPos[1]); // right + } + else + { + //!! doesn't work + + drawSetColor(Scheme::sc_secondary3); + drawFilledRect(0,0,wide,tall); + + drawSetColor(Scheme::sc_black); + drawOutlinedRect(0,0,wide,tall); + + drawSetColor(Scheme::sc_primary2); + drawFilledRect(_nobPos[0],0,_nobPos[1],tall); + + drawSetColor(Scheme::sc_black); + drawOutlinedRect(_nobPos[0],0,_nobPos[1],tall); + } +} + +void Slider2::setRange(int min,int max) +{ + if(maxmax) + { + min=max; + } + + _range[0]=min; + _range[1]=max; +} + +void Slider2::getRange(int& min,int& max) +{ + min=_range[0]; + max=_range[1]; +} + +void Slider2::privateCursorMoved(int x,int y,Panel* panel) +{ + if(!_dragging) + { + return; + } + + getApp()->getCursorPos(x,y); + screenToLocal(x,y); + + int wide,tall; + getPaintSize(wide,tall); + + if(_vertical) + { + _nobPos[0]=_nobDragStartPos[0]+(y-_dragStartPos[1]); + _nobPos[1]=_nobDragStartPos[1]+(y-_dragStartPos[1]); + + if(_nobPos[1]>tall) + { + _nobPos[0]=tall-(_nobPos[1]-_nobPos[0]); + _nobPos[1]=tall; + } + + if(_nobPos[0]<0) + { + _nobPos[1]=_nobPos[1]-_nobPos[0]; + _nobPos[0]=0; + } + } + else + { + _nobPos[0]=_nobDragStartPos[0]+(x-_dragStartPos[0]); + _nobPos[1]=_nobDragStartPos[1]+(x-_dragStartPos[0]); + + if(_nobPos[1]>wide) + { + _nobPos[0]=wide-(_nobPos[1]-_nobPos[0]); + _nobPos[1]=wide; + } + + if(_nobPos[0]<0) + { + _nobPos[1]=_nobPos[1]-_nobPos[0]; + _nobPos[0]=0; + } + } + + recomputeValueFromNobPos(); + repaint(); + fireIntChangeSignal(); +} + +void Slider2::privateMousePressed(MouseCode code,Panel* panel) +{ + int x,y; + getApp()->getCursorPos(x,y); + screenToLocal(x,y); + + if(_vertical) + { + if((y>=_nobPos[0])&&(y<_nobPos[1])) + { + _dragging=true; + getApp()->setMouseCapture(this); + _nobDragStartPos[0]=_nobPos[0]; + _nobDragStartPos[1]=_nobPos[1]; + _dragStartPos[0]=x; + _dragStartPos[1]=y; + } + } + else + { + if((x>=_nobPos[0])&&(x<_nobPos[1])) + { + _dragging=true; + getApp()->setMouseCapture(this); + _nobDragStartPos[0]=_nobPos[0]; + _nobDragStartPos[1]=_nobPos[1]; + _dragStartPos[0]=x; + _dragStartPos[1]=y; + } + } + +} + +void Slider2::privateMouseReleased(MouseCode code,Panel* panel) +{ + _dragging=false; + getApp()->setMouseCapture(null); +} + +void Slider2::getNobPos(int& min, int& max) +{ + min=_nobPos[0]; + max=_nobPos[1]; +} + +void Slider2::setRangeWindow(int rangeWindow) +{ + _rangeWindow=rangeWindow; +} + +void Slider2::setRangeWindowEnabled(bool state) +{ + _rangeWindowEnabled=state; +} + +void Slider2::setButtonOffset(int buttonOffset) +{ + _buttonOffset=buttonOffset; +} diff --git a/hlsdk/game_shared/vgui_slider2.h b/hlsdk/game_shared/vgui_slider2.h new file mode 100644 index 00000000..74dcf089 --- /dev/null +++ b/hlsdk/game_shared/vgui_slider2.h @@ -0,0 +1,69 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_SLIDER2_H +#define VGUI_SLIDER2_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +#include +#include +#include + +namespace vgui +{ + +enum MouseCode; +class IntChangeSignal; + +class VGUIAPI Slider2 : public Panel +{ +private: + bool _vertical; + bool _dragging; + int _nobPos[2]; + int _nobDragStartPos[2]; + int _dragStartPos[2]; + Dar _intChangeSignalDar; + int _range[2]; + int _value; + int _rangeWindow; + bool _rangeWindowEnabled; + int _buttonOffset; +public: + Slider2(int x,int y,int wide,int tall,bool vertical); +public: + virtual void setValue(int value); + virtual int getValue(); + virtual bool isVertical(); + virtual void addIntChangeSignal(IntChangeSignal* s); + virtual void setRange(int min,int max); + virtual void getRange(int& min,int& max); + virtual void setRangeWindow(int rangeWindow); + virtual void setRangeWindowEnabled(bool state); + virtual void setSize(int wide,int tall); + virtual void getNobPos(int& min, int& max); + virtual bool hasFullRange(); + virtual void setButtonOffset(int buttonOffset); +private: + virtual void recomputeNobPosFromValue(); + virtual void recomputeValueFromNobPos(); +public: //bullshit public + virtual void privateCursorMoved(int x,int y,Panel* panel); + virtual void privateMousePressed(MouseCode code,Panel* panel); + virtual void privateMouseReleased(MouseCode code,Panel* panel); +protected: + virtual void fireIntChangeSignal(); + virtual void paintBackground(); +}; + +} + +#endif // VGUI_SLIDER2_H diff --git a/hlsdk/game_shared/voice_banmgr.cpp b/hlsdk/game_shared/voice_banmgr.cpp new file mode 100644 index 00000000..8f7303dc --- /dev/null +++ b/hlsdk/game_shared/voice_banmgr.cpp @@ -0,0 +1,205 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include +#include "voice_banmgr.h" + +#if defined _MSC_VER && _MSC_VER >= 1400 + #ifndef _CRT_SECURE_NO_DEPRECATE + #define _CRT_SECURE_NO_DEPRECATE + #endif + + #pragma warning(disable: 4996) // deprecated functions +#endif + + +#define BANMGR_FILEVERSION 1 +char const *g_pBanMgrFilename = "voice_ban.dt"; + + + +// Hash a player ID to a byte. +unsigned char HashPlayerID(char const playerID[16]) +{ + unsigned char curHash = 0; + + for(int i=0; i < 16; i++) + curHash += (unsigned char)playerID[i]; + + return curHash; +} + + + +CVoiceBanMgr::CVoiceBanMgr() +{ + Clear(); +} + + +CVoiceBanMgr::~CVoiceBanMgr() +{ + Term(); +} + + +bool CVoiceBanMgr::Init(char const *pGameDir) +{ + Term(); + + char filename[512]; + _snprintf(filename, sizeof(filename), "%s/%s", pGameDir, g_pBanMgrFilename); + + // Load in the squelch file. + FILE *fp = fopen(filename, "rb"); + if(fp) + { + int version; + fread(&version, 1, sizeof(version), fp); + if(version == BANMGR_FILEVERSION) + { + fseek(fp, 0, SEEK_END); + int nIDs = (ftell(fp) - sizeof(version)) / 16; + fseek(fp, sizeof(version), SEEK_SET); + + for(int i=0; i < nIDs; i++) + { + char playerID[16]; + fread(playerID, 1, 16, fp); + AddBannedPlayer(playerID); + } + } + + fclose(fp); + } + + return true; +} + + +void CVoiceBanMgr::Term() +{ + // Free all the player structures. + for(int i=0; i < 256; i++) + { + BannedPlayer *pListHead = &m_PlayerHash[i]; + BannedPlayer *pNext; + for(BannedPlayer *pCur=pListHead->m_pNext; pCur != pListHead; pCur=pNext) + { + pNext = pCur->m_pNext; + delete pCur; + } + } + + Clear(); +} + + +void CVoiceBanMgr::SaveState(char const *pGameDir) +{ + // Save the file out. + char filename[512]; + _snprintf(filename, sizeof(filename), "%s/%s", pGameDir, g_pBanMgrFilename); + + FILE *fp = fopen(filename, "wb"); + if(fp) + { + int version = BANMGR_FILEVERSION; + fwrite(&version, 1, sizeof(version), fp); + + for(int i=0; i < 256; i++) + { + BannedPlayer *pListHead = &m_PlayerHash[i]; + for(BannedPlayer *pCur=pListHead->m_pNext; pCur != pListHead; pCur=pCur->m_pNext) + { + fwrite(pCur->m_PlayerID, 1, 16, fp); + } + } + + fclose(fp); + } +} + + +bool CVoiceBanMgr::GetPlayerBan(char const playerID[16]) +{ + return !!InternalFindPlayerSquelch(playerID); +} + + +void CVoiceBanMgr::SetPlayerBan(char const playerID[16], bool bSquelch) +{ + if(bSquelch) + { + // Is this guy already squelched? + if(GetPlayerBan(playerID)) + return; + + AddBannedPlayer(playerID); + } + else + { + BannedPlayer *pPlayer = InternalFindPlayerSquelch(playerID); + if(pPlayer) + { + pPlayer->m_pPrev->m_pNext = pPlayer->m_pNext; + pPlayer->m_pNext->m_pPrev = pPlayer->m_pPrev; + delete pPlayer; + } + } +} + + +void CVoiceBanMgr::ForEachBannedPlayer(void (*callback)(char id[16])) +{ + for(int i=0; i < 256; i++) + { + for(BannedPlayer *pCur=m_PlayerHash[i].m_pNext; pCur != &m_PlayerHash[i]; pCur=pCur->m_pNext) + { + callback(pCur->m_PlayerID); + } + } +} + + +void CVoiceBanMgr::Clear() +{ + // Tie off the hash table entries. + for(int i=0; i < 256; i++) + m_PlayerHash[i].m_pNext = m_PlayerHash[i].m_pPrev = &m_PlayerHash[i]; +} + + +CVoiceBanMgr::BannedPlayer* CVoiceBanMgr::InternalFindPlayerSquelch(char const playerID[16]) +{ + int index = HashPlayerID(playerID); + BannedPlayer *pListHead = &m_PlayerHash[index]; + for(BannedPlayer *pCur=pListHead->m_pNext; pCur != pListHead; pCur=pCur->m_pNext) + { + if(memcmp(playerID, pCur->m_PlayerID, 16) == 0) + return pCur; + } + + return NULL; +} + + +CVoiceBanMgr::BannedPlayer* CVoiceBanMgr::AddBannedPlayer(char const playerID[16]) +{ + BannedPlayer *pNew = new BannedPlayer; + if(!pNew) + return NULL; + + int index = HashPlayerID(playerID); + memcpy(pNew->m_PlayerID, playerID, 16); + pNew->m_pNext = &m_PlayerHash[index]; + pNew->m_pPrev = m_PlayerHash[index].m_pPrev; + pNew->m_pPrev->m_pNext = pNew->m_pNext->m_pPrev = pNew; + return pNew; +} + diff --git a/hlsdk/game_shared/voice_banmgr.h b/hlsdk/game_shared/voice_banmgr.h new file mode 100644 index 00000000..48461679 --- /dev/null +++ b/hlsdk/game_shared/voice_banmgr.h @@ -0,0 +1,59 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VOICE_BANMGR_H +#define VOICE_BANMGR_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + + +// This class manages the (persistent) list of squelched players. +class CVoiceBanMgr +{ +public: + + CVoiceBanMgr(); + ~CVoiceBanMgr(); + + // Init loads the list of squelched players from disk. + bool Init(char const *pGameDir); + void Term(); + + // Saves the state into voice_squelch.dt. + void SaveState(char const *pGameDir); + + bool GetPlayerBan(char const playerID[16]); + void SetPlayerBan(char const playerID[16], bool bSquelch); + + // Call your callback for each banned player. + void ForEachBannedPlayer(void (*callback)(char id[16])); + + +protected: + + class BannedPlayer + { + public: + char m_PlayerID[16]; + BannedPlayer *m_pPrev, *m_pNext; + }; + + void Clear(); + BannedPlayer* InternalFindPlayerSquelch(char const playerID[16]); + BannedPlayer* AddBannedPlayer(char const playerID[16]); + + +protected: + + BannedPlayer m_PlayerHash[256]; +}; + + +#endif // VOICE_BANMGR_H diff --git a/hlsdk/game_shared/voice_common.h b/hlsdk/game_shared/voice_common.h new file mode 100644 index 00000000..c83effd2 --- /dev/null +++ b/hlsdk/game_shared/voice_common.h @@ -0,0 +1,26 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VOICE_COMMON_H +#define VOICE_COMMON_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + + +#include "bitvec.h" + + +#define VOICE_MAX_PLAYERS 32 // (todo: this should just be set to MAX_CLIENTS). +#define VOICE_MAX_PLAYERS_DW ((VOICE_MAX_PLAYERS / 32) + !!(VOICE_MAX_PLAYERS & 31)) + +typedef CBitVec CPlayerBitVec; + + +#endif // VOICE_COMMON_H diff --git a/hlsdk/game_shared/voice_gamemgr.cpp b/hlsdk/game_shared/voice_gamemgr.cpp new file mode 100644 index 00000000..83071e70 --- /dev/null +++ b/hlsdk/game_shared/voice_gamemgr.cpp @@ -0,0 +1,274 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "voice_gamemgr.h" +#include +#include +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" + + + +#define UPDATE_INTERVAL 0.3 + + +// These are stored off as CVoiceGameMgr is created and deleted. +CPlayerBitVec g_PlayerModEnable; // Set to 1 for each player if the player wants to use voice in this mod. + // (If it's zero, then the server reports that the game rules are saying the + // player can't hear anyone). + +CPlayerBitVec g_BanMasks[VOICE_MAX_PLAYERS]; // Tells which players don't want to hear each other. + // These are indexed as clients and each bit represents a client + // (so player entity is bit+1). + +CPlayerBitVec g_SentGameRulesMasks[VOICE_MAX_PLAYERS]; // These store the masks we last sent to each client so we can determine if +CPlayerBitVec g_SentBanMasks[VOICE_MAX_PLAYERS]; // we need to resend them. +CPlayerBitVec g_bWantModEnable; + +cvar_t voice_serverdebug = {"voice_serverdebug", "0"}; + +// Set game rules to allow all clients to talk to each other. +// Muted players still can't talk to each other. +cvar_t sv_alltalk = {"sv_alltalk", "0"}; + +// ------------------------------------------------------------------------ // +// Static helpers. +// ------------------------------------------------------------------------ // + +// Find a player with a case-insensitive name search. +/*static CBasePlayer* FindPlayerByName(const char *pTestName) +{ + for(int i=1; i <= gpGlobals->maxClients; i++) + { + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex(i); + if(pEdict) + { + CBaseEntity *pEnt = CBaseEntity::Instance(pEdict); + if(pEnt && pEnt->IsPlayer()) + { + const char *pNetName = STRING(pEnt->pev->netname); + if(stricmp(pNetName, pTestName) == 0) + { + return (CBasePlayer*)pEnt; + } + } + } + } + + return NULL; +}*/ + +static void VoiceServerDebug( char const *pFmt, ... ) +{ + char msg[4096]; + va_list marker; + + if( !voice_serverdebug.value ) + return; + + va_start( marker, pFmt ); + _vsnprintf( msg, sizeof(msg), pFmt, marker ); + va_end( marker ); + + ALERT( at_console, "%s", msg ); +} + + + +// ------------------------------------------------------------------------ // +// CVoiceGameMgr. +// ------------------------------------------------------------------------ // + +CVoiceGameMgr::CVoiceGameMgr() +{ + m_UpdateInterval = 0; + m_nMaxPlayers = 0; +} + + +CVoiceGameMgr::~CVoiceGameMgr() +{ +} + + +bool CVoiceGameMgr::Init( + IVoiceGameMgrHelper *pHelper, + int maxClients) +{ + m_pHelper = pHelper; + m_nMaxPlayers = VOICE_MAX_PLAYERS < maxClients ? VOICE_MAX_PLAYERS : maxClients; + g_engfuncs.pfnPrecacheModel("sprites/voiceicon.spr"); + + m_msgPlayerVoiceMask = REG_USER_MSG( "VoiceMask", VOICE_MAX_PLAYERS_DW*4 * 2 ); + m_msgRequestState = REG_USER_MSG( "ReqState", 0 ); + + // register voice_serverdebug if it hasn't been registered already + if ( !CVAR_GET_POINTER( "voice_serverdebug" ) ) + CVAR_REGISTER( &voice_serverdebug ); + + if( !CVAR_GET_POINTER( "sv_alltalk" ) ) + CVAR_REGISTER( &sv_alltalk ); + + return true; +} + + +void CVoiceGameMgr::SetHelper(IVoiceGameMgrHelper *pHelper) +{ + m_pHelper = pHelper; +} + + +void CVoiceGameMgr::Update(double frametime) +{ + // Only update periodically. + m_UpdateInterval += frametime; + if(m_UpdateInterval < UPDATE_INTERVAL) + return; + + UpdateMasks(); +} + + +void CVoiceGameMgr::ClientConnected(edict_t *pEdict) +{ + int index = ENTINDEX(pEdict) - 1; + + // Clear out everything we use for deltas on this guy. + g_bWantModEnable[index] = true; + g_SentGameRulesMasks[index].Init(0); + g_SentBanMasks[index].Init(0); +} + +// Called to determine if the Receiver has muted (blocked) the Sender +// Returns true if the receiver has blocked the sender +bool CVoiceGameMgr::PlayerHasBlockedPlayer(CBasePlayer *pReceiver, CBasePlayer *pSender) +{ + int iReceiverIndex, iSenderIndex; + + if ( !pReceiver || !pSender ) + return false; + + iReceiverIndex = pReceiver->entindex() - 1; + iSenderIndex = pSender->entindex() - 1; + + if ( iReceiverIndex < 0 || iReceiverIndex >= m_nMaxPlayers || iSenderIndex < 0 || iSenderIndex >= m_nMaxPlayers ) + return false; + + return ( g_BanMasks[iReceiverIndex][iSenderIndex] ? true : false ); +} + +bool CVoiceGameMgr::ClientCommand(CBasePlayer *pPlayer, const char *cmd) +{ + int playerClientIndex = pPlayer->entindex() - 1; + if(playerClientIndex < 0 || playerClientIndex >= m_nMaxPlayers) + { + VoiceServerDebug( "CVoiceGameMgr::ClientCommand: cmd %s from invalid client (%d)\n", cmd, playerClientIndex ); + return true; + } + + bool bBan = stricmp(cmd, "vban") == 0; + if(bBan && CMD_ARGC() >= 2) + { + for(int i=1; i < CMD_ARGC(); i++) + { + unsigned long mask = 0; + sscanf(CMD_ARGV(i), "%lx", &mask); + + if(i <= VOICE_MAX_PLAYERS_DW) + { + VoiceServerDebug( "CVoiceGameMgr::ClientCommand: vban (0x%x) from %d\n", mask, playerClientIndex ); + g_BanMasks[playerClientIndex].SetDWord(i-1, mask); + } + else + { + VoiceServerDebug( "CVoiceGameMgr::ClientCommand: invalid index (%d)\n", i ); + } + } + + // Force it to update the masks now. + //UpdateMasks(); + return true; + } + else if(stricmp(cmd, "VModEnable") == 0 && CMD_ARGC() >= 2) + { + VoiceServerDebug( "CVoiceGameMgr::ClientCommand: VModEnable (%d)\n", !!atoi(CMD_ARGV(1)) ); + g_PlayerModEnable[playerClientIndex] = !!atoi(CMD_ARGV(1)); + g_bWantModEnable[playerClientIndex] = false; + //UpdateMasks(); + return true; + } + else + { + return false; + } +} + + +void CVoiceGameMgr::UpdateMasks() +{ + m_UpdateInterval = 0; + + bool bAllTalk = !!g_engfuncs.pfnCVarGetFloat( "sv_alltalk" ); + + for(int iClient=0; iClient < m_nMaxPlayers; iClient++) + { + CBaseEntity *pEnt = UTIL_PlayerByIndex(iClient+1); + if(!pEnt || !pEnt->IsPlayer()) + continue; + + // Request the state of their "VModEnable" cvar. + if(g_bWantModEnable[iClient]) + { + MESSAGE_BEGIN(MSG_ONE, m_msgRequestState, NULL, pEnt->pev); + MESSAGE_END(); + } + + CBasePlayer *pPlayer = (CBasePlayer*)pEnt; + + CPlayerBitVec gameRulesMask; + if( g_PlayerModEnable[iClient] ) + { + // Build a mask of who they can hear based on the game rules. + for(int iOtherClient=0; iOtherClient < m_nMaxPlayers; iOtherClient++) + { + CBaseEntity *pEnt = UTIL_PlayerByIndex(iOtherClient+1); + if(pEnt && pEnt->IsPlayer() && + (bAllTalk || m_pHelper->CanPlayerHearPlayer(pPlayer, (CBasePlayer*)pEnt)) ) + { + gameRulesMask[iOtherClient] = true; + } + } + } + + // If this is different from what the client has, send an update. + if(gameRulesMask != g_SentGameRulesMasks[iClient] || + g_BanMasks[iClient] != g_SentBanMasks[iClient]) + { + g_SentGameRulesMasks[iClient] = gameRulesMask; + g_SentBanMasks[iClient] = g_BanMasks[iClient]; + + MESSAGE_BEGIN(MSG_ONE, m_msgPlayerVoiceMask, NULL, pPlayer->pev); + int dw; + for(dw=0; dw < VOICE_MAX_PLAYERS_DW; dw++) + { + WRITE_LONG(gameRulesMask.GetDWord(dw)); + WRITE_LONG(g_BanMasks[iClient].GetDWord(dw)); + } + MESSAGE_END(); + } + + // Tell the engine. + for(int iOtherClient=0; iOtherClient < m_nMaxPlayers; iOtherClient++) + { + bool bCanHear = gameRulesMask[iOtherClient] && !g_BanMasks[iClient][iOtherClient]; + g_engfuncs.pfnVoice_SetClientListening(iClient+1, iOtherClient+1, bCanHear); + } + } +} diff --git a/hlsdk/game_shared/voice_gamemgr.h b/hlsdk/game_shared/voice_gamemgr.h new file mode 100644 index 00000000..7a0ee1fa --- /dev/null +++ b/hlsdk/game_shared/voice_gamemgr.h @@ -0,0 +1,83 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VOICE_GAMEMGR_H +#define VOICE_GAMEMGR_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + + +#include "voice_common.h" + + +class CGameRules; +class CBasePlayer; + + +class IVoiceGameMgrHelper +{ +public: + virtual ~IVoiceGameMgrHelper() {} + + // Called each frame to determine which players are allowed to hear each other. This overrides + // whatever squelch settings players have. + virtual bool CanPlayerHearPlayer(CBasePlayer *pListener, CBasePlayer *pTalker) = 0; +}; + + +// CVoiceGameMgr manages which clients can hear which other clients. +class CVoiceGameMgr +{ +public: + CVoiceGameMgr(); + virtual ~CVoiceGameMgr(); + + bool Init( + IVoiceGameMgrHelper *m_pHelper, + int maxClients + ); + + void SetHelper(IVoiceGameMgrHelper *pHelper); + + // Updates which players can hear which other players. + // If gameplay mode is DM, then only players within the PVS can hear each other. + // If gameplay mode is teamplay, then only players on the same team can hear each other. + // Player masks are always applied. + void Update(double frametime); + + // Called when a new client connects (unsquelches its entity for everyone). + void ClientConnected(struct edict_s *pEdict); + + // Called on ClientCommand. Checks for the squelch and unsquelch commands. + // Returns true if it handled the command. + bool ClientCommand(CBasePlayer *pPlayer, const char *cmd); + + // Called to determine if the Receiver has muted (blocked) the Sender + // Returns true if the receiver has blocked the sender + bool PlayerHasBlockedPlayer(CBasePlayer *pReceiver, CBasePlayer *pSender); + + +private: + + // Force it to update the client masks. + void UpdateMasks(); + + +private: + int m_msgPlayerVoiceMask; + int m_msgRequestState; + + IVoiceGameMgrHelper *m_pHelper; + int m_nMaxPlayers; + double m_UpdateInterval; // How long since the last update. +}; + + +#endif // VOICE_GAMEMGR_H diff --git a/hlsdk/game_shared/voice_status.cpp b/hlsdk/game_shared/voice_status.cpp new file mode 100644 index 00000000..e5378d00 --- /dev/null +++ b/hlsdk/game_shared/voice_status.cpp @@ -0,0 +1,874 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $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" +#elif defined( RICOCHET_BUILD ) + #include "../ricochet/cl_dll/hud.h" + #include "../ricochet/cl_dll/cl_util.h" +#else + #include "../cl_dll/hud.h" + #include "../cl_dll/cl_util.h" +#endif + +#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" +#elif defined( RICOCHET_BUILD ) + #include "../ricochet/cl_dll/parsemsg.h" + #include "../ricochet/cl_dll/hud_servers.h" + #include "../ricochet/cl_dll/demo.h" +#else + #include "../cl_dll/parsemsg.h" + #include "../cl_dll/hud_servers.h" + #include "../cl_dll/demo.h" +#endif + +#include "demo_api.h" +#include "voice_status.h" +#include "r_efx.h" +#include "entity_types.h" +#include "VGUI_ActionSignal.h" +#include "VGUI_Scheme.h" +#include "VGUI_TextImage.h" +#include "vgui_loadtga.h" +#include "vgui_helpers.h" +#include "vgui_mousecode.h" + + + +using namespace vgui; + + +extern int cam_thirdperson; + + +#define VOICE_MODEL_INTERVAL 0.3 +#define SCOREBOARD_BLINK_FREQUENCY 0.3 // How often to blink the scoreboard icons. +#define SQUELCHOSCILLATE_PER_SECOND 2.0f + + +extern BitmapTGA *LoadTGA( const char* pImageName ); + + + +// ---------------------------------------------------------------------- // +// The voice manager for the client. +// ---------------------------------------------------------------------- // +CVoiceStatus g_VoiceStatus; + +CVoiceStatus* GetClientVoiceMgr() +{ + return &g_VoiceStatus; +} + + + +// ---------------------------------------------------------------------- // +// CVoiceStatus. +// ---------------------------------------------------------------------- // + +static CVoiceStatus *g_pInternalVoiceStatus = NULL; + +int __MsgFunc_VoiceMask(const char *pszName, int iSize, void *pbuf) +{ + if(g_pInternalVoiceStatus) + g_pInternalVoiceStatus->HandleVoiceMaskMsg(iSize, pbuf); + + return 1; +} + +int __MsgFunc_ReqState(const char *pszName, int iSize, void *pbuf) +{ + if(g_pInternalVoiceStatus) + g_pInternalVoiceStatus->HandleReqStateMsg(iSize, pbuf); + + return 1; +} + + +int g_BannedPlayerPrintCount; +void ForEachBannedPlayer(char id[16]) +{ + char str[256]; + sprintf(str, "Ban %d: %2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x\n", + g_BannedPlayerPrintCount++, + id[0], id[1], id[2], id[3], + id[4], id[5], id[6], id[7], + id[8], id[9], id[10], id[11], + id[12], id[13], id[14], id[15] + ); + strupr(str); + gEngfuncs.pfnConsolePrint(str); +} + + +void ShowBannedCallback() +{ + if(g_pInternalVoiceStatus) + { + g_BannedPlayerPrintCount = 0; + gEngfuncs.pfnConsolePrint("------- BANNED PLAYERS -------\n"); + g_pInternalVoiceStatus->m_BanMgr.ForEachBannedPlayer(ForEachBannedPlayer); + gEngfuncs.pfnConsolePrint("------------------------------\n"); + } +} + + +// ---------------------------------------------------------------------- // +// CVoiceStatus. +// ---------------------------------------------------------------------- // + +CVoiceStatus::CVoiceStatus() +{ + m_bBanMgrInitialized = false; + m_LastUpdateServerState = 0; + + m_pSpeakerLabelIcon = NULL; + m_pScoreboardNeverSpoken = NULL; + m_pScoreboardNotSpeaking = NULL; + m_pScoreboardSpeaking = NULL; + m_pScoreboardSpeaking2 = NULL; + m_pScoreboardSquelch = NULL; + m_pScoreboardBanned = NULL; + + m_pLocalBitmap = NULL; + m_pAckBitmap = NULL; + + m_bTalking = m_bServerAcked = false; + + memset(m_pBanButtons, 0, sizeof(m_pBanButtons)); + + m_bServerModEnable = -1; + + m_pchGameDir = NULL; +} + + +CVoiceStatus::~CVoiceStatus() +{ + g_pInternalVoiceStatus = NULL; + + for(int i=0; i < MAX_VOICE_SPEAKERS; i++) + { + delete m_Labels[i].m_pLabel; + m_Labels[i].m_pLabel = NULL; + + delete m_Labels[i].m_pIcon; + m_Labels[i].m_pIcon = NULL; + + delete m_Labels[i].m_pBackground; + m_Labels[i].m_pBackground = NULL; + } + + delete m_pLocalLabel; + m_pLocalLabel = NULL; + + FreeBitmaps(); + + if(m_pchGameDir) + { + if(m_bBanMgrInitialized) + { + m_BanMgr.SaveState(m_pchGameDir); + } + + free(m_pchGameDir); + } +} + + +int CVoiceStatus::Init( + IVoiceStatusHelper *pHelper, + Panel **pParentPanel) +{ + // Setup the voice_modenable cvar. + gEngfuncs.pfnRegisterVariable("voice_modenable", "1", FCVAR_ARCHIVE); + + gEngfuncs.pfnRegisterVariable("voice_clientdebug", "0", 0); + + gEngfuncs.pfnAddCommand("voice_showbanned", ShowBannedCallback); + + if(gEngfuncs.pfnGetGameDirectory()) + { + m_BanMgr.Init(gEngfuncs.pfnGetGameDirectory()); + m_bBanMgrInitialized = true; + } + + assert(!g_pInternalVoiceStatus); + g_pInternalVoiceStatus = this; + + m_BlinkTimer = 0; + m_VoiceHeadModel = NULL; + memset(m_Labels, 0, sizeof(m_Labels)); + + for(int i=0; i < MAX_VOICE_SPEAKERS; i++) + { + CVoiceLabel *pLabel = &m_Labels[i]; + + pLabel->m_pBackground = new Label(""); + + if(pLabel->m_pLabel = new Label("")) + { + pLabel->m_pLabel->setVisible( true ); + pLabel->m_pLabel->setFont( Scheme::sf_primary2 ); + pLabel->m_pLabel->setTextAlignment( Label::a_east ); + pLabel->m_pLabel->setContentAlignment( Label::a_east ); + pLabel->m_pLabel->setParent( pLabel->m_pBackground ); + } + + if( pLabel->m_pIcon = new ImagePanel( NULL ) ) + { + pLabel->m_pIcon->setVisible( true ); + pLabel->m_pIcon->setParent( pLabel->m_pBackground ); + } + + pLabel->m_clientindex = -1; + } + + m_pLocalLabel = new ImagePanel(NULL); + + m_bInSquelchMode = false; + + m_pHelper = pHelper; + m_pParentPanel = pParentPanel; + gHUD.AddHudElem(this); + m_iFlags = HUD_ACTIVE; + HOOK_MESSAGE(VoiceMask); + HOOK_MESSAGE(ReqState); + + // Cache the game directory for use when we shut down + const char *pchGameDirT = gEngfuncs.pfnGetGameDirectory(); + m_pchGameDir = (char *)malloc(strlen(pchGameDirT) + 1); + strcpy(m_pchGameDir, pchGameDirT); + + return 1; +} + + +int CVoiceStatus::VidInit() +{ + FreeBitmaps(); + + + if( m_pLocalBitmap = vgui_LoadTGA("gfx/vgui/icntlk_pl.tga") ) + { + m_pLocalBitmap->setColor(Color(255,255,255,135)); + } + + if( m_pAckBitmap = vgui_LoadTGA("gfx/vgui/icntlk_sv.tga") ) + { + m_pAckBitmap->setColor(Color(255,255,255,135)); // Give just a tiny bit of translucency so software draws correctly. + } + + m_pLocalLabel->setImage( m_pLocalBitmap ); + m_pLocalLabel->setVisible( false ); + + + if( m_pSpeakerLabelIcon = vgui_LoadTGANoInvertAlpha("gfx/vgui/speaker4.tga" ) ) + m_pSpeakerLabelIcon->setColor( Color(255,255,255,1) ); // Give just a tiny bit of translucency so software draws correctly. + + if (m_pScoreboardNeverSpoken = vgui_LoadTGANoInvertAlpha("gfx/vgui/640_speaker1.tga")) + m_pScoreboardNeverSpoken->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + if(m_pScoreboardNotSpeaking = vgui_LoadTGANoInvertAlpha("gfx/vgui/640_speaker2.tga")) + m_pScoreboardNotSpeaking->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + if(m_pScoreboardSpeaking = vgui_LoadTGANoInvertAlpha("gfx/vgui/640_speaker3.tga")) + m_pScoreboardSpeaking->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + if(m_pScoreboardSpeaking2 = vgui_LoadTGANoInvertAlpha("gfx/vgui/640_speaker4.tga")) + m_pScoreboardSpeaking2->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + if(m_pScoreboardSquelch = vgui_LoadTGA("gfx/vgui/icntlk_squelch.tga")) + m_pScoreboardSquelch->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + if(m_pScoreboardBanned = vgui_LoadTGA("gfx/vgui/640_voiceblocked.tga")) + m_pScoreboardBanned->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + // Figure out the voice head model height. + m_VoiceHeadModelHeight = 45; + char *pFile = (char *)gEngfuncs.COM_LoadFile("scripts/voicemodel.txt", 5, NULL); + if(pFile) + { + char token[4096]; + gEngfuncs.COM_ParseFile(pFile, token); + if(token[0] >= '0' && token[0] <= '9') + { + m_VoiceHeadModelHeight = (float)atof(token); + } + + gEngfuncs.COM_FreeFile(pFile); + } + + m_VoiceHeadModel = gEngfuncs.pfnSPR_Load("sprites/voiceicon.spr"); + return TRUE; +} + + +void CVoiceStatus::Frame(double frametime) +{ + // check server banned players once per second + if(gEngfuncs.GetClientTime() - m_LastUpdateServerState > 1) + { + UpdateServerState(false); + } + + m_BlinkTimer += frametime; + + // Update speaker labels. + if( m_pHelper->CanShowSpeakerLabels() ) + { + for( int i=0; i < MAX_VOICE_SPEAKERS; i++ ) + m_Labels[i].m_pBackground->setVisible( m_Labels[i].m_clientindex != -1 ); + } + else + { + for( int i=0; i < MAX_VOICE_SPEAKERS; i++ ) + m_Labels[i].m_pBackground->setVisible( false ); + } + + for(int i=0; i < VOICE_MAX_PLAYERS; i++) + UpdateBanButton(i); +} + + +void CVoiceStatus::CreateEntities() +{ + if(!m_VoiceHeadModel) + return; + + cl_entity_t *localPlayer = gEngfuncs.GetLocalPlayer(); + + int iOutModel = 0; + for(int i=0; i < VOICE_MAX_PLAYERS; i++) + { + if(!m_VoicePlayers[i]) + continue; + + cl_entity_s *pClient = gEngfuncs.GetEntityByIndex(i+1); + + // Don't show an icon if the player is not in our PVS. + if(!pClient || pClient->curstate.messagenum < localPlayer->curstate.messagenum) + continue; + + // Don't show an icon for dead or spectating players (ie: invisible entities). + if(pClient->curstate.effects & EF_NODRAW) + continue; + + // Don't show an icon for the local player unless we're in thirdperson mode. + if(pClient == localPlayer && !cam_thirdperson) + continue; + + cl_entity_s *pEnt = &m_VoiceHeadModels[iOutModel]; + ++iOutModel; + + memset(pEnt, 0, sizeof(*pEnt)); + + pEnt->curstate.rendermode = kRenderTransAdd; + pEnt->curstate.renderamt = 255; + pEnt->baseline.renderamt = 255; + pEnt->curstate.renderfx = kRenderFxNoDissipation; + pEnt->curstate.framerate = 1; + pEnt->curstate.frame = 0; + pEnt->model = (struct model_s*)gEngfuncs.GetSpritePointer(m_VoiceHeadModel); + pEnt->angles[0] = pEnt->angles[1] = pEnt->angles[2] = 0; + pEnt->curstate.scale = 0.5f; + + pEnt->origin[0] = pEnt->origin[1] = 0; + pEnt->origin[2] = 45; + + VectorAdd(pEnt->origin, pClient->origin, pEnt->origin); + + // Tell the engine. + gEngfuncs.CL_CreateVisibleEntity(ET_NORMAL, pEnt); + } +} + + +void CVoiceStatus::UpdateSpeakerStatus(int entindex, qboolean bTalking) +{ + if(!*m_pParentPanel) + return; + + if( gEngfuncs.pfnGetCvarFloat("voice_clientdebug") ) + { + char msg[256]; + _snprintf( msg, sizeof(msg), "CVoiceStatus::UpdateSpeakerStatus: ent %d talking = %d\n", entindex, bTalking ); + gEngfuncs.pfnConsolePrint( msg ); + } + + // Is it the local player talking? + if( entindex == -1 ) + { + m_bTalking = !!bTalking; + if( bTalking ) + { + // Enable voice for them automatically if they try to talk. + gEngfuncs.pfnClientCmd( "voice_modenable 1" ); + } + } + else if( entindex == -2 ) + { + m_bServerAcked = !!bTalking; + } + else if(entindex >= 0 && entindex <= VOICE_MAX_PLAYERS) + { + int iClient = entindex - 1; + if(iClient < 0) + return; + + CVoiceLabel *pLabel = FindVoiceLabel(iClient); + if(bTalking) + { + m_VoicePlayers[iClient] = true; + m_VoiceEnabledPlayers[iClient] = true; + + // If we don't have a label for this guy yet, then create one. + if(!pLabel) + { + if(pLabel = GetFreeVoiceLabel()) + { + // Get the name from the engine. + hud_player_info_t info; + memset(&info, 0, sizeof(info)); + GetPlayerInfo(entindex, &info); + + char paddedName[512]; + _snprintf(paddedName, sizeof(paddedName), "%s ", info.name); + + int color[3]; + m_pHelper->GetPlayerTextColor( entindex, color ); + + if( pLabel->m_pBackground ) + { + pLabel->m_pBackground->setBgColor( color[0], color[1], color[2], 135 ); + 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->setBgColor( 0, 0, 0, 255 ); + pLabel->m_pLabel->setText( paddedName ); + } + + pLabel->m_clientindex = iClient; + } + } + } + else + { + m_VoicePlayers[iClient] = false; + + // If we have a label for this guy, kill it. + if(pLabel) + { + pLabel->m_pBackground->setVisible(false); + pLabel->m_clientindex = -1; + } + } + } + + RepositionLabels(); +} + + +void CVoiceStatus::UpdateServerState(bool bForce) +{ + // Can't do anything when we're not in a level. + char const *pLevelName = gEngfuncs.pfnGetLevelName(); + if( pLevelName[0] == 0 ) + { + if( gEngfuncs.pfnGetCvarFloat("voice_clientdebug") ) + { + gEngfuncs.pfnConsolePrint( "CVoiceStatus::UpdateServerState: pLevelName[0]==0\n" ); + } + + return; + } + + int bCVarModEnable = !!gEngfuncs.pfnGetCvarFloat("voice_modenable"); + if(bForce || m_bServerModEnable != bCVarModEnable) + { + m_bServerModEnable = bCVarModEnable; + + char str[256]; + _snprintf(str, sizeof(str), "VModEnable %d", m_bServerModEnable); + ServerCmd(str); + + if(gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + char msg[256]; + sprintf(msg, "CVoiceStatus::UpdateServerState: Sending '%s'\n", str); + gEngfuncs.pfnConsolePrint(msg); + } + } + + char str[2048]; + sprintf(str, "vban"); + bool bChange = false; + + for(unsigned long dw=0; dw < VOICE_MAX_PLAYERS_DW; dw++) + { + unsigned long serverBanMask = 0; + unsigned long banMask = 0; + for(unsigned long i=0; i < 32; i++) + { + char playerID[16]; + if(!gEngfuncs.GetPlayerUniqueID(i+1, playerID)) + continue; + + if(m_BanMgr.GetPlayerBan(playerID)) + banMask |= 1 << i; + + if(m_ServerBannedPlayers[dw*32 + i]) + serverBanMask |= 1 << i; + } + + if(serverBanMask != banMask) + bChange = true; + + // Ok, the server needs to be updated. + char numStr[512]; + sprintf(numStr, " %x", banMask); + strcat(str, numStr); + } + + if(bChange || bForce) + { + if(gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + char msg[256]; + sprintf(msg, "CVoiceStatus::UpdateServerState: Sending '%s'\n", str); + gEngfuncs.pfnConsolePrint(msg); + } + + gEngfuncs.pfnServerCmdUnreliable(str); // Tell the server.. + } + else + { + if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + gEngfuncs.pfnConsolePrint( "CVoiceStatus::UpdateServerState: no change\n" ); + } + } + + m_LastUpdateServerState = gEngfuncs.GetClientTime(); +} + +void CVoiceStatus::UpdateSpeakerImage(Label *pLabel, int iPlayer) +{ + m_pBanButtons[iPlayer-1] = pLabel; + UpdateBanButton(iPlayer-1); +} + +void CVoiceStatus::UpdateBanButton(int iClient) +{ + Label *pPanel = m_pBanButtons[iClient]; + + if (!pPanel) + return; + + char playerID[16]; + extern bool HACK_GetPlayerUniqueID( int iPlayer, char playerID[16] ); + if(!HACK_GetPlayerUniqueID(iClient+1, playerID)) + return; + + // Figure out if it's blinking or not. + bool bBlink = fmod(m_BlinkTimer, SCOREBOARD_BLINK_FREQUENCY*2) < SCOREBOARD_BLINK_FREQUENCY; + bool bTalking = !!m_VoicePlayers[iClient]; + bool bBanned = m_BanMgr.GetPlayerBan(playerID); + bool bNeverSpoken = !m_VoiceEnabledPlayers[iClient]; + + // Get the appropriate image to display on the panel. + if (bBanned) + { + pPanel->setImage(m_pScoreboardBanned); + } + else if (bTalking) + { + if (bBlink) + { + pPanel->setImage(m_pScoreboardSpeaking2); + } + else + { + pPanel->setImage(m_pScoreboardSpeaking); + } + pPanel->setFgColor(255, 170, 0, 1); + } + else if (bNeverSpoken) + { + pPanel->setImage(m_pScoreboardNeverSpoken); + pPanel->setFgColor(100, 100, 100, 1); + } + else + { + pPanel->setImage(m_pScoreboardNotSpeaking); + } +} + + +void CVoiceStatus::HandleVoiceMaskMsg(int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + unsigned long dw; + for(dw=0; dw < VOICE_MAX_PLAYERS_DW; dw++) + { + m_AudiblePlayers.SetDWord(dw, (unsigned long)READ_LONG()); + m_ServerBannedPlayers.SetDWord(dw, (unsigned long)READ_LONG()); + + if(gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + char str[256]; + gEngfuncs.pfnConsolePrint("CVoiceStatus::HandleVoiceMaskMsg\n"); + + sprintf(str, " - m_AudiblePlayers[%d] = %lu\n", dw, m_AudiblePlayers.GetDWord(dw)); + gEngfuncs.pfnConsolePrint(str); + + sprintf(str, " - m_ServerBannedPlayers[%d] = %lu\n", dw, m_ServerBannedPlayers.GetDWord(dw)); + gEngfuncs.pfnConsolePrint(str); + } + } + + m_bServerModEnable = READ_BYTE(); +} + +void CVoiceStatus::HandleReqStateMsg(int iSize, void *pbuf) +{ + if(gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + gEngfuncs.pfnConsolePrint("CVoiceStatus::HandleReqStateMsg\n"); + } + + UpdateServerState(true); +} + +void CVoiceStatus::StartSquelchMode() +{ + if(m_bInSquelchMode) + return; + + m_bInSquelchMode = true; + m_pHelper->UpdateCursorState(); +} + +void CVoiceStatus::StopSquelchMode() +{ + m_bInSquelchMode = false; + m_pHelper->UpdateCursorState(); +} + +bool CVoiceStatus::IsInSquelchMode() +{ + return m_bInSquelchMode; +} + +CVoiceLabel* CVoiceStatus::FindVoiceLabel(int clientindex) +{ + for(int i=0; i < MAX_VOICE_SPEAKERS; i++) + { + if(m_Labels[i].m_clientindex == clientindex) + return &m_Labels[i]; + } + + return NULL; +} + + +CVoiceLabel* CVoiceStatus::GetFreeVoiceLabel() +{ + return FindVoiceLabel(-1); +} + + +void CVoiceStatus::RepositionLabels() +{ + // find starting position to draw from, along right-hand side of screen + int y = ScreenHeight / 2; + + int iconWide = 8, iconTall = 8; + if( m_pSpeakerLabelIcon ) + { + m_pSpeakerLabelIcon->getSize( iconWide, iconTall ); + } + + // Reposition active labels. + for(int i = 0; i < MAX_VOICE_SPEAKERS; i++) + { + CVoiceLabel *pLabel = &m_Labels[i]; + + if( pLabel->m_clientindex == -1 || !pLabel->m_pLabel ) + { + if( pLabel->m_pBackground ) + pLabel->m_pBackground->setVisible( false ); + + continue; + } + + int textWide, textTall; + pLabel->m_pLabel->getContentSize( textWide, textTall ); + + // Don't let it stretch too far across their screen. + if( textWide > (ScreenWidth*2)/3 ) + textWide = (ScreenWidth*2)/3; + + // Setup the background label to fit everything in. + int border = 2; + int bgWide = textWide + iconWide + border*3; + int bgTall = max( textTall, iconTall ) + border*2; + pLabel->m_pBackground->setBounds( ScreenWidth - bgWide - 8, y, bgWide, bgTall ); + + // Put the text at the left. + pLabel->m_pLabel->setBounds( border, (bgTall - textTall) / 2, textWide, textTall ); + + // Put the icon at the right. + int iconLeft = border + textWide + border; + int iconTop = (bgTall - iconTall) / 2; + if( pLabel->m_pIcon ) + { + pLabel->m_pIcon->setImage( m_pSpeakerLabelIcon ); + pLabel->m_pIcon->setBounds( iconLeft, iconTop, iconWide, iconTall ); + } + + y += bgTall + 2; + } + + if( m_pLocalBitmap && m_pAckBitmap && m_pLocalLabel && (m_bTalking || m_bServerAcked) ) + { + m_pLocalLabel->setParent(*m_pParentPanel); + m_pLocalLabel->setVisible( true ); + + if( m_bServerAcked && !!gEngfuncs.pfnGetCvarFloat("voice_clientdebug") ) + m_pLocalLabel->setImage( m_pAckBitmap ); + else + m_pLocalLabel->setImage( m_pLocalBitmap ); + + int sizeX, sizeY; + m_pLocalBitmap->getSize(sizeX, sizeY); + + int local_xPos = ScreenWidth - sizeX - 10; + int local_yPos = m_pHelper->GetAckIconHeight() - sizeY; + + m_pLocalLabel->setPos( local_xPos, local_yPos ); + } + else + { + m_pLocalLabel->setVisible( false ); + } +} + + +void CVoiceStatus::FreeBitmaps() +{ + // Delete all the images we have loaded. + delete m_pLocalBitmap; + m_pLocalBitmap = NULL; + + delete m_pAckBitmap; + m_pAckBitmap = NULL; + + delete m_pSpeakerLabelIcon; + m_pSpeakerLabelIcon = NULL; + + delete m_pScoreboardNeverSpoken; + m_pScoreboardNeverSpoken = NULL; + + delete m_pScoreboardNotSpeaking; + m_pScoreboardNotSpeaking = NULL; + + delete m_pScoreboardSpeaking; + m_pScoreboardSpeaking = NULL; + + delete m_pScoreboardSpeaking2; + m_pScoreboardSpeaking2 = NULL; + + delete m_pScoreboardSquelch; + m_pScoreboardSquelch = NULL; + + delete m_pScoreboardBanned; + m_pScoreboardBanned = NULL; + + // Clear references to the images in panels. + for(int i=0; i < VOICE_MAX_PLAYERS; i++) + { + if (m_pBanButtons[i]) + { + m_pBanButtons[i]->setImage(NULL); + } + } + + if(m_pLocalLabel) + m_pLocalLabel->setImage(NULL); +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if the target client has been banned +// Input : playerID - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CVoiceStatus::IsPlayerBlocked(int iPlayer) +{ + char playerID[16]; + if (!gEngfuncs.GetPlayerUniqueID(iPlayer, playerID)) + return false; + + return m_BanMgr.GetPlayerBan(playerID); +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if the player can't hear the other client due to game rules (eg. the other team) +// Input : playerID - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CVoiceStatus::IsPlayerAudible(int iPlayer) +{ + return !!m_AudiblePlayers[iPlayer-1]; +} + +//----------------------------------------------------------------------------- +// Purpose: blocks/unblocks the target client from being heard +// Input : playerID - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +void CVoiceStatus::SetPlayerBlockedState(int iPlayer, bool blocked) +{ + if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + gEngfuncs.pfnConsolePrint( "CVoiceStatus::SetPlayerBlockedState part 1\n" ); + } + + char playerID[16]; + if (!gEngfuncs.GetPlayerUniqueID(iPlayer, playerID)) + return; + + if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + gEngfuncs.pfnConsolePrint( "CVoiceStatus::SetPlayerBlockedState part 2\n" ); + } + + // Squelch or (try to) unsquelch this player. + if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + char str[256]; + sprintf(str, "CVoiceStatus::SetPlayerBlockedState: setting player %d ban to %d\n", iPlayer, !m_BanMgr.GetPlayerBan(playerID)); + gEngfuncs.pfnConsolePrint(str); + } + + m_BanMgr.SetPlayerBan( playerID, blocked ); + UpdateServerState(false); +} diff --git a/hlsdk/game_shared/voice_status.h b/hlsdk/game_shared/voice_status.h new file mode 100644 index 00000000..113b328c --- /dev/null +++ b/hlsdk/game_shared/voice_status.h @@ -0,0 +1,232 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VOICE_STATUS_H +#define VOICE_STATUS_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + + +#include "VGUI_Label.h" +#include "VGUI_LineBorder.h" +#include "VGUI_ImagePanel.h" +#include "VGUI_BitmapTGA.h" +#include "VGUI_InputSignal.h" +#include "VGUI_Button.h" +#include "voice_common.h" +#include "cl_entity.h" +#include "voice_banmgr.h" +#include "vgui_checkbutton2.h" +#include "vgui_defaultinputsignal.h" + + +class CVoiceStatus; + + +class CVoiceLabel +{ +public: + vgui::Label *m_pLabel; + vgui::Label *m_pBackground; + vgui::ImagePanel *m_pIcon; // Voice icon next to player name. + int m_clientindex; // Client index of the speaker. -1 if this label isn't being used. +}; + + +// This is provided by each mod to access data that may not be the same across mods. +class IVoiceStatusHelper +{ +public: + virtual ~IVoiceStatusHelper() {} + + // Get RGB color for voice status text about this player. + virtual void GetPlayerTextColor(int entindex, int color[3]) = 0; + + // Force it to update the cursor state. + virtual void UpdateCursorState() = 0; + + // Return the height above the bottom that the voice ack icons should be drawn at. + virtual int GetAckIconHeight() = 0; + + // Return true if the voice manager is allowed to show speaker labels + // (mods usually return false when the scoreboard is up). + virtual bool CanShowSpeakerLabels() = 0; +}; + +//----------------------------------------------------------------------------- +// Purpose: Holds a color for the shared image +//----------------------------------------------------------------------------- +class VoiceImagePanel : public vgui::ImagePanel +{ + virtual void paintBackground() + { + if (_image!=null) + { + vgui::Color col; + getFgColor(col); + _image->setColor(col); + _image->doPaint(this); + } + } +}; + + +class CVoiceStatus : public CHudBase, public vgui::CDefaultInputSignal +{ +public: + CVoiceStatus(); + virtual ~CVoiceStatus(); + +// CHudBase overrides. +public: + + // Initialize the cl_dll's voice manager. + virtual int Init( + IVoiceStatusHelper *m_pHelper, + vgui::Panel **pParentPanel); + + // ackPosition is the bottom position of where CVoiceStatus will draw the voice acknowledgement labels. + virtual int VidInit(); + + +public: + + // Call from HUD_Frame each frame. + void Frame(double frametime); + + // Called when a player starts or stops talking. + // entindex is -1 to represent the local client talking (before the data comes back from the server). + // When the server acknowledges that the local client is talking, then entindex will be gEngfuncs.GetLocalPlayer(). + // entindex is -2 to represent the local client's voice being acked by the server. + void UpdateSpeakerStatus(int entindex, qboolean bTalking); + + // sets the correct image in the label for the player + void UpdateSpeakerImage(vgui::Label *pLabel, int iPlayer); + + // Call from the HUD_CreateEntities function so it can add sprites above player heads. + void CreateEntities(); + + // Called when the server registers a change to who this client can hear. + void HandleVoiceMaskMsg(int iSize, void *pbuf); + + // The server sends this message initially to tell the client to send their state. + void HandleReqStateMsg(int iSize, void *pbuf); + + +// Squelch mode functions. +public: + + // When you enter squelch mode, pass in + void StartSquelchMode(); + void StopSquelchMode(); + bool IsInSquelchMode(); + + // returns true if the target client has been banned + // playerIndex is of range 1..maxplayers + bool IsPlayerBlocked(int iPlayerIndex); + + // returns false if the player can't hear the other client due to game rules (eg. the other team) + bool IsPlayerAudible(int iPlayerIndex); + + // blocks the target client from being heard + void SetPlayerBlockedState(int iPlayerIndex, bool blocked); + +public: + + CVoiceLabel* FindVoiceLabel(int clientindex); // Find a CVoiceLabel representing the specified speaker. + // Returns NULL if none. + // entindex can be -1 if you want a currently-unused voice label. + CVoiceLabel* GetFreeVoiceLabel(); // Get an unused voice label. Returns NULL if none. + + void RepositionLabels(); + + void FreeBitmaps(); + + void UpdateServerState(bool bForce); + + // Update the button artwork to reflect the client's current state. + void UpdateBanButton(int iClient); + + +public: + + enum {MAX_VOICE_SPEAKERS=7}; + + float m_LastUpdateServerState; // Last time we called this function. + int m_bServerModEnable; // What we've sent to the server about our "voice_modenable" cvar. + + vgui::Panel **m_pParentPanel; + CPlayerBitVec m_VoicePlayers; // Who is currently talking. Indexed by client index. + + // This is the gamerules-defined list of players that you can hear. It is based on what teams people are on + // and is totally separate from the ban list. Indexed by client index. + CPlayerBitVec m_AudiblePlayers; + + // Players who have spoken at least once in the game so far + CPlayerBitVec m_VoiceEnabledPlayers; + + // This is who the server THINKS we have banned (it can become incorrect when a new player arrives on the server). + // It is checked periodically, and the server is told to squelch or unsquelch the appropriate players. + CPlayerBitVec m_ServerBannedPlayers; + + cl_entity_s m_VoiceHeadModels[VOICE_MAX_PLAYERS]; // These aren't necessarily in the order of players. They are just + // a place for it to put data in during CreateEntities. + + IVoiceStatusHelper *m_pHelper; // Each mod provides an implementation of this. + + + // Scoreboard icons. + double m_BlinkTimer; // Blink scoreboard icons.. + vgui::BitmapTGA *m_pScoreboardNeverSpoken; + vgui::BitmapTGA *m_pScoreboardNotSpeaking; + vgui::BitmapTGA *m_pScoreboardSpeaking; + vgui::BitmapTGA *m_pScoreboardSpeaking2; + vgui::BitmapTGA *m_pScoreboardSquelch; + vgui::BitmapTGA *m_pScoreboardBanned; + + vgui::Label *m_pBanButtons[VOICE_MAX_PLAYERS]; // scoreboard buttons. + + // Squelch mode stuff. + bool m_bInSquelchMode; + + HSPRITE m_VoiceHeadModel; // Voice head model (goes above players who are speaking). + float m_VoiceHeadModelHeight; // Height above their head to place the model. + + vgui::Image *m_pSpeakerLabelIcon; // Icon next to speaker labels. + + // Lower-right icons telling when the local player is talking.. + vgui::BitmapTGA *m_pLocalBitmap; // Represents the local client talking. + vgui::BitmapTGA *m_pAckBitmap; // Represents the server ack'ing the client talking. + vgui::ImagePanel *m_pLocalLabel; // Represents the local client talking. + + bool m_bTalking; // Set to true when the client thinks it's talking. + bool m_bServerAcked; // Set to true when the server knows the client is talking. + +public: + + CVoiceBanMgr m_BanMgr; // Tracks which users we have squelched and don't want to hear. + +public: + + bool m_bBanMgrInitialized; + + // Labels telling who is speaking. + CVoiceLabel m_Labels[MAX_VOICE_SPEAKERS]; + + // Cache the game directory for use when we shut down + char * m_pchGameDir; +}; + + +// Get the (global) voice manager. +CVoiceStatus* GetClientVoiceMgr(); + + +#endif // VOICE_STATUS_H diff --git a/hlsdk/game_shared/voice_vgui_tweakdlg.cpp b/hlsdk/game_shared/voice_vgui_tweakdlg.cpp new file mode 100644 index 00000000..b5cde708 --- /dev/null +++ b/hlsdk/game_shared/voice_vgui_tweakdlg.cpp @@ -0,0 +1,289 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "../cl_dll/hud.h" +#include "../cl_dll/cl_util.h" +#include "../cl_dll/vgui_teamfortressviewport.h" + + +#include "vgui_actionsignal.h" +#include "voice_vgui_tweakdlg.h" +#include "voice_vgui_tweakdlg.h" +#include "vgui_panel.h" +#include "vgui_scrollbar.h" +#include "vgui_slider.h" +#include "ivoicetweak.h" +#include "vgui_button.h" +#include "vgui_checkbutton2.h" +#include "vgui_helpers.h" + + +#define ITEM_BORDER 40 // Border between text and scrollbars on left and right. +#define VOICETWEAK_TRANSPARENCY 150 + + +class TweakScroller +{ +public: + TweakScroller(); + void Init(Panel *pParent, char *pText, int yPos); + + // Get/set value. Values are 0-1. + float GetValue(); + void SetValue(float val); + +public: + Label m_Label; + ScrollBar m_Scroll; + Slider m_Slider; +}; + + +class CVoiceVGUITweakDlg : public CMenuPanel, public ICheckButton2Handler +{ +typedef CMenuPanel BaseClass; + +public: + CVoiceVGUITweakDlg(); + ~CVoiceVGUITweakDlg(); + +// CMenuPanel overrides. +public: + virtual void Open(); + virtual void Close(); + + +// ICheckButton2Handler overrides. +public: + + virtual void StateChanged(CCheckButton2 *pButton); + + + +// Panel overrides. +public: + virtual void paintBackground(); + + +private: + + int m_DlgWidth; + int m_DlgHeight; + + Label m_Label; + + IVoiceTweak *m_pVoiceTweak; // Engine voice tweak API. + + TweakScroller m_MicVolume; + TweakScroller m_SpeakerVolume; + + CCheckButton2 m_VoiceModEnable; + + Button m_Button_OK; +}; + + + +bool g_bTweakDlgOpen = false; + +bool IsTweakDlgOpen() +{ + return g_bTweakDlgOpen; +} + + + +// ------------------------------------------------------------------------ // +// Global functions. +// ------------------------------------------------------------------------ // + +static CVoiceVGUITweakDlg g_VoiceTweakDlg; +CMenuPanel* GetVoiceTweakDlg() +{ + return &g_VoiceTweakDlg; +} + + +class CVoiceTweakOKButton : public ActionSignal +{ +public: + virtual void actionPerformed(Panel *pPanel) + { + gViewPort->HideVGUIMenu(); + } +}; +CVoiceTweakOKButton g_OKButtonSignal; + + + +// ------------------------------------------------------------------------ // +// TweakScroller +// ------------------------------------------------------------------------ // + +TweakScroller::TweakScroller() : + m_Label(""), + m_Scroll(0,0,0,0,false), + m_Slider(0,0,10,10,false) +{ +} + + +void TweakScroller::Init(Panel *pParent, char *pText, int yPos) +{ + int parentWidth, parentHeight; + pParent->getSize(parentWidth, parentHeight); + + // Setup the volume scroll bar. + m_Label.setParent(pParent); + m_Label.setFont(Scheme::sf_primary1); + m_Label.setContentAlignment(vgui::Label::a_northwest); + m_Label.setBgColor(0, 0, 0, 255); + m_Label.setFgColor(255,255,255,0); + m_Label.setPos(ITEM_BORDER, yPos); + m_Label.setSize(parentWidth/2-ITEM_BORDER, 20); + m_Label.setText(pText); + m_Label.setVisible(true); + + m_Slider.setRangeWindow(10); + m_Slider.setRangeWindowEnabled(true); + + m_Scroll.setPos(parentWidth/2+ITEM_BORDER, yPos); + m_Scroll.setSize(parentWidth/2-ITEM_BORDER*2, 20); + m_Scroll.setSlider(&m_Slider); + m_Scroll.setParent(pParent); + m_Scroll.setRange(0, 100); + m_Scroll.setFgColor(255,255,255,0); + m_Scroll.setBgColor(255,255,255,0); +} + + +float TweakScroller::GetValue() +{ + return m_Scroll.getValue() / 100.0f; +} + + +void TweakScroller::SetValue(float val) +{ + m_Scroll.setValue((int)(val * 100.0f)); +} + + +// ------------------------------------------------------------------------ // +// CVoiceVGUITweakDlg implementation. +// ------------------------------------------------------------------------ // + +CVoiceVGUITweakDlg::CVoiceVGUITweakDlg() + : CMenuPanel(VOICETWEAK_TRANSPARENCY, false, 0, 0, 0, 0), + m_Button_OK("",0,0), + m_Label("") +{ + m_pVoiceTweak = NULL; + m_Button_OK.addActionSignal(&g_OKButtonSignal); + m_Label.setBgColor(255,255,255,200); +} + + +CVoiceVGUITweakDlg::~CVoiceVGUITweakDlg() +{ +} + + +void CVoiceVGUITweakDlg::Open() +{ + if(g_bTweakDlgOpen) + return; + + g_bTweakDlgOpen = true; + + m_DlgWidth = ScreenWidth; + m_DlgHeight = ScreenHeight; + + m_pVoiceTweak = gEngfuncs.pVoiceTweak; + + // Tell the engine to start voice tweak mode (pipe voice output right to speakers). + m_pVoiceTweak->StartVoiceTweakMode(); + + // Set our size. + setPos((ScreenWidth - m_DlgWidth) / 2, (ScreenHeight - m_DlgHeight) / 2); + setSize(m_DlgWidth, m_DlgHeight); + + int curY = ITEM_BORDER; + m_MicVolume.Init(this, gHUD.m_TextMessage.BufferedLocaliseTextString("#Mic_Volume"), curY); + m_MicVolume.SetValue(m_pVoiceTweak->GetControlFloat(MicrophoneVolume)); + curY = PanelBottom(&m_MicVolume.m_Label); + + m_SpeakerVolume.Init(this, gHUD.m_TextMessage.BufferedLocaliseTextString("#Speaker_Volume"), curY); + m_SpeakerVolume.SetValue(m_pVoiceTweak->GetControlFloat(OtherSpeakerScale)); + curY = PanelBottom(&m_SpeakerVolume.m_Label); + + m_VoiceModEnable.setParent(this); + m_VoiceModEnable.SetImages("gfx/vgui/checked.tga", "gfx/vgui/unchecked.tga"); + m_VoiceModEnable.SetText("Enable Voice In This Mod"); + m_VoiceModEnable.setPos(ITEM_BORDER, curY); + m_VoiceModEnable.SetCheckboxLeft(false); + m_VoiceModEnable.SetChecked(!!gEngfuncs.pfnGetCvarFloat("voice_modenable")); + m_VoiceModEnable.SetHandler(this); + + // Setup the OK button. + int buttonWidth, buttonHeight; + m_Button_OK.setText(gHUD.m_TextMessage.BufferedLocaliseTextString("#Menu_OK")); + m_Button_OK.getSize(buttonWidth, buttonHeight); + m_Button_OK.setPos((m_DlgWidth - buttonWidth) / 2, m_DlgHeight - buttonHeight - 3); + m_Button_OK.setParent(this); + + // Put the label on the top. + m_Label.setBgColor(0, 0, 0, 255); + m_Label.setFgColor(255,255,255,0); + m_Label.setText(gHUD.m_TextMessage.BufferedLocaliseTextString("#Voice_Properties")); + int labelWidth, labelHeight; + m_Label.getSize(labelWidth, labelHeight); + m_Label.setPos((m_DlgWidth - labelWidth) / 2, 5); + m_Label.setParent(this); + + BaseClass::Open(); +} + + +void CVoiceVGUITweakDlg::Close() +{ + m_pVoiceTweak->EndVoiceTweakMode(); + g_bTweakDlgOpen = false; + + BaseClass::Close(); +} + + +void CVoiceVGUITweakDlg::paintBackground() +{ + BaseClass::paintBackground(); + + // Draw our border. + int w,h; + getSize(w,h); + + drawSetColor(128,128,128,1); + drawOutlinedRect(0, 0, w, h); + + float volume = m_MicVolume.GetValue(); + m_pVoiceTweak->SetControlFloat(MicrophoneVolume, volume); + + m_pVoiceTweak->SetControlFloat(OtherSpeakerScale, m_SpeakerVolume.GetValue()); +} + + +void CVoiceVGUITweakDlg::StateChanged(CCheckButton2 *pButton) +{ + if(pButton == &m_VoiceModEnable) + { + if(pButton->IsChecked()) + gEngfuncs.pfnClientCmd("voice_modenable 1"); + else + gEngfuncs.pfnClientCmd("voice_modenable 0"); + } +} + diff --git a/hlsdk/game_shared/voice_vgui_tweakdlg.h b/hlsdk/game_shared/voice_vgui_tweakdlg.h new file mode 100644 index 00000000..1f1cb12e --- /dev/null +++ b/hlsdk/game_shared/voice_vgui_tweakdlg.h @@ -0,0 +1,27 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VOICE_VGUI_TWEAKDLG_H +#define VOICE_VGUI_TWEAKDLG_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + + +class CMenuPanel; + + +// Returns true if the tweak dialog is currently up. +bool IsTweakDlgOpen(); + +// Returns a global instance of the tweak dialog. +CMenuPanel* GetVoiceTweakDlg(); + + +#endif // VOICE_VGUI_TWEAKDLG_H diff --git a/hlsdk/network/Delta.txt b/hlsdk/network/Delta.txt new file mode 100644 index 00000000..71c43f78 --- /dev/null +++ b/hlsdk/network/Delta.txt @@ -0,0 +1,2 @@ +Place delta.lst in the valve\ folder ( or a customized copy can be placed in your game directory's folder ) + diff --git a/hlsdk/network/delta.lst b/hlsdk/network/delta.lst new file mode 100644 index 00000000..ebb0e127 --- /dev/null +++ b/hlsdk/network/delta.lst @@ -0,0 +1,265 @@ +// structure name +// none == no conditional encode routine +// gamedll routine_name : before transmitting data, invoke the named function from the game .dll to reset fields as needed +// clientdll routine_name : same as above, except the routine is called via the client.dll + +clientdata_t none +{ + DEFINE_DELTA( flTimeStepSound, DT_INTEGER, 10, 1.0 ), + DEFINE_DELTA( origin[0], DT_SIGNED | DT_FLOAT, 21, 128.0 ), + DEFINE_DELTA( origin[1], DT_SIGNED | DT_FLOAT, 21, 128.0 ), + DEFINE_DELTA( velocity[0], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( velocity[1], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + + DEFINE_DELTA( m_flNextAttack, DT_FLOAT | DT_SIGNED, 22, 1000.0 ), + + DEFINE_DELTA( origin[2], DT_SIGNED | DT_FLOAT, 21, 128.0 ), + DEFINE_DELTA( velocity[2], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + + DEFINE_DELTA( ammo_nails, DT_SIGNED | DT_INTEGER, 10, 1.0 ), + DEFINE_DELTA( ammo_shells, DT_SIGNED | DT_INTEGER, 10, 1.0 ), + DEFINE_DELTA( ammo_cells, DT_SIGNED | DT_INTEGER, 10, 1.0 ), + DEFINE_DELTA( ammo_rockets, DT_SIGNED | DT_INTEGER, 10, 1.0 ), + + DEFINE_DELTA( m_iId, DT_INTEGER, 5, 1.0 ), + + DEFINE_DELTA( punchangle[2], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( flags, DT_INTEGER, 32, 1.0 ), // Cut to 3 bits? + DEFINE_DELTA( weaponanim, DT_INTEGER, 8, 1.0 ), + DEFINE_DELTA( health, DT_SIGNED | DT_FLOAT, 10, 1.0 ), // Cut # of bits? + DEFINE_DELTA( maxspeed, DT_FLOAT, 16, 10.0 ), + DEFINE_DELTA( flDuckTime, DT_INTEGER, 10, 1.0 ), + DEFINE_DELTA( view_ofs[2], DT_SIGNED | DT_FLOAT, 10, 4.0 ), + DEFINE_DELTA( punchangle[0], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( punchangle[1], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( viewmodel, DT_INTEGER, 10, 1.0 ), + DEFINE_DELTA( weapons, DT_INTEGER, 32, 1.0 ), + + DEFINE_DELTA( pushmsec, DT_INTEGER, 11, 1.0 ), + DEFINE_DELTA( deadflag, DT_INTEGER, 3, 1.0 ), + DEFINE_DELTA( fov, DT_FLOAT, 8, 1.0 ), + DEFINE_DELTA( physinfo, DT_STRING, 1, 1.0 ), + DEFINE_DELTA( bInDuck, DT_INTEGER, 1, 1.0 ), + DEFINE_DELTA( flSwimTime, DT_INTEGER, 10, 1.0 ), + DEFINE_DELTA( waterjumptime, DT_INTEGER, 15, 1.0 ), + DEFINE_DELTA( waterlevel, DT_INTEGER, 2, 1.0 ), + + DEFINE_DELTA( vuser1[0], DT_SIGNED | DT_FLOAT, 10, 1.0 ), + DEFINE_DELTA( vuser1[1], DT_SIGNED | DT_FLOAT, 10, 1.0 ), + DEFINE_DELTA( vuser1[2], DT_SIGNED | DT_FLOAT, 10, 1.0 ), + + DEFINE_DELTA( vuser2[0], DT_SIGNED | DT_FLOAT, 10, 1.0 ), + DEFINE_DELTA( vuser2[1], DT_SIGNED | DT_FLOAT, 10, 1.0 ), + DEFINE_DELTA( vuser2[2], DT_SIGNED | DT_FLOAT, 10, 1.0 ), + + DEFINE_DELTA( vuser3[0], DT_SIGNED | DT_FLOAT, 10, 1.0 ), + DEFINE_DELTA( vuser3[1], DT_SIGNED | DT_FLOAT, 10, 1.0 ), + DEFINE_DELTA( vuser3[2], DT_SIGNED | DT_FLOAT, 10, 1.0 ), + + DEFINE_DELTA( vuser4[0], DT_SIGNED | DT_FLOAT, 10, 1.0 ), + DEFINE_DELTA( vuser4[1], DT_SIGNED | DT_FLOAT, 10, 1.0 ), + DEFINE_DELTA( vuser4[2], DT_SIGNED | DT_FLOAT, 10, 1.0 ), + + DEFINE_DELTA( fuser1, DT_SIGNED | DT_FLOAT, 22, 128.0 ), + DEFINE_DELTA( fuser2, DT_SIGNED | DT_FLOAT, 10, 128.0 ), + DEFINE_DELTA( fuser3, DT_SIGNED | DT_FLOAT, 10, 128.0 ), + DEFINE_DELTA( fuser4, DT_SIGNED | DT_FLOAT, 2, 128.0 ) +} + +entity_state_t gamedll Entity_Encode +{ + DEFINE_DELTA( animtime, DT_TIMEWINDOW_8, 8, 1.0 ), + DEFINE_DELTA( frame, DT_FLOAT, 10, 4.0 ), + DEFINE_DELTA( origin[0], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( angles[0], DT_ANGLE, 16, 1.0 ), + DEFINE_DELTA( angles[1], DT_ANGLE, 16, 1.0 ), + DEFINE_DELTA( origin[1], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( origin[2], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( sequence, DT_INTEGER, 8, 1.0 ), + DEFINE_DELTA( modelindex, DT_INTEGER, 10, 1.0 ), + DEFINE_DELTA( movetype, DT_INTEGER, 4, 1.0 ), + DEFINE_DELTA( solid, DT_SHORT, 3, 1.0 ), + DEFINE_DELTA( mins[0], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( mins[1], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( mins[2], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( maxs[0], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( maxs[1], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( maxs[2], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + + DEFINE_DELTA( endpos[0], DT_SIGNED | DT_FLOAT, 13, 1.0 ), + DEFINE_DELTA( endpos[1], DT_SIGNED | DT_FLOAT, 13, 1.0 ), + DEFINE_DELTA( endpos[2], DT_SIGNED | DT_FLOAT, 13, 1.0 ), + DEFINE_DELTA( startpos[0], DT_SIGNED | DT_FLOAT, 13, 1.0 ), + DEFINE_DELTA( startpos[1], DT_SIGNED | DT_FLOAT, 13, 1.0 ), + DEFINE_DELTA( startpos[2], DT_SIGNED | DT_FLOAT, 13, 1.0 ), + DEFINE_DELTA( impacttime, DT_TIMEWINDOW_BIG, 13, 100.0 ), + DEFINE_DELTA( starttime, DT_TIMEWINDOW_BIG, 13, 100.0 ), + + DEFINE_DELTA( weaponmodel, DT_INTEGER, 10, 1.0 ), + DEFINE_DELTA( owner, DT_INTEGER, 5, 1.0 ), + DEFINE_DELTA( effects, DT_INTEGER, 8, 1.0 ), + DEFINE_DELTA( eflags, DT_INTEGER, 1, 1.0 ), + DEFINE_DELTA( angles[2], DT_ANGLE, 16, 1.0 ), + DEFINE_DELTA( colormap, DT_INTEGER, 16, 1.0 ), + DEFINE_DELTA( framerate, DT_SIGNED | DT_FLOAT, 8, 16.0 ), + DEFINE_DELTA( skin, DT_SHORT | DT_SIGNED, 9, 1.0 ), + DEFINE_DELTA( controller[0], DT_BYTE, 8, 1.0 ), + DEFINE_DELTA( controller[1], DT_BYTE, 8, 1.0 ), + DEFINE_DELTA( controller[2], DT_BYTE, 8, 1.0 ), + DEFINE_DELTA( controller[3], DT_BYTE, 8, 1.0 ), + DEFINE_DELTA( blending[0], DT_BYTE, 8, 1.0 ), + DEFINE_DELTA( blending[1], DT_BYTE, 8, 1.0 ), + DEFINE_DELTA( body, DT_INTEGER, 8, 1.0 ), + DEFINE_DELTA( rendermode, DT_INTEGER, 8, 1.0 ), + DEFINE_DELTA( renderamt, DT_INTEGER, 8, 1.0 ), + DEFINE_DELTA( renderfx, DT_INTEGER, 8, 1.0 ), + DEFINE_DELTA( scale, DT_FLOAT, 16, 256.0 ), + DEFINE_DELTA( rendercolor.r, DT_BYTE, 8, 1.0 ), + DEFINE_DELTA( rendercolor.g, DT_BYTE, 8, 1.0 ), + DEFINE_DELTA( rendercolor.b, DT_BYTE, 8, 1.0 ), + DEFINE_DELTA( aiment, DT_INTEGER, 11, 1.0 ), + DEFINE_DELTA( basevelocity[0], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( basevelocity[1], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( basevelocity[2], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + + // Playerclass signifies it's a decalable glass item when referring to an object + + DEFINE_DELTA( playerclass, DT_INTEGER, 1, 1.0 ) +} + +entity_state_player_t gamedll Player_Encode +{ + DEFINE_DELTA( animtime, DT_TIMEWINDOW_8, 8, 1.0 ), + DEFINE_DELTA( frame, DT_FLOAT, 8, 1.0 ), + DEFINE_DELTA( origin[0], DT_SIGNED | DT_FLOAT, 18, 32.0 ), + DEFINE_DELTA( angles[0], DT_ANGLE, 16, 1.0 ), + DEFINE_DELTA( angles[1], DT_ANGLE, 16, 1.0 ), + DEFINE_DELTA( origin[1], DT_SIGNED | DT_FLOAT, 18, 32.0 ), + DEFINE_DELTA( origin[2], DT_SIGNED | DT_FLOAT, 18, 32.0 ), + DEFINE_DELTA( gaitsequence, DT_INTEGER, 8, 1.0 ), + DEFINE_DELTA( sequence, DT_INTEGER, 8, 1.0 ), + DEFINE_DELTA( modelindex, DT_INTEGER, 10, 1.0 ), + DEFINE_DELTA( movetype, DT_INTEGER, 4, 1.0 ), + DEFINE_DELTA( solid, DT_SHORT, 3, 1.0 ), + DEFINE_DELTA( mins[0], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( mins[1], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( mins[2], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( maxs[0], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( maxs[1], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( maxs[2], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( weaponmodel, DT_INTEGER, 10, 1.0 ), + DEFINE_DELTA( team, DT_INTEGER, 4, 1.0 ) + DEFINE_DELTA( playerclass, DT_INTEGER, 4, 1.0 ) + DEFINE_DELTA( owner, DT_INTEGER, 5, 1.0 ), + DEFINE_DELTA( effects, DT_INTEGER, 8, 1.0 ), + DEFINE_DELTA( angles[2], DT_ANGLE, 16, 1.0 ), + DEFINE_DELTA( colormap, DT_INTEGER, 16, 1.0 ), + DEFINE_DELTA( framerate, DT_SIGNED | DT_FLOAT, 8, 16.0 ), + DEFINE_DELTA( skin, DT_SHORT | DT_SIGNED, 9, 1.0 ), + DEFINE_DELTA( controller[0], DT_BYTE, 8, 1.0 ), + DEFINE_DELTA( controller[1], DT_BYTE, 8, 1.0 ), + DEFINE_DELTA( controller[2], DT_BYTE, 8, 1.0 ), + DEFINE_DELTA( controller[3], DT_BYTE, 8, 1.0 ), + DEFINE_DELTA( blending[0], DT_BYTE, 8, 1.0 ), + DEFINE_DELTA( blending[1], DT_BYTE, 8, 1.0 ), + DEFINE_DELTA( body, DT_INTEGER, 8, 1.0 ), + DEFINE_DELTA( rendermode, DT_INTEGER, 8, 1.0 ), + DEFINE_DELTA( renderamt, DT_INTEGER, 8, 1.0 ), + DEFINE_DELTA( renderfx, DT_INTEGER, 8, 1.0 ), + DEFINE_DELTA( scale, DT_FLOAT, 16, 256.0 ), + DEFINE_DELTA( rendercolor.r, DT_BYTE, 8, 1.0 ), + DEFINE_DELTA( rendercolor.g, DT_BYTE, 8, 1.0 ), + DEFINE_DELTA( rendercolor.b, DT_BYTE, 8, 1.0 ), + DEFINE_DELTA( friction, DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( usehull, DT_INTEGER, 1, 1.0 ), + DEFINE_DELTA( gravity, DT_SIGNED | DT_FLOAT, 16, 32.0 ), + DEFINE_DELTA( aiment, DT_INTEGER, 11, 1.0 ), + DEFINE_DELTA( basevelocity[0], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( basevelocity[1], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( basevelocity[2], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( spectator, DT_INTEGER, 1, 1.0 ) +} + +custom_entity_state_t gamedll Custom_Encode +{ + DEFINE_DELTA( rendermode, DT_INTEGER, 8, 1.0 ), + DEFINE_DELTA( origin[0], DT_SIGNED | DT_FLOAT, 17, 8.0 ), + DEFINE_DELTA( origin[1], DT_SIGNED | DT_FLOAT, 17, 8.0 ), + DEFINE_DELTA( origin[2], DT_SIGNED | DT_FLOAT, 17, 8.0 ), + DEFINE_DELTA( angles[0], DT_SIGNED | DT_FLOAT, 17, 8.0 ), + DEFINE_DELTA( angles[1], DT_SIGNED | DT_FLOAT, 17, 8.0 ), + DEFINE_DELTA( angles[2], DT_SIGNED | DT_FLOAT, 17, 8.0 ), + DEFINE_DELTA( sequence, DT_INTEGER, 16, 1.0 ), + DEFINE_DELTA( skin, DT_INTEGER, 16, 1.0 ), + DEFINE_DELTA( modelindex, DT_INTEGER, 16, 1.0 ), + DEFINE_DELTA_POST( scale, DT_FLOAT, 8, 1.0, 0.1 ), + DEFINE_DELTA( body, DT_INTEGER, 8, 1.0 ), + DEFINE_DELTA( rendercolor.r, DT_BYTE, 8, 1.0 ), + DEFINE_DELTA( rendercolor.g, DT_BYTE, 8, 1.0 ), + DEFINE_DELTA( rendercolor.b, DT_BYTE, 8, 1.0 ), + DEFINE_DELTA( renderfx, DT_INTEGER, 8, 1.0 ), + DEFINE_DELTA( renderamt, DT_INTEGER, 8, 1.0 ), + DEFINE_DELTA( frame, DT_FLOAT, 8, 1.0 ), + DEFINE_DELTA_POST( animtime, DT_FLOAT, 8, 1.0, 0.1 ) +} + +usercmd_t none +{ + DEFINE_DELTA( lerp_msec, DT_SHORT, 9, 1.0 ), + DEFINE_DELTA( msec, DT_BYTE, 8, 1.0 ), + DEFINE_DELTA( viewangles[1], DT_ANGLE, 16, 1.0 ), + DEFINE_DELTA( viewangles[0], DT_ANGLE, 16, 1.0 ), + DEFINE_DELTA( buttons, DT_SHORT, 16, 1.0 ), + DEFINE_DELTA( forwardmove, DT_SIGNED | DT_FLOAT, 12, 1.0 ), + DEFINE_DELTA( lightlevel, DT_BYTE, 8, 1.0 ), + DEFINE_DELTA( sidemove, DT_SIGNED | DT_FLOAT, 12, 1.0 ), + DEFINE_DELTA( upmove, DT_SIGNED | DT_FLOAT, 12, 1.0 ), + DEFINE_DELTA( impulse, DT_BYTE, 8, 1.0 ), + DEFINE_DELTA( viewangles[2], DT_ANGLE, 16, 1.0 ), + DEFINE_DELTA( impact_index, DT_INTEGER, 6, 1.0 ), + DEFINE_DELTA( impact_position[0], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( impact_position[1], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( impact_position[2], DT_SIGNED | DT_FLOAT, 16, 8.0 ) +} + +weapon_data_t none +{ + DEFINE_DELTA( m_flTimeWeaponIdle, DT_FLOAT | DT_SIGNED, 22, 1000.0 ), + DEFINE_DELTA( m_flNextPrimaryAttack, DT_FLOAT | DT_SIGNED, 22, 1000.0 ), + DEFINE_DELTA( m_flNextReload, DT_FLOAT | DT_SIGNED, 22, 1000.0 ), + DEFINE_DELTA( m_fNextAimBonus, DT_FLOAT | DT_SIGNED, 22, 1000.0 ), + DEFINE_DELTA( m_flNextSecondaryAttack, DT_FLOAT | DT_SIGNED, 22, 1000.0 ), + DEFINE_DELTA( m_iClip, DT_SIGNED | DT_INTEGER, 10, 1.0 ), + DEFINE_DELTA( m_flPumpTime, DT_FLOAT | DT_SIGNED, 22, 1000.0 ), + DEFINE_DELTA( m_fInSpecialReload, DT_INTEGER, 2, 1.0 ), + DEFINE_DELTA( m_fReloadTime, DT_FLOAT, 16, 100.0 ), + DEFINE_DELTA( m_fInReload, DT_INTEGER, 1, 1.0 ), + DEFINE_DELTA( m_fAimedDamage, DT_FLOAT, 6, 0.1 ), + DEFINE_DELTA( m_fInZoom, DT_INTEGER, 1, 1.0 ), + DEFINE_DELTA( m_iWeaponState, DT_INTEGER, 2, 1.0 ), + DEFINE_DELTA( m_iId, DT_INTEGER, 5, 1.0 ), + DEFINE_DELTA( iuser1, DT_SIGNED | DT_INTEGER, 10, 1.0 ), + DEFINE_DELTA( iuser2, DT_SIGNED | DT_INTEGER, 10, 1.0 ), + DEFINE_DELTA( iuser3, DT_SIGNED | DT_INTEGER, 10, 1.0 ), + DEFINE_DELTA( fuser1, DT_SIGNED | DT_FLOAT, 22, 1000.0 ), + DEFINE_DELTA( fuser2, DT_SIGNED | DT_FLOAT, 22, 128.0 ), + DEFINE_DELTA( fuser3, DT_SIGNED | DT_FLOAT, 22, 128.0 ) +} + +event_t none +{ + DEFINE_DELTA( entindex, DT_INTEGER, 10, 1.0 ), + DEFINE_DELTA( bparam1, DT_INTEGER, 1, 1.0 ), + DEFINE_DELTA( bparam2, DT_INTEGER, 1, 1.0 ), + DEFINE_DELTA( origin[0], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( origin[1], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( origin[2], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( fparam1, DT_FLOAT | DT_SIGNED, 20, 100.0 ), + DEFINE_DELTA( fparam2, DT_FLOAT | DT_SIGNED, 20, 100.0 ), + DEFINE_DELTA( iparam1, DT_INTEGER | DT_SIGNED, 16, 1.0 ), + DEFINE_DELTA( iparam2, DT_INTEGER | DT_SIGNED, 16, 1.0 ), + DEFINE_DELTA( angles[0], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( angles[1], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( angles[2], DT_SIGNED | DT_FLOAT, 16, 8.0 ), + DEFINE_DELTA( ducking, DT_INTEGER, 1, 1.0 ) +} diff --git a/hlsdk/pm_shared/pm_debug.c b/hlsdk/pm_shared/pm_debug.c new file mode 100644 index 00000000..3b0879c7 --- /dev/null +++ b/hlsdk/pm_shared/pm_debug.c @@ -0,0 +1,322 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include "mathlib.h" +#include "const.h" +#include "usercmd.h" +#include "pm_defs.h" +#include "pm_shared.h" +#include "pm_movevars.h" +#include "pm_debug.h" + +#include + +#ifdef _MSC_VER +#pragma warning(disable : 4244) +#pragma warning(disable : 4305) +#endif + +extern playermove_t *pmove; + +// Expand debugging BBOX particle hulls by this many units. +#define BOX_GAP 0.0f + +static int PM_boxpnt[6][4] = +{ + { 0, 4, 6, 2 }, // +X + { 0, 1, 5, 4 }, // +Y + { 0, 2, 3, 1 }, // +Z + { 7, 5, 1, 3 }, // -X + { 7, 3, 2, 6 }, // -Y + { 7, 6, 4, 5 }, // -Z +}; + +void PM_ShowClipBox( void ) +{ +#if defined( _DEBUG ) + vec3_t org; + vec3_t offset = { 0, 0, 0 }; + + if ( !pmove->runfuncs ) + return; + + // More debugging, draw the particle bbox for player and for the entity we are looking directly at. + // aslo prints entity info to the console overlay. + //if ( !pmove->server ) + // return; + + // Draw entity in center of view + // Also draws the normal to the clip plane that intersects our movement ray. Leaves a particle + // trail at the intersection point. + PM_ViewEntity(); + + VectorCopy( pmove->origin, org ); + + if ( pmove->server ) + { + VectorAdd( org, offset, org ); + } + else + { + VectorSubtract( org, offset, org ); + } + + // Show our BBOX in particles. + PM_DrawBBox( pmove->player_mins[pmove->usehull], pmove->player_maxs[pmove->usehull], org, pmove->server ? 132 : 0, 0.1 ); + + PM_ParticleLine( org, org, pmove->server ? 132 : 0, 0.1, 5.0 ); +/* + { + int i; + for ( i = 0; i < pmove->numphysent; i++ ) + { + if ( pmove->physents[ i ].info >= 1 && pmove->physents[ i ].info <= 4 ) + { + PM_DrawBBox( pmove->player_mins[pmove->usehull], pmove->player_maxs[pmove->usehull], pmove->physents[i].origin, 132, 0.1 ); + } + } + } +*/ +#endif +} + +/* +=============== +PM_ParticleLine(vec3_t start, vec3_t end, int color, float life) + +================ +*/ +void PM_ParticleLine(vec3_t start, vec3_t end, int pcolor, float life, float vert) +{ + float linestep = 2.0f; + float curdist; + float len; + vec3_t curpos; + vec3_t diff; + int i; + // Determine distance; + + VectorSubtract(end, start, diff); + + len = VectorNormalize(diff); + + curdist = 0; + while (curdist <= len) + { + for (i = 0; i < 3; i++) + curpos[i] = start[i] + curdist * diff[i]; + + pmove->PM_Particle( curpos, pcolor, life, 0, vert); + curdist += linestep; + } + +} + +/* +================ +PM_DrawRectangle(vec3_t tl, vec3_t br) + +================ +*/ +void PM_DrawRectangle(vec3_t tl, vec3_t bl, vec3_t tr, vec3_t br, int pcolor, float life) +{ + PM_ParticleLine(tl, bl, pcolor, life, 0); + PM_ParticleLine(bl, br, pcolor, life, 0); + PM_ParticleLine(br, tr, pcolor, life, 0); + PM_ParticleLine(tr, tl, pcolor, life, 0); +} + +/* +================ +PM_DrawPhysEntBBox(int num) + +================ +*/ +void PM_DrawPhysEntBBox(int num, int pcolor, float life) +{ + physent_t *pe; + vec3_t org; + int j; + vec3_t tmp; + vec3_t p[8]; + float gap = BOX_GAP; + vec3_t modelmins, modelmaxs; + + if (num >= pmove->numphysent || + num <= 0) + return; + + pe = &pmove->physents[num]; + + if (pe->model) + { + VectorCopy(pe->origin, org); + + pmove->PM_GetModelBounds( pe->model, modelmins, modelmaxs ); + for (j = 0; j < 8; j++) + { + tmp[0] = (j & 1) ? modelmins[0] - gap : modelmaxs[0] + gap; + tmp[1] = (j & 2) ? modelmins[1] - gap : modelmaxs[1] + gap; + tmp[2] = (j & 4) ? modelmins[2] - gap : modelmaxs[2] + gap; + + VectorCopy(tmp, p[j]); + } + + // If the bbox should be rotated, do that + if (pe->angles[0] || pe->angles[1] || pe->angles[2]) + { + vec3_t forward, right, up; + + AngleVectorsTranspose(pe->angles, forward, right, up); + for (j = 0; j < 8; j++) + { + VectorCopy(p[j], tmp); + p[j][0] = DotProduct ( tmp, forward ); + p[j][1] = DotProduct ( tmp, right ); + p[j][2] = DotProduct ( tmp, up ); + } + } + + // Offset by entity origin, if any. + for (j = 0; j < 8; j++) + VectorAdd(p[j], org, p[j]); + + for (j = 0; j < 6; j++) + { + PM_DrawRectangle( + p[PM_boxpnt[j][1]], + p[PM_boxpnt[j][0]], + p[PM_boxpnt[j][2]], + p[PM_boxpnt[j][3]], + pcolor, life); + } + } + else + { + for (j = 0; j < 8; j++) + { + tmp[0] = (j & 1) ? pe->mins[0] : pe->maxs[0]; + tmp[1] = (j & 2) ? pe->mins[1] : pe->maxs[1]; + tmp[2] = (j & 4) ? pe->mins[2] : pe->maxs[2]; + + VectorAdd(tmp, pe->origin, tmp); + VectorCopy(tmp, p[j]); + } + + for (j = 0; j < 6; j++) + { + PM_DrawRectangle( + p[PM_boxpnt[j][1]], + p[PM_boxpnt[j][0]], + p[PM_boxpnt[j][2]], + p[PM_boxpnt[j][3]], + pcolor, life); + } + + } +} + +/* +================ +PM_DrawBBox(vec3_t mins, vec3_t maxs, vec3_t origin, int pcolor, float life) + +================ +*/ +void PM_DrawBBox(vec3_t mins, vec3_t maxs, vec3_t origin, int pcolor, float life) +{ + int j; + + vec3_t tmp; + vec3_t p[8]; + float gap = BOX_GAP; + + for (j = 0; j < 8; j++) + { + tmp[0] = (j & 1) ? mins[0] - gap : maxs[0] + gap; + tmp[1] = (j & 2) ? mins[1] - gap : maxs[1] + gap ; + tmp[2] = (j & 4) ? mins[2] - gap : maxs[2] + gap ; + + VectorAdd(tmp, origin, tmp); + VectorCopy(tmp, p[j]); + } + + for (j = 0; j < 6; j++) + { + PM_DrawRectangle( + p[PM_boxpnt[j][1]], + p[PM_boxpnt[j][0]], + p[PM_boxpnt[j][2]], + p[PM_boxpnt[j][3]], + pcolor, life); + } +} + + +#ifndef DEDICATED + +/* +================ +PM_ViewEntity + +Shows a particle trail from player to entity in crosshair. +Shows particles at that entities bbox + +Tries to shoot a ray out by about 128 units. +================ +*/ +void PM_ViewEntity( void ) +{ + vec3_t forward, right, up; + float raydist = 256.0f; + vec3_t origin; + vec3_t end; + int i; + pmtrace_t trace; + int pcolor = 77; + float fup; + +#if 0 + if ( !pm_showclip.value ) + return; +#endif + + AngleVectors (pmove->angles, forward, right, up); // Determine movement angles + + VectorCopy( pmove->origin, origin); + + fup = 0.5*( pmove->player_mins[pmove->usehull][2] + pmove->player_maxs[pmove->usehull][2] ); + fup += pmove->view_ofs[2]; + fup -= 4; + + for (i = 0; i < 3; i++) + { + end[i] = origin[i] + raydist * forward[i]; + } + + trace = pmove->PM_PlayerTrace( origin, end, PM_STUDIO_BOX, -1 ); + + if (trace.ent > 0) // Not the world + { + pcolor = 111; + } + + // Draw the hull or bbox. + if (trace.ent > 0) + { + PM_DrawPhysEntBBox(trace.ent, pcolor, 0.3f); + } +} + +#endif diff --git a/hlsdk/pm_shared/pm_debug.h b/hlsdk/pm_shared/pm_debug.h new file mode 100644 index 00000000..a506d37e --- /dev/null +++ b/hlsdk/pm_shared/pm_debug.h @@ -0,0 +1,28 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PM_DEBUG_H +#define PM_DEBUG_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +void PM_ViewEntity( void ); +void PM_DrawBBox(vec3_t mins, vec3_t maxs, vec3_t origin, int pcolor, float life); +void PM_ParticleLine(vec3_t start, vec3_t end, int pcolor, float life, float vert); +void PM_ShowClipBox( void ); + +#endif // PMOVEDBG_H diff --git a/hlsdk/pm_shared/pm_defs.h b/hlsdk/pm_shared/pm_defs.h new file mode 100644 index 00000000..c59391af --- /dev/null +++ b/hlsdk/pm_shared/pm_defs.h @@ -0,0 +1,266 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// pm_defs.h +#if !defined( PM_DEFSH ) +#define PM_DEFSH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +#define MAX_PHYSENTS 600 // Must have room for all entities in the world. +#define MAX_MOVEENTS 64 +#define MAX_CLIP_PLANES 5 + +#define PM_NORMAL 0x00000000 +#define PM_STUDIO_IGNORE 0x00000001 // Skip studio models +#define PM_STUDIO_BOX 0x00000002 // Use boxes for non-complex studio models (even in traceline) +#define PM_GLASS_IGNORE 0x00000004 // Ignore entities with non-normal rendermode +#define PM_WORLD_ONLY 0x00000008 // Only trace against the world + +// Values for flags parameter of PM_TraceLine +#define PM_TRACELINE_PHYSENTSONLY 0 +#define PM_TRACELINE_ANYVISIBLE 1 + +#if defined _MSC_VER && _MSC_VER >= 1400 + #ifndef _CRT_SECURE_NO_DEPRECATE + #define _CRT_SECURE_NO_DEPRECATE + #endif + + #pragma warning(disable: 4996) // deprecated functions +#endif + +#include "pm_info.h" + +// PM_PlayerTrace results. +#include "pmtrace.h" + +#if !defined ( USERCMD_H ) +#include "usercmd.h" +#endif + +// physent_t +typedef struct physent_s +{ + char name[32]; // Name of model, or "player" or "world". + int player; + vec3_t origin; // Model's origin in world coordinates. + struct model_s *model; // only for bsp models + struct model_s *studiomodel; // SOLID_BBOX, but studio clip intersections. + vec3_t mins, maxs; // only for non-bsp models + int info; // For client or server to use to identify (index into edicts or cl_entities) + vec3_t angles; // rotated entities need this info for hull testing to work. + + int solid; // Triggers and func_door type WATER brushes are SOLID_NOT + int skin; // BSP Contents for such things like fun_door water brushes. + int rendermode; // So we can ignore glass + + // Complex collision detection. + float frame; + int sequence; + byte controller[4]; + byte blending[2]; + + int movetype; + int takedamage; + int blooddecal; + int team; + int classnumber; + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; +} physent_t; + +#ifndef _DCLIPNODE_DEFINED_ +#define _DCLIPNODE_DEFINED_ +typedef struct +{ + int planenum; + short children[2]; // negative numbers are contents +} dclipnode_t; +#endif + +#ifndef _MPLANE_DEFINED_ +#define _MPLANE_DEFINED_ +typedef struct mplane_s +{ + vec3_t normal; // surface normal + float dist; // closest appoach to origin + byte type; // for texture axis selection and fast side tests + byte signbits; // signx + signy<<1 + signz<<1 + byte pad[2]; +} mplane_t; +#endif + +#ifndef _HULL_DEFINED_ +#define _HULL_DEFINED_ +typedef struct hull_s +{ + dclipnode_t *clipnodes; + mplane_t *planes; + int firstclipnode; + int lastclipnode; + vec3_t clip_mins; + vec3_t clip_maxs; +} hull_t; +#endif + +typedef struct playermove_s +{ + int player_index; // So we don't try to run the PM_CheckStuck nudging too quickly. + qboolean server; // For debugging, are we running physics code on server side? + + qboolean multiplayer; // 1 == multiplayer server + float time; // realtime on host, for reckoning duck timing + float frametime; // Duration of this frame + + vec3_t forward, right, up; // Vectors for angles + // player state + vec3_t origin; // Movement origin. + vec3_t angles; // Movement view angles. + vec3_t oldangles; // Angles before movement view angles were looked at. + vec3_t velocity; // Current movement direction. + vec3_t movedir; // For waterjumping, a forced forward velocity so we can fly over lip of ledge. + vec3_t basevelocity; // Velocity of the conveyor we are standing, e.g. + + // For ducking/dead + vec3_t view_ofs; // Our eye position. + float flDuckTime; // Time we started duck + qboolean bInDuck; // In process of ducking or ducked already? + + // For walking/falling + int flTimeStepSound; // Next time we can play a step sound + int iStepLeft; + + float flFallVelocity; + vec3_t punchangle; + + float flSwimTime; + + float flNextPrimaryAttack; + + int effects; // MUZZLE FLASH, e.g. + + int flags; // FL_ONGROUND, FL_DUCKING, etc. + int usehull; // 0 = regular player hull, 1 = ducked player hull, 2 = point hull + float gravity; // Our current gravity and friction. + float friction; + int oldbuttons; // Buttons last usercmd + float waterjumptime; // Amount of time left in jumping out of water cycle. + qboolean dead; // Are we a dead player? + int deadflag; + int spectator; // Should we use spectator physics model? + int movetype; // Our movement type, NOCLIP, WALK, FLY + + int onground; + int waterlevel; + int watertype; + int oldwaterlevel; + + char sztexturename[256]; + char chtexturetype; + + float maxspeed; + float clientmaxspeed; // Player specific maxspeed + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; + // world state + // Number of entities to clip against. + int numphysent; + physent_t physents[MAX_PHYSENTS]; + // Number of momvement entities (ladders) + int nummoveent; + // just a list of ladders + physent_t moveents[MAX_MOVEENTS]; + + // All things being rendered, for tracing against things you don't actually collide with + int numvisent; + physent_t visents[ MAX_PHYSENTS ]; + + // input to run through physics. + usercmd_t cmd; + + // Trace results for objects we collided with. + int numtouch; + pmtrace_t touchindex[MAX_PHYSENTS]; + + char physinfo[ MAX_PHYSINFO_STRING ]; // Physics info string + + struct movevars_s *movevars; + vec3_t player_mins[ 4 ]; + vec3_t player_maxs[ 4 ]; + + // Common functions + const char *(*PM_Info_ValueForKey) ( const char *s, const char *key ); + void (*PM_Particle)( float *origin, int color, float life, int zpos, int zvel); + int (*PM_TestPlayerPosition) (float *pos, pmtrace_t *ptrace ); + void (*Con_NPrintf)( int idx, char *fmt, ... ); + void (*Con_DPrintf)( char *fmt, ... ); + void (*Con_Printf)( char *fmt, ... ); + double (*Sys_FloatTime)( void ); + void (*PM_StuckTouch)( int hitent, pmtrace_t *ptraceresult ); + int (*PM_PointContents) (float *p, int *truecontents /*filled in if this is non-null*/ ); + int (*PM_TruePointContents) (float *p); + int (*PM_HullPointContents) ( struct hull_s *hull, int num, float *p); + pmtrace_t (*PM_PlayerTrace) (float *start, float *end, int traceFlags, int ignore_pe ); + struct pmtrace_s *(*PM_TraceLine)( float *start, float *end, int flags, int usehulll, int ignore_pe ); + long (*RandomLong)( long lLow, long lHigh ); + float (*RandomFloat)( float flLow, float flHigh ); + int (*PM_GetModelType)( struct model_s *mod ); + void (*PM_GetModelBounds)( struct model_s *mod, float *mins, float *maxs ); + void *(*PM_HullForBsp)( physent_t *pe, float *offset ); + float (*PM_TraceModel)( physent_t *pEnt, float *start, float *end, trace_t *trace ); + int (*COM_FileSize)(char *filename); + byte *(*COM_LoadFile) (char *path, int usehunk, int *pLength); + void (*COM_FreeFile) ( void *buffer ); + char *(*memfgets)( byte *pMemFile, int fileSize, int *pFilePos, char *pBuffer, int bufferSize ); + + // Functions + // Run functions for this frame? + qboolean runfuncs; + void (*PM_PlaySound) ( int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch ); + const char *(*PM_TraceTexture) ( int ground, float *vstart, float *vend ); + void (*PM_PlaybackEventFull) ( int flags, int clientindex, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); + + pmtrace_t (*PM_PlayerTraceEx) (float *start, float *end, int traceFlags, int (*pfnIgnore)( physent_t *pe ) ); + int (*PM_TestPlayerPositionEx) (float *pos, pmtrace_t *ptrace, int (*pfnIgnore)( physent_t *pe ) ); + struct pmtrace_s *(*PM_TraceLineEx)( float *start, float *end, int flags, int usehulll, int (*pfnIgnore)( physent_t *pe ) ); +} playermove_t; + +#endif diff --git a/hlsdk/pm_shared/pm_info.h b/hlsdk/pm_shared/pm_info.h new file mode 100644 index 00000000..fa3cc192 --- /dev/null +++ b/hlsdk/pm_shared/pm_info.h @@ -0,0 +1,26 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Physics info string definition +#if !defined( PM_INFOH ) +#define PM_INFOH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +#define MAX_PHYSINFO_STRING 256 + +#endif // PM_INFOH diff --git a/hlsdk/pm_shared/pm_materials.h b/hlsdk/pm_shared/pm_materials.h new file mode 100644 index 00000000..41407817 --- /dev/null +++ b/hlsdk/pm_shared/pm_materials.h @@ -0,0 +1,37 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( PM_MATERIALSH ) +#define PM_MATERIALSH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +#define CBTEXTURENAMEMAX 13 // only load first n chars of name + +#define CHAR_TEX_CONCRETE 'C' // texture types +#define CHAR_TEX_METAL 'M' +#define CHAR_TEX_DIRT 'D' +#define CHAR_TEX_VENT 'V' +#define CHAR_TEX_GRATE 'G' +#define CHAR_TEX_TILE 'T' +#define CHAR_TEX_SLOSH 'S' +#define CHAR_TEX_WOOD 'W' +#define CHAR_TEX_COMPUTER 'P' +#define CHAR_TEX_GLASS 'Y' +#define CHAR_TEX_FLESH 'F' + +#endif // !PM_MATERIALSH diff --git a/hlsdk/pm_shared/pm_math.c b/hlsdk/pm_shared/pm_math.c new file mode 100644 index 00000000..91a08448 --- /dev/null +++ b/hlsdk/pm_shared/pm_math.c @@ -0,0 +1,424 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// pm_math.c -- math primitives + +#include "mathlib.h" +#include "const.h" +#include + +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 + +#ifdef _MSC_VER +#pragma warning(disable : 4244) +#endif + +vec3_t vec3_origin = {0,0,0}; +int nanmask = 255<<23; + +float anglemod(float a) +{ + a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); + return a; +} + +void AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + 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; + } +} + +void AngleVectorsTranspose (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + 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] = (sr*sp*cy+cr*-sy); + forward[2] = (cr*sp*cy+-sr*-sy); + } + if (right) + { + right[0] = cp*sy; + right[1] = (sr*sp*sy+cr*cy); + right[2] = (cr*sp*sy+-sr*cy); + } + if (up) + { + up[0] = -sp; + up[1] = sr*cp; + up[2] = cr*cp; + } +} + + +void AngleMatrix (const vec3_t angles, float (*matrix)[4] ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + 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); + + // matrix = (YAW * PITCH) * ROLL + matrix[0][0] = cp*cy; + matrix[1][0] = cp*sy; + matrix[2][0] = -sp; + matrix[0][1] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[2][1] = sr*cp; + matrix[0][2] = (cr*sp*cy+-sr*-sy); + matrix[1][2] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.0; + matrix[1][3] = 0.0; + matrix[2][3] = 0.0; +} + +void AngleIMatrix (const vec3_t angles, float matrix[3][4] ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + 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); + + // matrix = (YAW * PITCH) * ROLL + matrix[0][0] = cp*cy; + matrix[0][1] = cp*sy; + matrix[0][2] = -sp; + matrix[1][0] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[1][2] = sr*cp; + matrix[2][0] = (cr*sp*cy+-sr*-sy); + matrix[2][1] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.0; + matrix[1][3] = 0.0; + matrix[2][3] = 0.0; +} + +void NormalizeAngles( float *angles ) +{ + int i; + // Normalize angles + for ( i = 0; i < 3; i++ ) + { + if ( angles[i] > 180.0 ) + { + angles[i] -= 360.0; + } + else if ( angles[i] < -180.0 ) + { + angles[i] += 360.0; + } + } +} + +/* +=================== +InterpolateAngles + +Interpolate Euler angles. +FIXME: Use Quaternions to avoid discontinuities +Frac is 0.0 to 1.0 ( i.e., should probably be clamped, but doesn't have to be ) +=================== +*/ +void InterpolateAngles( float *start, float *end, float *output, float frac ) +{ + int i; + float ang1, ang2; + float d; + + NormalizeAngles( start ); + NormalizeAngles( end ); + + for ( i = 0 ; i < 3 ; i++ ) + { + ang1 = start[i]; + ang2 = end[i]; + + d = ang2 - ang1; + if ( d > 180 ) + { + d -= 360; + } + else if ( d < -180 ) + { + d += 360; + } + + output[i] = ang1 + d * frac; + } + + NormalizeAngles( output ); +} + + +/* +=================== +AngleBetweenVectors + +=================== +*/ +float AngleBetweenVectors( const vec3_t v1, const vec3_t v2 ) +{ + float angle; + float l1 = Length( v1 ); + float l2 = Length( v2 ); + + if ( !l1 || !l2 ) + return 0.0f; + + angle = acos( DotProduct( v1, v2 ) ) / (l1*l2); + angle = ( angle * 180.0f ) / M_PI; + + return angle; +} + + +void VectorTransform (const vec3_t in1, float in2[3][4], vec3_t out) +{ + out[0] = DotProduct(in1, in2[0]) + in2[0][3]; + out[1] = DotProduct(in1, in2[1]) + in2[1][3]; + out[2] = DotProduct(in1, in2[2]) + in2[2][3]; +} + + +int VectorCompare (const vec3_t v1, const vec3_t v2) +{ + int i; + + for (i=0 ; i<3 ; i++) + if (v1[i] != v2[i]) + return 0; + + return 1; +} + +void VectorMA (const vec3_t veca, float scale, const vec3_t vecb, vec3_t vecc) +{ + vecc[0] = veca[0] + scale*vecb[0]; + vecc[1] = veca[1] + scale*vecb[1]; + vecc[2] = veca[2] + scale*vecb[2]; +} + + +vec_t _DotProduct (vec3_t v1, vec3_t v2) +{ + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} + +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]-vecb[0]; + out[1] = veca[1]-vecb[1]; + out[2] = veca[2]-vecb[2]; +} + +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]+vecb[0]; + out[1] = veca[1]+vecb[1]; + out[2] = veca[2]+vecb[2]; +} + +void _VectorCopy (vec3_t in, vec3_t out) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +void CrossProduct (const vec3_t v1, const vec3_t v2, vec3_t cross) +{ + cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; + cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; + cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} + +double sqrt(double x); + +float Length(const vec3_t v) +{ + int i; + float length = 0.0f; + + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = sqrt (length); // FIXME + + return length; +} + +float Distance(const vec3_t v1, const vec3_t v2) +{ + vec3_t d; + VectorSubtract(v2,v1,d); + return Length(d); +} + +float VectorNormalize (vec3_t v) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; + +} + +void VectorInverse (vec3_t v) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void VectorScale (const vec3_t in, vec_t scale, vec3_t out) +{ + out[0] = in[0]*scale; + out[1] = in[1]*scale; + out[2] = in[2]*scale; +} + + +int Q_log2(int val) +{ + int answer=0; + while (val>>=1) + answer++; + return answer; +} + +void VectorMatrix( vec3_t forward, vec3_t right, vec3_t up) +{ + vec3_t tmp; + + if (forward[0] == 0 && forward[1] == 0) + { + right[0] = 1; + right[1] = 0; + right[2] = 0; + up[0] = -forward[2]; + up[1] = 0; + up[2] = 0; + return; + } + + tmp[0] = 0; tmp[1] = 0; tmp[2] = 1.0; + CrossProduct( forward, tmp, right ); + VectorNormalize( right ); + CrossProduct( right, forward, up ); + VectorNormalize( up ); +} + + +void VectorAngles( const vec3_t forward, vec3_t 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; +} diff --git a/hlsdk/pm_shared/pm_movevars.h b/hlsdk/pm_shared/pm_movevars.h new file mode 100644 index 00000000..66c99ee7 --- /dev/null +++ b/hlsdk/pm_shared/pm_movevars.h @@ -0,0 +1,47 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// pm_movevars.h +#if !defined( PM_MOVEVARSH ) +#define PM_MOVEVARSH + +// movevars_t // Physics variables. +typedef struct movevars_s movevars_t; + +struct movevars_s +{ + float gravity; // Gravity for map + float stopspeed; // Deceleration when not moving + float maxspeed; // Max allowed speed + float spectatormaxspeed; + float accelerate; // Acceleration factor + float airaccelerate; // Same for when in open air + float wateraccelerate; // Same for when in water + float friction; + float edgefriction; // Extra friction near dropofs + float waterfriction; // Less in water + float entgravity; // 1.0 + float bounce; // Wall bounce value. 1.0 + float stepsize; // sv_stepsize; + float maxvelocity; // maximum server velocity. + float zmax; // Max z-buffer range (for GL) + float waveHeight; // Water wave height (for GL) + qboolean footsteps; // Play footstep sounds + char skyName[32]; // Name of the sky map + float rollangle; + float rollspeed; + float skycolor_r; // Sky color + float skycolor_g; // + float skycolor_b; // + float skyvec_x; // Sky vector + float skyvec_y; // + float skyvec_z; // +}; + +extern movevars_t movevars; + +#endif diff --git a/hlsdk/pm_shared/pm_shared.c b/hlsdk/pm_shared/pm_shared.c new file mode 100644 index 00000000..dbce3143 --- /dev/null +++ b/hlsdk/pm_shared/pm_shared.c @@ -0,0 +1,3308 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include +#include "mathlib.h" +#include "const.h" +#include "usercmd.h" +#include "pm_defs.h" +#include "pm_shared.h" +#include "pm_movevars.h" +#include "pm_debug.h" +#include // NULL +#include // sqrt +#include // strcpy +#include // atoi +#include // isspace + +#ifdef CLIENT_DLL + // Spectator Mode + int iJumpSpectator; + float vJumpOrigin[3]; + float vJumpAngles[3]; +#endif + +static int pm_shared_initialized = 0; + +#ifdef _MSC_VER +#pragma warning( disable : 4305 ) +#endif + +typedef enum {mod_brush, mod_sprite, mod_alias, mod_studio} modtype_t; + +playermove_t *pmove = NULL; + +// Ducking time +#define TIME_TO_DUCK 0.4 +#define VEC_DUCK_HULL_MIN -18 +#define VEC_DUCK_HULL_MAX 18 +#define VEC_DUCK_VIEW 12 +#define PM_DEAD_VIEWHEIGHT -8 +#define MAX_CLIMB_SPEED 200 +#define STUCK_MOVEUP 1 +#define STUCK_MOVEDOWN -1 +#define VEC_HULL_MIN -36 +#define VEC_HULL_MAX 36 +#define VEC_VIEW 28 +#define STOP_EPSILON 0.1 + +#define CTEXTURESMAX 512 // max number of textures loaded +#define CBTEXTURENAMEMAX 13 // only load first n chars of name + +#define CHAR_TEX_CONCRETE 'C' // texture types +#define CHAR_TEX_METAL 'M' +#define CHAR_TEX_DIRT 'D' +#define CHAR_TEX_VENT 'V' +#define CHAR_TEX_GRATE 'G' +#define CHAR_TEX_TILE 'T' +#define CHAR_TEX_SLOSH 'S' +#define CHAR_TEX_WOOD 'W' +#define CHAR_TEX_COMPUTER 'P' +#define CHAR_TEX_GLASS 'Y' +#define CHAR_TEX_FLESH 'F' + +#define STEP_CONCRETE 0 // default step sound +#define STEP_METAL 1 // metal floor +#define STEP_DIRT 2 // dirt, sand, rock +#define STEP_VENT 3 // ventillation duct +#define STEP_GRATE 4 // metal grating +#define STEP_TILE 5 // floor tiles +#define STEP_SLOSH 6 // shallow liquid puddle +#define STEP_WADE 7 // wading in liquid +#define STEP_LADDER 8 // climbing ladder + +#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_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_LONGJUMP_SPEED 350 // how fast we longjump + +// double to float warning +#ifdef _MSC_VER +#pragma warning(disable : 4244) +#endif + +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#define min(a, b) (((a) < (b)) ? (a) : (b)) +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 + +#define MAX_CLIENTS 32 + +#define CONTENTS_CURRENT_0 -9 +#define CONTENTS_CURRENT_90 -10 +#define CONTENTS_CURRENT_180 -11 +#define CONTENTS_CURRENT_270 -12 +#define CONTENTS_CURRENT_UP -13 +#define CONTENTS_CURRENT_DOWN -14 + +#define CONTENTS_TRANSLUCENT -15 + +static vec3_t rgv3tStuckTable[54]; +static int rgStuckLast[MAX_CLIENTS][2]; + +// Texture names +static int gcTextures = 0; +static char grgszTextureName[CTEXTURESMAX][CBTEXTURENAMEMAX]; +static char grgchTextureType[CTEXTURESMAX]; + +int g_onladder = 0; + +void PM_SwapTextures( int i, int j ) +{ + char chTemp; + char szTemp[ CBTEXTURENAMEMAX ]; + + strcpy( szTemp, grgszTextureName[ i ] ); + chTemp = grgchTextureType[ i ]; + + strcpy( grgszTextureName[ i ], grgszTextureName[ j ] ); + grgchTextureType[ i ] = grgchTextureType[ j ]; + + strcpy( grgszTextureName[ j ], szTemp ); + grgchTextureType[ j ] = chTemp; +} + +void PM_SortTextures( void ) +{ + // Bubble sort, yuck, but this only occurs at startup and it's only 512 elements... + // + int i, j; + + for ( i = 0 ; i < gcTextures; i++ ) + { + for ( j = i + 1; j < gcTextures; j++ ) + { + if ( stricmp( grgszTextureName[ i ], grgszTextureName[ j ] ) > 0 ) + { + // Swap + // + PM_SwapTextures( i, j ); + } + } + } +} + +void PM_InitTextureTypes() +{ + char buffer[512]; + int i, j; + byte *pMemFile; + int fileSize, filePos; + static qboolean bTextureTypeInit = false; + + if ( bTextureTypeInit ) + 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 ); + if ( !pMemFile ) + return; + + filePos = 0; + // for each line in the file... + while ( pmove->memfgets( pMemFile, fileSize, &filePos, buffer, 511 ) != NULL && (gcTextures < CTEXTURESMAX) ) + { + // skip whitespace + i = 0; + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // skip comment lines + if (buffer[i] == '/' || !isalpha(buffer[i])) + continue; + + // get texture type + grgchTextureType[gcTextures] = toupper(buffer[i++]); + + // skip whitespace + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // get sentence name + j = i; + while (buffer[j] && !isspace(buffer[j])) + j++; + + if (!buffer[j]) + continue; + + // null-terminate name and save in sentences array + j = min (j, CBTEXTURENAMEMAX-1+i); + buffer[j] = 0; + 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 ) +{ + int left, right, pivot; + int val; + + assert( pm_shared_initialized ); + + left = 0; + right = gcTextures - 1; + + while ( left <= right ) + { + pivot = ( left + right ) / 2; + + val = strnicmp( name, grgszTextureName[ pivot ], CBTEXTURENAMEMAX-1 ); + if ( val == 0 ) + { + return grgchTextureType[ pivot ]; + } + else if ( val > 0 ) + { + left = pivot + 1; + } + else if ( val < 0 ) + { + right = pivot - 1; + } + } + + return CHAR_TEX_CONCRETE; +} + +void PM_PlayStepSound( int step, float fvol ) +{ + static int iSkipStep = 0; + int irand; + vec3_t hvel; + + pmove->iStepLeft = !pmove->iStepLeft; + + if ( !pmove->runfuncs ) + { + return; + } + + irand = pmove->RandomLong(0,1) + ( pmove->iStepLeft * 2 ); + + // FIXME mp_footsteps needs to be a movevar + if ( pmove->multiplayer && !pmove->movevars->footsteps ) + return; + + VectorCopy( pmove->velocity, hvel ); + hvel[2] = 0.0; + + 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 + // FIXME, move to player state + + switch (step) + { + default: + case STEP_CONCRETE: + switch (irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_step1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_step3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_step2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_step4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_METAL: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_metal1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_metal3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_metal2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_metal4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_DIRT: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_dirt1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_dirt3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_dirt2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_dirt4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_VENT: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_duct1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_duct3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_duct2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_duct4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_GRATE: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_grate1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_grate3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_grate2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_grate4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_TILE: + if ( !pmove->RandomLong(0,4) ) + irand = 4; + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_tile1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_tile3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_tile2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_tile4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 4: pmove->PM_PlaySound( CHAN_BODY, "player/pl_tile5.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_SLOSH: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_slosh1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_slosh3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_slosh2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_slosh4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_WADE: + if ( iSkipStep == 0 ) + { + iSkipStep++; + break; + } + + if ( iSkipStep++ == 3 ) + { + iSkipStep = 0; + } + + switch (irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_LADDER: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_ladder1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_ladder3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_ladder2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_ladder4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + } +} + +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; + } +} + +/* +==================== +PM_CatagorizeTextureType + +Determine texture info for the texture we are standing on. +==================== +*/ +void PM_CatagorizeTextureType( void ) +{ + vec3_t start, end; + const char *pTextureName; + + VectorCopy( pmove->origin, start ); + VectorCopy( pmove->origin, end ); + + // Straight down + end[2] -= 64; + + // Fill in default values, just in case. + pmove->sztexturename[0] = '\0'; + pmove->chtexturetype = CHAR_TEX_CONCRETE; + + pTextureName = pmove->PM_TraceTexture( pmove->onground, start, end ); + if ( !pTextureName ) + return; + + // strip leading '-0' or '+0~' or '{' or '!' + if (*pTextureName == '-' || *pTextureName == '+') + pTextureName += 2; + + if (*pTextureName == '{' || *pTextureName == '!' || *pTextureName == '~' || *pTextureName == ' ') + pTextureName++; + // '}}' + + strcpy( pmove->sztexturename, pTextureName); + pmove->sztexturename[ CBTEXTURENAMEMAX - 1 ] = 0; + + // get texture type + pmove->chtexturetype = PM_FindTextureType( pmove->sztexturename ); +} + +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; + + if ( pmove->flTimeStepSound > 0 ) + return; + + if ( pmove->flags & FL_FROZEN ) + return; + + 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!!!! + 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; + } + + // 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 ) ) + { + fWalking = speed < velrun; + + VectorCopy( pmove->origin, center ); + VectorCopy( pmove->origin, knee ); + VectorCopy( pmove->origin, feet ); + + height = pmove->player_maxs[ pmove->usehull ][ 2 ] - pmove->player_mins[ pmove->usehull ][ 2 ]; + + knee[2] = pmove->origin[2] - 0.3 * height; + feet[2] = pmove->origin[2] - 0.5 * height; + + // find out what we're stepping in or on... + if (fLadder) + { + step = STEP_LADDER; + fvol = 0.35; + pmove->flTimeStepSound = 350; + } + else if ( pmove->PM_PointContents ( knee, NULL ) == CONTENTS_WATER ) + { + step = STEP_WADE; + fvol = 0.65; + 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; + } + else + { + // find texture under player, if different from current texture, + // get material type + step = PM_MapTextureTypeStepType( pmove->chtexturetype ); + + switch ( pmove->chtexturetype ) + { + default: + case CHAR_TEX_CONCRETE: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_METAL: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_DIRT: + fvol = fWalking ? 0.25 : 0.55; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_VENT: + fvol = fWalking ? 0.4 : 0.7; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_GRATE: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_TILE: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_SLOSH: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + } + } + + pmove->flTimeStepSound += flduck; // slower step time if ducking + + // play the sound + // 35% volume if ducking + if ( pmove->flags & FL_DUCKING ) + { + fvol *= 0.35; + } + + PM_PlayStepSound( step, fvol ); + } +} + +/* +================ +PM_AddToTouched + +Add's the trace result to touch list, if contact is not already in list. +================ +*/ +qboolean PM_AddToTouched(pmtrace_t tr, vec3_t impactvelocity) +{ + int i; + + for (i = 0; i < pmove->numtouch; i++) + { + if (pmove->touchindex[i].ent == tr.ent) + break; + } + if (i != pmove->numtouch) // Already in list. + return false; + + VectorCopy( impactvelocity, tr.deltavelocity ); + + if (pmove->numtouch >= MAX_PHYSENTS) + pmove->Con_DPrintf("Too many entities were touched!\n"); + + pmove->touchindex[pmove->numtouch++] = tr; + return true; +} + +/* +================ +PM_CheckVelocity + +See if the player has a bogus velocity value. +================ +*/ +void PM_CheckVelocity () +{ + int i; + +// +// bound velocity +// + for (i=0 ; i<3 ; i++) + { + // See if it's bogus. + if (IS_NAN(pmove->velocity[i])) + { + pmove->Con_Printf ("PM Got a NaN velocity %i\n", i); + pmove->velocity[i] = 0; + } + if (IS_NAN(pmove->origin[i])) + { + pmove->Con_Printf ("PM Got a NaN origin on %i\n", i); + pmove->origin[i] = 0; + } + + // Bound it. + if (pmove->velocity[i] > pmove->movevars->maxvelocity) + { + pmove->Con_DPrintf ("PM Got a velocity too high on %i\n", i); + pmove->velocity[i] = pmove->movevars->maxvelocity; + } + else if (pmove->velocity[i] < -pmove->movevars->maxvelocity) + { + pmove->Con_DPrintf ("PM Got a velocity too low on %i\n", i); + pmove->velocity[i] = -pmove->movevars->maxvelocity; + } + } +} + +/* +================== +PM_ClipVelocity + +Slide off of the impacting object +returns the blocked flags: +0x01 == floor +0x02 == step / wall +================== +*/ +int PM_ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce) +{ + float backoff; + float change; + float angle; + int i, blocked; + + angle = normal[ 2 ]; + + blocked = 0x00; // Assume unblocked. + if (angle > 0) // If the plane that is blocking us has a positive z component, then assume it's a floor. + blocked |= 0x01; // + if (!angle) // If the plane has no Z, it is vertical (wall/step) + blocked |= 0x02; // + + // Determine how far along plane to slide based on incoming direction. + // Scale by overbounce factor. + backoff = DotProduct (in, normal) * overbounce; + + for (i=0 ; i<3 ; i++) + { + change = normal[i]*backoff; + out[i] = in[i] - change; + // If out velocity is too small, zero it out. + if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) + out[i] = 0; + } + + // Return blocking flags. + return blocked; +} + +void PM_AddCorrectGravity () +{ + float ent_gravity; + + if ( pmove->waterjumptime ) + return; + + if (pmove->gravity) + ent_gravity = pmove->gravity; + else + ent_gravity = 1.0; + + // Add gravity so they'll be in the correct position during movement + // yes, this 0.5 looks wrong, but it's not. + pmove->velocity[2] -= (ent_gravity * pmove->movevars->gravity * 0.5 * pmove->frametime ); + pmove->velocity[2] += pmove->basevelocity[2] * pmove->frametime; + pmove->basevelocity[2] = 0; + + PM_CheckVelocity(); +} + + +void PM_FixupGravityVelocity () +{ + float ent_gravity; + + if ( pmove->waterjumptime ) + return; + + if (pmove->gravity) + ent_gravity = pmove->gravity; + else + ent_gravity = 1.0; + + // Get the correct velocity for the end of the dt + pmove->velocity[2] -= (ent_gravity * pmove->movevars->gravity * pmove->frametime * 0.5 ); + + PM_CheckVelocity(); +} + +/* +============ +PM_FlyMove + +The basic solid body movement clip that slides along multiple planes +============ +*/ +int PM_FlyMove (void) +{ + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity, original_velocity; + vec3_t new_velocity; + int i, j; + pmtrace_t trace; + vec3_t end; + float time_left, allFraction; + int blocked; + + numbumps = 4; // Bump up to four times + + blocked = 0; // Assume not blocked + numplanes = 0; // and not sliding along any planes + VectorCopy (pmove->velocity, original_velocity); // Store original velocity + VectorCopy (pmove->velocity, primal_velocity); + + allFraction = 0; + time_left = pmove->frametime; // Total time for this movement operation. + + for (bumpcount=0 ; bumpcountvelocity[0] && !pmove->velocity[1] && !pmove->velocity[2]) + break; + + // Assume we can move all the way from the current origin to the + // end point. + for (i=0 ; i<3 ; i++) + end[i] = pmove->origin[i] + time_left * pmove->velocity[i]; + + // See if we can make it from origin to end point. + trace = pmove->PM_PlayerTrace (pmove->origin, end, PM_NORMAL, -1 ); + + allFraction += trace.fraction; + // If we started in a solid object, or we were in solid space + // the whole way, zero out our velocity and return that we + // are blocked by floor and wall. + if (trace.allsolid) + { // entity is trapped in another solid + VectorCopy (vec3_origin, pmove->velocity); + //Con_DPrintf("Trapped 4\n"); + return 4; + } + + // If we moved some portion of the total distance, then + // copy the end position into the pmove->origin and + // zero the plane counter. + if (trace.fraction > 0) + { // actually covered some distance + VectorCopy (trace.endpos, pmove->origin); + VectorCopy (pmove->velocity, original_velocity); + numplanes = 0; + } + + // If we covered the entire distance, we are done + // and can return. + if (trace.fraction == 1) + break; // moved the entire distance + + //if (!trace.ent) + // Sys_Error ("PM_PlayerTrace: !trace.ent"); + + // Save entity that blocked us (since fraction was < 1.0) + // for contact + // Add it if it's not already in the list!!! + PM_AddToTouched(trace, pmove->velocity); + + // If the plane we hit has a high z component in the normal, then + // it's probably a floor + if (trace.plane.normal[2] > 0.7) + { + blocked |= 1; // floor + } + // If the plane has a zero z component in the normal, then it's a + // step or wall + if (!trace.plane.normal[2]) + { + blocked |= 2; // step / wall + //Con_DPrintf("Blocked by %i\n", trace.ent); + } + + // Reduce amount of pmove->frametime left by total time left * fraction + // that we covered. + time_left -= time_left * trace.fraction; + + // Did we run out of planes to clip against? + if (numplanes >= MAX_CLIP_PLANES) + { // this shouldn't really happen + // Stop our movement if so. + VectorCopy (vec3_origin, pmove->velocity); + //Con_DPrintf("Too many planes 4\n"); + + break; + } + + // Set up next clipping plane + VectorCopy (trace.plane.normal, planes[numplanes]); + numplanes++; +// + +// modify original_velocity so it parallels all of the clip planes +// + if ( pmove->movetype == MOVETYPE_WALK && + ((pmove->onground == -1) || (pmove->friction != 1)) ) // relfect player velocity + { + for ( i = 0; i < numplanes; i++ ) + { + if ( planes[i][2] > 0.7 ) + {// floor or slope + PM_ClipVelocity( original_velocity, planes[i], new_velocity, 1 ); + VectorCopy( new_velocity, original_velocity ); + } + else + PM_ClipVelocity( original_velocity, planes[i], new_velocity, 1.0 + pmove->movevars->bounce * (1-pmove->friction) ); + } + + VectorCopy( new_velocity, pmove->velocity ); + VectorCopy( new_velocity, original_velocity ); + } + else + { + for (i=0 ; ivelocity, + 1); + for (j=0 ; jvelocity, planes[j]) < 0) + break; // not ok + } + if (j == numplanes) // Didn't have to clip, so we're ok + break; + } + + // Did we go all the way through plane set + if (i != numplanes) + { // go along this plane + // pmove->velocity is set in clipping call, no need to set again. + ; + } + else + { // go along the crease + if (numplanes != 2) + { + //Con_Printf ("clip velocity, numplanes == %i\n",numplanes); + VectorCopy (vec3_origin, pmove->velocity); + //Con_DPrintf("Trapped 4\n"); + + break; + } + CrossProduct (planes[0], planes[1], dir); + d = DotProduct (dir, pmove->velocity); + VectorScale (dir, d, pmove->velocity ); + } + + // + // if original velocity is against the original velocity, stop dead + // to avoid tiny occilations in sloping corners + // + if (DotProduct (pmove->velocity, primal_velocity) <= 0) + { + //Con_DPrintf("Back\n"); + VectorCopy (vec3_origin, pmove->velocity); + break; + } + } + } + + if ( allFraction == 0 ) + { + VectorCopy (vec3_origin, pmove->velocity); + //Con_DPrintf( "Don't stick\n" ); + } + + return blocked; +} + +/* +============== +PM_Accelerate +============== +*/ +void PM_Accelerate (vec3_t wishdir, float wishspeed, float accel) +{ + int i; + float addspeed, accelspeed, currentspeed; + + // Dead player's don't accelerate + if (pmove->dead) + return; + + // If waterjumping, don't accelerate + if (pmove->waterjumptime) + return; + + // See if we are changing direction a bit + currentspeed = DotProduct (pmove->velocity, wishdir); + + // Reduce wishspeed by the amount of veer. + addspeed = wishspeed - currentspeed; + + // If not going to add any speed, done. + if (addspeed <= 0) + return; + + // Determine amount of accleration. + accelspeed = accel * pmove->frametime * wishspeed * pmove->friction; + + // Cap at addspeed + if (accelspeed > addspeed) + accelspeed = addspeed; + + // Adjust velocity. + for (i=0 ; i<3 ; i++) + { + pmove->velocity[i] += accelspeed * wishdir[i]; + } +} + +/* +===================== +PM_WalkMove + +Only used by players. Moves along the ground when player is a MOVETYPE_WALK. +====================== +*/ +void PM_WalkMove () +{ + int clip; + int oldonground; + int i; + + vec3_t wishvel; + float spd; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + + vec3_t dest, start; + vec3_t original, originalvel; + vec3_t down, downvel; + float downdist, updist; + + pmtrace_t trace; + + // Copy movement amounts + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + // Zero out z components of movement vectors + pmove->forward[2] = 0; + pmove->right[2] = 0; + + VectorNormalize (pmove->forward); // Normalize remainder of vectors. + VectorNormalize (pmove->right); // + + for (i=0 ; i<2 ; i++) // Determine x and y parts of velocity + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + + wishvel[2] = 0; // Zero out z part of velocity + + VectorCopy (wishvel, wishdir); // Determine maginitude of speed of move + wishspeed = VectorNormalize(wishdir); + +// +// Clamp to server defined max speed +// + if (wishspeed > pmove->maxspeed) + { + VectorScale (wishvel, pmove->maxspeed/wishspeed, wishvel); + wishspeed = pmove->maxspeed; + } + + // Set pmove velocity + pmove->velocity[2] = 0; + PM_Accelerate (wishdir, wishspeed, pmove->movevars->accelerate); + pmove->velocity[2] = 0; + + // Add in any base velocity to the current velocity. + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity ); + + spd = Length( pmove->velocity ); + + if (spd < 1.0f) + { + VectorClear( pmove->velocity ); + return; + } + + // If we are not moving, do nothing + //if (!pmove->velocity[0] && !pmove->velocity[1] && !pmove->velocity[2]) + // return; + + oldonground = pmove->onground; + +// first try just moving to the destination + dest[0] = pmove->origin[0] + pmove->velocity[0]*pmove->frametime; + dest[1] = pmove->origin[1] + pmove->velocity[1]*pmove->frametime; + dest[2] = pmove->origin[2]; + + // first try moving directly to the next spot + VectorCopy (dest, start); + trace = pmove->PM_PlayerTrace (pmove->origin, dest, PM_NORMAL, -1 ); + // If we made it all the way, then copy trace end + // as new player position. + if (trace.fraction == 1) + { + VectorCopy (trace.endpos, pmove->origin); + return; + } + + if (oldonground == -1 && // Don't walk up stairs if not on ground. + pmove->waterlevel == 0) + return; + + if (pmove->waterjumptime) // If we are jumping out of water, don't do anything more. + return; + + // Try sliding forward both on ground and up 16 pixels + // take the move that goes farthest + VectorCopy (pmove->origin, original); // Save out original pos & + VectorCopy (pmove->velocity, originalvel); // velocity. + + // Slide move + clip = PM_FlyMove (); + + // Copy the results out + VectorCopy (pmove->origin , down); + VectorCopy (pmove->velocity, downvel); + + // Reset original values. + VectorCopy (original, pmove->origin); + + VectorCopy (originalvel, pmove->velocity); + + // Start out up one stair height + VectorCopy (pmove->origin, dest); + dest[2] += pmove->movevars->stepsize; + + trace = pmove->PM_PlayerTrace (pmove->origin, dest, PM_NORMAL, -1 ); + // If we started okay and made it part of the way at least, + // copy the results to the movement start position and then + // run another move try. + if (!trace.startsolid && !trace.allsolid) + { + VectorCopy (trace.endpos, pmove->origin); + } + +// slide move the rest of the way. + clip = PM_FlyMove (); + +// Now try going back down from the end point +// press down the stepheight + VectorCopy (pmove->origin, dest); + dest[2] -= pmove->movevars->stepsize; + + trace = pmove->PM_PlayerTrace (pmove->origin, dest, PM_NORMAL, -1 ); + + // If we are not on the ground any more then + // use the original movement attempt + if ( trace.plane.normal[2] < 0.7) + goto usedown; + // If the trace ended up in empty space, copy the end + // over to the origin. + if (!trace.startsolid && !trace.allsolid) + { + VectorCopy (trace.endpos, pmove->origin); + } + // Copy this origion to up. + VectorCopy (pmove->origin, pmove->up); + + // decide which one went farther + downdist = (down[0] - original[0])*(down[0] - original[0]) + + (down[1] - original[1])*(down[1] - original[1]); + updist = (pmove->up[0] - original[0])*(pmove->up[0] - original[0]) + + (pmove->up[1] - original[1])*(pmove->up[1] - original[1]); + + if (downdist > updist) + { +usedown: + VectorCopy (down , pmove->origin); + VectorCopy (downvel, pmove->velocity); + } else // copy z value from slide move + pmove->velocity[2] = downvel[2]; + +} + +/* +================== +PM_Friction + +Handles both ground friction and water friction +================== +*/ +void PM_Friction (void) +{ + float *vel; + float speed, newspeed, control; + float friction; + float drop; + vec3_t newvel; + + // If we are in water jump cycle, don't apply friction + if (pmove->waterjumptime) + return; + + // Get velocity + vel = pmove->velocity; + + // Calculate speed + speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1] + vel[2]*vel[2]); + + // If too slow, return + if (speed < 0.1f) + { + return; + } + + drop = 0; + +// apply ground friction + if (pmove->onground != -1) // On an entity that is the ground + { + vec3_t start, stop; + pmtrace_t trace; + + start[0] = stop[0] = pmove->origin[0] + vel[0]/speed*16; + start[1] = stop[1] = pmove->origin[1] + vel[1]/speed*16; + start[2] = pmove->origin[2] + pmove->player_mins[pmove->usehull][2]; + stop[2] = start[2] - 34; + + trace = pmove->PM_PlayerTrace (start, stop, PM_NORMAL, -1 ); + + if (trace.fraction == 1.0) + friction = pmove->movevars->friction*pmove->movevars->edgefriction; + else + friction = pmove->movevars->friction; + + // Grab friction value. + //friction = pmove->movevars->friction; + + friction *= pmove->friction; // player friction? + + // Bleed off some speed, but if we have less than the bleed + // threshhold, bleed the theshold amount. + control = (speed < pmove->movevars->stopspeed) ? + pmove->movevars->stopspeed : speed; + // Add the amount to t'he drop amount. + drop += control*friction*pmove->frametime; + } + +// apply water friction +// if (pmove->waterlevel) +// drop += speed * pmove->movevars->waterfriction * waterlevel * pmove->frametime; + +// scale the velocity + newspeed = speed - drop; + if (newspeed < 0) + newspeed = 0; + + // Determine proportion of old speed we are using. + newspeed /= speed; + + // Adjust velocity according to proportion. + newvel[0] = vel[0] * newspeed; + newvel[1] = vel[1] * newspeed; + newvel[2] = vel[2] * newspeed; + + VectorCopy( newvel, pmove->velocity ); +} + +void PM_AirAccelerate (vec3_t wishdir, float wishspeed, float accel) +{ + int i; + float addspeed, accelspeed, currentspeed, wishspd = wishspeed; + + if (pmove->dead) + return; + if (pmove->waterjumptime) + return; + + // Cap speed + //wishspd = VectorNormalize (pmove->wishveloc); + + if (wishspd > 30) + wishspd = 30; + // Determine veer amount + currentspeed = DotProduct (pmove->velocity, wishdir); + // See how much to add + addspeed = wishspd - currentspeed; + // If not adding any, done. + if (addspeed <= 0) + return; + // Determine acceleration speed after acceleration + + accelspeed = accel * wishspeed * pmove->frametime * pmove->friction; + // Cap it + if (accelspeed > addspeed) + accelspeed = addspeed; + + // Adjust pmove vel. + for (i=0 ; i<3 ; i++) + { + pmove->velocity[i] += accelspeed*wishdir[i]; + } +} + +/* +=================== +PM_WaterMove + +=================== +*/ +void PM_WaterMove (void) +{ + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + vec3_t start, dest; + vec3_t temp; + pmtrace_t trace; + + float speed, newspeed, addspeed, accelspeed; + +// +// user intentions +// + for (i=0 ; i<3 ; i++) + wishvel[i] = pmove->forward[i]*pmove->cmd.forwardmove + pmove->right[i]*pmove->cmd.sidemove; + + // Sinking after no other movement occurs + if (!pmove->cmd.forwardmove && !pmove->cmd.sidemove && !pmove->cmd.upmove) + wishvel[2] -= 60; // drift towards bottom + else // Go straight up by upmove amount. + wishvel[2] += pmove->cmd.upmove; + + // Copy it over and determine speed + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + // Cap speed. + if (wishspeed > pmove->maxspeed) + { + VectorScale (wishvel, pmove->maxspeed/wishspeed, wishvel); + wishspeed = pmove->maxspeed; + } + // Slow us down a bit. + wishspeed *= 0.8; + + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity); +// Water friction + VectorCopy(pmove->velocity, temp); + speed = VectorNormalize(temp); + if (speed) + { + newspeed = speed - pmove->frametime * speed * pmove->movevars->friction * pmove->friction; + + if (newspeed < 0) + newspeed = 0; + VectorScale (pmove->velocity, newspeed/speed, pmove->velocity); + } + else + newspeed = 0; + +// +// water acceleration +// + if ( wishspeed < 0.1f ) + { + return; + } + + addspeed = wishspeed - newspeed; + if (addspeed > 0) + { + + VectorNormalize(wishvel); + accelspeed = pmove->movevars->accelerate * wishspeed * pmove->frametime * pmove->friction; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i = 0; i < 3; i++) + pmove->velocity[i] += accelspeed * wishvel[i]; + } + +// Now move +// assume it is a stair or a slope, so press down from stepheight above + VectorMA (pmove->origin, pmove->frametime, pmove->velocity, dest); + VectorCopy (dest, start); + start[2] += pmove->movevars->stepsize + 1; + trace = pmove->PM_PlayerTrace (start, dest, PM_NORMAL, -1 ); + if (!trace.startsolid && !trace.allsolid) // FIXME: check steep slope? + { // walked up the step, so just keep result and exit + VectorCopy (trace.endpos, pmove->origin); + return; + } + + // Try moving straight along out normal path. + PM_FlyMove (); +} + + +/* +=================== +PM_AirMove + +=================== +*/ +void PM_AirMove (void) +{ + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + + // Copy movement amounts + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + // Zero out z components of movement vectors + pmove->forward[2] = 0; + pmove->right[2] = 0; + // Renormalize + VectorNormalize (pmove->forward); + VectorNormalize (pmove->right); + + // Determine x and y parts of velocity + for (i=0 ; i<2 ; i++) + { + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + } + // Zero out z part of velocity + wishvel[2] = 0; + + // Determine maginitude of speed of move + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + // Clamp to server defined max speed + if (wishspeed > pmove->maxspeed) + { + VectorScale (wishvel, pmove->maxspeed/wishspeed, wishvel); + wishspeed = pmove->maxspeed; + } + + PM_AirAccelerate (wishdir, wishspeed, pmove->movevars->airaccelerate); + + // Add in any base velocity to the current velocity. + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity ); + + PM_FlyMove (); +} + +qboolean PM_InWater( void ) +{ + return ( pmove->waterlevel > 1 ); +} + +/* +============= +PM_CheckWater + +Sets pmove->waterlevel and pmove->watertype values. +============= +*/ +qboolean PM_CheckWater () +{ + vec3_t point; + int cont; + int truecont; + float height; + float heightover2; + + // Pick a spot just above the players feet. + point[0] = pmove->origin[0] + (pmove->player_mins[pmove->usehull][0] + pmove->player_maxs[pmove->usehull][0]) * 0.5; + point[1] = pmove->origin[1] + (pmove->player_mins[pmove->usehull][1] + pmove->player_maxs[pmove->usehull][1]) * 0.5; + point[2] = pmove->origin[2] + pmove->player_mins[pmove->usehull][2] + 1; + + // Assume that we are not in water at all. + pmove->waterlevel = 0; + pmove->watertype = CONTENTS_EMPTY; + + // Grab point contents. + cont = pmove->PM_PointContents (point, &truecont ); + // Are we under water? (not solid and not empty?) + if (cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) + { + // Set water type + pmove->watertype = cont; + + // We are at least at level one + pmove->waterlevel = 1; + + height = (pmove->player_mins[pmove->usehull][2] + pmove->player_maxs[pmove->usehull][2]); + heightover2 = height * 0.5; + + // Now check a point that is at the player hull midpoint. + point[2] = pmove->origin[2] + heightover2; + cont = pmove->PM_PointContents (point, NULL ); + // If that point is also under water... + if (cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) + { + // Set a higher water level. + pmove->waterlevel = 2; + + // Now check the eye position. (view_ofs is relative to the origin) + point[2] = pmove->origin[2] + pmove->view_ofs[2]; + + cont = pmove->PM_PointContents (point, NULL ); + if (cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) + pmove->waterlevel = 3; // In over our eyes + } + + // Adjust velocity based on water current, if any. + if ( ( truecont <= CONTENTS_CURRENT_0 ) && + ( truecont >= CONTENTS_CURRENT_DOWN ) ) + { + // The deeper we are, the stronger the current. + static vec3_t current_table[] = + { + {1, 0, 0}, {0, 1, 0}, {-1, 0, 0}, + {0, -1, 0}, {0, 0, 1}, {0, 0, -1} + }; + + VectorMA (pmove->basevelocity, 50.0*pmove->waterlevel, current_table[CONTENTS_CURRENT_0 - truecont], pmove->basevelocity); + } + } + + return pmove->waterlevel > 1; +} + +/* +============= +PM_CatagorizePosition +============= +*/ +void PM_CatagorizePosition (void) +{ + vec3_t point; + pmtrace_t tr; + +// if the player hull point one unit down is solid, the player +// is on ground + +// see if standing on something solid + + // Doing this before we move may introduce a potential latency in water detection, but + // doing it after can get us stuck on the bottom in water if the amount we move up + // is less than the 1 pixel 'threshold' we're about to snap to. Also, we'll call + // this several times per frame, so we really need to avoid sticking to the bottom of + // water on each call, and the converse case will correct itself if called twice. + PM_CheckWater(); + + point[0] = pmove->origin[0]; + point[1] = pmove->origin[1]; + point[2] = pmove->origin[2] - 2; + + if (pmove->velocity[2] > 180) // Shooting up really fast. Definitely not on ground. + { + pmove->onground = -1; + } + else + { + // Try and move down. + tr = pmove->PM_PlayerTrace (pmove->origin, point, PM_NORMAL, -1 ); + // If we hit a steep plane, we are not on ground + if ( tr.plane.normal[2] < 0.7) + pmove->onground = -1; // too steep + else + pmove->onground = tr.ent; // Otherwise, point to index of ent under us. + + // If we are on something... + if (pmove->onground != -1) + { + // Then we are not in water jump sequence + pmove->waterjumptime = 0; + // If we could make the move, drop us down that 1 pixel + if (pmove->waterlevel < 2 && !tr.startsolid && !tr.allsolid) + VectorCopy (tr.endpos, pmove->origin); + } + + // Standing on an entity other than the world + if (tr.ent > 0) // So signal that we are touching something. + { + PM_AddToTouched(tr, pmove->velocity); + } + } +} + +/* +================= +PM_GetRandomStuckOffsets + +When a player is stuck, it's costly to try and unstick them +Grab a test offset for the player based on a passed in index +================= +*/ +int PM_GetRandomStuckOffsets(int nIndex, int server, vec3_t offset) +{ + // Last time we did a full + int idx; + idx = rgStuckLast[nIndex][server]++; + + VectorCopy(rgv3tStuckTable[idx % 54], offset); + + return (idx % 54); +} + +void PM_ResetStuckOffsets(int nIndex, int server) +{ + rgStuckLast[nIndex][server] = 0; +} + +/* +================= +NudgePosition + +If pmove->origin is in a solid position, +try nudging slightly on all axis to +allow for the cut precision of the net coordinates +================= +*/ +#define PM_CHECKSTUCK_MINTIME 0.05 // Don't check again too quickly. + +int PM_CheckStuck (void) +{ + vec3_t base; + vec3_t offset; + vec3_t test; + int hitent; + int idx; + float fTime; + int i; + pmtrace_t traceresult; + + static float rgStuckCheckTime[MAX_CLIENTS][2]; // Last time we did a full + + // If position is okay, exit + hitent = pmove->PM_TestPlayerPosition (pmove->origin, &traceresult ); + if (hitent == -1 ) + { + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + return 0; + } + + VectorCopy (pmove->origin, base); + + // + // Deal with precision error in network. + // + if (!pmove->server) + { + // World or BSP model + if ( ( hitent == 0 ) || + ( pmove->physents[hitent].model != NULL ) ) + { + int nReps = 0; + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + do + { + i = PM_GetRandomStuckOffsets(pmove->player_index, pmove->server, offset); + + VectorAdd(base, offset, test); + if (pmove->PM_TestPlayerPosition (test, &traceresult ) == -1) + { + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + + VectorCopy ( test, pmove->origin ); + return 0; + } + nReps++; + } while (nReps < 54); + } + } + + // Only an issue on the client. + + if (pmove->server) + idx = 0; + else + idx = 1; + + fTime = pmove->Sys_FloatTime(); + // Too soon? + if (rgStuckCheckTime[pmove->player_index][idx] >= + ( fTime - PM_CHECKSTUCK_MINTIME ) ) + { + return 1; + } + rgStuckCheckTime[pmove->player_index][idx] = fTime; + + pmove->PM_StuckTouch( hitent, &traceresult ); + + i = PM_GetRandomStuckOffsets(pmove->player_index, pmove->server, offset); + + VectorAdd(base, offset, test); + if ( ( hitent = pmove->PM_TestPlayerPosition ( test, NULL ) ) == -1 ) + { + //Con_DPrintf("Nudged\n"); + + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + + if (i >= 27) + VectorCopy ( test, pmove->origin ); + + return 0; + } + + // If player is flailing while stuck in another player ( should never happen ), then see + // if we can't "unstick" them forceably. + if ( pmove->cmd.buttons & ( IN_JUMP | IN_DUCK | IN_ATTACK ) && ( pmove->physents[ hitent ].player != 0 ) ) + { + float x, y, z; + float xystep = 8.0; + float zstep = 18.0; + float xyminmax = xystep; + float zminmax = 4 * zstep; + + for ( z = 0; z <= zminmax; z += zstep ) + { + for ( x = -xyminmax; x <= xyminmax; x += xystep ) + { + for ( y = -xyminmax; y <= xyminmax; y += xystep ) + { + VectorCopy( base, test ); + test[0] += x; + test[1] += y; + test[2] += z; + + if ( pmove->PM_TestPlayerPosition ( test, NULL ) == -1 ) + { + VectorCopy( test, pmove->origin ); + return 0; + } + } + } + } + } + + //VectorCopy (base, pmove->origin); + + return 1; +} + +/* +=============== +PM_SpectatorMove +=============== +*/ +void PM_SpectatorMove (void) +{ + float speed, drop, friction, control, newspeed; + //float accel; + float currentspeed, addspeed, accelspeed; + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + // this routine keeps track of the spectators psoition + // there a two different main move types : track player or moce freely (OBS_ROAMING) + // doesn't need excate track position, only to generate PVS, so just copy + // targets position and real view position is calculated on client (saves server CPU) + + if ( pmove->iuser1 == OBS_ROAMING) + { + +#ifdef CLIENT_DLL + // jump only in roaming mode + if ( iJumpSpectator ) + { + VectorCopy( vJumpOrigin, pmove->origin ); + VectorCopy( vJumpAngles, pmove->angles ); + VectorCopy( vec3_origin, pmove->velocity ); + iJumpSpectator = 0; + return; + } + #endif + // Move around in normal spectator method + + speed = Length (pmove->velocity); + if (speed < 1) + { + VectorCopy (vec3_origin, pmove->velocity) + } + else + { + drop = 0; + + friction = pmove->movevars->friction*1.5; // extra friction + control = speed < pmove->movevars->stopspeed ? pmove->movevars->stopspeed : speed; + drop += control*friction*pmove->frametime; + + // scale the velocity + newspeed = speed - drop; + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + + VectorScale (pmove->velocity, newspeed, pmove->velocity); + } + + // accelerate + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + VectorNormalize (pmove->forward); + VectorNormalize (pmove->right); + + for (i=0 ; i<3 ; i++) + { + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + } + wishvel[2] += pmove->cmd.upmove; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + // + // clamp to server defined max speed + // + if (wishspeed > pmove->movevars->spectatormaxspeed) + { + VectorScale (wishvel, pmove->movevars->spectatormaxspeed/wishspeed, wishvel); + wishspeed = pmove->movevars->spectatormaxspeed; + } + + currentspeed = DotProduct(pmove->velocity, wishdir); + addspeed = wishspeed - currentspeed; + if (addspeed <= 0) + return; + + accelspeed = pmove->movevars->accelerate*pmove->frametime*wishspeed; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i=0 ; i<3 ; i++) + pmove->velocity[i] += accelspeed*wishdir[i]; + + // move + VectorMA (pmove->origin, pmove->frametime, pmove->velocity, pmove->origin); + } + else + { + // all other modes just track some kind of target, so spectator PVS = target PVS + + int target; + + // no valid target ? + if ( pmove->iuser2 <= 0) + return; + + // Find the client this player's targeting + for (target = 0; target < pmove->numphysent; target++) + { + if ( pmove->physents[target].info == pmove->iuser2 ) + break; + } + + if (target == pmove->numphysent) + return; + + // use targets position as own origin for PVS + VectorCopy( pmove->physents[target].angles, pmove->angles ); + VectorCopy( pmove->physents[target].origin, pmove->origin ); + + // no velocity + VectorCopy( vec3_origin, pmove->velocity ); + } +} + +/* +================== +PM_SplineFraction + +Use for ease-in, ease-out style interpolation (accel/decel) +Used by ducking code. +================== +*/ +float PM_SplineFraction( float value, float scale ) +{ + float valueSquared; + + value = scale * value; + valueSquared = value * value; + + // Nice little ease-in, ease-out spline-like curve + return 3 * valueSquared - 2 * valueSquared * value; +} + +void PM_FixPlayerCrouchStuck( int direction ) +{ + int hitent; + int i; + vec3_t test; + + hitent = pmove->PM_TestPlayerPosition ( pmove->origin, NULL ); + if (hitent == -1 ) + return; + + VectorCopy( pmove->origin, test ); + for ( i = 0; i < 36; i++ ) + { + pmove->origin[2] += direction; + hitent = pmove->PM_TestPlayerPosition ( pmove->origin, NULL ); + if (hitent == -1 ) + return; + } + + VectorCopy( test, pmove->origin ); // Failed +} + +void PM_UnDuck( void ) +{ + int i; + pmtrace_t trace; + vec3_t newOrigin; + + VectorCopy( pmove->origin, newOrigin ); + + if ( pmove->onground != -1 ) + { + for ( i = 0; i < 3; i++ ) + { + newOrigin[i] += ( pmove->player_mins[1][i] - pmove->player_mins[0][i] ); + } + } + + trace = pmove->PM_PlayerTrace( newOrigin, newOrigin, PM_NORMAL, -1 ); + + if ( !trace.startsolid ) + { + pmove->usehull = 0; + + // Oh, no, changing hulls stuck us into something, try unsticking downward first. + trace = pmove->PM_PlayerTrace( newOrigin, newOrigin, PM_NORMAL, -1 ); + if ( trace.startsolid ) + { + // See if we are stuck? If so, stay ducked with the duck hull until we have a clear spot + //Con_Printf( "unstick got stuck\n" ); + pmove->usehull = 1; + return; + } + + pmove->flags &= ~FL_DUCKING; + pmove->bInDuck = false; + pmove->view_ofs[2] = VEC_VIEW; + pmove->flDuckTime = 0; + + VectorCopy( newOrigin, pmove->origin ); + + // Recatagorize position since ducking can change origin + PM_CatagorizePosition(); + } +} + +void PM_Duck( void ) +{ + int i; + float time; + float duckFraction; + + int buttonsChanged = ( pmove->oldbuttons ^ pmove->cmd.buttons ); // These buttons have changed this frame + int nButtonPressed = buttonsChanged & pmove->cmd.buttons; // The changed ones still down are "pressed" + + if ( pmove->cmd.buttons & IN_DUCK ) + { + pmove->oldbuttons |= IN_DUCK; + } + else + { + pmove->oldbuttons &= ~IN_DUCK; + } + + // Prevent ducking if the iuser3 variable is set + if ( pmove->iuser3 || pmove->dead ) + { + // Try to unduck + if ( pmove->flags & FL_DUCKING ) + { + PM_UnDuck(); + } + return; + } + + if ( pmove->flags & FL_DUCKING ) + { + pmove->cmd.forwardmove *= 0.333; + pmove->cmd.sidemove *= 0.333; + pmove->cmd.upmove *= 0.333; + } + + if ( ( pmove->cmd.buttons & IN_DUCK ) || ( pmove->bInDuck ) || ( pmove->flags & FL_DUCKING ) ) + { + if ( pmove->cmd.buttons & IN_DUCK ) + { + if ( (nButtonPressed & IN_DUCK ) && !( pmove->flags & FL_DUCKING ) ) + { + // Use 1 second so super long jump will work + pmove->flDuckTime = 1000; + pmove->bInDuck = true; + } + + time = max( 0.0, ( 1.0 - (float)pmove->flDuckTime / 1000.0 ) ); + + if ( pmove->bInDuck ) + { + // Finish ducking immediately if duck time is over or not on ground + if ( ( (float)pmove->flDuckTime / 1000.0 <= ( 1.0 - TIME_TO_DUCK ) ) || + ( pmove->onground == -1 ) ) + { + pmove->usehull = 1; + pmove->view_ofs[2] = VEC_DUCK_VIEW; + pmove->flags |= FL_DUCKING; + pmove->bInDuck = false; + + // HACKHACK - Fudge for collision bug - no time to fix this properly + if ( pmove->onground != -1 ) + { + for ( i = 0; i < 3; i++ ) + { + pmove->origin[i] -= ( pmove->player_mins[1][i] - pmove->player_mins[0][i] ); + } + // See if we are stuck? + PM_FixPlayerCrouchStuck( STUCK_MOVEUP ); + + // Recatagorize position since ducking can change origin + PM_CatagorizePosition(); + } + } + else + { + float fMore = (VEC_DUCK_HULL_MIN - VEC_HULL_MIN); + + // Calc parametric time + duckFraction = PM_SplineFraction( time, (1.0/TIME_TO_DUCK) ); + pmove->view_ofs[2] = ((VEC_DUCK_VIEW - fMore ) * duckFraction) + (VEC_VIEW * (1-duckFraction)); + } + } + } + else + { + // Try to unduck + PM_UnDuck(); + } + } +} + +void PM_LadderMove( physent_t *pLadder ) +{ + vec3_t ladderCenter; + trace_t trace; + qboolean onFloor; + vec3_t floor; + vec3_t modelmins, modelmaxs; + + if ( pmove->movetype == MOVETYPE_NOCLIP ) + return; + + pmove->PM_GetModelBounds( pLadder->model, modelmins, modelmaxs ); + + VectorAdd( modelmins, modelmaxs, ladderCenter ); + VectorScale( ladderCenter, 0.5, ladderCenter ); + + pmove->movetype = MOVETYPE_FLY; + + // On ladder, convert movement to be relative to the ladder + + VectorCopy( pmove->origin, floor ); + floor[2] += pmove->player_mins[pmove->usehull][2] - 1; + + if ( pmove->PM_PointContents( floor, NULL ) == CONTENTS_SOLID ) + onFloor = true; + else + onFloor = false; + + pmove->gravity = 0; + pmove->PM_TraceModel( pLadder, pmove->origin, ladderCenter, &trace ); + if ( trace.fraction != 1.0 ) + { + float forward = 0, right = 0; + vec3_t vpn, v_right; + + AngleVectors( pmove->angles, vpn, v_right, NULL ); + if ( pmove->cmd.buttons & IN_BACK ) + forward -= MAX_CLIMB_SPEED; + if ( pmove->cmd.buttons & IN_FORWARD ) + forward += MAX_CLIMB_SPEED; + if ( pmove->cmd.buttons & IN_MOVELEFT ) + right -= MAX_CLIMB_SPEED; + if ( pmove->cmd.buttons & IN_MOVERIGHT ) + right += MAX_CLIMB_SPEED; + + if ( pmove->cmd.buttons & IN_JUMP ) + { + pmove->movetype = MOVETYPE_WALK; + VectorScale( trace.plane.normal, 270, pmove->velocity ); + } + else + { + if ( forward != 0 || right != 0 ) + { + vec3_t velocity, perp, cross, lateral, tmp; + float normal; + + //ALERT(at_console, "pev %.2f %.2f %.2f - ", + // pev->velocity.x, pev->velocity.y, pev->velocity.z); + // Calculate player's intended velocity + //Vector velocity = (forward * gpGlobals->v_forward) + (right * gpGlobals->v_right); + VectorScale( vpn, forward, velocity ); + VectorMA( velocity, right, v_right, velocity ); + + + // Perpendicular in the ladder plane + // Vector perp = CrossProduct( Vector(0,0,1), trace.vecPlaneNormal ); + // perp = perp.Normalize(); + VectorClear( tmp ); + tmp[2] = 1; + CrossProduct( tmp, trace.plane.normal, perp ); + VectorNormalize( perp ); + + + // decompose velocity into ladder plane + normal = DotProduct( velocity, trace.plane.normal ); + // This is the velocity into the face of the ladder + VectorScale( trace.plane.normal, normal, cross ); + + + // This is the player's additional velocity + VectorSubtract( velocity, cross, lateral ); + + // This turns the velocity into the face of the ladder into velocity that + // is roughly vertically perpendicular to the face of the ladder. + // NOTE: It IS possible to face up and move down or face down and move up + // because the velocity is a sum of the directional velocity and the converted + // velocity through the face of the ladder -- by design. + CrossProduct( trace.plane.normal, perp, tmp ); + VectorMA( lateral, -normal, tmp, pmove->velocity ); + if ( onFloor && normal > 0 ) // On ground moving away from the ladder + { + VectorMA( pmove->velocity, MAX_CLIMB_SPEED, trace.plane.normal, pmove->velocity ); + } + //pev->velocity = lateral - (CrossProduct( trace.vecPlaneNormal, perp ) * normal); + } + else + { + VectorClear( pmove->velocity ); + } + } + } +} + +physent_t *PM_Ladder( void ) +{ + int i; + physent_t *pe; + hull_t *hull; + int num; + vec3_t test; + + for ( i = 0; i < pmove->nummoveent; i++ ) + { + pe = &pmove->moveents[i]; + + if ( pe->model && (modtype_t)pmove->PM_GetModelType( pe->model ) == mod_brush && pe->skin == CONTENTS_LADDER ) + { + + hull = (hull_t *)pmove->PM_HullForBsp( pe, test ); + num = hull->firstclipnode; + + // Offset the test point appropriately for this hull. + VectorSubtract ( pmove->origin, test, test); + + // Test the player's hull for intersection with this model + if ( pmove->PM_HullPointContents (hull, num, test) == CONTENTS_EMPTY) + continue; + + return pe; + } + } + + return NULL; +} + + + +void PM_WaterJump (void) +{ + if ( pmove->waterjumptime > 10000 ) + { + pmove->waterjumptime = 10000; + } + + if ( !pmove->waterjumptime ) + return; + + pmove->waterjumptime -= pmove->cmd.msec; + if ( pmove->waterjumptime < 0 || + !pmove->waterlevel ) + { + pmove->waterjumptime = 0; + pmove->flags &= ~FL_WATERJUMP; + } + + pmove->velocity[0] = pmove->movedir[0]; + pmove->velocity[1] = pmove->movedir[1]; +} + +/* +============ +PM_AddGravity + +============ +*/ +void PM_AddGravity () +{ + float ent_gravity; + + if (pmove->gravity) + ent_gravity = pmove->gravity; + else + ent_gravity = 1.0; + + // Add gravity incorrectly + pmove->velocity[2] -= (ent_gravity * pmove->movevars->gravity * pmove->frametime ); + pmove->velocity[2] += pmove->basevelocity[2] * pmove->frametime; + pmove->basevelocity[2] = 0; + PM_CheckVelocity(); +} +/* +============ +PM_PushEntity + +Does not change the entities velocity at all +============ +*/ +pmtrace_t PM_PushEntity (vec3_t push) +{ + pmtrace_t trace; + vec3_t end; + + VectorAdd (pmove->origin, push, end); + + trace = pmove->PM_PlayerTrace (pmove->origin, end, PM_NORMAL, -1 ); + + VectorCopy (trace.endpos, pmove->origin); + + // So we can run impact function afterwards. + if (trace.fraction < 1.0 && + !trace.allsolid) + { + PM_AddToTouched(trace, pmove->velocity); + } + + return trace; +} + +/* +============ +PM_Physics_Toss() + +Dead player flying through air., e.g. +============ +*/ +void PM_Physics_Toss() +{ + pmtrace_t trace; + vec3_t move; + float backoff; + + PM_CheckWater(); + + if (pmove->velocity[2] > 0) + pmove->onground = -1; + + // If on ground and not moving, return. + if ( pmove->onground != -1 ) + { + if (VectorCompare(pmove->basevelocity, vec3_origin) && + VectorCompare(pmove->velocity, vec3_origin)) + return; + } + + PM_CheckVelocity (); + +// add gravity + if ( pmove->movetype != MOVETYPE_FLY && + pmove->movetype != MOVETYPE_BOUNCEMISSILE && + pmove->movetype != MOVETYPE_FLYMISSILE ) + PM_AddGravity (); + +// move origin + // Base velocity is not properly accounted for since this entity will move again after the bounce without + // taking it into account + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity); + + PM_CheckVelocity(); + VectorScale (pmove->velocity, pmove->frametime, move); + VectorSubtract (pmove->velocity, pmove->basevelocity, pmove->velocity); + + trace = PM_PushEntity (move); // Should this clear basevelocity + + PM_CheckVelocity(); + + if (trace.allsolid) + { + // entity is trapped in another solid + pmove->onground = trace.ent; + VectorCopy (vec3_origin, pmove->velocity); + return; + } + + if (trace.fraction == 1) + { + PM_CheckWater(); + return; + } + + + if (pmove->movetype == MOVETYPE_BOUNCE) + backoff = 2.0 - pmove->friction; + else if (pmove->movetype == MOVETYPE_BOUNCEMISSILE) + backoff = 2.0; + else + backoff = 1; + + PM_ClipVelocity (pmove->velocity, trace.plane.normal, pmove->velocity, backoff); + + // stop if on ground + if (trace.plane.normal[2] > 0.7) + { + float vel; + vec3_t base; + + VectorClear( base ); + if (pmove->velocity[2] < pmove->movevars->gravity * pmove->frametime) + { + // we're rolling on the ground, add static friction. + pmove->onground = trace.ent; + pmove->velocity[2] = 0; + } + + vel = DotProduct( pmove->velocity, pmove->velocity ); + + // Con_DPrintf("%f %f: %.0f %.0f %.0f\n", vel, trace.fraction, ent->velocity[0], ent->velocity[1], ent->velocity[2] ); + + if (vel < (30 * 30) || (pmove->movetype != MOVETYPE_BOUNCE && pmove->movetype != MOVETYPE_BOUNCEMISSILE)) + { + pmove->onground = trace.ent; + VectorCopy (vec3_origin, pmove->velocity); + } + else + { + VectorScale (pmove->velocity, (1.0 - trace.fraction) * pmove->frametime * 0.9, move); + trace = PM_PushEntity (move); + } + VectorSubtract( pmove->velocity, base, pmove->velocity ) + } + +// check for in water + PM_CheckWater(); +} + +/* +==================== +PM_NoClip + +==================== +*/ +void PM_NoClip() +{ + int i; + vec3_t wishvel; + float fmove, smove; +// float currentspeed, addspeed, accelspeed; + + // Copy movement amounts + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + VectorNormalize ( pmove->forward ); + VectorNormalize ( pmove->right ); + + for (i=0 ; i<3 ; i++) // Determine x and y parts of velocity + { + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + } + wishvel[2] += pmove->cmd.upmove; + + VectorMA (pmove->origin, pmove->frametime, wishvel, pmove->origin); + + // Zero out the velocity so that we don't accumulate a huge downward velocity from + // gravity, etc. + VectorClear( pmove->velocity ); + +} + +// Only allow bunny jumping up to 1.7x server / player maxspeed setting +#define BUNNYJUMP_MAX_SPEED_FACTOR 1.7f + +//----------------------------------------------------------------------------- +// Purpose: Corrects bunny jumping ( where player initiates a bunny jump before other +// movement logic runs, thus making onground == -1 thus making PM_Friction get skipped and +// running PM_AirMove, which doesn't crop velocity to maxspeed like the ground / other +// movement logic does. +//----------------------------------------------------------------------------- +void PM_PreventMegaBunnyJumping( void ) +{ + // Current player speed + float spd; + // If we have to crop, apply this cropping fraction to velocity + float fraction; + // Speed at which bunny jumping is limited + float maxscaledspeed; + + maxscaledspeed = BUNNYJUMP_MAX_SPEED_FACTOR * pmove->maxspeed; + + // Don't divide by zero + if ( maxscaledspeed <= 0.0f ) + return; + + spd = Length( pmove->velocity ); + + if ( spd <= maxscaledspeed ) + return; + + fraction = ( maxscaledspeed / spd ) * 0.65; //Returns the modifier for the velocity + + VectorScale( pmove->velocity, fraction, pmove->velocity ); //Crop it down!. +} + +/* +============= +PM_Jump +============= +*/ +void PM_Jump (void) +{ + int i; + qboolean tfc = false; + + qboolean cansuperjump = false; + + if (pmove->dead) + { + pmove->oldbuttons |= IN_JUMP ; // don't jump again until released + 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 ) + { + pmove->waterjumptime -= pmove->cmd.msec; + if (pmove->waterjumptime < 0) + { + pmove->waterjumptime = 0; + } + return; + } + + // If we are in the water most of the way... + if (pmove->waterlevel >= 2) + { // swimming, not jumping + pmove->onground = -1; + + if (pmove->watertype == CONTENTS_WATER) // We move up a certain amount + pmove->velocity[2] = 100; + else if (pmove->watertype == CONTENTS_SLIME) + pmove->velocity[2] = 80; + else // LAVA + pmove->velocity[2] = 50; + + // play swiming sound + if ( pmove->flSwimTime <= 0 ) + { + // Don't play sound again for 1 second + pmove->flSwimTime = 1000; + switch ( pmove->RandomLong( 0, 3 ) ) + { + case 0: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 1: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 2: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 3: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + } + } + + return; + } + + // No more effect + if ( pmove->onground == -1 ) + { + // Flag that we jumped. + // HACK HACK HACK + // Remove this when the game .dll no longer does physics code!!!! + pmove->oldbuttons |= IN_JUMP; // don't jump again until released + return; // in air, so no effect + } + + if ( pmove->oldbuttons & IN_JUMP ) + return; // don't pogo stick + + // In the air now. + pmove->onground = -1; + + 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 ); + } + + // See if user can super long jump? + cansuperjump = atoi( pmove->PM_Info_ValueForKey( pmove->physinfo, "slj" ) ) == 1 ? true : false; + + // Acclerate upward + // If we are ducking... + if ( ( pmove->bInDuck ) || ( pmove->flags & FL_DUCKING ) ) + { + // Adjust for super long jump module + // UNDONE -- note this should be based on forward angles, not current velocity. + if ( cansuperjump && + ( pmove->cmd.buttons & IN_DUCK ) && + ( pmove->flDuckTime > 0 ) && + Length( pmove->velocity ) > 50 ) + { + pmove->punchangle[0] = -5; + + for (i =0; i < 2; i++) + { + pmove->velocity[i] = pmove->forward[i] * PLAYER_LONGJUMP_SPEED * 1.6; + } + + pmove->velocity[2] = sqrt(2 * 800 * 56.0); + } + else + { + pmove->velocity[2] = sqrt(2 * 800 * 45.0); + } + } + else + { + pmove->velocity[2] = sqrt(2 * 800 * 45.0); + } + + // Decay it for simulation + PM_FixupGravityVelocity(); + + // Flag that we jumped. + pmove->oldbuttons |= IN_JUMP; // don't jump again until released +} + +/* +============= +PM_CheckWaterJump +============= +*/ +#define WJ_HEIGHT 8 +void PM_CheckWaterJump (void) +{ + vec3_t vecStart, vecEnd; + vec3_t flatforward; + vec3_t flatvelocity; + float curspeed; + pmtrace_t tr; + int savehull; + + // Already water jumping. + if ( pmove->waterjumptime ) + return; + + // Don't hop out if we just jumped in + if ( pmove->velocity[2] < -180 ) + return; // only hop out if we are moving up + + // See if we are backing up + flatvelocity[0] = pmove->velocity[0]; + flatvelocity[1] = pmove->velocity[1]; + flatvelocity[2] = 0; + + // Must be moving + curspeed = VectorNormalize( flatvelocity ); + + // see if near an edge + flatforward[0] = pmove->forward[0]; + flatforward[1] = pmove->forward[1]; + flatforward[2] = 0; + VectorNormalize (flatforward); + + // Are we backing into water from steps or something? If so, don't pop forward + if ( curspeed != 0.0 && ( DotProduct( flatvelocity, flatforward ) < 0.0 ) ) + return; + + VectorCopy( pmove->origin, vecStart ); + vecStart[2] += WJ_HEIGHT; + + VectorMA ( vecStart, 24, flatforward, vecEnd ); + + // Trace, this trace should use the point sized collision hull + savehull = pmove->usehull; + pmove->usehull = 2; + tr = pmove->PM_PlayerTrace( vecStart, vecEnd, PM_NORMAL, -1 ); + if ( tr.fraction < 1.0 && fabs( tr.plane.normal[2] ) < 0.1f ) // Facing a near vertical wall? + { + vecStart[2] += pmove->player_maxs[ savehull ][2] - WJ_HEIGHT; + VectorMA( vecStart, 24, flatforward, vecEnd ); + VectorMA( vec3_origin, -50, tr.plane.normal, pmove->movedir ); + + tr = pmove->PM_PlayerTrace( vecStart, vecEnd, PM_NORMAL, -1 ); + if ( tr.fraction == 1.0 ) + { + pmove->waterjumptime = 2000; + pmove->velocity[2] = 225; + pmove->oldbuttons |= IN_JUMP; + pmove->flags |= FL_WATERJUMP; + } + } + + // Reset the collision hull + pmove->usehull = savehull; +} + +void PM_CheckFalling( void ) +{ + if ( pmove->onground != -1 && + !pmove->dead && + pmove->flFallVelocity >= PLAYER_FALL_PUNCH_THRESHHOLD ) + { + float fvol = 0.5; + + if ( pmove->waterlevel > 0 ) + { + } + else if ( pmove->flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED ) + { + // NOTE: In the original game dll , there were no breaks after these cases, causing the first one to + // cascade into the second + //switch ( RandomLong(0,1) ) + //{ + //case 0: + //pmove->PM_PlaySound( CHAN_VOICE, "player/pl_fallpain2.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + //break; + //case 1: + pmove->PM_PlaySound( CHAN_VOICE, "player/pl_fallpain3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + // break; + //} + fvol = 1.0; + } + 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 ) + { + fvol = 0; + } + + if ( fvol > 0.0 ) + { + // Play landing step right away + pmove->flTimeStepSound = 0; + + PM_UpdateStepSound(); + + // play step sound for current texture + PM_PlayStepSound( PM_MapTextureTypeStepType( pmove->chtexturetype ), fvol ); + + // Knock the screen around a little bit, temporary effect + pmove->punchangle[ 2 ] = pmove->flFallVelocity * 0.013; // punch z axis + + if ( pmove->punchangle[ 0 ] > 8 ) + { + pmove->punchangle[ 0 ] = 8; + } + } + } + + if ( pmove->onground != -1 ) + { + pmove->flFallVelocity = 0; + } +} + +/* +================= +PM_PlayWaterSounds + +================= +*/ +void PM_PlayWaterSounds( void ) +{ + // Did we enter or leave water? + if ( ( pmove->oldwaterlevel == 0 && pmove->waterlevel != 0 ) || + ( pmove->oldwaterlevel != 0 && pmove->waterlevel == 0 ) ) + { + switch ( pmove->RandomLong(0,3) ) + { + case 0: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 1: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 2: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 3: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + } + } +} + +/* +=============== +PM_CalcRoll + +=============== +*/ +float PM_CalcRoll (vec3_t angles, vec3_t velocity, float rollangle, float rollspeed ) +{ + float sign; + float side; + float value; + vec3_t forward, right, up; + + AngleVectors (angles, forward, right, up); + + side = DotProduct (velocity, right); + + sign = side < 0 ? -1 : 1; + + side = fabs(side); + + value = rollangle; + + if (side < rollspeed) + { + side = side * value / rollspeed; + } + else + { + side = value; + } + + return side * sign; +} + +/* +============= +PM_DropPunchAngle + +============= +*/ +void PM_DropPunchAngle ( vec3_t punchangle ) +{ + float len; + + len = VectorNormalize ( punchangle ); + len -= (10.0 + len * 0.5) * pmove->frametime; + len = max( len, 0.0 ); + VectorScale ( punchangle, len, punchangle); +} + +/* +============== +PM_CheckParamters + +============== +*/ +void PM_CheckParamters( void ) +{ + float spd; + float maxspeed; + vec3_t v_angle; + + spd = ( pmove->cmd.forwardmove * pmove->cmd.forwardmove ) + + ( pmove->cmd.sidemove * pmove->cmd.sidemove ) + + ( pmove->cmd.upmove * pmove->cmd.upmove ); + spd = sqrt( spd ); + + maxspeed = pmove->clientmaxspeed; //atof( pmove->PM_Info_ValueForKey( pmove->physinfo, "maxspd" ) ); + if ( maxspeed != 0.0 ) + { + pmove->maxspeed = min( maxspeed, pmove->maxspeed ); + } + + if ( ( spd != 0.0 ) && + ( spd > pmove->maxspeed ) ) + { + float fRatio = pmove->maxspeed / spd; + pmove->cmd.forwardmove *= fRatio; + pmove->cmd.sidemove *= fRatio; + pmove->cmd.upmove *= fRatio; + } + + if ( pmove->flags & FL_FROZEN || + pmove->flags & FL_ONTRAIN || + pmove->dead ) + { + pmove->cmd.forwardmove = 0; + pmove->cmd.sidemove = 0; + pmove->cmd.upmove = 0; + } + + + PM_DropPunchAngle( pmove->punchangle ); + + // Take angles from command. + if ( !pmove->dead ) + { + VectorCopy ( pmove->cmd.viewangles, v_angle ); + VectorAdd( v_angle, pmove->punchangle, v_angle ); + + // Set up view angles. + pmove->angles[ROLL] = PM_CalcRoll ( v_angle, pmove->velocity, pmove->movevars->rollangle, pmove->movevars->rollspeed )*4; + pmove->angles[PITCH] = v_angle[PITCH]; + pmove->angles[YAW] = v_angle[YAW]; + } + else + { + VectorCopy( pmove->oldangles, pmove->angles ); + } + + // Set dead player view_offset + if ( pmove->dead ) + { + pmove->view_ofs[2] = PM_DEAD_VIEWHEIGHT; + } + + // Adjust client view angles to match values used on server. + if (pmove->angles[YAW] > 180.0f) + { + pmove->angles[YAW] -= 360.0f; + } + +} + +void PM_ReduceTimers( void ) +{ + if ( pmove->flTimeStepSound > 0 ) + { + pmove->flTimeStepSound -= pmove->cmd.msec; + if ( pmove->flTimeStepSound < 0 ) + { + pmove->flTimeStepSound = 0; + } + } + if ( pmove->flDuckTime > 0 ) + { + pmove->flDuckTime -= pmove->cmd.msec; + if ( pmove->flDuckTime < 0 ) + { + pmove->flDuckTime = 0; + } + } + if ( pmove->flSwimTime > 0 ) + { + pmove->flSwimTime -= pmove->cmd.msec; + if ( pmove->flSwimTime < 0 ) + { + pmove->flSwimTime = 0; + } + } +} + +/* +============= +PlayerMove + +Returns with origin, angles, and velocity modified in place. + +Numtouch and touchindex[] will be set if any of the physents +were contacted during the move. +============= +*/ +void PM_PlayerMove ( qboolean server ) +{ + physent_t *pLadder = NULL; + + // Are we running server code? + pmove->server = server; + + // Adjust speeds etc. + PM_CheckParamters(); + + // Assume we don't touch anything + pmove->numtouch = 0; + + // # of msec to apply movement + pmove->frametime = pmove->cmd.msec * 0.001; + + PM_ReduceTimers(); + + // Convert view angles to vectors + AngleVectors (pmove->angles, pmove->forward, pmove->right, pmove->up); + + // PM_ShowClipBox(); + + // Special handling for spectator and observers. (iuser1 is set if the player's in observer mode) + if ( pmove->spectator || pmove->iuser1 > 0 ) + { + PM_SpectatorMove(); + PM_CatagorizePosition(); + return; + } + + // Always try and unstick us unless we are in NOCLIP mode + if ( pmove->movetype != MOVETYPE_NOCLIP && pmove->movetype != MOVETYPE_NONE ) + { + if ( PM_CheckStuck() ) + { + return; // Can't move, we're stuck + } + } + + // Now that we are "unstuck", see where we are ( waterlevel and type, pmove->onground ). + PM_CatagorizePosition(); + + // Store off the starting water level + pmove->oldwaterlevel = pmove->waterlevel; + + // If we are not on ground, store off how fast we are moving down + if ( pmove->onground == -1 ) + { + pmove->flFallVelocity = -pmove->velocity[2]; + } + + g_onladder = 0; + // Don't run ladder code if dead or on a train + if ( !pmove->dead && !(pmove->flags & FL_ONTRAIN) ) + { + pLadder = PM_Ladder(); + if ( pLadder ) + { + g_onladder = 1; + } + } + + PM_UpdateStepSound(); + + PM_Duck(); + + // Don't run ladder code if dead or on a train + if ( !pmove->dead && !(pmove->flags & FL_ONTRAIN) ) + { + if ( pLadder ) + { + PM_LadderMove( pLadder ); + } + else if ( pmove->movetype != MOVETYPE_WALK && + pmove->movetype != MOVETYPE_NOCLIP ) + { + // Clear ladder stuff unless player is noclipping + // it will be set immediately again next frame if necessary + pmove->movetype = MOVETYPE_WALK; + } + } + + // Slow down, I'm pulling it! (a box maybe) but only when I'm standing on ground + if ( ( pmove->onground != -1 ) && ( pmove->cmd.buttons & IN_USE) ) + { + VectorScale( pmove->velocity, 0.3, pmove->velocity ); + } + + // Handle movement + switch ( pmove->movetype ) + { + default: + pmove->Con_DPrintf("Bogus pmove player movetype %i on (%i) 0=cl 1=sv\n", pmove->movetype, pmove->server); + break; + + case MOVETYPE_NONE: + break; + + case MOVETYPE_NOCLIP: + PM_NoClip(); + break; + + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + PM_Physics_Toss(); + break; + + case MOVETYPE_FLY: + + PM_CheckWater(); + + // Was jump button pressed? + // If so, set velocity to 270 away from ladder. This is currently wrong. + // Also, set MOVE_TYPE to walk, too. + if ( pmove->cmd.buttons & IN_JUMP ) + { + if ( !pLadder ) + { + PM_Jump (); + } + } + else + { + pmove->oldbuttons &= ~IN_JUMP; + } + + // Perform the move accounting for any base velocity. + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity); + PM_FlyMove (); + VectorSubtract (pmove->velocity, pmove->basevelocity, pmove->velocity); + break; + + case MOVETYPE_WALK: + if ( !PM_InWater() ) + { + PM_AddCorrectGravity(); + } + + // If we are leaping out of the water, just update the counters. + if ( pmove->waterjumptime ) + { + PM_WaterJump(); + PM_FlyMove(); + + // Make sure waterlevel is set correctly + PM_CheckWater(); + return; + } + + // If we are swimming in the water, see if we are nudging against a place we can jump up out + // of, and, if so, start out jump. Otherwise, if we are not moving up, then reset jump timer to 0 + if ( pmove->waterlevel >= 2 ) + { + if ( pmove->waterlevel == 2 ) + { + PM_CheckWaterJump(); + } + + // If we are falling again, then we must not trying to jump out of water any more. + if ( pmove->velocity[2] < 0 && pmove->waterjumptime ) + { + pmove->waterjumptime = 0; + } + + // Was jump button pressed? + if (pmove->cmd.buttons & IN_JUMP) + { + PM_Jump (); + } + else + { + pmove->oldbuttons &= ~IN_JUMP; + } + + // Perform regular water movement + PM_WaterMove(); + + VectorSubtract (pmove->velocity, pmove->basevelocity, pmove->velocity); + + // Get a final position + PM_CatagorizePosition(); + } + else + + // Not underwater + { + // Was jump button pressed? + if ( pmove->cmd.buttons & IN_JUMP ) + { + if ( !pLadder ) + { + PM_Jump (); + } + } + else + { + pmove->oldbuttons &= ~IN_JUMP; + } + + // Fricion is handled before we add in any base velocity. That way, if we are on a conveyor, + // we don't slow when standing still, relative to the conveyor. + if ( pmove->onground != -1 ) + { + pmove->velocity[2] = 0.0; + PM_Friction(); + } + + // Make sure velocity is valid. + PM_CheckVelocity(); + + // Are we on ground now + if ( pmove->onground != -1 ) + { + PM_WalkMove(); + } + else + { + PM_AirMove(); // Take into account movement when in air. + } + + // Set final flags. + PM_CatagorizePosition(); + + // Now pull the base velocity back out. + // Base velocity is set if you are on a moving object, like + // a conveyor (or maybe another monster?) + VectorSubtract (pmove->velocity, pmove->basevelocity, pmove->velocity ); + + // Make sure velocity is valid. + PM_CheckVelocity(); + + // Add any remaining gravitational component. + if ( !PM_InWater() ) + { + PM_FixupGravityVelocity(); + } + + // If we are on ground, no downward velocity. + if ( pmove->onground != -1 ) + { + pmove->velocity[2] = 0; + } + + // See if we landed on the ground with enough force to play + // a landing sound. + PM_CheckFalling(); + } + + // Did we enter or leave the water? + PM_PlayWaterSounds(); + break; + } +} + +void PM_CreateStuckTable( void ) +{ + float x, y, z; + int idx; + int i; + float zi[3]; + + memset(rgv3tStuckTable, 0, 54 * sizeof(vec3_t)); + + idx = 0; + // Little Moves. + x = y = 0; + // Z moves + for (z = -0.125 ; z <= 0.125 ; z += 0.125) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + x = z = 0; + // Y moves + for (y = -0.125 ; y <= 0.125 ; y += 0.125) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + y = z = 0; + // X moves + for (x = -0.125 ; x <= 0.125 ; x += 0.125) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + + // Remaining multi axis nudges. + for ( x = - 0.125; x <= 0.125; x += 0.250 ) + { + for ( y = - 0.125; y <= 0.125; y += 0.250 ) + { + for ( z = - 0.125; z <= 0.125; z += 0.250 ) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + } + } + + // Big Moves. + x = y = 0; + zi[0] = 0.0f; + zi[1] = 1.0f; + zi[2] = 6.0f; + + for (i = 0; i < 3; i++) + { + // Z moves + z = zi[i]; + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + + x = z = 0; + + // Y moves + for (y = -2.0f ; y <= 2.0f ; y += 2.0) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + y = z = 0; + // X moves + for (x = -2.0f ; x <= 2.0f ; x += 2.0f) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + + // Remaining multi axis nudges. + for (i = 0 ; i < 3; i++) + { + z = zi[i]; + + for (x = -2.0f ; x <= 2.0f ; x += 2.0f) + { + for (y = -2.0f ; y <= 2.0f ; y += 2.0) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + } + } +} + + + +/* +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 +invoked by each side as appropriate. There should be no distinction, internally, between server +and client. This will ensure that prediction behaves appropriately. +*/ + +void PM_Move ( struct playermove_s *ppmove, int server ) +{ + assert( pm_shared_initialized ); + + pmove = ppmove; + + PM_PlayerMove( ( server != 0 ) ? true : false ); + + if ( pmove->onground != -1 ) + { + pmove->flags |= FL_ONGROUND; + } + else + { + pmove->flags &= ~FL_ONGROUND; + } + + // In single player, reset friction after each movement to FrictionModifier Triggers work still. + if ( !pmove->multiplayer && ( pmove->movetype == MOVETYPE_WALK ) ) + { + pmove->friction = 1.0f; + } +} + +int PM_GetVisEntInfo( int ent ) +{ + if ( ent >= 0 && ent <= pmove->numvisent ) + { + return pmove->visents[ ent ].info; + } + return -1; +} + +int PM_GetPhysEntInfo( int ent ) +{ + if ( ent >= 0 && ent <= pmove->numphysent) + { + return pmove->physents[ ent ].info; + } + return -1; +} + +void PM_Init( struct playermove_s *ppmove ) +{ + assert( !pm_shared_initialized ); + + pmove = ppmove; + + PM_CreateStuckTable(); + PM_InitTextureTypes(); + + pm_shared_initialized = 1; +} diff --git a/hlsdk/pm_shared/pm_shared.h b/hlsdk/pm_shared/pm_shared.h new file mode 100644 index 00000000..153c539f --- /dev/null +++ b/hlsdk/pm_shared/pm_shared.h @@ -0,0 +1,40 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +// +// pm_shared.h +// +#if !defined( PM_SHAREDH ) +#define PM_SHAREDH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +void PM_Init( struct playermove_s *ppmove ); +void PM_Move ( struct playermove_s *ppmove, int server ); +char PM_FindTextureType( char *name ); + +// Spectator Movement modes (stored in pev->iuser1, so the physics code can get at them) +#define OBS_NONE 0 +#define OBS_CHASE_LOCKED 1 +#define OBS_CHASE_FREE 2 +#define OBS_ROAMING 3 +#define OBS_IN_EYE 4 +#define OBS_MAP_FREE 5 +#define OBS_MAP_CHASE 6 + +#endif diff --git a/hlsdk/ricochet/cl_dll/GameStudioModelRenderer.cpp b/hlsdk/ricochet/cl_dll/GameStudioModelRenderer.cpp new file mode 100644 index 00000000..1b0b0289 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/GameStudioModelRenderer.cpp @@ -0,0 +1,982 @@ +#include +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "com_model.h" +#include "studio.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "dlight.h" +#include "triangleapi.h" + +#include +#include +#include +#include + +#include "studio_util.h" +#include "r_studioint.h" + +#include "StudioModelRenderer.h" +#include "GameStudioModelRenderer.h" + +void Ricochet_GetSequence( int *seq, int *gaitseq ); +void Ricochet_GetOrientation( float *o, float *a ); + +float g_flStartScaleTime; +int iPrevRenderState; +int iRenderStateChanged; + +// Global engine <-> studio model rendering code interface +extern engine_studio_api_t IEngineStudio; + +typedef struct +{ + vec3_t origin; + vec3_t angles; + + vec3_t realangles; + + float animtime; + float frame; + int sequence; + int gaitsequence; + float framerate; + + int m_fSequenceLoops; + int m_fSequenceFinished; + + byte controller[ 4 ]; + byte blending[ 2 ]; + + latchedvars_t lv; +} client_anim_state_t; + +static client_anim_state_t g_state; +static client_anim_state_t g_clientstate; + +// The renderer object, created on the stack. +CGameStudioModelRenderer g_StudioRenderer; +/* +==================== +CGameStudioModelRenderer + +==================== +*/ +CGameStudioModelRenderer::CGameStudioModelRenderer( void ) +{ + // If you want to predict animations locally, set this to TRUE + // NOTE: The animation code is somewhat broken, but gives you a sense for how + // to do client side animation of the predicted player in a third person game. + m_bLocal = false; +} + +/* +==================== +StudioSetupBones + +==================== +*/ +void CGameStudioModelRenderer::StudioSetupBones ( void ) +{ + int i; + double f; + + mstudiobone_t *pbones; + mstudioseqdesc_t *pseqdesc; + mstudioanim_t *panim; + + static float pos[MAXSTUDIOBONES][3]; + static vec4_t q[MAXSTUDIOBONES]; + float bonematrix[3][4]; + + static float pos2[MAXSTUDIOBONES][3]; + static vec4_t q2[MAXSTUDIOBONES]; + static float pos3[MAXSTUDIOBONES][3]; + static vec4_t q3[MAXSTUDIOBONES]; + static float pos4[MAXSTUDIOBONES][3]; + static vec4_t q4[MAXSTUDIOBONES]; + + // Use default bone setup for nonplayers + if ( !m_pCurrentEntity->player ) + { + CStudioModelRenderer::StudioSetupBones(); + return; + } + + // Bound sequence number. + if ( m_pCurrentEntity->curstate.sequence >= m_pStudioHeader->numseq ) + { + m_pCurrentEntity->curstate.sequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + if ( m_pPlayerInfo && m_pPlayerInfo->gaitsequence != 0 ) + { + f = m_pPlayerInfo->gaitframe; + } + else + { + f = StudioEstimateFrame( pseqdesc ); + } + + // Discwar knows how to do three way blending + if ( pseqdesc->numblends == 3 ) + { + float s; + + // Get left anim + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + + // Blending is 0-127 == Left to Middle, 128 to 255 == Middle to right + if ( m_pCurrentEntity->curstate.blending[0] <= 127 ) + { + StudioCalcRotations( pos, q, pseqdesc, panim, f ); + + // Scale 0-127 blending up to 0-255 + s = m_pCurrentEntity->curstate.blending[0]; + s = ( s * 2.0 ); + } + else + { + + // Skip ahead to middle + panim += m_pStudioHeader->numbones; + + StudioCalcRotations( pos, q, pseqdesc, panim, f ); + + // Scale 127-255 blending up to 0-255 + s = m_pCurrentEntity->curstate.blending[0]; + s = 2.0 * ( s - 127.0 ); + } + + // Normalize interpolant + s /= 255.0; + + // Go to middle or right + panim += m_pStudioHeader->numbones; + + StudioCalcRotations( pos2, q2, pseqdesc, panim, f ); + + // Spherically interpolate the bones + StudioSlerpBones( q, pos, q2, pos2, s ); + } + else + { + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + StudioCalcRotations( pos, q, pseqdesc, panim, f ); + } + + // Are we in the process of transitioning from one sequence to another. + if ( m_fDoInterp && + m_pCurrentEntity->latched.sequencetime && + ( m_pCurrentEntity->latched.sequencetime + 0.2 > m_clTime ) && + ( m_pCurrentEntity->latched.prevsequence < m_pStudioHeader->numseq )) + { + // blend from last sequence + static float pos1b[MAXSTUDIOBONES][3]; + static vec4_t q1b[MAXSTUDIOBONES]; + float s; + + // Blending value into last sequence + unsigned char prevseqblending = m_pCurrentEntity->latched.prevseqblending[ 0 ]; + + // Point at previous sequenece + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->latched.prevsequence; + + // Know how to do three way blends + if ( pseqdesc->numblends == 3 ) + { + float s; + + // Get left animation + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + + if ( prevseqblending <= 127 ) + { + // Set up bones based on final frame of previous sequence + StudioCalcRotations( pos1b, q1b, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + s = prevseqblending; + s = ( s * 2.0 ); + } + else + { + // Skip to middle blend + panim += m_pStudioHeader->numbones; + + StudioCalcRotations( pos1b, q1b, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + s = prevseqblending; + s = 2.0 * ( s - 127.0 ); + } + + // Normalize + s /= 255.0; + + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos2, q2, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + // Interpolate bones + StudioSlerpBones( q1b, pos1b, q2, pos2, s ); + } + else + { + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + // clip prevframe + StudioCalcRotations( pos1b, q1b, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + } + + // Now blend last frame of previous sequence with current sequence. + s = 1.0 - (m_clTime - m_pCurrentEntity->latched.sequencetime) / 0.2; + StudioSlerpBones( q, pos, q1b, pos1b, s ); + } + else + { + m_pCurrentEntity->latched.prevframe = f; + } + + // Now convert quaternions and bone positions into matrices + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + QuaternionMatrix( q[i], bonematrix ); + + bonematrix[0][3] = pos[i][0]; + bonematrix[1][3] = pos[i][1]; + bonematrix[2][3] = pos[i][2]; + + if (pbones[i].parent == -1) + { + if ( IEngineStudio.IsHardware() ) + { + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + } + else + { + ConcatTransforms ((*m_paliastransform), bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + } + + // Apply client-side effects to the transformation matrix + StudioFxTransform( m_pCurrentEntity, (*m_pbonetransform)[i] ); + } + else + { + ConcatTransforms ((*m_pbonetransform)[pbones[i].parent], bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_plighttransform)[pbones[i].parent], bonematrix, (*m_plighttransform)[i]); + } + } +} + +/* +==================== +StudioEstimateGait + +==================== +*/ +void CGameStudioModelRenderer::StudioEstimateGait( entity_state_t *pplayer ) +{ + float dt; + vec3_t est_velocity; + + dt = (m_clTime - m_clOldTime); + dt = max( 0.0, dt ); + dt = min( 1.0, dt ); + + if (dt == 0 || m_pPlayerInfo->renderframe == m_nFrameCount) + { + m_flGaitMovement = 0; + return; + } + + // VectorAdd( pplayer->velocity, pplayer->prediction_error, est_velocity ); + if ( m_fGaitEstimation ) + { + VectorSubtract( m_pCurrentEntity->origin, m_pPlayerInfo->prevgaitorigin, est_velocity ); + VectorCopy( m_pCurrentEntity->origin, m_pPlayerInfo->prevgaitorigin ); + m_flGaitMovement = Length( est_velocity ); + if (dt <= 0 || m_flGaitMovement / dt < 5) + { + m_flGaitMovement = 0; + est_velocity[0] = 0; + est_velocity[1] = 0; + } + } + else + { + VectorCopy( pplayer->velocity, est_velocity ); + m_flGaitMovement = Length( est_velocity ) * dt; + } + + if (est_velocity[1] == 0 && est_velocity[0] == 0) + { + float flYawDiff = m_pCurrentEntity->angles[YAW] - m_pPlayerInfo->gaityaw; + flYawDiff = flYawDiff - (int)(flYawDiff / 360) * 360; + if (flYawDiff > 180) + flYawDiff -= 360; + if (flYawDiff < -180) + flYawDiff += 360; + + if (dt < 0.25) + flYawDiff *= dt * 4; + else + flYawDiff *= dt; + + m_pPlayerInfo->gaityaw += flYawDiff; + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - (int)(m_pPlayerInfo->gaityaw / 360) * 360; + + m_flGaitMovement = 0; + } + else + { + m_pPlayerInfo->gaityaw = (atan2(est_velocity[1], est_velocity[0]) * 180 / M_PI); + if (m_pPlayerInfo->gaityaw > 180) + m_pPlayerInfo->gaityaw = 180; + if (m_pPlayerInfo->gaityaw < -180) + m_pPlayerInfo->gaityaw = -180; + } + +} + +/* +==================== +StudioProcessGait + +==================== +*/ +void CGameStudioModelRenderer::StudioProcessGait( entity_state_t *pplayer ) +{ + mstudioseqdesc_t *pseqdesc; + float dt; + float flYaw; // view direction relative to movement + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + m_pCurrentEntity->angles[PITCH] = 0; + m_pCurrentEntity->latched.prevangles[PITCH] = m_pCurrentEntity->angles[PITCH]; + + dt = (m_clTime - m_clOldTime); + dt = max( 0.0, dt ); + dt = min( 1.0, dt ); + + StudioEstimateGait( pplayer ); + + // calc side to side turning + flYaw = m_pCurrentEntity->angles[YAW] - m_pPlayerInfo->gaityaw; + + flYaw = fmod( flYaw, 360.0f ); + + if (flYaw < -180) + { + flYaw = flYaw + 360; + } + else if (flYaw > 180) + { + flYaw = flYaw - 360; + } + + float maxyaw = 120.0; + + if (flYaw > maxyaw) + { + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - 180; + m_flGaitMovement = -m_flGaitMovement; + flYaw = flYaw - 180; + } + else if (flYaw < -maxyaw) + { + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw + 180; + m_flGaitMovement = -m_flGaitMovement; + flYaw = flYaw + 180; + } + + float blend_yaw = ( flYaw / 90.0 ) * 128.0 + 127.0; + blend_yaw = min( 255.0, blend_yaw ); + blend_yaw = max( 0.0, blend_yaw ); + + blend_yaw = 255.0 - blend_yaw; + + m_pCurrentEntity->curstate.blending[0] = (int)(blend_yaw); + m_pCurrentEntity->latched.prevblending[0] = m_pCurrentEntity->curstate.blending[0]; + m_pCurrentEntity->latched.prevseqblending[0] = m_pCurrentEntity->curstate.blending[0]; + + m_pCurrentEntity->angles[YAW] = m_pPlayerInfo->gaityaw; + if (m_pCurrentEntity->angles[YAW] < -0) + { + m_pCurrentEntity->angles[YAW] += 360; + } + m_pCurrentEntity->latched.prevangles[YAW] = m_pCurrentEntity->angles[YAW]; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + pplayer->gaitsequence; + + // Calc gait frame + if (pseqdesc->linearmovement[0] > 0) + { + m_pPlayerInfo->gaitframe += (m_flGaitMovement / pseqdesc->linearmovement[0]) * pseqdesc->numframes; + } + else + { + m_pPlayerInfo->gaitframe += pseqdesc->fps * dt * m_pCurrentEntity->curstate.framerate; + } + + // Do modulo + m_pPlayerInfo->gaitframe = m_pPlayerInfo->gaitframe - (int)(m_pPlayerInfo->gaitframe / pseqdesc->numframes) * pseqdesc->numframes; + if (m_pPlayerInfo->gaitframe < 0) + { + m_pPlayerInfo->gaitframe += pseqdesc->numframes; + } +} + +/* +============================== +SavePlayerState + +For local player, in third person, we need to store real render data and then + setup for with fake/client side animation data +============================== +*/ +void CGameStudioModelRenderer::SavePlayerState( entity_state_t *pplayer ) +{ + client_anim_state_t *st; + cl_entity_t *ent = IEngineStudio.GetCurrentEntity(); + assert( ent ); + if ( !ent ) + return; + + st = &g_state; + + st->angles = ent->curstate.angles; + st->origin = ent->curstate.origin; + + st->realangles = ent->angles; + + st->sequence = ent->curstate.sequence; + st->gaitsequence = pplayer->gaitsequence; + st->animtime = ent->curstate.animtime; + st->frame = ent->curstate.frame; + st->framerate = ent->curstate.framerate; + memcpy( st->blending, ent->curstate.blending, 2 ); + memcpy( st->controller, ent->curstate.controller, 4 ); + + st->lv = ent->latched; +} + +void GetSequenceInfo( void *pmodel, client_anim_state_t *pev, float *pflFrameRate, float *pflGroundSpeed ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return; + + mstudioseqdesc_t *pseqdesc; + + if (pev->sequence >= pstudiohdr->numseq) + { + *pflFrameRate = 0.0; + *pflGroundSpeed = 0.0; + return; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + if (pseqdesc->numframes > 1) + { + *pflFrameRate = 256 * pseqdesc->fps / (pseqdesc->numframes - 1); + *pflGroundSpeed = sqrt( pseqdesc->linearmovement[0]*pseqdesc->linearmovement[0]+ pseqdesc->linearmovement[1]*pseqdesc->linearmovement[1]+ pseqdesc->linearmovement[2]*pseqdesc->linearmovement[2] ); + *pflGroundSpeed = *pflGroundSpeed * pseqdesc->fps / (pseqdesc->numframes - 1); + } + else + { + *pflFrameRate = 256.0; + *pflGroundSpeed = 0.0; + } +} + +int GetSequenceFlags( void *pmodel, client_anim_state_t *pev ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || pev->sequence >= pstudiohdr->numseq ) + return 0; + + mstudioseqdesc_t *pseqdesc; + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + return pseqdesc->flags; +} + +float StudioFrameAdvance ( client_anim_state_t *st, float framerate, float flInterval ) +{ + if (flInterval == 0.0) + { + flInterval = (gEngfuncs.GetClientTime() - st->animtime); + if (flInterval <= 0.001) + { + st->animtime = gEngfuncs.GetClientTime(); + return 0.0; + } + } + if (!st->animtime) + flInterval = 0.0; + + st->frame += flInterval * framerate * st->framerate; + st->animtime = gEngfuncs.GetClientTime(); + + if (st->frame < 0.0 || st->frame >= 256.0) + { + if ( st->m_fSequenceLoops ) + st->frame -= (int)(st->frame / 256.0) * 256.0; + else + st->frame = (st->frame < 0.0) ? 0 : 255; + st->m_fSequenceFinished = TRUE; // just in case it wasn't caught in GetEvents + } + + return flInterval; +} + +/* +============================== +SetupClientAnimation + +Called to set up local player's animation values +============================== +*/ +void CGameStudioModelRenderer::SetupClientAnimation( entity_state_t *pplayer ) +{ + static double oldtime; + double curtime, dt; + + client_anim_state_t *st; + float fr, gs; + + cl_entity_t *ent = IEngineStudio.GetCurrentEntity(); + assert( ent ); + if ( !ent ) + return; + + curtime = gEngfuncs.GetClientTime(); + dt = curtime - oldtime; + dt = min( 1.0, max( 0.0, dt ) ); + + oldtime = curtime; + st = &g_clientstate; + + st->framerate = 1.0; + + int oldseq = st->sequence; + Ricochet_GetSequence( &st->sequence, &st->gaitsequence ); + Ricochet_GetOrientation( (float *)&st->origin, (float *)&st->angles ); + st->realangles = st->angles; + + if ( st->sequence != oldseq ) + { + st->frame = 0.0; + st->lv.prevsequence = oldseq; + st->lv.sequencetime = st->animtime; + + memcpy( st->lv.prevseqblending, st->blending, 2 ); + memcpy( st->lv.prevcontroller, st->controller, 4 ); + } + + void *pmodel = (studiohdr_t *)IEngineStudio.Mod_Extradata( ent->model ); + + GetSequenceInfo( pmodel, st, &fr, &gs ); + st->m_fSequenceLoops = ((GetSequenceFlags( pmodel, st ) & STUDIO_LOOPING) != 0); + StudioFrameAdvance( st, fr, dt ); + + ent->angles = st->realangles; + ent->curstate.angles = st->angles; + ent->curstate.origin = st->origin; + + ent->curstate.sequence = st->sequence; + pplayer->gaitsequence = st->gaitsequence; + ent->curstate.animtime = st->animtime; + ent->curstate.frame = st->frame; + ent->curstate.framerate = st->framerate; + memcpy( ent->curstate.blending, st->blending, 2 ); + memcpy( ent->curstate.controller, st->controller, 4 ); + + ent->latched = st->lv; +} + +/* +============================== +RestorePlayerState + +Called to restore original player state information +============================== +*/ +void CGameStudioModelRenderer::RestorePlayerState( entity_state_t *pplayer ) +{ + client_anim_state_t *st; + cl_entity_t *ent = IEngineStudio.GetCurrentEntity(); + assert( ent ); + if ( !ent ) + return; + + st = &g_clientstate; + + st->angles = ent->curstate.angles; + st->origin = ent->curstate.origin; + st->realangles = ent->angles; + + st->sequence = ent->curstate.sequence; + st->gaitsequence = pplayer->gaitsequence; + st->animtime = ent->curstate.animtime; + st->frame = ent->curstate.frame; + st->framerate = ent->curstate.framerate; + memcpy( st->blending, ent->curstate.blending, 2 ); + memcpy( st->controller, ent->curstate.controller, 4 ); + + st->lv = ent->latched; + + st = &g_state; + + ent->curstate.angles = st->angles; + ent->curstate.origin = st->origin; + ent->angles = st->realangles; + + ent->curstate.sequence = st->sequence; + pplayer->gaitsequence = st->gaitsequence; + ent->curstate.animtime = st->animtime; + ent->curstate.frame = st->frame; + ent->curstate.framerate = st->framerate; + memcpy( ent->curstate.blending, st->blending, 2 ); + memcpy( ent->curstate.controller, st->controller, 4 ); + + ent->latched = st->lv; +} + +/* +============================== +StudioDrawPlayer + +============================== +*/ +int CGameStudioModelRenderer::StudioDrawPlayer( int flags, entity_state_t *pplayer ) +{ + int iret = 0; + + bool isLocalPlayer = false; + + // Set up for client? + if ( m_bLocal && IEngineStudio.GetCurrentEntity() == gEngfuncs.GetLocalPlayer() ) + { + isLocalPlayer = true; + } + + if ( isLocalPlayer ) + { + // Store original data + SavePlayerState( pplayer ); + + // Copy in client side animation data + SetupClientAnimation( pplayer ); + } + + // Call real draw function + iret = _StudioDrawPlayer( flags, pplayer ); + + // Restore for client? + if ( isLocalPlayer ) + { + // Restore the original data for the player + RestorePlayerState( pplayer ); + } + + return iret; +} + +/* +==================== +_StudioDrawPlayer + +==================== +*/ +int CGameStudioModelRenderer::_StudioDrawPlayer( int flags, entity_state_t *pplayer ) +{ + alight_t lighting; + vec3_t dir; + + m_pCurrentEntity = IEngineStudio.GetCurrentEntity(); + IEngineStudio.GetTimes( &m_nFrameCount, &m_clTime, &m_clOldTime ); + IEngineStudio.GetViewInfo( m_vRenderOrigin, m_vUp, m_vRight, m_vNormal ); + IEngineStudio.GetAliasScale( &m_fSoftwareXScale, &m_fSoftwareYScale ); + + m_nPlayerIndex = pplayer->number - 1; + + if (m_nPlayerIndex < 0 || m_nPlayerIndex >= gEngfuncs.GetMaxClients()) + return 0; + + m_pRenderModel = IEngineStudio.SetupPlayerModel( m_nPlayerIndex ); + if (m_pRenderModel == NULL) + return 0; + + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (m_pRenderModel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + IEngineStudio.SetRenderModel( m_pRenderModel ); + + if (pplayer->gaitsequence) + { + vec3_t orig_angles; + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + + VectorCopy( m_pCurrentEntity->angles, orig_angles ); + + StudioProcessGait( pplayer ); + + m_pPlayerInfo->gaitsequence = pplayer->gaitsequence; + m_pPlayerInfo = NULL; + + StudioSetUpTransform( 0 ); + VectorCopy( orig_angles, m_pCurrentEntity->angles ); + } + else + { + m_pCurrentEntity->curstate.controller[0] = 127; + m_pCurrentEntity->curstate.controller[1] = 127; + m_pCurrentEntity->curstate.controller[2] = 127; + m_pCurrentEntity->curstate.controller[3] = 127; + m_pCurrentEntity->latched.prevcontroller[0] = m_pCurrentEntity->curstate.controller[0]; + m_pCurrentEntity->latched.prevcontroller[1] = m_pCurrentEntity->curstate.controller[1]; + m_pCurrentEntity->latched.prevcontroller[2] = m_pCurrentEntity->curstate.controller[2]; + m_pCurrentEntity->latched.prevcontroller[3] = m_pCurrentEntity->curstate.controller[3]; + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + m_pPlayerInfo->gaitsequence = 0; + + StudioSetUpTransform( 0 ); + } + + if (flags & STUDIO_RENDER) + { + // see if the bounding box lets us trivially reject, also sets + if (!IEngineStudio.StudioCheckBBox ()) + return 0; + + (*m_pModelsDrawn)++; + (*m_pStudioModelCount)++; // render data cache cookie + + if (m_pStudioHeader->numbodyparts == 0) + return 1; + } + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + StudioSetupBones( ); + StudioSaveBones( ); + m_pPlayerInfo->renderframe = m_nFrameCount; + + m_pPlayerInfo = NULL; + + if (flags & STUDIO_EVENTS) + { + StudioCalcAttachments( ); + IEngineStudio.StudioClientEvents( ); + // copy attachments into global entity array + if ( m_pCurrentEntity->index > 0 ) + { + cl_entity_t *ent = gEngfuncs.GetEntityByIndex( m_pCurrentEntity->index ); + + memcpy( ent->attachment, m_pCurrentEntity->attachment, sizeof( vec3_t ) * 4 ); + } + } + + if (flags & STUDIO_RENDER) + { + /* + if (m_pCvarHiModels->value && m_pRenderModel != m_pCurrentEntity->model ) + { + // show highest resolution multiplayer model + m_pCurrentEntity->curstate.body = 255; + } + + if (!(m_pCvarDeveloper->value == 0 && gEngfuncs.GetMaxClients() == 1 ) && ( m_pRenderModel == m_pCurrentEntity->model ) ) + { + m_pCurrentEntity->curstate.body = 1; // force helmet + } + */ + + lighting.plightvec = dir; + IEngineStudio.StudioDynamicLight(m_pCurrentEntity, &lighting ); + + IEngineStudio.StudioEntityLight( &lighting ); + + // model and frame independant + IEngineStudio.StudioSetupLighting (&lighting); + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + + // get remap colors + m_nTopColor = m_pPlayerInfo->topcolor; + if (m_nTopColor < 0) + m_nTopColor = 0; + if (m_nTopColor > 360) + m_nTopColor = 360; + m_nBottomColor = m_pPlayerInfo->bottomcolor; + if (m_nBottomColor < 0) + m_nBottomColor = 0; + if (m_nBottomColor > 360) + m_nBottomColor = 360; + + IEngineStudio.StudioSetRemapColors( m_nTopColor, m_nBottomColor ); + + StudioRenderModel( ); + m_pPlayerInfo = NULL; + + if (pplayer->weaponmodel) + { + cl_entity_t saveent = *m_pCurrentEntity; + + model_t *pweaponmodel = IEngineStudio.GetModelByIndex( pplayer->weaponmodel ); + + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (pweaponmodel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + + StudioMergeBones( pweaponmodel); + + IEngineStudio.StudioSetupLighting (&lighting); + + StudioRenderModel( ); + + StudioCalcAttachments( ); + + *m_pCurrentEntity = saveent; + } + } + + return 1; +} + +/* +==================== +Studio_FxTransform + +==================== +*/ +void CGameStudioModelRenderer::StudioFxTransform( cl_entity_t *ent, float transform[3][4] ) +{ + switch( ent->curstate.renderfx ) + { + case kRenderFxDistort: + case kRenderFxHologram: + if ( gEngfuncs.pfnRandomLong(0,49) == 0 ) + { + int axis = gEngfuncs.pfnRandomLong(0,1); + if ( axis == 1 ) // Choose between x & z + axis = 2; + VectorScale( transform[axis], gEngfuncs.pfnRandomFloat(1,1.484), transform[axis] ); + } + else if ( gEngfuncs.pfnRandomLong(0,49) == 0 ) + { + float offset; + int axis = gEngfuncs.pfnRandomLong(0,1); + if ( axis == 1 ) // Choose between x & z + axis = 2; + offset = gEngfuncs.pfnRandomFloat(-10,10); + transform[gEngfuncs.pfnRandomLong(0,2)][3] += offset; + } + break; + case kRenderFxExplode: + { + if ( iRenderStateChanged ) + { + g_flStartScaleTime = m_clTime; + iRenderStateChanged = FALSE; + } + + // Make the Model continue to shrink + float flTimeDelta = m_clTime - g_flStartScaleTime; + if ( flTimeDelta > 0 ) + { + float flScale = 0.001; + // Goes almost all away + if ( flTimeDelta <= 2.0 ) + flScale = 1.0 - (flTimeDelta / 2.0); + + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + transform[i][j] *= flScale; + } + } + } + break; + } +} + +//////////////////////////////////// +// Hooks to class implementation +//////////////////////////////////// + +/* +==================== +R_StudioDrawPlayer + +==================== +*/ +int R_StudioDrawPlayer( int flags, entity_state_t *pplayer ) +{ + return g_StudioRenderer.StudioDrawPlayer( flags, pplayer ); +} + +/* +==================== +R_StudioDrawModel + +==================== +*/ +int R_StudioDrawModel( int flags ) +{ + return g_StudioRenderer.StudioDrawModel( flags ); +} + +/* +==================== +R_StudioInit + +==================== +*/ +void R_StudioInit( void ) +{ + g_StudioRenderer.Init(); +} + +// The simple drawing interface we'll pass back to the engine +r_studio_interface_t studio = +{ + STUDIO_INTERFACE_VERSION, + R_StudioDrawModel, + R_StudioDrawPlayer, +}; + +/* +==================== +HUD_GetStudioModelInterface + +Export this function for the engine to use the studio renderer class to render objects. +==================== +*/ +#define DLLEXPORT __declspec( dllexport ) +extern "C" int DLLEXPORT HUD_GetStudioModelInterface( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio ) +{ + if ( version != STUDIO_INTERFACE_VERSION ) + return 0; + + // Point the engine to our callbacks + *ppinterface = &studio; + + // Copy in engine helper functions + memcpy( &IEngineStudio, pstudio, sizeof( IEngineStudio ) ); + + // Initialize local variables, etc. + R_StudioInit(); + + // Success + return 1; +} diff --git a/hlsdk/ricochet/cl_dll/GameStudioModelRenderer.h b/hlsdk/ricochet/cl_dll/GameStudioModelRenderer.h new file mode 100644 index 00000000..64f6c8b0 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/GameStudioModelRenderer.h @@ -0,0 +1,48 @@ +#if !defined( GAMESTUDIOMODELRENDERER_H ) +#define GAMESTUDIOMODELRENDERER_H +#if defined( _WIN32 ) +#pragma once +#endif + +/* +==================== +CGameStudioModelRenderer + +==================== +*/ +class CGameStudioModelRenderer : public CStudioModelRenderer +{ +public: + CGameStudioModelRenderer( void ); + + // Set up model bone positions + virtual void StudioSetupBones ( void ); + + // Estimate gait frame for player + virtual void StudioEstimateGait ( entity_state_t *pplayer ); + + // Process movement of player + virtual void StudioProcessGait ( entity_state_t *pplayer ); + + // Player drawing code + virtual int StudioDrawPlayer( int flags, entity_state_t *pplayer ); + virtual int _StudioDrawPlayer( int flags, entity_state_t *pplayer ); + + // Apply special effects to transform matrix + virtual void StudioFxTransform( cl_entity_t *ent, float transform[3][4] ); + +private: + // For local player, in third person, we need to store real render data and then + // setup for with fake/client side animation data + void SavePlayerState( entity_state_t *pplayer ); + // Called to set up local player's animation values + void SetupClientAnimation( entity_state_t *pplayer ); + // Called to restore original player state information + void RestorePlayerState( entity_state_t *pplayer ); + +private: + // Private data + bool m_bLocal; +}; + +#endif // GAMESTUDIOMODELRENDERER_H \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/Ricochet_BSPFile.h b/hlsdk/ricochet/cl_dll/Ricochet_BSPFile.h new file mode 100644 index 00000000..8e21ea0d --- /dev/null +++ b/hlsdk/ricochet/cl_dll/Ricochet_BSPFile.h @@ -0,0 +1,41 @@ +#if !defined( RICOCHET_BSPFILE_H ) +#define RICOCHET_BSPFILE_H +#ifdef _WIN32 +#pragma once +#endif + +// MINI-version of BSPFILE.H to support Ricochet 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 // RICOCHET_BSPFILE_H \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/Ricochet_JumpPads.cpp b/hlsdk/ricochet/cl_dll/Ricochet_JumpPads.cpp new file mode 100644 index 00000000..7581f9b2 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/Ricochet_JumpPads.cpp @@ -0,0 +1,586 @@ +#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 "Ricochet_BSPFile.h" + +extern "C" playermove_t *pmove; +extern int g_runfuncs; + +// Don't support more than MAX_PADS pads ( map still can load, but we'll just have some pads that don't predict. ) +#define MAX_PADS 256 + +// We only care about two kinds of entities for now: Jump pads and their targets +// FIXME: After loading, store a pointer from pad to target instead of looking up all the time. +typedef enum +{ + // Entity is a jump pad + RIC_PAD = 0, + // Entity is a target + RIC_TARGET +} ric_padtype_t; + +typedef struct +{ + // Type of entity + ric_padtype_t type; + + // Classname + char classname[ 32 ]; + + // Model name + char modelname[ 32 ]; + + // What this entity targets + char target[ 32 ]; + + // If entity is a target, the name tag it uses + char targetname[ 32 ]; + + // Orientation of the pad + float angles[3]; + + // Target origin + float origin[3]; + + // Bounding box of the pad + float absmin[3]; + float absmax[3]; + + // Model associated with the pad + struct model_s *model; + + float height; +} ric_pad_t; + +// Pad/Target entity database +static ric_pad_t s_pads[ MAX_PADS ]; +static int s_num_pads = 0; + +// We'll use this for playing the jump sounds locally. +static unsigned short s_usJump; + +/* +============================== +Ricochet_SetKeyValue + +Fill in key/values fro the pad +============================== +*/ +void Ricochet_SetKeyValue( ric_pad_t *pad, const char *key, const char *value ) +{ + float x, y, z; + + if ( !stricmp( key, "classname" ) ) + { + strcpy( pad->classname, value ); + } + else if ( !stricmp( key, "target" ) ) + { + strcpy( pad->target, value ); + } + else if ( !stricmp( key, "targetname" ) ) + { + strcpy( pad->targetname, value ); + } + else if ( !stricmp( key, "model" ) ) + { + strcpy( pad->modelname, value ); + } + else if ( !stricmp( key, "height" ) ) + { + pad->height = atof( value ); + } + else if ( !stricmp( key, "angles" ) ) + { + if ( sscanf( value, "%f %f %f", &x, &y, &z ) == 3 ) + { + pad->angles[ 0 ] = x ; + pad->angles[ 1 ] = y; + pad->angles[ 2 ] = z; + } + } + else if ( !stricmp( key, "origin" ) ) + { + if ( sscanf( value, "%f %f %f", &x, &y, &z ) == 3 ) + { + pad->origin[ 0 ] = x; + pad->origin[ 1 ] = y; + pad->origin[ 2 ] = z; + } + } +} + +/* +============================== +Ricochet_ParsePad + +Evaluate Key/Value pairs for the entity +============================== +*/ +char *Ricochet_ParsePad( char *buffer, ric_pad_t *pad, int *error ) +{ + char key[256]; + char token[ 1024 ]; + int n; + + memset( pad, 0, sizeof( *pad ) ); + + while (1) + { + // Parse key + buffer = gEngfuncs.COM_ParseFile ( buffer, token ); + if ( token[0] == '}' ) + break; + + // Ran out of input buffer? + if ( !buffer ) + { + *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 + buffer = gEngfuncs.COM_ParseFile ( buffer, token ); + + // Ran out of buffer? + if (!buffer) + { + *error = 1; + break; + } + + // Hit the end instead of a value? + if ( token[0] == '}' ) + { + *error = 1; + break; + } + + // Assign k/v pair + Ricochet_SetKeyValue( pad, key, token ); + } + + // Return what's left in the stream + return buffer; +} + +/* +============================== +Ricochet_ProcessEnts + +Parse through entity lump looking for pads or targets +============================== +*/ +void Ricochet_ProcessEnts( char *buffer ) +{ + int i; + char token[ 1024 ]; + ric_pad_t *pad = NULL; + int error = 0; + + // parse entities from entity lump of .bsp file + while (1) + { + // parse the opening brace + buffer = gEngfuncs.COM_ParseFile ( buffer, token ); + if (!buffer) + break; + + // Didn't find opening brace? + if ( token[0] != '{' ) + { + gEngfuncs.Con_Printf ("Ricochet_ProcessEnts: found %s when expecting {\n", token ); + return; + } + + // Assume we're filling in this pad + pad = &s_pads[ s_num_pads ]; + + // Fill in data + buffer = Ricochet_ParsePad( buffer, pad, &error ); + + // Check for errors and abort if any + if ( error ) + { + gEngfuncs.Con_Printf ("Ricochet_ProcessEnts: error parsing entities\n" ); + return; + } + + // Check classname + if ( stricmp( pad->classname, "trigger_jump" ) && stricmp( pad->classname, "info_target" ) ) + continue; + + // Set type based on classname + if ( !stricmp( pad->classname, "trigger_jump" ) ) + { + pad->type = RIC_PAD; + } + else + { + pad->type = RIC_TARGET; + } + + // Load up the model + pad->model = gEngfuncs.CL_LoadModel( pad->modelname, NULL ); + if ( pad->model ) + { + // Fill in abs bbox + for ( i = 0; i < 3; i++ ) + { + pad->absmin[ i ] = pad->model->mins[ i ] - 1.0; + pad->absmax[ i ] = pad->model->maxs[ i ] + 1.0; + } + } + + // If we got to here, we're using the entity + s_num_pads++; + + // No more room... + if ( s_num_pads >= MAX_PADS ) + break; + } +} + +/* +============================== +Ricochet_LoadEntityLump + +Open the .bsp and read in the entity lump +============================== +*/ +char *Ricochet_LoadEntityLump( const char *filename ) +{ + FILE *fp; + int i; + dheader_t header; + int size; + lump_t *curLump; + char *buffer = NULL; + + fp = fopen( filename, "rb" ); + if ( !fp ) + return NULL; + + // Read in the .bsp header + if ( fread(&header, sizeof(dheader_t), 1, fp) != 1 ) + { + gEngfuncs.Con_Printf("Ricochet_LoadEntityLump: Could not read BSP header for map [%s].\n", filename); + fclose(fp); + return NULL; + } + + // Check the version + i = header.version; + if ( i != 29 && i != 30) + { + fclose(fp); + gEngfuncs.Con_Printf("Ricochet_LoadEntityLump: Map [%s] has incorrect BSP version (%i should be %i).\n", filename, 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 = (char *)malloc( size + 1 ); + if ( !buffer ) + { + fclose(fp); + gEngfuncs.Con_Printf("Ricochet_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; +} + +/* +============================== +Ricochet_LoadJumpPads + +Load in the .bsp file and process the entities +============================== +*/ +void Ricochet_LoadJumpPads( const char *map ) +{ + char *buffer = NULL; + char filename[ 256 ]; + + sprintf( filename, "%s/%s", gEngfuncs.pfnGetGameDirectory(), map ); + + // TODO: Fix Slashes? + + // Reset count + s_num_pads = 0; + + // Load entity lump + buffer = Ricochet_LoadEntityLump( filename ); + if ( !buffer ) + return; + + // Process buffer and extract pads/targets + Ricochet_ProcessEnts( buffer ); + + // Discard buffer + free( buffer ); +} + +/* +============================== +Ricochet_FindTarget + +Search entity list for target matching "name" +============================== +*/ +ric_pad_t *Ricochet_FindTarget( const char *name, int numpads, ric_pad_t *pads ) +{ + int i; + ric_pad_t *target; + + // Find the target + for ( i = 0; i < numpads; i++ ) + { + target = &pads[ i ]; + if ( !target ) + continue; + + if ( stricmp( target->targetname, name ) ) + continue; + + return target; + } + + return NULL; +} + +/* +============================== +Ricochet_PadTouched + +Register impact ( impart velocity on player and if final function call, play appropriate jump sound ) +============================== +*/ +void Ricochet_PadTouched( int numpads, ric_pad_t *pads, ric_pad_t *pad, struct local_state_s *player ) +{ + int i; + ric_pad_t *target; + float origin[ 3 ]; + pmtrace_t tr; + float flGravity = pmove->movevars->gravity; + + float vecMidPoint[3]; + float end[ 3 ]; + + // Ricochet jump pads use default jump height + float flHeight = 150; + + float zero[ 3 ] = { 0.0, 0.0, 0.0 }; + + // Find the target + target = Ricochet_FindTarget( pad->target, numpads, pads ); + + // Target now points to target pad + for ( i = 0; i < 3; i++ ) + { + origin[ i ] = player->playerstate.origin[ i ]; + + // Get a rough idea of how high to launch + vecMidPoint[ i ] = origin[ i ] + ( target->origin[ i ] - origin[ i ]) * 0.5; + end[ i ] = vecMidPoint[ i ]; + } + + if ( pad->height != 0.0 ) + { + flHeight = pad->height; + } + + // Move up by height + end[ 2 ] += flHeight; + + // See if we can reach the apex from the midpoint + gEngfuncs.pEventAPI->EV_PlayerTrace( vecMidPoint, end, PM_STUDIO_BOX, -1, &tr ); + + // Use the end point of the trace as the midpoint of the actual toss + for ( i = 0; i < 3; i++ ) + { + vecMidPoint[i] = tr.endpos[i]; + } + + // Subtract some units so we don't hit the ceiling) + vecMidPoint[2] -= 15; + + // How high should we travel to reach the apex + float distance1 = fabs(vecMidPoint[2] - origin[2]); + float distance2 = fabs(vecMidPoint[2] - target->origin[2]); + + // How long will it take to travel this distance + float time1 = sqrt( distance1 / (0.5 * flGravity) ); + float time2 = sqrt( distance2 / (0.5 * flGravity) ); + if (time1 < 0.1) + return; + + // Determine how hard to launch to get there in time. + float vecTargetVel[3]; + + for ( i = 0; i < 3; i++ ) + { + vecTargetVel[ i ] = (target->origin[ i ] - origin[ i ]) / (time1 + time2); + } + + // Adjust upward velocity needed + vecTargetVel[ 2 ] = flGravity * time1; + + // Fill in needed velocity + for ( i = 0; i < 3; i++ ) + { + player->client.velocity[i] = vecTargetVel[i]; + } + + // Play sound if appropriate + if ( s_usJump && g_runfuncs ) + { + gEngfuncs.pfnPlaybackEvent( FEV_NOTHOST, NULL, s_usJump, 0.0, zero, zero, 0.0, 0.0, 0, 0, 0, 0 ); + } +} + +/* +============================== +Ricochet_TouchPads + +See if player's resting position impacts any jump pads +============================== +*/ +void Ricochet_TouchPads ( struct local_state_s *player, ric_pad_t *pads, int numpads ) +{ + int i, j; + ric_pad_t *pad; + float absmin[3], absmax[3]; + physent_t pe; + hull_t *hull; + int num; + float test[3]; + float pmins[ 3 ] = { 16, 16, 36 }; + + // Determine player's bbox + for ( j = 0; j < 3; j++ ) + { + absmin[ j ] = player->playerstate.origin[ j ] - pmins[ j ]; + absmax[ j ] = player->playerstate.origin[ j ] + pmins[ j ]; + } + + // Cycle through pads looking for a match + for ( i = 0; i < numpads; i++ ) + { + pad = &pads[ i ]; + if ( !pad ) + continue; + + // Target entities don't make us jump + if ( pad->type != RIC_PAD ) + continue; + + // Trivial reject? + if ( absmin[0] > pad->absmax[0] + || absmin[1] > pad->absmax[1] + || absmin[2] > pad->absmax[2] + || absmax[0] < pad->absmin[0] + || absmax[1] < pad->absmin[1] + || absmax[2] < pad->absmin[2] ) + continue; + + // Set up physent for the test case + pe.model = pad->model; + pe.origin = pad->origin; + + // Use standing player hull + pmove->usehull = 0; + + // Make sure it's a brush model, of course + if ( !pe.model || (modtype_t)pmove->PM_GetModelType( pe.model ) != mod_brush ) + continue; + + // Get the hull + hull = (hull_t *)pmove->PM_HullForBsp( &pe, test ); + num = hull->firstclipnode; + + // Offset the origin by the offset appropriate for this hull. + for ( j = 0; j < 3; j++ ) + { + test[ j ] = player->playerstate.origin[ j ] - test[ j ]; + } + + // Test the player's hull for intersection with this model + if ( pmove->PM_HullPointContents ( hull, num, test ) != CONTENTS_SOLID ) + { + continue; + } + + // TOUCHED!!! + Ricochet_PadTouched( numpads, pads, pad, player ); + + // Only touch one pad at a time + break; + } +} + +/* +============================== +Ricochet_CheckJumpPads + +Load data if needed, otherwise just run checks on player's final position to see if jump pad needs + to impart velocity on the player. +============================== +*/ +void Ricochet_CheckJumpPads( struct local_state_s *from, struct local_state_s *to ) +{ + static char current_level[ 128 ]; + + // See if we've changed to a new map + if ( stricmp( current_level, gEngfuncs.pfnGetLevelName() ) ) + { + strcpy( current_level, gEngfuncs.pfnGetLevelName() ); + Ricochet_LoadJumpPads( current_level ); + + // Grab sound event + s_usJump = gEngfuncs.pfnPrecacheEvent( 1, "events/jump.sc" ); + } + + // Not while spectating + if ( to->client.iuser1 ) + return; + + // Run test + Ricochet_TouchPads( to, s_pads, s_num_pads ); +} \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/Ricochet_JumpPads.h b/hlsdk/ricochet/cl_dll/Ricochet_JumpPads.h new file mode 100644 index 00000000..7243df9b --- /dev/null +++ b/hlsdk/ricochet/cl_dll/Ricochet_JumpPads.h @@ -0,0 +1,9 @@ +#if !defined( RICHOCHET_JUMPADS_H ) +#define RICHOCHET_JUMPADS_H +#ifdef _WIN32 +#pragma once +#endif + +void Ricochet_CheckJumpPads( struct local_state_s *from, struct local_state_s *to ); + +#endif // RICHOCHET_JUMPADS_H \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/StudioModelRenderer.cpp b/hlsdk/ricochet/cl_dll/StudioModelRenderer.cpp new file mode 100644 index 00000000..8caf368e --- /dev/null +++ b/hlsdk/ricochet/cl_dll/StudioModelRenderer.cpp @@ -0,0 +1,1607 @@ +// studio_model.cpp +// routines for setting up to draw 3DStudio models + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "com_model.h" +#include "studio.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "dlight.h" +#include "triangleapi.h" + +#include +#include +#include +#include + +#include "studio_util.h" +#include "r_studioint.h" + +#include "StudioModelRenderer.h" +#include "GameStudioModelRenderer.h" + +// Global engine <-> studio model rendering code interface +engine_studio_api_t IEngineStudio; + +///////////////////// +// Implementation of CStudioModelRenderer.h + +/* +==================== +Init + +==================== +*/ +void CStudioModelRenderer::Init( void ) +{ + // Set up some variables shared with engine + m_pCvarHiModels = IEngineStudio.GetCvar( "cl_himodels" ); + m_pCvarDeveloper = IEngineStudio.GetCvar( "developer" ); + m_pCvarDrawEntities = IEngineStudio.GetCvar( "r_drawentities" ); + + m_pChromeSprite = IEngineStudio.GetChromeSprite(); + + IEngineStudio.GetModelCounters( &m_pStudioModelCount, &m_pModelsDrawn ); + + // Get pointers to engine data structures + m_pbonetransform = (float (*)[MAXSTUDIOBONES][3][4])IEngineStudio.StudioGetBoneTransform(); + m_plighttransform = (float (*)[MAXSTUDIOBONES][3][4])IEngineStudio.StudioGetLightTransform(); + m_paliastransform = (float (*)[3][4])IEngineStudio.StudioGetAliasTransform(); + m_protationmatrix = (float (*)[3][4])IEngineStudio.StudioGetRotationMatrix(); +} + +/* +==================== +CStudioModelRenderer + +==================== +*/ +CStudioModelRenderer::CStudioModelRenderer( void ) +{ + m_fDoInterp = 1; + m_fGaitEstimation = 1; + m_pCurrentEntity = NULL; + m_pCvarHiModels = NULL; + m_pCvarDeveloper = NULL; + m_pCvarDrawEntities = NULL; + m_pChromeSprite = NULL; + m_pStudioModelCount = NULL; + m_pModelsDrawn = NULL; + m_protationmatrix = NULL; + m_paliastransform = NULL; + m_pbonetransform = NULL; + m_plighttransform = NULL; + m_pStudioHeader = NULL; + m_pBodyPart = NULL; + m_pSubModel = NULL; + m_pPlayerInfo = NULL; + m_pRenderModel = NULL; +} + +/* +==================== +~CStudioModelRenderer + +==================== +*/ +CStudioModelRenderer::~CStudioModelRenderer( void ) +{ +} + +/* +==================== +StudioCalcBoneAdj + +==================== +*/ +void CStudioModelRenderer::StudioCalcBoneAdj( float dadt, float *adj, const byte *pcontroller1, const byte *pcontroller2, byte mouthopen ) +{ + int i, j; + float value; + mstudiobonecontroller_t *pbonecontroller; + + pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bonecontrollerindex); + + for (j = 0; j < m_pStudioHeader->numbonecontrollers; j++) + { + i = pbonecontroller[j].index; + if (i <= 3) + { + // check for 360% wrapping + if (pbonecontroller[j].type & STUDIO_RLOOP) + { + if (abs(pcontroller1[i] - pcontroller2[i]) > 128) + { + int a, b; + a = (pcontroller1[j] + 128) % 256; + b = (pcontroller2[j] + 128) % 256; + value = ((a * dadt) + (b * (1 - dadt)) - 128) * (360.0/256.0) + pbonecontroller[j].start; + } + else + { + value = ((pcontroller1[i] * dadt + (pcontroller2[i]) * (1.0 - dadt))) * (360.0/256.0) + pbonecontroller[j].start; + } + } + else + { + value = (pcontroller1[i] * dadt + pcontroller2[i] * (1.0 - dadt)) / 255.0; + if (value < 0) value = 0; + if (value > 1.0) value = 1.0; + value = (1.0 - value) * pbonecontroller[j].start + value * pbonecontroller[j].end; + } + } + else + { + value = mouthopen / 64.0; + if (value > 1.0) value = 1.0; + value = (1.0 - value) * pbonecontroller[j].start + value * pbonecontroller[j].end; + } + switch(pbonecontroller[j].type & STUDIO_TYPES) + { + case STUDIO_XR: + case STUDIO_YR: + case STUDIO_ZR: + adj[j] = value * (M_PI / 180.0); + break; + case STUDIO_X: + case STUDIO_Y: + case STUDIO_Z: + adj[j] = value; + break; + } + } +} + + +/* +==================== +StudioCalcBoneQuaterion + +==================== +*/ +void CStudioModelRenderer::StudioCalcBoneQuaterion( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *q ) +{ + int j, k; + vec4_t q1, q2; + vec3_t angle1, angle2; + mstudioanimvalue_t *panimvalue; + + for (j = 0; j < 3; j++) + { + if (panim->offset[j+3] == 0) + { + angle2[j] = angle1[j] = pbone->value[j+3]; // default; + } + else + { + panimvalue = (mstudioanimvalue_t *)((byte *)panim + panim->offset[j+3]); + k = frame; + // DEBUG + if (panimvalue->num.total < panimvalue->num.valid) + k = 0; + while (panimvalue->num.total <= k) + { + k -= panimvalue->num.total; + panimvalue += panimvalue->num.valid + 1; + // DEBUG + if (panimvalue->num.total < panimvalue->num.valid) + k = 0; + } + // Bah, missing blend! + if (panimvalue->num.valid > k) + { + angle1[j] = panimvalue[k+1].value; + + if (panimvalue->num.valid > k + 1) + { + angle2[j] = panimvalue[k+2].value; + } + else + { + if (panimvalue->num.total > k + 1) + angle2[j] = angle1[j]; + else + angle2[j] = panimvalue[panimvalue->num.valid+2].value; + } + } + else + { + angle1[j] = panimvalue[panimvalue->num.valid].value; + if (panimvalue->num.total > k + 1) + { + angle2[j] = angle1[j]; + } + else + { + angle2[j] = panimvalue[panimvalue->num.valid + 2].value; + } + } + angle1[j] = pbone->value[j+3] + angle1[j] * pbone->scale[j+3]; + angle2[j] = pbone->value[j+3] + angle2[j] * pbone->scale[j+3]; + } + + if (pbone->bonecontroller[j+3] != -1) + { + angle1[j] += adj[pbone->bonecontroller[j+3]]; + angle2[j] += adj[pbone->bonecontroller[j+3]]; + } + } + + if (!VectorCompare( angle1, angle2 )) + { + AngleQuaternion( angle1, q1 ); + AngleQuaternion( angle2, q2 ); + QuaternionSlerp( q1, q2, s, q ); + } + else + { + AngleQuaternion( angle1, q ); + } +} + +/* +==================== +StudioCalcBonePosition + +==================== +*/ +void CStudioModelRenderer::StudioCalcBonePosition( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *pos ) +{ + int j, k; + mstudioanimvalue_t *panimvalue; + + for (j = 0; j < 3; j++) + { + pos[j] = pbone->value[j]; // default; + if (panim->offset[j] != 0) + { + panimvalue = (mstudioanimvalue_t *)((byte *)panim + panim->offset[j]); + /* + if (i == 0 && j == 0) + Con_DPrintf("%d %d:%d %f\n", frame, panimvalue->num.valid, panimvalue->num.total, s ); + */ + + k = frame; + // DEBUG + if (panimvalue->num.total < panimvalue->num.valid) + k = 0; + // find span of values that includes the frame we want + while (panimvalue->num.total <= k) + { + k -= panimvalue->num.total; + panimvalue += panimvalue->num.valid + 1; + // DEBUG + if (panimvalue->num.total < panimvalue->num.valid) + k = 0; + } + // if we're inside the span + if (panimvalue->num.valid > k) + { + // and there's more data in the span + if (panimvalue->num.valid > k + 1) + { + pos[j] += (panimvalue[k+1].value * (1.0 - s) + s * panimvalue[k+2].value) * pbone->scale[j]; + } + else + { + pos[j] += panimvalue[k+1].value * pbone->scale[j]; + } + } + else + { + // are we at the end of the repeating values section and there's another section with data? + if (panimvalue->num.total <= k + 1) + { + pos[j] += (panimvalue[panimvalue->num.valid].value * (1.0 - s) + s * panimvalue[panimvalue->num.valid + 2].value) * pbone->scale[j]; + } + else + { + pos[j] += panimvalue[panimvalue->num.valid].value * pbone->scale[j]; + } + } + } + if ( pbone->bonecontroller[j] != -1 && adj ) + { + pos[j] += adj[pbone->bonecontroller[j]]; + } + } +} + +/* +==================== +StudioSlerpBones + +==================== +*/ +void CStudioModelRenderer::StudioSlerpBones( vec4_t q1[], float pos1[][3], vec4_t q2[], float pos2[][3], float s ) +{ + int i; + vec4_t q3; + float s1; + + if (s < 0) s = 0; + else if (s > 1.0) s = 1.0; + + s1 = 1.0 - s; + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + QuaternionSlerp( q1[i], q2[i], s, q3 ); + q1[i][0] = q3[0]; + q1[i][1] = q3[1]; + q1[i][2] = q3[2]; + q1[i][3] = q3[3]; + pos1[i][0] = pos1[i][0] * s1 + pos2[i][0] * s; + pos1[i][1] = pos1[i][1] * s1 + pos2[i][1] * s; + pos1[i][2] = pos1[i][2] * s1 + pos2[i][2] * s; + } +} + +/* +==================== +StudioGetAnim + +==================== +*/ +mstudioanim_t *CStudioModelRenderer::StudioGetAnim( model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc ) +{ + mstudioseqgroup_t *pseqgroup; + cache_user_t *paSequences; + + pseqgroup = (mstudioseqgroup_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqgroupindex) + pseqdesc->seqgroup; + + if (pseqdesc->seqgroup == 0) + { + return (mstudioanim_t *)((byte *)m_pStudioHeader + pseqgroup->data + pseqdesc->animindex); + } + + paSequences = (cache_user_t *)m_pSubModel->submodels; + + if (paSequences == NULL) + { + paSequences = (cache_user_t *)IEngineStudio.Mem_Calloc( 16, sizeof( cache_user_t ) ); // UNDONE: leak! + m_pSubModel->submodels = (dmodel_t *)paSequences; + } + + if (!IEngineStudio.Cache_Check( (struct cache_user_s *)&(paSequences[pseqdesc->seqgroup]))) + { + gEngfuncs.Con_DPrintf("loading %s\n", pseqgroup->name ); + IEngineStudio.LoadCacheFile( pseqgroup->name, (struct cache_user_s *)&paSequences[pseqdesc->seqgroup] ); + } + return (mstudioanim_t *)((byte *)paSequences[pseqdesc->seqgroup].data + pseqdesc->animindex); +} + +/* +==================== +StudioPlayerBlend + +==================== +*/ +void CStudioModelRenderer::StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch ) +{ + // calc up/down pointing + *pBlend = (*pPitch * 3); + if (*pBlend < pseqdesc->blendstart[0]) + { + *pPitch -= pseqdesc->blendstart[0] / 3.0; + *pBlend = 0; + } + else if (*pBlend > pseqdesc->blendend[0]) + { + *pPitch -= pseqdesc->blendend[0] / 3.0; + *pBlend = 255; + } + else + { + if (pseqdesc->blendend[0] - pseqdesc->blendstart[0] < 0.1) // catch qc error + *pBlend = 127; + else + *pBlend = 255 * (*pBlend - pseqdesc->blendstart[0]) / (pseqdesc->blendend[0] - pseqdesc->blendstart[0]); + *pPitch = 0; + } +} + +/* +==================== +StudioSetUpTransform + +==================== +*/ +void CStudioModelRenderer::StudioSetUpTransform (int trivial_accept) +{ + int i; + vec3_t angles; + vec3_t modelpos; + + VectorCopy( m_pCurrentEntity->origin, modelpos ); + +// TODO: should really be stored with the entity instead of being reconstructed +// TODO: should use a look-up table +// TODO: could cache lazily, stored in the entity + angles[ROLL] = m_pCurrentEntity->curstate.angles[ROLL]; + angles[PITCH] = m_pCurrentEntity->curstate.angles[PITCH]; + angles[YAW] = m_pCurrentEntity->curstate.angles[YAW]; + + if (m_pCurrentEntity->curstate.movetype == MOVETYPE_STEP) + { + float f = 0; + float d; + + // don't do it if the goalstarttime hasn't updated in a while. + + // NOTE: Because we need to interpolate multiplayer characters, the interpolation time limit + // was increased to 1.0 s., which is 2x the max lag we are accounting for. + + if ( ( m_clTime < m_pCurrentEntity->curstate.animtime + 1.0f ) && + ( m_pCurrentEntity->curstate.animtime != m_pCurrentEntity->latched.prevanimtime ) ) + { + f = (m_clTime - m_pCurrentEntity->curstate.animtime) / (m_pCurrentEntity->curstate.animtime - m_pCurrentEntity->latched.prevanimtime); + } + + if (m_fDoInterp) + { + // ugly hack to interpolate angle, position. current is reached 0.1 seconds after being set + f = f - 1.0; + } + else + { + f = 0; + } + + for (i = 0; i < 3; i++) + { + modelpos[i] += (m_pCurrentEntity->origin[i] - m_pCurrentEntity->latched.prevorigin[i]) * f; + } + + for (i = 0; i < 3; i++) + { + float ang1, ang2; + + ang1 = m_pCurrentEntity->angles[i]; + ang2 = m_pCurrentEntity->latched.prevangles[i]; + + d = ang1 - ang2; + if (d > 180) + { + d -= 360; + } + else if (d < -180) + { + d += 360; + } + + angles[i] += d * f; + } + } + else if ( m_pCurrentEntity->curstate.movetype != MOVETYPE_NONE ) + { + VectorCopy( m_pCurrentEntity->angles, angles ); + } + + angles[PITCH] = -angles[PITCH]; + AngleMatrix (angles, (*m_protationmatrix)); + + if ( !IEngineStudio.IsHardware() ) + { + static float viewmatrix[3][4]; + + VectorCopy (m_vRight, viewmatrix[0]); + VectorCopy (m_vUp, viewmatrix[1]); + VectorInverse (viewmatrix[1]); + VectorCopy (m_vNormal, viewmatrix[2]); + + (*m_protationmatrix)[0][3] = modelpos[0] - m_vRenderOrigin[0]; + (*m_protationmatrix)[1][3] = modelpos[1] - m_vRenderOrigin[1]; + (*m_protationmatrix)[2][3] = modelpos[2] - m_vRenderOrigin[2]; + + ConcatTransforms (viewmatrix, (*m_protationmatrix), (*m_paliastransform)); + + // do the scaling up of x and y to screen coordinates as part of the transform + // for the unclipped case (it would mess up clipping in the clipped case). + // Also scale down z, so 1/z is scaled 31 bits for free, and scale down x and y + // correspondingly so the projected x and y come out right + // FIXME: make this work for clipped case too? + if (trivial_accept) + { + for (i=0 ; i<4 ; i++) + { + (*m_paliastransform)[0][i] *= m_fSoftwareXScale * + (1.0 / (ZISCALE * 0x10000)); + (*m_paliastransform)[1][i] *= m_fSoftwareYScale * + (1.0 / (ZISCALE * 0x10000)); + (*m_paliastransform)[2][i] *= 1.0 / (ZISCALE * 0x10000); + + } + } + } + + (*m_protationmatrix)[0][3] = modelpos[0]; + (*m_protationmatrix)[1][3] = modelpos[1]; + (*m_protationmatrix)[2][3] = modelpos[2]; +} + + +/* +==================== +StudioEstimateInterpolant + +==================== +*/ +float CStudioModelRenderer::StudioEstimateInterpolant( void ) +{ + float dadt = 1.0; + + if ( m_fDoInterp && ( m_pCurrentEntity->curstate.animtime >= m_pCurrentEntity->latched.prevanimtime + 0.01 ) ) + { + dadt = (m_clTime - m_pCurrentEntity->curstate.animtime) / 0.1; + if (dadt > 2.0) + { + dadt = 2.0; + } + } + return dadt; +} + +/* +==================== +StudioCalcRotations + +==================== +*/ +void CStudioModelRenderer::StudioCalcRotations ( float pos[][3], vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f ) +{ + int i; + int frame; + mstudiobone_t *pbone; + + float s; + float adj[MAXSTUDIOCONTROLLERS]; + float dadt; + + if (f > pseqdesc->numframes - 1) + { + f = 0; // bah, fix this bug with changing sequences too fast + } + // BUG ( somewhere else ) but this code should validate this data. + // This could cause a crash if the frame # is negative, so we'll go ahead + // and clamp it here + else if ( f < -0.01 ) + { + f = -0.01; + } + + frame = (int)f; + + dadt = StudioEstimateInterpolant( ); + s = (f - frame); + + // add in programtic controllers + pbone = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + StudioCalcBoneAdj( dadt, adj, m_pCurrentEntity->curstate.controller, m_pCurrentEntity->latched.prevcontroller, m_pCurrentEntity->mouth.mouthopen ); + + for (i = 0; i < m_pStudioHeader->numbones; i++, pbone++, panim++) + { + StudioCalcBoneQuaterion( frame, s, pbone, panim, adj, q[i] ); + + StudioCalcBonePosition( frame, s, pbone, panim, adj, pos[i] ); + } + + if (pseqdesc->motiontype & STUDIO_X) + { + pos[pseqdesc->motionbone][0] = 0.0; + } + if (pseqdesc->motiontype & STUDIO_Y) + { + pos[pseqdesc->motionbone][1] = 0.0; + } + if (pseqdesc->motiontype & STUDIO_Z) + { + pos[pseqdesc->motionbone][2] = 0.0; + } + + s = 0 * ((1.0 - (f - (int)(f))) / (pseqdesc->numframes)) * m_pCurrentEntity->curstate.framerate; + + if (pseqdesc->motiontype & STUDIO_LX) + { + pos[pseqdesc->motionbone][0] += s * pseqdesc->linearmovement[0]; + } + if (pseqdesc->motiontype & STUDIO_LY) + { + pos[pseqdesc->motionbone][1] += s * pseqdesc->linearmovement[1]; + } + if (pseqdesc->motiontype & STUDIO_LZ) + { + pos[pseqdesc->motionbone][2] += s * pseqdesc->linearmovement[2]; + } +} + +/* +==================== +Studio_FxTransform + +==================== +*/ +void CStudioModelRenderer::StudioFxTransform( cl_entity_t *ent, float transform[3][4] ) +{ + switch( ent->curstate.renderfx ) + { + case kRenderFxDistort: + case kRenderFxHologram: + if ( gEngfuncs.pfnRandomLong(0,49) == 0 ) + { + int axis = gEngfuncs.pfnRandomLong(0,1); + if ( axis == 1 ) // Choose between x & z + axis = 2; + VectorScale( transform[axis], gEngfuncs.pfnRandomFloat(1,1.484), transform[axis] ); + } + else if ( gEngfuncs.pfnRandomLong(0,49) == 0 ) + { + float offset; + int axis = gEngfuncs.pfnRandomLong(0,1); + if ( axis == 1 ) // Choose between x & z + axis = 2; + offset = gEngfuncs.pfnRandomFloat(-10,10); + transform[gEngfuncs.pfnRandomLong(0,2)][3] += offset; + } + break; + case kRenderFxExplode: + { + float scale; + + scale = 1.0 + ( m_clTime - ent->curstate.animtime) * 10.0; + if ( scale > 2 ) // Don't blow up more than 200% + scale = 2; + transform[0][1] *= scale; + transform[1][1] *= scale; + transform[2][1] *= scale; + } + break; + + } +} + +/* +==================== +StudioEstimateFrame + +==================== +*/ +float CStudioModelRenderer::StudioEstimateFrame( mstudioseqdesc_t *pseqdesc ) +{ + double dfdt, f; + + if ( m_fDoInterp ) + { + if ( m_clTime < m_pCurrentEntity->curstate.animtime ) + { + dfdt = 0; + } + else + { + dfdt = (m_clTime - m_pCurrentEntity->curstate.animtime) * m_pCurrentEntity->curstate.framerate * pseqdesc->fps; + + } + } + else + { + dfdt = 0; + } + + if (pseqdesc->numframes <= 1) + { + f = 0; + } + else + { + f = (m_pCurrentEntity->curstate.frame * (pseqdesc->numframes - 1)) / 256.0; + } + + f += dfdt; + + if (pseqdesc->flags & STUDIO_LOOPING) + { + if (pseqdesc->numframes > 1) + { + f -= (int)(f / (pseqdesc->numframes - 1)) * (pseqdesc->numframes - 1); + } + if (f < 0) + { + f += (pseqdesc->numframes - 1); + } + } + else + { + if (f >= pseqdesc->numframes - 1.001) + { + f = pseqdesc->numframes - 1.001; + } + if (f < 0.0) + { + f = 0.0; + } + } + return f; +} + +/* +==================== +StudioSetupBones + +==================== +*/ +void CStudioModelRenderer::StudioSetupBones ( void ) +{ + int i; + double f; + + mstudiobone_t *pbones; + mstudioseqdesc_t *pseqdesc; + mstudioanim_t *panim; + + static float pos[MAXSTUDIOBONES][3]; + static vec4_t q[MAXSTUDIOBONES]; + float bonematrix[3][4]; + + static float pos2[MAXSTUDIOBONES][3]; + static vec4_t q2[MAXSTUDIOBONES]; + static float pos3[MAXSTUDIOBONES][3]; + static vec4_t q3[MAXSTUDIOBONES]; + static float pos4[MAXSTUDIOBONES][3]; + static vec4_t q4[MAXSTUDIOBONES]; + + if (m_pCurrentEntity->curstate.sequence >= m_pStudioHeader->numseq) + { + m_pCurrentEntity->curstate.sequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + f = StudioEstimateFrame( pseqdesc ); + + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + StudioCalcRotations( pos, q, pseqdesc, panim, f ); + + if (pseqdesc->numblends > 1) + { + float s; + float dadt; + + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos2, q2, pseqdesc, panim, f ); + + dadt = StudioEstimateInterpolant(); + s = (m_pCurrentEntity->curstate.blending[0] * dadt + m_pCurrentEntity->latched.prevblending[0] * (1.0 - dadt)) / 255.0; + + StudioSlerpBones( q, pos, q2, pos2, s ); + + if (pseqdesc->numblends == 4) + { + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos3, q3, pseqdesc, panim, f ); + + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos4, q4, pseqdesc, panim, f ); + + s = (m_pCurrentEntity->curstate.blending[0] * dadt + m_pCurrentEntity->latched.prevblending[0] * (1.0 - dadt)) / 255.0; + StudioSlerpBones( q3, pos3, q4, pos4, s ); + + s = (m_pCurrentEntity->curstate.blending[1] * dadt + m_pCurrentEntity->latched.prevblending[1] * (1.0 - dadt)) / 255.0; + StudioSlerpBones( q, pos, q3, pos3, s ); + } + } + + if (m_fDoInterp && + m_pCurrentEntity->latched.sequencetime && + ( m_pCurrentEntity->latched.sequencetime + 0.2 > m_clTime ) && + ( m_pCurrentEntity->latched.prevsequence < m_pStudioHeader->numseq )) + { + // blend from last sequence + static float pos1b[MAXSTUDIOBONES][3]; + static vec4_t q1b[MAXSTUDIOBONES]; + float s; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->latched.prevsequence; + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + // clip prevframe + StudioCalcRotations( pos1b, q1b, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + if (pseqdesc->numblends > 1) + { + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos2, q2, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + s = (m_pCurrentEntity->latched.prevseqblending[0]) / 255.0; + StudioSlerpBones( q1b, pos1b, q2, pos2, s ); + + if (pseqdesc->numblends == 4) + { + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos3, q3, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos4, q4, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + s = (m_pCurrentEntity->latched.prevseqblending[0]) / 255.0; + StudioSlerpBones( q3, pos3, q4, pos4, s ); + + s = (m_pCurrentEntity->latched.prevseqblending[1]) / 255.0; + StudioSlerpBones( q1b, pos1b, q3, pos3, s ); + } + } + + s = 1.0 - (m_clTime - m_pCurrentEntity->latched.sequencetime) / 0.2; + StudioSlerpBones( q, pos, q1b, pos1b, s ); + } + else + { + m_pCurrentEntity->latched.prevframe = f; + } + + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + // calc gait animation + if (m_pPlayerInfo && m_pPlayerInfo->gaitsequence != 0) + { + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pPlayerInfo->gaitsequence; + + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + StudioCalcRotations( pos2, q2, pseqdesc, panim, m_pPlayerInfo->gaitframe ); + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + if (strcmp( pbones[i].name, "Bip01 Spine") == 0) + break; + memcpy( pos[i], pos2[i], sizeof( pos[i] )); + memcpy( q[i], q2[i], sizeof( q[i] )); + } + } + + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + QuaternionMatrix( q[i], bonematrix ); + + bonematrix[0][3] = pos[i][0]; + bonematrix[1][3] = pos[i][1]; + bonematrix[2][3] = pos[i][2]; + + if (pbones[i].parent == -1) + { + if ( IEngineStudio.IsHardware() ) + { + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_pbonetransform)[i]); + + // MatrixCopy should be faster... + //ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + MatrixCopy( (*m_pbonetransform)[i], (*m_plighttransform)[i] ); + } + else + { + ConcatTransforms ((*m_paliastransform), bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + } + + // Apply client-side effects to the transformation matrix + StudioFxTransform( m_pCurrentEntity, (*m_pbonetransform)[i] ); + } + else + { + ConcatTransforms ((*m_pbonetransform)[pbones[i].parent], bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_plighttransform)[pbones[i].parent], bonematrix, (*m_plighttransform)[i]); + } + } +} + + +/* +==================== +StudioSaveBones + +==================== +*/ +void CStudioModelRenderer::StudioSaveBones( void ) +{ + int i; + + mstudiobone_t *pbones; + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + m_nCachedBones = m_pStudioHeader->numbones; + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + strcpy( m_nCachedBoneNames[i], pbones[i].name ); + MatrixCopy( (*m_pbonetransform)[i], m_rgCachedBoneTransform[i] ); + MatrixCopy( (*m_plighttransform)[i], m_rgCachedLightTransform[i] ); + } +} + + +/* +==================== +StudioMergeBones + +==================== +*/ +void CStudioModelRenderer::StudioMergeBones ( model_t *m_pSubModel ) +{ + int i, j; + double f; + int do_hunt = true; + + mstudiobone_t *pbones; + mstudioseqdesc_t *pseqdesc; + mstudioanim_t *panim; + + static float pos[MAXSTUDIOBONES][3]; + float bonematrix[3][4]; + static vec4_t q[MAXSTUDIOBONES]; + + if (m_pCurrentEntity->curstate.sequence >= m_pStudioHeader->numseq) + { + m_pCurrentEntity->curstate.sequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + f = StudioEstimateFrame( pseqdesc ); + + panim = StudioGetAnim( m_pSubModel, pseqdesc ); + StudioCalcRotations( pos, q, pseqdesc, panim, f ); + + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + for (j = 0; j < m_nCachedBones; j++) + { + if (stricmp(pbones[i].name, m_nCachedBoneNames[j]) == 0) + { + MatrixCopy( m_rgCachedBoneTransform[j], (*m_pbonetransform)[i] ); + MatrixCopy( m_rgCachedLightTransform[j], (*m_plighttransform)[i] ); + break; + } + } + if (j >= m_nCachedBones) + { + QuaternionMatrix( q[i], bonematrix ); + + bonematrix[0][3] = pos[i][0]; + bonematrix[1][3] = pos[i][1]; + bonematrix[2][3] = pos[i][2]; + + if (pbones[i].parent == -1) + { + if ( IEngineStudio.IsHardware() ) + { + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_pbonetransform)[i]); + + // MatrixCopy should be faster... + //ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + MatrixCopy( (*m_pbonetransform)[i], (*m_plighttransform)[i] ); + } + else + { + ConcatTransforms ((*m_paliastransform), bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + } + + // Apply client-side effects to the transformation matrix + StudioFxTransform( m_pCurrentEntity, (*m_pbonetransform)[i] ); + } + else + { + ConcatTransforms ((*m_pbonetransform)[pbones[i].parent], bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_plighttransform)[pbones[i].parent], bonematrix, (*m_plighttransform)[i]); + } + } + } +} + +/* +==================== +StudioDrawModel + +==================== +*/ +int CStudioModelRenderer::StudioDrawModel( int flags ) +{ + alight_t lighting; + vec3_t dir; + + m_pCurrentEntity = IEngineStudio.GetCurrentEntity(); + IEngineStudio.GetTimes( &m_nFrameCount, &m_clTime, &m_clOldTime ); + IEngineStudio.GetViewInfo( m_vRenderOrigin, m_vUp, m_vRight, m_vNormal ); + IEngineStudio.GetAliasScale( &m_fSoftwareXScale, &m_fSoftwareYScale ); + + if (m_pCurrentEntity->curstate.renderfx == kRenderFxDeadPlayer) + { + entity_state_t deadplayer; + + int result; + int save_interp; + + if (m_pCurrentEntity->curstate.renderamt <= 0 || m_pCurrentEntity->curstate.renderamt > gEngfuncs.GetMaxClients() ) + return 0; + + // get copy of player + deadplayer = *(IEngineStudio.GetPlayerState( m_pCurrentEntity->curstate.renderamt - 1 )); //cl.frames[cl.parsecount & CL_UPDATE_MASK].playerstate[m_pCurrentEntity->curstate.renderamt-1]; + + // clear weapon, movement state + deadplayer.number = m_pCurrentEntity->curstate.renderamt; + deadplayer.weaponmodel = 0; + deadplayer.gaitsequence = 0; + + deadplayer.movetype = MOVETYPE_NONE; + VectorCopy( m_pCurrentEntity->curstate.angles, deadplayer.angles ); + VectorCopy( m_pCurrentEntity->curstate.origin, deadplayer.origin ); + + save_interp = m_fDoInterp; + m_fDoInterp = 0; + + // draw as though it were a player + result = StudioDrawPlayer( flags, &deadplayer ); + + m_fDoInterp = save_interp; + return result; + } + + m_pRenderModel = m_pCurrentEntity->model; + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (m_pRenderModel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + IEngineStudio.SetRenderModel( m_pRenderModel ); + + StudioSetUpTransform( 0 ); + + if (flags & STUDIO_RENDER) + { + // see if the bounding box lets us trivially reject, also sets + if (!IEngineStudio.StudioCheckBBox ()) + return 0; + + (*m_pModelsDrawn)++; + (*m_pStudioModelCount)++; // render data cache cookie + + if (m_pStudioHeader->numbodyparts == 0) + return 1; + } + + if (m_pCurrentEntity->curstate.movetype == MOVETYPE_FOLLOW) + { + StudioMergeBones( m_pRenderModel ); + } + else + { + StudioSetupBones( ); + } + StudioSaveBones( ); + + if (flags & STUDIO_EVENTS) + { + StudioCalcAttachments( ); + IEngineStudio.StudioClientEvents( ); + // copy attachments into global entity array + if ( m_pCurrentEntity->index > 0 ) + { + cl_entity_t *ent = gEngfuncs.GetEntityByIndex( m_pCurrentEntity->index ); + + memcpy( ent->attachment, m_pCurrentEntity->attachment, sizeof( vec3_t ) * 4 ); + } + } + + if (flags & STUDIO_RENDER) + { + lighting.plightvec = dir; + IEngineStudio.StudioDynamicLight(m_pCurrentEntity, &lighting ); + + IEngineStudio.StudioEntityLight( &lighting ); + + // model and frame independant + IEngineStudio.StudioSetupLighting (&lighting); + + // get remap colors + m_nTopColor = m_pCurrentEntity->curstate.colormap & 0xFF; + m_nBottomColor = (m_pCurrentEntity->curstate.colormap & 0xFF00) >> 8; + + IEngineStudio.StudioSetRemapColors( m_nTopColor, m_nBottomColor ); + + StudioRenderModel( ); + } + + return 1; +} + +/* +==================== +StudioEstimateGait + +==================== +*/ +void CStudioModelRenderer::StudioEstimateGait( entity_state_t *pplayer ) +{ + float dt; + vec3_t est_velocity; + + dt = (m_clTime - m_clOldTime); + if (dt < 0) + dt = 0; + else if (dt > 1.0) + dt = 1; + + if (dt == 0 || m_pPlayerInfo->renderframe == m_nFrameCount) + { + m_flGaitMovement = 0; + return; + } + + // VectorAdd( pplayer->velocity, pplayer->prediction_error, est_velocity ); + if ( m_fGaitEstimation ) + { + VectorSubtract( m_pCurrentEntity->origin, m_pPlayerInfo->prevgaitorigin, est_velocity ); + VectorCopy( m_pCurrentEntity->origin, m_pPlayerInfo->prevgaitorigin ); + m_flGaitMovement = Length( est_velocity ); + if (dt <= 0 || m_flGaitMovement / dt < 5) + { + m_flGaitMovement = 0; + est_velocity[0] = 0; + est_velocity[1] = 0; + } + } + else + { + VectorCopy( pplayer->velocity, est_velocity ); + m_flGaitMovement = Length( est_velocity ) * dt; + } + + if (est_velocity[1] == 0 && est_velocity[0] == 0) + { + float flYawDiff = m_pCurrentEntity->angles[YAW] - m_pPlayerInfo->gaityaw; + flYawDiff = flYawDiff - (int)(flYawDiff / 360) * 360; + if (flYawDiff > 180) + flYawDiff -= 360; + if (flYawDiff < -180) + flYawDiff += 360; + + if (dt < 0.25) + flYawDiff *= dt * 4; + else + flYawDiff *= dt; + + m_pPlayerInfo->gaityaw += flYawDiff; + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - (int)(m_pPlayerInfo->gaityaw / 360) * 360; + + m_flGaitMovement = 0; + } + else + { + m_pPlayerInfo->gaityaw = (atan2(est_velocity[1], est_velocity[0]) * 180 / M_PI); + if (m_pPlayerInfo->gaityaw > 180) + m_pPlayerInfo->gaityaw = 180; + if (m_pPlayerInfo->gaityaw < -180) + m_pPlayerInfo->gaityaw = -180; + } + +} + +/* +==================== +StudioProcessGait + +==================== +*/ +void CStudioModelRenderer::StudioProcessGait( entity_state_t *pplayer ) +{ + mstudioseqdesc_t *pseqdesc; + float dt; + int iBlend; + float flYaw; // view direction relative to movement + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + StudioPlayerBlend( pseqdesc, &iBlend, &m_pCurrentEntity->angles[PITCH] ); + + m_pCurrentEntity->latched.prevangles[PITCH] = m_pCurrentEntity->angles[PITCH]; + m_pCurrentEntity->curstate.blending[0] = iBlend; + m_pCurrentEntity->latched.prevblending[0] = m_pCurrentEntity->curstate.blending[0]; + m_pCurrentEntity->latched.prevseqblending[0] = m_pCurrentEntity->curstate.blending[0]; + + dt = (m_clTime - m_clOldTime); + if (dt < 0) + dt = 0; + else if (dt > 1.0) + dt = 1; + + StudioEstimateGait( pplayer ); + + // calc side to side turning + flYaw = m_pCurrentEntity->angles[YAW] - m_pPlayerInfo->gaityaw; + flYaw = flYaw - (int)(flYaw / 360) * 360; + if (flYaw < -180) + flYaw = flYaw + 360; + if (flYaw > 180) + flYaw = flYaw - 360; + + if (flYaw > 120) + { + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - 180; + m_flGaitMovement = -m_flGaitMovement; + flYaw = flYaw - 180; + } + else if (flYaw < -120) + { + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw + 180; + m_flGaitMovement = -m_flGaitMovement; + flYaw = flYaw + 180; + } + + // adjust torso + m_pCurrentEntity->curstate.controller[0] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); + m_pCurrentEntity->curstate.controller[1] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); + m_pCurrentEntity->curstate.controller[2] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); + m_pCurrentEntity->curstate.controller[3] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); + m_pCurrentEntity->latched.prevcontroller[0] = m_pCurrentEntity->curstate.controller[0]; + m_pCurrentEntity->latched.prevcontroller[1] = m_pCurrentEntity->curstate.controller[1]; + m_pCurrentEntity->latched.prevcontroller[2] = m_pCurrentEntity->curstate.controller[2]; + m_pCurrentEntity->latched.prevcontroller[3] = m_pCurrentEntity->curstate.controller[3]; + + m_pCurrentEntity->angles[YAW] = m_pPlayerInfo->gaityaw; + if (m_pCurrentEntity->angles[YAW] < -0) + m_pCurrentEntity->angles[YAW] += 360; + m_pCurrentEntity->latched.prevangles[YAW] = m_pCurrentEntity->angles[YAW]; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + pplayer->gaitsequence; + + // calc gait frame + if (pseqdesc->linearmovement[0] > 0) + { + m_pPlayerInfo->gaitframe += (m_flGaitMovement / pseqdesc->linearmovement[0]) * pseqdesc->numframes; + } + else + { + m_pPlayerInfo->gaitframe += pseqdesc->fps * dt; + } + + // do modulo + m_pPlayerInfo->gaitframe = m_pPlayerInfo->gaitframe - (int)(m_pPlayerInfo->gaitframe / pseqdesc->numframes) * pseqdesc->numframes; + if (m_pPlayerInfo->gaitframe < 0) + m_pPlayerInfo->gaitframe += pseqdesc->numframes; +} + +/* +==================== +StudioDrawPlayer + +==================== +*/ +int CStudioModelRenderer::StudioDrawPlayer( int flags, entity_state_t *pplayer ) +{ + alight_t lighting; + vec3_t dir; + + m_pCurrentEntity = IEngineStudio.GetCurrentEntity(); + IEngineStudio.GetTimes( &m_nFrameCount, &m_clTime, &m_clOldTime ); + IEngineStudio.GetViewInfo( m_vRenderOrigin, m_vUp, m_vRight, m_vNormal ); + IEngineStudio.GetAliasScale( &m_fSoftwareXScale, &m_fSoftwareYScale ); + + m_nPlayerIndex = pplayer->number - 1; + + if (m_nPlayerIndex < 0 || m_nPlayerIndex >= gEngfuncs.GetMaxClients()) + return 0; + + m_pRenderModel = IEngineStudio.SetupPlayerModel( m_nPlayerIndex ); + if (m_pRenderModel == NULL) + return 0; + + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (m_pRenderModel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + IEngineStudio.SetRenderModel( m_pRenderModel ); + + if (pplayer->gaitsequence) + { + vec3_t orig_angles; + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + + VectorCopy( m_pCurrentEntity->angles, orig_angles ); + + StudioProcessGait( pplayer ); + + m_pPlayerInfo->gaitsequence = pplayer->gaitsequence; + m_pPlayerInfo = NULL; + + StudioSetUpTransform( 0 ); + VectorCopy( orig_angles, m_pCurrentEntity->angles ); + } + else + { + m_pCurrentEntity->curstate.controller[0] = 127; + m_pCurrentEntity->curstate.controller[1] = 127; + m_pCurrentEntity->curstate.controller[2] = 127; + m_pCurrentEntity->curstate.controller[3] = 127; + m_pCurrentEntity->latched.prevcontroller[0] = m_pCurrentEntity->curstate.controller[0]; + m_pCurrentEntity->latched.prevcontroller[1] = m_pCurrentEntity->curstate.controller[1]; + m_pCurrentEntity->latched.prevcontroller[2] = m_pCurrentEntity->curstate.controller[2]; + m_pCurrentEntity->latched.prevcontroller[3] = m_pCurrentEntity->curstate.controller[3]; + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + m_pPlayerInfo->gaitsequence = 0; + + StudioSetUpTransform( 0 ); + } + + if (flags & STUDIO_RENDER) + { + // see if the bounding box lets us trivially reject, also sets + if (!IEngineStudio.StudioCheckBBox ()) + return 0; + + (*m_pModelsDrawn)++; + (*m_pStudioModelCount)++; // render data cache cookie + + if (m_pStudioHeader->numbodyparts == 0) + return 1; + } + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + StudioSetupBones( ); + StudioSaveBones( ); + m_pPlayerInfo->renderframe = m_nFrameCount; + + m_pPlayerInfo = NULL; + + if (flags & STUDIO_EVENTS) + { + StudioCalcAttachments( ); + IEngineStudio.StudioClientEvents( ); + // copy attachments into global entity array + if ( m_pCurrentEntity->index > 0 ) + { + cl_entity_t *ent = gEngfuncs.GetEntityByIndex( m_pCurrentEntity->index ); + + memcpy( ent->attachment, m_pCurrentEntity->attachment, sizeof( vec3_t ) * 4 ); + } + } + + if (flags & STUDIO_RENDER) + { + if (m_pCvarHiModels->value && m_pRenderModel != m_pCurrentEntity->model ) + { + // show highest resolution multiplayer model + m_pCurrentEntity->curstate.body = 255; + } + + if (!(m_pCvarDeveloper->value == 0 && gEngfuncs.GetMaxClients() == 1 ) && ( m_pRenderModel == m_pCurrentEntity->model ) ) + { + m_pCurrentEntity->curstate.body = 1; // force helmet + } + + lighting.plightvec = dir; + IEngineStudio.StudioDynamicLight(m_pCurrentEntity, &lighting ); + + IEngineStudio.StudioEntityLight( &lighting ); + + // model and frame independant + IEngineStudio.StudioSetupLighting (&lighting); + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + + // get remap colors + m_nTopColor = m_pPlayerInfo->topcolor; + if (m_nTopColor < 0) + m_nTopColor = 0; + if (m_nTopColor > 360) + m_nTopColor = 360; + m_nBottomColor = m_pPlayerInfo->bottomcolor; + if (m_nBottomColor < 0) + m_nBottomColor = 0; + if (m_nBottomColor > 360) + m_nBottomColor = 360; + + IEngineStudio.StudioSetRemapColors( m_nTopColor, m_nBottomColor ); + + StudioRenderModel( ); + m_pPlayerInfo = NULL; + + if (pplayer->weaponmodel) + { + cl_entity_t saveent = *m_pCurrentEntity; + + model_t *pweaponmodel = IEngineStudio.GetModelByIndex( pplayer->weaponmodel ); + + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (pweaponmodel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + + StudioMergeBones( pweaponmodel); + + IEngineStudio.StudioSetupLighting (&lighting); + + StudioRenderModel( ); + + StudioCalcAttachments( ); + + *m_pCurrentEntity = saveent; + } + } + + return 1; +} + +/* +==================== +StudioCalcAttachments + +==================== +*/ +void CStudioModelRenderer::StudioCalcAttachments( void ) +{ + int i; + mstudioattachment_t *pattachment; + + if ( m_pStudioHeader->numattachments > 4 ) + { + gEngfuncs.Con_DPrintf( "Too many attachments on %s\n", m_pCurrentEntity->model->name ); + exit( -1 ); + } + + // calculate attachment points + pattachment = (mstudioattachment_t *)((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex); + for (i = 0; i < m_pStudioHeader->numattachments; i++) + { + VectorTransform( pattachment[i].org, (*m_plighttransform)[pattachment[i].bone], m_pCurrentEntity->attachment[i] ); + } +} + +/* +==================== +StudioRenderModel + +==================== +*/ +void CStudioModelRenderer::StudioRenderModel( void ) +{ + IEngineStudio.SetChromeOrigin(); + IEngineStudio.SetForceFaceFlags( 0 ); + + if ( m_pCurrentEntity->curstate.renderfx == kRenderFxGlowShell ) + { + m_pCurrentEntity->curstate.renderfx = kRenderFxNone; + StudioRenderFinal( ); + + if ( !IEngineStudio.IsHardware() ) + { + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + } + + IEngineStudio.SetForceFaceFlags( STUDIO_NF_CHROME ); + + gEngfuncs.pTriAPI->SpriteTexture( m_pChromeSprite, 0 ); + m_pCurrentEntity->curstate.renderfx = kRenderFxGlowShell; + + StudioRenderFinal( ); + if ( !IEngineStudio.IsHardware() ) + { + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); + } + } + else + { + StudioRenderFinal( ); + } +} + +/* +==================== +StudioRenderFinal_Software + +==================== +*/ +void CStudioModelRenderer::StudioRenderFinal_Software( void ) +{ + int i; + + // Note, rendermode set here has effect in SW + IEngineStudio.SetupRenderer( 0 ); + + if (m_pCvarDrawEntities->value == 2) + { + IEngineStudio.StudioDrawBones( ); + } + else if (m_pCvarDrawEntities->value == 3) + { + IEngineStudio.StudioDrawHulls( ); + } + else + { + for (i=0 ; i < m_pStudioHeader->numbodyparts ; i++) + { + IEngineStudio.StudioSetupModel( i, (void **)&m_pBodyPart, (void **)&m_pSubModel ); + IEngineStudio.StudioDrawPoints( ); + } + } + + if (m_pCvarDrawEntities->value == 4) + { + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + IEngineStudio.StudioDrawHulls( ); + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); + } + + if (m_pCvarDrawEntities->value == 5) + { + IEngineStudio.StudioDrawAbsBBox( ); + } + + IEngineStudio.RestoreRenderer(); +} + +/* +==================== +StudioRenderFinal_Hardware + +==================== +*/ +void CStudioModelRenderer::StudioRenderFinal_Hardware( void ) +{ + int i; + int rendermode; + + rendermode = IEngineStudio.GetForceFaceFlags() ? kRenderTransAdd : m_pCurrentEntity->curstate.rendermode; + IEngineStudio.SetupRenderer( rendermode ); + + if (m_pCvarDrawEntities->value == 2) + { + IEngineStudio.StudioDrawBones(); + } + else if (m_pCvarDrawEntities->value == 3) + { + IEngineStudio.StudioDrawHulls(); + } + else + { + for (i=0 ; i < m_pStudioHeader->numbodyparts ; i++) + { + IEngineStudio.StudioSetupModel( i, (void **)&m_pBodyPart, (void **)&m_pSubModel ); + + if (m_fDoInterp) + { + // interpolation messes up bounding boxes. + m_pCurrentEntity->trivial_accept = 0; + } + + IEngineStudio.GL_SetRenderMode( rendermode ); + IEngineStudio.StudioDrawPoints(); + IEngineStudio.GL_StudioDrawShadow(); + } + } + + if ( m_pCvarDrawEntities->value == 4 ) + { + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + IEngineStudio.StudioDrawHulls( ); + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); + } + + IEngineStudio.RestoreRenderer(); +} + +/* +==================== +StudioRenderFinal + +==================== +*/ +void CStudioModelRenderer::StudioRenderFinal(void) +{ + if ( IEngineStudio.IsHardware() ) + { + StudioRenderFinal_Hardware(); + } + else + { + StudioRenderFinal_Software(); + } +} + diff --git a/hlsdk/ricochet/cl_dll/StudioModelRenderer.h b/hlsdk/ricochet/cl_dll/StudioModelRenderer.h new file mode 100644 index 00000000..fae3a8ee --- /dev/null +++ b/hlsdk/ricochet/cl_dll/StudioModelRenderer.h @@ -0,0 +1,182 @@ +#if !defined ( STUDIOMODELRENDERER_H ) +#define STUDIOMODELRENDERER_H +#if defined( _WIN32 ) +#pragma once +#endif + +/* +==================== +CStudioModelRenderer + +==================== +*/ +class CStudioModelRenderer +{ +public: + // Construction/Destruction + CStudioModelRenderer( void ); + virtual ~CStudioModelRenderer( void ); + + // Initialization + virtual void Init( void ); + +public: + // Public Interfaces + virtual int StudioDrawModel ( int flags ); + virtual int StudioDrawPlayer ( int flags, struct entity_state_s *pplayer ); + +public: + // Local interfaces + // + + // Look up animation data for sequence + virtual mstudioanim_t *StudioGetAnim ( model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc ); + + // Interpolate model position and angles and set up matrices + virtual void StudioSetUpTransform (int trivial_accept); + + // Set up model bone positions + virtual void StudioSetupBones ( void ); + + // Find final attachment points + virtual void StudioCalcAttachments ( void ); + + // Save bone matrices and names + virtual void StudioSaveBones( void ); + + // Merge cached bones with current bones for model + virtual void StudioMergeBones ( model_t *m_pSubModel ); + + // Determine interpolation fraction + virtual float StudioEstimateInterpolant( void ); + + // Determine current frame for rendering + virtual float StudioEstimateFrame ( mstudioseqdesc_t *pseqdesc ); + + // Apply special effects to transform matrix + virtual void StudioFxTransform( cl_entity_t *ent, float transform[3][4] ); + + // Spherical interpolation of bones + virtual void StudioSlerpBones ( vec4_t q1[], float pos1[][3], vec4_t q2[], float pos2[][3], float s ); + + // Compute bone adjustments ( bone controllers ) + virtual void StudioCalcBoneAdj ( float dadt, float *adj, const byte *pcontroller1, const byte *pcontroller2, byte mouthopen ); + + // Get bone quaternions + virtual void StudioCalcBoneQuaterion ( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *q ); + + // Get bone positions + virtual void StudioCalcBonePosition ( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *pos ); + + // Compute rotations + virtual void StudioCalcRotations ( float pos[][3], vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f ); + + // Send bones and verts to renderer + virtual void StudioRenderModel ( void ); + + // Finalize rendering + virtual void StudioRenderFinal (void); + + // GL&D3D vs. Software renderer finishing functions + virtual void StudioRenderFinal_Software ( void ); + virtual void StudioRenderFinal_Hardware ( void ); + + // Player specific data + // Determine pitch and blending amounts for players + virtual void StudioPlayerBlend ( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch ); + + // Estimate gait frame for player + virtual void StudioEstimateGait ( entity_state_t *pplayer ); + + // Process movement of player + virtual void StudioProcessGait ( entity_state_t *pplayer ); + +public: + + // Client clock + double m_clTime; + // Old Client clock + double m_clOldTime; + + // Do interpolation? + int m_fDoInterp; + // Do gait estimation? + int m_fGaitEstimation; + + // Current render frame # + int m_nFrameCount; + + // Cvars that studio model code needs to reference + // + // Use high quality models? + cvar_t *m_pCvarHiModels; + // Developer debug output desired? + cvar_t *m_pCvarDeveloper; + // Draw entities bone hit boxes, etc? + cvar_t *m_pCvarDrawEntities; + + // The entity which we are currently rendering. + cl_entity_t *m_pCurrentEntity; + + // The model for the entity being rendered + model_t *m_pRenderModel; + + // Player info for current player, if drawing a player + player_info_t *m_pPlayerInfo; + + // The index of the player being drawn + int m_nPlayerIndex; + + // The player's gait movement + float m_flGaitMovement; + + // Pointer to header block for studio model data + studiohdr_t *m_pStudioHeader; + + // Pointers to current body part and submodel + mstudiobodyparts_t *m_pBodyPart; + mstudiomodel_t *m_pSubModel; + + // Palette substition for top and bottom of model + int m_nTopColor; + int m_nBottomColor; + + // + // Sprite model used for drawing studio model chrome + model_t *m_pChromeSprite; + + // Caching + // Number of bones in bone cache + int m_nCachedBones; + // Names of cached bones + char m_nCachedBoneNames[ MAXSTUDIOBONES ][ 32 ]; + // Cached bone & light transformation matrices + float m_rgCachedBoneTransform [ MAXSTUDIOBONES ][ 3 ][ 4 ]; + float m_rgCachedLightTransform[ MAXSTUDIOBONES ][ 3 ][ 4 ]; + + // Software renderer scale factors + float m_fSoftwareXScale, m_fSoftwareYScale; + + // Current view vectors and render origin + float m_vUp[ 3 ]; + float m_vRight[ 3 ]; + float m_vNormal[ 3 ]; + + float m_vRenderOrigin[ 3 ]; + + // Model render counters ( from engine ) + int *m_pStudioModelCount; + int *m_pModelsDrawn; + + // Matrices + // Model to world transformation + float (*m_protationmatrix)[ 3 ][ 4 ]; + // Model to view transformation + float (*m_paliastransform)[ 3 ][ 4 ]; + + // Concatenated bone and light transforms + float (*m_pbonetransform) [ MAXSTUDIOBONES ][ 3 ][ 4 ]; + float (*m_plighttransform)[ MAXSTUDIOBONES ][ 3 ][ 4 ]; +}; + +#endif // STUDIOMODELRENDERER_H \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/ammo.cpp b/hlsdk/ricochet/cl_dll/ammo.cpp new file mode 100644 index 00000000..c7573c82 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/ammo.cpp @@ -0,0 +1,930 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// +// Ammo.cpp +// +// implementation of CHudAmmo class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "ammohistory.h" + +WEAPON *gpActiveSel; // NULL means off, 1 means just the menu bar, otherwise + // this points to the active weapon menu item +WEAPON *gpLastSel; // Last weapon menu selection + +client_sprite_t *GetSpriteList(client_sprite_t *pList, const char *psz, int iRes, int iCount); + +WeaponsResource gWR; + +int g_weaponselect = 0; + +void WeaponsResource :: LoadAllWeaponSprites( void ) +{ + for (int i = 0; i < MAX_WEAPONS; i++) + { + if ( rgWeapons[i].iId ) + LoadWeaponSprites( &rgWeapons[i] ); + } +} + +int WeaponsResource :: CountAmmo( int iId ) +{ + if ( iId < 0 ) + return 0; + + return riAmmo[iId]; +} + +int WeaponsResource :: HasAmmo( WEAPON *p ) +{ + if ( !p ) + return FALSE; + + // weapons with no max ammo can always be selected + if ( p->iMax1 == -1 ) + return TRUE; + + return (p->iAmmoType == -1) || p->iClip > 0 || CountAmmo(p->iAmmoType) + || CountAmmo(p->iAmmo2Type) || ( p->iFlags & WEAPON_FLAGS_SELECTONEMPTY ); +} + + +void WeaponsResource :: LoadWeaponSprites( WEAPON *pWeapon ) +{ + int i, iRes; + + if (ScreenWidth < 640) + iRes = 320; + else + iRes = 640; + + char sz[128]; + + if ( !pWeapon ) + return; + + memset( &pWeapon->rcActive, 0, sizeof(wrect_t) ); + memset( &pWeapon->rcInactive, 0, sizeof(wrect_t) ); + memset( &pWeapon->rcAmmo, 0, sizeof(wrect_t) ); + memset( &pWeapon->rcAmmo2, 0, sizeof(wrect_t) ); + pWeapon->hInactive = 0; + pWeapon->hActive = 0; + pWeapon->hAmmo = 0; + pWeapon->hAmmo2 = 0; + + sprintf(sz, "sprites/%s.txt", pWeapon->szName); + client_sprite_t *pList = SPR_GetList(sz, &i); + + if (!pList) + return; + + client_sprite_t *p; + + p = GetSpriteList( pList, "crosshair", iRes, i ); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hCrosshair = SPR_Load(sz); + pWeapon->rcCrosshair = p->rc; + } + else + pWeapon->hCrosshair = NULL; + + p = GetSpriteList(pList, "autoaim", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hAutoaim = SPR_Load(sz); + pWeapon->rcAutoaim = p->rc; + } + else + pWeapon->hAutoaim = 0; + + p = GetSpriteList( pList, "zoom", iRes, i ); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hZoomedCrosshair = SPR_Load(sz); + pWeapon->rcZoomedCrosshair = p->rc; + } + else + { + pWeapon->hZoomedCrosshair = pWeapon->hCrosshair; //default to non-zoomed crosshair + pWeapon->rcZoomedCrosshair = pWeapon->rcCrosshair; + } + + p = GetSpriteList(pList, "zoom_autoaim", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hZoomedAutoaim = SPR_Load(sz); + pWeapon->rcZoomedAutoaim = p->rc; + } + else + { + pWeapon->hZoomedAutoaim = pWeapon->hZoomedCrosshair; //default to zoomed crosshair + pWeapon->rcZoomedAutoaim = pWeapon->rcZoomedCrosshair; + } + + p = GetSpriteList(pList, "weapon", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hInactive = SPR_Load(sz); + pWeapon->rcInactive = p->rc; + + gHR.iHistoryGap = max( gHR.iHistoryGap, pWeapon->rcActive.bottom - pWeapon->rcActive.top ); + } + else + pWeapon->hInactive = 0; + + p = GetSpriteList(pList, "weapon_s", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hActive = SPR_Load(sz); + pWeapon->rcActive = p->rc; + } + else + pWeapon->hActive = 0; + + p = GetSpriteList(pList, "ammo", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hAmmo = SPR_Load(sz); + pWeapon->rcAmmo = p->rc; + + gHR.iHistoryGap = max( gHR.iHistoryGap, pWeapon->rcActive.bottom - pWeapon->rcActive.top ); + } + else + pWeapon->hAmmo = 0; + + p = GetSpriteList(pList, "ammo2", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hAmmo2 = SPR_Load(sz); + pWeapon->rcAmmo2 = p->rc; + + gHR.iHistoryGap = max( gHR.iHistoryGap, pWeapon->rcActive.bottom - pWeapon->rcActive.top ); + } + else + pWeapon->hAmmo2 = 0; + +} + +// Returns the first weapon for a given slot. +WEAPON *WeaponsResource :: GetFirstPos( int iSlot ) +{ + WEAPON *pret = NULL; + + for (int i = 0; i < MAX_WEAPON_POSITIONS; i++) + { + if ( rgSlots[iSlot][i] && HasAmmo( rgSlots[iSlot][i] ) ) + { + pret = rgSlots[iSlot][i]; + break; + } + } + + return pret; +} + + +WEAPON* WeaponsResource :: GetNextActivePos( int iSlot, int iSlotPos ) +{ + if ( iSlotPos >= MAX_WEAPON_POSITIONS || iSlot >= MAX_WEAPON_SLOTS ) + return NULL; + + WEAPON *p = gWR.rgSlots[ iSlot ][ iSlotPos+1 ]; + + if ( !p || !gWR.HasAmmo(p) ) + return GetNextActivePos( iSlot, iSlotPos + 1 ); + + return p; +} + + +int giBucketHeight, giBucketWidth, giABHeight, giABWidth; // Ammo Bar width and height + +HSPRITE ghsprBuckets; // Sprite for top row of weapons menu + +DECLARE_MESSAGE(m_Ammo, CurWeapon ); // Current weapon and clip +DECLARE_MESSAGE(m_Ammo, WeaponList); // new weapon type +DECLARE_MESSAGE(m_Ammo, AmmoX); // update known ammo type's count +DECLARE_MESSAGE(m_Ammo, AmmoPickup); // flashes an ammo pickup record +DECLARE_MESSAGE(m_Ammo, WeapPickup); // flashes a weapon pickup record +DECLARE_MESSAGE(m_Ammo, HideWeapon); // hides the weapon, ammo, and crosshair displays temporarily +DECLARE_MESSAGE(m_Ammo, ItemPickup); + +DECLARE_COMMAND(m_Ammo, Slot1); +DECLARE_COMMAND(m_Ammo, Slot2); +DECLARE_COMMAND(m_Ammo, Slot3); +DECLARE_COMMAND(m_Ammo, Slot4); +DECLARE_COMMAND(m_Ammo, Slot5); +DECLARE_COMMAND(m_Ammo, Slot6); +DECLARE_COMMAND(m_Ammo, Slot7); +DECLARE_COMMAND(m_Ammo, Slot8); +DECLARE_COMMAND(m_Ammo, Slot9); +DECLARE_COMMAND(m_Ammo, Slot10); +DECLARE_COMMAND(m_Ammo, Close); +DECLARE_COMMAND(m_Ammo, NextWeapon); +DECLARE_COMMAND(m_Ammo, PrevWeapon); + +// width of ammo fonts +#define AMMO_SMALL_WIDTH 10 +#define AMMO_LARGE_WIDTH 20 + +#define HISTORY_DRAW_TIME "5" + +int CHudAmmo::Init(void) +{ + gHUD.AddHudElem(this); + + HOOK_MESSAGE(CurWeapon); + HOOK_MESSAGE(WeaponList); + HOOK_MESSAGE(AmmoPickup); + HOOK_MESSAGE(WeapPickup); + HOOK_MESSAGE(ItemPickup); + HOOK_MESSAGE(HideWeapon); + HOOK_MESSAGE(AmmoX); + + HOOK_COMMAND("slot1", Slot1); + HOOK_COMMAND("slot2", Slot2); + HOOK_COMMAND("slot3", Slot3); + HOOK_COMMAND("slot4", Slot4); + HOOK_COMMAND("slot5", Slot5); + HOOK_COMMAND("slot6", Slot6); + HOOK_COMMAND("slot7", Slot7); + HOOK_COMMAND("slot8", Slot8); + HOOK_COMMAND("slot9", Slot9); + HOOK_COMMAND("slot10", Slot10); + HOOK_COMMAND("cancelselect", Close); + HOOK_COMMAND("invnext", NextWeapon); + HOOK_COMMAND("invprev", PrevWeapon); + + Reset(); + + CVAR_CREATE( "hud_drawhistory_time", HISTORY_DRAW_TIME, 0 ); + CVAR_CREATE( "hud_fastswitch", "0", FCVAR_ARCHIVE ); // controls whether or not weapons can be selected in one keypress + + m_iFlags |= HUD_ACTIVE; //!!! + + gWR.Init(); + gHR.Init(); + + return 1; +}; + +void CHudAmmo::Reset(void) +{ + m_fFade = 0; + m_iFlags |= HUD_ACTIVE; //!!! + + gpActiveSel = NULL; + gHUD.m_iHideHUDDisplay = 0; + + gWR.Reset(); + gHR.Reset(); + + // VidInit(); + +} + +int CHudAmmo::VidInit(void) +{ + // Load sprites for buckets (top row of weapon menu) + m_HUD_bucket0 = gHUD.GetSpriteIndex( "bucket1" ); + m_HUD_selection = gHUD.GetSpriteIndex( "selection" ); + + ghsprBuckets = gHUD.GetSprite(m_HUD_bucket0); + giBucketWidth = gHUD.GetSpriteRect(m_HUD_bucket0).right - gHUD.GetSpriteRect(m_HUD_bucket0).left; + giBucketHeight = gHUD.GetSpriteRect(m_HUD_bucket0).bottom - gHUD.GetSpriteRect(m_HUD_bucket0).top; + + gHR.iHistoryGap = max( gHR.iHistoryGap, gHUD.GetSpriteRect(m_HUD_bucket0).bottom - gHUD.GetSpriteRect(m_HUD_bucket0).top); + + // If we've already loaded weapons, let's get new sprites + gWR.LoadAllWeaponSprites(); + + if (ScreenWidth >= 640) + { + giABWidth = 20; + giABHeight = 4; + } + else + { + giABWidth = 10; + giABHeight = 2; + } + + return 1; +} + +// +// Think: +// Used for selection of weapon menu item. +// +void CHudAmmo::Think(void) +{ + if ( gHUD.m_fPlayerDead ) + return; + + if ( gHUD.m_iWeaponBits != gWR.iOldWeaponBits ) + { + gWR.iOldWeaponBits = gHUD.m_iWeaponBits; + + for (int i = MAX_WEAPONS-1; i > 0; i-- ) + { + WEAPON *p = gWR.GetWeapon(i); + + if ( p ) + { + if ( gHUD.m_iWeaponBits & ( 1 << p->iId ) ) + gWR.PickupWeapon( p ); + else + gWR.DropWeapon( p ); + } + } + } + + if (!gpActiveSel) + return; + + // has the player selected one? + if (gHUD.m_iKeyBits & IN_ATTACK) + { + 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); + } + +} + +// +// Helper function to return a Ammo pointer from id +// + +HSPRITE* WeaponsResource :: GetAmmoPicFromWeapon( int iAmmoId, wrect_t& rect ) +{ + for ( int i = 0; i < MAX_WEAPONS; i++ ) + { + if ( rgWeapons[i].iAmmoType == iAmmoId ) + { + rect = rgWeapons[i].rcAmmo; + return &rgWeapons[i].hAmmo; + } + else if ( rgWeapons[i].iAmmo2Type == iAmmoId ) + { + rect = rgWeapons[i].rcAmmo2; + return &rgWeapons[i].hAmmo2; + } + } + + return NULL; +} + + +// Menu Selection Code + +void WeaponsResource :: SelectSlot( int iSlot, int fAdvance, int iDirection ) +{ + // Discwar has no weapons to switch to + return; + + if ( gHUD.m_Menu.m_fMenuDisplayed && (fAdvance == FALSE) && (iDirection == 1) ) + { // menu is overriding slot use commands + gHUD.m_Menu.SelectMenuItem( iSlot + 1 ); // slots are one off the key numbers + return; + } + + if ( iSlot > MAX_WEAPON_SLOTS ) + return; + + if ( gHUD.m_fPlayerDead || gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL ) ) + return; + + 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; + + if ( (gpActiveSel == NULL) || (gpActiveSel == (WEAPON *)1) || (iSlot != gpActiveSel->iSlot) ) + { + PlaySound( "common/wpn_hudon.wav", 1 ); + p = GetFirstPos( iSlot ); + + if ( p && fastSwitch ) // check for fast weapon switch mode + { + // if fast weapon switch is on, then weapons can be selected in a single keypress + // but only if there is only one item in the bucket + WEAPON *p2 = GetNextActivePos( p->iSlot, p->iSlotPos ); + if ( !p2 ) + { // only one active item in bucket, so change directly to weapon + ServerCmd( p->szName ); + g_weaponselect = p->iId; + return; + } + } + } + else + { + PlaySound("common/wpn_moveselect.wav", 1); + if ( gpActiveSel ) + p = GetNextActivePos( gpActiveSel->iSlot, gpActiveSel->iSlotPos ); + if ( !p ) + p = GetFirstPos( iSlot ); + } + + + if ( !p ) // no selection found + { + // just display the weapon list, unless fastswitch is on just ignore it + if ( !fastSwitch ) + gpActiveSel = (WEAPON *)1; + else + gpActiveSel = NULL; + } + else + gpActiveSel = p; +} + + +//------------------------------------------------------------------------ +// Message Handlers +//------------------------------------------------------------------------ + +// +// AmmoX -- Update the count of a known type of ammo +// +int CHudAmmo::MsgFunc_AmmoX(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + int iIndex = READ_BYTE(); + int iCount = READ_BYTE(); + + gWR.SetAmmo( iIndex, abs(iCount) ); + + return 1; +} + +int CHudAmmo::MsgFunc_AmmoPickup( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + int iIndex = READ_BYTE(); + int iCount = READ_BYTE(); + + // Add ammo to the history + gHR.AddToHistory( HISTSLOT_AMMO, iIndex, abs(iCount) ); + + return 1; +} + +int CHudAmmo::MsgFunc_WeapPickup( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + int iIndex = READ_BYTE(); + + // Add the weapon to the history + gHR.AddToHistory( HISTSLOT_WEAP, iIndex ); + + return 1; +} + +int CHudAmmo::MsgFunc_ItemPickup( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + const char *szName = READ_STRING(); + + // Add the weapon to the history + gHR.AddToHistory( HISTSLOT_ITEM, szName ); + + return 1; +} + + +int CHudAmmo::MsgFunc_HideWeapon( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + gHUD.m_iHideHUDDisplay = READ_BYTE(); + + if ( gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL ) ) + { + static wrect_t nullrc; + gpActiveSel = NULL; + SetCrosshair( 0, nullrc, 0, 0, 0 ); + } + else + { + if ( m_pWeapon ) + SetCrosshair( m_pWeapon->hCrosshair, m_pWeapon->rcCrosshair, 255, 255, 255 ); + } + + return 1; +} + +// +// CurWeapon: Update hud state with the current weapon and clip count. Ammo +// counts are updated with AmmoX. Server assures that the Weapon ammo type +// numbers match a real ammo type. +// +int CHudAmmo::MsgFunc_CurWeapon(const char *pszName, int iSize, void *pbuf ) +{ + static wrect_t nullrc; + int fOnTarget = FALSE; + + BEGIN_READ( pbuf, iSize ); + + int iState = READ_BYTE(); + int iId = READ_CHAR(); + int iClip = READ_CHAR(); + + // detect if we're also on target + if ( iState > 1 ) + { + fOnTarget = TRUE; + } + + if ( iId < 1 ) + { + SetCrosshair(0, nullrc, 0, 0, 0); + return 0; + } + + // Is player dead??? + if ((iId == -1) && (iClip == -1)) + { + gHUD.m_fPlayerDead = TRUE; + gpActiveSel = NULL; + return 1; + } + gHUD.m_fPlayerDead = FALSE; + + WEAPON *pWeapon = gWR.GetWeapon( iId ); + + if ( !pWeapon ) + return 0; + + if ( iClip < -1 ) + pWeapon->iClip = abs(iClip); + else + pWeapon->iClip = iClip; + + + 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 ) + { // normal crosshairs + if (fOnTarget && m_pWeapon->hAutoaim) + SetCrosshair(m_pWeapon->hAutoaim, m_pWeapon->rcAutoaim, 255, 255, 255); + else + SetCrosshair(m_pWeapon->hCrosshair, m_pWeapon->rcCrosshair, 255, 255, 255); + } + else + { // zoomed crosshairs + if (fOnTarget && m_pWeapon->hZoomedAutoaim) + SetCrosshair(m_pWeapon->hZoomedAutoaim, m_pWeapon->rcZoomedAutoaim, 255, 255, 255); + else + SetCrosshair(m_pWeapon->hZoomedCrosshair, m_pWeapon->rcZoomedCrosshair, 255, 255, 255); + } + } + + m_fFade = 200.0f; //!!! + m_iFlags |= HUD_ACTIVE; + + return 1; +} + +// +// WeaponList -- Tells the hud about a new weapon type. +// +int CHudAmmo::MsgFunc_WeaponList(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + WEAPON Weapon; + + strcpy( Weapon.szName, READ_STRING() ); + Weapon.iAmmoType = (int)READ_CHAR(); + + Weapon.iMax1 = READ_BYTE(); + if (Weapon.iMax1 == 255) + Weapon.iMax1 = -1; + + Weapon.iAmmo2Type = READ_CHAR(); + Weapon.iMax2 = READ_BYTE(); + if (Weapon.iMax2 == 255) + Weapon.iMax2 = -1; + + Weapon.iSlot = READ_CHAR(); + Weapon.iSlotPos = READ_CHAR(); + Weapon.iId = READ_CHAR(); + Weapon.iFlags = READ_BYTE(); + Weapon.iClip = 0; + + gWR.AddWeapon( &Weapon ); + + return 1; + +} + +//------------------------------------------------------------------------ +// Command Handlers +//------------------------------------------------------------------------ + +void CHudAmmo::UserCmd_Slot1(void) +{ + gWR.SelectSlot(0, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot2(void) +{ + gWR.SelectSlot(1, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot3(void) +{ + gWR.SelectSlot(2, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot4(void) +{ + gWR.SelectSlot(3, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot5(void) +{ + gWR.SelectSlot(4, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot6(void) +{ + gWR.SelectSlot(5, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot7(void) +{ + gWR.SelectSlot(6, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot8(void) +{ + gWR.SelectSlot(7, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot9(void) +{ + gWR.SelectSlot(8, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot10(void) +{ + gWR.SelectSlot(9, FALSE, 1); +} + +void CHudAmmo::UserCmd_Close(void) +{ + if (gpActiveSel) + { + gpLastSel = gpActiveSel; + gpActiveSel = NULL; + PlaySound("common/wpn_hudoff.wav", 1); + } + else + ClientCmd("escape"); +} + + +// Selects the next item in the weapon menu +void CHudAmmo::UserCmd_NextWeapon(void) +{ + if ( gHUD.m_fPlayerDead || (gHUD.m_iHideHUDDisplay & (HIDEHUD_WEAPONS | HIDEHUD_ALL)) ) + return; + + if ( !gpActiveSel || gpActiveSel == (WEAPON*)1 ) + gpActiveSel = m_pWeapon; + + int pos = 0; + int slot = 0; + if ( gpActiveSel ) + { + pos = gpActiveSel->iSlotPos + 1; + slot = gpActiveSel->iSlot; + } + + for ( int loop = 0; loop <= 1; loop++ ) + { + for ( ; slot < MAX_WEAPON_SLOTS; slot++ ) + { + for ( ; pos < MAX_WEAPON_POSITIONS; pos++ ) + { + WEAPON *wsp = gWR.GetWeaponSlot( slot, pos ); + + if ( wsp && gWR.HasAmmo(wsp) ) + { + gpActiveSel = wsp; + return; + } + } + + pos = 0; + } + + slot = 0; // start looking from the first slot again + } + + gpActiveSel = NULL; +} + +// Selects the previous item in the menu +void CHudAmmo::UserCmd_PrevWeapon(void) +{ + if ( gHUD.m_fPlayerDead || (gHUD.m_iHideHUDDisplay & (HIDEHUD_WEAPONS | HIDEHUD_ALL)) ) + return; + + if ( !gpActiveSel || gpActiveSel == (WEAPON*)1 ) + gpActiveSel = m_pWeapon; + + int pos = MAX_WEAPON_POSITIONS-1; + int slot = MAX_WEAPON_SLOTS-1; + if ( gpActiveSel ) + { + pos = gpActiveSel->iSlotPos - 1; + slot = gpActiveSel->iSlot; + } + + for ( int loop = 0; loop <= 1; loop++ ) + { + for ( ; slot >= 0; slot-- ) + { + for ( ; pos >= 0; pos-- ) + { + WEAPON *wsp = gWR.GetWeaponSlot( slot, pos ); + + if ( wsp && gWR.HasAmmo(wsp) ) + { + gpActiveSel = wsp; + return; + } + } + + pos = MAX_WEAPON_POSITIONS-1; + } + + slot = MAX_WEAPON_SLOTS-1; + } + + gpActiveSel = NULL; +} + + + +//------------------------------------------------------------------------- +// Drawing code +//------------------------------------------------------------------------- + +int CHudAmmo::Draw(float flTime) +{ + // No ammo in discwar + return 1; +} + + +// +// Draws the ammo bar on the hud +// +int DrawBar(int x, int y, int width, int height, float f) +{ + int r, g, b; + + if (f < 0) + f = 0; + if (f > 1) + f = 1; + + if (f) + { + int w = f * width; + + // Always show at least one pixel if we have ammo. + if (w <= 0) + w = 1; + UnpackRGB(r, g, b, RGB_GREENISH); + FillRGBA(x, y, w, height, r, g, b, 255); + x += w; + width -= w; + } + + UnpackRGB(r, g, b, RGB_YELLOWISH); + + FillRGBA(x, y, width, height, r, g, b, 128); + + return (x + width); +} + + + +void DrawAmmoBar(WEAPON *p, int x, int y, int width, int height) +{ + if ( !p ) + return; + + if (p->iAmmoType != -1) + { + if (!gWR.CountAmmo(p->iAmmoType)) + return; + + float f = (float)gWR.CountAmmo(p->iAmmoType)/(float)p->iMax1; + + x = DrawBar(x, y, width, height, f); + + + // Do we have secondary ammo too? + + if (p->iAmmo2Type != -1) + { + f = (float)gWR.CountAmmo(p->iAmmo2Type)/(float)p->iMax2; + + x += 5; //!!! + + DrawBar(x, y, width, height, f); + } + } +} + + + + +// +// Draw Weapon Menu +// +int CHudAmmo::DrawWList(float flTime) +{ + // No ammo in discwar + return 0; +} + + +/* ================================= + GetSpriteList + +Finds and returns the matching +sprite name 'psz' and resolution 'iRes' +in the given sprite list 'pList' +iCount is the number of items in the pList +================================= */ +client_sprite_t *GetSpriteList(client_sprite_t *pList, const char *psz, int iRes, int iCount) +{ + if (!pList) + return NULL; + + int i = iCount; + client_sprite_t *p = pList; + + while(i--) + { + if ((!strcmp(psz, p->szName)) && (p->iRes == iRes)) + return p; + p++; + } + + return NULL; +} diff --git a/hlsdk/ricochet/cl_dll/ammo.h b/hlsdk/ricochet/cl_dll/ammo.h new file mode 100644 index 00000000..a3125ab7 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/ammo.h @@ -0,0 +1,62 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ + +#ifndef __AMMO_H__ +#define __AMMO_H__ + +#define MAX_WEAPON_NAME 128 + + +#define WEAPON_FLAGS_SELECTONEMPTY 1 + +#define WEAPON_IS_ONTARGET 0x40 + +struct WEAPON +{ + char szName[MAX_WEAPON_NAME]; + int iAmmoType; + int iAmmo2Type; + int iMax1; + int iMax2; + int iSlot; + int iSlotPos; + int iFlags; + int iId; + int iClip; + + int iCount; // # of itesm in plist + + HSPRITE hActive; + wrect_t rcActive; + HSPRITE hInactive; + wrect_t rcInactive; + HSPRITE hAmmo; + wrect_t rcAmmo; + HSPRITE hAmmo2; + wrect_t rcAmmo2; + HSPRITE hCrosshair; + wrect_t rcCrosshair; + HSPRITE hAutoaim; + wrect_t rcAutoaim; + HSPRITE hZoomedCrosshair; + wrect_t rcZoomedCrosshair; + HSPRITE hZoomedAutoaim; + wrect_t rcZoomedAutoaim; +}; + +typedef int AMMO; + + +#endif \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/ammo_secondary.cpp b/hlsdk/ricochet/cl_dll/ammo_secondary.cpp new file mode 100644 index 00000000..d71b2ee4 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/ammo_secondary.cpp @@ -0,0 +1,159 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// +// ammo_secondary.cpp +// +// implementation of CHudAmmoSecondary class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE( m_AmmoSecondary, SecAmmoVal ); +DECLARE_MESSAGE( m_AmmoSecondary, SecAmmoIcon ); + +int CHudAmmoSecondary :: Init( void ) +{ + HOOK_MESSAGE( SecAmmoVal ); + HOOK_MESSAGE( SecAmmoIcon ); + + gHUD.AddHudElem(this); + m_HUD_ammoicon = 0; + + for ( int i = 0; i < MAX_SEC_AMMO_VALUES; i++ ) + m_iAmmoAmounts[i] = -1; // -1 means don't draw this value + + Reset(); + + return 1; +} + +void CHudAmmoSecondary :: Reset( void ) +{ + m_fFade = 0; +} + +int CHudAmmoSecondary :: VidInit( void ) +{ + return 1; +} + +int CHudAmmoSecondary :: Draw(float flTime) +{ + if ( (gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL )) ) + return 1; + + // draw secondary ammo icons above normal ammo readout + int a, x, y, r, g, b, AmmoWidth; + UnpackRGB( r, g, b, RGB_YELLOWISH ); + a = (int) max( MIN_ALPHA, m_fFade ); + if (m_fFade > 0) + m_fFade -= (gHUD.m_flTimeDelta * 20); // slowly lower alpha to fade out icons + ScaleColors( r, g, b, a ); + + AmmoWidth = gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left; + + y = ScreenHeight - (gHUD.m_iFontHeight*4); // this is one font height higher than the weapon ammo values + x = ScreenWidth - AmmoWidth; + + if ( m_HUD_ammoicon ) + { + // Draw the ammo icon + x -= (gHUD.GetSpriteRect(m_HUD_ammoicon).right - gHUD.GetSpriteRect(m_HUD_ammoicon).left); + y -= (gHUD.GetSpriteRect(m_HUD_ammoicon).top - gHUD.GetSpriteRect(m_HUD_ammoicon).bottom); + + SPR_Set( gHUD.GetSprite(m_HUD_ammoicon), r, g, b ); + SPR_DrawAdditive( 0, x, y, &gHUD.GetSpriteRect(m_HUD_ammoicon) ); + } + else + { // move the cursor by the '0' char instead, since we don't have an icon to work with + x -= AmmoWidth; + y -= (gHUD.GetSpriteRect(gHUD.m_HUD_number_0).top - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).bottom); + } + + // draw the ammo counts, in reverse order, from right to left + for ( int i = MAX_SEC_AMMO_VALUES-1; i >= 0; i-- ) + { + if ( m_iAmmoAmounts[i] < 0 ) + continue; // negative ammo amounts imply that they shouldn't be drawn + + // half a char gap between the ammo number and the previous pic + x -= (AmmoWidth / 2); + + // draw the number, right-aligned + x -= (gHUD.GetNumWidth( m_iAmmoAmounts[i], DHN_DRAWZERO ) * AmmoWidth); + gHUD.DrawHudNumber( x, y, DHN_DRAWZERO, m_iAmmoAmounts[i], r, g, b ); + + if ( i != 0 ) + { + // draw the divider bar + x -= (AmmoWidth / 2); + FillRGBA(x, y, (AmmoWidth/10), gHUD.m_iFontHeight, r, g, b, a); + } + } + + return 1; +} + +// Message handler for Secondary Ammo Value +// accepts one value: +// string: sprite name +int CHudAmmoSecondary :: MsgFunc_SecAmmoIcon( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + m_HUD_ammoicon = gHUD.GetSpriteIndex( READ_STRING() ); + + return 1; +} + +// Message handler for Secondary Ammo Icon +// Sets an ammo value +// takes two values: +// byte: ammo index +// byte: ammo value +int CHudAmmoSecondary :: MsgFunc_SecAmmoVal( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int index = READ_BYTE(); + if ( index < 0 || index >= MAX_SEC_AMMO_VALUES ) + return 1; + + m_iAmmoAmounts[index] = READ_BYTE(); + m_iFlags |= HUD_ACTIVE; + + // check to see if there is anything left to draw + int count = 0; + for ( int i = 0; i < MAX_SEC_AMMO_VALUES; i++ ) + { + count += max( 0, m_iAmmoAmounts[i] ); + } + + if ( count == 0 ) + { // the ammo fields are all empty, so turn off this hud area + m_iFlags &= ~HUD_ACTIVE; + return 1; + } + + // make the icons light up + m_fFade = 200.0f; + + return 1; +} + + diff --git a/hlsdk/ricochet/cl_dll/ammohistory.cpp b/hlsdk/ricochet/cl_dll/ammohistory.cpp new file mode 100644 index 00000000..e9cbbd42 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/ammohistory.cpp @@ -0,0 +1,190 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// +// ammohistory.cpp +// + + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "ammohistory.h" + +HistoryResource gHR; + +#define AMMO_PICKUP_GAP (gHR.iHistoryGap+5) +#define AMMO_PICKUP_PICK_HEIGHT (32 + (gHR.iHistoryGap * 2)) +#define AMMO_PICKUP_HEIGHT_MAX (ScreenHeight - 100) + +#define MAX_ITEM_NAME 32 +int HISTORY_DRAW_TIME = 5; + +// keep a list of items +struct ITEM_INFO +{ + char szName[MAX_ITEM_NAME]; + HSPRITE spr; + wrect_t rect; +}; + +void HistoryResource :: AddToHistory( int iType, int iId, int iCount ) +{ + if ( iType == HISTSLOT_AMMO && !iCount ) + return; // no amount, so don't add + + if ( (((AMMO_PICKUP_GAP * iCurrentHistorySlot) + AMMO_PICKUP_PICK_HEIGHT) > AMMO_PICKUP_HEIGHT_MAX) || (iCurrentHistorySlot >= MAX_HISTORY) ) + { // the pic would have to be drawn too high + // so start from the bottom + iCurrentHistorySlot = 0; + } + + HIST_ITEM *freeslot = &rgAmmoHistory[iCurrentHistorySlot++]; // default to just writing to the first slot + HISTORY_DRAW_TIME = CVAR_GET_FLOAT( "hud_drawhistory_time" ); + + freeslot->type = iType; + freeslot->iId = iId; + freeslot->iCount = iCount; + freeslot->DisplayTime = gHUD.m_flTime + HISTORY_DRAW_TIME; +} + +void HistoryResource :: AddToHistory( int iType, const char *szName, int iCount ) +{ + if ( iType != HISTSLOT_ITEM ) + return; + + if ( (((AMMO_PICKUP_GAP * iCurrentHistorySlot) + AMMO_PICKUP_PICK_HEIGHT) > AMMO_PICKUP_HEIGHT_MAX) || (iCurrentHistorySlot >= MAX_HISTORY) ) + { // the pic would have to be drawn too high + // so start from the bottom + iCurrentHistorySlot = 0; + } + + HIST_ITEM *freeslot = &rgAmmoHistory[iCurrentHistorySlot++]; // default to just writing to the first slot + + // I am really unhappy with all the code in this file + + int i = gHUD.GetSpriteIndex( szName ); + if ( i == -1 ) + return; // unknown sprite name, don't add it to history + + freeslot->iId = i; + freeslot->type = iType; + freeslot->iCount = iCount; + + HISTORY_DRAW_TIME = CVAR_GET_FLOAT( "hud_drawhistory_time" ); + freeslot->DisplayTime = gHUD.m_flTime + HISTORY_DRAW_TIME; +} + + +void HistoryResource :: CheckClearHistory( void ) +{ + for ( int i = 0; i < MAX_HISTORY; i++ ) + { + if ( rgAmmoHistory[i].type ) + return; + } + + iCurrentHistorySlot = 0; +} + +// +// Draw Ammo pickup history +// +int HistoryResource :: DrawAmmoHistory( float flTime ) +{ + for ( int i = 0; i < MAX_HISTORY; i++ ) + { + if ( rgAmmoHistory[i].type ) + { + rgAmmoHistory[i].DisplayTime = min( rgAmmoHistory[i].DisplayTime, gHUD.m_flTime + HISTORY_DRAW_TIME ); + + if ( rgAmmoHistory[i].DisplayTime <= flTime ) + { // pic drawing time has expired + memset( &rgAmmoHistory[i], 0, sizeof(HIST_ITEM) ); + CheckClearHistory(); + } + else if ( rgAmmoHistory[i].type == HISTSLOT_AMMO ) + { + wrect_t rcPic; + HSPRITE *spr = gWR.GetAmmoPicFromWeapon( rgAmmoHistory[i].iId, rcPic ); + + int r, g, b; + UnpackRGB(r,g,b, RGB_YELLOWISH); + float scale = (rgAmmoHistory[i].DisplayTime - flTime) * 80; + ScaleColors(r, g, b, min(scale, 255) ); + + // Draw the pic + int ypos = ScreenHeight - (AMMO_PICKUP_PICK_HEIGHT + (AMMO_PICKUP_GAP * i)); + int xpos = ScreenWidth - 24; + if ( spr && *spr ) // weapon isn't loaded yet so just don't draw the pic + { // the dll has to make sure it has sent info the weapons you need + SPR_Set( *spr, r, g, b ); + SPR_DrawAdditive( 0, xpos, ypos, &rcPic ); + } + + // Draw the number + gHUD.DrawHudNumberString( xpos - 10, ypos, xpos - 100, rgAmmoHistory[i].iCount, r, g, b ); + } + else if ( rgAmmoHistory[i].type == HISTSLOT_WEAP ) + { + WEAPON *weap = gWR.GetWeapon( rgAmmoHistory[i].iId ); + + if ( !weap ) + 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); + + if ( !gWR.HasAmmo( weap ) ) + UnpackRGB(r,g,b, RGB_REDISH); // if the weapon doesn't have ammo, display it as red + + float scale = (rgAmmoHistory[i].DisplayTime - flTime) * 80; + ScaleColors(r, g, b, min(scale, 255) ); + + int ypos = ScreenHeight - (AMMO_PICKUP_PICK_HEIGHT + (AMMO_PICKUP_GAP * i)); + int xpos = ScreenWidth - (weap->rcInactive.right - weap->rcInactive.left); + SPR_Set( weap->hInactive, r, g, b ); + SPR_DrawAdditive( 0, xpos, ypos, &weap->rcInactive ); + } + else if ( rgAmmoHistory[i].type == HISTSLOT_ITEM ) + { + int r, g, b; + + if ( !rgAmmoHistory[i].iId ) + continue; // sprite not loaded + + wrect_t rect = gHUD.GetSpriteRect( rgAmmoHistory[i].iId ); + + UnpackRGB(r,g,b, RGB_YELLOWISH); + float scale = (rgAmmoHistory[i].DisplayTime - flTime) * 80; + ScaleColors(r, g, b, min(scale, 255) ); + + int ypos = ScreenHeight - (AMMO_PICKUP_PICK_HEIGHT + (AMMO_PICKUP_GAP * i)); + int xpos = ScreenWidth - (rect.right - rect.left) - 10; + + SPR_Set( gHUD.GetSprite( rgAmmoHistory[i].iId ), r, g, b ); + SPR_DrawAdditive( 0, xpos, ypos, &rect ); + } + } + } + + + return 1; +} + + diff --git a/hlsdk/ricochet/cl_dll/ammohistory.h b/hlsdk/ricochet/cl_dll/ammohistory.h new file mode 100644 index 00000000..a717508f --- /dev/null +++ b/hlsdk/ricochet/cl_dll/ammohistory.h @@ -0,0 +1,143 @@ +/*** +* +* 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. +* 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. +* +****/ +// +// ammohistory.h +// + +// this is the max number of items in each bucket +#define MAX_WEAPON_POSITIONS MAX_WEAPON_SLOTS + +class WeaponsResource +{ +private: + // Information about weapons & ammo + WEAPON rgWeapons[MAX_WEAPONS]; // Weapons Array + + // 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 ) + { + memset( rgWeapons, 0, sizeof rgWeapons ); + Reset(); + } + + void Reset( void ) + { + iOldWeaponBits = 0; + memset( rgSlots, 0, sizeof rgSlots ); + memset( riAmmo, 0, sizeof riAmmo ); + } + +///// WEAPON ///// + int iOldWeaponBits; + + WEAPON *GetWeapon( int iId ) { return &rgWeapons[iId]; } + void AddWeapon( WEAPON *wp ) + { + rgWeapons[ wp->iId ] = *wp; + LoadWeaponSprites( &rgWeapons[ wp->iId ] ); + } + + void PickupWeapon( WEAPON *wp ) + { + rgSlots[ wp->iSlot ][ wp->iSlotPos ] = wp; + } + + void DropWeapon( WEAPON *wp ) + { + rgSlots[ wp->iSlot ][ wp->iSlotPos ] = NULL; + } + + void DropAllWeapons( void ) + { + for ( int i = 0; i < MAX_WEAPONS; i++ ) + { + if ( rgWeapons[i].iId ) + DropWeapon( &rgWeapons[i] ); + } + } + + WEAPON* GetWeaponSlot( int slot, int pos ) { return rgSlots[slot][pos]; } + + void LoadWeaponSprites( WEAPON* wp ); + void LoadAllWeaponSprites( void ); + WEAPON* GetFirstPos( int iSlot ); + void SelectSlot( int iSlot, int fAdvance, int iDirection ); + WEAPON* GetNextActivePos( int iSlot, int iSlotPos ); + + int HasAmmo( WEAPON *p ); + +///// AMMO ///// + AMMO GetAmmo( int iId ) { return riAmmo[ iId ]; } + + void SetAmmo( int iId, int iCount ) { riAmmo[ iId ] = iCount; } + + int CountAmmo( int iId ); + + HSPRITE* GetAmmoPicFromWeapon( int iAmmoId, wrect_t& rect ); +}; + +extern WeaponsResource gWR; + + +#define MAX_HISTORY 12 +enum { + HISTSLOT_EMPTY, + HISTSLOT_AMMO, + HISTSLOT_WEAP, + HISTSLOT_ITEM, +}; + +class HistoryResource +{ +private: + struct HIST_ITEM { + int type; + float DisplayTime; // the time at which this item should be removed from the history + int iCount; + int iId; + }; + + HIST_ITEM rgAmmoHistory[MAX_HISTORY]; + +public: + + void Init( void ) + { + Reset(); + } + + void Reset( void ) + { + memset( rgAmmoHistory, 0, sizeof rgAmmoHistory ); + } + + int iHistoryGap; + int iCurrentHistorySlot; + + void AddToHistory( int iType, int iId, int iCount = 0 ); + void AddToHistory( int iType, const char *szName, int iCount = 0 ); + + void CheckClearHistory( void ); + int DrawAmmoHistory( float flTime ); +}; + +extern HistoryResource gHR; + + + diff --git a/hlsdk/ricochet/cl_dll/battery.cpp b/hlsdk/ricochet/cl_dll/battery.cpp new file mode 100644 index 00000000..f5e9d390 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/battery.cpp @@ -0,0 +1,79 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// +// battery.cpp +// +// implementation of CHudBattery class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +DECLARE_MESSAGE(m_Battery, Battery) + +int CHudBattery::Init(void) +{ + m_iBat = 0; + m_fFade = 0; + m_iFlags = 0; + + HOOK_MESSAGE(Battery); + + gHUD.AddHudElem(this); + + return 1; +}; + + +int CHudBattery::VidInit(void) +{ + int HUD_suit_empty = gHUD.GetSpriteIndex( "suit_empty" ); + int HUD_suit_full = gHUD.GetSpriteIndex( "suit_full" ); + + m_hSprite1 = m_hSprite2 = 0; // delaying get sprite handles until we know the sprites are loaded + m_prc1 = &gHUD.GetSpriteRect( HUD_suit_empty ); + m_prc2 = &gHUD.GetSpriteRect( HUD_suit_full ); + m_iHeight = m_prc2->bottom - m_prc1->top; + m_fFade = 0; + return 1; +}; + +int CHudBattery:: MsgFunc_Battery(const char *pszName, int iSize, void *pbuf ) +{ + m_iFlags |= HUD_ACTIVE; + + + BEGIN_READ( pbuf, iSize ); + int x = READ_SHORT(); + + if (x != m_iBat) + { + m_fFade = FADE_TIME; + m_iBat = x; + } + + return 1; +} + + +int CHudBattery::Draw(float flTime) +{ + // No Armor in Discwar + return 1; +} \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/camera.h b/hlsdk/ricochet/cl_dll/camera.h new file mode 100644 index 00000000..607142bf --- /dev/null +++ b/hlsdk/ricochet/cl_dll/camera.h @@ -0,0 +1,31 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// Camera.h -- defines and such for a 3rd person camera +// NOTE: must include quakedef.h first + +#ifndef _CAMERA_H_ +#define _CAMEA_H_ + +// pitch, yaw, dist +extern vec3_t cam_ofs; +// Using third person camera +extern int cam_thirdperson; + +void CAM_Init( void ); +void CAM_ClearStates( void ); +void CAM_StartMouseMove(void); +void CAM_EndMouseMove(void); + +#endif // _CAMERA_H_ diff --git a/hlsdk/ricochet/cl_dll/cdll_int.cpp b/hlsdk/ricochet/cl_dll/cdll_int.cpp new file mode 100644 index 00000000..c6cd1cdb --- /dev/null +++ b/hlsdk/ricochet/cl_dll/cdll_int.cpp @@ -0,0 +1,268 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// +// cdll_int.c +// +// this implementation handles the linking of the engine to the DLL +// + +#include "hud.h" +#include "cl_util.h" +#include +#include "netadr.h" +#include "vgui_SchemeManager.h" + +#define DLLEXPORT __declspec( dllexport ) +cl_enginefunc_t gEngfuncs; +CHud gHUD ; +TeamFortressViewport *gViewPort = NULL; +extern "C" +{ +#include "pm_shared.h" +} + +#include "hud_servers.h" +#include "vgui_int.h" + + +void InitInput (void); +void EV_HookEvents( void ); +void IN_Commands( void ); + +/* +========================== + Initialize + +Called when the DLL is first loaded. +========================== +*/ +extern "C" +{ +int DLLEXPORT Initialize( cl_enginefunc_t *pEnginefuncs, int iVersion ); +int DLLEXPORT HUD_VidInit( void ); +void DLLEXPORT HUD_Init( void ); +int DLLEXPORT HUD_Redraw( float flTime, int intermission ); +int DLLEXPORT HUD_UpdateClientData( client_data_t *cdata, float flTime ); +void DLLEXPORT HUD_Reset ( void ); +void DLLEXPORT HUD_PlayerMove( struct playermove_s *ppmove, int server ); +void DLLEXPORT HUD_PlayerMoveInit( struct playermove_s *ppmove ); +char DLLEXPORT HUD_PlayerMoveTexture( char *name ); +int DLLEXPORT HUD_ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); +int DLLEXPORT HUD_GetHullBounds( int hullnumber, float *mins, float *maxs ); +void DLLEXPORT HUD_Frame( double time ); +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 ); +void DLLEXPORT HUD_VoiceStatus(int entindex, qboolean bTalking); +} + +/* +================================ +HUD_GetHullBounds + + Engine calls this to enumerate player collision hulls, for prediction. Return 0 if the hullnumber doesn't exist. +================================ +*/ +int DLLEXPORT HUD_GetHullBounds( int hullnumber, float *mins, float *maxs ) +{ + int iret = 0; + + switch ( hullnumber ) + { + case 0: // Normal player + mins = Vector(-16, -16, -36); + maxs = Vector(16, 16, 36); + iret = 1; + break; + case 1: // Crouched player + mins = Vector(-16, -16, -18 ); + maxs = Vector(16, 16, 18 ); + iret = 1; + break; + case 2: // Point based hull + mins = Vector( 0, 0, 0 ); + maxs = Vector( 0, 0, 0 ); + iret = 1; + break; + } + + return iret; +} + +/* +================================ +HUD_ConnectionlessPacket + + Return 1 if the packet is valid. Set response_buffer_size if you want to send a response packet. Incoming, it holds the max + size of the response_buffer, so you must zero it out if you choose not to respond. +================================ +*/ +int DLLEXPORT HUD_ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ) +{ + // Parse stuff from args + int max_buffer_size = *response_buffer_size; + + // Zero it out since we aren't going to respond. + // If we wanted to response, we'd write data into response_buffer + *response_buffer_size = 0; + + // Since we don't listen for anything here, just respond that it's a bogus message + // If we didn't reject the message, we'd return 1 for success instead. + return 0; +} + +void DLLEXPORT HUD_PlayerMoveInit( struct playermove_s *ppmove ) +{ + PM_Init( ppmove ); +} + +char DLLEXPORT HUD_PlayerMoveTexture( char *name ) +{ + return PM_FindTextureType( name ); +} + +void DLLEXPORT HUD_PlayerMove( struct playermove_s *ppmove, int server ) +{ + PM_Move( ppmove, server ); +} + +int DLLEXPORT Initialize( cl_enginefunc_t *pEnginefuncs, int iVersion ) +{ + gEngfuncs = *pEnginefuncs; + + if (iVersion != CLDLL_INTERFACE_VERSION) + return 0; + + memcpy(&gEngfuncs, pEnginefuncs, sizeof(cl_enginefunc_t)); + + EV_HookEvents(); + + return 1; +} + + +/* +========================== + HUD_VidInit + +Called when the game initializes +and whenever the vid_mode is changed +so the HUD can reinitialize itself. +========================== +*/ + +int DLLEXPORT HUD_VidInit( void ) +{ + gHUD.VidInit(); + + VGui_Startup(); + + return 1; +} + +/* +========================== + HUD_Init + +Called whenever the client connects +to a server. Reinitializes all +the hud variables. +========================== +*/ + +void DLLEXPORT HUD_Init( void ) +{ + InitInput(); + gHUD.Init(); + Scheme_Init(); +} + + +/* +========================== + HUD_Redraw + +called every screen frame to +redraw the HUD. +=========================== +*/ + +int DLLEXPORT HUD_Redraw( float time, int intermission ) +{ + gHUD.Redraw( time, intermission ); + + return 1; +} + + +/* +========================== + HUD_UpdateClientData + +called every time shared client +dll/engine data gets changed, +and gives the cdll a chance +to modify the data. + +returns 1 if anything has been changed, 0 otherwise. +========================== +*/ + +int DLLEXPORT HUD_UpdateClientData(client_data_t *pcldata, float flTime ) +{ + IN_Commands(); + + return gHUD.UpdateClientData(pcldata, flTime ); +} + +/* +========================== + HUD_Reset + +Called at start and end of demos to restore to "non"HUD state. +========================== +*/ + +void DLLEXPORT HUD_Reset( void ) +{ + gHUD.VidInit(); +} + + +/* +========================== +HUD_Frame + +Called by engine every frame that client .dll is loaded +========================== +*/ + +void DLLEXPORT HUD_Frame( double time ) +{ + ServersThink( time ); + + GetClientVoiceMgr()->Frame(time); +} + +/* +========================== +HUD_VoiceStatus + +Called when a player starts or stops talking. +========================== +*/ + +void DLLEXPORT HUD_VoiceStatus(int entindex, qboolean bTalking) +{ + GetClientVoiceMgr()->UpdateSpeakerStatus(entindex, bTalking); +} \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/cl_dll.h b/hlsdk/ricochet/cl_dll/cl_dll.h new file mode 100644 index 00000000..bb03c712 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/cl_dll.h @@ -0,0 +1,39 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// +// cl_dll.h +// + +// 4-23-98 JOHN + +// +// This DLL is linked by the client when they first initialize. +// This DLL is responsible for the following tasks: +// - Loading the HUD graphics upon initialization +// - Drawing the HUD graphics every frame +// - Handling the custum HUD-update packets +// +typedef unsigned char byte; +typedef unsigned short word; +typedef float vec_t; +typedef int (*pfnUserMsgHook)(const char *pszName, int iSize, void *pbuf); + +#include "util_vector.h" +#define EXPORT _declspec( dllexport ) + +#include "../engine/cdll_int.h" +#include "../dlls/cdll_dll.h" + +extern cl_enginefunc_t gEngfuncs; diff --git a/hlsdk/ricochet/cl_dll/cl_util.h b/hlsdk/ricochet/cl_dll/cl_util.h new file mode 100644 index 00000000..153efcf7 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/cl_util.h @@ -0,0 +1,151 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// +// util.h +// + +#include "cvardef.h" + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +// Macros to hook function calls into the HUD object +#define HOOK_MESSAGE(x) gEngfuncs.pfnHookUserMsg(#x, __MsgFunc_##x ); + +#define DECLARE_MESSAGE(y, x) int __MsgFunc_##x(const char *pszName, int iSize, void *pbuf) \ + { \ + return gHUD.##y.MsgFunc_##x(pszName, iSize, pbuf ); \ + } + + +#define HOOK_COMMAND(x, y) gEngfuncs.pfnAddCommand( x, __CmdFunc_##y ); +#define DECLARE_COMMAND(y, x) void __CmdFunc_##x( void ) \ + { \ + gHUD.##y.UserCmd_##x( ); \ + } + +inline float CVAR_GET_FLOAT( const char *x ) { return gEngfuncs.pfnGetCvarFloat( (char*)x ); } +inline char* CVAR_GET_STRING( const char *x ) { return gEngfuncs.pfnGetCvarString( (char*)x ); } +inline struct cvar_s *CVAR_CREATE( const char *cv, const char *val, const int flags ) { return gEngfuncs.pfnRegisterVariable( (char*)cv, (char*)val, flags ); } + +#define SPR_Load (*gEngfuncs.pfnSPR_Load) +#define SPR_Set (*gEngfuncs.pfnSPR_Set) +#define SPR_Frames (*gEngfuncs.pfnSPR_Frames) +#define SPR_GetList (*gEngfuncs.pfnSPR_GetList) + +// SPR_Draw draws a the current sprite as solid +#define SPR_Draw (*gEngfuncs.pfnSPR_Draw) +// SPR_DrawHoles draws the current sprites, with color index255 not drawn (transparent) +#define SPR_DrawHoles (*gEngfuncs.pfnSPR_DrawHoles) +// SPR_DrawAdditive adds the sprites RGB values to the background (additive transulency) +#define SPR_DrawAdditive (*gEngfuncs.pfnSPR_DrawAdditive) + +// SPR_EnableScissor sets a clipping rect for HUD sprites. (0,0) is the top-left hand corner of the screen. +#define SPR_EnableScissor (*gEngfuncs.pfnSPR_EnableScissor) +// SPR_DisableScissor disables the clipping rect +#define SPR_DisableScissor (*gEngfuncs.pfnSPR_DisableScissor) +// +#define FillRGBA (*gEngfuncs.pfnFillRGBA) + + +// ScreenHeight returns the height of the screen, in pixels +#define ScreenHeight (gHUD.m_scrinfo.iHeight) +// ScreenWidth returns the width of the screen, in pixels +#define ScreenWidth (gHUD.m_scrinfo.iWidth) + +#define GetScreenInfo (*gEngfuncs.pfnGetScreenInfo) +#define ServerCmd (*gEngfuncs.pfnServerCmd) +#define ClientCmd (*gEngfuncs.pfnClientCmd) +#define SetCrosshair (*gEngfuncs.pfnSetCrosshair) +#define AngleVectors (*gEngfuncs.pfnAngleVectors) + + +// Gets the height & width of a sprite, at the specified frame +inline int SPR_Height( HSPRITE x, int f ) { return gEngfuncs.pfnSPR_Height(x, f); } +inline int SPR_Width( HSPRITE x, int f ) { return gEngfuncs.pfnSPR_Width(x, f); } + +inline client_textmessage_t *TextMessageGet( const char *pName ) { return gEngfuncs.pfnTextMessageGet( pName ); } +inline int TextMessageDrawChar( int x, int y, int number, int r, int g, int b ) +{ + return gEngfuncs.pfnDrawCharacter( x, y, number, r, g, b ); +} + +inline int DrawConsoleString( int x, int y, const char *string ) +{ + return gEngfuncs.pfnDrawConsoleString( x, y, (char*) string ); +} + +inline void GetConsoleStringSize( const char *string, int *width, int *height ) +{ + gEngfuncs.pfnDrawConsoleStringLen( string, width, height ); +} + +inline int ConsoleStringLen( const char *string ) +{ + int _width, _height; + GetConsoleStringSize( string, &_width, &_height ); + return _width; +} + +inline void ConsolePrint( const char *string ) +{ + gEngfuncs.pfnConsolePrint( string ); +} + +inline void CenterPrint( const char *string ) +{ + gEngfuncs.pfnCenterPrint( string ); +} + +// returns the players name of entity no. +#define GetPlayerInfo (*gEngfuncs.pfnGetPlayerInfo) + +// sound functions +inline void PlaySound( char *szSound, float vol ) { gEngfuncs.pfnPlaySoundByName( szSound, vol ); } +inline void PlaySound( int iSound, float vol ) { gEngfuncs.pfnPlaySoundByIndex( iSound, vol ); } + +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#define fabs(x) ((x) > 0 ? (x) : 0 - (x)) + +void ScaleColors( int &r, int &g, int &b, int a ); + +#define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]) +#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 VectorClear(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); +void VectorScale (const float *in, float scale, float *out); +float VectorNormalize (float *v); +void VectorInverse ( float *v ); + +extern vec3_t vec3_origin; +// disable 'possible loss of data converting float to int' warning message +#pragma warning( disable: 4244 ) +// disable 'truncation from 'const double' to 'float' warning message +#pragma warning( disable: 4305 ) + +inline void UnpackRGB(int &r, int &g, int &b, unsigned long ulRGB)\ +{\ + r = (ulRGB & 0xFF0000) >>16;\ + g = (ulRGB & 0xFF00) >> 8;\ + b = ulRGB & 0xFF;\ +} + +HSPRITE LoadSprite(const char *pszName); diff --git a/hlsdk/ricochet/cl_dll/com_weapons.cpp b/hlsdk/ricochet/cl_dll/com_weapons.cpp new file mode 100644 index 00000000..677ecbe5 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/com_weapons.cpp @@ -0,0 +1,278 @@ +/*** +* +* 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. +* 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. +* +****/ + +// Com_Weapons.cpp +// Shared weapons common/shared functions +#include +#include "hud.h" +#include "cl_util.h" +#include "com_weapons.h" + +#include "const.h" +#include "entity_state.h" +#include "r_efx.h" + +// g_runfuncs is true if this is the first time we've "predicated" a particular movement/firing +// command. If it is 1, then we should play events/sounds etc., otherwise, we just will be +// updating state info, but not firing events +int g_runfuncs = 0; + +// During our weapon prediction processing, we'll need to reference some data that is part of +// the final state passed into the postthink functionality. We'll set this pointer and then +// reset it to NULL as appropriate +struct local_state_s *g_finalstate = NULL; + +/* +==================== +COM_Log + +Log debug messages to file ( appends ) +==================== +*/ +void COM_Log( char *pszFile, char *fmt, ...) +{ + va_list argptr; + char string[1024]; + FILE *fp; + char *pfilename; + + if ( !pszFile ) + { + pfilename = "c:\\hllog.txt"; + } + else + { + pfilename = pszFile; + } + + va_start (argptr,fmt); + vsprintf (string, fmt,argptr); + va_end (argptr); + + fp = fopen( pfilename, "a+t"); + if (fp) + { + fprintf(fp, "%s", string); + fclose(fp); + } +} + +// remember the current animation for the view model, in case we get out of sync with +// server. +static int g_currentanim; + +/* +===================== +HUD_SendWeaponAnim + +Change weapon model animation +===================== +*/ +void HUD_SendWeaponAnim( int iAnim, int body, int force ) +{ + // Don't actually change it. + if ( !g_runfuncs && !force ) + return; + + g_currentanim = iAnim; + + // Tell animation system new info + gEngfuncs.pfnWeaponAnim( iAnim, body ); +} + +/* +===================== +HUD_GetWeaponAnim + +Retrieve current predicted weapon animation +===================== +*/ +int HUD_GetWeaponAnim( void ) +{ + return g_currentanim; +} + +/* +===================== +HUD_PlaySound + +Play a sound, if we are seeing this command for the first time +===================== +*/ +void HUD_PlaySound( char *sound, float volume ) +{ + if ( !g_runfuncs || !g_finalstate ) + return; + + gEngfuncs.pfnPlaySoundByNameAtLocation( sound, volume, (float *)&g_finalstate->playerstate.origin ); +} + +/* +===================== +HUD_PlaybackEvent + +Directly queue up an event on the client +===================== +*/ +void HUD_PlaybackEvent( int flags, const edict_t *pInvoker, unsigned short eventindex, float delay, + float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ) +{ + vec3_t org; + vec3_t ang; + + if ( !g_runfuncs || !g_finalstate ) + return; + + // Weapon prediction events are assumed to occur at the player's origin + org = g_finalstate->playerstate.origin; + ang = v_angles; + gEngfuncs.pfnPlaybackEvent( flags, pInvoker, eventindex, delay, (float *)&org, (float *)&ang, fparam1, fparam2, iparam1, iparam2, bparam1, bparam2 ); +} + +/* +===================== +HUD_SetMaxSpeed + +===================== +*/ +void HUD_SetMaxSpeed( const edict_t *ed, float speed ) +{ +} + + +/* +===================== +UTIL_WeaponTimeBase + +Always 0.0 on client, even if not predicting weapons ( won't get called + in that case ) +===================== +*/ +float UTIL_WeaponTimeBase( void ) +{ + return 0.0; +} + +static unsigned int glSeed = 0; + +unsigned int seed_table[ 256 ] = +{ + 28985, 27138, 26457, 9451, 17764, 10909, 28790, 8716, 6361, 4853, 17798, 21977, 19643, 20662, 10834, 20103, + 27067, 28634, 18623, 25849, 8576, 26234, 23887, 18228, 32587, 4836, 3306, 1811, 3035, 24559, 18399, 315, + 26766, 907, 24102, 12370, 9674, 2972, 10472, 16492, 22683, 11529, 27968, 30406, 13213, 2319, 23620, 16823, + 10013, 23772, 21567, 1251, 19579, 20313, 18241, 30130, 8402, 20807, 27354, 7169, 21211, 17293, 5410, 19223, + 10255, 22480, 27388, 9946, 15628, 24389, 17308, 2370, 9530, 31683, 25927, 23567, 11694, 26397, 32602, 15031, + 18255, 17582, 1422, 28835, 23607, 12597, 20602, 10138, 5212, 1252, 10074, 23166, 19823, 31667, 5902, 24630, + 18948, 14330, 14950, 8939, 23540, 21311, 22428, 22391, 3583, 29004, 30498, 18714, 4278, 2437, 22430, 3439, + 28313, 23161, 25396, 13471, 19324, 15287, 2563, 18901, 13103, 16867, 9714, 14322, 15197, 26889, 19372, 26241, + 31925, 14640, 11497, 8941, 10056, 6451, 28656, 10737, 13874, 17356, 8281, 25937, 1661, 4850, 7448, 12744, + 21826, 5477, 10167, 16705, 26897, 8839, 30947, 27978, 27283, 24685, 32298, 3525, 12398, 28726, 9475, 10208, + 617, 13467, 22287, 2376, 6097, 26312, 2974, 9114, 21787, 28010, 4725, 15387, 3274, 10762, 31695, 17320, + 18324, 12441, 16801, 27376, 22464, 7500, 5666, 18144, 15314, 31914, 31627, 6495, 5226, 31203, 2331, 4668, + 12650, 18275, 351, 7268, 31319, 30119, 7600, 2905, 13826, 11343, 13053, 15583, 30055, 31093, 5067, 761, + 9685, 11070, 21369, 27155, 3663, 26542, 20169, 12161, 15411, 30401, 7580, 31784, 8985, 29367, 20989, 14203, + 29694, 21167, 10337, 1706, 28578, 887, 3373, 19477, 14382, 675, 7033, 15111, 26138, 12252, 30996, 21409, + 25678, 18555, 13256, 23316, 22407, 16727, 991, 9236, 5373, 29402, 6117, 15241, 27715, 19291, 19888, 19847 +}; + +unsigned int U_Random( void ) +{ + glSeed *= 69069; + glSeed += seed_table[ glSeed & 0xff ]; + + return ( ++glSeed & 0x0fffffff ); +} + +void U_Srand( unsigned int seed ) +{ + glSeed = seed_table[ seed & 0xff ]; +} + +/* +===================== +UTIL_SharedRandomLong +===================== +*/ +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ) +{ + + unsigned int range; + + U_Srand( (int)seed + low + high ); + + range = high - low + 1; + if ( !(range - 1) ) + { + return low; + } + else + { + int offset; + int rnum; + + rnum = U_Random(); + + offset = rnum % range; + + return (low + offset); + } +} + +/* +===================== +UTIL_SharedRandomFloat +===================== +*/ +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ) +{ + // + unsigned int range; + + U_Srand( (int)seed + *(int *)&low + *(int *)&high ); + + U_Random(); + U_Random(); + + range = high - low; + if ( !range ) + { + return low; + } + else + { + int tensixrand; + float offset; + + tensixrand = U_Random() & 65535; + + offset = (float)tensixrand / 65536.0; + + return (low + offset * range ); + } +} + +/* +====================== +stub_* + +stub functions for such things as precaching. So we don't have to modify weapons code that + is compiled into both game and client .dlls. +====================== +*/ +int stub_PrecacheModel ( char* s ) { return 0; } +int stub_PrecacheSound ( char* s ) { return 0; } +unsigned short stub_PrecacheEvent ( int type, const char *s ) { return 0; } +const char *stub_NameForFunction ( uint32 function ) { return "func"; } +void stub_SetModel ( edict_t *e, const char *m ) {} diff --git a/hlsdk/ricochet/cl_dll/com_weapons.h b/hlsdk/ricochet/cl_dll/com_weapons.h new file mode 100644 index 00000000..61b7190f --- /dev/null +++ b/hlsdk/ricochet/cl_dll/com_weapons.h @@ -0,0 +1,41 @@ +// com_weapons.h +// Shared weapons common function prototypes +#if !defined( COM_WEAPONSH ) +#define COM_WEAPONSH +#ifdef _WIN32 +#pragma once +#endif + +#include "hud_iface.h" + +extern "C" +{ + 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 ); +} + +void COM_Log( char *pszFile, char *fmt, ...); +int CL_IsDead( void ); + +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ); +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ); + +int HUD_GetWeaponAnim( void ); +void HUD_SendWeaponAnim( int iAnim, int body, int force ); +void HUD_PlaySound( char *sound, float volume ); +void HUD_PlaybackEvent( int flags, const struct edict_s *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); +void HUD_SetMaxSpeed( const struct edict_s *ed, float speed ); +int stub_PrecacheModel( char* s ); +int stub_PrecacheSound( char* s ); +unsigned short stub_PrecacheEvent( int type, const char *s ); +const char *stub_NameForFunction ( uint32 function ); +void stub_SetModel ( struct edict_s *e, const char *m ); + + +extern cvar_t *cl_lw; + +extern int g_runfuncs; +extern vec3_t v_angles; +extern float g_lastFOV; +extern struct local_state_s *g_finalstate; + +#endif \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/death.cpp b/hlsdk/ricochet/cl_dll/death.cpp new file mode 100644 index 00000000..f9457269 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/death.cpp @@ -0,0 +1,307 @@ +/*** +* +* 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. +* 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. +* +****/ +// +// death notice +// +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "vgui_TeamFortressViewport.h" + +DECLARE_MESSAGE( m_DeathNotice, DeathMsg ); + +struct DeathNoticeItem { + char szKiller[MAX_PLAYER_NAME_LENGTH*2]; + char szVictim[MAX_PLAYER_NAME_LENGTH*2]; + int iId; // the index number of the associated sprite + int iSuicide; + int iTeamKill; + int iNonPlayerKill; + float flDisplayTime; + float *KillerColor; + float *VictimColor; +}; + +#define MAX_DEATHNOTICES 4 +static int DEATHNOTICE_DISPLAY_TIME = 6; + +#define DEATHNOTICE_TOP 20 + +DeathNoticeItem rgDeathNoticeList[ MAX_DEATHNOTICES + 1 ]; + +float g_ColorBlue[3] = { 0.6, 0.8, 1.0 }; +float g_ColorRed[3] = { 1.0, 0.6, 0.3 }; +float g_ColorGreen[3] = { 0.6, 1.0, 0.6 }; +float g_ColorYellow[3] = { 1.0, 0.7, 0.0 }; + +float *GetClientColor( int clientIndex ) +{ + const char *teamName = g_PlayerExtraInfo[clientIndex].teamname; + + if ( !teamName || *teamName == 0 ) + return NULL; + + if ( !stricmp( "blue", teamName ) ) + return g_ColorBlue; + else if ( !stricmp( "red", teamName ) ) + return g_ColorRed; + else if ( !stricmp( "green", teamName ) ) + return g_ColorGreen; + else if ( !stricmp( "yellow", teamName ) ) + return g_ColorYellow; + + return NULL; +} + + +int CHudDeathNotice :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( DeathMsg ); + + CVAR_CREATE( "hud_deathnotice_time", "6", 0 ); + + return 1; +} + + +void CHudDeathNotice :: InitHUDData( void ) +{ + memset( rgDeathNoticeList, 0, sizeof(rgDeathNoticeList) ); +} + + +int CHudDeathNotice :: VidInit( void ) +{ + m_HUD_d_skull = gHUD.GetSpriteIndex( "d_skull" ); + + return 1; +} + +int CHudDeathNotice :: Draw( float flTime ) +{ + int x, y, r, g, b; + + for ( int i = 0; i < MAX_DEATHNOTICES; i++ ) + { + if ( rgDeathNoticeList[i].iId == 0 ) + break; // we've gone through them all + + if ( rgDeathNoticeList[i].flDisplayTime < flTime ) + { // display time has expired + // remove the current item from the list + memmove( &rgDeathNoticeList[i], &rgDeathNoticeList[i+1], sizeof(DeathNoticeItem) * (MAX_DEATHNOTICES - i) ); + i--; // continue on the next item; stop the counter getting incremented + continue; + } + + rgDeathNoticeList[i].flDisplayTime = min( rgDeathNoticeList[i].flDisplayTime, gHUD.m_flTime + DEATHNOTICE_DISPLAY_TIME ); + + // Draw the death notice + + // Make it a bigger increment in 640, 'cos the Discwar death sprites are 32 tall, not 16 in 640 + if ( ScreenHeight >= 640 ) + y = DEATHNOTICE_TOP + (36 * i); //!!! + else + y = DEATHNOTICE_TOP + (20 * i); //!!! + + 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); + + if ( !rgDeathNoticeList[i].iSuicide ) + { + x -= (5 + ConsoleStringLen( rgDeathNoticeList[i].szKiller ) ); + + // Draw killers name + if ( rgDeathNoticeList[i].KillerColor ) + gEngfuncs.pfnDrawSetTextColor( rgDeathNoticeList[i].KillerColor[0], rgDeathNoticeList[i].KillerColor[1], rgDeathNoticeList[i].KillerColor[2] ); + x = 5 + DrawConsoleString( x, y, rgDeathNoticeList[i].szKiller ); + } + + r = 255; g = 80; b = 0; + 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) ); + + x += (gHUD.GetSpriteRect(id).right - gHUD.GetSpriteRect(id).left); + + // Draw victims name (if it was a player that was killed) + if (rgDeathNoticeList[i].iNonPlayerKill == FALSE) + { + if ( rgDeathNoticeList[i].VictimColor ) + gEngfuncs.pfnDrawSetTextColor( rgDeathNoticeList[i].VictimColor[0], rgDeathNoticeList[i].VictimColor[1], rgDeathNoticeList[i].VictimColor[2] ); + x = DrawConsoleString( x, y, rgDeathNoticeList[i].szVictim ); + } + } + + return 1; +} + +// This message handler may be better off elsewhere +int CHudDeathNotice :: MsgFunc_DeathMsg( const char *pszName, int iSize, void *pbuf ) +{ + m_iFlags |= HUD_ACTIVE; + + BEGIN_READ( pbuf, iSize ); + + int killer = READ_BYTE(); + int victim = READ_BYTE(); + + char killedwith[32]; + strcpy( killedwith, "d_" ); + strncat( killedwith, READ_STRING(), 32 ); + + if (gViewPort) + gViewPort->DeathMsg( killer, victim ); + + int i; + for ( i = 0; i < MAX_DEATHNOTICES; i++ ) + { + if ( rgDeathNoticeList[i].iId == 0 ) + break; + } + if ( i == MAX_DEATHNOTICES ) + { // move the rest of the list forward to make room for this item + memmove( rgDeathNoticeList, rgDeathNoticeList+1, sizeof(DeathNoticeItem) * MAX_DEATHNOTICES ); + i = MAX_DEATHNOTICES - 1; + } + + if (gViewPort) + gViewPort->GetAllPlayersInfo(); + + // Get the Killer's name + char *killer_name = g_PlayerInfoList[ killer ].name; + if ( !killer_name ) + { + killer_name = ""; + rgDeathNoticeList[i].szKiller[0] = 0; + } + else + { + rgDeathNoticeList[i].KillerColor = GetClientColor( killer ); + strncpy( rgDeathNoticeList[i].szKiller, killer_name, MAX_PLAYER_NAME_LENGTH ); + rgDeathNoticeList[i].szKiller[MAX_PLAYER_NAME_LENGTH-1] = 0; + } + + // Get the Victim's name + char *victim_name = NULL; + // If victim is -1, the killer killed a specific, non-player object (like a sentrygun) + if ( ((char)victim) != -1 ) + victim_name = g_PlayerInfoList[ victim ].name; + if ( !victim_name ) + { + victim_name = ""; + rgDeathNoticeList[i].szVictim[0] = 0; + } + else + { + rgDeathNoticeList[i].VictimColor = GetClientColor( victim ); + strncpy( rgDeathNoticeList[i].szVictim, victim_name, MAX_PLAYER_NAME_LENGTH ); + rgDeathNoticeList[i].szVictim[MAX_PLAYER_NAME_LENGTH-1] = 0; + } + + // Is it a non-player object kill? + if ( ((char)victim) == -1 ) + { + rgDeathNoticeList[i].iNonPlayerKill = TRUE; + + // Store the object's name in the Victim slot (skip the d_ bit) + strcpy( rgDeathNoticeList[i].szVictim, killedwith+2 ); + } + else + { + if ( killer == victim || killer == 0 ) + rgDeathNoticeList[i].iSuicide = TRUE; + + if ( !strcmp( killedwith, "d_teammate" ) ) + rgDeathNoticeList[i].iTeamKill = TRUE; + } + + // Find the sprite in the list + int spr = gHUD.GetSpriteIndex( killedwith ); + + rgDeathNoticeList[i].iId = spr; + + DEATHNOTICE_DISPLAY_TIME = CVAR_GET_FLOAT( "hud_deathnotice_time" ); + rgDeathNoticeList[i].flDisplayTime = gHUD.m_flTime + DEATHNOTICE_DISPLAY_TIME; + + if (rgDeathNoticeList[i].iNonPlayerKill) + { + ConsolePrint( rgDeathNoticeList[i].szKiller ); + ConsolePrint( " killed a " ); + ConsolePrint( rgDeathNoticeList[i].szVictim ); + ConsolePrint( "\n" ); + } + 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" ); + } + + return 1; +} + + + + diff --git a/hlsdk/ricochet/cl_dll/demo.cpp b/hlsdk/ricochet/cl_dll/demo.cpp new file mode 100644 index 00000000..e6256291 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/demo.cpp @@ -0,0 +1,70 @@ +/*** +* +* Copyright (c) 1999, 2000 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 + +#define DLLEXPORT __declspec( dllexport ) + +extern "C" +{ + void DLLEXPORT Demo_ReadBuffer( int size, unsigned char *buffer ); +} + +/* +===================== +Demo_WriteBuffer + +Write some data to the demo stream +===================== +*/ +void Demo_WriteBuffer( int type, int size, unsigned char *buffer ) +{ + int pos = 0; + unsigned char buf[ 32 * 1024 ]; + *( int * )&buf[pos] = type; + pos+=sizeof( int ); + + memcpy( &buf[pos], buffer, size ); + + // Write full buffer out + gEngfuncs.pDemoAPI->WriteBuffer( size + sizeof( int ), buf ); +} + +/* +===================== +Demo_ReadBuffer + +Engine wants us to parse some data from the demo stream +===================== +*/ +void DLLEXPORT Demo_ReadBuffer( int size, unsigned char *buffer ) +{ + int type; + int i = 0; + + type = *( int * )buffer; + i += sizeof( int ); + switch ( type ) + { + case TYPE_USER: + break; + default: + gEngfuncs.Con_DPrintf( "Unknown demo buffer type, skipping.\n" ); + break; + } +} \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/demo.h b/hlsdk/ricochet/cl_dll/demo.h new file mode 100644 index 00000000..f538e824 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/demo.h @@ -0,0 +1,27 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +#if !defined( DEMOH ) +#define DEMOH +#pragma once + +// Types of demo messages we can write/parse +enum +{ + TYPE_USER = 0, +}; + +void Demo_WriteBuffer( int type, int size, unsigned char *buffer ); + +#endif \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/entity.cpp b/hlsdk/ricochet/cl_dll/entity.cpp new file mode 100644 index 00000000..58eb2b33 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/entity.cpp @@ -0,0 +1,643 @@ +/*** +* +* Copyright (c) 1999, 2000, 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. +* +****/ +// Client side entity management functions +#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 + +#define DLLEXPORT __declspec( dllexport ) + +extern vec3_t v_origin; +extern int iPrevRenderState; +extern int iRenderStateChanged; + +void Game_AddObjects( void ); + +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 ); + void DLLEXPORT HUD_TempEntUpdate( double frametime, double client_time, double cl_gravity, struct tempent_s **ppTempEntFree, struct tempent_s **ppTempEntActive, int ( *Callback_AddVisibleEntity )( struct cl_entity_s *pEntity ), void ( *Callback_TempEntPlaySound )( struct tempent_s *pTemp, float damp ) ); + struct cl_entity_s DLLEXPORT *HUD_GetUserEntity( int index ); +} + +/* +======================== +HUD_AddEntity + Return 0 to filter entity from visible list for rendering +======================== +*/ +int DLLEXPORT HUD_AddEntity( int type, struct cl_entity_s *ent, const char *modelname ) +{ + switch ( type ) + { + case ET_NORMAL: + case ET_PLAYER: + case ET_BEAM: + case ET_TEMPENTITY: + case ET_FRAGMENTED: + default: + break; + } + + return 1; +} + +/* +========================= +HUD_TxferLocalOverrides + +The server sends us our origin with extra precision as part of the clientdata structure, not during the normal +playerstate update in entity_state_t. In order for these overrides to eventually get to the appropriate playerstate +structure, we need to copy them into the state structure at this point. +========================= +*/ +void DLLEXPORT HUD_TxferLocalOverrides( struct entity_state_s *state, const struct clientdata_s *client ) +{ + VectorCopy( client->origin, state->origin ); + + // Spectator + state->iuser1 = client->iuser1; + state->iuser2 = client->iuser2; +} + +/* +========================= +HUD_ProcessPlayerState + +We have received entity_state_t for this player over the network. We need to copy appropriate fields to the +playerstate structure +========================= +*/ +void DLLEXPORT HUD_ProcessPlayerState( struct entity_state_s *dst, const struct entity_state_s *src ) +{ + cl_entity_t *player = gEngfuncs.GetLocalPlayer(); // Get the local player's index + + // Copy in network data + VectorCopy( src->origin, dst->origin ); + VectorCopy( src->angles, dst->angles ); + + VectorCopy( src->velocity, dst->velocity ); + + dst->frame = src->frame; + dst->modelindex = src->modelindex; + dst->skin = src->skin; + dst->effects = src->effects; + dst->weaponmodel = src->weaponmodel; + dst->movetype = src->movetype; + dst->sequence = src->sequence; + dst->animtime = src->animtime; + + dst->solid = src->solid; + + dst->rendermode = src->rendermode; + dst->renderamt = src->renderamt; + dst->rendercolor.r = src->rendercolor.r; + dst->rendercolor.g = src->rendercolor.g; + dst->rendercolor.b = src->rendercolor.b; + dst->renderfx = src->renderfx; + + // Hack to find out when our render state changes. + // Needed because we need the previous render state when we flip to thirdperson + if ( dst->number == player->index ) + { + if ( iPrevRenderState != dst->renderfx ) + iRenderStateChanged = TRUE; + iPrevRenderState = dst->renderfx; + } + + dst->framerate = src->framerate; + dst->body = src->body; + + memcpy( &dst->controller[0], &src->controller[0], 4 * sizeof( byte ) ); + memcpy( &dst->blending[0], &src->blending[0], 2 * sizeof( byte ) ); + + VectorCopy( src->basevelocity, dst->basevelocity ); + + dst->friction = src->friction; + dst->gravity = src->gravity; + dst->gaitsequence = src->gaitsequence; + dst->spectator = src->spectator; + dst->usehull = src->usehull; + dst->playerclass = src->playerclass; + dst->team = src->team; + dst->colormap = src->colormap; + + // Save off some data so other areas of the Client DLL can get to it + if ( dst->number == player->index ) + { + g_iPlayerClass = dst->playerclass; + g_iTeamNumber = dst->team; + g_iUser1 = src->iuser1; + g_iUser2 = src->iuser2; + } +} + +/* +========================= +HUD_TxferPredictionData + +Because we can predict an arbitrary number of frames before the server responds with an update, we need to be able to copy client side prediction data in + from the state that the server ack'd receiving, which can be anywhere along the predicted frame path ( i.e., we could predict 20 frames into the future and the server ack's + up through 10 of those frames, so we need to copy persistent client-side only state from the 10th predicted frame to the slot the server + update is occupying. +========================= +*/ +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 ) +{ + ps->oldbuttons = pps->oldbuttons; + ps->flFallVelocity = pps->flFallVelocity; + ps->iStepLeft = pps->iStepLeft; + ps->playerclass = pps->playerclass; + + ps->sequence = pps->sequence; + ps->gaitsequence = pps->gaitsequence; + + pcd->viewmodel = ppcd->viewmodel; + pcd->m_iId = ppcd->m_iId; + pcd->ammo_shells = ppcd->ammo_shells; + pcd->ammo_nails = ppcd->ammo_nails; + pcd->ammo_cells = ppcd->ammo_cells; + pcd->ammo_rockets = ppcd->ammo_rockets; + pcd->m_flNextAttack = ppcd->m_flNextAttack; + pcd->fov = ppcd->fov; + pcd->weaponanim = ppcd->weaponanim; + pcd->tfstate = ppcd->tfstate; + pcd->maxspeed = ppcd->maxspeed; + + pcd->deadflag = ppcd->deadflag; + + // Spectator + pcd->iuser1 = ppcd->iuser1; + pcd->iuser2 = ppcd->iuser2; + + pcd->fuser1 = ppcd->fuser1; + pcd->fuser2 = ppcd->fuser2; + pcd->fuser3 = ppcd->fuser3; + + memcpy( wd, pwd, 32 * sizeof( weapon_data_t ) ); +} + +/* +========================= +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(); + + 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 + +Simulation and cleanup of temporary entities +================= +*/ +void DLLEXPORT HUD_TempEntUpdate ( + double frametime, // Simulation time + double client_time, // Absolute time on client + double cl_gravity, // True gravity on client + TEMPENTITY **ppTempEntFree, // List of freed temporary ents + TEMPENTITY **ppTempEntActive, // List + int ( *Callback_AddVisibleEntity )( cl_entity_t *pEntity ), + void ( *Callback_TempEntPlaySound )( TEMPENTITY *pTemp, float damp ) ) +{ + static int gTempEntFrame = 0; + int i; + TEMPENTITY *pTemp, *pnext, *pprev; + float freq, gravity, gravitySlow, life, fastFreq; + + // Nothing to simulate + if ( !*ppTempEntActive ) + return; + + // in order to have tents collide with players, we have to run the player prediction code so + // that the client has the player list. We run this code once when we detect any COLLIDEALL + // tent, then set this BOOL to true so the code doesn't get run again if there's more than + // one COLLIDEALL ent for this update. (often are). + 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 ( -1 ); + + // !!!BUGBUG -- This needs to be time based + gTempEntFrame = (gTempEntFrame+1) & 31; + + pTemp = *ppTempEntActive; + + // !!! Don't simulate while paused.... This is sort of a hack, revisit. + if ( frametime <= 0 ) + { + while ( pTemp ) + { + if ( !(pTemp->flags & FTENT_NOMODEL ) ) + { + Callback_AddVisibleEntity( &pTemp->entity ); + } + pTemp = pTemp->next; + } + goto finish; + } + + pprev = NULL; + freq = client_time * 0.01; + fastFreq = client_time * 5.5; + gravity = -frametime * cl_gravity; + gravitySlow = gravity * 0.5; + + while ( pTemp ) + { + int active; + + active = 1; + + life = pTemp->die - client_time; + pnext = pTemp->next; + if ( life < 0 ) + { + if ( pTemp->flags & FTENT_FADEOUT ) + { + if (pTemp->entity.curstate.rendermode == kRenderNormal) + pTemp->entity.curstate.rendermode = kRenderTransTexture; + pTemp->entity.curstate.renderamt = pTemp->entity.baseline.renderamt * ( 1 + life * pTemp->fadeSpeed ); + if ( pTemp->entity.curstate.renderamt <= 0 ) + active = 0; + + } + else + active = 0; + } + if ( !active ) // Kill it + { + pTemp->next = *ppTempEntFree; + *ppTempEntFree = pTemp; + if ( !pprev ) // Deleting at head of list + *ppTempEntActive = pnext; + else + pprev->next = pnext; + } + else + { + pprev = pTemp; + + VectorCopy( pTemp->entity.origin, pTemp->entity.prevstate.origin ); + + if ( pTemp->flags & FTENT_SPARKSHOWER ) + { + // Adjust speed if it's time + // Scale is next think time + if ( client_time > pTemp->entity.baseline.scale ) + { + // Show Sparks + gEngfuncs.pEfxAPI->R_SparkEffect( pTemp->entity.origin, 8, -200, 200 ); + + // Reduce life + pTemp->entity.baseline.framerate -= 0.1; + + if ( pTemp->entity.baseline.framerate <= 0.0 ) + { + pTemp->die = client_time; + } + else + { + // So it will die no matter what + pTemp->die = client_time + 0.5; + + // Next think + pTemp->entity.baseline.scale = client_time + 0.1; + } + } + } + else if ( pTemp->flags & FTENT_PLYRATTACHMENT ) + { + cl_entity_t *pClient; + + pClient = gEngfuncs.GetEntityByIndex( pTemp->clientIndex ); + + VectorAdd( pClient->origin, pTemp->tentOffset, pTemp->entity.origin ); + } + else if ( pTemp->flags & FTENT_SINEWAVE ) + { + pTemp->x += pTemp->entity.baseline.origin[0] * frametime; + pTemp->y += pTemp->entity.baseline.origin[1] * frametime; + + pTemp->entity.origin[0] = pTemp->x + sin( pTemp->entity.baseline.origin[2] + client_time * pTemp->entity.prevstate.frame ) * (10*pTemp->entity.curstate.framerate); + pTemp->entity.origin[1] = pTemp->y + sin( pTemp->entity.baseline.origin[2] + fastFreq + 0.7 ) * (8*pTemp->entity.curstate.framerate); + pTemp->entity.origin[2] += pTemp->entity.baseline.origin[2] * frametime; + } + else if ( pTemp->flags & FTENT_SPIRAL ) + { + float s, c; + s = sin( pTemp->entity.baseline.origin[2] + fastFreq ); + c = cos( pTemp->entity.baseline.origin[2] + fastFreq ); + + pTemp->entity.origin[0] += pTemp->entity.baseline.origin[0] * frametime + 8 * sin( client_time * 20 + (int)pTemp ); + 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++ ) + pTemp->entity.origin[i] += pTemp->entity.baseline.origin[i] * frametime; + } + + if ( pTemp->flags & FTENT_SPRANIMATE ) + { + pTemp->entity.curstate.frame += frametime * pTemp->entity.curstate.framerate; + if ( pTemp->entity.curstate.frame >= pTemp->frameMax ) + { + pTemp->entity.curstate.frame = pTemp->entity.curstate.frame - (int)(pTemp->entity.curstate.frame); + + if ( !(pTemp->flags & FTENT_SPRANIMATELOOP) ) + { + // this animating sprite isn't set to loop, so destroy it. + pTemp->die = client_time; + pTemp = pnext; + continue; + } + } + } + else if ( pTemp->flags & FTENT_SPRCYCLE ) + { + pTemp->entity.curstate.frame += frametime * 10; + if ( pTemp->entity.curstate.frame >= pTemp->frameMax ) + { + pTemp->entity.curstate.frame = pTemp->entity.curstate.frame - (int)(pTemp->entity.curstate.frame); + } + } +// Experiment +#if 0 + if ( pTemp->flags & FTENT_SCALE ) + pTemp->entity.curstate.framerate += 20.0 * (frametime / pTemp->entity.curstate.framerate); +#endif + + if ( pTemp->flags & FTENT_ROTATE ) + { + pTemp->entity.angles[0] += pTemp->entity.baseline.angles[0] * frametime; + pTemp->entity.angles[1] += pTemp->entity.baseline.angles[1] * frametime; + pTemp->entity.angles[2] += pTemp->entity.baseline.angles[2] * frametime; + + VectorCopy( pTemp->entity.angles, pTemp->entity.latched.prevangles ); + } + + if ( pTemp->flags & (FTENT_COLLIDEALL | FTENT_COLLIDEWORLD) ) + { + vec3_t traceNormal; + float traceFraction = 1; + + if ( pTemp->flags & FTENT_COLLIDEALL ) + { + pmtrace_t pmtrace; + physent_t *pe; + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + + gEngfuncs.pEventAPI->EV_PlayerTrace( pTemp->entity.prevstate.origin, pTemp->entity.origin, PM_STUDIO_BOX, -1, &pmtrace ); + + + if ( pmtrace.fraction != 1 ) + { + pe = gEngfuncs.pEventAPI->EV_GetPhysent( pmtrace.ent ); + + if ( !pmtrace.ent || ( pe->info != pTemp->clientIndex ) ) + { + traceFraction = pmtrace.fraction; + VectorCopy( pmtrace.plane.normal, traceNormal ); + + if ( pTemp->hitcallback ) + { + (*pTemp->hitcallback)( pTemp, &pmtrace ); + } + } + } + } + else if ( pTemp->flags & FTENT_COLLIDEWORLD ) + { + pmtrace_t pmtrace; + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + + gEngfuncs.pEventAPI->EV_PlayerTrace( pTemp->entity.prevstate.origin, pTemp->entity.origin, PM_STUDIO_BOX | PM_WORLD_ONLY, -1, &pmtrace ); + + if ( pmtrace.fraction != 1 ) + { + traceFraction = pmtrace.fraction; + VectorCopy( pmtrace.plane.normal, traceNormal ); + + if ( pTemp->flags & FTENT_SPARKSHOWER ) + { + // Chop spark speeds a bit more + // + VectorScale( pTemp->entity.baseline.origin, 0.6, pTemp->entity.baseline.origin ); + + if ( Length( pTemp->entity.baseline.origin ) < 10 ) + { + pTemp->entity.baseline.framerate = 0.0; + } + } + + if ( pTemp->hitcallback ) + { + (*pTemp->hitcallback)( pTemp, &pmtrace ); + } + } + } + + if ( traceFraction != 1 ) // Decent collision now, and damping works + { + float proj, damp; + + // Place at contact point + VectorMA( pTemp->entity.prevstate.origin, traceFraction*frametime, pTemp->entity.baseline.origin, pTemp->entity.origin ); + // Damp velocity + damp = pTemp->bounceFactor; + if ( pTemp->flags & (FTENT_GRAVITY|FTENT_SLOWGRAVITY) ) + { + damp *= 0.5; + if ( traceNormal[2] > 0.9 ) // Hit floor? + { + if ( pTemp->entity.baseline.origin[2] <= 0 && pTemp->entity.baseline.origin[2] >= gravity*3 ) + { + damp = 0; // Stop + pTemp->flags &= ~(FTENT_ROTATE|FTENT_GRAVITY|FTENT_SLOWGRAVITY|FTENT_COLLIDEWORLD|FTENT_SMOKETRAIL); + pTemp->entity.angles[0] = 0; + pTemp->entity.angles[2] = 0; + } + } + } + + if (pTemp->hitSound) + { + Callback_TempEntPlaySound(pTemp, damp); + } + + if (pTemp->flags & FTENT_COLLIDEKILL) + { + // die on impact + pTemp->flags &= ~FTENT_FADEOUT; + pTemp->die = client_time; + } + else + { + // Reflect velocity + if ( damp != 0 ) + { + proj = DotProduct( pTemp->entity.baseline.origin, traceNormal ); + VectorMA( pTemp->entity.baseline.origin, -proj*2, traceNormal, pTemp->entity.baseline.origin ); + // Reflect rotation (fake) + + pTemp->entity.angles[1] = -pTemp->entity.angles[1]; + } + + if ( damp != 1 ) + { + + VectorScale( pTemp->entity.baseline.origin, damp, pTemp->entity.baseline.origin ); + VectorScale( pTemp->entity.angles, 0.9, pTemp->entity.angles ); + } + } + } + } + + + if ( (pTemp->flags & FTENT_FLICKER) && gTempEntFrame == pTemp->entity.curstate.effects ) + { + dlight_t *dl = gEngfuncs.pEfxAPI->CL_AllocDlight (0); + VectorCopy (pTemp->entity.origin, dl->origin); + dl->radius = 60; + dl->color.r = 255; + dl->color.g = 120; + dl->color.b = 0; + dl->die = client_time + 0.01; + } + + if ( pTemp->flags & FTENT_SMOKETRAIL ) + { + gEngfuncs.pEfxAPI->R_RocketTrail (pTemp->entity.prevstate.origin, pTemp->entity.origin, 1); + } + + 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 ) + { + if ( pTemp->callback ) + { + ( *pTemp->callback )( pTemp, frametime, client_time ); + } + } + + // Cull to PVS (not frustum cull, just PVS) + if ( !(pTemp->flags & FTENT_NOMODEL ) ) + { + if ( !Callback_AddVisibleEntity( &pTemp->entity ) ) + { + if ( !(pTemp->flags & FTENT_PERSIST) ) + { + pTemp->die = client_time; // If we can't draw it this frame, just dump it. + pTemp->flags &= ~FTENT_FADEOUT; // Don't fade out, just die + } + } + } + } + pTemp = pnext; + } + +finish: + // Restore state info + gEngfuncs.pEventAPI->EV_PopPMStates(); +} + +/* +================= +HUD_GetUserEntity + +If you specify negative numbers for beam start and end point entities, then + the engine will call back into this function requesting a pointer to a cl_entity_t + object that describes the entity to attach the beam onto. + +Indices must start at 1, not zero. +================= +*/ +cl_entity_t DLLEXPORT *HUD_GetUserEntity( int index ) +{ +return NULL; +} diff --git a/hlsdk/ricochet/cl_dll/ev_common.cpp b/hlsdk/ricochet/cl_dll/ev_common.cpp new file mode 100644 index 00000000..2d2e37ee --- /dev/null +++ b/hlsdk/ricochet/cl_dll/ev_common.cpp @@ -0,0 +1,198 @@ +/*** +* +* 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. +* 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. +* +****/ +// shared event functions +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" + +#include "r_efx.h" + +#include "eventscripts.h" +#include "event_api.h" + +/* +================= +GetEntity + +Return's the requested cl_entity_t +================= +*/ +struct cl_entity_s *GetEntity( int idx ) +{ + return gEngfuncs.GetEntityByIndex( idx ); +} + +/* +================= +GetViewEntity + +Return's the current weapon/view model +================= +*/ +struct cl_entity_s *GetViewEntity( void ) +{ + return gEngfuncs.GetViewModel(); +} + +/* +================= +EV_CreateTracer + +Creates a tracer effect +================= +*/ +void EV_CreateTracer( float *start, float *end ) +{ + gEngfuncs.pEfxAPI->R_TracerEffect( start, end ); +} + +/* +================= +EV_IsPlayer + +Is the entity's index in the player range? +================= +*/ +qboolean EV_IsPlayer( int idx ) +{ + if ( idx >= 1 && idx <= gEngfuncs.GetMaxClients() ) + return true; + + return false; +} + +/* +================= +EV_IsLocal + +Is the entity == the local player +================= +*/ +qboolean EV_IsLocal( int idx ) +{ + return gEngfuncs.pEventAPI->EV_IsLocal( idx - 1 ) ? true : false; +} + +/* +================= +EV_GetGunPosition + +Figure out the height of the gun +================= +*/ +void EV_GetGunPosition( event_args_t *args, float *pos, float *origin ) +{ + int idx; + vec3_t view_ofs; + + idx = args->entindex; + + VectorClear( view_ofs ); + view_ofs[2] = DEFAULT_VIEWHEIGHT; + + if ( EV_IsPlayer( idx ) ) + { + if ( EV_IsLocal( idx ) ) + { + // Grab predicted result for local player + gEngfuncs.pEventAPI->EV_LocalPlayerViewheight( view_ofs ); + } + else if ( args->ducking == 1 ) + { + view_ofs[2] = VEC_DUCK_VIEW; + } + } + + VectorAdd( origin, view_ofs, pos ); +} + +/* +================= +EV_EjectBrass + +Bullet shell casings +================= +*/ +void EV_EjectBrass( float *origin, float *velocity, float rotation, int model, int soundtype ) +{ + vec3_t endpos; + VectorClear( endpos ); + endpos[1] = rotation; + gEngfuncs.pEfxAPI->R_TempModel( origin, velocity, endpos, 2.5, model, soundtype ); +} + +/* +================= +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 ) +{ + int i; + vec3_t view_ofs; + float fR, fU; + + int idx; + + idx = args->entindex; + + VectorClear( view_ofs ); + view_ofs[2] = DEFAULT_VIEWHEIGHT; + + if ( EV_IsPlayer( idx ) ) + { + if ( EV_IsLocal( idx ) ) + { + gEngfuncs.pEventAPI->EV_LocalPlayerViewheight( view_ofs ); + } + else if ( args->ducking == 1 ) + { + view_ofs[2] = VEC_DUCK_VIEW; + } + } + + fR = gEngfuncs.pfnRandomFloat( 50, 70 ); + fU = gEngfuncs.pfnRandomFloat( 100, 150 ); + + 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; + } +} + +/* +================= +EV_MuzzleFlash + +Flag weapon/view model for muzzle flash +================= +*/ +void EV_MuzzleFlash( void ) +{ + // Add muzzle flash to current weapon model + cl_entity_t *ent = GetViewEntity(); + if ( !ent ) + { + return; + } + + // Or in the muzzle flash + ent->curstate.effects |= EF_MUZZLEFLASH; +} \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/ev_hldm.cpp b/hlsdk/ricochet/cl_dll/ev_hldm.cpp new file mode 100644 index 00000000..11dbf668 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/ev_hldm.cpp @@ -0,0 +1,150 @@ +/*** +* +* 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. +* 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_hldm.h" + +#include "r_efx.h" +#include "event_api.h" +#include "event_args.h" +#include "in_defs.h" + +#include + +extern "C" +{ +// RICOCHET +void EV_FireDisc( struct event_args_s *args ); +void EV_TriggerJump( struct event_args_s *args ); +void EV_TrainPitchAdjust( struct event_args_s *args ); +} + +/* +============================== +EV_TriggerJump + +Plays the jump pad sound +============================== +*/ +void EV_TriggerJump( event_args_t *args ) +{ + int idx; + idx = args->entindex; + vec3_t origin; + + VectorCopy( args->origin, origin ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_AUTO, "triggerjump.wav", 1.0, ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); +} + +/* +============================== +EV_FireDisc + +Play's disc firing animation and play's appropriate sound effect +============================== +*/ +void EV_FireDisc( event_args_t *args ) +{ + int idx; + + idx = args->entindex; + vec3_t origin; + int decap; + + VectorCopy( args->origin, origin ); + decap = args->bparam1 ? 1 : 0; + + if ( EV_IsLocal( idx ) ) + { + // Add muzzle flash to current weapon model + gEngfuncs.pEventAPI->EV_WeaponAnimation( DISC_THROW1, 2 ); + } + + if ( decap ) + { + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/altfire.wav", 0.8, ATTN_NORM, 0, 100 ); + } + else + { + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/cbar_miss1.wav", 0.8, ATTN_NORM, 0, 100 ); + } +} + +#define SND_CHANGE_PITCH (1<<7) + +/* +============================== +EV_TrainPitchAdjust + +Do we support trains in Ricochet? +============================== +*/ +void EV_TrainPitchAdjust( event_args_t *args ) +{ + int idx; + vec3_t origin; + + unsigned short us_params; + int noise; + float m_flVolume; + int pitch; + int stop; + + char sz[ 256 ]; + + idx = args->entindex; + + VectorCopy( args->origin, origin ); + + us_params = (unsigned short)args->iparam1; + stop = args->bparam1; + + m_flVolume = (float)(us_params & 0x003f)/40.0; + noise = (int)(((us_params) >> 12 ) & 0x0007); + pitch = (int)( 10.0 * (float)( ( us_params >> 6 ) & 0x003f ) ); + + switch ( noise ) + { + case 1: strcpy( sz, "plats/ttrain1.wav"); break; + case 2: strcpy( sz, "plats/ttrain2.wav"); break; + case 3: strcpy( sz, "plats/ttrain3.wav"); break; + case 4: strcpy( sz, "plats/ttrain4.wav"); break; + case 5: strcpy( sz, "plats/ttrain6.wav"); break; + case 6: strcpy( sz, "plats/ttrain7.wav"); break; + default: + // no sound + strcpy( sz, "" ); + return; + } + + if ( stop ) + { + gEngfuncs.pEventAPI->EV_StopSound( idx, CHAN_STATIC, sz ); + } + else + { + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_STATIC, sz, m_flVolume, ATTN_NORM, SND_CHANGE_PITCH, pitch ); + } +} diff --git a/hlsdk/ricochet/cl_dll/ev_hldm.h b/hlsdk/ricochet/cl_dll/ev_hldm.h new file mode 100644 index 00000000..8d3aec79 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/ev_hldm.h @@ -0,0 +1,16 @@ +#if !defined ( EV_HLDMH ) +#define EV_HLDMH + +enum disc_e +{ + DISC_IDLE = 0, + DISC_FIDGET, + DISC_PINPULL, + DISC_THROW1, // toss + DISC_THROW2, // medium + DISC_THROW3, // hard + DISC_HOLSTER, + DISC_DRAW +}; + +#endif // EV_HLDMH \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/events.cpp b/hlsdk/ricochet/cl_dll/events.cpp new file mode 100644 index 00000000..04c8f57c --- /dev/null +++ b/hlsdk/ricochet/cl_dll/events.cpp @@ -0,0 +1,30 @@ +/*** +* +* Copyright (c) 1999, 2000 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" + +void Game_HookEvents( void ); + +/* +=================== +EV_HookEvents + +See if game specific code wants to hook any events. +=================== +*/ +void EV_HookEvents( void ) +{ + Game_HookEvents(); +} \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/eventscripts.h b/hlsdk/ricochet/cl_dll/eventscripts.h new file mode 100644 index 00000000..0ac3251c --- /dev/null +++ b/hlsdk/ricochet/cl_dll/eventscripts.h @@ -0,0 +1,80 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// eventscripts.h +#if !defined ( EVENTSCRIPTSH ) +#define EVENTSCRIPTSH + +// defaults for clientinfo messages +#define DEFAULT_VIEWHEIGHT 28 +#define VEC_DUCK_VIEW 12 + +#define FTENT_FADEOUT 0x00000080 + +#define DMG_GENERIC 0 // generic damage was done +#define DMG_CRUSH (1 << 0) // crushed by falling or moving object +#define DMG_BULLET (1 << 1) // shot +#define DMG_SLASH (1 << 2) // cut, clawed, stabbed +#define DMG_BURN (1 << 3) // heat burned +#define DMG_FREEZE (1 << 4) // frozen +#define DMG_FALL (1 << 5) // fell too far +#define DMG_BLAST (1 << 6) // explosive blast damage +#define DMG_CLUB (1 << 7) // crowbar, punch, headbutt +#define DMG_SHOCK (1 << 8) // electric shock +#define DMG_SONIC (1 << 9) // sound pulse shockwave +#define DMG_ENERGYBEAM (1 << 10) // laser or other high energy beam +#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 + +#define DMG_DROWN (1 << 14) // Drowning +#define DMG_FIRSTTIMEBASED DMG_DROWN + +#define DMG_PARALYZE (1 << 15) // slows affected creature down +#define DMG_NERVEGAS (1 << 16) // nerve toxins, very bad +#define DMG_POISON (1 << 17) // blood poisioning +#define DMG_RADIATION (1 << 18) // radiation exposure +#define DMG_DROWNRECOVER (1 << 19) // drowning recovery +#define DMG_ACID (1 << 20) // toxic chemicals or acid burns +#define DMG_SLOWBURN (1 << 21) // in an oven +#define DMG_SLOWFREEZE (1 << 22) // in a subzero freezer +#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_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 ); +qboolean EV_IsLocal( int idx ); +qboolean EV_IsPlayer( int idx ); +void EV_CreateTracer( float *start, float *end ); + +struct cl_entity_s *GetEntity( int idx ); +struct cl_entity_s *GetViewEntity( void ); +void EV_MuzzleFlash( void ); + +#endif // EVENTSCRIPTSH diff --git a/hlsdk/ricochet/cl_dll/flashlight.cpp b/hlsdk/ricochet/cl_dll/flashlight.cpp new file mode 100644 index 00000000..48a73f47 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/flashlight.cpp @@ -0,0 +1,100 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// +// flashlight.cpp +// +// implementation of CHudFlashlight class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + + + +DECLARE_MESSAGE(m_Flash, FlashBat) +DECLARE_MESSAGE(m_Flash, Flashlight) + +#define BAT_NAME "sprites/%d_Flashlight.spr" + +int CHudFlashlight::Init(void) +{ + m_fFade = 0; + m_fOn = 0; + + HOOK_MESSAGE(Flashlight); + HOOK_MESSAGE(FlashBat); + + m_iFlags |= HUD_ACTIVE; + + gHUD.AddHudElem(this); + + return 1; +}; + +void CHudFlashlight::Reset(void) +{ + m_fFade = 0; + m_fOn = 0; +} + +int CHudFlashlight::VidInit(void) +{ + int HUD_flash_empty = gHUD.GetSpriteIndex( "flash_empty" ); + int HUD_flash_full = gHUD.GetSpriteIndex( "flash_full" ); + int HUD_flash_beam = gHUD.GetSpriteIndex( "flash_beam" ); + + m_hSprite1 = gHUD.GetSprite(HUD_flash_empty); + m_hSprite2 = gHUD.GetSprite(HUD_flash_full); + m_hBeam = gHUD.GetSprite(HUD_flash_beam); + m_prc1 = &gHUD.GetSpriteRect(HUD_flash_empty); + m_prc2 = &gHUD.GetSpriteRect(HUD_flash_full); + m_prcBeam = &gHUD.GetSpriteRect(HUD_flash_beam); + m_iWidth = m_prc2->right - m_prc2->left; + + return 1; +}; + +int CHudFlashlight:: MsgFunc_FlashBat(const char *pszName, int iSize, void *pbuf ) +{ + + + BEGIN_READ( pbuf, iSize ); + int x = READ_BYTE(); + m_iBat = x; + m_flBat = ((float)x)/100.0; + + return 1; +} + +int CHudFlashlight:: MsgFunc_Flashlight(const char *pszName, int iSize, void *pbuf ) +{ + + BEGIN_READ( pbuf, iSize ); + m_fOn = READ_BYTE(); + int x = READ_BYTE(); + m_iBat = x; + m_flBat = ((float)x)/100.0; + + return 1; +} + +int CHudFlashlight::Draw(float flTime) +{ + return 1; +} \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/geiger.cpp b/hlsdk/ricochet/cl_dll/geiger.cpp new file mode 100644 index 00000000..c50d6959 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/geiger.cpp @@ -0,0 +1,184 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// +// Geiger.cpp +// +// implementation of CHudAmmo class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include + +#include "parsemsg.h" + +DECLARE_MESSAGE(m_Geiger, Geiger ) + +int CHudGeiger::Init(void) +{ + HOOK_MESSAGE( Geiger ); + + m_iGeigerRange = 0; + m_iFlags = 0; + + gHUD.AddHudElem(this); + + srand( (unsigned)time( NULL ) ); + + return 1; +}; + +int CHudGeiger::VidInit(void) +{ + return 1; +}; + +int CHudGeiger::MsgFunc_Geiger(const char *pszName, int iSize, void *pbuf) +{ + + BEGIN_READ( pbuf, iSize ); + + // update geiger data + m_iGeigerRange = READ_BYTE(); + m_iGeigerRange = m_iGeigerRange << 2; + + m_iFlags |= HUD_ACTIVE; + + return 1; +} + +int CHudGeiger::Draw (float flTime) +{ + int pct; + float flvol; + int rg[3]; + int i; + + if (m_iGeigerRange < 1000 && m_iGeigerRange > 0) + { + // peicewise linear is better than continuous formula for this + if (m_iGeigerRange > 800) + { + pct = 0; //Con_Printf ( "range > 800\n"); + } + else if (m_iGeigerRange > 600) + { + pct = 2; + flvol = 0.4; //Con_Printf ( "range > 600\n"); + rg[0] = 1; + rg[1] = 1; + i = 2; + } + else if (m_iGeigerRange > 500) + { + pct = 4; + flvol = 0.5; //Con_Printf ( "range > 500\n"); + rg[0] = 1; + rg[1] = 2; + i = 2; + } + else if (m_iGeigerRange > 400) + { + pct = 8; + flvol = 0.6; //Con_Printf ( "range > 400\n"); + rg[0] = 1; + rg[1] = 2; + rg[2] = 3; + i = 3; + } + else if (m_iGeigerRange > 300) + { + pct = 8; + flvol = 0.7; //Con_Printf ( "range > 300\n"); + rg[0] = 2; + rg[1] = 3; + rg[2] = 4; + i = 3; + } + else if (m_iGeigerRange > 200) + { + pct = 28; + flvol = 0.78; //Con_Printf ( "range > 200\n"); + rg[0] = 2; + rg[1] = 3; + rg[2] = 4; + i = 3; + } + else if (m_iGeigerRange > 150) + { + pct = 40; + flvol = 0.80; //Con_Printf ( "range > 150\n"); + rg[0] = 3; + rg[1] = 4; + rg[2] = 5; + i = 3; + } + else if (m_iGeigerRange > 100) + { + pct = 60; + flvol = 0.85; //Con_Printf ( "range > 100\n"); + rg[0] = 3; + rg[1] = 4; + rg[2] = 5; + i = 3; + } + else if (m_iGeigerRange > 75) + { + pct = 80; + flvol = 0.9; //Con_Printf ( "range > 75\n"); + //gflGeigerDelay = cl.time + GEIGERDELAY * 0.75; + rg[0] = 4; + rg[1] = 5; + rg[2] = 6; + i = 3; + } + else if (m_iGeigerRange > 50) + { + pct = 90; + flvol = 0.95; //Con_Printf ( "range > 50\n"); + rg[0] = 5; + rg[1] = 6; + i = 2; + } + else + { + pct = 95; + flvol = 1.0; //Con_Printf ( "range < 50\n"); + rg[0] = 5; + rg[1] = 6; + i = 2; + } + + flvol = (flvol * ((rand() & 127)) / 255) + 0.25; // UTIL_RandomFloat(0.25, 0.5); + + if ((rand() & 127) < pct || (rand() & 127) < pct) + { + //S_StartDynamicSound (-1, 0, rgsfx[rand() % i], r_origin, flvol, 1.0, 0, 100); + char sz[256]; + + int j = rand() & 1; + if (i > 2) + j += rand() & 1; + + sprintf(sz, "player/geiger%d.wav", j + 1); + PlaySound(sz, flvol); + + } + } + + return 1; +} diff --git a/hlsdk/ricochet/cl_dll/health.cpp b/hlsdk/ricochet/cl_dll/health.cpp new file mode 100644 index 00000000..f733c421 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/health.cpp @@ -0,0 +1,425 @@ +/*** +* +* 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. +* 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. +* +****/ +// +// Health.cpp +// +// implementation of CHudHealth class +// + +#include "STDIO.H" +#include "STDLIB.H" +#include "MATH.H" + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" +#include "vgui_TeamFortressViewport.h" +#include + + +DECLARE_MESSAGE(m_Health, Health ) +DECLARE_MESSAGE(m_Health, Damage ) + +#define PAIN_NAME "sprites/%d_pain.spr" +#define DAMAGE_NAME "sprites/%d_dmg.spr" + +int giDmgHeight, giDmgWidth; + +int giDmgFlags[NUM_DMG_TYPES] = +{ + DMG_POISON, + DMG_ACID, + DMG_FREEZE|DMG_SLOWFREEZE, + DMG_DROWN, + DMG_BURN|DMG_SLOWBURN, + 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_bitsDamage = 0; + m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 0; + giDmgHeight = 0; + giDmgWidth = 0; + + memset(m_dmg, 0, sizeof(DAMAGE_IMAGE) * NUM_DMG_TYPES); + + + gHUD.AddHudElem(this); + return 1; +} + +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++ ) + { + m_dmg[i].fExpire = 0; + } +} + +int CHudHealth::VidInit(void) +{ + m_hSprite = 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; + 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; + } + + return 1; +} + + +int CHudHealth:: MsgFunc_Damage(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int armor = READ_BYTE(); // armor + int damageTaken = READ_BYTE(); // health + long bitsDamage = READ_LONG(); // damage bits + + vec3_t vecFrom; + + for ( int i = 0 ; i < 3 ; i++) + vecFrom[i] = READ_COORD(); + + UpdateTiles(gHUD.m_flTime, bitsDamage); + + // 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 ) +{ + int iHealth = m_iHealth; + + if (iHealth > 25) + iHealth -= 25; + else if ( iHealth < 0 ) + iHealth = 0; +#if 0 + g = iHealth * 255 / 100; + r = 255 - g; + b = 0; +#else + if (m_iHealth > 25) + { + UnpackRGB(r,g,b, RGB_YELLOWISH); + } + else + { + r = 250; + g = 0; + b = 0; + } +#endif +} + +int CHudHealth::Draw(float flTime) +{ + // No Health in Discwar + if ( gHUD.m_iHideHUDDisplay & HIDEHUD_HEALTH ) + return 1; + + if ( !m_hSprite ) + m_hSprite = LoadSprite(PAIN_NAME); + + return DrawDamage(flTime); +} + +void CHudHealth::CalcDamageDirection(vec3_t vecFrom) +{ + vec3_t forward, right, up; + float side, front; + vec3_t vecOrigin, vecAngles; + + if (!vecFrom[0] && !vecFrom[1] && !vecFrom[2]) + { + m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 0; + 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(); + + vecFrom = vecFrom.Normalize(); + AngleVectors (vecAngles, forward, right, up); + + front = DotProduct (vecFrom, right); + side = DotProduct (vecFrom, forward); + + if (flDistToTarget <= 50) + { + m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 1; + } + else + { + if (side > 0) + { + if (side > 0.3) + m_fAttackFront = max(m_fAttackFront, side); + } + else + { + float f = fabs(side); + if (f > 0.3) + m_fAttackRear = max(m_fAttackRear, f); + } + + if (front > 0) + { + if (front > 0.3) + m_fAttackRight = max(m_fAttackRight, front); + } + else + { + float f = fabs(front); + if (f > 0.3) + m_fAttackLeft = max(m_fAttackLeft, f); + } + } +} + +int CHudHealth::DrawPain(float flTime) +{ + if (!(m_fAttackFront || m_fAttackRear || m_fAttackLeft || m_fAttackRight)) + return 1; + + int r, g, b; + int x, y, a, shade; + + // TODO: get the shift value of the health + a = 255; // max brightness until then + + float fFade = gHUD.m_flTimeDelta * 2; + + // SPR_Draw top + if (m_fAttackFront > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackFront, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 - SPR_Width(m_hSprite, 0)/2; + y = ScreenHeight/2 - SPR_Height(m_hSprite,0) * 3; + SPR_DrawAdditive(0, x, y, NULL); + m_fAttackFront = max( 0, m_fAttackFront - fFade ); + } else + m_fAttackFront = 0; + + if (m_fAttackRight > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackRight, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 + SPR_Width(m_hSprite, 1) * 2; + y = ScreenHeight/2 - SPR_Height(m_hSprite,1)/2; + SPR_DrawAdditive(1, x, y, NULL); + m_fAttackRight = max( 0, m_fAttackRight - fFade ); + } else + m_fAttackRight = 0; + + if (m_fAttackRear > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackRear, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 - SPR_Width(m_hSprite, 2)/2; + y = ScreenHeight/2 + SPR_Height(m_hSprite,2) * 2; + SPR_DrawAdditive(2, x, y, NULL); + m_fAttackRear = max( 0, m_fAttackRear - fFade ); + } else + m_fAttackRear = 0; + + if (m_fAttackLeft > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackLeft, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 - SPR_Width(m_hSprite, 3) * 3; + y = ScreenHeight/2 - SPR_Height(m_hSprite,3)/2; + SPR_DrawAdditive(3, x, y, NULL); + + m_fAttackLeft = max( 0, m_fAttackLeft - fFade ); + } else + m_fAttackLeft = 0; + + return 1; +} + +int CHudHealth::DrawDamage(float flTime) +{ + int r, g, b, a; + DAMAGE_IMAGE *pdmg; + + if (!m_bitsDamage) + return 1; + + UnpackRGB(r,g,b, RGB_YELLOWISH); + + a = (int)( fabs(sin(flTime*2)) * 256.0); + + ScaleColors(r, g, b, a); + + // Draw all the items + for (int i = 0; i < NUM_DMG_TYPES; i++) + { + if (m_bitsDamage & giDmgFlags[i]) + { + pdmg = &m_dmg[i]; + SPR_Set(gHUD.GetSprite(m_HUD_dmg_bio + i), r, g, b ); + + // Discwar: Hack. Freeze is the only icon we use. Just place it directly above the disc ammo + int iX = (ScreenWidth - DISC_ICON_WIDTH) / 2; + int iXPos = iX - DISC_ICON_SPACER; + SPR_DrawAdditive(0, iXPos + (DISC_ICON_SPACER), ScreenHeight - YRES(92), &gHUD.GetSpriteRect(m_HUD_dmg_bio + i)); + } + } + +/* + // check for bits that should be expired + for ( i = 0; i < NUM_DMG_TYPES; i++ ) + { + DAMAGE_IMAGE *pdmg = &m_dmg[i]; + + if ( m_bitsDamage & giDmgFlags[i] ) + { + pdmg->fExpire = min( flTime + DMG_IMAGE_LIFE, pdmg->fExpire ); + + if ( pdmg->fExpire <= flTime // when the time has expired + && a < 40 ) // and the flash is at the low point of the cycle + { + pdmg->fExpire = 0; + + int y = pdmg->y; + pdmg->x = pdmg->y = 0; + + // move everyone above down + for (int j = 0; j < NUM_DMG_TYPES; j++) + { + pdmg = &m_dmg[j]; + if ((pdmg->y) && (pdmg->y < y)) + pdmg->y += giDmgHeight; + + } + + m_bitsDamage &= ~giDmgFlags[i]; // clear the bits + } + } + } +*/ + return 1; +} + + +void CHudHealth::UpdateTiles(float flTime, long bitsDamage) +{ + DAMAGE_IMAGE *pdmg; + + // Which types are new? + long bitsOn = ~m_bitsDamage & bitsDamage; + + for (int i = 0; i < NUM_DMG_TYPES; i++) + { + pdmg = &m_dmg[i]; + + // Is this one already on? + if (m_bitsDamage & giDmgFlags[i]) + { + pdmg->fExpire = flTime + DMG_IMAGE_LIFE; // extend the duration + if (!pdmg->fBaseline) + pdmg->fBaseline = flTime; + } + + // Are we just turning it on? + if (bitsOn & giDmgFlags[i]) + { + // put this one at the bottom + pdmg->x = giDmgWidth/8; + pdmg->y = ScreenHeight - giDmgHeight * 2; + pdmg->fExpire=flTime + DMG_IMAGE_LIFE; + + // move everyone else up + for (int j = 0; j < NUM_DMG_TYPES; j++) + { + if (j == i) + continue; + + pdmg = &m_dmg[j]; + if (pdmg->y) + pdmg->y -= giDmgHeight; + + } + pdmg = &m_dmg[i]; + } + } + + // damage bits are only turned on here; they are turned off when the draw time has expired (in DrawDamage()) + m_bitsDamage |= bitsDamage; +} diff --git a/hlsdk/ricochet/cl_dll/health.h b/hlsdk/ricochet/cl_dll/health.h new file mode 100644 index 00000000..dd598978 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/health.h @@ -0,0 +1,128 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ + +#define DMG_IMAGE_LIFE 2 // seconds that image is up + +#define DMG_IMAGE_POISON 0 +#define DMG_IMAGE_ACID 1 +#define DMG_IMAGE_COLD 2 +#define DMG_IMAGE_DROWN 3 +#define DMG_IMAGE_BURN 4 +#define DMG_IMAGE_NERVE 5 +#define DMG_IMAGE_RAD 6 +#define DMG_IMAGE_SHOCK 7 +//tf defines +#define DMG_IMAGE_CALTROP 8 +#define DMG_IMAGE_TRANQ 9 +#define DMG_IMAGE_CONCUSS 10 +#define DMG_IMAGE_HALLUC 11 +#define NUM_DMG_TYPES 12 +// instant damage + +#define DMG_GENERIC 0 // generic damage was done +#define DMG_CRUSH (1 << 0) // crushed by falling or moving object +#define DMG_BULLET (1 << 1) // shot +#define DMG_SLASH (1 << 2) // cut, clawed, stabbed +#define DMG_BURN (1 << 3) // heat burned +#define DMG_FREEZE (1 << 4) // frozen +#define DMG_FALL (1 << 5) // fell too far +#define DMG_BLAST (1 << 6) // explosive blast damage +#define DMG_CLUB (1 << 7) // crowbar, punch, headbutt +#define DMG_SHOCK (1 << 8) // electric shock +#define DMG_SONIC (1 << 9) // sound pulse shockwave +#define DMG_ENERGYBEAM (1 << 10) // laser or other high energy beam +#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 + + +#define DMG_DROWN (1 << 14) // Drowning +#define DMG_FIRSTTIMEBASED DMG_DROWN + +#define DMG_PARALYZE (1 << 15) // slows affected creature down +#define DMG_NERVEGAS (1 << 16) // nerve toxins, very bad +#define DMG_POISON (1 << 17) // blood poisioning +#define DMG_RADIATION (1 << 18) // radiation exposure +#define DMG_DROWNRECOVER (1 << 19) // drowning recovery +#define DMG_ACID (1 << 20) // toxic chemicals or acid burns +#define DMG_SLOWBURN (1 << 21) // in an oven +#define DMG_SLOWFREEZE (1 << 22) // in a subzero freezer +#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_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) + +// 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; + float fBaseline; + int x, y; +} DAMAGE_IMAGE; + +// +//----------------------------------------------------- +// +class CHudHealth: public CHudBase +{ +public: + virtual int Init( void ); + 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; + + int m_bitsDamage; + +private: + HSPRITE m_hSprite; + HSPRITE m_hDamage; + + DAMAGE_IMAGE m_dmg[NUM_DMG_TYPES]; + int DrawPain(float fTime); + int DrawDamage(float fTime); + void CalcDamageDirection(vec3_t vecFrom); + void UpdateTiles(float fTime, long bits); +}; diff --git a/hlsdk/ricochet/cl_dll/hl/hl_baseentity.cpp b/hlsdk/ricochet/cl_dll/hl/hl_baseentity.cpp new file mode 100644 index 00000000..16809e90 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/hl/hl_baseentity.cpp @@ -0,0 +1,248 @@ +/*** +* +* 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. +* 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" + +// 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 ) { } +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 ) { } + +CBaseEntity* CBaseMonster :: CheckTraceHullAttack( float flDist, int iDamage, int iDmgType ) { return NULL; } +void CBaseMonster :: Look ( int iDistance ) { } +float CBaseAnimating :: StudioFrameAdvance ( float flInterval ) { return 0.0; } +int CBaseMonster::IRelationship ( CBaseEntity *pTarget ) { return 0; } +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::ChangeYaw ( int yawSpeed ) { return 0; } +int CBaseAnimating :: LookupActivity ( int activity ) { return 0; } +int CBaseAnimating :: LookupActivityHeaviest ( int activity ) { return 0; } + +BOOL CBaseAnimating :: GetSequenceFlags( ) { return FALSE; } +void CBaseAnimating :: DispatchAnimEvents ( float flInterval ) { } +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; } +void CBaseEntity::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) { } +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 ) { } +void CBaseMonster::ReportAIState( void ) { } +void CBaseMonster :: KeyValue( KeyValueData *pkvd ) { } +BOOL CBaseMonster :: FCheckAITrigger ( void ) { return FALSE; } +void CBaseMonster::CorpseFallThink( void ) { } +void CBaseMonster :: MonsterInitDead( void ) { } +void CBaseMonster :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) { } +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 (Activity)0; } +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 TrainSpeed(int iSpeed, int iMax) { return 0; } +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 CBasePlayer :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { return 0; } +void CBasePlayer::RemoveAllItems( BOOL removeSuit ) { } +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 :: PlayStepSound(int step, float fvol) { } +void CBasePlayer :: UpdateStepSound( void ) { } +void CBasePlayer::PreThink(void) { } +void CBasePlayer::CheckTimeBasedDamage() { } +void CBasePlayer :: UpdateGeigerCounter( void ) { } +void CBasePlayer::CheckSuitUpdate() { } +void CBasePlayer::SetSuitUpdate(char *name, int fgroup, int iNoRepeatTime) { } +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() { } +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; } +void CBasePlayer :: BarnacleVictimBitten ( entvars_t *pevBarnacle ) { } +void CBasePlayer :: BarnacleVictimReleased ( void ) { } +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 CBasePlayer::RemoveAllPowerups( void ) {} +bool CBasePlayer::HasPowerup(int) { return 0; } + +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 ) { } \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/hl/hl_events.cpp b/hlsdk/ricochet/cl_dll/hl/hl_events.cpp new file mode 100644 index 00000000..0906ab3a --- /dev/null +++ b/hlsdk/ricochet/cl_dll/hl/hl_events.cpp @@ -0,0 +1,45 @@ +/*** +* +* 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. +* 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" +{ +// RICOCHET +void EV_TriggerJump( struct event_args_s *args ); +void EV_FireDisc( struct event_args_s *args ); +void EV_TrainPitchAdjust( 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/train.sc", EV_TrainPitchAdjust ); + gEngfuncs.pfnHookEvent( "events/firedisc.sc", EV_FireDisc ); + gEngfuncs.pfnHookEvent( "events/jump.sc", EV_TriggerJump ); +} \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/hl/hl_objects.cpp b/hlsdk/ricochet/cl_dll/hl/hl_objects.cpp new file mode 100644 index 00000000..204d0bcb --- /dev/null +++ b/hlsdk/ricochet/cl_dll/hl/hl_objects.cpp @@ -0,0 +1,28 @@ +/*** +* +* 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. +* 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" + +/* +===================== +Game_AddObjects + +Add game specific, client-side objects here +===================== +*/ +void Game_AddObjects( void ) +{ +} \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/hl/hl_weapons.cpp b/hlsdk/ricochet/cl_dll/hl/hl_weapons.cpp new file mode 100644 index 00000000..3df4d82a --- /dev/null +++ b/hlsdk/ricochet/cl_dll/hl/hl_weapons.cpp @@ -0,0 +1,1423 @@ +/*** +* +* 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. +* 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 "disc_weapon.h" +#include "discwar.h" +#include "nodes.h" +#include "player.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 "../hud_iface.h" +#include "../com_weapons.h" +#include "../demo.h" + +#include "r_studioint.h" + +#include "../Ricochet_JumpPads.h" +#include "studio.h" +#include "com_model.h" + +// Global engine <-> studio model rendering code interface +extern engine_studio_api_t IEngineStudio; + +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 CBasePlayerWeapon *g_pWpns[ 32 ]; + +// For storing predicted sequence and gaitsequence and origin/angles data +static int g_rseq = 0, g_gaitseq = 0; +static vec3_t g_clorg, g_clang; + +// HLDM Weapon placeholder entities. +CDiscWeapon g_Disc; +extern int g_iCannotFire; + +/* +====================== +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 ); +} + +/* +===================== +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 ] = (CBasePlayerWeapon *)pEntity; + } +} + +/* +===================== +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 ) +{ +#if 0 // FIXME, need to know primary ammo to get this right + 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; +#endif + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + fDelay; + + //!!UNDONE -- reload sound goes here !!! + SendWeaponAnim( iAnim ); + + 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 ) +{ + if ( !CanDeploy() ) + return FALSE; + + gEngfuncs.CL_LoadModel( szViewModel, &m_pPlayer->pev->viewmodel ); + + SendWeaponAnim( iAnim ); + + 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 ) +{ + m_pPlayer->pev->weaponanim = iAnim; + + int body = 0; + + HUD_SendWeaponAnim( iAnim, body, 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; + + // 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 < 0.0 ) + { + Reload(); + return; + } + + 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 ); + + // Allocate slot(s) for each weapon that we are going to be predicting + HUD_PrepEntity( &g_Disc , &player ); +} + +/* +============================== +LookupSequence + +Find sequence # of named sequence +============================== +*/ +int LookupSequence( void *pmodel, const char *label ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + for (int i = 0; i < pstudiohdr->numseq; i++) + { + if (stricmp( pseqdesc[i].label, label ) == 0) + return i; + } + + return -1; +} + +/* +============================== +LookupSequence + +============================== +*/ +int CBaseAnimating :: LookupSequence ( const char *label ) +{ + cl_entity_t *current; + + current = gEngfuncs.GetLocalPlayer(); + if ( !current || !current->model ) + return 0; + + return ::LookupSequence( (studiohdr_t *)IEngineStudio.Mod_Extradata( current->model ), label ); +} + +/* +============================== +GetSequenceInfo + +============================== +*/ +void GetSequenceInfo( void *pmodel, entvars_t *pev, float *pflFrameRate, float *pflGroundSpeed ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return; + + mstudioseqdesc_t *pseqdesc; + + if (pev->sequence >= pstudiohdr->numseq) + { + *pflFrameRate = 0.0; + *pflGroundSpeed = 0.0; + return; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + if (pseqdesc->numframes > 1) + { + *pflFrameRate = 256 * pseqdesc->fps / (pseqdesc->numframes - 1); + *pflGroundSpeed = sqrt( pseqdesc->linearmovement[0]*pseqdesc->linearmovement[0]+ pseqdesc->linearmovement[1]*pseqdesc->linearmovement[1]+ pseqdesc->linearmovement[2]*pseqdesc->linearmovement[2] ); + *pflGroundSpeed = *pflGroundSpeed * pseqdesc->fps / (pseqdesc->numframes - 1); + } + else + { + *pflFrameRate = 256.0; + *pflGroundSpeed = 0.0; + } +} + +/* +============================== +GetSequenceFlags + +============================== +*/ +int GetSequenceFlags( void *pmodel, entvars_t *pev ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || pev->sequence >= pstudiohdr->numseq ) + return 0; + + mstudioseqdesc_t *pseqdesc; + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + return pseqdesc->flags; +} + +/* +============================== +ResetSequenceInfo + +============================== +*/ +void CBaseAnimating :: ResetSequenceInfo ( ) +{ + cl_entity_t *current; + + current = gEngfuncs.GetLocalPlayer(); + if ( !current || !current->model ) + return; + + void *pmodel = (studiohdr_t *)IEngineStudio.Mod_Extradata( current->model ); + + GetSequenceInfo( pmodel, pev, &m_flFrameRate, &m_flGroundSpeed ); + m_fSequenceLoops = ((GetSequenceFlags() & STUDIO_LOOPING) != 0); + pev->animtime = gpGlobals->time; + pev->framerate = 1.0; + m_fSequenceFinished = FALSE; + m_flLastEventCheck = gpGlobals->time; +} + +/* +============================== +UTIL_MakeVectors + +============================== +*/ +void UTIL_MakeVectors( const Vector &vecAngles ) +{ + gEngfuncs.pfnAngleVectors ( (float *)&vecAngles, gpGlobals->v_forward, gpGlobals->v_right, gpGlobals->v_up); +} + +/* +============================== +GetFallAnimation + +============================== +*/ +int CBasePlayer::GetFallAnimation( void ) +{ + Vector vecNormVel = pev->velocity; + vecNormVel.Normalize(); + int fallAnim; + + UTIL_MakeVectors( pev->angles ); + float flDot = DotProduct( vecNormVel, gpGlobals->v_forward ); + float flSideDot = DotProduct( vecNormVel, gpGlobals->v_right ); + // Choose a falling animation based upon the velocity vector + if ( flDot < -0.6 ) + fallAnim = LookupSequence( "fall_b" ); + else if ( flSideDot < -0.6 ) + fallAnim = LookupSequence( "fall_r" ); + else if ( flSideDot > 0.6 ) + fallAnim = LookupSequence( "fall_l" ); + else + fallAnim = LookupSequence( "fall_f" ); + + return fallAnim; +} + +#define WALK_SPEED 100 +// Set the activity based on an event or current state + +/* +============================== +SetAnimation + +============================== +*/ +void CBasePlayer::SetAnimation( PLAYER_ANIM playerAnim ) +{ + int animDesired; + float speed; + + speed = pev->velocity.Length2D(); + + switch (playerAnim) + { + case PLAYER_JUMP: + m_IdealActivity = ACT_HOP; + break; + + case PLAYER_SUPERJUMP: + m_IdealActivity = ACT_LEAP; + break; + + case PLAYER_ATTACK1: + m_IdealActivity = ACT_BASE_THROW; + break; + + case PLAYER_FALL: + m_IdealActivity = ACT_FALL; + break; + + case PLAYER_IDLE: + case PLAYER_WALK: + if ( !FBitSet( pev->flags, FL_ONGROUND ) && (m_Activity == ACT_HOP) ) // Still jumping + { + m_IdealActivity = m_Activity; + } + else + { + m_IdealActivity = ACT_BASE_WALK; + } + break; + } + + Vector vecNormVel; + float flDot, flSideDot, flVelDot; + bool bInReverse; + int iFrame; + + // Decide which sequence to play based upon the activity + switch (m_IdealActivity) + { + case ACT_DIEFORWARD: + case ACT_FALL: + default: + if ( m_Activity == m_IdealActivity) + return; + m_Activity = ACT_FALL; + + animDesired = GetFallAnimation(); + // Already using the desired animation? + if (pev->sequence == animDesired) + return; + + pev->gaitsequence = 0; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + + case ACT_LEAP: + UTIL_MakeVectors( pev->angles ); + vecNormVel = pev->velocity; + vecNormVel.Normalize(); + flDot = DotProduct( vecNormVel, gpGlobals->v_forward ); + if ( flDot < -0.6 ) + { + // Use non-blended backflip + animDesired = LookupSequence( "backflip" ); + m_Activity = m_IdealActivity; + pev->gaitsequence = 0; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + } + else + { + // Use blended longjump + animDesired = LookupSequence( "longjump" ); + m_Activity = ACT_LEAP; + pev->gaitsequence = animDesired; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + } + break; + + case ACT_DIE_HEADSHOT: + animDesired = LookupSequence( "die_simple" ); + m_Activity = m_IdealActivity; + + pev->gaitsequence = 0; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + + case ACT_HOP: + iFrame = pev->frame / 18; + if ( iFrame >= 2 && iFrame <= 11 ) + animDesired = LookupSequence( "jump" ); + else + animDesired = LookupSequence( "jumpl" ); + // Already using the desired animation? + if (pev->sequence == animDesired) + return; + + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + + case ACT_BASE_THROW: + // No throw animation during backflip + if ( pev->sequence == LookupSequence( "backflip" ) ) + return; + + // If we're in the air, we need to use the blended longjump throw + if ( pev->sequence == LookupSequence( "longjump" ) ) + { + // Use blended longjump + animDesired = LookupSequence( "longjump_throw" ); + m_Activity = ACT_FLINCH_CLOCKWISE; + pev->gaitsequence = animDesired; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + } + + animDesired = GetThrowAnim(); + m_Activity = m_IdealActivity; + m_flThrowTime = 0.25; + break; + + case ACT_BASE_WALK: + UTIL_MakeVectors( pev->angles ); + bInReverse = ( pev->sequence == LookupSequence("base_reverse") ); + vecNormVel = pev->velocity; + vecNormVel.Normalize(); + flDot = DotProduct( vecNormVel, gpGlobals->v_forward ); + flSideDot = DotProduct( vecNormVel, gpGlobals->v_right ); + flVelDot = DotProduct( m_vecOldVelocity, vecNormVel ); + + if ( ( m_flBackupTime <= 0 ) && (m_Activity != ACT_BASE_THROW) || m_fSequenceFinished ) + { + if ( speed == 0 ) + { + animDesired = LookupSequence( "base_stand" ); + } + else if ( flDot < -0.6 ) + { + animDesired = LookupSequence( "base_backup" ); + } + else if ( ( flVelDot <= 0 ) && ( flDot <= 0.6 ) ) + { + animDesired = LookupSequence( "base_reverse" ); + m_flBackupTime = 0.7; + pev->effects |= EF_NOINTERP; + } + else + { + if ( speed > WALK_SPEED ) + animDesired = LookupSequence( "base_run" ); + else + animDesired = LookupSequence( "base_walk" ); + } + + if (animDesired == -1) + { + animDesired = 0; + } + m_Activity = ACT_BASE_WALK; + } + else if ( bInReverse ) + { + // Don't play the backup run if we're still in the backup run + if ( DotProduct( m_vecOldVelocity, vecNormVel ) < 0 ) + { + m_flBackupTime = 0; + if ( speed > WALK_SPEED ) + animDesired = LookupSequence( "base_run" ); + else + animDesired = LookupSequence( "base_walk" ); + pev->effects |= EF_NOINTERP; + } + else + { + animDesired = pev->sequence; + } + } + else + { + animDesired = pev->sequence; + } + break; + } + + // Set gait animation + if ( m_flBackupTime > 0 ) + { + pev->gaitsequence = LookupSequence( "base_backup" ); + } + else + { + if ( speed > WALK_SPEED ) + { + pev->gaitsequence = LookupSequence( "base_run" ); + } + else if (speed > 0) + { + pev->gaitsequence = LookupSequence( "base_walk" ); + } + } + + // Idle? + if (speed <= 0) + { + pev->gaitsequence = LookupSequence( "base_stand" ); + } + + // Already using the desired animation? + if (pev->sequence == animDesired) + return; + + // Reset to first frame of desired animation + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); +} + +int CBasePlayer::GetThrowAnim( void ) +{ + int throwAnim; + + if ( pev->velocity.Length2D() == 0 ) + throwAnim = LookupSequence( "base_stand_throw" ); + else + throwAnim = LookupSequence( "base_throw" ); + + return throwAnim; +} + +int CBasePlayer::GetHoldAnim( void ) +{ + int holdAnim; + + // Choose hold anim based upon powerups. + // Multiple Powerups can be had, in which case the one considered more dangerous has the animation. + if ( m_iPowerups & POW_TRIPLE ) + holdAnim = LookupSequence( "triple_ready" ); + else if ( m_iPowerups & POW_FAST ) + holdAnim = LookupSequence( "kill_ready" ); + else if ( m_iPowerups & POW_HARD ) + holdAnim = LookupSequence( "hard_ready" ); + else if ( m_iPowerups & POW_FREEZE ) + holdAnim = LookupSequence( "freeze_ready" ); + else + holdAnim = LookupSequence( "base_ready" ); + + return holdAnim; +} + +/* +============================== +PostThink + +============================== +*/ +void CBasePlayer::PostThink() +{ + if ( !g_runfuncs ) + return; + + // select the proper animation for the player character + if ( !CL_IsDead() && (m_flTouchedByJumpPad < gpGlobals->time) ) + { + if (!pev->velocity.x && !pev->velocity.y) + SetAnimation( PLAYER_IDLE ); + else if ((pev->velocity.x || pev->velocity.y) && (FBitSet(pev->flags, FL_ONGROUND))) + SetAnimation( PLAYER_WALK ); + else if (pev->waterlevel > 1) + SetAnimation( PLAYER_WALK ); + } + + if ( !CL_IsDead() && ( m_flThrowTime <= 0.0 ) ) + { + m_Activity = ACT_BASE_WALK; + m_flThrowTime = 0.0; + if (!pev->velocity.x && !pev->velocity.y) + { + SetAnimation( PLAYER_IDLE ); + } + else + { + SetAnimation( PLAYER_WALK ); + } + } + + // Store old velocity for use in backpedalling animations + m_vecOldVelocity = pev->velocity; + m_vecOldVelocity.Normalize(); +} + +/* +===================== +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; + CBasePlayerWeapon *pWeapon = NULL; + CBasePlayerWeapon *pCurrent; + weapon_data_t nulldata, *pfrom, *pto; + static int lasthealth; + + memset( &nulldata, 0, sizeof( nulldata ) ); + + HUD_InitClientWeapons(); + + // Get current clock + gpGlobals->time = time; + + // 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 ) + { + case WEAPON_DISC: + pWeapon = &g_Disc; + break; + } + + // 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_iClip = pfrom->m_iClip; + pCurrent->m_flNextPrimaryAttack = pfrom->m_flNextPrimaryAttack; + pCurrent->m_flNextSecondaryAttack = pfrom->m_flNextSecondaryAttack; + pCurrent->m_flTimeWeaponIdle = pfrom->m_flTimeWeaponIdle; + + // Ricochet uses m_iClip to transmit current/primary ammo to client + if ( pWeapon == pCurrent ) + { + player.m_rgAmmo[pCurrent->m_iPrimaryAmmoType] = pfrom->m_iClip; + } + } + + // 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; + player.m_flBackupTime = from->client.fuser1; + player.m_Activity = (Activity)(int)from->client.fuser2; + player.m_flThrowTime = from->client.fuser3; + + player.m_vecOldVelocity = from->client.vuser1; + + player.pev->sequence = from->playerstate.sequence; + player.pev->gaitsequence = from->playerstate.gaitsequence; + player.pev->angles = from->playerstate.angles; + + // Point to current weapon object + if ( from->client.m_iId ) + { + player.m_pActiveItem = g_pWpns[ 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; + + // 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(); + } + } + + // 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; + } + + // Fix up animations, etc. + player.PostThink(); + + // 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 predcition 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; + to->client.fuser1 = player.m_flBackupTime; + to->client.fuser2 = (float)(int)player.m_Activity; + to->client.fuser3 = player.m_flThrowTime; + + to->client.vuser1 = player.m_vecOldVelocity; + + to->playerstate.sequence = player.pev->sequence; + to->playerstate.gaitsequence = player.pev->gaitsequence; + + // 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; + } + + pto->m_fInReload = pCurrent->m_fInReload; + pto->m_iClip = pCurrent->m_iClip; + pto->m_flNextPrimaryAttack = pCurrent->m_flNextPrimaryAttack; + pto->m_flNextSecondaryAttack = pCurrent->m_flNextSecondaryAttack; + pto->m_flTimeWeaponIdle = pCurrent->m_flTimeWeaponIdle; + + // 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; + + if ( pto->m_flPumpTime != -9999 ) + { + pto->m_flPumpTime -= cmd->msec / 1000.0; + if ( pto->m_flPumpTime < -0.001 ) + pto->m_flPumpTime = -0.001; + } + + 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; + } + } + + // 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.fuser1 -= cmd->msec / 1000.0; + if ( to->client.fuser1 < -0.001 ) + { + to->client.fuser1 = -0.001; + } + + to->client.fuser3 -= cmd->msec / 1000.0; + if ( to->client.fuser3 < -0.001 ) + { + to->client.fuser3 = -0.001; + } + + // Wipe it so we can't use it after this frame + g_finalstate = NULL; +} + +/* +============================== +Ricochet_GetSequence + +============================== +*/ +void Ricochet_GetSequence( int *seq, int *gaitseq ) +{ + *seq = g_rseq; + *gaitseq = g_gaitseq; +} + +/* +============================== +Ricochet_SetSequence + +============================== +*/ +void Ricochet_SetSequence( int seq, int gaitseq ) +{ + g_rseq = seq; + g_gaitseq = gaitseq; +} + +/* +============================== +Ricochet_SetOrientation + +============================== +*/ +void Ricochet_SetOrientation( vec3_t o, vec3_t a ) +{ + g_clorg = o; + g_clang = a; +} + +/* +============================== +Ricochet_GetOrientation + +============================== +*/ +void Ricochet_GetOrientation( float *o, float *a ) +{ + int i; + + for ( i = 0; i < 3; i++ ) + { + o[ i ] = g_clorg[ i ]; + a[ i ] = g_clang[ i ]; + } +} + + +/* +===================== +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; + + // We'll always do prediction of disc throwing + if ( cl_lw && cl_lw->value ) + { + // Allowed to fire? + if ( g_iCannotFire == FALSE ) + HUD_WeaponsPostThink( from, to, cmd, time, random_seed ); + } + else + { + to->client.fov = g_lastFOV; + } + + // Store of final sequence, etc. for client side animation + if ( g_runfuncs ) + { + Ricochet_SetSequence( to->playerstate.sequence, to->playerstate.gaitsequence ); + Ricochet_SetOrientation( to->playerstate.origin, cmd->viewangles ); + } + + // See if we stepped on a jump pad + Ricochet_CheckJumpPads( from, to ); + + // All games can use FOV state + g_lastFOV = to->client.fov; +} diff --git a/hlsdk/ricochet/cl_dll/hud.cpp b/hlsdk/ricochet/cl_dll/hud.cpp new file mode 100644 index 00000000..1d9188bb --- /dev/null +++ b/hlsdk/ricochet/cl_dll/hud.cpp @@ -0,0 +1,589 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// +// hud.cpp +// +// implementation of CHud class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" +#include "hud_servers.h" +#include "vgui_TeamFortressViewport.h" + +#include "demo.h" +#include "demo_api.h" + +#include "vgui_scorepanel.h" + +extern TeamFortressViewport *gViewPort; + + +class CHLVoiceStatusHelper : public IVoiceStatusHelper +{ +public: + virtual void GetPlayerTextColor(int entindex, int color[3]) + { + color[0] = color[1] = color[2] = 255; + + /* if( entindex >= 0 && entindex < sizeof(g_PlayerExtraInfo)/sizeof(g_PlayerExtraInfo[0]) ) + { + int iTeam = g_PlayerExtraInfo[entindex].teamnumber; + + if ( iTeam < 0 ) + { + iTeam = 0; + } + + iTeam = iTeam % iNumberOfTeamColors; + + color[0] = iTeamColors[iTeam][0]; + color[1] = iTeamColors[iTeam][1]; + color[2] = iTeamColors[iTeam][2]; + }*/ + } + + virtual void UpdateCursorState() + { + gViewPort->UpdateCursorState(); + } + + virtual int GetAckIconHeight() + { + return ScreenHeight - gHUD.m_iFontHeight*3 - 6; + } + + virtual bool CanShowSpeakerLabels() + { + if( gViewPort && gViewPort->m_pScoreBoard ) + return !gViewPort->m_pScoreBoard->isVisible(); + else + return false; + } +}; +static CHLVoiceStatusHelper g_VoiceStatusHelper; + +extern client_sprite_t *GetSpriteList(client_sprite_t *pList, const char *psz, int iRes, int iCount); + +extern cvar_t *sensitivity; +cvar_t *cl_lw = NULL; + +void ShutdownInput (void); + +void __CmdFunc_ToggleServerBrowser( void ) +{ + if ( gViewPort ) + { + gViewPort->ToggleServerBrowser(); + } +} + +//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) +{ + return gHUD.MsgFunc_ResetHUD(pszName, iSize, pbuf ); +} + +int __MsgFunc_InitHUD(const char *pszName, int iSize, void *pbuf) +{ + gHUD.MsgFunc_InitHUD( pszName, iSize, pbuf ); + return 1; +} + +int __MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_SetFOV( pszName, iSize, pbuf ); +} + +int __MsgFunc_Concuss(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_Concuss( pszName, iSize, pbuf ); +} + +int __MsgFunc_GameMode(const char *pszName, int iSize, void *pbuf ) +{ + return gHUD.MsgFunc_GameMode( pszName, iSize, pbuf ); +} + +void __CmdFunc_OpenCommandMenu(void) +{ + if ( gViewPort ) + { + gViewPort->ShowCommandMenu(); + } +} + +void __CmdFunc_CloseCommandMenu(void) +{ + if ( gViewPort ) + { + gViewPort->InputSignalHideCommandMenu(); + } +} + +int __MsgFunc_StartRnd(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_StartRnd( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_EndRnd(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_EndRnd( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_ScoreInfo(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_ScoreInfo( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_TeamScore(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_TeamScore( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_TeamInfo(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_TeamInfo( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_Powerup(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_Powerup( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_Reward(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_Reward( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_Frozen(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_Frozen( 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 ); + HOOK_MESSAGE( SetFOV ); + HOOK_MESSAGE( Concuss ); + + HOOK_MESSAGE( ScoreInfo ); + HOOK_MESSAGE( TeamScore ); + HOOK_MESSAGE( TeamInfo ); + + // Discwar + HOOK_MESSAGE( StartRnd ); + HOOK_MESSAGE( EndRnd ); + HOOK_MESSAGE( Powerup ); + HOOK_MESSAGE( Reward ); + HOOK_MESSAGE( Frozen ); + + HOOK_COMMAND( "+commandmenu", OpenCommandMenu ); + HOOK_COMMAND( "-commandmenu", CloseCommandMenu ); + + m_iLogo = 0; + m_iFOV = 0; + + CVAR_CREATE( "zoom_sensitivity_ratio", "1.2", 0 ); + default_fov = CVAR_CREATE( "default_fov", "90", 0 ); + m_pCvarStealMouse = CVAR_CREATE( "hud_capturemouse", "1", FCVAR_ARCHIVE ); + cl_lw = gEngfuncs.pfnGetCvarPointer( "cl_lw" ); + + m_pSpriteList = NULL; + + // Clear any old HUD list + if ( m_pHudList ) + { + HUDLIST *pList; + while ( m_pHudList ) + { + pList = m_pHudList; + m_pHudList = m_pHudList->pNext; + free( pList ); + } + m_pHudList = NULL; + } + + // In case we get messages before the first update -- time will be valid + m_flTime = 1.0; + + m_Ammo.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_AmmoSecondary.Init(); + m_TextMessage.Init(); + m_StatusIcons.Init(); + + m_SayText.Init(); + m_Menu.Init(); + + GetClientVoiceMgr()->Init(&g_VoiceStatusHelper, (vgui::Panel**)&gViewPort); + + ServersInit(); + + MsgFunc_ResetHUD(0, 0, NULL ); +} + +// CHud destructor +// cleans up memory allocated for m_rg* arrays +CHud :: ~CHud() +{ + delete [] m_rghSprites; + delete [] m_rgrcRects; + delete [] m_rgszSpriteNames; + + if ( m_pHudList ) + { + HUDLIST *pList; + while ( m_pHudList ) + { + pList = m_pHudList; + m_pHudList = m_pHudList->pNext; + free( pList ); + } + m_pHudList = NULL; + } + + ServersShutdown(); +} + +// GetSpriteIndex() +// searches through the sprite list loaded from hud.txt for a name matching SpriteName +// returns an index into the gHUD.m_rghSprites[] array +// returns 0 if sprite not found +int CHud :: GetSpriteIndex( const char *SpriteName ) +{ + // look through the loaded sprite name list for SpriteName + for ( int i = 0; i < m_iSpriteCount; i++ ) + { + if ( strncmp( SpriteName, m_rgszSpriteNames + (i * MAX_SPRITE_NAME_LENGTH), MAX_SPRITE_NAME_LENGTH ) == 0 ) + return i; + } + + return -1; // invalid sprite +} + +void CHud :: VidInit( void ) +{ + m_scrinfo.iSize = sizeof(m_scrinfo); + GetScreenInfo(&m_scrinfo); + + // ---------- + // Load Sprites + // --------- +// m_hsprFont = LoadSprite("sprites/%d_font.spr"); + + m_hsprLogo = 0; + m_hsprCursor = 0; + + if (ScreenWidth < 640) + m_iRes = 320; + else + m_iRes = 640; + + // Only load this once + if ( !m_pSpriteList ) + { + // we need to load the hud.txt, and all sprites within + m_pSpriteList = SPR_GetList("sprites/hud.txt", &m_iSpriteCountAllRes); + + if (m_pSpriteList) + { + // count the number of sprites of the appropriate res + m_iSpriteCount = 0; + client_sprite_t *p = m_pSpriteList; + for ( int j = 0; j < m_iSpriteCountAllRes; j++ ) + { + if ( p->iRes == m_iRes ) + m_iSpriteCount++; + p++; + } + + // allocated memory for sprite handle arrays + m_rghSprites = new HSPRITE[m_iSpriteCount]; + m_rgrcRects = new wrect_t[m_iSpriteCount]; + m_rgszSpriteNames = new char[m_iSpriteCount * MAX_SPRITE_NAME_LENGTH]; + + p = m_pSpriteList; + int index = 0; + for ( int j = 0; j < m_iSpriteCountAllRes; j++ ) + { + if ( p->iRes == m_iRes ) + { + char sz[256]; + sprintf(sz, "sprites/%s.spr", p->szSprite); + m_rghSprites[index] = SPR_Load(sz); + m_rgrcRects[index] = p->rc; + strncpy( &m_rgszSpriteNames[index * MAX_SPRITE_NAME_LENGTH], p->szName, MAX_SPRITE_NAME_LENGTH ); + + index++; + } + + p++; + } + } + } + else + { + // we have already have loaded the sprite reference from hud.txt, but + // we need to make sure all the sprites have been loaded (we've gone through a transition, or loaded a save game) + client_sprite_t *p = m_pSpriteList; + int index = 0; + for ( int j = 0; j < m_iSpriteCountAllRes; j++ ) + { + if ( p->iRes == m_iRes ) + { + char sz[256]; + sprintf( sz, "sprites/%s.spr", p->szSprite ); + m_rghSprites[index] = SPR_Load(sz); + index++; + } + + p++; + } + } + + // assumption: number_1, number_2, etc, are all listed and loaded sequentially + m_HUD_number_0 = GetSpriteIndex( "number_0" ); + + m_iFontHeight = m_rgrcRects[m_HUD_number_0].bottom - m_rgrcRects[m_HUD_number_0].top; + + m_Ammo.VidInit(); + m_Health.VidInit(); + m_Geiger.VidInit(); + m_Train.VidInit(); + m_Battery.VidInit(); + m_Flash.VidInit(); + m_Message.VidInit(); + m_StatusBar.VidInit(); + m_DeathNotice.VidInit(); + m_SayText.VidInit(); + m_Menu.VidInit(); + m_AmmoSecondary.VidInit(); + m_TextMessage.VidInit(); + m_StatusIcons.VidInit(); + + GetClientVoiceMgr()->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; +} + +float g_lastFOV = 0.0; + +/* +============ +COM_FileBase +============ +*/ +// Extracts the base name of a file (no path, no extension, assumes '/' as path separator) +void COM_FileBase ( const char *in, char *out) +{ + int len, start, end; + + len = strlen( in ); + + // scan backward for '.' + end = len - 1; + while ( end && in[end] != '.' && in[end] != '/' && in[end] != '\\' ) + end--; + + if ( in[end] != '.' ) // no '.', copy to end + end = len-1; + else + end--; // Found ',', copy to left of '.' + + + // Scan backward for '/' + start = len-1; + while ( start >= 0 && in[start] != '/' && in[start] != '\\' ) + start--; + + if ( in[start] != '/' && in[start] != '\\' ) + start = 0; + else + start++; + + // Length of new sting + len = end - start + 1; + + // Copy partial string + strncpy( out, &in[start], len ); + // Terminate it + out[len] = 0; +} + +/* +================= +HUD_IsGame + +================= +*/ +int HUD_IsGame( const char *game ) +{ + const char *gamedir; + char gd[ 1024 ]; + + gamedir = gEngfuncs.pfnGetGameDirectory(); + if ( gamedir && gamedir[0] ) + { + COM_FileBase( gamedir, gd ); + if ( !stricmp( gd, game ) ) + return 1; + } + return 0; +} + +/* +===================== +HUD_GetFOV + +Returns last FOV +===================== +*/ +float HUD_GetFOV( void ) +{ +/* + if ( gEngfuncs.pDemoAPI->IsRecording() ) + { + // Write it + int i = 0; + unsigned char buf[ 100 ]; + + // Active + *( float * )&buf[ i ] = g_lastFOV; + i += sizeof( float ); + + Demo_WriteBuffer( TYPE_ZOOM, i, buf ); + } + + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + { + g_lastFOV = g_demozoom; + } +*/ + return g_lastFOV; +} + +int CHud::MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + int newfov = READ_BYTE(); + int def_fov = CVAR_GET_FLOAT( "default_fov" ); + + if ( newfov == 0 ) + { + m_iFOV = def_fov; + } + else + { + m_iFOV = newfov; + } + + // the clients fov is actually set in the client data update section of the hud + + // Set a new sensitivity + if ( m_iFOV == def_fov ) + { + // reset to saved sensitivity + m_flMouseSensitivity = 0; + } + else + { + // set a new sensitivity that is proportional to the change from the FOV default + m_flMouseSensitivity = sensitivity->value * ((float)newfov / (float)def_fov) * CVAR_GET_FLOAT("zoom_sensitivity_ratio"); + } + + return 1; +} + + +void CHud::AddHudElem(CHudBase *phudelem) +{ + HUDLIST *pdl, *ptemp; + +//phudelem->Think(); + + if (!phudelem) + return; + + pdl = (HUDLIST *)malloc(sizeof(HUDLIST)); + if (!pdl) + return; + + memset(pdl, 0, sizeof(HUDLIST)); + pdl->p = phudelem; + + if (!m_pHudList) + { + m_pHudList = pdl; + return; + } + + ptemp = m_pHudList; + + while (ptemp->pNext) + ptemp = ptemp->pNext; + + ptemp->pNext = pdl; +} + +float CHud::GetSensitivity( void ) +{ + return m_flMouseSensitivity; +} \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/hud.h b/hlsdk/ricochet/cl_dll/hud.h new file mode 100644 index 00000000..6efe2347 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/hud.h @@ -0,0 +1,583 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// +// hud.h +// +// class CHud declaration +// +// CHud handles the message, calculation, and drawing the HUD +// + + +#define RGB_YELLOWISH 0x00FFA000 //255,160,0 +#define RGB_REDISH 0x00FF1010 //255,160,0 +#define RGB_GREENISH 0x0000A000 //0,160,0 + +#include "wrect.h" +#include "cl_dll.h" +#include "ammo.h" + +#define DHN_DRAWZERO 1 +#define DHN_2DIGITS 2 +#define DHN_3DIGITS 4 +#define MIN_ALPHA 100 + +#define HUDELEM_ACTIVE 1 + +typedef struct { + int x, y; +} POSITION; + +typedef struct { + unsigned char r,g,b,a; +} RGBA; + + +#define HUD_ACTIVE 1 +#define HUD_INTERMISSION 2 + +#define MAX_PLAYER_NAME_LENGTH 32 +#define MAX_MOTD_LENGTH 1024 + +// +//----------------------------------------------------- +// +class CHudBase +{ +public: + POSITION m_pos; + int m_type; + int m_iFlags; // active, moving, + virtual int Init( void ) {return 0;} + virtual int VidInit( void ) {return 0;} + virtual int Draw(float flTime) {return 0;} + virtual void Think(void) {return;} + virtual void Reset(void) {return;} + virtual void InitHUDData( void ) {} // called every time a server is connected to + +}; + +struct HUDLIST { + CHudBase *p; + HUDLIST *pNext; +}; + +#include "voice_status.h" + +// +//----------------------------------------------------- +// +class CHudAmmo: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + void Think(void); + void Reset(void); + int DrawWList(float flTime); + int MsgFunc_CurWeapon(const char *pszName, int iSize, void *pbuf); + int MsgFunc_WeaponList(const char *pszName, int iSize, void *pbuf); + int MsgFunc_AmmoX(const char *pszName, int iSize, void *pbuf); + int MsgFunc_AmmoPickup( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_WeapPickup( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_ItemPickup( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_HideWeapon( const char *pszName, int iSize, void *pbuf ); + + void _cdecl UserCmd_Slot1( void ); + void _cdecl UserCmd_Slot2( void ); + void _cdecl UserCmd_Slot3( void ); + void _cdecl UserCmd_Slot4( void ); + void _cdecl UserCmd_Slot5( void ); + void _cdecl UserCmd_Slot6( void ); + void _cdecl UserCmd_Slot7( void ); + void _cdecl UserCmd_Slot8( void ); + void _cdecl UserCmd_Slot9( void ); + void _cdecl UserCmd_Slot10( void ); + void _cdecl UserCmd_Close( void ); + void _cdecl UserCmd_NextWeapon( void ); + void _cdecl UserCmd_PrevWeapon( void ); + +private: + float m_fFade; + RGBA m_rgba; + WEAPON *m_pWeapon; + int m_HUD_bucket0; + int m_HUD_selection; + +}; + +// +//----------------------------------------------------- +// + +class CHudAmmoSecondary: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + void Reset( void ); + int Draw(float flTime); + + int MsgFunc_SecAmmoVal( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_SecAmmoIcon( const char *pszName, int iSize, void *pbuf ); + +private: + enum { + MAX_SEC_AMMO_VALUES = 4 + }; + + int m_HUD_ammoicon; // sprite indices + int m_iAmmoAmounts[MAX_SEC_AMMO_VALUES]; + float m_fFade; +}; + + +#include "health.h" + + +#define FADE_TIME 100 + + +// +//----------------------------------------------------- +// +class CHudGeiger: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_Geiger(const char *pszName, int iSize, void *pbuf); + +private: + int m_iGeigerRange; + +}; + +// +//----------------------------------------------------- +// +class CHudTrain: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_Train(const char *pszName, int iSize, void *pbuf); + +private: + HSPRITE m_hSprite; + int m_iPos; + +}; + +// +//----------------------------------------------------- +// +class CHudStatusBar : public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw( float flTime ); + void Reset( void ); + void ParseStatusString( int line_num ); + + int MsgFunc_StatusText( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_StatusValue( const char *pszName, int iSize, void *pbuf ); + +protected: + enum { + MAX_STATUSTEXT_LENGTH = 128, + MAX_STATUSBAR_VALUES = 8, + MAX_STATUSBAR_LINES = 2, + }; + + char m_szStatusText[MAX_STATUSBAR_LINES][MAX_STATUSTEXT_LENGTH]; // a text string describing how the status bar is to be drawn + char m_szStatusBar[MAX_STATUSBAR_LINES][MAX_STATUSTEXT_LENGTH]; // the constructed bar that is drawn + int m_iStatusValues[MAX_STATUSBAR_VALUES]; // an array of values for use in the status bar + + int m_bReparseString; // set to TRUE whenever the m_szStatusBar needs to be recalculated +}; + +// +//----------------------------------------------------- +// +enum +{ + MAX_PLAYERS = 64, + MAX_TEAMS = 64, + MAX_TEAM_NAME = 16, +}; + +struct extra_player_info_t +{ + short frags; + short deaths; + short playerclass; + short teamnumber; + char teamname[MAX_TEAM_NAME]; +}; + +struct team_info_t +{ + char name[MAX_TEAM_NAME]; + short frags; + short deaths; + short ping; + short packetloss; + short ownteam; + short players; + int already_drawn; + int scores_overriden; + int teamnumber; +}; + +extern hud_player_info_t g_PlayerInfoList[MAX_PLAYERS+1]; // player info from the engine +extern extra_player_info_t g_PlayerExtraInfo[MAX_PLAYERS+1]; // additional player info sent directly to the client dll +extern team_info_t g_TeamInfo[MAX_TEAMS+1]; +extern int g_IsSpectator[MAX_PLAYERS+1]; + + +// +//----------------------------------------------------- +// +class CHudDeathNotice : public CHudBase +{ +public: + int Init( void ); + void InitHUDData( void ); + int VidInit( void ); + int Draw( float flTime ); + int MsgFunc_DeathMsg( const char *pszName, int iSize, void *pbuf ); + +private: + int m_HUD_d_skull; // sprite index of skull icon +}; + +// +//----------------------------------------------------- +// +class CHudMenu : public CHudBase +{ +public: + int Init( void ); + void InitHUDData( void ); + int VidInit( void ); + void Reset( void ); + int Draw( float flTime ); + int MsgFunc_ShowMenu( const char *pszName, int iSize, void *pbuf ); + + void SelectMenuItem( int menu_item ); + + int m_fMenuDisplayed; + int m_bitsValidSlots; + float m_flShutoffTime; + int m_fWaitingForMore; +}; + +// +//----------------------------------------------------- +// +class CHudSayText : public CHudBase +{ +public: + int Init( void ); + void InitHUDData( void ); + int VidInit( void ); + int Draw( float flTime ); + int MsgFunc_SayText( const char *pszName, int iSize, void *pbuf ); + void SayTextPrint( const char *pszBuf, int iBufSize, int clientIndex = -1 ); + 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 +}; + + +// +//----------------------------------------------------- +// +class CHudFlashlight: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + void Reset( void ); + int MsgFunc_Flashlight(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_FlashBat(const char *pszName, int iSize, void *pbuf ); + +private: + HSPRITE m_hSprite1; + HSPRITE m_hSprite2; + HSPRITE m_hBeam; + wrect_t *m_prc1; + wrect_t *m_prc2; + wrect_t *m_prcBeam; + float m_flBat; + int m_iBat; + int m_fOn; + float m_fFade; + int m_iWidth; // width of the battery innards +}; + +// +//----------------------------------------------------- +// +const int maxHUDMessages = 16; +struct message_parms_t +{ + client_textmessage_t *pMessage; + float time; + int x, y; + int totalWidth, totalHeight; + int width; + int lines; + int lineLength; + int length; + int r, g, b; + int text; + int fadeBlend; + float charTime; + float fadeTime; +}; + +// +//----------------------------------------------------- +// + +class CHudTextMessage: public CHudBase +{ +public: + int Init( void ); + static char *LocaliseTextString( const char *msg, char *dst_buffer, int buffer_size ); + static char *BufferedLocaliseTextString( const char *msg ); + char *LookupString( const char *msg_name, int *msg_dest = NULL ); + int MsgFunc_TextMsg(const char *pszName, int iSize, void *pbuf); +}; + +// +//----------------------------------------------------- +// + +class CHudMessage: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_HudText(const char *pszName, int iSize, void *pbuf); + int MsgFunc_GameTitle(const char *pszName, int iSize, void *pbuf); + + float FadeBlend( float fadein, float fadeout, float hold, float localTime ); + int XPosition( float x, int width, int lineWidth ); + int YPosition( float y, int height ); + + void MessageAdd( const char *pName, float time ); + void MessageDrawScan( client_textmessage_t *pMessage, float time ); + void MessageScanStart( void ); + void MessageScanNextChar( void ); + void Reset( void ); + +private: + client_textmessage_t *m_pMessages[maxHUDMessages]; + float m_startTime[maxHUDMessages]; + message_parms_t m_parms; + float m_gameTitleTime; + client_textmessage_t *m_pGameTitle; + + int m_HUD_title_life; + int m_HUD_title_half; +}; + +// +//----------------------------------------------------- +// +#define MAX_SPRITE_NAME_LENGTH 24 + +class CHudStatusIcons: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + void Reset( void ); + int Draw(float flTime); + int MsgFunc_StatusIcon(const char *pszName, int iSize, void *pbuf); + + enum { + MAX_ICONSPRITENAME_LENGTH = MAX_SPRITE_NAME_LENGTH, + MAX_ICONSPRITES = 4, + }; + + + //had to make these public so CHud could access them (to enable concussion icon) + //could use a friend declaration instead... + void EnableIcon( char *pszIconName, unsigned char red, unsigned char green, unsigned char blue ); + void DisableIcon( char *pszIconName ); + +private: + + typedef struct + { + char szSpriteName[MAX_ICONSPRITENAME_LENGTH]; + HSPRITE spr; + wrect_t rc; + unsigned char r, g, b; + } icon_sprite_t; + + icon_sprite_t m_IconList[MAX_ICONSPRITES]; + +}; + + +// +//----------------------------------------------------- +// +typedef struct cvar_s cvar_t; + +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; + float m_flTime; // the current client time + float m_fOldTime; // the time at which the HUD was last redrawn + double m_flTimeDelta; // the difference between flTime and fOldTime + Vector m_vecOrigin; + Vector m_vecAngles; + int m_iKeyBits; + int m_iHideHUDDisplay; + int m_iFOV; + int m_Teamplay; + int m_iRes; + cvar_t *m_pCvarStealMouse; + + int m_iFontHeight; + int DrawHudNumber(int x, int y, int iFlags, int iNumber, int r, int g, int b ); + int DrawHudString(int x, int y, int iMaxX, char *szString, int r, int g, int b ); + int DrawHudStringReverse( int xpos, int ypos, int iMinX, char *szString, int r, int g, int b ); + int DrawHudNumberString( int xpos, int ypos, int iMinX, int iNumber, int r, int g, int b ); + int GetNumWidth(int iNumber, int iFlags); + +private: + // the memory for these arrays are allocated in the first call to CHud::VidInit(), when the hud.txt and associated sprites are loaded. + // freed in ~CHud() + HSPRITE *m_rghSprites; /*[HUD_SPRITE_COUNT]*/ // the sprites loaded from hud.txt + wrect_t *m_rgrcRects; /*[HUD_SPRITE_COUNT]*/ + char *m_rgszSpriteNames; /*[HUD_SPRITE_COUNT][MAX_SPRITE_NAME_LENGTH]*/ + + struct cvar_s *default_fov; +public: + HSPRITE GetSprite( int index ) + { + return (index < 0) ? 0 : m_rghSprites[index]; + } + + wrect_t& GetSpriteRect( int index ) + { + return m_rgrcRects[index]; + } + + + 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; + CHudAmmoSecondary m_AmmoSecondary; + CHudTextMessage m_TextMessage; + CHudStatusIcons m_StatusIcons; + + void Init( void ); + void VidInit( void ); + void Think(void); + int Redraw( float flTime, int intermission ); + int UpdateClientData( client_data_t *cdata, float time ); + + CHud() : m_iSpriteCount(0), m_pHudList(NULL) {} + ~CHud(); // destructor, frees allocated memory + + // 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); + int _cdecl MsgFunc_Concuss( const char *pszName, int iSize, void *pbuf ); + // Screen information + SCREENINFO m_scrinfo; + + int m_iWeaponBits; + int m_fPlayerDead; + int m_iIntermission; + + // sprite indexes + int m_HUD_number_0; + + + void AddHudElem(CHudBase *p); + + float GetSensitivity(); +}; + +extern CHud gHUD; + +class TeamFortressViewport; + +extern TeamFortressViewport *gViewPort; + +extern int g_iPlayerClass; +extern int g_iTeamNumber; +extern int g_iUser1; +extern int g_iUser2; diff --git a/hlsdk/ricochet/cl_dll/hud_iface.h b/hlsdk/ricochet/cl_dll/hud_iface.h new file mode 100644 index 00000000..e0279481 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/hud_iface.h @@ -0,0 +1,27 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +#if !defined( HUD_IFACEH ) +#define HUD_IFACEH +#pragma once + +#define EXPORT _declspec( dllexport ) +#define _DLLEXPORT __declspec( dllexport ) + +typedef int (*pfnUserMsgHook)(const char *pszName, int iSize, void *pbuf); +#include "wrect.h" +#include "../engine/cdll_int.h" +extern cl_enginefunc_t gEngfuncs; + +#endif \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/hud_msg.cpp b/hlsdk/ricochet/cl_dll/hud_msg.cpp new file mode 100644 index 00000000..0c83bd3c --- /dev/null +++ b/hlsdk/ricochet/cl_dll/hud_msg.cpp @@ -0,0 +1,117 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// +// hud_msg.cpp +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include "vgui_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_discobjects.h" + +/// USER-DEFINED SERVER MESSAGE HANDLERS + +int g_iArenaMode = 0; + +int CHud :: MsgFunc_ResetHUD(const char *pszName, int iSize, void *pbuf ) +{ + ASSERT( iSize == 0 ); + + // clear all hud data + HUDLIST *pList = m_pHudList; + + while ( pList ) + { + if ( pList->p ) + pList->p->Reset(); + pList = pList->pNext; + } + + // reset sensitivity + m_flMouseSensitivity = 0; + + // reset concussion effect + m_iConcussionEffect = 0; + + return 1; +} + +void CHud :: MsgFunc_InitHUD( const char *pszName, int iSize, void *pbuf ) +{ + // prepare all hud data + HUDLIST *pList = m_pHudList; + + while (pList) + { + if ( pList->p ) + pList->p->InitHUDData(); + pList = pList->pNext; + } +} + + +int CHud :: MsgFunc_GameMode(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + g_iArenaMode = READ_BYTE(); + + if (gViewPort) + { + gViewPort->m_pDiscEndRound->setVisible( false ); + gViewPort->m_pDiscStartRound->setVisible ( false ); + } + + return 1; +} + + +int CHud :: MsgFunc_Damage(const char *pszName, int iSize, void *pbuf ) +{ + int armor, blood; + Vector from; + int i; + float count; + + BEGIN_READ( pbuf, iSize ); + armor = READ_BYTE(); + blood = READ_BYTE(); + + for (i=0 ; i<3 ; i++) + from[i] = READ_COORD(); + + count = (blood * 0.5) + (armor * 0.5); + + if (count < 10) + count = 10; + + // TODO: kick viewangles, show damage visually + + return 1; +} + +int CHud :: MsgFunc_Concuss( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + m_iConcussionEffect = READ_BYTE(); + if (m_iConcussionEffect) + this->m_StatusIcons.EnableIcon("dmg_concuss",255,160,0); + else + this->m_StatusIcons.DisableIcon("dmg_concuss"); + return 1; +} + diff --git a/hlsdk/ricochet/cl_dll/hud_redraw.cpp b/hlsdk/ricochet/cl_dll/hud_redraw.cpp new file mode 100644 index 00000000..977fa4eb --- /dev/null +++ b/hlsdk/ricochet/cl_dll/hud_redraw.cpp @@ -0,0 +1,261 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// +// hud_redraw.cpp +// +#include +#include "hud.h" +#include "cl_util.h" + + +#define MAX_LOGO_FRAMES 56 + +int grgLogoFrame[MAX_LOGO_FRAMES] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 13, 13, 13, 13, 13, 12, 11, 10, 9, 8, 14, 15, + 16, 17, 18, 19, 20, 20, 20, 20, 20, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 29, 29, 29, 29, 29, 28, 27, 26, 25, 24, 30, 31 +}; + +extern int g_iVisibleMouse; + +// Think +void CHud::Think(void) +{ + HUDLIST *pList = m_pHudList; + while (pList) + { + if (pList->p->m_iFlags & HUD_ACTIVE) + pList->p->Think(); + pList = pList->pNext; + } + + // think about default fov + if ( m_iFOV == 0 ) + { // only let players adjust up in fov, and only if they are not overriden by something else + m_iFOV = max( default_fov->value, 90 ); + } +} + +// Redraw +// step through the local data, placing the appropriate graphics & text as appropriate +// returns 1 if they've changed, 0 otherwise +int CHud :: Redraw( float flTime, int intermission ) +{ + m_fOldTime = m_flTime; // save time of previous redraw + m_flTime = flTime; + m_flTimeDelta = (double)m_flTime - m_fOldTime; + + // Clock was reset, reset delta + if ( m_flTimeDelta < 0 ) + m_flTimeDelta = 0; + + m_iIntermission = intermission; + + // if no redrawing is necessary + // return 0; + + HUDLIST *pList = m_pHudList; + + while (pList) + { + if ( !intermission ) + { + if ((pList->p->m_iFlags & HUD_ACTIVE) && !(m_iHideHUDDisplay & HIDEHUD_ALL)) + pList->p->Draw(flTime); + } + else + { // it's an intermission, so only draw hud elements that are set to draw during intermissions + if ( pList->p->m_iFlags & HUD_INTERMISSION ) + pList->p->Draw( flTime ); + } + + 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); + } + + return 1; +} + +void ScaleColors( int &r, int &g, int &b, int a ) +{ + float x = (float)a / 255; + r = (int)(r * x); + g = (int)(g * x); + b = (int)(b * x); +} + +int CHud :: DrawHudString(int xpos, int ypos, int iMaxX, char *szIt, int r, int g, int b ) +{ + // draw the string until we hit the null character or a newline character + for ( ; *szIt != 0 && *szIt != '\n'; szIt++ ) + { + int next = xpos + gHUD.m_scrinfo.charWidths[ *szIt ]; // variable-width fonts look cool + if ( next > iMaxX ) + return xpos; + + TextMessageDrawChar( xpos, ypos, *szIt, r, g, b ); + xpos = next; + } + + return xpos; +} + +int CHud :: DrawHudNumberString( int xpos, int ypos, int iMinX, int iNumber, int r, int g, int b ) +{ + char szString[32]; + sprintf( szString, "%d", iNumber ); + return DrawHudStringReverse( xpos, ypos, iMinX, szString, r, g, b ); + +} + +// draws a string from right to left (right-aligned) +int CHud :: DrawHudStringReverse( int xpos, int ypos, int iMinX, char *szString, int r, int g, int b ) +{ + // find the end of the string + char *szIt = NULL; + for ( szIt = szString; *szIt != 0; szIt++ ) + { // we should count the length? + } + + // iterate throug the string in reverse + for ( szIt--; szIt != (szString-1); szIt-- ) + { + int next = xpos - gHUD.m_scrinfo.charWidths[ *szIt ]; // variable-width fonts look cool + if ( next < iMinX ) + return xpos; + xpos = next; + + TextMessageDrawChar( xpos, ypos, *szIt, r, g, b ); + } + + return xpos; +} + +int CHud :: DrawHudNumber( int x, int y, int iFlags, int iNumber, int r, int g, int b) +{ + int iWidth = GetSpriteRect(m_HUD_number_0).right - GetSpriteRect(m_HUD_number_0).left; + int k; + + if (iNumber > 0) + { + // SPR_Draw 100's + if (iNumber >= 100) + { + k = iNumber/100; + SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); + SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); + x += iWidth; + } + else if (iFlags & (DHN_3DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + // SPR_Draw 10's + if (iNumber >= 10) + { + k = (iNumber % 100)/10; + SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); + SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); + x += iWidth; + } + else if (iFlags & (DHN_3DIGITS | DHN_2DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + // SPR_Draw ones + k = iNumber % 10; + SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); + SPR_DrawAdditive(0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); + x += iWidth; + } + else if (iFlags & DHN_DRAWZERO) + { + SPR_Set(GetSprite(m_HUD_number_0), r, g, b ); + + // SPR_Draw 100's + if (iFlags & (DHN_3DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + if (iFlags & (DHN_3DIGITS | DHN_2DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + // SPR_Draw ones + + SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0)); + x += iWidth; + } + + return x; +} + + +int CHud::GetNumWidth( int iNumber, int iFlags ) +{ + if (iFlags & (DHN_3DIGITS)) + return 3; + + if (iFlags & (DHN_2DIGITS)) + return 2; + + if (iNumber <= 0) + { + if (iFlags & (DHN_DRAWZERO)) + return 1; + else + return 0; + } + + if (iNumber < 10) + return 1; + + if (iNumber < 100) + return 2; + + return 3; + +} + + diff --git a/hlsdk/ricochet/cl_dll/hud_servers.cpp b/hlsdk/ricochet/cl_dll/hud_servers.cpp new file mode 100644 index 00000000..faa7186f --- /dev/null +++ b/hlsdk/ricochet/cl_dll/hud_servers.cpp @@ -0,0 +1,1242 @@ +// hud_servers.cpp +#include "hud.h" +#include "cl_util.h" +#include "hud_servers_priv.h" +#include "hud_servers.h" +#include "net_api.h" +#include +#include + +static int context_id; + +// Default master server address in case we can't read any from woncomm.lst file +#define VALVE_MASTER_ADDRESS "half-life.east.won.net" +#define PORT_MASTER 27010 +#define PORT_SERVER 27015 + +// File where we really should look for master servers +#define MASTER_PARSE_FILE "woncomm.lst" + +#define MAX_QUERIES 20 + +#define NET_API gEngfuncs.pNetAPI + +static CHudServers *g_pServers = NULL; + +/* +=================== +ListResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK ListResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->ListResponse( response ); + } +} + +/* +=================== +ServerResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK ServerResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->ServerResponse( response ); + } +} + +/* +=================== +PingResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK PingResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->PingResponse( response ); + } +} + +/* +=================== +RulesResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK RulesResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->RulesResponse( response ); + } +} +/* +=================== +PlayersResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK PlayersResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->PlayersResponse( response ); + } +} +/* +=================== +ListResponse + +=================== +*/ +void CHudServers::ListResponse( struct net_response_s *response ) +{ + request_t *list; + request_t *p; + int c = 0; + + if ( !( response->error == NET_SUCCESS ) ) + return; + + if ( response->type != NETAPI_REQUEST_SERVERLIST ) + return; + + if ( response->response ) + { + list = ( request_t * ) response->response; + while ( list ) + { + c++; + + //if ( c < 40 ) + { + // Copy from parsed stuff + p = new request_t; + p->context = -1; + p->remote_address = list->remote_address; + p->next = m_pServerList; + m_pServerList = p; + } + + // Move on + list = list->next; + } + } + + gEngfuncs.Con_Printf( "got list\n" ); + + m_nQuerying = 1; + m_nActiveQueries = 0; +} + +/* +=================== +ServerResponse + +=================== +*/ +void CHudServers::ServerResponse( struct net_response_s *response ) +{ + char *szresponse; + request_t *p; + server_t *browser; + int len; + char sz[ 32 ]; + + // Remove from active list + p = FindRequest( response->context, m_pActiveList ); + if ( p ) + { + static int first = 0; + + RemoveServerFromList( &m_pActiveList, p ); + m_nActiveQueries--; + + if ( !first ) + { + gEngfuncs.Con_Printf( "recv first %f\n", gEngfuncs.GetClientTime() ); + first = 1; + } + } + + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_DETAILS: + if ( response->response ) + { + szresponse = (char *)response->response; + len = strlen( szresponse ) + 100 + 1; + sprintf( sz, "%i", (int)( 1000.0 * response->ping ) ); + + browser = new server_t; + browser->remote_address = response->remote_address; + browser->info = new char[ len ]; + browser->ping = (int)( 1000.0 * response->ping ); + strcpy( browser->info, szresponse ); + + NET_API->SetValueForKey( browser->info, "address", gEngfuncs.pNetAPI->AdrToString( &response->remote_address ), len ); + NET_API->SetValueForKey( browser->info, "ping", sz, len ); + + AddServer( &m_pServers, browser ); + } + break; + default: + break; + } +} + +/* +=================== +PingResponse + +=================== +*/ +void CHudServers::PingResponse( struct net_response_s *response ) +{ + char sz[ 32 ]; + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_PING: + sprintf( sz, "%.2f", 1000.0 * response->ping ); + + gEngfuncs.Con_Printf( "ping == %s\n", sz ); + break; + default: + break; + } +} + +/* +=================== +RulesResponse + +=================== +*/ +void CHudServers::RulesResponse( struct net_response_s *response ) +{ + char *szresponse; + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_RULES: + if ( response->response ) + { + szresponse = (char *)response->response; + + gEngfuncs.Con_Printf( "rules %s\n", szresponse ); + } + break; + default: + break; + } +} + +/* +=================== +PlayersResponse + +=================== +*/ +void CHudServers::PlayersResponse( struct net_response_s *response ) +{ + char *szresponse; + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_PLAYERS: + if ( response->response ) + { + szresponse = (char *)response->response; + + gEngfuncs.Con_Printf( "players %s\n", szresponse ); + } + break; + default: + break; + } +} + +/* +=================== +CompareServers + +Return 1 if p1 is "less than" p2, 0 otherwise +=================== +*/ +int CHudServers::CompareServers( server_t *p1, server_t *p2 ) +{ + const char *n1, *n2; + + if ( p1->ping < p2->ping ) + return 1; + + if ( p1->ping == p2->ping ) + { + // Pings equal, sort by second key: hostname + if ( p1->info && p2->info ) + { + n1 = NET_API->ValueForKey( p1->info, "hostname" ); + n2 = NET_API->ValueForKey( p2->info, "hostname" ); + + if ( n1 && n2 ) + { + if ( stricmp( n1, n2 ) < 0 ) + return 1; + } + } + } + + return 0; +} + +/* +=================== +AddServer + +=================== +*/ +void CHudServers::AddServer( server_t **ppList, server_t *p ) +{ +server_t *list; + + if ( !ppList || ! p ) + return; + + m_nServerCount++; + + // What sort key? Ping? + list = *ppList; + + // Head of list? + if ( !list ) + { + p->next = NULL; + *ppList = p; + return; + } + + // Put on head of list + if ( CompareServers( p, list ) ) + { + p->next = *ppList; + *ppList = p; + } + else + { + while ( list->next ) + { + // Insert before list next + if ( CompareServers( p, list->next ) ) + { + p->next = list->next->next; + list->next = p; + return; + } + + list = list->next; + } + + // Just add at end + p->next = NULL; + list->next = p; + } +} + +/* +=================== +Think + +=================== +*/ +void CHudServers::Think( double time ) +{ + m_fElapsed += time; + + if ( !m_nRequesting ) + return; + + if ( !m_nQuerying ) + return; + + QueryThink(); + + if ( ServerListSize() > 0 ) + return; + + m_dStarted = 0.0; + m_nRequesting = 0; + m_nDone = 0; + m_nQuerying = 0; + m_nActiveQueries = 0; +} + +/* +=================== +QueryThink + +=================== +*/ +void CHudServers::QueryThink( void ) +{ + request_t *p; + + if ( !m_nRequesting || m_nDone ) + return; + + if ( !m_nQuerying ) + return; + + if ( m_nActiveQueries > MAX_QUERIES ) + return; + + // Nothing left + if ( !m_pServerList ) + return; + + while ( 1 ) + { + static int first = 0; + p = m_pServerList; + + // No more in list? + if ( !p ) + break; + + // Move to next + m_pServerList = m_pServerList->next; + + // Setup context_id + p->context = context_id; + + // Make sure networking system has started. + // NET_API->InitNetworking(); + + if ( !first ) + { + gEngfuncs.Con_Printf( "send first %f\n", gEngfuncs.GetClientTime() ); + first = 1; + } + + // Start up query on this one + NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, 0, 2.0, &p->remote_address, ::ServerResponse ); + + // Increment active list + m_nActiveQueries++; + + // Add to active list + p->next = m_pActiveList; + m_pActiveList = p; + + // Too many active? + if ( m_nActiveQueries > MAX_QUERIES ) + break; + } +} + +/* +================== +ServerListSize + +# of servers in active query and in pending to be queried lists +================== +*/ +int CHudServers::ServerListSize( void ) +{ + int c = 0; + request_t *p; + + p = m_pServerList; + while ( p ) + { + c++; + p = p->next; + } + + p = m_pActiveList; + while ( p ) + { + c++; + p = p->next; + } + + return c; +} + +/* +=================== +FindRequest + +Look up a request by context id +=================== +*/ +CHudServers::request_t *CHudServers::FindRequest( int context, request_t *pList ) +{ + request_t *p; + p = pList; + while ( p ) + { + if ( context == p->context ) + return p; + + p = p->next; + } + return NULL; +} + +/* +=================== +RemoveServerFromList + +Remote, but don't delete, item from *ppList +=================== +*/ +void CHudServers::RemoveServerFromList( request_t **ppList, request_t *item ) +{ + request_t *p, *n; + request_t *newlist = NULL; + + if ( !ppList ) + return; + + p = *ppList; + while ( p ) + { + n = p->next; + if ( p != item ) + { + p->next = newlist; + newlist = p; + } + p = n; + } + *ppList = newlist; +} + +/* +=================== +ClearRequestList + +=================== +*/ +void CHudServers::ClearRequestList( request_t **ppList ) +{ + request_t *p, *n; + + if ( !ppList ) + return; + + p = *ppList; + while ( p ) + { + n = p->next; + delete p; + p = n; + } + *ppList = NULL; +} + +/* +=================== +ClearServerList + +=================== +*/ +void CHudServers::ClearServerList( server_t **ppList ) +{ + server_t *p, *n; + + if ( !ppList ) + return; + + p = *ppList; + while ( p ) + { + n = p->next; + delete[] p->info; + delete p; + p = n; + } + *ppList = NULL; +} + +int CompareField( CHudServers::server_t *p1, CHudServers::server_t *p2, const char *fieldname, int iSortOrder ) +{ + const char *sz1, *sz2; + float fv1, fv2; + + sz1 = NET_API->ValueForKey( p1->info, fieldname ); + sz2 = NET_API->ValueForKey( p2->info, fieldname ); + + fv1 = atof( sz1 ); + fv2 = atof( sz2 ); + + if ( fv1 && fv2 ) + { + if ( fv1 > fv2 ) + return iSortOrder; + else if ( fv1 < fv2 ) + return -iSortOrder; + else + return 0; + } + + // String compare + return stricmp( sz1, sz2 ); +} + +int CALLBACK ServerListCompareFunc( CHudServers::server_t *p1, CHudServers::server_t *p2, const char *fieldname ) +{ + if (!p1 || !p2) // No meaningful comparison + return 0; + + int iSortOrder = 1; + + int retval = 0; + + retval = CompareField( p1, p2, fieldname, iSortOrder ); + + return retval; +} + +static char g_fieldname[ 256 ]; +int __cdecl FnServerCompare(const void *elem1, const void *elem2 ) +{ + CHudServers::server_t *list1, *list2; + + list1 = *(CHudServers::server_t **)elem1; + list2 = *(CHudServers::server_t **)elem2; + + return ServerListCompareFunc( list1, list2, g_fieldname ); +} + +void CHudServers::SortServers( const char *fieldname ) +{ + server_t *p; + // Create a list + if ( !m_pServers ) + return; + + strcpy( g_fieldname, fieldname ); + + int i; + int c = 0; + + p = m_pServers; + while ( p ) + { + c++; + p = p->next; + } + + server_t **pSortArray; + + pSortArray = new server_t *[ c ]; + memset( pSortArray, 0, c * sizeof( server_t * ) ); + + // Now copy the list into the pSortArray: + p = m_pServers; + i = 0; + while ( p ) + { + pSortArray[ i++ ] = p; + p = p->next; + } + + // Now do that actual sorting. + size_t nCount = c; + size_t nSize = sizeof( server_t * ); + + qsort( + pSortArray, + (size_t)nCount, + (size_t)nSize, + FnServerCompare + ); + + // Now rebuild the list. + m_pServers = pSortArray[0]; + for ( i = 0; i < c - 1; i++ ) + { + pSortArray[ i ]->next = pSortArray[ i + 1 ]; + } + pSortArray[ c - 1 ]->next = NULL; + + // Clean Up. + delete[] pSortArray; +} + +/* +=================== +GetServer + +Return particular server +=================== +*/ +CHudServers::server_t *CHudServers::GetServer( int server ) +{ + int c = 0; + server_t *p; + + p = m_pServers; + while ( p ) + { + if ( c == server ) + return p; + + c++; + p = p->next; + } + return NULL; +} + +/* +=================== +GetServerInfo + +Return info ( key/value ) string for particular server +=================== +*/ +char *CHudServers::GetServerInfo( int server ) +{ + server_t *p = GetServer( server ); + if ( p ) + { + return p->info; + } + return NULL; +} + +/* +=================== +CancelRequest + +Kill all pending requests in engine +=================== +*/ +void CHudServers::CancelRequest( void ) +{ + m_nRequesting = 0; + m_nQuerying = 0; + m_nDone = 1; + + NET_API->CancelAllRequests(); +} + +/* +================== +LoadMasterAddresses + +Loads the master server addresses from file and into the passed in array +================== +*/ +int CHudServers::LoadMasterAddresses( int maxservers, int *count, netadr_t *padr ) +{ + int i; + char szMaster[ 256 ]; + char szMasterFile[256]; + char *pbuffer = NULL; + char *pstart = NULL ; + netadr_t adr; + char szAdr[64]; + int nPort; + int nCount = 0; + bool bIgnore; + int nDefaultPort; + + // Assume default master and master file + strcpy( szMaster, VALVE_MASTER_ADDRESS ); // IP:PORT string + strcpy( szMasterFile, MASTER_PARSE_FILE ); + + // See if there is a command line override + i = gEngfuncs.CheckParm( "-comm", &pstart ); + if ( i && pstart ) + { + strcpy (szMasterFile, pstart ); + } + + // Read them in from proper file + pbuffer = (char *)gEngfuncs.COM_LoadFile( szMasterFile, 5, NULL ); // Use malloc + if ( !pbuffer ) + { + goto finish_master; + } + + pstart = pbuffer; + + while ( nCount < maxservers ) + { + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if ( strlen(m_szToken) <= 0) + break; + + bIgnore = true; + + if ( !stricmp( m_szToken, "Master" ) ) + { + nDefaultPort = PORT_MASTER; + bIgnore = FALSE; + } + + // Now parse all addresses between { } + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + if ( strlen(m_szToken) <= 0 ) + break; + + if ( stricmp ( m_szToken, "{" ) ) + break; + + // Parse addresses until we get to "}" + while ( nCount < maxservers ) + { + char base[256]; + + // Now parse all addresses between { } + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if (strlen(m_szToken) <= 0) + break; + + if ( !stricmp ( m_szToken, "}" ) ) + break; + + sprintf( base, "%s", m_szToken ); + + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if (strlen(m_szToken) <= 0) + break; + + if ( stricmp( m_szToken, ":" ) ) + break; + + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if (strlen(m_szToken) <= 0) + break; + + nPort = atoi ( m_szToken ); + if ( !nPort ) + nPort = nDefaultPort; + + sprintf( szAdr, "%s:%i", base, nPort ); + + // Can we resolve it any better + if ( !NET_API->StringToAdr( szAdr, &adr ) ) + bIgnore = true; + + if ( !bIgnore ) + { + padr[ nCount++ ] = adr; + } + } + } + +finish_master: + if ( !nCount ) + { + sprintf( szMaster, VALVE_MASTER_ADDRESS ); // IP:PORT string + + // Convert to netadr_t + if ( NET_API->StringToAdr ( szMaster, &adr ) ) + { + + padr[ nCount++ ] = adr; + } + } + + *count = nCount; + + if ( pbuffer ) + { + gEngfuncs.COM_FreeFile( pbuffer ); + } + + return ( nCount > 0 ) ? 1 : 0; +} + +/* +=================== +RequestList + +Request list of game servers from master +=================== +*/ +void CHudServers::RequestList( void ) +{ + m_nRequesting = 1; + m_nDone = 0; + m_dStarted = m_fElapsed; + + int count = 0; + netadr_t adr; + + if ( !LoadMasterAddresses( 1, &count, &adr ) ) + { + gEngfuncs.Con_DPrintf( "SendRequest: Unable to read master server addresses\n" ); + return; + } + + ClearRequestList( &m_pActiveList ); + ClearRequestList( &m_pServerList ); + ClearServerList( &m_pServers ); + + m_nServerCount = 0; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Kill off left overs if any + NET_API->CancelAllRequests(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_SERVERLIST, 0, 5.0, &adr, ::ListResponse ); +} + +void CHudServers::RequestBroadcastList( int clearpending ) +{ + m_nRequesting = 1; + m_nDone = 0; + m_dStarted = m_fElapsed; + + netadr_t adr; + memset( &adr, 0, sizeof( adr ) ); + + if ( clearpending ) + { + ClearRequestList( &m_pActiveList ); + ClearRequestList( &m_pServerList ); + ClearServerList( &m_pServers ); + + m_nServerCount = 0; + } + + // Make sure to byte swap server if necessary ( using "host" to "net" conversion + adr.port = htons( PORT_SERVER ); + + // Make sure networking system has started. + NET_API->InitNetworking(); + + if ( clearpending ) + { + // Kill off left overs if any + NET_API->CancelAllRequests(); + } + + adr.type = NA_BROADCAST; + + // Request Servers from LAN via IP + NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, FNETAPI_MULTIPLE_RESPONSE, 5.0, &adr, ::ServerResponse ); + + adr.type = NA_BROADCAST_IPX; + + // Request Servers from LAN via IPX ( if supported ) + NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, FNETAPI_MULTIPLE_RESPONSE, 5.0, &adr, ::ServerResponse ); +} + +void CHudServers::ServerPing( int server ) +{ + server_t *p; + + p = GetServer( server ); + if ( !p ) + return; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_PING, 0, 5.0, &p->remote_address, ::PingResponse ); +} + +void CHudServers::ServerRules( int server ) +{ + server_t *p; + + p = GetServer( server ); + if ( !p ) + return; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_RULES, 0, 5.0, &p->remote_address, ::RulesResponse ); +} + +void CHudServers::ServerPlayers( int server ) +{ + server_t *p; + + p = GetServer( server ); + if ( !p ) + return; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_PLAYERS, 0, 5.0, &p->remote_address, ::PlayersResponse ); +} + +int CHudServers::isQuerying() +{ + return m_nRequesting ? 1 : 0; +} + + +/* +=================== +GetServerCount + +Return number of servers in browser list +=================== +*/ +int CHudServers::GetServerCount( void ) +{ + return m_nServerCount; +} + +/* +=================== +CHudServers + +=================== +*/ +CHudServers::CHudServers( void ) +{ + m_nRequesting = 0; + m_dStarted = 0.0; + m_nDone = 0; + m_pServerList = NULL; + m_pServers = NULL; + m_pActiveList = NULL; + m_nQuerying = 0; + m_nActiveQueries = 0; + + m_fElapsed = 0.0; + + + m_pPingRequest = NULL; + m_pRulesRequest = NULL; + m_pPlayersRequest = NULL; +} + +/* +=================== +~CHudServers + +=================== +*/ +CHudServers::~CHudServers( void ) +{ + ClearRequestList( &m_pActiveList ); + ClearRequestList( &m_pServerList ); + ClearServerList( &m_pServers ); + + m_nServerCount = 0; + + if ( m_pPingRequest ) + { + delete m_pPingRequest; + m_pPingRequest = NULL; + + } + + if ( m_pRulesRequest ) + { + delete m_pRulesRequest; + m_pRulesRequest = NULL; + } + + if ( m_pPlayersRequest ) + { + delete m_pPlayersRequest; + m_pPlayersRequest = NULL; + } +} + +/////////////////////////////// +// +// PUBLIC APIs +// +/////////////////////////////// + +/* +=================== +ServersGetCount + +=================== +*/ +int ServersGetCount( void ) +{ + if ( g_pServers ) + { + return g_pServers->GetServerCount(); + } + return 0; +} + +int ServersIsQuerying( void ) +{ + if ( g_pServers ) + { + return g_pServers->isQuerying(); + } + return 0; +} + +/* +=================== +ServersGetInfo + +=================== +*/ +const char *ServersGetInfo( int server ) +{ + if ( g_pServers ) + { + return g_pServers->GetServerInfo( server ); + } + + return NULL; +} + +void SortServers( const char *fieldname ) +{ + if ( g_pServers ) + { + g_pServers->SortServers( fieldname ); + } +} + +/* +=================== +ServersShutdown + +=================== +*/ +void ServersShutdown( void ) +{ + if ( g_pServers ) + { + delete g_pServers; + g_pServers = NULL; + } +} + +/* +=================== +ServersInit + +=================== +*/ +void ServersInit( void ) +{ + // Kill any previous instance + ServersShutdown(); + + g_pServers = new CHudServers(); +} + +/* +=================== +ServersThink + +=================== +*/ +void ServersThink( double time ) +{ + if ( g_pServers ) + { + g_pServers->Think( time ); + } +} + +/* +=================== +ServersCancel + +=================== +*/ +void ServersCancel( void ) +{ + if ( g_pServers ) + { + g_pServers->CancelRequest(); + } +} + +// Requests +/* +=================== +ServersList + +=================== +*/ +void ServersList( void ) +{ + if ( g_pServers ) + { + g_pServers->RequestList(); + } +} + +void BroadcastServersList( int clearpending ) +{ + if ( g_pServers ) + { + g_pServers->RequestBroadcastList( clearpending ); + } +} + +void ServerPing( int server ) +{ + if ( g_pServers ) + { + g_pServers->ServerPing( server ); + } +} + +void ServerRules( int server ) +{ + if ( g_pServers ) + { + g_pServers->ServerRules( server ); + } +} + +void ServerPlayers( int server ) +{ + if ( g_pServers ) + { + g_pServers->ServerPlayers( server ); + } +} \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/hud_servers.h b/hlsdk/ricochet/cl_dll/hud_servers.h new file mode 100644 index 00000000..577b5551 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/hud_servers.h @@ -0,0 +1,34 @@ +#if !defined( HUD_SERVERSH ) +#define HUD_SERVERSH +#pragma once + +#define NET_CALLBACK /* */ + +// Dispatchers +void NET_CALLBACK ListResponse( struct net_response_s *response ); +void NET_CALLBACK ServerResponse( struct net_response_s *response ); +void NET_CALLBACK PingResponse( struct net_response_s *response ); +void NET_CALLBACK RulesResponse( struct net_response_s *response ); +void NET_CALLBACK PlayersResponse( struct net_response_s *response ); + +void ServersInit( void ); +void ServersShutdown( void ); +void ServersThink( double time ); +void ServersCancel( void ); + +// Get list and get server info from each +void ServersList( void ); + +// Query for IP / IPX LAN servers +void BroadcastServersList( int clearpending ); + +void ServerPing( int server ); +void ServerRules( int server ); +void ServerPlayers( int server ); + +int ServersGetCount( void ); +const char *ServersGetInfo( int server ); +int ServersIsQuerying( void ); +void SortServers( const char *fieldname ); + +#endif // HUD_SERVERSH \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/hud_servers_priv.h b/hlsdk/ricochet/cl_dll/hud_servers_priv.h new file mode 100644 index 00000000..84f1e2a2 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/hud_servers_priv.h @@ -0,0 +1,91 @@ +#if !defined( HUD_SERVERS_PRIVH ) +#define HUD_SERVERS_PRIVH +#pragma once + +#include "netadr.h" + +class CHudServers +{ +public: + typedef struct request_s + { + struct request_s *next; + netadr_t remote_address; + int context; + } request_t; + + typedef struct server_s + { + struct server_s *next; + netadr_t remote_address; + char *info; + int ping; + } server_t; + + CHudServers(); + ~CHudServers(); + + void Think( double time ); + void QueryThink( void ); + int isQuerying( void ); + + int LoadMasterAddresses( int maxservers, int *count, netadr_t *padr ); + + void RequestList( void ); + void RequestBroadcastList( int clearpending ); + + void ServerPing( int server ); + void ServerRules( int server ); + void ServerPlayers( int server ); + + void CancelRequest( void ); + + int CompareServers( server_t *p1, server_t *p2 ); + + void ClearServerList( server_t **ppList ); + void ClearRequestList( request_t **ppList ); + + void AddServer( server_t **ppList, server_t *p ); + + void RemoveServerFromList( request_t **ppList, request_t *item ); + + request_t *FindRequest( int context, request_t *pList ); + + int ServerListSize( void ); + char *GetServerInfo( int server ); + int GetServerCount( void ); + void SortServers( const char *fieldname ); + + void ListResponse( struct net_response_s *response ); + void ServerResponse( struct net_response_s *response ); + void PingResponse( struct net_response_s *response ); + void RulesResponse( struct net_response_s *response ); + void PlayersResponse( struct net_response_s *response ); +private: + + server_t *GetServer( int server ); + + // + char m_szToken[ 1024 ]; + int m_nRequesting; + int m_nDone; + + double m_dStarted; + + request_t *m_pServerList; + request_t *m_pActiveList; + + server_t *m_pServers; + + int m_nServerCount; + + int m_nActiveQueries; + int m_nQuerying; + double m_fElapsed; + + request_t *m_pPingRequest; + request_t *m_pRulesRequest; + request_t *m_pPlayersRequest; +}; + +#endif // HUD_SERVERS_PRIVH \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/hud_update.cpp b/hlsdk/ricochet/cl_dll/hud_update.cpp new file mode 100644 index 00000000..3a69a00d --- /dev/null +++ b/hlsdk/ricochet/cl_dll/hud_update.cpp @@ -0,0 +1,54 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// +// hud_update.cpp +// + +#include +#include "hud.h" +#include "cl_util.h" +#include +#include + +int CL_ButtonBits( int ); +void CL_ResetButtonBits( int bits ); + +extern float v_idlescale; +float in_fov; +extern void HUD_SetCmdBits( int bits ); + +int CHud::UpdateClientData(client_data_t *cdata, float time) +{ + memcpy(m_vecOrigin, cdata->origin, sizeof(vec3_t)); + memcpy(m_vecAngles, cdata->viewangles, sizeof(vec3_t)); + + m_iKeyBits = CL_ButtonBits( 0 ); + m_iWeaponBits = cdata->iWeaponBits; + + in_fov = cdata->fov; + + Think(); + + cdata->fov = m_iFOV; + + v_idlescale = m_iConcussionEffect; + + CL_ResetButtonBits( m_iKeyBits ); + + // return 1 if in anything in the client_data struct has been changed, 0 otherwise + return 1; +} + + diff --git a/hlsdk/ricochet/cl_dll/in_camera.cpp b/hlsdk/ricochet/cl_dll/in_camera.cpp new file mode 100644 index 00000000..08123cab --- /dev/null +++ b/hlsdk/ricochet/cl_dll/in_camera.cpp @@ -0,0 +1,620 @@ +/*** +* +* Copyright (c) 1999, 2000 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 "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" + +#include "windows.h" + +float CL_KeyState (kbutton_t *key); + +extern "C" +{ + void DLLEXPORT CAM_Think( void ); + int DLLEXPORT CL_IsThirdPerson( void ); + void DLLEXPORT CL_CameraOffset( float *ofs ); +} + +extern cl_enginefunc_t gEngfuncs; + +//-------------------------------------------------- Constants + +#define CAM_DIST_DELTA 1.0 +#define CAM_ANGLE_DELTA 2.5 +#define CAM_ANGLE_SPEED 2.5 +#define CAM_MIN_DIST 30.0 +#define CAM_ANGLE_MOVE .5 +#define MAX_ANGLE_DIFF 10.0 +#define PITCH_MAX 90.0 +#define PITCH_MIN 0 +#define YAW_MAX 135.0 +#define YAW_MIN -135.0 + +enum ECAM_Command +{ + CAM_COMMAND_NONE = 0, + CAM_COMMAND_TOTHIRDPERSON = 1, + CAM_COMMAND_TOFIRSTPERSON = 2 +}; + +//-------------------------------------------------- Global Variables + +cvar_t *cam_command; +cvar_t *cam_snapto; +cvar_t *cam_idealyaw; +cvar_t *cam_idealpitch; +cvar_t *cam_idealdist; +cvar_t *cam_contain; + +cvar_t *c_maxpitch; +cvar_t *c_minpitch; +cvar_t *c_maxyaw; +cvar_t *c_minyaw; +cvar_t *c_maxdistance; +cvar_t *c_mindistance; + +// pitch, yaw, dist +vec3_t cam_ofs; + + +// In third person +int cam_thirdperson; +int cam_mousemove; //true if we are moving the cam with the mouse, False if not +int iMouseInUse=0; +int cam_distancemove; +extern int mouse_x, mouse_y; //used to determine what the current x and y values are +int cam_old_mouse_x, cam_old_mouse_y; //holds the last ticks mouse movement +POINT cam_mouse; +//-------------------------------------------------- Local Variables + +static kbutton_t cam_pitchup, cam_pitchdown, cam_yawleft, cam_yawright; +static kbutton_t cam_in, cam_out, cam_move; + +//-------------------------------------------------- Prototypes + +void CAM_ToThirdPerson(void); +void CAM_ToFirstPerson(void); +void CAM_StartDistance(void); +void CAM_EndDistance(void); + + +//-------------------------------------------------- Local Functions + +float MoveToward( float cur, float goal, float maxspeed ) +{ + if( cur != goal ) + { + if( abs( cur - goal ) > 180.0 ) + { + if( cur < goal ) + cur += 360.0; + else + cur -= 360.0; + } + + if( cur < goal ) + { + if( cur < goal - 1.0 ) + cur += ( goal - cur ) / 4.0; + else + cur = goal; + } + else + { + if( cur > goal + 1.0 ) + cur -= ( cur - goal ) / 4.0; + else + cur = goal; + } + } + + + // bring cur back into range + if( cur < 0 ) + cur += 360.0; + else if( cur >= 360 ) + cur -= 360; + + return cur; +} + + +//-------------------------------------------------- Gobal Functions + +typedef struct +{ + vec3_t boxmins, boxmaxs;// enclose the test object along entire move + float *mins, *maxs; // size of the moving object + vec3_t mins2, maxs2; // size when clipping against mosnters + float *start, *end; + trace_t trace; + int type; + edict_t *passedict; + qboolean monsterclip; +} moveclip_t; + +extern trace_t SV_ClipMoveToEntity (edict_t *ent, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end); + +void DLLEXPORT CAM_Think( void ) +{ + vec3_t origin; + vec3_t ext, pnt, camForward, camRight, camUp; + moveclip_t clip; + float dist; + vec3_t camAngles; + float flSensitivity; +#ifdef LATER + int i; +#endif + vec3_t viewangles; + + switch( (int) cam_command->value ) + { + case CAM_COMMAND_TOTHIRDPERSON: + CAM_ToThirdPerson(); + break; + + case CAM_COMMAND_TOFIRSTPERSON: + CAM_ToFirstPerson(); + break; + + case CAM_COMMAND_NONE: + default: + break; + } + + if( !cam_thirdperson ) + return; + +#ifdef LATER + if ( cam_contain->value ) + { + gEngfuncs.GetClientOrigin( origin ); + ext[0] = ext[1] = ext[2] = 0.0; + } +#endif + + camAngles[ PITCH ] = cam_idealpitch->value; + camAngles[ YAW ] = cam_idealyaw->value; + dist = cam_idealdist->value; + // + //movement of the camera with the mouse + // + if (cam_mousemove) + { + //get windows cursor position + GetCursorPos (&cam_mouse); + //check for X delta values and adjust accordingly + //eventually adjust YAW based on amount of movement + //don't do any movement of the cam using YAW/PITCH if we are zooming in/out the camera + if (!cam_distancemove) + { + + //keep the camera within certain limits around the player (ie avoid certain bad viewing angles) + if (cam_mouse.x>gEngfuncs.GetWindowCenterX()) + { + //if ((camAngles[YAW]>=225.0)||(camAngles[YAW]<135.0)) + if (camAngles[YAW]value) + { + camAngles[ YAW ] += (CAM_ANGLE_MOVE)*((cam_mouse.x-gEngfuncs.GetWindowCenterX())/2); + } + if (camAngles[YAW]>c_maxyaw->value) + { + + camAngles[YAW]=c_maxyaw->value; + } + } + else if (cam_mouse.x225.0)) + if (camAngles[YAW]>c_minyaw->value) + { + camAngles[ YAW ] -= (CAM_ANGLE_MOVE)* ((gEngfuncs.GetWindowCenterX()-cam_mouse.x)/2); + + } + if (camAngles[YAW]value) + { + camAngles[YAW]=c_minyaw->value; + + } + } + + //check for y delta values and adjust accordingly + //eventually adjust PITCH based on amount of movement + //also make sure camera is within bounds + if (cam_mouse.y>gEngfuncs.GetWindowCenterY()) + { + if(camAngles[PITCH]value) + { + camAngles[PITCH] +=(CAM_ANGLE_MOVE)* ((cam_mouse.y-gEngfuncs.GetWindowCenterY())/2); + } + if (camAngles[PITCH]>c_maxpitch->value) + { + camAngles[PITCH]=c_maxpitch->value; + } + } + else if (cam_mouse.yc_minpitch->value) + { + camAngles[PITCH] -= (CAM_ANGLE_MOVE)*((gEngfuncs.GetWindowCenterY()-cam_mouse.y)/2); + } + if (camAngles[PITCH]value) + { + camAngles[PITCH]=c_minpitch->value; + } + } + + //set old mouse coordinates to current mouse coordinates + //since we are done with the mouse + + if ( ( flSensitivity = gHUD.GetSensitivity() ) != 0 ) + { + cam_old_mouse_x=cam_mouse.x*flSensitivity; + cam_old_mouse_y=cam_mouse.y*flSensitivity; + } + else + { + cam_old_mouse_x=cam_mouse.x; + cam_old_mouse_y=cam_mouse.y; + } + SetCursorPos (gEngfuncs.GetWindowCenterX(), gEngfuncs.GetWindowCenterY()); + } + } + + //Nathan code here + if( CL_KeyState( &cam_pitchup ) ) + camAngles[ PITCH ] += CAM_ANGLE_DELTA; + else if( CL_KeyState( &cam_pitchdown ) ) + camAngles[ PITCH ] -= CAM_ANGLE_DELTA; + + if( CL_KeyState( &cam_yawleft ) ) + camAngles[ YAW ] -= CAM_ANGLE_DELTA; + else if( CL_KeyState( &cam_yawright ) ) + camAngles[ YAW ] += CAM_ANGLE_DELTA; + + if( CL_KeyState( &cam_in ) ) + { + dist -= CAM_DIST_DELTA; + if( dist < CAM_MIN_DIST ) + { + // If we go back into first person, reset the angle + camAngles[ PITCH ] = 0; + camAngles[ YAW ] = 0; + dist = CAM_MIN_DIST; + } + + } + else if( CL_KeyState( &cam_out ) ) + dist += CAM_DIST_DELTA; + + if (cam_distancemove) + { + if (cam_mouse.y>gEngfuncs.GetWindowCenterY()) + { + if(distvalue) + { + dist +=CAM_DIST_DELTA * ((cam_mouse.y-gEngfuncs.GetWindowCenterY())/2); + } + if (dist>c_maxdistance->value) + { + dist=c_maxdistance->value; + } + } + else if (cam_mouse.yc_mindistance->value) + { + dist -= (CAM_DIST_DELTA)*((gEngfuncs.GetWindowCenterY()-cam_mouse.y)/2); + } + if (distvalue) + { + dist=c_mindistance->value; + } + } + //set old mouse coordinates to current mouse coordinates + //since we are done with the mouse + cam_old_mouse_x=cam_mouse.x*gHUD.GetSensitivity(); + cam_old_mouse_y=cam_mouse.y*gHUD.GetSensitivity(); + SetCursorPos (gEngfuncs.GetWindowCenterX(), gEngfuncs.GetWindowCenterY()); + } +#ifdef LATER + if( cam_contain->value ) + { + // check new ideal + VectorCopy( origin, pnt ); + AngleVectors( camAngles, camForward, camRight, camUp ); + for (i=0 ; i<3 ; i++) + pnt[i] += -dist*camForward[i]; + + // check line from r_refdef.vieworg to pnt + memset ( &clip, 0, sizeof ( moveclip_t ) ); + clip.trace = SV_ClipMoveToEntity( sv.edicts, r_refdef.vieworg, ext, ext, pnt ); + if( clip.trace.fraction == 1.0 ) + { + // update ideal + cam_idealpitch->value = camAngles[ PITCH ]; + cam_idealyaw->value = camAngles[ YAW ]; + cam_idealdist->value = dist; + } + } + else +#endif + { + // update ideal + cam_idealpitch->value = camAngles[ PITCH ]; + cam_idealyaw->value = camAngles[ YAW ]; + cam_idealdist->value = dist; + } + + // Move towards ideal + VectorCopy( cam_ofs, camAngles ); + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + if( cam_snapto->value ) + { + camAngles[ YAW ] = cam_idealyaw->value + viewangles[ YAW ]; + camAngles[ PITCH ] = cam_idealpitch->value + viewangles[ PITCH ]; + camAngles[ 2 ] = cam_idealdist->value; + } + else + { + if( camAngles[ YAW ] - viewangles[ YAW ] != cam_idealyaw->value ) + camAngles[ YAW ] = MoveToward( camAngles[ YAW ], cam_idealyaw->value + viewangles[ YAW ], CAM_ANGLE_SPEED ); + + if( camAngles[ PITCH ] - viewangles[ PITCH ] != cam_idealpitch->value ) + camAngles[ PITCH ] = MoveToward( camAngles[ PITCH ], cam_idealpitch->value + viewangles[ PITCH ], CAM_ANGLE_SPEED ); + + if( abs( camAngles[ 2 ] - cam_idealdist->value ) < 2.0 ) + camAngles[ 2 ] = cam_idealdist->value; + else + camAngles[ 2 ] += ( cam_idealdist->value - camAngles[ 2 ] ) / 4.0; + } +#ifdef LATER + if( cam_contain->value ) + { + // Test new position + dist = camAngles[ ROLL ]; + camAngles[ ROLL ] = 0; + + VectorCopy( origin, pnt ); + AngleVectors( camAngles, camForward, camRight, camUp ); + for (i=0 ; i<3 ; i++) + pnt[i] += -dist*camForward[i]; + + // check line from r_refdef.vieworg to pnt + memset ( &clip, 0, sizeof ( moveclip_t ) ); + ext[0] = ext[1] = ext[2] = 0.0; + clip.trace = SV_ClipMoveToEntity( sv.edicts, r_refdef.vieworg, ext, ext, pnt ); + if( clip.trace.fraction != 1.0 ) + return; + } +#endif + cam_ofs[ 0 ] = camAngles[ 0 ]; + cam_ofs[ 1 ] = camAngles[ 1 ]; + cam_ofs[ 2 ] = dist; +} + +extern void KeyDown (kbutton_t *b); // HACK +extern void KeyUp (kbutton_t *b); // HACK + +void CAM_PitchUpDown(void) { KeyDown( &cam_pitchup ); } +void CAM_PitchUpUp(void) { KeyUp( &cam_pitchup ); } +void CAM_PitchDownDown(void) { KeyDown( &cam_pitchdown ); } +void CAM_PitchDownUp(void) { KeyUp( &cam_pitchdown ); } +void CAM_YawLeftDown(void) { KeyDown( &cam_yawleft ); } +void CAM_YawLeftUp(void) { KeyUp( &cam_yawleft ); } +void CAM_YawRightDown(void) { KeyDown( &cam_yawright ); } +void CAM_YawRightUp(void) { KeyUp( &cam_yawright ); } +void CAM_InDown(void) { KeyDown( &cam_in ); } +void CAM_InUp(void) { KeyUp( &cam_in ); } +void CAM_OutDown(void) { KeyDown( &cam_out ); } +void CAM_OutUp(void) { KeyUp( &cam_out ); } + +void CAM_ToThirdPerson(void) +{ + vec3_t viewangles; + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + if( !cam_thirdperson ) + { + cam_thirdperson = 1; + + cam_ofs[ YAW ] = viewangles[ YAW ]; + cam_ofs[ PITCH ] = viewangles[ PITCH ]; + cam_ofs[ 2 ] = CAM_MIN_DIST; + } + + gEngfuncs.Cvar_SetValue( "cam_command", 0 ); +} + +void CAM_ToFirstPerson(void) +{ + cam_thirdperson = 0; + + gEngfuncs.Cvar_SetValue( "cam_command", 0 ); +} + +void CAM_ToggleSnapto( void ) +{ + cam_snapto->value = !cam_snapto->value; +} + +void CAM_Init( void ) +{ + gEngfuncs.pfnAddCommand( "+campitchup", CAM_PitchUpDown ); + gEngfuncs.pfnAddCommand( "-campitchup", CAM_PitchUpUp ); + gEngfuncs.pfnAddCommand( "+campitchdown", CAM_PitchDownDown ); + gEngfuncs.pfnAddCommand( "-campitchdown", CAM_PitchDownUp ); + gEngfuncs.pfnAddCommand( "+camyawleft", CAM_YawLeftDown ); + gEngfuncs.pfnAddCommand( "-camyawleft", CAM_YawLeftUp ); + gEngfuncs.pfnAddCommand( "+camyawright", CAM_YawRightDown ); + gEngfuncs.pfnAddCommand( "-camyawright", CAM_YawRightUp ); + gEngfuncs.pfnAddCommand( "+camin", CAM_InDown ); + gEngfuncs.pfnAddCommand( "-camin", CAM_InUp ); + gEngfuncs.pfnAddCommand( "+camout", CAM_OutDown ); + gEngfuncs.pfnAddCommand( "-camout", CAM_OutUp ); + gEngfuncs.pfnAddCommand( "thirdperson", CAM_ToThirdPerson ); + gEngfuncs.pfnAddCommand( "firstperson", CAM_ToFirstPerson ); + gEngfuncs.pfnAddCommand( "+cammousemove",CAM_StartMouseMove); + gEngfuncs.pfnAddCommand( "-cammousemove",CAM_EndMouseMove); + gEngfuncs.pfnAddCommand( "+camdistance", CAM_StartDistance ); + gEngfuncs.pfnAddCommand( "-camdistance", CAM_EndDistance ); + gEngfuncs.pfnAddCommand( "snapto", CAM_ToggleSnapto ); + + cam_command = gEngfuncs.pfnRegisterVariable ( "cam_command", "0", 0 ); // tells camera to go to thirdperson + cam_snapto = gEngfuncs.pfnRegisterVariable ( "cam_snapto", "0", 0 ); // snap to thirdperson view + cam_idealyaw = gEngfuncs.pfnRegisterVariable ( "cam_idealyaw", "0", 0 ); // ? yaw + cam_idealpitch = gEngfuncs.pfnRegisterVariable ( "cam_idealpitch", "0", 0 ); // thirperson pitch + cam_idealdist = gEngfuncs.pfnRegisterVariable ( "cam_idealdist", "196", 0 ); // thirdperson distance + cam_contain = gEngfuncs.pfnRegisterVariable ( "cam_contain", "0", 0 ); // contain camera to world + + c_maxpitch = gEngfuncs.pfnRegisterVariable ( "c_maxpitch", "90.0", 0 ); + c_minpitch = gEngfuncs.pfnRegisterVariable ( "c_minpitch", "0.0", 0 ); + c_maxyaw = gEngfuncs.pfnRegisterVariable ( "c_maxyaw", "135.0", 0 ); + c_minyaw = gEngfuncs.pfnRegisterVariable ( "c_minyaw", "-135.0", 0 ); + c_maxdistance = gEngfuncs.pfnRegisterVariable ( "c_maxdistance", "200.0", 0 ); + c_mindistance = gEngfuncs.pfnRegisterVariable ( "c_mindistance", "30.0", 0 ); +} + +void CAM_ClearStates( void ) +{ + vec3_t viewangles; + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + cam_pitchup.state = 0; + cam_pitchdown.state = 0; + cam_yawleft.state = 0; + cam_yawright.state = 0; + cam_in.state = 0; + cam_out.state = 0; + + cam_thirdperson = 0; + cam_command->value = 0; + cam_mousemove=0; + + cam_snapto->value = 0; + cam_distancemove = 0; + + cam_ofs[ 0 ] = 0.0; + cam_ofs[ 1 ] = 0.0; + cam_ofs[ 2 ] = CAM_MIN_DIST; + + cam_idealpitch->value = viewangles[ PITCH ]; + cam_idealyaw->value = viewangles[ YAW ]; + cam_idealdist->value = CAM_MIN_DIST; +} + +void CAM_StartMouseMove(void) +{ + float flSensitivity; + + //only move the cam with mouse if we are in third person. + if (cam_thirdperson) + { + //set appropriate flags and initialize the old mouse position + //variables for mouse camera movement + if (!cam_mousemove) + { + cam_mousemove=1; + iMouseInUse=1; + GetCursorPos (&cam_mouse); + + if ( ( flSensitivity = gHUD.GetSensitivity() ) != 0 ) + { + cam_old_mouse_x=cam_mouse.x*flSensitivity; + cam_old_mouse_y=cam_mouse.y*flSensitivity; + } + else + { + cam_old_mouse_x=cam_mouse.x; + cam_old_mouse_y=cam_mouse.y; + } + } + } + //we are not in 3rd person view..therefore do not allow camera movement + else + { + cam_mousemove=0; + iMouseInUse=0; + } +} + +//the key has been released for camera movement +//tell the engine that mouse camera movement is off +void CAM_EndMouseMove(void) +{ + cam_mousemove=0; + iMouseInUse=0; +} + + +//---------------------------------------------------------- +//routines to start the process of moving the cam in or out +//using the mouse +//---------------------------------------------------------- +void CAM_StartDistance(void) +{ + //only move the cam with mouse if we are in third person. + if (cam_thirdperson) + { + //set appropriate flags and initialize the old mouse position + //variables for mouse camera movement + if (!cam_distancemove) + { + cam_distancemove=1; + cam_mousemove=1; + iMouseInUse=1; + GetCursorPos (&cam_mouse); + cam_old_mouse_x=cam_mouse.x*gHUD.GetSensitivity(); + cam_old_mouse_y=cam_mouse.y*gHUD.GetSensitivity(); + } + } + //we are not in 3rd person view..therefore do not allow camera movement + else + { + cam_distancemove=0; + cam_mousemove=0; + iMouseInUse=0; + } +} + +//the key has been released for camera movement +//tell the engine that mouse camera movement is off +void CAM_EndDistance(void) +{ + cam_distancemove=0; + cam_mousemove=0; + iMouseInUse=0; +} + +int DLLEXPORT CL_IsThirdPerson( void ) +{ + return cam_thirdperson ? 1 : 0; +} + +void DLLEXPORT CL_CameraOffset( float *ofs ) +{ + VectorCopy( cam_ofs, ofs ); +} \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/in_defs.h b/hlsdk/ricochet/cl_dll/in_defs.h new file mode 100644 index 00000000..257b9660 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/in_defs.h @@ -0,0 +1,28 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +#if !defined( IN_DEFSH ) +#define IN_DEFSH +#pragma once + +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 + +#define DLLEXPORT __declspec( dllexport ) + +#endif \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/input.cpp b/hlsdk/ricochet/cl_dll/input.cpp new file mode 100644 index 00000000..4e2f58f0 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/input.cpp @@ -0,0 +1,980 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// input.cpp -- builds an intended movement command to send to the server +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +extern "C" +{ +#include "kbutton.h" +} +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "view.h" +#include +#include + +#include "vgui_TeamFortressViewport.h" +#include "vgui_discobjects.h" + +// Observer Movement modes (stored in pev->iuser1, so the physics code can get at them) +#define OBS_CHASE_LOCKED 1 +#define OBS_CHASE_FREE 2 +#define OBS_ROAMING 3 +#define OBS_LOCKEDVIEW 4 + +extern "C" +{ + struct kbutton_s DLLEXPORT *KB_Find( const char *name ); + void DLLEXPORT CL_CreateMove ( float frametime, struct usercmd_s *cmd, int active ); + void DLLEXPORT HUD_Shutdown( void ); + int DLLEXPORT HUD_Key_Event( int eventcode, int keynum, const char *pszCurrentBinding ); +} + +extern int g_weaponselect; +extern cl_enginefunc_t gEngfuncs; + +// Defined in pm_math.c +extern "C" float anglemod( float a ); + +void IN_Init (void); +void IN_Move ( float frametime, usercmd_t *cmd); +void IN_Shutdown( void ); +void V_Init( void ); +int CL_ButtonBits( int ); + +// xxx need client dll function to get and clear impuse +extern cvar_t *in_joystick; + +int in_impulse = 0; +int in_cancel = 0; + +cvar_t *m_pitch; +cvar_t *m_yaw; +cvar_t *m_forward; +cvar_t *m_side; + +cvar_t *lookstrafe; +cvar_t *lookspring; +cvar_t *cl_pitchup; +cvar_t *cl_pitchdown; +cvar_t *cl_upspeed; +cvar_t *cl_forwardspeed; +cvar_t *cl_backspeed; +cvar_t *cl_sidespeed; +cvar_t *cl_movespeedkey; +cvar_t *cl_yawspeed; +cvar_t *cl_pitchspeed; +cvar_t *cl_anglespeedkey; +cvar_t *cl_vsmoothing; +/* +=============================================================================== + +KEY BUTTONS + +Continuous button event tracking is complicated by the fact that two different +input sources (say, mouse button 1 and the control key) can both press the +same button, but the button should only be released when both of the +pressing key have been released. + +When a key event issues a button command (+forward, +attack, etc), it appends +its key number as a parameter to the command so it can be matched up with +the release. + +state bit 0 is the current state of the key +state bit 1 is edge triggered on the up to down transition +state bit 2 is edge triggered on the down to up transition + +=============================================================================== +*/ + + +kbutton_t in_mlook; +kbutton_t in_klook; +kbutton_t in_jlook; +kbutton_t in_left; +kbutton_t in_right; +kbutton_t in_forward; +kbutton_t in_back; +kbutton_t in_lookup; +kbutton_t in_lookdown; +kbutton_t in_moveleft; +kbutton_t in_moveright; +kbutton_t in_strafe; +kbutton_t in_speed; +kbutton_t in_use; +kbutton_t in_jump; +kbutton_t in_attack; +kbutton_t in_attack2; +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 + +typedef struct kblist_s +{ + struct kblist_s *next; + kbutton_t *pkey; + char name[32]; +} kblist_t; + +kblist_t *g_kbkeys = NULL; + +/* +============ +KB_ConvertString + +Removes references to +use and replaces them with the keyname in the output string. If + a binding is unfound, then the original text is retained. +NOTE: Only works for text with +word in it. +============ +*/ +int KB_ConvertString( char *in, char **ppout ) +{ + char sz[ 4096 ]; + char binding[ 64 ]; + char *p; + char *pOut; + char *pEnd; + const char *pBinding; + + if ( !ppout ) + return 0; + + *ppout = NULL; + p = in; + pOut = sz; + while ( *p ) + { + if ( *p == '+' ) + { + pEnd = binding; + while ( *p && ( isalnum( *p ) || ( pEnd == binding ) ) && ( ( pEnd - binding ) < 63 ) ) + { + *pEnd++ = *p++; + } + + *pEnd = '\0'; + + pBinding = NULL; + if ( strlen( binding + 1 ) > 0 ) + { + // See if there is a binding for binding? + pBinding = gEngfuncs.Key_LookupBinding( binding + 1 ); + } + + if ( pBinding ) + { + *pOut++ = '['; + pEnd = (char *)pBinding; + } + else + { + pEnd = binding; + } + + while ( *pEnd ) + { + *pOut++ = *pEnd++; + } + + if ( pBinding ) + { + *pOut++ = ']'; + } + } + else + { + *pOut++ = *p++; + } + } + + *pOut = '\0'; + + pOut = ( char * )malloc( strlen( sz ) + 1 ); + strcpy( pOut, sz ); + *ppout = pOut; + + return 1; +} + +/* +============ +KB_Find + +Allows the engine to get a kbutton_t directly ( so it can check +mlook state, etc ) for saving out to .cfg files +============ +*/ +struct kbutton_s DLLEXPORT *KB_Find( const char *name ) +{ + kblist_t *p; + p = g_kbkeys; + while ( p ) + { + if ( !stricmp( name, p->name ) ) + return p->pkey; + + p = p->next; + } + return NULL; +} + +/* +============ +KB_Add + +Add a kbutton_t * to the list of pointers the engine can retrieve via KB_Find +============ +*/ +void KB_Add( const char *name, kbutton_t *pkb ) +{ + kblist_t *p; + kbutton_t *kb; + + kb = KB_Find( name ); + + if ( kb ) + return; + + p = ( kblist_t * )malloc( sizeof( kblist_t ) ); + memset( p, 0, sizeof( *p ) ); + + strcpy( p->name, name ); + p->pkey = pkb; + + p->next = g_kbkeys; + g_kbkeys = p; +} + +/* +============ +KB_Init + +Add kbutton_t definitions that the engine can query if needed +============ +*/ +void KB_Init( void ) +{ + g_kbkeys = NULL; + + KB_Add( "in_graph", &in_graph ); + KB_Add( "in_mlook", &in_mlook ); + KB_Add( "in_jlook", &in_jlook ); +} + +/* +============ +KB_Shutdown + +Clear kblist +============ +*/ +void KB_Shutdown( void ) +{ + kblist_t *p, *n; + p = g_kbkeys; + while ( p ) + { + n = p->next; + free( p ); + p = n; + } + g_kbkeys = NULL; +} + +/* +============ +KeyDown +============ +*/ +void KeyDown (kbutton_t *b) +{ + int k; + char *c; + + c = gEngfuncs.Cmd_Argv(1); + if (c[0]) + k = atoi(c); + else + k = -1; // typed manually at the console for continuous down + + if (k == b->down[0] || k == b->down[1]) + return; // repeating key + + if (!b->down[0]) + b->down[0] = k; + else if (!b->down[1]) + b->down[1] = k; + else + { + gEngfuncs.Con_DPrintf ("Three keys down for a button '%c' '%c' '%c'!\n", b->down[0], b->down[1], c); + return; + } + + if (b->state & 1) + return; // still down + b->state |= 1 + 2; // down + impulse down +} + +/* +============ +KeyUp +============ +*/ +void KeyUp (kbutton_t *b) +{ + int k; + char *c; + + c = gEngfuncs.Cmd_Argv(1); + if (c[0]) + k = atoi(c); + else + { // typed manually at the console, assume for unsticking, so clear all + b->down[0] = b->down[1] = 0; + b->state = 4; // impulse up + return; + } + + if (b->down[0] == k) + b->down[0] = 0; + else if (b->down[1] == k) + b->down[1] = 0; + else + return; // key up without coresponding down (menu pass through) + if (b->down[0] || b->down[1]) + { + return; // some other key is still holding it down + } + + if (!(b->state & 1)) + return; // still up (this should not happen) + + b->state &= ~1; // now up + b->state |= 4; // impulse up +} +/* +============ +HUD_Key_Event + +Return 1 to allow engine to process the key, otherwise, act on it as needed +============ +*/ +int DLLEXPORT HUD_Key_Event( int down, int keynum, const char *pszCurrentBinding ) +{ + if (gViewPort) + return gViewPort->KeyInput(down, keynum, pszCurrentBinding); + + return 1; +} + +void IN_BreakDown( void ) { KeyDown( &in_break );}; +void IN_BreakUp( void ) { KeyUp( &in_break ); }; +void IN_KLookDown (void) {KeyDown(&in_klook);} +void IN_KLookUp (void) {KeyUp(&in_klook);} +void IN_JLookDown (void) {KeyDown(&in_jlook);} +void IN_JLookUp (void) {KeyUp(&in_jlook);} +void IN_MLookDown (void) {KeyDown(&in_mlook);} +void IN_UpDown(void) {KeyDown(&in_up);} +void IN_UpUp(void) {KeyUp(&in_up);} +void IN_DownDown(void) {KeyDown(&in_down);} +void IN_DownUp(void) {KeyUp(&in_down);} +void IN_LeftDown(void) {KeyDown(&in_left);} +void IN_LeftUp(void) {KeyUp(&in_left);} +void IN_RightDown(void) {KeyDown(&in_right);} +void IN_RightUp(void) {KeyUp(&in_right);} +void IN_ForwardDown(void) {KeyDown(&in_forward);} +void IN_ForwardUp(void) {KeyUp(&in_forward);} +void IN_BackDown(void) {KeyDown(&in_back);} +void IN_BackUp(void) {KeyUp(&in_back);} +void IN_LookupDown(void) {KeyDown(&in_lookup);} +void IN_LookupUp(void) {KeyUp(&in_lookup);} +void IN_LookdownDown(void) {KeyDown(&in_lookdown);} +void IN_LookdownUp(void) {KeyUp(&in_lookdown);} +void IN_MoveleftDown(void) {KeyDown(&in_moveleft);} +void IN_MoveleftUp(void) {KeyUp(&in_moveleft);} +void IN_MoverightDown(void) {KeyDown(&in_moveright);} +void IN_MoverightUp(void) {KeyUp(&in_moveright);} +void IN_SpeedDown(void) {KeyDown(&in_speed);} +void IN_SpeedUp(void) {KeyUp(&in_speed);} +void IN_StrafeDown(void) {KeyDown(&in_strafe);} +void IN_StrafeUp(void) {KeyUp(&in_strafe);} +void IN_Attack2Down(void) {KeyDown(&in_attack2);} +void IN_Attack2Up(void) {KeyUp(&in_attack2);} +void IN_UseDown (void) {KeyDown(&in_use);} +void IN_UseUp (void) {KeyUp(&in_use);} +void IN_JumpDown (void) {KeyDown(&in_jump);} +void IN_JumpUp (void) {KeyUp(&in_jump);} +void IN_DuckDown(void) {KeyDown(&in_duck);} +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);} + +void IN_AttackDown(void) +{ + KeyDown( &in_attack ); +} + +void IN_AttackUp(void) +{ + KeyUp( &in_attack ); + in_cancel = 0; +} + +// Special handling +void IN_Cancel(void) +{ + in_cancel = 1; +} + +void IN_Impulse (void) +{ + in_impulse = atoi( gEngfuncs.Cmd_Argv(1) ); +} + +void IN_ScoreDown(void) +{ + KeyDown(&in_score); + if ( gViewPort ) + { + gViewPort->ShowScoreBoard(); + } +} + +void IN_ScoreUp(void) +{ + KeyUp(&in_score); + if ( gViewPort ) + { + gViewPort->HideScoreBoard(); + } +} + +void IN_MLookUp (void) +{ + KeyUp( &in_mlook ); + if ( !( in_mlook.state & 1 ) && lookspring->value ) + { + V_StartPitchDrift(); + } +} + +/* +=============== +CL_KeyState + +Returns 0.25 if a key was pressed and released during the frame, +0.5 if it was pressed and held +0 if held then released, and +1.0 if held for the entire time +=============== +*/ +float CL_KeyState (kbutton_t *key) +{ + float val = 0.0; + int impulsedown, impulseup, down; + + impulsedown = key->state & 2; + impulseup = key->state & 4; + down = key->state & 1; + + if ( impulsedown && !impulseup ) + { + // pressed and held this frame? + val = down ? 0.5 : 0.0; + } + + if ( impulseup && !impulsedown ) + { + // released this frame? + val = down ? 0.0 : 0.0; + } + + if ( !impulsedown && !impulseup ) + { + // held the entire frame? + val = down ? 1.0 : 0.0; + } + + if ( impulsedown && impulseup ) + { + if ( down ) + { + // released and re-pressed this frame + val = 0.75; + } + else + { + // pressed and released this frame + val = 0.25; + } + } + + // clear impulses + key->state &= 1; + return val; +} + +bool bCanMoveMouse ( void ) +{ + if ( gViewPort && ( gViewPort->m_iUser1 == OBS_ROAMING || gViewPort->m_iUser1 == OBS_CHASE_FREE ) ) + return TRUE; + + if ( gViewPort && gViewPort->m_iUser1 == OBS_LOCKEDVIEW ) + return FALSE; + + return TRUE; +} +/* +================ +CL_AdjustAngles + +Moves the local angle positions +================ +*/ +void CL_AdjustAngles ( float frametime, float *viewangles ) +{ + float speed; + float up, down; + + if (in_speed.state & 1) + { + speed = frametime * cl_anglespeedkey->value; + } + else + { + speed = frametime; + } + + // Ricochet: Don't let them move the mouse when they're in spectator mode + if ( bCanMoveMouse() == FALSE ) + return; + + if (!(in_strafe.state & 1)) + { + viewangles[YAW] -= speed*cl_yawspeed->value*CL_KeyState (&in_right); + viewangles[YAW] += speed*cl_yawspeed->value*CL_KeyState (&in_left); + viewangles[YAW] = anglemod(viewangles[YAW]); + } + if (in_klook.state & 1) + { + V_StopPitchDrift (); + viewangles[PITCH] -= speed*cl_pitchspeed->value * CL_KeyState (&in_forward); + viewangles[PITCH] += speed*cl_pitchspeed->value * CL_KeyState (&in_back); + } + + up = CL_KeyState (&in_lookup); + down = CL_KeyState(&in_lookdown); + + viewangles[PITCH] -= speed*cl_pitchspeed->value * up; + viewangles[PITCH] += speed*cl_pitchspeed->value * down; + + if (up || down) + V_StopPitchDrift (); + + if (viewangles[PITCH] > cl_pitchdown->value) + viewangles[PITCH] = cl_pitchdown->value; + if (viewangles[PITCH] < -cl_pitchup->value) + viewangles[PITCH] = -cl_pitchup->value; + + if (viewangles[ROLL] > 50) + viewangles[ROLL] = 50; + if (viewangles[ROLL] < -50) + viewangles[ROLL] = -50; +} + +/* +================ +CL_CreateMove + +Send the intended movement message to the server +if active == 1 then we are 1) not playing back demos ( where our commands are ignored ) and +2 ) we have finished signing on to server +================ +*/ +void DLLEXPORT CL_CreateMove ( float frametime, struct usercmd_s *cmd, int active ) +{ + float spd; + vec3_t viewangles; + static vec3_t oldangles; + + if ( active ) + { + //memset( viewangles, 0, sizeof( vec3_t ) ); + //viewangles[ 0 ] = viewangles[ 1 ] = viewangles[ 2 ] = 0.0; + gEngfuncs.GetViewAngles( (float *)viewangles ); + + CL_AdjustAngles ( frametime, viewangles ); + + memset (cmd, 0, sizeof(*cmd)); + + gEngfuncs.SetViewAngles( (float *)viewangles ); + + if ( in_strafe.state & 1 ) + { + cmd->sidemove += cl_sidespeed->value * CL_KeyState (&in_right); + cmd->sidemove -= cl_sidespeed->value * CL_KeyState (&in_left); + } + + cmd->sidemove += cl_sidespeed->value * CL_KeyState (&in_moveright); + cmd->sidemove -= cl_sidespeed->value * CL_KeyState (&in_moveleft); + + cmd->upmove += cl_upspeed->value * CL_KeyState (&in_up); + cmd->upmove -= cl_upspeed->value * CL_KeyState (&in_down); + + if ( !(in_klook.state & 1 ) ) + { + cmd->forwardmove += cl_forwardspeed->value * CL_KeyState (&in_forward); + cmd->forwardmove -= cl_backspeed->value * CL_KeyState (&in_back); + } + + // adjust for speed key + if ( in_speed.state & 1 ) + { + cmd->forwardmove *= cl_movespeedkey->value; + cmd->sidemove *= cl_movespeedkey->value; + cmd->upmove *= cl_movespeedkey->value; + } + + // clip to maxspeed + spd = gEngfuncs.GetClientMaxspeed(); + if ( spd != 0.0 ) + { + // scale the 3 speeds so that the total velocity is not > cl.maxspeed + float fmov = sqrt( (cmd->forwardmove*cmd->forwardmove) + (cmd->sidemove*cmd->sidemove) + (cmd->upmove*cmd->upmove) ); + + if ( fmov > spd ) + { + float fratio = spd / fmov; + cmd->forwardmove *= fratio; + cmd->sidemove *= fratio; + cmd->upmove *= fratio; + } + } + + // Allow mice and other controllers to add their inputs + IN_Move ( frametime, cmd ); + } + + cmd->impulse = in_impulse; + in_impulse = 0; + + cmd->weaponselect = g_weaponselect; + g_weaponselect = 0; + // + // set button and flag bits + // + cmd->buttons = CL_ButtonBits( 1 ); + + // If they're in a modal dialog, ignore the attack button. + if( GetClientVoiceMgr()->IsInSquelchMode() ) + cmd->buttons &= ~IN_ATTACK; + + // Using joystick? + if ( in_joystick->value ) + { + if ( cmd->forwardmove > 0 ) + { + cmd->buttons |= IN_FORWARD; + } + else if ( cmd->forwardmove < 0 ) + { + cmd->buttons |= IN_BACK; + } + } + + gEngfuncs.GetViewAngles( (float *)viewangles ); + // Set current view angles. + + if ( gHUD.m_Health.m_iHealth > 0 ) + { + VectorCopy( viewangles, cmd->viewangles ); + VectorCopy( viewangles, oldangles ); + } + else + { + VectorCopy( oldangles, cmd->viewangles ); + } +} + +/* +============ +CL_IsDead + +Returns 1 if health is <= 0 +============ +*/ +int CL_IsDead( void ) +{ + return ( gHUD.m_Health.m_iHealth <= 0 ) ? 1 : 0; +} + +/* +============ +CL_ButtonBits + +Returns appropriate button info for keyboard and mouse state +Set bResetState to 1 to clear old state info +============ +*/ +int CL_ButtonBits( int bResetState ) +{ + int bits = 0; + + if ( in_attack.state & 3 ) + { + bits |= IN_ATTACK; + } + + if (in_duck.state & 3) + { + bits |= IN_DUCK; + } + + if (in_jump.state & 3) + { + bits |= IN_JUMP; + } + + if ( in_forward.state & 3 ) + { + bits |= IN_FORWARD; + } + + if (in_back.state & 3) + { + bits |= IN_BACK; + } + + if (in_use.state & 3) + { + bits |= IN_USE; + } + + if (in_cancel) + { + bits |= IN_CANCEL; + } + + if ( in_left.state & 3 ) + { + bits |= IN_LEFT; + } + + if (in_right.state & 3) + { + bits |= IN_RIGHT; + } + + if ( in_moveleft.state & 3 ) + { + bits |= IN_MOVELEFT; + } + + if (in_moveright.state & 3) + { + bits |= IN_MOVERIGHT; + } + + if (in_attack2.state & 3) + { + bits |= IN_ATTACK2; + } + + if (in_reload.state & 3) + { + bits |= IN_RELOAD; + } + + if (in_alt1.state & 3) + { + bits |= IN_ALT1; + } + + if ( in_score.state & 3 ) + { + bits |= IN_SCORE; + } + + // Dead or in intermission? Shore scoreboard, too + if ( CL_IsDead() || gHUD.m_iIntermission ) + { + bits |= IN_SCORE; + } + + if ( bResetState ) + { + in_attack.state &= ~2; + in_duck.state &= ~2; + in_jump.state &= ~2; + in_forward.state &= ~2; + in_back.state &= ~2; + in_use.state &= ~2; + in_left.state &= ~2; + in_right.state &= ~2; + in_moveleft.state &= ~2; + in_moveright.state &= ~2; + in_attack2.state &= ~2; + in_reload.state &= ~2; + in_alt1.state &= ~2; + in_score.state &= ~2; + } + + return bits; +} + +/* +============ +CL_ResetButtonBits + +============ +*/ +void CL_ResetButtonBits( int bits ) +{ + int bitsNew = CL_ButtonBits( 0 ) ^ bits; + + // Has the attack button been changed + if ( bitsNew & IN_ATTACK ) + { + // Was it pressed? or let go? + if ( bits & IN_ATTACK ) + { + KeyDown( &in_attack ); + } + else + { + // totally clear state + in_attack.state &= ~7; + } + } +} + +/* +============ +InitInput +============ +*/ +void InitInput (void) +{ + gEngfuncs.pfnAddCommand ("+moveup",IN_UpDown); + gEngfuncs.pfnAddCommand ("-moveup",IN_UpUp); + gEngfuncs.pfnAddCommand ("+movedown",IN_DownDown); + gEngfuncs.pfnAddCommand ("-movedown",IN_DownUp); + gEngfuncs.pfnAddCommand ("+left",IN_LeftDown); + gEngfuncs.pfnAddCommand ("-left",IN_LeftUp); + gEngfuncs.pfnAddCommand ("+right",IN_RightDown); + gEngfuncs.pfnAddCommand ("-right",IN_RightUp); + gEngfuncs.pfnAddCommand ("+forward",IN_ForwardDown); + gEngfuncs.pfnAddCommand ("-forward",IN_ForwardUp); + gEngfuncs.pfnAddCommand ("+back",IN_BackDown); + gEngfuncs.pfnAddCommand ("-back",IN_BackUp); + gEngfuncs.pfnAddCommand ("+lookup", IN_LookupDown); + gEngfuncs.pfnAddCommand ("-lookup", IN_LookupUp); + gEngfuncs.pfnAddCommand ("+lookdown", IN_LookdownDown); + gEngfuncs.pfnAddCommand ("-lookdown", IN_LookdownUp); + gEngfuncs.pfnAddCommand ("+strafe", IN_StrafeDown); + gEngfuncs.pfnAddCommand ("-strafe", IN_StrafeUp); + gEngfuncs.pfnAddCommand ("+moveleft", IN_MoveleftDown); + gEngfuncs.pfnAddCommand ("-moveleft", IN_MoveleftUp); + gEngfuncs.pfnAddCommand ("+moveright", IN_MoverightDown); + gEngfuncs.pfnAddCommand ("-moveright", IN_MoverightUp); + gEngfuncs.pfnAddCommand ("+speed", IN_SpeedDown); + gEngfuncs.pfnAddCommand ("-speed", IN_SpeedUp); + gEngfuncs.pfnAddCommand ("+attack", IN_AttackDown); + gEngfuncs.pfnAddCommand ("-attack", IN_AttackUp); + gEngfuncs.pfnAddCommand ("+attack2", IN_Attack2Down); + gEngfuncs.pfnAddCommand ("-attack2", IN_Attack2Up); + gEngfuncs.pfnAddCommand ("+use", IN_UseDown); + gEngfuncs.pfnAddCommand ("-use", IN_UseUp); + gEngfuncs.pfnAddCommand ("+jump", IN_JumpDown); + gEngfuncs.pfnAddCommand ("-jump", IN_JumpUp); + gEngfuncs.pfnAddCommand ("impulse", IN_Impulse); + gEngfuncs.pfnAddCommand ("+klook", IN_KLookDown); + gEngfuncs.pfnAddCommand ("-klook", IN_KLookUp); + gEngfuncs.pfnAddCommand ("+mlook", IN_MLookDown); + gEngfuncs.pfnAddCommand ("-mlook", IN_MLookUp); + gEngfuncs.pfnAddCommand ("+jlook", IN_JLookDown); + gEngfuncs.pfnAddCommand ("-jlook", IN_JLookUp); + gEngfuncs.pfnAddCommand ("+duck", IN_DuckDown); + 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); + gEngfuncs.pfnAddCommand ("-showscores", IN_ScoreUp); + gEngfuncs.pfnAddCommand ("+graph", IN_GraphDown); + gEngfuncs.pfnAddCommand ("-graph", IN_GraphUp); + gEngfuncs.pfnAddCommand ("+break",IN_BreakDown); + gEngfuncs.pfnAddCommand ("-break",IN_BreakUp); + + lookstrafe = gEngfuncs.pfnRegisterVariable ( "lookstrafe", "0", FCVAR_ARCHIVE ); + lookspring = gEngfuncs.pfnRegisterVariable ( "lookspring", "0", FCVAR_ARCHIVE ); + cl_anglespeedkey = gEngfuncs.pfnRegisterVariable ( "cl_anglespeedkey", "0.67", 0 ); + cl_yawspeed = gEngfuncs.pfnRegisterVariable ( "cl_yawspeed", "210", 0 ); + cl_pitchspeed = gEngfuncs.pfnRegisterVariable ( "cl_pitchspeed", "225", 0 ); + cl_upspeed = gEngfuncs.pfnRegisterVariable ( "cl_upspeed", "320", 0 ); + cl_forwardspeed = gEngfuncs.pfnRegisterVariable ( "cl_forwardspeed", "400", FCVAR_ARCHIVE ); + cl_backspeed = gEngfuncs.pfnRegisterVariable ( "cl_backspeed", "400", FCVAR_ARCHIVE ); + cl_sidespeed = gEngfuncs.pfnRegisterVariable ( "cl_sidespeed", "400", 0 ); + 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 ); + m_yaw = gEngfuncs.pfnRegisterVariable ( "m_yaw","0.022", FCVAR_ARCHIVE ); + m_forward = gEngfuncs.pfnRegisterVariable ( "m_forward","1", FCVAR_ARCHIVE ); + m_side = gEngfuncs.pfnRegisterVariable ( "m_side","0.8", FCVAR_ARCHIVE ); + + // Initialize third person camera controls. + CAM_Init(); + // Initialize inputs + IN_Init(); + // Initialize keyboard + KB_Init(); + // Initialize view system + V_Init(); +} + +/* +============ +ShutdownInput +============ +*/ +void ShutdownInput (void) +{ + IN_Shutdown(); + KB_Shutdown(); +} + +void DLLEXPORT HUD_Shutdown( void ) +{ + ShutdownInput(); +} \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/inputw32.cpp b/hlsdk/ricochet/cl_dll/inputw32.cpp new file mode 100644 index 00000000..5dda7787 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/inputw32.cpp @@ -0,0 +1,958 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// inputw32.cpp-- windows 95/98/nt mouse and joystick code +#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 "../engine/keydefs.h" +#include "view.h" +#include "windows.h" +#include "vgui_TeamFortressViewport.h" + +#define MOUSE_BUTTON_COUNT 5 + +// Set this to 1 to show mouse cursor. Experimental +int g_iVisibleMouse = 0; + +extern "C" +{ + void DLLEXPORT IN_ActivateMouse( void ); + void DLLEXPORT IN_DeactivateMouse( void ); + void DLLEXPORT IN_MouseEvent (int mstate); + void DLLEXPORT IN_Accumulate (void); + void DLLEXPORT IN_ClearStates (void); +} + +extern cl_enginefunc_t gEngfuncs; + +extern int iMouseInUse; + +extern kbutton_t in_strafe; +extern kbutton_t in_mlook; +extern kbutton_t in_speed; +extern kbutton_t in_jlook; + +extern cvar_t *m_pitch; +extern cvar_t *m_yaw; +extern cvar_t *m_forward; +extern cvar_t *m_side; + +extern cvar_t *lookstrafe; +extern cvar_t *lookspring; +extern cvar_t *cl_pitchdown; +extern cvar_t *cl_pitchup; +extern cvar_t *cl_yawspeed; +extern cvar_t *cl_sidespeed; +extern cvar_t *cl_forwardspeed; +extern cvar_t *cl_pitchspeed; +extern cvar_t *cl_movespeedkey; + +// mouse variables +cvar_t *m_filter; +cvar_t *sensitivity; + +int mouse_buttons; +int mouse_oldbuttonstate; +POINT current_pos; +int mouse_x, mouse_y, old_mouse_x, old_mouse_y, mx_accum, my_accum; + +static int restore_spi; +static int originalmouseparms[3], newmouseparms[3] = {0, 0, 1}; +static int mouseactive; +int mouseinitialized; +static int mouseparmsvalid; +static int mouseshowtoggle = 1; + +// joystick defines and variables +// where should defines be moved? +#define JOY_ABSOLUTE_AXIS 0x00000000 // control like a joystick +#define JOY_RELATIVE_AXIS 0x00000010 // control like a mouse, spinner, trackball +#define JOY_MAX_AXES 6 // X, Y, Z, R, U, V +#define JOY_AXIS_X 0 +#define JOY_AXIS_Y 1 +#define JOY_AXIS_Z 2 +#define JOY_AXIS_R 3 +#define JOY_AXIS_U 4 +#define JOY_AXIS_V 5 + +enum _ControlList +{ + AxisNada = 0, + AxisForward, + AxisLook, + AxisSide, + AxisTurn +}; + +DWORD dwAxisFlags[JOY_MAX_AXES] = +{ + JOY_RETURNX, + JOY_RETURNY, + JOY_RETURNZ, + JOY_RETURNR, + JOY_RETURNU, + JOY_RETURNV +}; + +DWORD dwAxisMap[ JOY_MAX_AXES ]; +DWORD dwControlMap[ JOY_MAX_AXES ]; +PDWORD pdwRawValue[ JOY_MAX_AXES ]; + +// none of these cvars are saved over a session +// this means that advanced controller configuration needs to be executed +// each time. this avoids any problems with getting back to a default usage +// or when changing from one controller to another. this way at least something +// works. +cvar_t *in_joystick; +cvar_t *joy_name; +cvar_t *joy_advanced; +cvar_t *joy_advaxisx; +cvar_t *joy_advaxisy; +cvar_t *joy_advaxisz; +cvar_t *joy_advaxisr; +cvar_t *joy_advaxisu; +cvar_t *joy_advaxisv; +cvar_t *joy_forwardthreshold; +cvar_t *joy_sidethreshold; +cvar_t *joy_pitchthreshold; +cvar_t *joy_yawthreshold; +cvar_t *joy_forwardsensitivity; +cvar_t *joy_sidesensitivity; +cvar_t *joy_pitchsensitivity; +cvar_t *joy_yawsensitivity; +cvar_t *joy_wwhack1; +cvar_t *joy_wwhack2; + +int joy_avail, joy_advancedinit, joy_haspov; +DWORD joy_oldbuttonstate, joy_oldpovstate; + +int joy_id; +DWORD joy_flags; +DWORD joy_numbuttons; + +static JOYINFOEX ji; + +/* +=========== +Force_CenterView_f +=========== +*/ +void Force_CenterView_f (void) +{ + vec3_t viewangles; + + if (!iMouseInUse) + { + gEngfuncs.GetViewAngles( (float *)viewangles ); + viewangles[PITCH] = 0; + gEngfuncs.SetViewAngles( (float *)viewangles ); + } +} + +/* +=========== +IN_ActivateMouse +=========== +*/ +void DLLEXPORT IN_ActivateMouse (void) +{ + if (mouseinitialized) + { + if (mouseparmsvalid) + restore_spi = SystemParametersInfo (SPI_SETMOUSE, 0, newmouseparms, 0); + mouseactive = 1; + } +} + +/* +=========== +IN_DeactivateMouse +=========== +*/ +void DLLEXPORT IN_DeactivateMouse (void) +{ + if (mouseinitialized) + { + if (restore_spi) + SystemParametersInfo (SPI_SETMOUSE, 0, originalmouseparms, 0); + + mouseactive = 0; + } +} + +/* +=========== +IN_StartupMouse +=========== +*/ +void IN_StartupMouse (void) +{ + if ( gEngfuncs.CheckParm ("-nomouse", NULL ) ) + return; + + mouseinitialized = 1; + mouseparmsvalid = SystemParametersInfo (SPI_GETMOUSE, 0, originalmouseparms, 0); + + if (mouseparmsvalid) + { + if ( gEngfuncs.CheckParm ("-noforcemspd", NULL ) ) + newmouseparms[2] = originalmouseparms[2]; + + if ( gEngfuncs.CheckParm ("-noforcemaccel", NULL ) ) + { + newmouseparms[0] = originalmouseparms[0]; + newmouseparms[1] = originalmouseparms[1]; + } + + if ( gEngfuncs.CheckParm ("-noforcemparms", NULL ) ) + { + newmouseparms[0] = originalmouseparms[0]; + newmouseparms[1] = originalmouseparms[1]; + newmouseparms[2] = originalmouseparms[2]; + } + } + + mouse_buttons = MOUSE_BUTTON_COUNT; +} + +/* +=========== +IN_Shutdown +=========== +*/ +void IN_Shutdown (void) +{ + IN_DeactivateMouse (); +} + +/* +=========== +IN_GetMousePos + +Ask for mouse position from engine +=========== +*/ +void IN_GetMousePos( int *mx, int *my ) +{ + gEngfuncs.GetMousePosition( mx, my ); +} + +/* +=========== +IN_ResetMouse + +FIXME: Call through to engine? +=========== +*/ +void IN_ResetMouse( void ) +{ + SetCursorPos ( gEngfuncs.GetWindowCenterX(), gEngfuncs.GetWindowCenterY() ); +} + +/* +=========== +IN_MouseEvent +=========== +*/ +void DLLEXPORT IN_MouseEvent (int mstate) +{ + int i; + + if ( iMouseInUse || g_iVisibleMouse ) + return; + + // perform button actions + for (i=0 ; ivalue) + { + 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) + { + V_StopPitchDrift (); + } + + 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; + } + } + + // if the mouse has moved, force it to the center, so there's room to move + if ( mx || my ) + { + IN_ResetMouse(); + } + } + + gEngfuncs.SetViewAngles( (float *)viewangles ); + +/* +//#define TRACE_TEST +#if defined( TRACE_TEST ) + { + int mx, my; + void V_Move( int mx, int my ); + IN_GetMousePos( &mx, &my ); + V_Move( mx, my ); + } +#endif +*/ +} + +/* +=========== +IN_Accumulate +=========== +*/ +void DLLEXPORT IN_Accumulate (void) +{ + //only accumulate mouse if we are not moving the camera with the mouse + if ( !iMouseInUse && !g_iVisibleMouse ) + { + if (mouseactive) + { + GetCursorPos (¤t_pos); + + mx_accum += current_pos.x - gEngfuncs.GetWindowCenterX(); + my_accum += current_pos.y - gEngfuncs.GetWindowCenterY(); + + // force the mouse to the center, so there's room to move + IN_ResetMouse(); + } + } + +} + +/* +=================== +IN_ClearStates +=================== +*/ +void DLLEXPORT IN_ClearStates (void) +{ + if ( !mouseactive ) + return; + + mx_accum = 0; + my_accum = 0; + mouse_oldbuttonstate = 0; +} + +/* +=============== +IN_StartupJoystick +=============== +*/ +void IN_StartupJoystick (void) +{ + int numdevs; + JOYCAPS jc; + MMRESULT mmr; + + // assume no joystick + joy_avail = 0; + + // abort startup if user requests no joystick + if ( gEngfuncs.CheckParm ("-nojoy", NULL ) ) + return; + + // verify joystick driver is present + if ((numdevs = joyGetNumDevs ()) == 0) + { + gEngfuncs.Con_DPrintf ("joystick not found -- driver not present\n\n"); + return; + } + + // cycle through the joystick ids for the first valid one + for (joy_id=0 ; joy_idvalue == 0.0) + { + // default joystick initialization + // 2 axes only with joystick control + dwAxisMap[JOY_AXIS_X] = AxisTurn; + // dwControlMap[JOY_AXIS_X] = JOY_ABSOLUTE_AXIS; + dwAxisMap[JOY_AXIS_Y] = AxisForward; + // dwControlMap[JOY_AXIS_Y] = JOY_ABSOLUTE_AXIS; + } + else + { + if ( strcmp ( joy_name->string, "joystick") != 0 ) + { + // notify user of advanced controller + gEngfuncs.Con_Printf ("\n%s configured\n\n", joy_name->string); + } + + // advanced initialization here + // data supplied by user via joy_axisn cvars + dwTemp = (DWORD) joy_advaxisx->value; + dwAxisMap[JOY_AXIS_X] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_X] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisy->value; + dwAxisMap[JOY_AXIS_Y] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_Y] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisz->value; + dwAxisMap[JOY_AXIS_Z] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_Z] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisr->value; + dwAxisMap[JOY_AXIS_R] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_R] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisu->value; + dwAxisMap[JOY_AXIS_U] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_U] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisv->value; + dwAxisMap[JOY_AXIS_V] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_V] = dwTemp & JOY_RELATIVE_AXIS; + } + + // compute the axes to collect from DirectInput + joy_flags = JOY_RETURNCENTERED | JOY_RETURNBUTTONS | JOY_RETURNPOV; + for (i = 0; i < JOY_MAX_AXES; i++) + { + if (dwAxisMap[i] != AxisNada) + { + joy_flags |= dwAxisFlags[i]; + } + } +} + + +/* +=========== +IN_Commands +=========== +*/ +void IN_Commands (void) +{ + int i, key_index; + DWORD buttonstate, povstate; + + if (!joy_avail) + { + return; + } + + + // loop through the joystick buttons + // key a joystick event or auxillary event for higher number buttons for each state change + buttonstate = ji.dwButtons; + for (i=0 ; i < (int)joy_numbuttons ; i++) + { + if ( (buttonstate & (1<value != 0.0) + { + ji.dwUpos += 100; + } + return 1; + } + else + { + // read error occurred + // turning off the joystick seems too harsh for 1 read error,\ + // but what should be done? + // Con_Printf ("IN_ReadJoystick: no response\n"); + // joy_avail = 0; + return 0; + } +} + + +/* +=========== +IN_JoyMove +=========== +*/ +void IN_JoyMove ( float frametime, usercmd_t *cmd ) +{ + float speed, aspeed; + float fAxisValue, fTemp; + int i; + vec3_t viewangles; + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + + // complete initialization if first time in + // this is needed as cvars are not available at initialization time + if( joy_advancedinit != 1 ) + { + Joy_AdvancedUpdate_f(); + joy_advancedinit = 1; + } + + // verify joystick is available and that the user wants to use it + if (!joy_avail || !in_joystick->value) + { + return; + } + + // collect the joystick data, if possible + if (IN_ReadJoystick () != 1) + { + return; + } + + if (in_speed.state & 1) + speed = cl_movespeedkey->value; + else + speed = 1; + + aspeed = speed * frametime; + + // loop through the axes + for (i = 0; i < JOY_MAX_AXES; i++) + { + // get the floating point zero-centered, potentially-inverted data for the current axis + fAxisValue = (float) *pdwRawValue[i]; + // move centerpoint to zero + fAxisValue -= 32768.0; + + if (joy_wwhack2->value != 0.0) + { + if (dwAxisMap[i] == AxisTurn) + { + // this is a special formula for the Logitech WingMan Warrior + // y=ax^b; where a = 300 and b = 1.3 + // also x values are in increments of 800 (so this is factored out) + // then bounds check result to level out excessively high spin rates + fTemp = 300.0 * pow(abs(fAxisValue) / 800.0, 1.3); + if (fTemp > 14000.0) + fTemp = 14000.0; + // restore direction information + fAxisValue = (fAxisValue > 0.0) ? fTemp : -fTemp; + } + } + + // convert range from -32768..32767 to -1..1 + fAxisValue /= 32768.0; + + switch (dwAxisMap[i]) + { + case AxisForward: + if ((joy_advanced->value == 0.0) && (in_jlook.state & 1)) + { + // user wants forward control to become look control + if (fabs(fAxisValue) > joy_pitchthreshold->value) + { + // if mouse invert is on, invert the joystick pitch value + // only absolute control support here (joy_advanced is 0) + if (m_pitch->value < 0.0) + { + viewangles[PITCH] -= (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value; + } + else + { + viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value; + } + V_StopPitchDrift(); + } + else + { + // no pitch movement + // disable pitch return-to-center unless requested by user + // *** this code can be removed when the lookspring bug is fixed + // *** the bug always has the lookspring feature on + if(lookspring->value == 0.0) + { + V_StopPitchDrift(); + } + } + } + else + { + // user wants forward control to be forward control + if (fabs(fAxisValue) > joy_forwardthreshold->value) + { + cmd->forwardmove += (fAxisValue * joy_forwardsensitivity->value) * speed * cl_forwardspeed->value; + } + } + break; + + case AxisSide: + if (fabs(fAxisValue) > joy_sidethreshold->value) + { + cmd->sidemove += (fAxisValue * joy_sidesensitivity->value) * speed * cl_sidespeed->value; + } + break; + + case AxisTurn: + if ((in_strafe.state & 1) || (lookstrafe->value && (in_jlook.state & 1))) + { + // user wants turn control to become side control + if (fabs(fAxisValue) > joy_sidethreshold->value) + { + cmd->sidemove -= (fAxisValue * joy_sidesensitivity->value) * speed * cl_sidespeed->value; + } + } + else + { + // user wants turn control to be turn control + if (fabs(fAxisValue) > joy_yawthreshold->value) + { + if(dwControlMap[i] == JOY_ABSOLUTE_AXIS) + { + viewangles[YAW] += (fAxisValue * joy_yawsensitivity->value) * aspeed * cl_yawspeed->value; + } + else + { + viewangles[YAW] += (fAxisValue * joy_yawsensitivity->value) * speed * 180.0; + } + + } + } + break; + + case AxisLook: + if (in_jlook.state & 1) + { + if (fabs(fAxisValue) > joy_pitchthreshold->value) + { + // pitch movement detected and pitch movement desired by user + if(dwControlMap[i] == JOY_ABSOLUTE_AXIS) + { + viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value; + } + else + { + viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * speed * 180.0; + } + V_StopPitchDrift(); + } + else + { + // no pitch movement + // disable pitch return-to-center unless requested by user + // *** this code can be removed when the lookspring bug is fixed + // *** the bug always has the lookspring feature on + if( lookspring->value == 0.0 ) + { + V_StopPitchDrift(); + } + } + } + break; + + default: + break; + } + } + + // bounds check pitch + if (viewangles[PITCH] > cl_pitchdown->value) + viewangles[PITCH] = cl_pitchdown->value; + if (viewangles[PITCH] < -cl_pitchup->value) + viewangles[PITCH] = -cl_pitchup->value; + + gEngfuncs.SetViewAngles( (float *)viewangles ); + +} + +/* +=========== +IN_Move +=========== +*/ +void IN_Move ( float frametime, usercmd_t *cmd) +{ + if ( !iMouseInUse && mouseactive ) + { + IN_MouseMove ( frametime, cmd); + } + + IN_JoyMove ( frametime, cmd); +} + +/* +=========== +IN_Init +=========== +*/ +void IN_Init (void) +{ + m_filter = gEngfuncs.pfnRegisterVariable ( "m_filter","0", FCVAR_ARCHIVE ); + sensitivity = gEngfuncs.pfnRegisterVariable ( "sensitivity","3", FCVAR_ARCHIVE ); // user mouse sensitivity setting. + + in_joystick = gEngfuncs.pfnRegisterVariable ( "joystick","0", FCVAR_ARCHIVE ); + joy_name = gEngfuncs.pfnRegisterVariable ( "joyname", "joystick", 0 ); + joy_advanced = gEngfuncs.pfnRegisterVariable ( "joyadvanced", "0", 0 ); + joy_advaxisx = gEngfuncs.pfnRegisterVariable ( "joyadvaxisx", "0", 0 ); + joy_advaxisy = gEngfuncs.pfnRegisterVariable ( "joyadvaxisy", "0", 0 ); + joy_advaxisz = gEngfuncs.pfnRegisterVariable ( "joyadvaxisz", "0", 0 ); + joy_advaxisr = gEngfuncs.pfnRegisterVariable ( "joyadvaxisr", "0", 0 ); + joy_advaxisu = gEngfuncs.pfnRegisterVariable ( "joyadvaxisu", "0", 0 ); + joy_advaxisv = gEngfuncs.pfnRegisterVariable ( "joyadvaxisv", "0", 0 ); + joy_forwardthreshold = gEngfuncs.pfnRegisterVariable ( "joyforwardthreshold", "0.15", 0 ); + joy_sidethreshold = gEngfuncs.pfnRegisterVariable ( "joysidethreshold", "0.15", 0 ); + joy_pitchthreshold = gEngfuncs.pfnRegisterVariable ( "joypitchthreshold", "0.15", 0 ); + joy_yawthreshold = gEngfuncs.pfnRegisterVariable ( "joyyawthreshold", "0.15", 0 ); + joy_forwardsensitivity = gEngfuncs.pfnRegisterVariable ( "joyforwardsensitivity", "-1.0", 0 ); + joy_sidesensitivity = gEngfuncs.pfnRegisterVariable ( "joysidesensitivity", "-1.0", 0 ); + joy_pitchsensitivity = gEngfuncs.pfnRegisterVariable ( "joypitchsensitivity", "1.0", 0 ); + joy_yawsensitivity = gEngfuncs.pfnRegisterVariable ( "joyyawsensitivity", "-1.0", 0 ); + joy_wwhack1 = gEngfuncs.pfnRegisterVariable ( "joywwhack1", "0.0", 0 ); + joy_wwhack2 = gEngfuncs.pfnRegisterVariable ( "joywwhack2", "0.0", 0 ); + + gEngfuncs.pfnAddCommand ("force_centerview", Force_CenterView_f); + gEngfuncs.pfnAddCommand ("joyadvancedupdate", Joy_AdvancedUpdate_f); + + IN_StartupMouse (); + IN_StartupJoystick (); +} diff --git a/hlsdk/ricochet/cl_dll/kbutton.h b/hlsdk/ricochet/cl_dll/kbutton.h new file mode 100644 index 00000000..f4ccf852 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/kbutton.h @@ -0,0 +1,25 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +#if !defined( KBUTTONH ) +#define KBUTTONH +#pragma once + +typedef struct kbutton_s +{ + int down[2]; // key nums holding it down + int state; // low bit is down state +} kbutton_t; + +#endif // !KBUTTONH \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/menu.cpp b/hlsdk/ricochet/cl_dll/menu.cpp new file mode 100644 index 00000000..b066bd94 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/menu.cpp @@ -0,0 +1,187 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// +// menu.cpp +// +// generic menu handler +// +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "vgui_TeamFortressViewport.h" + +#define MAX_MENU_STRING 512 +char g_szMenuString[MAX_MENU_STRING]; +char g_szPrelocalisedMenuString[MAX_MENU_STRING]; + +int KB_ConvertString( char *in, char **ppout ); + +DECLARE_MESSAGE( m_Menu, ShowMenu ); + +int CHudMenu :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( ShowMenu ); + + InitHUDData(); + + return 1; +} + +void CHudMenu :: InitHUDData( void ) +{ + m_fMenuDisplayed = 0; + m_bitsValidSlots = 0; + Reset(); +} + +void CHudMenu :: Reset( void ) +{ + g_szPrelocalisedMenuString[0] = 0; + m_fWaitingForMore = FALSE; +} + +int CHudMenu :: VidInit( void ) +{ + return 1; +} + +int CHudMenu :: Draw( float flTime ) +{ + // check for if menu is set to disappear + if ( m_flShutoffTime > 0 ) + { + if ( m_flShutoffTime <= gHUD.m_flTime ) + { // times up, shutoff + m_fMenuDisplayed = 0; + m_iFlags &= ~HUD_ACTIVE; + return 1; + } + } + + // don't draw the menu if the scoreboard is being shown + if ( gViewPort && gViewPort->IsScoreBoardVisible() ) + return 1; + + // draw the menu, along the left-hand side of the screen + + // count the number of newlines + int nlc = 0, i = 0; + for ( i = 0; i < MAX_MENU_STRING && g_szMenuString[i] != '\0'; i++ ) + { + if ( g_szMenuString[i] == '\n' ) + nlc++; + } + + // center it + int y = (ScreenHeight/2) - ((nlc/2)*12) - 40; // make sure it is above the say text + int x = 20; + + i = 0; + while ( i < MAX_MENU_STRING && g_szMenuString[i] != '\0' ) + { + gHUD.DrawHudString( x, y, 320, g_szMenuString + i, 255, 255, 255 ); + y += 12; + + while ( i < MAX_MENU_STRING && g_szMenuString[i] != '\0' && g_szMenuString[i] != '\n' ) + i++; + if ( g_szMenuString[i] == '\n' ) + i++; + } + + return 1; +} + +// selects an item from the menu +void CHudMenu :: SelectMenuItem( int menu_item ) +{ + // if menu_item is in a valid slot, send a menuselect command to the server + if ( (menu_item > 0) && (m_bitsValidSlots & (1 << (menu_item-1))) ) + { + char szbuf[32]; + sprintf( szbuf, "menuselect %d\n", menu_item ); + ClientCmd( szbuf ); + + // remove the menu + m_fMenuDisplayed = 0; + m_iFlags &= ~HUD_ACTIVE; + } +} + + +// Message handler for ShowMenu message +// takes four values: +// short: a bitfield of keys that are valid input +// char : the duration, in seconds, the menu should stay up. -1 means is stays until something is chosen. +// byte : a boolean, TRUE if there is more string yet to be received before displaying the menu, FALSE if it's the last string +// string: menu string to display +// if this message is never received, then scores will simply be the combined totals of the players. +int CHudMenu :: MsgFunc_ShowMenu( const char *pszName, int iSize, void *pbuf ) +{ + char *temp = NULL; + + BEGIN_READ( pbuf, iSize ); + + m_bitsValidSlots = READ_SHORT(); + int DisplayTime = READ_CHAR(); + int NeedMore = READ_BYTE(); + + if ( DisplayTime > 0 ) + m_flShutoffTime = DisplayTime + gHUD.m_flTime; + else + m_flShutoffTime = -1; + + if ( m_bitsValidSlots ) + { + if ( !m_fWaitingForMore ) // this is the start of a new menu + { + strncpy( g_szPrelocalisedMenuString, READ_STRING(), MAX_MENU_STRING ); + } + else + { // append to the current menu string + strncat( g_szPrelocalisedMenuString, READ_STRING(), MAX_MENU_STRING - strlen(g_szPrelocalisedMenuString) ); + } + g_szPrelocalisedMenuString[MAX_MENU_STRING-1] = 0; // ensure null termination (strncat/strncpy does not) + + if ( !NeedMore ) + { // we have the whole string, so we can localise it now + strcpy( g_szMenuString, gHUD.m_TextMessage.BufferedLocaliseTextString( g_szPrelocalisedMenuString ) ); + + // Swap in characters + if ( KB_ConvertString( g_szMenuString, &temp ) ) + { + strcpy( g_szMenuString, temp ); + free( temp ); + } + } + + m_fMenuDisplayed = 1; + m_iFlags |= HUD_ACTIVE; + } + else + { + m_fMenuDisplayed = 0; // no valid slots means that the menu should be turned off + m_iFlags &= ~HUD_ACTIVE; + } + + m_fWaitingForMore = NeedMore; + + return 1; +} diff --git a/hlsdk/ricochet/cl_dll/message.cpp b/hlsdk/ricochet/cl_dll/message.cpp new file mode 100644 index 00000000..3307b251 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/message.cpp @@ -0,0 +1,463 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// +// Message.cpp +// +// implementation of CHudMessage class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE( m_Message, HudText ) +DECLARE_MESSAGE( m_Message, GameTitle ) + + +int CHudMessage::Init(void) +{ + HOOK_MESSAGE( HudText ); + HOOK_MESSAGE( GameTitle ); + + gHUD.AddHudElem(this); + + Reset(); + + return 1; +}; + +int CHudMessage::VidInit( void ) +{ + m_HUD_title_half = gHUD.GetSpriteIndex( "title_half" ); + m_HUD_title_life = gHUD.GetSpriteIndex( "title_life" ); + + return 1; +}; + + +void CHudMessage::Reset( void ) +{ + memset( m_pMessages, 0, sizeof( m_pMessages[0] ) * maxHUDMessages ); + memset( m_startTime, 0, sizeof( m_startTime[0] ) * maxHUDMessages ); + + m_gameTitleTime = 0; + m_pGameTitle = NULL; +} + + +float CHudMessage::FadeBlend( float fadein, float fadeout, float hold, float localTime ) +{ + float fadeTime = fadein + hold; + float fadeBlend; + + if ( localTime < 0 ) + return 0; + + if ( localTime < fadein ) + { + fadeBlend = 1 - ((fadein - localTime) / fadein); + } + else if ( localTime > fadeTime ) + { + if ( fadeout > 0 ) + fadeBlend = 1 - ((localTime - fadeTime) / fadeout); + else + fadeBlend = 0; + } + else + fadeBlend = 1; + + return fadeBlend; +} + + +int CHudMessage::XPosition( float x, int width, int totalWidth ) +{ + int xPos; + + if ( x == -1 ) + { + xPos = (ScreenWidth - width) / 2; + } + else + { + if ( x < 0 ) + xPos = (1.0 + x) * ScreenWidth - totalWidth; // Alight right + else + xPos = x * ScreenWidth; + } + + if ( xPos + width > ScreenWidth ) + xPos = ScreenWidth - width; + else if ( xPos < 0 ) + xPos = 0; + + return xPos; +} + + +int CHudMessage::YPosition( float y, int height ) +{ + int yPos; + + if ( y == -1 ) // Centered? + yPos = (ScreenHeight - height) * 0.5; + else + { + // Alight bottom? + if ( y < 0 ) + yPos = (1.0 + y) * ScreenHeight - height; // Alight bottom + else // align top + yPos = y * ScreenHeight; + } + + if ( yPos + height > ScreenHeight ) + yPos = ScreenHeight - height; + else if ( yPos < 0 ) + yPos = 0; + + return yPos; +} + + +void CHudMessage::MessageScanNextChar( void ) +{ + int srcRed, srcGreen, srcBlue, destRed, destGreen, destBlue; + int blend; + + srcRed = m_parms.pMessage->r1; + srcGreen = m_parms.pMessage->g1; + srcBlue = m_parms.pMessage->b1; + blend = 0; // Pure source + + switch( m_parms.pMessage->effect ) + { + // Fade-in / Fade-out + case 0: + case 1: + destRed = destGreen = destBlue = 0; + blend = m_parms.fadeBlend; + break; + + case 2: + m_parms.charTime += m_parms.pMessage->fadein; + if ( m_parms.charTime > m_parms.time ) + { + srcRed = srcGreen = srcBlue = 0; + blend = 0; // pure source + } + else + { + float deltaTime = m_parms.time - m_parms.charTime; + + destRed = destGreen = destBlue = 0; + if ( m_parms.time > m_parms.fadeTime ) + { + blend = m_parms.fadeBlend; + } + else if ( deltaTime > m_parms.pMessage->fxtime ) + blend = 0; // pure dest + else + { + destRed = m_parms.pMessage->r2; + destGreen = m_parms.pMessage->g2; + destBlue = m_parms.pMessage->b2; + blend = 255 - (deltaTime * (1.0/m_parms.pMessage->fxtime) * 255.0 + 0.5); + } + } + break; + } + if ( blend > 255 ) + blend = 255; + else if ( blend < 0 ) + blend = 0; + + m_parms.r = ((srcRed * (255-blend)) + (destRed * blend)) >> 8; + m_parms.g = ((srcGreen * (255-blend)) + (destGreen * blend)) >> 8; + m_parms.b = ((srcBlue * (255-blend)) + (destBlue * blend)) >> 8; + + if ( m_parms.pMessage->effect == 1 && m_parms.charTime != 0 ) + { + if ( m_parms.x >= 0 && m_parms.y >= 0 && (m_parms.x + gHUD.m_scrinfo.charWidths[ m_parms.text ]) <= ScreenWidth ) + TextMessageDrawChar( m_parms.x, m_parms.y, m_parms.text, m_parms.pMessage->r2, m_parms.pMessage->g2, m_parms.pMessage->b2 ); + } +} + + +void CHudMessage::MessageScanStart( void ) +{ + switch( m_parms.pMessage->effect ) + { + // Fade-in / out with flicker + case 1: + case 0: + m_parms.fadeTime = m_parms.pMessage->fadein + m_parms.pMessage->holdtime; + + + if ( m_parms.time < m_parms.pMessage->fadein ) + { + m_parms.fadeBlend = ((m_parms.pMessage->fadein - m_parms.time) * (1.0/m_parms.pMessage->fadein) * 255); + } + else if ( m_parms.time > m_parms.fadeTime ) + { + if ( m_parms.pMessage->fadeout > 0 ) + m_parms.fadeBlend = (((m_parms.time - m_parms.fadeTime) / m_parms.pMessage->fadeout) * 255); + else + m_parms.fadeBlend = 255; // Pure dest (off) + } + else + m_parms.fadeBlend = 0; // Pure source (on) + m_parms.charTime = 0; + + if ( m_parms.pMessage->effect == 1 && (rand()%100) < 10 ) + m_parms.charTime = 1; + break; + + case 2: + m_parms.fadeTime = (m_parms.pMessage->fadein * m_parms.length) + m_parms.pMessage->holdtime; + + if ( m_parms.time > m_parms.fadeTime && m_parms.pMessage->fadeout > 0 ) + m_parms.fadeBlend = (((m_parms.time - m_parms.fadeTime) / m_parms.pMessage->fadeout) * 255); + else + m_parms.fadeBlend = 0; + break; + } +} + + +void CHudMessage::MessageDrawScan( client_textmessage_t *pMessage, float time ) +{ + int i, j, length, width; + const char *pText; + unsigned char line[80]; + + pText = pMessage->pMessage; + // Count lines + m_parms.lines = 1; + m_parms.time = time; + m_parms.pMessage = pMessage; + length = 0; + width = 0; + m_parms.totalWidth = 0; + while ( *pText ) + { + if ( *pText == '\n' ) + { + m_parms.lines++; + if ( width > m_parms.totalWidth ) + m_parms.totalWidth = width; + width = 0; + } + else + width += gHUD.m_scrinfo.charWidths[*pText]; + pText++; + length++; + } + m_parms.length = length; + m_parms.totalHeight = (m_parms.lines * gHUD.m_scrinfo.iCharHeight); + + + m_parms.y = YPosition( pMessage->y, m_parms.totalHeight ); + pText = pMessage->pMessage; + + m_parms.charTime = 0; + + MessageScanStart(); + + for ( i = 0; i < m_parms.lines; i++ ) + { + m_parms.lineLength = 0; + m_parms.width = 0; + while ( *pText && *pText != '\n' ) + { + unsigned char c = *pText; + line[m_parms.lineLength] = c; + m_parms.width += gHUD.m_scrinfo.charWidths[c]; + m_parms.lineLength++; + pText++; + } + pText++; // Skip LF + line[m_parms.lineLength] = 0; + + m_parms.x = XPosition( pMessage->x, m_parms.width, m_parms.totalWidth ); + + for ( j = 0; j < m_parms.lineLength; j++ ) + { + m_parms.text = line[j]; + int next = m_parms.x + gHUD.m_scrinfo.charWidths[ m_parms.text ]; + MessageScanNextChar(); + + if ( m_parms.x >= 0 && m_parms.y >= 0 && next <= ScreenWidth ) + TextMessageDrawChar( m_parms.x, m_parms.y, m_parms.text, m_parms.r, m_parms.g, m_parms.b ); + m_parms.x = next; + } + + m_parms.y += gHUD.m_scrinfo.iCharHeight; + } +} + + +int CHudMessage::Draw( float fTime ) +{ + int i, drawn; + client_textmessage_t *pMessage; + float endTime; + + drawn = 0; + + if ( m_gameTitleTime > 0 ) + { + float localTime = gHUD.m_flTime - m_gameTitleTime; + float brightness; + + // Maybe timer isn't set yet + if ( m_gameTitleTime > gHUD.m_flTime ) + m_gameTitleTime = gHUD.m_flTime; + + if ( localTime > (m_pGameTitle->fadein + m_pGameTitle->holdtime + m_pGameTitle->fadeout) ) + m_gameTitleTime = 0; + else + { + brightness = FadeBlend( m_pGameTitle->fadein, m_pGameTitle->fadeout, m_pGameTitle->holdtime, localTime ); + + int halfWidth = gHUD.GetSpriteRect(m_HUD_title_half).right - gHUD.GetSpriteRect(m_HUD_title_half).left; + int fullWidth = halfWidth + gHUD.GetSpriteRect(m_HUD_title_life).right - gHUD.GetSpriteRect(m_HUD_title_life).left; + int fullHeight = gHUD.GetSpriteRect(m_HUD_title_half).bottom - gHUD.GetSpriteRect(m_HUD_title_half).top; + + int x = XPosition( m_pGameTitle->x, fullWidth, fullWidth ); + int y = YPosition( m_pGameTitle->y, fullHeight ); + + + SPR_Set( gHUD.GetSprite(m_HUD_title_half), brightness * m_pGameTitle->r1, brightness * m_pGameTitle->g1, brightness * m_pGameTitle->b1 ); + SPR_DrawAdditive( 0, x, y, &gHUD.GetSpriteRect(m_HUD_title_half) ); + + SPR_Set( gHUD.GetSprite(m_HUD_title_life), brightness * m_pGameTitle->r1, brightness * m_pGameTitle->g1, brightness * m_pGameTitle->b1 ); + SPR_DrawAdditive( 0, x + halfWidth, y, &gHUD.GetSpriteRect(m_HUD_title_life) ); + + drawn = 1; + } + } + // Fixup level transitions + for ( i = 0; i < maxHUDMessages; i++ ) + { + // Assume m_parms.time contains last time + if ( m_pMessages[i] ) + { + pMessage = m_pMessages[i]; + if ( m_startTime[i] > gHUD.m_flTime ) + m_startTime[i] = gHUD.m_flTime + m_parms.time - m_startTime[i] + 0.2; // Server takes 0.2 seconds to spawn, adjust for this + } + } + + for ( i = 0; i < maxHUDMessages; i++ ) + { + if ( m_pMessages[i] ) + { + pMessage = m_pMessages[i]; + + // This is when the message is over + switch( pMessage->effect ) + { + case 0: + case 1: + endTime = m_startTime[i] + pMessage->fadein + pMessage->fadeout + pMessage->holdtime; + break; + + // Fade in is per character in scanning messages + case 2: + endTime = m_startTime[i] + (pMessage->fadein * strlen( pMessage->pMessage )) + pMessage->fadeout + pMessage->holdtime; + break; + } + + if ( fTime <= endTime ) + { + float messageTime = fTime - m_startTime[i]; + + // Draw the message + // effect 0 is fade in/fade out + // effect 1 is flickery credits + // effect 2 is write out (training room) + MessageDrawScan( pMessage, messageTime ); + + drawn++; + } + else + { + // The message is over + m_pMessages[i] = NULL; + } + } + } + + // Remember the time -- to fix up level transitions + m_parms.time = gHUD.m_flTime; + // Don't call until we get another message + if ( !drawn ) + m_iFlags &= ~HUD_ACTIVE; + + return 1; +} + + +void CHudMessage::MessageAdd( const char *pName, float time ) +{ + int i; + + for ( i = 0; i < maxHUDMessages; i++ ) + { + if ( !m_pMessages[i] ) + { + m_pMessages[i] = TextMessageGet( pName ); + m_startTime[i] = time; + return; + } + } +} + + +int CHudMessage::MsgFunc_HudText( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + char *pString = READ_STRING(); + + MessageAdd( pString, gHUD.m_flTime ); + // Remember the time -- to fix up level transitions + m_parms.time = gHUD.m_flTime; + + // Turn on drawing + if ( !(m_iFlags & HUD_ACTIVE) ) + m_iFlags |= HUD_ACTIVE; + + return 1; +} + + +int CHudMessage::MsgFunc_GameTitle( const char *pszName, int iSize, void *pbuf ) +{ + m_pGameTitle = TextMessageGet( "GAMETITLE" ); + if ( m_pGameTitle != NULL ) + { + m_gameTitleTime = gHUD.m_flTime; + + // Turn on drawing + if ( !(m_iFlags & HUD_ACTIVE) ) + m_iFlags |= HUD_ACTIVE; + } + + return 1; +} diff --git a/hlsdk/ricochet/cl_dll/msvc6/cl_dll.dsp b/hlsdk/ricochet/cl_dll/msvc6/cl_dll.dsp new file mode 100644 index 00000000..2225de47 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/msvc6/cl_dll.dsp @@ -0,0 +1,524 @@ +# Microsoft Developer Studio Project File - Name="cl_dll" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=cl_dll - 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 "cl_dll.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 "cl_dll.mak" CFG="cl_dll - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "cl_dll - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "cl_dll - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "cl_dll - 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 ".\Release" +# PROP Intermediate_Dir ".\Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /MT /W3 /GX /Zi /O2 /I "..\" /I "..\..\" /I "..\..\dlls" /I "..\..\..\engine" /I "..\..\..\common" /I "..\..\..\game_shared" /I "..\..\pm_shared" /I "..\..\..\utils\vgui\include" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "CLIENT_DLL" /D "RICOCHET_BUILD" /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 winmm.lib ../../../utils/vgui/lib/win32_vc6/vgui.lib wsock32.lib /nologo /subsystem:windows /dll /map /machine:I386 /out:".\Release\client.dll" +# SUBTRACT LINK32 /debug + +!ELSEIF "$(CFG)" == "cl_dll - 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 ".\Debug" +# PROP Intermediate_Dir ".\Debug" +# 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 /GX /ZI /Od /I "..\" /I "..\..\dlls" /I "..\..\" /I "..\..\..\game_shared" /I "..\..\..\engine" /I "..\..\..\common" /I "..\..\pm_shared" /I "..\..\..\utils\vgui\include" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "CLIENT_DLL" /D "RICOCHET_BUILD" /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 /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 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" + +!ENDIF + +# Begin Target + +# Name "cl_dll - Win32 Release" +# Name "cl_dll - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Group "hl" + +# PROP Default_Filter "*.cpp" +# Begin Source File + +SOURCE=..\..\dlls\wpn_shared\disc_weapon_disc.cpp +# End Source File +# Begin Source File + +SOURCE=..\ev_hldm.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=..\Ricochet_JumpPads.cpp +# End Source File +# End Group +# Begin Source File + +SOURCE=..\ammo.cpp +# End Source File +# Begin Source File + +SOURCE=..\ammo_secondary.cpp +# End Source File +# Begin Source File + +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 + +SOURCE=..\com_weapons.cpp +# End Source File +# Begin Source File + +SOURCE=..\death.cpp +# End Source File +# Begin Source File + +SOURCE=..\demo.cpp +# End Source File +# Begin Source File + +SOURCE=..\entity.cpp +# End Source File +# Begin Source File + +SOURCE=..\ev_common.cpp +# End Source File +# Begin Source File + +SOURCE=..\events.cpp +# End Source File +# Begin Source File + +SOURCE=..\flashlight.cpp +# End Source File +# Begin Source File + +SOURCE=..\GameStudioModelRenderer.cpp +# End Source File +# Begin Source File + +SOURCE=..\geiger.cpp +# End Source File +# Begin Source File + +SOURCE=..\health.cpp +# End Source File +# Begin Source File + +SOURCE=..\hud.cpp +# End Source File +# Begin Source File + +SOURCE=..\hud_msg.cpp +# End Source File +# Begin Source File + +SOURCE=..\hud_redraw.cpp +# End Source File +# Begin Source File + +SOURCE=..\hud_servers.cpp +# End Source File +# Begin Source File + +SOURCE=..\hud_update.cpp +# End Source File +# Begin Source File + +SOURCE=..\in_camera.cpp +# End Source File +# Begin Source File + +SOURCE=..\input.cpp +# End Source File +# Begin Source File + +SOURCE=..\inputw32.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\interface.cpp +# End Source File +# Begin Source File + +SOURCE=..\menu.cpp +# End Source File +# Begin Source File + +SOURCE=..\message.cpp +# End Source File +# Begin Source File + +SOURCE=..\parsemsg.cpp +# End Source File +# Begin Source File + +SOURCE=..\parsemsg.h +# 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=..\saytext.cpp +# End Source File +# Begin Source File + +SOURCE=..\status_icons.cpp +# End Source File +# Begin Source File + +SOURCE=..\statusbar.cpp +# End Source File +# Begin Source File + +SOURCE=..\studio_util.cpp +# End Source File +# Begin Source File + +SOURCE=..\StudioModelRenderer.cpp +# End Source File +# Begin Source File + +SOURCE=..\text_message.cpp +# End Source File +# Begin Source File + +SOURCE=..\train.cpp +# End Source File +# Begin Source File + +SOURCE=..\tri.cpp +# End Source File +# Begin Source File + +SOURCE=..\util.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\game_shared\vgui_checkbutton2.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_ConsolePanel.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_ControlConfigPanel.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_CustomObjects.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_discobjects.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\game_shared\vgui_grid.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\game_shared\vgui_helpers.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_int.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\game_shared\vgui_listbox.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\game_shared\vgui_loadtga.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_MOTDWindow.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_SchemeManager.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_ScorePanel.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\game_shared\vgui_scrollbar2.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_ServerBrowser.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\game_shared\vgui_slider2.cpp +# End Source File +# Begin Source File + +SOURCE=..\vgui_TeamFortressViewport.cpp +# End Source File +# Begin Source File + +SOURCE=..\view.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\game_shared\voice_banmgr.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\game_shared\voice_status.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=..\ammo.h +# End Source File +# Begin Source File + +SOURCE=..\ammohistory.h +# End Source File +# Begin Source File + +SOURCE=..\camera.h +# End Source File +# Begin Source File + +SOURCE=..\cl_dll.h +# End Source File +# Begin Source File + +SOURCE=..\com_weapons.h +# End Source File +# Begin Source File + +SOURCE=..\demo.h +# End Source File +# Begin Source File + +SOURCE=..\ev_hldm.h +# End Source File +# Begin Source File + +SOURCE=..\eventscripts.h +# End Source File +# Begin Source File + +SOURCE=..\GameStudioModelRenderer.h +# End Source File +# Begin Source File + +SOURCE=..\health.h +# End Source File +# Begin Source File + +SOURCE=..\hud.h +# End Source File +# Begin Source File + +SOURCE=..\hud_iface.h +# End Source File +# Begin Source File + +SOURCE=..\hud_servers.h +# End Source File +# Begin Source File + +SOURCE=..\hud_servers_priv.h +# End Source File +# Begin Source File + +SOURCE=..\in_defs.h +# End Source File +# Begin Source File + +SOURCE=..\kbutton.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=..\Ricochet_BSPFile.h +# End Source File +# Begin Source File + +SOURCE=..\Ricochet_JumpPads.h +# End Source File +# Begin Source File + +SOURCE=..\studio_util.h +# End Source File +# Begin Source File + +SOURCE=..\StudioModelRenderer.h +# End Source File +# Begin Source File + +SOURCE=..\util_vector.h +# End Source File +# Begin Source File + +SOURCE=..\vgui_ConsolePanel.h +# End Source File +# Begin Source File + +SOURCE=..\vgui_ControlConfigPanel.h +# End Source File +# Begin Source File + +SOURCE=..\vgui_discobjects.h +# End Source File +# Begin Source File + +SOURCE=..\vgui_int.h +# End Source File +# Begin Source File + +SOURCE=..\vgui_SchemeManager.h +# End Source File +# Begin Source File + +SOURCE=..\vgui_ScorePanel.h +# End Source File +# Begin Source File + +SOURCE=..\vgui_ServerBrowser.h +# End Source File +# Begin Source File + +SOURCE=..\vgui_TeamFortressViewport.h +# End Source File +# Begin Source File + +SOURCE=..\view.h +# End Source File +# Begin Source File + +SOURCE=..\wrect.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/hlsdk/ricochet/cl_dll/msvc7/cl_dll.vcproj b/hlsdk/ricochet/cl_dll/msvc7/cl_dll.vcproj new file mode 100644 index 00000000..15701c08 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/msvc7/cl_dll.vcproj @@ -0,0 +1,1606 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hlsdk/ricochet/cl_dll/msvc8/cl_dll.vcproj b/hlsdk/ricochet/cl_dll/msvc8/cl_dll.vcproj new file mode 100644 index 00000000..e4e6723b --- /dev/null +++ b/hlsdk/ricochet/cl_dll/msvc8/cl_dll.vcproj @@ -0,0 +1,2040 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hlsdk/ricochet/cl_dll/parsemsg.cpp b/hlsdk/ricochet/cl_dll/parsemsg.cpp new file mode 100644 index 00000000..1fde25ee --- /dev/null +++ b/hlsdk/ricochet/cl_dll/parsemsg.cpp @@ -0,0 +1,166 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// +// parsemsg.cpp +// +typedef unsigned char byte; +#define true 1 + +static byte *gpBuf; +static int giSize; +static int giRead; +static int giBadRead; + +void BEGIN_READ( void *buf, int size ) +{ + giRead = 0; + giBadRead = 0; + giSize = size; + gpBuf = (byte*)buf; +} + + +int READ_CHAR( void ) +{ + int c; + + if (giRead + 1 > giSize) + { + giBadRead = true; + return -1; + } + + c = (signed char)gpBuf[giRead]; + giRead++; + + return c; +} + +int READ_BYTE( void ) +{ + int c; + + if (giRead+1 > giSize) + { + giBadRead = true; + return -1; + } + + c = (unsigned char)gpBuf[giRead]; + giRead++; + + return c; +} + +int READ_SHORT( void ) +{ + int c; + + if (giRead+2 > giSize) + { + giBadRead = true; + return -1; + } + + c = (short)( gpBuf[giRead] + ( gpBuf[giRead+1] << 8 ) ); + + giRead += 2; + + return c; +} + +int READ_WORD( void ) +{ + return READ_SHORT(); +} + + +int READ_LONG( void ) +{ + int c; + + if (giRead+4 > giSize) + { + giBadRead = true; + return -1; + } + + c = gpBuf[giRead] + (gpBuf[giRead + 1] << 8) + (gpBuf[giRead + 2] << 16) + (gpBuf[giRead + 3] << 24); + + giRead += 4; + + return c; +} + +float READ_FLOAT( void ) +{ + union + { + byte b[4]; + float f; + int l; + } dat; + + dat.b[0] = gpBuf[giRead]; + dat.b[1] = gpBuf[giRead+1]; + dat.b[2] = gpBuf[giRead+2]; + dat.b[3] = gpBuf[giRead+3]; + giRead += 4; + +// dat.l = LittleLong (dat.l); + + return dat.f; +} + +char* READ_STRING( void ) +{ + static char string[2048]; + int l,c; + + string[0] = 0; + + l = 0; + do + { + if ( giRead+1 > giSize ) + break; // no more characters + + c = READ_CHAR(); + if (c == -1 || c == 0) + break; + string[l] = c; + l++; + } while (l < sizeof(string)-1); + + string[l] = 0; + + return string; +} + +float READ_COORD( void ) +{ + return (float)(READ_SHORT() * (1.0/8)); +} + +float READ_ANGLE( void ) +{ + return (float)(READ_CHAR() * (360.0/256)); +} + +float READ_HIRESANGLE( void ) +{ + return (float)(READ_SHORT() * (360.0/65536)); +} + diff --git a/hlsdk/ricochet/cl_dll/parsemsg.h b/hlsdk/ricochet/cl_dll/parsemsg.h new file mode 100644 index 00000000..cc2b3ab8 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/parsemsg.h @@ -0,0 +1,40 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// +// parsemsg.h +// + +#define ASSERT( x ) + +void BEGIN_READ( void *buf, int size ); +int READ_CHAR( void ); +int READ_BYTE( void ); +int READ_SHORT( void ); +int READ_WORD( void ); +int READ_LONG( void ); +float READ_FLOAT( void ); +char* READ_STRING( void ); +float READ_COORD( void ); +float READ_ANGLE( void ); +float READ_HIRESANGLE( void ); + + + + + + + + + diff --git a/hlsdk/ricochet/cl_dll/saytext.cpp b/hlsdk/ricochet/cl_dll/saytext.cpp new file mode 100644 index 00000000..00e043c8 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/saytext.cpp @@ -0,0 +1,308 @@ +/*** +* +* 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. +* 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. +* +****/ +// +// saytext.cpp +// +// implementation of CHudSayText class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +extern float *GetClientColor( int clientIndex ); + +#define MAX_LINES 5 +#define MAX_CHARS_PER_LINE 256 /* it can be less than this, depending on char size */ + +// allow 20 pixels on either side of the text +#define MAX_LINE_WIDTH ( ScreenWidth - 40 ) +#define LINE_START 10 +static float SCROLL_SPEED = 5; + +static char g_szLineBuffer[ MAX_LINES + 1 ][ MAX_CHARS_PER_LINE ]; +static float *g_pflNameColors[ MAX_LINES + 1 ]; +static int g_iNameLengths[ MAX_LINES + 1 ]; +static float flScrollTime = 0; // the time at which the lines next scroll up + +static int Y_START = 0; +static int line_height = 0; + +DECLARE_MESSAGE( m_SayText, SayText ); + +int CHudSayText :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( SayText ); + + InitHUDData(); + + CVAR_CREATE( "hud_saytext_time", "5", 0 ); + + return 1; +} + + +void CHudSayText :: InitHUDData( void ) +{ + memset( g_szLineBuffer, 0, sizeof g_szLineBuffer ); + memset( g_pflNameColors, 0, sizeof g_pflNameColors ); + memset( g_iNameLengths, 0, sizeof g_iNameLengths ); +} + +int CHudSayText :: VidInit( void ) +{ + return 1; +} + + +int ScrollTextUp( void ) +{ + ConsolePrint( g_szLineBuffer[0] ); // move the first line into the console buffer + g_szLineBuffer[MAX_LINES][0] = 0; + memmove( g_szLineBuffer[0], g_szLineBuffer[1], sizeof(g_szLineBuffer) - sizeof(g_szLineBuffer[0]) ); // overwrite the first line + memmove( &g_pflNameColors[0], &g_pflNameColors[1], sizeof(g_pflNameColors) - sizeof(g_pflNameColors[0]) ); + memmove( &g_iNameLengths[0], &g_iNameLengths[1], sizeof(g_iNameLengths) - sizeof(g_iNameLengths[0]) ); + g_szLineBuffer[MAX_LINES-1][0] = 0; + + if ( g_szLineBuffer[0][0] == ' ' ) // also scroll up following lines + { + g_szLineBuffer[0][0] = 2; + return 1 + ScrollTextUp(); + } + + return 1; +} + +int CHudSayText :: Draw( float flTime ) +{ + int y = Y_START; + + // make sure the scrolltime is within reasonable bounds, to guard against the clock being reset + flScrollTime = min( flScrollTime, flTime + SCROLL_SPEED ); + + // make sure the scrolltime is within reasonable bounds, to guard against the clock being reset + flScrollTime = min( flScrollTime, flTime + SCROLL_SPEED ); + + if ( flScrollTime <= flTime ) + { + if ( *g_szLineBuffer[0] ) + { + flScrollTime = flTime + SCROLL_SPEED; + // push the console up + ScrollTextUp(); + } + else + { // buffer is empty, just disable drawing of this section + m_iFlags &= ~HUD_ACTIVE; + } + } + + for ( int i = 0; i < MAX_LINES; i++ ) + { + if ( *g_szLineBuffer[i] ) + { + if ( *g_szLineBuffer[i] == 2 && g_pflNameColors[i] ) + { + // it's a saytext string + static char buf[MAX_PLAYER_NAME_LENGTH+32]; + + // draw the first x characters in the player color + strncpy( buf, g_szLineBuffer[i], min(g_iNameLengths[i], MAX_PLAYER_NAME_LENGTH+32) ); + buf[ min(g_iNameLengths[i], MAX_PLAYER_NAME_LENGTH+31) ] = 0; + gEngfuncs.pfnDrawSetTextColor( g_pflNameColors[i][0], g_pflNameColors[i][1], g_pflNameColors[i][2] ); + int x = DrawConsoleString( LINE_START, y, buf ); + + // color is reset after each string draw + DrawConsoleString( x, y, g_szLineBuffer[i] + g_iNameLengths[i] ); + } + else + { + // normal draw + DrawConsoleString( LINE_START, y, g_szLineBuffer[i] ); + } + } + + y += line_height; + } + + + return 1; +} + +int CHudSayText :: MsgFunc_SayText( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int client_index = READ_BYTE(); // the client who spoke the message + SayTextPrint( READ_STRING(), iSize - 1, client_index ); + + return 1; +} + +void CHudSayText :: SayTextPrint( const char *pszBuf, int iBufSize, int clientIndex ) +{ + // find an empty string slot + int i; + for ( i = 0; i < MAX_LINES; i++ ) + { + if ( ! *g_szLineBuffer[i] ) + break; + } + if ( i == MAX_LINES ) + { + // force scroll buffer up + ScrollTextUp(); + i = MAX_LINES - 1; + } + + g_iNameLengths[i] = 0; + g_pflNameColors[i] = NULL; + + // if it's a say message, search for the players name in the string + if ( *pszBuf == 2 && clientIndex > 0 ) + { + GetPlayerInfo( clientIndex, &g_PlayerInfoList[clientIndex] ); + const char *pName = g_PlayerInfoList[clientIndex].name; + + if ( pName ) + { + const char *nameInString = strstr( pszBuf, pName ); + + if ( nameInString ) + { + g_iNameLengths[i] = strlen( pName ) + (nameInString - pszBuf); + g_pflNameColors[i] = GetClientColor( clientIndex ); + } + } + } + + strncpy( g_szLineBuffer[i], pszBuf, max(iBufSize -1, MAX_CHARS_PER_LINE-1) ); + + // make sure the text fits in one line + EnsureTextFitsInOneLineAndWrapIfHaveTo( i ); + + // Set scroll time + if ( i == 0 ) + { + SCROLL_SPEED = CVAR_GET_FLOAT( "hud_saytext_time" ); + flScrollTime = gHUD.m_flTime + SCROLL_SPEED; + } + + m_iFlags |= HUD_ACTIVE; + PlaySound( "misc/talk.wav", 1 ); + + if ( ScreenHeight >= 480 ) + Y_START = ScreenHeight - 45; + else + Y_START = ScreenHeight - 35; + Y_START -= (line_height * (MAX_LINES+1)); + +} + +void CHudSayText :: EnsureTextFitsInOneLineAndWrapIfHaveTo( int line ) +{ + int line_width = 0; + GetConsoleStringSize( g_szLineBuffer[line], &line_width, &line_height ); + + if ( (line_width + LINE_START) > MAX_LINE_WIDTH ) + { // string is too long to fit on line + // scan the string until we find what word is too long, and wrap the end of the sentence after the word + int length = LINE_START; + int tmp_len = 0; + char *last_break = NULL; + for ( char *x = g_szLineBuffer[line]; *x != 0; x++ ) + { + // check for a color change, if so skip past it + if ( x[0] == '/' && x[1] == '(' ) + { + x += 2; + // skip forward until past mode specifier + while ( *x != 0 && *x != ')' ) + x++; + + if ( *x != 0 ) + x++; + + if ( *x == 0 ) + break; + } + + char buf[2]; + buf[1] = 0; + + if ( *x == ' ' && x != g_szLineBuffer[line] ) // store each line break, except for the very first character + last_break = x; + + buf[0] = *x; // get the length of the current character + GetConsoleStringSize( buf, &tmp_len, &line_height ); + length += tmp_len; + + if ( length > MAX_LINE_WIDTH ) + { // needs to be broken up + if ( !last_break ) + last_break = x-1; + + x = last_break; + + // find an empty string slot + int j; + do + { + for ( j = 0; j < MAX_LINES; j++ ) + { + if ( ! *g_szLineBuffer[j] ) + break; + } + if ( j == MAX_LINES ) + { + // need to make more room to display text, scroll stuff up then fix the pointers + int linesmoved = ScrollTextUp(); + line -= linesmoved; + last_break = last_break - (sizeof(g_szLineBuffer[0]) * linesmoved); + } + } + while ( j == MAX_LINES ); + + // copy remaining string into next buffer, making sure it starts with a space character + if ( (char)*last_break == (char)' ' ) + { + int linelen = strlen(g_szLineBuffer[j]); + int remaininglen = strlen(last_break); + + if ( (linelen - remaininglen) <= MAX_CHARS_PER_LINE ) + strcat( g_szLineBuffer[j], last_break ); + } + else + { + if ( (strlen(g_szLineBuffer[j]) - strlen(last_break) - 2) < MAX_CHARS_PER_LINE ) + { + strcat( g_szLineBuffer[j], " " ); + strcat( g_szLineBuffer[j], last_break ); + } + } + + *last_break = 0; // cut off the last string + + EnsureTextFitsInOneLineAndWrapIfHaveTo( j ); + break; + } + } + } +} \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/status_icons.cpp b/hlsdk/ricochet/cl_dll/status_icons.cpp new file mode 100644 index 00000000..4d9587d1 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/status_icons.cpp @@ -0,0 +1,150 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// +// status_icons.cpp +// +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE( m_StatusIcons, StatusIcon ); + +int CHudStatusIcons::Init( void ) +{ + HOOK_MESSAGE( StatusIcon ); + + gHUD.AddHudElem( this ); + + Reset(); + + return 1; +} + +int CHudStatusIcons::VidInit( void ) +{ + + return 1; +} + +void CHudStatusIcons::Reset( void ) +{ + memset( m_IconList, 0, sizeof m_IconList ); + m_iFlags &= ~HUD_ACTIVE; +} + +// Draw status icons along the left-hand side of the screen +int CHudStatusIcons::Draw( float flTime ) +{ + // find starting position to draw from, along right-hand side of screen + int x = 5; + int y = ScreenHeight / 2; + + // loop through icon list, and draw any valid icons drawing up from the middle of screen + for ( int i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( m_IconList[i].spr ) + { + y -= ( m_IconList[i].rc.bottom - m_IconList[i].rc.top ) + 5; + + SPR_Set( m_IconList[i].spr, m_IconList[i].r, m_IconList[i].g, m_IconList[i].b ); + SPR_DrawAdditive( 0, x, y, &m_IconList[i].rc ); + } + } + + return 1; +} + +// Message handler for StatusIcon message +// accepts five values: +// byte : TRUE = ENABLE icon, FALSE = DISABLE icon +// string : the sprite name to display +// byte : red +// byte : green +// byte : blue +int CHudStatusIcons::MsgFunc_StatusIcon( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int ShouldEnable = READ_BYTE(); + char *pszIconName = READ_STRING(); + if ( ShouldEnable ) + { + int r = READ_BYTE(); + int g = READ_BYTE(); + int b = READ_BYTE(); + EnableIcon( pszIconName, r, g, b ); + m_iFlags |= HUD_ACTIVE; + } + else + { + DisableIcon( pszIconName ); + } + + return 1; +} + +// add the icon to the icon list, and set it's drawing color +void CHudStatusIcons::EnableIcon( char *pszIconName, unsigned char red, unsigned char green, unsigned char blue ) +{ + // check to see if the sprite is in the current list + int i; + for ( i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( !stricmp( m_IconList[i].szSpriteName, pszIconName ) ) + break; + } + + if ( i == MAX_ICONSPRITES ) + { + // icon not in list, so find an empty slot to add to + for ( i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( !m_IconList[i].spr ) + break; + } + } + + // if we've run out of space in the list, overwrite the first icon + if ( i == MAX_ICONSPRITES ) + { + i = 0; + } + + // Load the sprite and add it to the list + // the sprite must be listed in hud.txt + int spr_index = gHUD.GetSpriteIndex( pszIconName ); + m_IconList[i].spr = gHUD.GetSprite( spr_index ); + m_IconList[i].rc = gHUD.GetSpriteRect( spr_index ); + m_IconList[i].r = red; + m_IconList[i].g = green; + m_IconList[i].b = blue; + strcpy( m_IconList[i].szSpriteName, pszIconName ); +} + +void CHudStatusIcons::DisableIcon( char *pszIconName ) +{ + // find the sprite is in the current list + for ( int i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( !stricmp( m_IconList[i].szSpriteName, pszIconName ) ) + { + // clear the item from the list + memset( &m_IconList[i], 0, sizeof icon_sprite_t ); + return; + } + } +} diff --git a/hlsdk/ricochet/cl_dll/statusbar.cpp b/hlsdk/ricochet/cl_dll/statusbar.cpp new file mode 100644 index 00000000..41b8de96 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/statusbar.cpp @@ -0,0 +1,252 @@ +/*** +* +* 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. +* 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. +* +****/ +// +// statusbar.cpp +// +// generic text status bar, set by game dll +// runs across bottom of screen +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +DECLARE_MESSAGE( m_StatusBar, StatusText ); +DECLARE_MESSAGE( m_StatusBar, StatusValue ); + +#define STATUSBAR_ID_LINE 1 + +int CHudStatusBar :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( StatusText ); + HOOK_MESSAGE( StatusValue ); + + Reset(); + + CVAR_CREATE( "hud_centerid", "0", FCVAR_ARCHIVE ); + + return 1; +} + +int CHudStatusBar :: VidInit( void ) +{ + // Load sprites here + + return 1; +} + +void CHudStatusBar :: Reset( void ) +{ + m_iFlags &= ~HUD_ACTIVE; // start out inactive + for ( int i = 0; i < MAX_STATUSBAR_LINES; i++ ) + m_szStatusText[i][0] = 0; + memset( m_iStatusValues, 0, sizeof m_iStatusValues ); + + m_iStatusValues[0] = 1; // 0 is the special index, which always returns true +} + +void CHudStatusBar :: ParseStatusString( int line_num ) +{ + // localise string first + char szBuffer[MAX_STATUSTEXT_LENGTH]; + memset( szBuffer, 0, sizeof szBuffer ); + gHUD.m_TextMessage.LocaliseTextString( m_szStatusText[line_num], szBuffer, MAX_STATUSTEXT_LENGTH ); + + // parse m_szStatusText & m_iStatusValues into m_szStatusBar + memset( m_szStatusBar[line_num], 0, MAX_STATUSTEXT_LENGTH ); + char *src = szBuffer; + char *dst = m_szStatusBar[line_num]; + + char *src_start = src, *dst_start = dst; + + while ( *src != 0 ) + { + while ( *src == '\n' ) + src++; // skip over any newlines + + if ( ((src - src_start) >= MAX_STATUSTEXT_LENGTH) || ((dst - dst_start) >= MAX_STATUSTEXT_LENGTH) ) + break; + + int index = atoi( src ); + // should we draw this line? + if ( (index >= 0 && index < MAX_STATUSBAR_VALUES) && (m_iStatusValues[index] != 0) ) + { // parse this line and append result to the status bar + while ( *src >= '0' && *src <= '9' ) + src++; + + if ( *src == '\n' || *src == 0 ) + continue; // no more left in this text line + + // copy the text, char by char, until we hit a % or a \n + while ( *src != '\n' && *src != 0 ) + { + if ( *src != '%' ) + { // just copy the character + *dst = *src; + dst++, src++; + } + else + { + // get the descriptor + char valtype = *(++src); // move over % + + // if it's a %, draw a % sign + if ( valtype == '%' ) + { + *dst = valtype; + dst++, src++; + continue; + } + + // move over descriptor, then get and move over the index + index = atoi( ++src ); + while ( *src >= '0' && *src <= '9' ) + src++; + + if ( index >= 0 && index < MAX_STATUSBAR_VALUES ) + { + int indexval = m_iStatusValues[index]; + + // get the string to substitute in place of the %XX + char szRepString[MAX_PLAYER_NAME_LENGTH]; + switch ( valtype ) + { + case 'p': // player name + GetPlayerInfo( indexval, &g_PlayerInfoList[indexval] ); + if ( g_PlayerInfoList[indexval].name != NULL ) + { + strncpy( szRepString, g_PlayerInfoList[indexval].name, MAX_PLAYER_NAME_LENGTH ); + } + else + { + strcpy( szRepString, "******" ); + } + break; + case 'i': // number + sprintf( szRepString, "%d", indexval ); + break; + default: + szRepString[0] = 0; + } + + for ( char *cp = szRepString; *cp != 0 && ((dst - dst_start) < MAX_STATUSTEXT_LENGTH); cp++, dst++ ) + *dst = *cp; + } + } + } + } + else + { + // skip to next line of text + while ( *src != 0 && *src != '\n' ) + src++; + } + } +} + +int CHudStatusBar :: Draw( float fTime ) +{ + if ( m_bReparseString ) + { + for ( int i = 0; i < MAX_STATUSBAR_LINES; i++ ) + ParseStatusString( i ); + m_bReparseString = FALSE; + } + + // Draw the status bar lines + for ( int i = 0; i < MAX_STATUSBAR_LINES; i++ ) + { + int TextHeight, TextWidth; + GetConsoleStringSize( m_szStatusBar[i], &TextWidth, &TextHeight ); + + int Y_START; + if ( ScreenHeight >= 480 ) + Y_START = ScreenHeight - 45; + else + Y_START = ScreenHeight - 35; + + int x = 5; + int y = Y_START - ( TextHeight * i ); // draw along bottom of screen + + // let user set status ID bar centering + if ( (i == STATUSBAR_ID_LINE) && CVAR_GET_FLOAT("hud_centerid") ) + { + x = max( 0, max(2, (ScreenWidth - TextWidth)) / 2 ); + y = (ScreenHeight / 2) + (TextHeight*CVAR_GET_FLOAT("hud_centerid")); + } + + DrawConsoleString( x, y, m_szStatusBar[i] ); + } + + return 1; +} + +// Message handler for StatusText message +// accepts two values: +// byte: line number of status bar text +// string: status bar text +// this string describes how the status bar should be drawn +// a semi-regular expression: +// ( slotnum ([a..z] [%pX] [%iX])*)* +// where slotnum is an index into the Value table (see below) +// if slotnum is 0, the string is always drawn +// if StatusValue[slotnum] != 0, the following string is drawn, upto the next newline - otherwise the text is skipped upto next newline +// %pX, where X is an integer, will substitute a player name here, getting the player index from StatusValue[X] +// %iX, where X is an integer, will substitute a number here, getting the number from StatusValue[X] +int CHudStatusBar :: MsgFunc_StatusText( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int line = READ_BYTE(); + + if ( line < 0 || line >= MAX_STATUSBAR_LINES ) + return 1; + + strncpy( m_szStatusText[line], READ_STRING(), MAX_STATUSTEXT_LENGTH ); + m_szStatusText[line][MAX_STATUSTEXT_LENGTH-1] = 0; // ensure it's null terminated ( strncpy() won't null terminate if read string too long) + + if ( m_szStatusText[0] == 0 ) + m_iFlags &= ~HUD_ACTIVE; + else + m_iFlags |= HUD_ACTIVE; // we have status text, so turn on the status bar + + m_bReparseString = TRUE; + + return 1; +} + +// Message handler for StatusText message +// accepts two values: +// byte: index into the status value array +// short: value to store +int CHudStatusBar :: MsgFunc_StatusValue( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int index = READ_BYTE(); + if ( index < 1 || index >= MAX_STATUSBAR_VALUES ) + return 1; // index out of range + + m_iStatusValues[index] = READ_SHORT(); + + m_bReparseString = TRUE; + + return 1; +} \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/studio_util.cpp b/hlsdk/ricochet/cl_dll/studio_util.cpp new file mode 100644 index 00000000..71a0cfd6 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/studio_util.cpp @@ -0,0 +1,244 @@ +#include +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "com_model.h" +#include "studio_util.h" + +/* +==================== +AngleMatrix + +==================== +*/ +void AngleMatrix (const float *angles, float (*matrix)[4] ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + 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); + + // matrix = (YAW * PITCH) * ROLL + matrix[0][0] = cp*cy; + matrix[1][0] = cp*sy; + matrix[2][0] = -sp; + matrix[0][1] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[2][1] = sr*cp; + matrix[0][2] = (cr*sp*cy+-sr*-sy); + matrix[1][2] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.0; + matrix[1][3] = 0.0; + matrix[2][3] = 0.0; +} + +/* +==================== +VectorCompare + +==================== +*/ +int VectorCompare (const float *v1, const float *v2) +{ + int i; + + for (i=0 ; i<3 ; i++) + if (v1[i] != v2[i]) + return 0; + + return 1; +} + +/* +==================== +CrossProduct + +==================== +*/ +void CrossProduct (const float *v1, const float *v2, float *cross) +{ + cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; + cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; + cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} + +/* +==================== +VectorTransform + +==================== +*/ +void VectorTransform (const float *in1, float in2[3][4], float *out) +{ + out[0] = DotProduct(in1, in2[0]) + in2[0][3]; + out[1] = DotProduct(in1, in2[1]) + in2[1][3]; + out[2] = DotProduct(in1, in2[2]) + in2[2][3]; +} + +/* +================ +ConcatTransforms + +================ +*/ +void ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] + + in1[0][2] * in2[2][3] + in1[0][3]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] + + in1[1][2] * in2[2][3] + in1[1][3]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; + out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + + in1[2][2] * in2[2][3] + in1[2][3]; +} + +// angles index are not the same as ROLL, PITCH, YAW + +/* +==================== +AngleQuaternion + +==================== +*/ +void AngleQuaternion( float *angles, vec4_t quaternion ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + // FIXME: rescale the inputs to 1/2 angle + angle = angles[2] * 0.5; + sy = sin(angle); + cy = cos(angle); + angle = angles[1] * 0.5; + sp = sin(angle); + cp = cos(angle); + angle = angles[0] * 0.5; + sr = sin(angle); + cr = cos(angle); + + quaternion[0] = sr*cp*cy-cr*sp*sy; // X + quaternion[1] = cr*sp*cy+sr*cp*sy; // Y + quaternion[2] = cr*cp*sy-sr*sp*cy; // Z + quaternion[3] = cr*cp*cy+sr*sp*sy; // W +} + +/* +==================== +QuaternionSlerp + +==================== +*/ +void QuaternionSlerp( vec4_t p, vec4_t q, float t, vec4_t qt ) +{ + int i; + float omega, cosom, sinom, sclp, sclq; + + // decide if one of the quaternions is backwards + float a = 0; + float b = 0; + + for (i = 0; i < 4; i++) + { + a += (p[i]-q[i])*(p[i]-q[i]); + b += (p[i]+q[i])*(p[i]+q[i]); + } + if (a > b) + { + for (i = 0; i < 4; i++) + { + q[i] = -q[i]; + } + } + + cosom = p[0]*q[0] + p[1]*q[1] + p[2]*q[2] + p[3]*q[3]; + + if ((1.0 + cosom) > 0.000001) + { + if ((1.0 - cosom) > 0.000001) + { + omega = acos( cosom ); + sinom = sin( omega ); + sclp = sin( (1.0 - t)*omega) / sinom; + sclq = sin( t*omega ) / sinom; + } + else + { + sclp = 1.0 - t; + sclq = t; + } + for (i = 0; i < 4; i++) { + qt[i] = sclp * p[i] + sclq * q[i]; + } + } + else + { + qt[0] = -q[1]; + qt[1] = q[0]; + qt[2] = -q[3]; + qt[3] = q[2]; + sclp = sin( (1.0 - t) * (0.5 * M_PI)); + sclq = sin( t * (0.5 * M_PI)); + for (i = 0; i < 3; i++) + { + qt[i] = sclp * p[i] + sclq * qt[i]; + } + } +} + +/* +==================== +QuaternionMatrix + +==================== +*/ +void QuaternionMatrix( vec4_t quaternion, float (*matrix)[4] ) +{ + matrix[0][0] = 1.0 - 2.0 * quaternion[1] * quaternion[1] - 2.0 * quaternion[2] * quaternion[2]; + matrix[1][0] = 2.0 * quaternion[0] * quaternion[1] + 2.0 * quaternion[3] * quaternion[2]; + matrix[2][0] = 2.0 * quaternion[0] * quaternion[2] - 2.0 * quaternion[3] * quaternion[1]; + + matrix[0][1] = 2.0 * quaternion[0] * quaternion[1] - 2.0 * quaternion[3] * quaternion[2]; + matrix[1][1] = 1.0 - 2.0 * quaternion[0] * quaternion[0] - 2.0 * quaternion[2] * quaternion[2]; + matrix[2][1] = 2.0 * quaternion[1] * quaternion[2] + 2.0 * quaternion[3] * quaternion[0]; + + matrix[0][2] = 2.0 * quaternion[0] * quaternion[2] + 2.0 * quaternion[3] * quaternion[1]; + matrix[1][2] = 2.0 * quaternion[1] * quaternion[2] - 2.0 * quaternion[3] * quaternion[0]; + matrix[2][2] = 1.0 - 2.0 * quaternion[0] * quaternion[0] - 2.0 * quaternion[1] * quaternion[1]; +} + +/* +==================== +MatrixCopy + +==================== +*/ +void MatrixCopy( float in[3][4], float out[3][4] ) +{ + memcpy( out, in, sizeof( float ) * 3 * 4 ); +} \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/studio_util.h b/hlsdk/ricochet/cl_dll/studio_util.h new file mode 100644 index 00000000..49e506f3 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/studio_util.h @@ -0,0 +1,33 @@ +#if !defined( STUDIO_UTIL_H ) +#define STUDIO_UTIL_H +#if defined( WIN32 ) +#pragma once +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +#ifndef PITCH +// MOVEMENT INFO +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 +#endif + +#define FDotProduct( a, b ) (fabs((a[0])*(b[0])) + fabs((a[1])*(b[1])) + fabs((a[2])*(b[2]))) + +void AngleMatrix (const float *angles, float (*matrix)[4] ); +int VectorCompare (const float *v1, const float *v2); +void CrossProduct (const float *v1, const float *v2, float *cross); +void VectorTransform (const float *in1, float in2[3][4], float *out); +void ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]); +void MatrixCopy( float in[3][4], float out[3][4] ); +void QuaternionMatrix( vec4_t quaternion, float (*matrix)[4] ); +void QuaternionSlerp( vec4_t p, vec4_t q, float t, vec4_t qt ); +void AngleQuaternion( float *angles, vec4_t quaternion ); + +#endif // STUDIO_UTIL_H \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/text_message.cpp b/hlsdk/ricochet/cl_dll/text_message.cpp new file mode 100644 index 00000000..1bb718cf --- /dev/null +++ b/hlsdk/ricochet/cl_dll/text_message.cpp @@ -0,0 +1,207 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// +// text_message.cpp +// +// implementation of CHudTextMessage class +// +// this class routes messages through titles.txt for localisation +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE( m_TextMessage, TextMsg ); + +int CHudTextMessage::Init(void) +{ + HOOK_MESSAGE( TextMsg ); + + gHUD.AddHudElem( this ); + + Reset(); + + return 1; +}; + +// Searches through the string for any msg names (indicated by a '#') +// any found are looked up in titles.txt and the new message substituted +// the new value is pushed into dst_buffer +char *CHudTextMessage::LocaliseTextString( const char *msg, char *dst_buffer, int buffer_size ) +{ + char *dst = dst_buffer; + for ( char *src = (char*)msg; *src != 0 && buffer_size > 0; buffer_size-- ) + { + if ( *src == '#' ) + { + // cut msg name out of string + static char word_buf[255]; + char *wdst = word_buf, *word_start = src; + for ( ++src ; (*src >= 'A' && *src <= 'z') || (*src >= '0' && *src <= '9'); wdst++, src++ ) + { + *wdst = *src; + } + *wdst = 0; + + // lookup msg name in titles.txt + client_textmessage_t *clmsg = TextMessageGet( word_buf ); + if ( !clmsg || !(clmsg->pMessage) ) + { + src = word_start; + *dst = *src; + dst++, src++; + continue; + } + + // copy string into message over the msg name + for ( char *wsrc = (char*)clmsg->pMessage; *wsrc != 0; wsrc++, dst++ ) + { + *dst = *wsrc; + } + *dst = 0; + } + else + { + *dst = *src; + dst++, src++; + *dst = 0; + } + } + + dst_buffer[buffer_size-1] = 0; // ensure null termination + return dst_buffer; +} + +// As above, but with a local static buffer +char *CHudTextMessage::BufferedLocaliseTextString( const char *msg ) +{ + char dst_buffer[1024]; + return LocaliseTextString( msg, dst_buffer, 1024 ); +} + +// Simplified version of LocaliseTextString; assumes string is only one word +char *CHudTextMessage::LookupString( const char *msg, int *msg_dest ) +{ + if ( !msg ) + return ""; + + // '#' character indicates this is a reference to a string in titles.txt, and not the string itself + if ( msg[0] == '#' ) + { + // this is a message name, so look up the real message + client_textmessage_t *clmsg = TextMessageGet( msg+1 ); + + if ( !clmsg || !(clmsg->pMessage) ) + return (char*)msg; // lookup failed, so return the original string + + if ( msg_dest ) + { + // check to see if titles.txt info overrides msg destination + // if clmsg->effect is less than 0, then clmsg->effect holds -1 * message_destination + if ( clmsg->effect < 0 ) // + *msg_dest = -clmsg->effect; + } + + return (char*)clmsg->pMessage; + } + else + { // nothing special about this message, so just return the same string + return (char*)msg; + } +} + +void StripEndNewlineFromString( char *str ) +{ + int s = strlen( str ) - 1; + if ( str[s] == '\n' || str[s] == '\r' ) + str[s] = 0; +} + +// converts all '\r' characters to '\n', so that the engine can deal with the properly +// returns a pointer to str +char* ConvertCRtoNL( char *str ) +{ + for ( char *ch = str; *ch != 0; ch++ ) + if ( *ch == '\r' ) + *ch = '\n'; + return str; +} + +// Message handler for text messages +// displays a string, looking them up from the titles.txt file, which can be localised +// parameters: +// byte: message direction ( HUD_PRINTCONSOLE, HUD_PRINTNOTIFY, HUD_PRINTCENTER, HUD_PRINTTALK ) +// string: message +// optional parameters: +// string: message parameter 1 +// string: message parameter 2 +// string: message parameter 3 +// string: message parameter 4 +// any string that starts with the character '#' is a message name, and is used to look up the real message in titles.txt +// the next (optional) one to four strings are parameters for that string (which can also be message names if they begin with '#') +int CHudTextMessage::MsgFunc_TextMsg( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int msg_dest = READ_BYTE(); + + static char szBuf[6][128]; + char *msg_text = LookupString( READ_STRING(), &msg_dest ); + msg_text = strcpy( szBuf[0], msg_text ); + + // keep reading strings and using C format strings for subsituting the strings into the localised text string + char *sstr1 = LookupString( READ_STRING() ); + sstr1 = strcpy( szBuf[1], sstr1 ); + StripEndNewlineFromString( sstr1 ); // these strings are meant for subsitution into the main strings, so cull the automatic end newlines + char *sstr2 = LookupString( READ_STRING() ); + sstr2 = strcpy( szBuf[2], sstr2 ); + StripEndNewlineFromString( sstr2 ); + char *sstr3 = LookupString( READ_STRING() ); + sstr3 = strcpy( szBuf[3], sstr3 ); + StripEndNewlineFromString( sstr3 ); + char *sstr4 = LookupString( READ_STRING() ); + sstr4 = strcpy( szBuf[4], sstr4 ); + StripEndNewlineFromString( sstr4 ); + char *psz = szBuf[5]; + + switch ( msg_dest ) + { + case HUD_PRINTCENTER: + sprintf( psz, msg_text, sstr1, sstr2, sstr3, sstr4 ); + CenterPrint( ConvertCRtoNL( psz ) ); + break; + + case HUD_PRINTNOTIFY: + psz[0] = 1; // mark this message to go into the notify buffer + sprintf( psz+1, msg_text, sstr1, sstr2, sstr3, sstr4 ); + ConsolePrint( ConvertCRtoNL( psz ) ); + break; + + case HUD_PRINTTALK: + sprintf( psz, msg_text, sstr1, sstr2, sstr3, sstr4 ); + gHUD.m_SayText.SayTextPrint( ConvertCRtoNL( psz ), 128 ); + break; + + case HUD_PRINTCONSOLE: + sprintf( psz, msg_text, sstr1, sstr2, sstr3, sstr4 ); + ConsolePrint( ConvertCRtoNL( psz ) ); + break; + } + + return 1; +} diff --git a/hlsdk/ricochet/cl_dll/tf_defs.h b/hlsdk/ricochet/cl_dll/tf_defs.h new file mode 100644 index 00000000..5d7c2271 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/tf_defs.h @@ -0,0 +1,1357 @@ +/*** +* +* Copyright (c) 1998, 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. +* +****/ + +#ifndef __TF_DEFS_H +#define __TF_DEFS_H + +//=========================================================================== +// OLD OPTIONS.QC +//=========================================================================== +#define DEFAULT_AUTOZOOM FALSE +#define WEINER_SNIPER // autoaiming for sniper rifle +#define FLAME_MAXWORLDNUM 20 // maximum number of flames in the world. DO NOT PUT BELOW 20. + +//#define MAX_WORLD_PIPEBOMBS 15 // This is divided between teams - this is the most you should have on a net server +#define MAX_PLAYER_PIPEBOMBS 8 // maximum number of pipebombs any 1 player can have active +#define MAX_PLAYER_AMMOBOXES 3 // maximum number of ammoboxes any 1 player can have active + +//#define MAX_WORLD_FLARES 9 // This is the total number of flares allowed in the world at one time +//#define MAX_WORLD_AMMOBOXES 20 // This is divided between teams - this is the most you should have on a net server +#define GR_TYPE_MIRV_NO 4 // Number of Mirvs a Mirv Grenade breaks into +#define GR_TYPE_NAPALM_NO 8 // Number of flames napalm grenade breaks into (unused if net server) +#define MEDIKIT_IS_BIOWEAPON // Medikit acts as a bioweapon against enemies + +#define TEAM_HELP_RATE 60 // used only if teamplay bit 64 (help team with lower score) is set. + // 60 is a mild setting, and won't make too much difference + // increasing it _decreases_ the amount of help the losing team gets + // Minimum setting is 1, which would really help the losing team + +#define DISPLAY_CLASS_HELP TRUE // Change this to #OFF if you don't want the class help to + // appear whenever a player connects +#define NEVER_TEAMFRAGS FALSE // teamfrags options always off +#define ALWAYS_TEAMFRAGS FALSE // teamfrags options always on +#define CHECK_SPEEDS TRUE // makes sure players aren't moving too fast +#define SNIPER_RIFLE_RELOAD_TIME 1.5 // seconds + +#define MAPBRIEFING_MAXTEXTLENGTH 512 +#define PLAYER_PUSH_VELOCITY 50 // Players push teammates if they're moving under this speed + +// Debug Options +//#define MAP_DEBUG // Debug for Map code. I suggest running in a hi-res + // mode and/or piping the output from the server to a file. +#ifdef MAP_DEBUG + #define MDEBUG(x) x +#else + #define MDEBUG(x) +#endif +//#define VERBOSE // Verbose Debugging on/off + +//=========================================================================== +// OLD QUAKE Defs +//=========================================================================== +// items +#define IT_AXE 4096 +#define IT_SHOTGUN 1 +#define IT_SUPER_SHOTGUN 2 +#define IT_NAILGUN 4 +#define IT_SUPER_NAILGUN 8 +#define IT_GRENADE_LAUNCHER 16 +#define IT_ROCKET_LAUNCHER 32 +#define IT_LIGHTNING 64 +#define IT_EXTRA_WEAPON 128 + +#define IT_SHELLS 256 +#define IT_NAILS 512 +#define IT_ROCKETS 1024 +#define IT_CELLS 2048 + +#define IT_ARMOR1 8192 +#define IT_ARMOR2 16384 +#define IT_ARMOR3 32768 +#define IT_SUPERHEALTH 65536 + +#define IT_KEY1 131072 +#define IT_KEY2 262144 + +#define IT_INVISIBILITY 524288 +#define IT_INVULNERABILITY 1048576 +#define IT_SUIT 2097152 +#define IT_QUAD 4194304 +#define IT_HOOK 8388608 + +#define IT_KEY3 16777216 // Stomp invisibility +#define IT_KEY4 33554432 // Stomp invulnerability + +//=========================================================================== +// TEAMFORTRESS Defs +//=========================================================================== +// TeamFortress State Flags +#define TFSTATE_GRENPRIMED 1 // Whether the player has a primed grenade +#define TFSTATE_RELOADING 2 // Whether the player is reloading +#define TFSTATE_ALTKILL 4 // #TRUE if killed with a weapon not in self.weapon: NOT USED ANYMORE +#define TFSTATE_RANDOMPC 8 // Whether Playerclass is random, new one each respawn +#define TFSTATE_INFECTED 16 // set when player is infected by the bioweapon +#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 +#define TFSTATE_ZOOMOFF 4096 // doesn't want the FOV changed when zooming +#define TFSTATE_RESPAWN_READY 8192 // is waiting for respawn, and has pressed fire +#define TFSTATE_HALLUCINATING 16384 // set when player is hallucinating +#define TFSTATE_TRANQUILISED 32768 // set when player is tranquilised +#define TFSTATE_CANT_MOVE 65536 // set when player is setting a detpack +#define TFSTATE_RESET_FLAMETIME 131072 // set when the player has to have his flames increased in health + +// Defines used by TF_T_Damage (see combat.qc) +#define TF_TD_IGNOREARMOUR 1 // Bypasses the armour of the target +#define TF_TD_NOTTEAM 2 // Doesn't damage a team member (indicates direct fire weapon) +#define TF_TD_NOTSELF 4 // Doesn't damage self + +#define TF_TD_OTHER 0 // Ignore armorclass +#define TF_TD_SHOT 1 // Bullet damage +#define TF_TD_NAIL 2 // Nail damage +#define TF_TD_EXPLOSION 4 // Explosion damage +#define TF_TD_ELECTRICITY 8 // Electric damage +#define TF_TD_FIRE 16 // Fire damage +#define TF_TD_NOSOUND 256 // Special damage. Makes no sound/painframe, etc + +/*==================================================*/ +/* Toggleable Game Settings */ +/*==================================================*/ +#define TF_RESPAWNDELAY1 5 // seconds of waiting before player can respawn +#define TF_RESPAWNDELAY2 10 // seconds of waiting before player can respawn +#define TF_RESPAWNDELAY3 20 // seconds of waiting before player can respawn + +#define TEAMPLAY_NORMAL 1 +#define TEAMPLAY_HALFDIRECT 2 +#define TEAMPLAY_NODIRECT 4 +#define TEAMPLAY_HALFEXPLOSIVE 8 +#define TEAMPLAY_NOEXPLOSIVE 16 +#define TEAMPLAY_LESSPLAYERSHELP 32 +#define TEAMPLAY_LESSSCOREHELP 64 +#define TEAMPLAY_HALFDIRECTARMOR 128 +#define TEAMPLAY_NODIRECTARMOR 256 +#define TEAMPLAY_HALFEXPARMOR 512 +#define TEAMPLAY_NOEXPARMOR 1024 +#define TEAMPLAY_HALFDIRMIRROR 2048 +#define TEAMPLAY_FULLDIRMIRROR 4096 +#define TEAMPLAY_HALFEXPMIRROR 8192 +#define TEAMPLAY_FULLEXPMIRROR 16384 + +#define TEAMPLAY_TEAMDAMAGE (TEAMPLAY_NODIRECT | TEAMPLAY_HALFDIRECT | TEAMPLAY_HALFEXPLOSIVE | TEAMPLAY_NOEXPLOSIVE) +// FortressMap stuff +#define TEAM1_CIVILIANS 1 +#define TEAM2_CIVILIANS 2 +#define TEAM3_CIVILIANS 4 +#define TEAM4_CIVILIANS 8 + +// Defines for the playerclass +#define PC_UNDEFINED 0 + +#define PC_SCOUT 1 +#define PC_SNIPER 2 +#define PC_SOLDIER 3 +#define PC_DEMOMAN 4 +#define PC_MEDIC 5 +#define PC_HVYWEAP 6 +#define PC_PYRO 7 +#define PC_SPY 8 +#define PC_ENGINEER 9 + +// Insert new class definitions here + +// PC_RANDOM _MUST_ be the third last class +#define PC_RANDOM 10 // Random playerclass +#define PC_CIVILIAN 11 // Civilians are a special class. They cannot + // be chosen by players, only enforced by maps +#define PC_LASTCLASS 12 // Use this as the high-boundary for any loops + // through the playerclass. + +// These are just for the scanner +#define SCAN_SENTRY 13 +#define SCAN_GOALITEM 14 + +// Values returned by CheckArea +enum +{ + CAREA_CLEAR, + CAREA_BLOCKED, + CAREA_NOBUILD +}; + +/*==================================================*/ +/* Impulse Defines */ +/*==================================================*/ +// Alias check to see whether they already have the aliases +#define TF_ALIAS_CHECK 13 + +// CTF Support Impulses +#define HOOK_IMP1 22 +#define FLAG_INFO 23 +#define HOOK_IMP2 39 + +// Axe +#define AXE_IMP 40 + +// Camera Impulse +#define TF_CAM_TARGET 50 +#define TF_CAM_ZOOM 51 +#define TF_CAM_ANGLE 52 +#define TF_CAM_VEC 53 +#define TF_CAM_PROJECTILE 54 +#define TF_CAM_PROJECTILE_Z 55 +#define TF_CAM_REVANGLE 56 +#define TF_CAM_OFFSET 57 +#define TF_CAM_DROP 58 +#define TF_CAM_FADETOBLACK 59 +#define TF_CAM_FADEFROMBLACK 60 +#define TF_CAM_FADETOWHITE 61 +#define TF_CAM_FADEFROMWHITE 62 + +// Last Weapon impulse +#define TF_LAST_WEAPON 69 + +// Status Bar Resolution Settings. Same as CTF to maintain ease of use. +#define TF_STATUSBAR_RES_START 71 +#define TF_STATUSBAR_RES_END 81 + +// Clan Messages +#define TF_MESSAGE_1 82 +#define TF_MESSAGE_2 83 +#define TF_MESSAGE_3 84 +#define TF_MESSAGE_4 85 +#define TF_MESSAGE_5 86 + +#define TF_CHANGE_CLASS 99 // Bring up the Class Change menu + +// Added to PC_??? to get impulse to use if this clashes with your +// own impulses, just change this value, not the PC_?? +#define TF_CHANGEPC 100 +// The next few impulses are all the class selections +//PC_SCOUT 101 +//PC_SNIPER 102 +//PC_SOLDIER 103 +//PC_DEMOMAN 104 +//PC_MEDIC 105 +//PC_HVYWEAP 106 +//PC_PYRO 107 +//PC_RANDOM 108 +//PC_CIVILIAN 109 // Cannot be used +//PC_SPY 110 +//PC_ENGINEER 111 + +// Help impulses +#define TF_DISPLAYLOCATION 118 +#define TF_STATUS_QUERY 119 + +#define TF_HELP_MAP 131 + +// Information impulses +#define TF_INVENTORY 135 +#define TF_SHOWTF 136 +#define TF_SHOWLEGALCLASSES 137 + +// Team Impulses +#define TF_TEAM_1 140 // Join Team 1 +#define TF_TEAM_2 141 // Join Team 2 +#define TF_TEAM_3 142 // Join Team 3 +#define TF_TEAM_4 143 // Join Team 4 +#define TF_TEAM_CLASSES 144 // Impulse to display team classes +#define TF_TEAM_SCORES 145 // Impulse to display team scores +#define TF_TEAM_LIST 146 // Impulse to display the players in each team. + +// Grenade Impulses +#define TF_GRENADE_1 150 // Prime grenade type 1 +#define TF_GRENADE_2 151 // Prime grenade type 2 +#define TF_GRENADE_T 152 // Throw primed grenade + +// Impulses for new items +//#define TF_SCAN 159 // Scanner Pre-Impulse +#define TF_AUTO_SCAN 159 // Scanner On/Off +#define TF_SCAN_ENEMY 160 // Impulses to toggle scanning of enemies +#define TF_SCAN_FRIENDLY 161 // Impulses to toggle scanning of friendlies +//#define TF_SCAN_10 162 // Scan using 10 enery (1 cell) +#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 +#define TF_SPECIAL_SKILL 171 + +// Ammo Drop impulse +#define TF_DROP_AMMO 172 + +// Reload impulse +#define TF_RELOAD 173 + +// auto-zoom toggle +#define TF_AUTOZOOM 174 + +// drop/pass commands +#define TF_DROPKEY 175 + +// 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 + +// Medic +#define TF_MEDIC_HELPME 181 + +// Status bar +#define TF_STATUSBAR_ON 182 +#define TF_STATUSBAR_OFF 183 + +// Discard impulse +#define TF_DISCARD 184 + +// ID Player impulse +#define TF_ID 185 + +// Clan Battle impulses +#define TF_SHOWIDS 186 + +// More Engineer Impulses +#define TF_ENGINEER_DETDISP 187 +#define TF_ENGINEER_DETSENT 188 + +// Admin Commands +#define TF_ADMIN_DEAL_CYCLE 189 +#define TF_ADMIN_KICK 190 +#define TF_ADMIN_BAN 191 +#define TF_ADMIN_COUNTPLAYERS 192 +#define TF_ADMIN_CEASEFIRE 193 + +// Drop Goal Items +#define TF_DROPGOALITEMS 194 + +// More Admin Commands +#define TF_ADMIN_NEXT 195 + +// More Engineer Impulses +#define TF_ENGINEER_DETEXIT 196 +#define TF_ENGINEER_DETENTRANCE 197 + +// Yet MORE Admin Commands +#define TF_ADMIN_LISTIPS 198 + +// Silent Spy Feign +#define TF_SPY_SILENTDIE 199 + + +/*==================================================*/ +/* Colors */ +/*==================================================*/ +#define TEAM1_COLOR 150 +#define TEAM2_COLOR 250 +#define TEAM3_COLOR 45 +#define TEAM4_COLOR 100 + +/*==================================================*/ +/* Defines for the ENGINEER's Building ability */ +/*==================================================*/ +// Ammo costs +#define AMMO_COST_SHELLS 2 // Metal needed to make 1 shell +#define AMMO_COST_NAILS 1 +#define AMMO_COST_ROCKETS 2 +#define AMMO_COST_CELLS 2 + +// Building types +#define BUILD_DISPENSER 1 +#define BUILD_SENTRYGUN 2 +#define BUILD_MORTAR 3 +#define BUILD_TELEPORTER_ENTRANCE 4 +#define BUILD_TELEPORTER_EXIT 5 + +// Building metal costs +#define BUILD_COST_DISPENSER 100 // Metal needed to built +#define BUILD_COST_SENTRYGUN 130 +#define BUILD_COST_MORTAR 150 +#define BUILD_COST_TELEPORTER 125 + +#define BUILD_COST_SANDBAG 20 // Built with a separate alias + +// Building times +#define BUILD_TIME_DISPENSER 2 // seconds to build +#define BUILD_TIME_SENTRYGUN 5 +#define BUILD_TIME_MORTAR 5 +#define BUILD_TIME_TELEPORTER 4 + +// Building health levels +#define BUILD_HEALTH_DISPENSER 150 // Health of the building +#define BUILD_HEALTH_SENTRYGUN 150 +#define BUILD_HEALTH_MORTAR 200 +#define BUILD_HEALTH_TELEPORTER 80 + +// Dispenser's maximum carrying capability +#define BUILD_DISPENSER_MAX_SHELLS 400 +#define BUILD_DISPENSER_MAX_NAILS 600 +#define BUILD_DISPENSER_MAX_ROCKETS 300 +#define BUILD_DISPENSER_MAX_CELLS 400 +#define BUILD_DISPENSER_MAX_ARMOR 500 + +// Build state sent down to client +#define BS_BUILDING (1<<0) +#define BS_HAS_DISPENSER (1<<1) +#define BS_HAS_SENTRYGUN (1<<2) +#define BS_CANB_DISPENSER (1<<3) +#define BS_CANB_SENTRYGUN (1<<4) +/*==================================================*/ +/* Ammo quantities for dropping & dispenser use */ +/*==================================================*/ +#define DROP_SHELLS 20 +#define DROP_NAILS 20 +#define DROP_ROCKETS 10 +#define DROP_CELLS 10 +#define DROP_ARMOR 40 + +/*==================================================*/ +/* Team Defines */ +/*==================================================*/ +#define TM_MAX_NO 4 // Max number of teams. Simply changing this value isn't enough. + // A new global to hold new team colors is needed, and more flags + // in the spawnpoint spawnflags may need to be used. + // Basically, don't change this unless you know what you're doing :) + +/*==================================================*/ +/* New Weapon Defines */ +/*==================================================*/ +#define WEAP_HOOK 1 +#define WEAP_BIOWEAPON 2 +#define WEAP_MEDIKIT 4 +#define WEAP_SPANNER 8 +#define WEAP_AXE 16 +#define WEAP_SNIPER_RIFLE 32 +#define WEAP_AUTO_RIFLE 64 +#define WEAP_SHOTGUN 128 +#define WEAP_SUPER_SHOTGUN 256 +#define WEAP_NAILGUN 512 +#define WEAP_SUPER_NAILGUN 1024 +#define WEAP_GRENADE_LAUNCHER 2048 +#define WEAP_FLAMETHROWER 4096 +#define WEAP_ROCKET_LAUNCHER 8192 +#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 +// but we can remove some by giving the weapons +// a weapon mode (like the rifle) + +// HL-compatible weapon numbers +#define WEAPON_HOOK 1 +#define WEAPON_BIOWEAPON (WEAPON_HOOK+1) +#define WEAPON_MEDIKIT (WEAPON_HOOK+2) +#define WEAPON_SPANNER (WEAPON_HOOK+3) +#define WEAPON_AXE (WEAPON_HOOK+4) +#define WEAPON_SNIPER_RIFLE (WEAPON_HOOK+5) +#define WEAPON_AUTO_RIFLE (WEAPON_HOOK+6) +#define WEAPON_TF_SHOTGUN (WEAPON_HOOK+7) +#define WEAPON_SUPER_SHOTGUN (WEAPON_HOOK+8) +#define WEAPON_NAILGUN (WEAPON_HOOK+9) +#define WEAPON_SUPER_NAILGUN (WEAPON_HOOK+10) +#define WEAPON_GRENADE_LAUNCHER (WEAPON_HOOK+11) +#define WEAPON_FLAMETHROWER (WEAPON_HOOK+12) +#define WEAPON_ROCKET_LAUNCHER (WEAPON_HOOK+13) +#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) +#define WEAPON_KNIFE (WEAPON_HOOK+22) +#define WEAPON_BENCHMARK (WEAPON_HOOK+23) + +/*==================================================*/ +/* New Weapon Related Defines */ +/*==================================================*/ +// shots per reload +#define RE_SHOTGUN 8 +#define RE_SUPER_SHOTGUN 16 // 8 shots +#define RE_GRENADE_LAUNCHER 6 +#define RE_ROCKET_LAUNCHER 4 + +// reload times +#define RE_SHOTGUN_TIME 2 +#define RE_SUPER_SHOTGUN_TIME 3 +#define RE_GRENADE_LAUNCHER_TIME 4 +#define RE_ROCKET_LAUNCHER_TIME 5 + +// Maximum velocity you can move and fire the Sniper Rifle +#define WEAP_SNIPER_RIFLE_MAX_MOVE 50 + +// Medikit +#define WEAP_MEDIKIT_HEAL 200 // Amount medikit heals per hit +#define WEAP_MEDIKIT_OVERHEAL 50 // Amount of superhealth over max_health the medikit will dispense + +// 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 + +// Grenades +#define GR_PRIMETIME 3 +#define GR_CALTROP_PRIME 0.5 +#define GR_TYPE_NONE 0 +#define GR_TYPE_NORMAL 1 +#define GR_TYPE_CONCUSSION 2 +#define GR_TYPE_NAIL 3 +#define GR_TYPE_MIRV 4 +#define GR_TYPE_NAPALM 5 +//#define GR_TYPE_FLARE 6 +#define GR_TYPE_GAS 7 +#define GR_TYPE_EMP 8 +#define GR_TYPE_CALTROP 9 +//#define GR_TYPE_FLASH 10 + +// Defines for WeaponMode +#define GL_NORMAL 0 +#define GL_PIPEBOMB 1 + +// Defines for OLD Concussion Grenade +#define GR_OLD_CONCUSS_TIME 5 +#define GR_OLD_CONCUSS_DEC 20 + +// Defines for Concussion Grenade +#define GR_CONCUSS_TIME 0.25 +#define GR_CONCUSS_DEC 10 +#define MEDIUM_PING 150 +#define HIGH_PING 200 + +// Defines for the Gas Grenade +#define GR_HALLU_TIME 0.3 +#define GR_OLD_HALLU_TIME 0.5 +#define GR_HALLU_DEC 2.5 + +// Defines for the BioInfection +#define BIO_JUMP_RADIUS 128 // The distance the bioinfection can jump between players + +/*==================================================*/ +/* New Items */ +/*==================================================*/ +#define NIT_SCANNER 1 + +#define NIT_SILVER_DOOR_OPENED #IT_KEY1 // 131072 +#define NIT_GOLD_DOOR_OPENED #IT_KEY2 // 262144 + +/*==================================================*/ +/* New Item Flags */ +/*==================================================*/ +#define NIT_SCANNER_ENEMY 1 // Detect enemies +#define NIT_SCANNER_FRIENDLY 2 // Detect friendlies (team members) +#define NIT_SCANNER_SOUND 4 // Motion detection. Only report moving entities. + +/*==================================================*/ +/* New Item Related Defines */ +/*==================================================*/ +#define NIT_SCANNER_POWER 25 // The amount of power spent on a scan with the scanner + // is multiplied by this to get the scanrange. +#define NIT_SCANNER_MAXCELL 50 // The maximum number of cells than can be used in one scan +#define NIT_SCANNER_MIN_MOVEMENT 50 // The minimum velocity an entity must have to be detected + // by scanners that only detect movement + +/*==================================================*/ +/* Variables used for New Weapons and Reloading */ +/*==================================================*/ +// Armor Classes : Bitfields. Use the "armorclass" of armor for the Armor Type. +#define AT_SAVESHOT 1 // Kevlar : Reduces bullet damage by 15% +#define AT_SAVENAIL 2 // Wood :) : Reduces nail damage by 15% +#define AT_SAVEEXPLOSION 4 // Blast : Reduces explosion damage by 15% +#define AT_SAVEELECTRICITY 8 // Shock : Reduces electricity damage by 15% +#define AT_SAVEFIRE 16 // Asbestos : Reduces fire damage by 15% + +/*==========================================================================*/ +/* TEAMFORTRESS CLASS DETAILS */ +/*==========================================================================*/ +// Class Details for SCOUT +#define PC_SCOUT_SKIN 4 // Skin for this class when Classkin is on. +#define PC_SCOUT_MAXHEALTH 75 // Maximum Health Level +#define PC_SCOUT_MAXSPEED 400 // Maximum movement speed +#define PC_SCOUT_MAXSTRAFESPEED 400 // Maximum strafing movement speed +#define PC_SCOUT_MAXARMOR 50 // Maximum Armor Level, of any armor class +#define PC_SCOUT_INITARMOR 25 // Armor level when respawned +#define PC_SCOUT_MAXARMORTYPE 0.3 // Maximum level of Armor absorption +#define PC_SCOUT_INITARMORTYPE 0.3 // Absorption Level of armor when respawned +#define PC_SCOUT_ARMORCLASSES 3 // #AT_SAVESHOT | #AT_SAVENAIL <-Armor Classes allowed for this class +#define PC_SCOUT_INITARMORCLASS 0 // Armorclass worn when respawned +#define PC_SCOUT_WEAPONS WEAP_AXE | WEAP_SHOTGUN | WEAP_NAILGUN +#define PC_SCOUT_MAXAMMO_SHOT 50 // Maximum amount of shot ammo this class can carry +#define PC_SCOUT_MAXAMMO_NAIL 200 // Maximum amount of nail ammo this class can carry +#define PC_SCOUT_MAXAMMO_CELL 100 // Maximum amount of cell ammo this class can carry +#define PC_SCOUT_MAXAMMO_ROCKET 25 // Maximum amount of rocket ammo this class can carry +#define PC_SCOUT_INITAMMO_SHOT 25 // Amount of shot ammo this class has when respawned +#define PC_SCOUT_INITAMMO_NAIL 100 // Amount of nail ammo this class has when respawned +#define PC_SCOUT_INITAMMO_CELL 50 // Amount of cell ammo this class has when respawned +#define PC_SCOUT_INITAMMO_ROCKET 0 // Amount of rocket ammo this class has when respawned +#define PC_SCOUT_GRENADE_TYPE_1 GR_TYPE_CALTROP // <- 1st Type of Grenade this class has +#define PC_SCOUT_GRENADE_TYPE_2 GR_TYPE_CONCUSSION // <- 2nd Type of Grenade this class has +#define PC_SCOUT_GRENADE_INIT_1 2 // Number of grenades of Type 1 this class has when respawned +#define PC_SCOUT_GRENADE_INIT_2 3 // Number of grenades of Type 2 this class has when respawned +#define PC_SCOUT_TF_ITEMS NIT_SCANNER // <- TeamFortress Items this class has + +#define PC_SCOUT_MOTION_MIN_I 0.5 // < Short range +#define PC_SCOUT_MOTION_MIN_MOVE 50 // Minimum vlen of player velocity to be picked up by motion detector +#define PC_SCOUT_SCAN_TIME 2 // # of seconds between each scan pulse +#define PC_SCOUT_SCAN_RANGE 100 // Default scanner range +#define PC_SCOUT_SCAN_COST 2 // Default scanner cell useage per scan + +// Class Details for SNIPER +#define PC_SNIPER_SKIN 5 +#define PC_SNIPER_MAXHEALTH 90 +#define PC_SNIPER_MAXSPEED 300 +#define PC_SNIPER_MAXSTRAFESPEED 300 +#define PC_SNIPER_MAXARMOR 50 +#define PC_SNIPER_INITARMOR 0 +#define PC_SNIPER_MAXARMORTYPE 0.3 +#define PC_SNIPER_INITARMORTYPE 0.3 +#define PC_SNIPER_ARMORCLASSES 3 // #AT_SAVESHOT | #AT_SAVENAIL +#define PC_SNIPER_INITARMORCLASS 0 +#define PC_SNIPER_WEAPONS WEAP_SNIPER_RIFLE | WEAP_AUTO_RIFLE | WEAP_AXE | WEAP_NAILGUN +#define PC_SNIPER_MAXAMMO_SHOT 75 +#define PC_SNIPER_MAXAMMO_NAIL 100 +#define PC_SNIPER_MAXAMMO_CELL 50 +#define PC_SNIPER_MAXAMMO_ROCKET 25 +#define PC_SNIPER_INITAMMO_SHOT 60 +#define PC_SNIPER_INITAMMO_NAIL 50 +#define PC_SNIPER_INITAMMO_CELL 0 +#define PC_SNIPER_INITAMMO_ROCKET 0 +#define PC_SNIPER_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_SNIPER_GRENADE_TYPE_2 GR_TYPE_NONE +#define PC_SNIPER_GRENADE_INIT_1 2 +#define PC_SNIPER_GRENADE_INIT_2 0 +#define PC_SNIPER_TF_ITEMS 0 + +// Class Details for SOLDIER +#define PC_SOLDIER_SKIN 6 +#define PC_SOLDIER_MAXHEALTH 100 +#define PC_SOLDIER_MAXSPEED 240 +#define PC_SOLDIER_MAXSTRAFESPEED 240 +#define PC_SOLDIER_MAXARMOR 200 +#define PC_SOLDIER_INITARMOR 100 +#define PC_SOLDIER_MAXARMORTYPE 0.8 +#define PC_SOLDIER_INITARMORTYPE 0.8 +#define PC_SOLDIER_ARMORCLASSES 31 // ALL +#define PC_SOLDIER_INITARMORCLASS 0 +#define PC_SOLDIER_WEAPONS WEAP_AXE | WEAP_SHOTGUN | WEAP_SUPER_SHOTGUN | WEAP_ROCKET_LAUNCHER +#define PC_SOLDIER_MAXAMMO_SHOT 100 +#define PC_SOLDIER_MAXAMMO_NAIL 100 +#define PC_SOLDIER_MAXAMMO_CELL 50 +#define PC_SOLDIER_MAXAMMO_ROCKET 50 +#define PC_SOLDIER_INITAMMO_SHOT 50 +#define PC_SOLDIER_INITAMMO_NAIL 0 +#define PC_SOLDIER_INITAMMO_CELL 0 +#define PC_SOLDIER_INITAMMO_ROCKET 10 +#define PC_SOLDIER_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_SOLDIER_GRENADE_TYPE_2 GR_TYPE_NAIL +#define PC_SOLDIER_GRENADE_INIT_1 4 +#define PC_SOLDIER_GRENADE_INIT_2 1 +#define PC_SOLDIER_TF_ITEMS 0 + +#define MAX_NAIL_GRENS 2 // Can only have 2 Nail grens active +#define MAX_NAPALM_GRENS 2 // Can only have 2 Napalm grens active +#define MAX_GAS_GRENS 2 // Can only have 2 Gas grenades active +#define MAX_MIRV_GRENS 2 // Can only have 2 Mirv's +#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 +#define PC_MEDIC_MAXSPEED 320 +#define PC_MEDIC_MAXSTRAFESPEED 320 +#define PC_MEDIC_MAXARMOR 100 +#define PC_MEDIC_INITARMOR 50 +#define PC_MEDIC_MAXARMORTYPE 0.6 +#define PC_MEDIC_INITARMORTYPE 0.3 +#define PC_MEDIC_ARMORCLASSES 11 // ALL except EXPLOSION +#define PC_MEDIC_INITARMORCLASS 0 +#define PC_MEDIC_WEAPONS WEAP_BIOWEAPON | WEAP_MEDIKIT | WEAP_SHOTGUN | WEAP_SUPER_SHOTGUN | WEAP_SUPER_NAILGUN +#define PC_MEDIC_MAXAMMO_SHOT 75 +#define PC_MEDIC_MAXAMMO_NAIL 150 +#define PC_MEDIC_MAXAMMO_CELL 50 +#define PC_MEDIC_MAXAMMO_ROCKET 25 +#define PC_MEDIC_MAXAMMO_MEDIKIT 100 +#define PC_MEDIC_INITAMMO_SHOT 50 +#define PC_MEDIC_INITAMMO_NAIL 50 +#define PC_MEDIC_INITAMMO_CELL 0 +#define PC_MEDIC_INITAMMO_ROCKET 0 +#define PC_MEDIC_INITAMMO_MEDIKIT 50 +#define PC_MEDIC_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_MEDIC_GRENADE_TYPE_2 GR_TYPE_CONCUSSION +#define PC_MEDIC_GRENADE_INIT_1 3 +#define PC_MEDIC_GRENADE_INIT_2 2 +#define PC_MEDIC_TF_ITEMS 0 +#define PC_MEDIC_REGEN_TIME 3 // Number of seconds between each regen. +#define PC_MEDIC_REGEN_AMOUNT 2 // Amount of health regenerated each regen. + +// Class Details for HVYWEAP +#define PC_HVYWEAP_SKIN 2 +#define PC_HVYWEAP_MAXHEALTH 100 +#define PC_HVYWEAP_MAXSPEED 230 +#define PC_HVYWEAP_MAXSTRAFESPEED 230 +#define PC_HVYWEAP_MAXARMOR 300 +#define PC_HVYWEAP_INITARMOR 150 +#define PC_HVYWEAP_MAXARMORTYPE 0.8 +#define PC_HVYWEAP_INITARMORTYPE 0.8 +#define PC_HVYWEAP_ARMORCLASSES 31 // ALL +#define PC_HVYWEAP_INITARMORCLASS 0 +#define PC_HVYWEAP_WEAPONS WEAP_ASSAULT_CANNON | WEAP_AXE | WEAP_SHOTGUN | WEAP_SUPER_SHOTGUN +#define PC_HVYWEAP_MAXAMMO_SHOT 200 +#define PC_HVYWEAP_MAXAMMO_NAIL 200 +#define PC_HVYWEAP_MAXAMMO_CELL 50 +#define PC_HVYWEAP_MAXAMMO_ROCKET 25 +#define PC_HVYWEAP_INITAMMO_SHOT 200 +#define PC_HVYWEAP_INITAMMO_NAIL 0 +#define PC_HVYWEAP_INITAMMO_CELL 30 +#define PC_HVYWEAP_INITAMMO_ROCKET 0 +#define PC_HVYWEAP_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_HVYWEAP_GRENADE_TYPE_2 GR_TYPE_MIRV +#define PC_HVYWEAP_GRENADE_INIT_1 4 +#define PC_HVYWEAP_GRENADE_INIT_2 1 +#define PC_HVYWEAP_TF_ITEMS 0 +#define PC_HVYWEAP_CELL_USAGE 7 // Amount of cells spent to power up assault cannon + + + +// Class Details for PYRO +#define PC_PYRO_SKIN 21 +#define PC_PYRO_MAXHEALTH 100 +#define PC_PYRO_MAXSPEED 300 +#define PC_PYRO_MAXSTRAFESPEED 300 +#define PC_PYRO_MAXARMOR 150 +#define PC_PYRO_INITARMOR 50 +#define PC_PYRO_MAXARMORTYPE 0.6 +#define PC_PYRO_INITARMORTYPE 0.6 +#define PC_PYRO_ARMORCLASSES 27 // ALL except EXPLOSION +#define PC_PYRO_INITARMORCLASS 16 // #AT_SAVEFIRE +#define PC_PYRO_WEAPONS WEAP_INCENDIARY | WEAP_FLAMETHROWER | WEAP_AXE | WEAP_SHOTGUN +#define PC_PYRO_MAXAMMO_SHOT 40 +#define PC_PYRO_MAXAMMO_NAIL 50 +#define PC_PYRO_MAXAMMO_CELL 200 +#define PC_PYRO_MAXAMMO_ROCKET 20 +#define PC_PYRO_INITAMMO_SHOT 20 +#define PC_PYRO_INITAMMO_NAIL 0 +#define PC_PYRO_INITAMMO_CELL 120 +#define PC_PYRO_INITAMMO_ROCKET 5 +#define PC_PYRO_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_PYRO_GRENADE_TYPE_2 GR_TYPE_NAPALM +#define PC_PYRO_GRENADE_INIT_1 1 +#define PC_PYRO_GRENADE_INIT_2 4 +#define PC_PYRO_TF_ITEMS 0 +#define PC_PYRO_ROCKET_USAGE 3 // Number of rockets per incendiary cannon shot + +// Class Details for SPY +#define PC_SPY_SKIN 22 +#define PC_SPY_MAXHEALTH 90 +#define PC_SPY_MAXSPEED 300 +#define PC_SPY_MAXSTRAFESPEED 300 +#define PC_SPY_MAXARMOR 100 +#define PC_SPY_INITARMOR 25 +#define PC_SPY_MAXARMORTYPE 0.6 // Was 0.3 +#define PC_SPY_INITARMORTYPE 0.6 // Was 0.3 +#define PC_SPY_ARMORCLASSES 27 // ALL except EXPLOSION +#define PC_SPY_INITARMORCLASS 0 +#define PC_SPY_WEAPONS WEAP_AXE | WEAP_TRANQ | WEAP_SUPER_SHOTGUN | WEAP_NAILGUN +#define PC_SPY_MAXAMMO_SHOT 40 +#define PC_SPY_MAXAMMO_NAIL 100 +#define PC_SPY_MAXAMMO_CELL 30 +#define PC_SPY_MAXAMMO_ROCKET 15 +#define PC_SPY_INITAMMO_SHOT 40 +#define PC_SPY_INITAMMO_NAIL 50 +#define PC_SPY_INITAMMO_CELL 10 +#define PC_SPY_INITAMMO_ROCKET 0 +#define PC_SPY_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_SPY_GRENADE_TYPE_2 GR_TYPE_GAS +#define PC_SPY_GRENADE_INIT_1 2 +#define PC_SPY_GRENADE_INIT_2 2 +#define PC_SPY_TF_ITEMS 0 +#define PC_SPY_CELL_REGEN_TIME 5 +#define PC_SPY_CELL_REGEN_AMOUNT 1 +#define PC_SPY_CELL_USAGE 3 // Amount of cells spent while invisible +#define PC_SPY_GO_UNDERCOVER_TIME 4 // Time it takes to go undercover + +// Class Details for ENGINEER +#define PC_ENGINEER_SKIN 22 // Not used anymore +#define PC_ENGINEER_MAXHEALTH 80 +#define PC_ENGINEER_MAXSPEED 300 +#define PC_ENGINEER_MAXSTRAFESPEED 300 +#define PC_ENGINEER_MAXARMOR 50 +#define PC_ENGINEER_INITARMOR 25 +#define PC_ENGINEER_MAXARMORTYPE 0.6 +#define PC_ENGINEER_INITARMORTYPE 0.3 +#define PC_ENGINEER_ARMORCLASSES 31 // ALL +#define PC_ENGINEER_INITARMORCLASS 0 +#define PC_ENGINEER_WEAPONS WEAP_SPANNER | WEAP_LASER | WEAP_SUPER_SHOTGUN +#define PC_ENGINEER_MAXAMMO_SHOT 50 +#define PC_ENGINEER_MAXAMMO_NAIL 50 +#define PC_ENGINEER_MAXAMMO_CELL 200 // synonymous with metal +#define PC_ENGINEER_MAXAMMO_ROCKET 30 +#define PC_ENGINEER_INITAMMO_SHOT 20 +#define PC_ENGINEER_INITAMMO_NAIL 25 +#define PC_ENGINEER_INITAMMO_CELL 100 // synonymous with metal +#define PC_ENGINEER_INITAMMO_ROCKET 0 +#define PC_ENGINEER_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_ENGINEER_GRENADE_TYPE_2 GR_TYPE_EMP +#define PC_ENGINEER_GRENADE_INIT_1 2 +#define PC_ENGINEER_GRENADE_INIT_2 2 +#define PC_ENGINEER_TF_ITEMS 0 + +// Class Details for CIVILIAN +#define PC_CIVILIAN_SKIN 22 +#define PC_CIVILIAN_MAXHEALTH 50 +#define PC_CIVILIAN_MAXSPEED 240 +#define PC_CIVILIAN_MAXSTRAFESPEED 240 +#define PC_CIVILIAN_MAXARMOR 0 +#define PC_CIVILIAN_INITARMOR 0 +#define PC_CIVILIAN_MAXARMORTYPE 0 +#define PC_CIVILIAN_INITARMORTYPE 0 +#define PC_CIVILIAN_ARMORCLASSES 0 +#define PC_CIVILIAN_INITARMORCLASS 0 +#define PC_CIVILIAN_WEAPONS WEAP_AXE +#define PC_CIVILIAN_MAXAMMO_SHOT 0 +#define PC_CIVILIAN_MAXAMMO_NAIL 0 +#define PC_CIVILIAN_MAXAMMO_CELL 0 +#define PC_CIVILIAN_MAXAMMO_ROCKET 0 +#define PC_CIVILIAN_INITAMMO_SHOT 0 +#define PC_CIVILIAN_INITAMMO_NAIL 0 +#define PC_CIVILIAN_INITAMMO_CELL 0 +#define PC_CIVILIAN_INITAMMO_ROCKET 0 +#define PC_CIVILIAN_GRENADE_TYPE_1 0 +#define PC_CIVILIAN_GRENADE_TYPE_2 0 +#define PC_CIVILIAN_GRENADE_INIT_1 0 +#define PC_CIVILIAN_GRENADE_INIT_2 0 +#define PC_CIVILIAN_TF_ITEMS 0 + + +/*==========================================================================*/ +/* TEAMFORTRESS GOALS */ +/*==========================================================================*/ +// For all these defines, see the tfortmap.txt that came with the zip +// 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 + +// Defines for Goal Effects types : goal_effect +#define TFGE_AP 1 // AP is affected. Default. +#define TFGE_AP_TEAM 2 // All of the AP's team. +#define TFGE_NOT_AP_TEAM 4 // All except AP's team. +#define TFGE_NOT_AP 8 // All except AP. +#define TFGE_WALL 16 // If set, walls stop the Radius effects +#define TFGE_SAME_ENVIRONMENT 32 // If set, players in a different environment to the Goal are not affected +#define TFGE_TIMER_CHECK_AP 64 // If set, Timer Goals check their critera for all players fitting their effects + +// Defines for Goal Result types : goal_result +#define TFGR_SINGLE 1 // Goal can only be activated once +#define TFGR_ADD_BONUSES 2 // Any Goals activated by this one give their bonuses +#define TFGR_ENDGAME 4 // Goal fires Intermission, displays scores, and ends level +#define TFGR_NO_ITEM_RESULTS 8 // GoalItems given by this Goal don't do results +#define TFGR_REMOVE_DISGUISE 16 // Prevent/Remove undercover from any Spy +#define TFGR_FORCE_RESPAWN 32 // Forces the player to teleport to a respawn point +#define TFGR_DESTROY_BUILDINGS 64 // Destroys this player's buildings, if anys + +// Defines for Goal Group Result types : goal_group +// None! +// But I'm leaving this variable in there, since it's fairly likely +// that some will show up sometime. + +// Defines for Goal Item types, : goal_activation (in items) +#define TFGI_GLOW 1 // Players carrying this GoalItem will glow +#define TFGI_SLOW 2 // Players carrying this GoalItem will move at half-speed +#define TFGI_DROP 4 // Players dying with this item will drop it +#define TFGI_RETURN_DROP 8 // Return if a player with it dies +#define TFGI_RETURN_GOAL 16 // Return if a player with it has it removed by a goal's activation +#define TFGI_RETURN_REMOVE 32 // Return if it is removed by TFGI_REMOVE +#define TFGI_REVERSE_AP 64 // Only pickup if the player _doesn't_ match AP Details +#define TFGI_REMOVE 128 // Remove if left untouched for 2 minutes after being dropped +#define TFGI_KEEP 256 // Players keep this item even when they die +#define TFGI_ITEMGLOWS 512 // Item glows when on the ground +#define TFGI_DONTREMOVERES 1024 // Don't remove results when the item is removed +#define TFGI_DROPTOGROUND 2048 // Drop To Ground when spawning +#define TFGI_CANBEDROPPED 4096 // Can be voluntarily dropped by players +#define TFGI_SOLID 8192 // Is solid... blocks bullets, etc + +// Defines for methods of GoalItem returning +#define GI_RET_DROP_DEAD 0 // Dropped by a dead player +#define GI_RET_DROP_LIVING 1 // Dropped by a living player +#define GI_RET_GOAL 2 // Returned by a Goal +#define GI_RET_TIME 3 // Returned due to timeout + +// Defines for TeamSpawnpoints : goal_activation (in teamspawns) +#define TFSP_MULTIPLEITEMS 1 // Give out the GoalItem multiple times +#define TFSP_MULTIPLEMSGS 2 // Display the message multiple times + +// Defines for TeamSpawnpoints : goal_effects (in teamspawns) +#define TFSP_REMOVESELF 1 // Remove itself after being spawned on + +// Defines for Goal States +#define TFGS_ACTIVE 1 +#define TFGS_INACTIVE 2 +#define TFGS_REMOVED 3 +#define TFGS_DELAYED 4 + +// Defines for GoalItem Removing from Player Methods +#define GI_DROP_PLAYERDEATH 0 // Dropped by a dying player +#define GI_DROP_REMOVEGOAL 1 // Removed by a Goal +#define GI_DROP_PLAYERDROP 2 // Dropped by a player + +// Legal Playerclass Handling +#define TF_ILL_SCOUT 1 +#define TF_ILL_SNIPER 2 +#define TF_ILL_SOLDIER 4 +#define TF_ILL_DEMOMAN 8 +#define TF_ILL_MEDIC 16 +#define TF_ILL_HVYWEP 32 +#define TF_ILL_PYRO 64 +#define TF_ILL_RANDOMPC 128 +#define TF_ILL_SPY 256 +#define TF_ILL_ENGINEER 512 + +// Addition classes +#define CLASS_TFGOAL 128 +#define CLASS_TFGOAL_TIMER 129 +#define CLASS_TFGOAL_ITEM 130 +#define CLASS_TFSPAWN 131 + +/*==========================================================================*/ +/* Flamethrower */ +/*==========================================================================*/ +#define FLAME_PLYRMAXTIME 4.5 // lifetime in seconds of a flame on a player +#define FLAME_MAXBURNTIME 8 // lifetime in seconds of a flame on the world (big ones) +#define NAPALM_MAXBURNTIME 20 // lifetime in seconds of flame from a napalm grenade +#define FLAME_MAXPLYRFLAMES 4 // maximum number of flames on a player +#define FLAME_NUMLIGHTS 1 // maximum number of light flame +#define FLAME_BURNRATIO 0.3 // the chance of a flame not 'sticking' +#define GR_TYPE_FLAMES_NO 15 // number of flames spawned when a grenade explode +#define FLAME_DAMAGE_TIME 1 // Interval between damage burns from flames +#define FLAME_EFFECT_TIME 0.2 // frequency at which we display flame effects. +#define FLAME_THINK_TIME 0.1 // Seconds between times the flame checks burn + +/*==================================================*/ +/* CTF Support defines */ +/*==================================================*/ +#define CTF_FLAG1 1 +#define CTF_FLAG2 2 +#define CTF_DROPOFF1 3 +#define CTF_DROPOFF2 4 +#define CTF_SCORE1 5 +#define CTF_SCORE2 6 + +//.float hook_out; + +/*==================================================*/ +/* Camera defines */ +/*==================================================*/ +/* +float live_camera; +.float camdist; +.vector camangle; +.entity camera_list; +*/ + +/*==================================================*/ +/* QuakeWorld defines */ +/*==================================================*/ +/* +float already_chosen_map; + +// grappling hook variables +.entity hook; +.float on_hook; +.float fire_held_down;// flag - TRUE if player is still holding down the + // fire button after throwing a hook. +*/ +/*==================================================*/ +/* Server Settings */ +/*==================================================*/ +// Admin modes +#define ADMIN_MODE_NONE 0 +#define ADMIN_MODE_DEAL 1 + +/*==================================================*/ +/* Death Message defines */ +/*==================================================*/ +#define DMSG_SHOTGUN 1 +#define DMSG_SSHOTGUN 2 +#define DMSG_NAILGUN 3 +#define DMSG_SNAILGUN 4 +#define DMSG_GRENADEL 5 +#define DMSG_ROCKETL 6 +#define DMSG_LIGHTNING 7 +#define DMSG_GREN_HAND 8 +#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 +#define DMSG_ASSAULTCANNON 20 +#define DMSG_HOOK 21 +#define DMSG_BACKSTAB 22 +#define DMSG_MEDIKIT 23 +#define DMSG_GREN_GAS 24 +#define DMSG_TRANQ 25 +#define DMSG_LASERBOLT 26 +#define DMSG_SENTRYGUN_BULLET 27 +#define DMSG_SNIPERLEGSHOT 28 +#define DMSG_SNIPERHEADSHOT 29 +#define DMSG_GREN_EMP 30 +#define DMSG_GREN_EMP_AMMO 31 +#define DMSG_SPANNER 32 +#define DMSG_INCENDIARY 33 +#define DMSG_SENTRYGUN_ROCKET 34 +#define DMSG_GREN_FLASH 35 +#define DMSG_TRIGGER 36 +#define DMSG_MIRROR 37 +#define DMSG_SENTRYDEATH 38 +#define DMSG_DISPENSERDEATH 39 +#define DMSG_GREN_AIRPIPE 40 +#define DMSG_CALTROP 41 + +/*==================================================*/ +// TOGGLEFLAGS +/*==================================================*/ +// Some of the toggleflags aren't used anymore, but the bits are still +// there to provide compatability with old maps +#define TFLAG_CLASS_PERSIST (1 << 0) // Persistent Classes Bit +#define TFLAG_CHEATCHECK (1 << 1) // Cheatchecking Bit +#define TFLAG_RESPAWNDELAY (1 << 2) // RespawnDelay bit +//#define TFLAG_UN (1 << 3) // NOT USED ANYMORE +#define TFLAG_OLD_GRENS (1 << 3) // Use old concussion grenade and flash grenade +#define TFLAG_UN2 (1 << 4) // NOT USED ANYMORE +#define TFLAG_UN3 (1 << 5) // NOT USED ANYMORE +#define TFLAG_UN4 (1 << 6) // NOT USED ANYMORE: Was Autoteam. CVAR tfc_autoteam used now. +#define TFLAG_TEAMFRAGS (1 << 7) // Individual Frags, or Frags = TeamScore +#define TFLAG_FIRSTENTRY (1 << 8) // Used to determine the first time toggleflags is set + // In a map. Cannot be toggled by players. +#define TFLAG_SPYINVIS (1 << 9) // Spy invisible only +#define TFLAG_GRAPPLE (1 << 10) // Grapple on/off +//#define TFLAG_FULLTEAMSCORE (1 << 11) // Each Team's score is TeamScore + Frags +#define TFLAG_FLAGEMULATION (1 << 12) // Flag emulation on for old TF maps +#define TFLAG_USE_STANDARD (1 << 13) // Use the TF War standard for Flag emulation + +#define TFLAG_FRAGSCORING (1 << 14) // Use frag scoring only + +/*======================*/ +// Menu stuff // +/*======================*/ + +#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_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 + +//============================ +// Timer Types +#define TF_TIMER_ANY 0 +#define TF_TIMER_CONCUSSION 1 +#define TF_TIMER_INFECTION 2 +#define TF_TIMER_HALLUCINATION 3 +#define TF_TIMER_TRANQUILISATION 4 +#define TF_TIMER_ROTHEALTH 5 +#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 + +// Non Player timers +#define TF_TIMER_RETURNITEM 100 +#define TF_TIMER_DELAYEDGOAL 101 + +//============================ +// Teamscore printing +#define TS_PRINT_SHORT 1 +#define TS_PRINT_LONG 2 +#define TS_PRINT_LONG_TO_ALL 3 + +#ifndef TF_DEFS_ONLY +/*==================================================*/ +/* GLOBAL VARIABLES */ +/*==================================================*/ +// FortressMap stuff +extern float number_of_teams; // number of teams supported by the map +extern int illegalclasses[5]; // Illegal playerclasses for all teams +extern int civilianteams; // Bitfield holding Civilian teams +extern Vector rgbcolors[5]; // RGB colors for each of the 4 teams +extern int teamcolors[5]; // Colours for each of the 4 teams +extern int teamscores[5]; // Goal Score of each team +extern int g_iOrderedTeams[5]; // Teams ordered into order of winners->losers +extern int teamfrags[5]; // Total Frags for each team +extern int teamlives[5]; // Number of lives each team's players have +extern int teammaxplayers[5]; // Max number of players allowed in each team +extern float teamadvantage[5]; // only used if the teamplay equalisation bits are set + // stores the damage ratio players take/give +extern int teamallies[5]; // Keeps track of which teams are allied +extern string_t team_names[5]; + +extern BOOL CTF_Map; +extern BOOL birthday; +extern BOOL christmas; + +extern float num_world_flames; + +// Clan Battle stuff +extern float clan_scores_dumped; +extern float cb_prematch_time; +extern float fOldPrematch; +extern float fOldCeaseFire; +extern float cb_ceasefire_time; +extern float last_id; +extern float spy_off; +extern float old_grens; +extern float flagem_checked; +extern float flNextEqualisationCalc; +extern BOOL cease_fire; +extern BOOL initial_cease_fire; +extern BOOL last_cease_fire; +// Autokick stuff +extern float autokick_kills; + +extern float deathmsg; // Global, which is set before every T_Damage, to indicate + // the death message that should be used. + +extern char *sTeamSpawnNames[]; +extern char *sClassNames[]; +extern char *sClassModelFiles[]; +extern char *sClassModels[]; +extern char *sClassCfgs[]; +extern char *sGrenadeNames[]; +extern string_t team_menu_string; + +extern int toggleflags; // toggleable flags + +extern CBaseEntity* g_pLastSpawns[5]; +extern BOOL g_bFirstClient; + +extern float g_fNextPrematchAlert; + +typedef struct +{ + int ip; + edict_t *pEdict; +} ip_storage_t; + +extern ip_storage_t g_IpStorage[32]; + +class CGhost; +/*==========================================================================*/ +BOOL ClassIsRestricted(float tno, int pc); +char* GetTeamName(int tno); +int TeamFortress_GetNoPlayers(); +void DestroyBuilding(CBaseEntity *eng, char *bld); +void teamsprint( int tno, CBaseEntity *ignore, int msg_dest, const char *st, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL ); +float anglemod( float v ); + +// Team Funcs +BOOL TeamFortress_TeamIsCivilian(float tno); +void TeamFortress_TeamShowScores(BOOL bLong, CBasePlayer *pPlayer); +BOOL TeamFortress_TeamPutPlayerInTeam(); +void TeamFortress_TeamSetColor(int tno); +void TeamFortress_TeamIncreaseScore(int tno, int scoretoadd); +int TeamFortress_TeamGetScoreFrags(int tno); +int TeamFortress_TeamGetNoPlayers(int tno); +float TeamEqualiseDamage(CBaseEntity *targ, CBaseEntity *attacker, float damage); +BOOL IsSpawnPointValid( Vector &pos ); +BOOL TeamFortress_SortTeams( void ); +void DumpClanScores( void ); +void CalculateTeamEqualiser(); + +// mapscript funcs +void ParseTFServerSettings(); +void ParseTFMapSettings(); +CBaseEntity* Finditem(int ino); +CBaseEntity* Findgoal(int gno); +CBaseEntity* Findteamspawn(int gno); +void RemoveGoal(CBaseEntity *Goal); +void tfgoalitem_GiveToPlayer(CBaseEntity *Item, CBasePlayer *AP, CBaseEntity *Goal); +void dremove( CBaseEntity *te ); +void tfgoalitem_RemoveFromPlayer(CBaseEntity *Item, CBasePlayer *AP, int iMethod); +void tfgoalitem_drop(CBaseEntity *Item, BOOL PAlive, CBasePlayer *P); +void DisplayItemStatus(CBaseEntity *Goal, CBasePlayer *Player, CBaseEntity *Item); +void tfgoalitem_checkgoalreturn(CBaseEntity *Item); +void DoGoalWork(CBaseEntity *Goal, CBasePlayer *AP); +void DoResults(CBaseEntity *Goal, CBasePlayer *AP, BOOL bAddBonuses); +void DoGroupWork(CBaseEntity *Goal, CBasePlayer *AP); +// hooks into the mapscript for all entities +BOOL ActivateDoResults(CBaseEntity *Goal, CBasePlayer *AP, CBaseEntity *ActivatingGoal); +BOOL ActivationSucceeded(CBaseEntity *Goal, CBasePlayer *AP, CBaseEntity *ActivatingGoal); + +// prematch & ceasefire +void Display_Prematch(); +void Check_Ceasefire(); + +// admin +void KickPlayer( CBaseEntity *pTarget ); +void BanPlayer( CBaseEntity *pTarget ); +CGhost *FindGhost( int iGhostID ); +int GetBattleID( edict_t *pEntity ); + +extern cvar_t tfc_spam_penalty1;// the initial gag penalty for a spammer (seconds) +extern cvar_t tfc_spam_penalty2;// incremental gag penalty (seconds) for each time gagged spammer continues to speak. +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; + +/*==========================================================================*/ +class CTFFlame : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT FlameThink( void ); + static CTFFlame *FlameSpawn( CBaseEntity *pOwner, CBaseEntity *pTarget ); + void FlameDestroy( void ); + + float m_flNextDamageTime; +}; + +/*==========================================================================*/ +// MAPSCRIPT CLASSES +class CTFGoal : public CBaseAnimating +{ +public: + void Spawn( void ); + void StartGoal( void ); + void EXPORT PlaceGoal( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int Classify ( void ) { return CLASS_TFGOAL; } + + void SetObjectCollisionBox( void ); +}; + +class CTFGoalItem : public CTFGoal +{ +public: + void Spawn( void ); + void StartItem( void ); + void EXPORT PlaceItem( void ); + int Classify ( void ) { return CLASS_TFGOAL_ITEM; } + + float m_flDroppedAt; +}; + +class CTFTimerGoal : public CTFGoal +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_TFGOAL_TIMER; } +}; + +class CTFSpawn : public CBaseEntity +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_TFSPAWN; } +}; + +class CTFDetect : public CBaseEntity +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_TFGOAL; } +}; + +class CTelefragDeath : public CBaseEntity +{ +public: + void Spawn( void ); + void EXPORT DeathTouch( CBaseEntity *pOther ); +}; + +#endif // TF_DEFS_ONLY +#endif // __TF_DEFS_H + + diff --git a/hlsdk/ricochet/cl_dll/train.cpp b/hlsdk/ricochet/cl_dll/train.cpp new file mode 100644 index 00000000..9e15e167 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/train.cpp @@ -0,0 +1,85 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// +// Train.cpp +// +// implementation of CHudAmmo class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE(m_Train, Train ) + + +int CHudTrain::Init(void) +{ + HOOK_MESSAGE( Train ); + + m_iPos = 0; + m_iFlags = 0; + gHUD.AddHudElem(this); + + return 1; +}; + +int CHudTrain::VidInit(void) +{ + m_hSprite = 0; + + return 1; +}; + +int CHudTrain::Draw(float fTime) +{ + if ( !m_hSprite ) + m_hSprite = LoadSprite("sprites/%d_train.spr"); + + if (m_iPos) + { + int r, g, b, x, y; + + UnpackRGB(r,g,b, RGB_YELLOWISH); + SPR_Set(m_hSprite, r, g, b ); + + // This should show up to the right and part way up the armor number + y = ScreenHeight - SPR_Height(m_hSprite,0) - gHUD.m_iFontHeight; + x = ScreenWidth/3 + SPR_Width(m_hSprite,0)/4; + + SPR_DrawAdditive( m_iPos - 1, x, y, NULL); + + } + + return 1; +} + + +int CHudTrain::MsgFunc_Train(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + // update Train data + m_iPos = READ_BYTE(); + + if (m_iPos) + m_iFlags |= HUD_ACTIVE; + else + m_iFlags &= ~HUD_ACTIVE; + + return 1; +} diff --git a/hlsdk/ricochet/cl_dll/tri.cpp b/hlsdk/ricochet/cl_dll/tri.cpp new file mode 100644 index 00000000..0a0c8835 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/tri.cpp @@ -0,0 +1,129 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// Triangle rendering, if any + +#include "hud.h" +#include "cl_util.h" + +// Triangle rendering apis are in gEngfuncs.pTriAPI + +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "triangleapi.h" + +#define DLLEXPORT __declspec( dllexport ) + +extern "C" +{ + void DLLEXPORT HUD_DrawNormalTriangles( void ); + void DLLEXPORT HUD_DrawTransparentTriangles( void ); +}; + +//#define TEST_IT +#if defined( TEST_IT ) + +/* +================= +Draw_Triangles + +Example routine. Draws a sprite offset from the player origin. +================= +*/ +void Draw_Triangles( void ) +{ + cl_entity_t *player; + vec3_t org; + + // Load it up with some bogus data + player = gEngfuncs.GetLocalPlayer(); + if ( !player ) + return; + + org = player->origin; + + org.x += 50; + org.y += 50; + + if (gHUD.m_hsprCursor == 0) + { + char sz[256]; + sprintf( sz, "sprites/cursor.spr" ); + gHUD.m_hsprCursor = SPR_Load( sz ); + } + + 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 ); +} + +#endif + +/* +================= +HUD_DrawNormalTriangles + +Non-transparent triangles-- add them here +================= +*/ +void DLLEXPORT HUD_DrawNormalTriangles( void ) +{ + +#if defined( TEST_IT ) +// Draw_Triangles(); +#endif +} + +/* +================= +HUD_DrawTransparentTriangles + +Render any triangles with transparent rendermode needs here +================= +*/ +void DLLEXPORT HUD_DrawTransparentTriangles( void ) +{ + +#if defined( TEST_IT ) +// Draw_Triangles(); +#endif +} \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/util.cpp b/hlsdk/ricochet/cl_dll/util.cpp new file mode 100644 index 00000000..4ad17133 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/util.cpp @@ -0,0 +1,101 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// +// util.cpp +// +// implementation of class-less helper functions +// + +#include "STDIO.H" +#include "STDLIB.H" +#include "MATH.H" + +#include "hud.h" +#include "cl_util.h" +#include + +vec3_t vec3_origin( 0, 0, 0 ); + +double sqrt(double x); + +float Length(const float *v) +{ + int i; + float length; + + length = 0; + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = sqrt (length); // FIXME + + return length; +} + +float VectorNormalize (float *v) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; + +} + +void VectorInverse ( float *v ) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void VectorScale (const float *in, float scale, float *out) +{ + out[0] = in[0]*scale; + out[1] = in[1]*scale; + out[2] = in[2]*scale; +} + +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]; +} + + +HSPRITE LoadSprite(const char *pszName) +{ + int i; + char sz[256]; + + if (ScreenWidth < 640) + i = 320; + else + i = 640; + + sprintf(sz, pszName, i); + + return SPR_Load(sz); +} + diff --git a/hlsdk/ricochet/cl_dll/util_vector.h b/hlsdk/ricochet/cl_dll/util_vector.h new file mode 100644 index 00000000..40b9341d --- /dev/null +++ b/hlsdk/ricochet/cl_dll/util_vector.h @@ -0,0 +1,121 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// Vector.h +// A subset of the extdll.h in the project HL Entity DLL +// + +// Misc C-runtime library headers +#include "STDIO.H" +#include "STDLIB.H" +#include "MATH.H" + +// Header file containing definition of globalvars_t and entvars_t +typedef int func_t; // +typedef int string_t; // from engine's pr_comp.h; +typedef float vec_t; // needed before including progdefs.h + +//========================================================= +// 2DVector - used for many pathfinding and many other +// operations that are treated as planar rather than 3d. +//========================================================= +class Vector2D +{ +public: + inline Vector2D(void) { } + inline Vector2D(float X, float Y) { x = X; y = Y; } + inline Vector2D operator+(const Vector2D& v) const { return Vector2D(x+v.x, y+v.y); } + inline Vector2D operator-(const Vector2D& v) const { return Vector2D(x-v.x, y-v.y); } + inline Vector2D operator*(float fl) const { return Vector2D(x*fl, y*fl); } + inline Vector2D operator/(float fl) const { return Vector2D(x/fl, y/fl); } + + inline float Length(void) const { return (float)sqrt(x*x + y*y ); } + + inline Vector2D Normalize ( void ) const + { + Vector2D vec2; + + float flLen = Length(); + if ( flLen == 0 ) + { + return Vector2D( (float)0, (float)0 ); + } + else + { + flLen = 1 / flLen; + return Vector2D( x * flLen, y * flLen ); + } + } + + vec_t x, y; +}; + +inline float DotProduct(const Vector2D& a, const Vector2D& b) { return( a.x*b.x + a.y*b.y ); } +inline Vector2D operator*(float fl, const Vector2D& v) { return v * fl; } + +//========================================================= +// 3D Vector +//========================================================= +class Vector // same data-layout as engine's vec3_t, +{ // which is a vec_t[3] +public: + // Construction/destruction + inline Vector(void) { } + inline Vector(float X, float Y, float Z) { x = X; y = Y; z = Z; } + inline Vector(double X, double Y, double Z) { x = (float)X; y = (float)Y; z = (float)Z; } + inline Vector(int X, int Y, int Z) { x = (float)X; y = (float)Y; z = (float)Z; } + inline Vector(const Vector& v) { x = v.x; y = v.y; z = v.z; } + inline Vector(float rgfl[3]) { x = rgfl[0]; y = rgfl[1]; z = rgfl[2]; } + + // Operators + inline Vector operator-(void) const { return Vector(-x,-y,-z); } + inline int operator==(const Vector& v) const { return x==v.x && y==v.y && z==v.z; } + inline int operator!=(const Vector& v) const { return !(*this==v); } + inline Vector operator+(const Vector& v) const { return Vector(x+v.x, y+v.y, z+v.z); } + inline Vector operator-(const Vector& v) const { return Vector(x-v.x, y-v.y, z-v.z); } + inline Vector operator*(float fl) const { return Vector(x*fl, y*fl, z*fl); } + inline Vector operator/(float fl) const { return Vector(x/fl, y/fl, z/fl); } + + // Methods + inline void CopyToArray(float* rgfl) const { rgfl[0] = x, rgfl[1] = y, rgfl[2] = z; } + inline float Length(void) const { return (float)sqrt(x*x + y*y + z*z); } + operator float *() { return &x; } // Vectors will now automatically convert to float * when needed + operator const float *() const { return &x; } // Vectors will now automatically convert to float * when needed + inline Vector Normalize(void) const + { + float flLen = Length(); + if (flLen == 0) return Vector(0,0,1); // ???? + flLen = 1 / flLen; + return Vector(x * flLen, y * flLen, z * flLen); + } + + inline Vector2D Make2D ( void ) const + { + Vector2D Vec2; + + Vec2.x = x; + Vec2.y = y; + + return Vec2; + } + inline float Length2D(void) const { return (float)sqrt(x*x + y*y); } + + // Members + vec_t x, y, z; +}; +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 ); } + +#define vec3_t Vector diff --git a/hlsdk/ricochet/cl_dll/vgui_ConsolePanel.cpp b/hlsdk/ricochet/cl_dll/vgui_ConsolePanel.cpp new file mode 100644 index 00000000..172ec1fa --- /dev/null +++ b/hlsdk/ricochet/cl_dll/vgui_ConsolePanel.cpp @@ -0,0 +1,95 @@ + +#include"vgui_ConsolePanel.h" +#include"hud.h" +#include +#include +#include +#include +#include + +using namespace vgui; + + +namespace +{ + +class Handler : public ActionSignal +{ +private: + + ConsolePanel* _consolePanel; + +public: + + Handler(ConsolePanel* consolePanel) + { + _consolePanel=consolePanel; + } + +public: + + virtual void actionPerformed(Panel* panel) + { + _consolePanel->doExecCommand(); + } + +}; + +} + + + +ConsolePanel::ConsolePanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) +{ + setBorder(new EtchedBorder()); + + _textGrid=new TextGrid(80,21,5,5,200,100); + _textGrid->setBorder(new LoweredBorder()); + _textGrid->setParent(this); + + _textEntry=new TextEntry("",5,5,200,20); + _textEntry->setParent(this); + _textEntry->addActionSignal(new Handler(this)); +} + +int ConsolePanel::print(const char* text) +{ + return _textGrid->printf("%s",text); +} + +int ConsolePanel::vprintf(const char* format,va_list argList) +{ + return _textGrid->vprintf(format,argList); +} + +int ConsolePanel::printf(const char* format,...) +{ + va_list argList; + va_start(argList,format); + int ret=vprintf(format,argList); + va_end(argList); + return ret; +} + +void ConsolePanel::doExecCommand() +{ + char buf[2048]; + _textEntry->getText(0,buf,2048); + _textEntry->setText(null,0); + gEngfuncs.pfnClientCmd(buf); +} + +void ConsolePanel::setSize(int wide,int tall) +{ + Panel::setSize(wide,tall); + + getPaintSize(wide,tall); + + _textGrid->setBounds(5,5,wide-10,tall-35); + _textEntry->setBounds(5,tall-25,wide-10,20); +} + + + + + diff --git a/hlsdk/ricochet/cl_dll/vgui_ConsolePanel.h b/hlsdk/ricochet/cl_dll/vgui_ConsolePanel.h new file mode 100644 index 00000000..7e545e76 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/vgui_ConsolePanel.h @@ -0,0 +1,32 @@ + +#ifndef CONSOLEPANEL_H +#define CONSOLEPANEL_H + +#include +#include + +namespace vgui +{ +class TextGrid; +class TextEntry; +} + + +class ConsolePanel : public vgui::Panel +{ +private: + vgui::TextGrid* _textGrid; + vgui::TextEntry* _textEntry; +public: + ConsolePanel(int x,int y,int wide,int tall); +public: + virtual void setSize(int wide,int tall); + virtual int print(const char* text); + virtual int vprintf(const char* format,va_list argList); + virtual int printf(const char* format,...); + virtual void doExecCommand(); +}; + + + +#endif \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/vgui_ControlConfigPanel.cpp b/hlsdk/ricochet/cl_dll/vgui_ControlConfigPanel.cpp new file mode 100644 index 00000000..4c53f5c8 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/vgui_ControlConfigPanel.cpp @@ -0,0 +1,206 @@ + +#include +#include"vgui_ControlConfigPanel.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace vgui; + +namespace +{ +class FooTablePanel : public TablePanel +{ +private: + Label* _label; + TextEntry* _textEntry; + ControlConfigPanel* _controlConfigPanel; +public: + FooTablePanel(ControlConfigPanel* controlConfigPanel,int x,int y,int wide,int tall,int columnCount) : TablePanel(x,y,wide,tall,columnCount) + { + _controlConfigPanel=controlConfigPanel; + _label=new Label("You are a dumb monkey",0,0,100,20); + _label->setBgColor(Scheme::sc_primary3); + _label->setFgColor(Scheme::sc_primary1); + _label->setFont(Scheme::sf_primary3); + + _textEntry=new TextEntry("",0,0,100,20); + //_textEntry->setFont(Scheme::sf_primary3); + } +public: + virtual int getRowCount() + { + return _controlConfigPanel->GetCVarCount(); + } + virtual int getCellTall(int row) + { + return 12; + } + virtual Panel* getCellRenderer(int column,int row,bool columnSelected,bool rowSelected,bool cellSelected) + { + char cvar[128],desc[128],bind[128],bindAlt[128]; + _controlConfigPanel->GetCVar(row,cvar,128,desc,128); + + if(cellSelected) + { + _label->setBgColor(Scheme::sc_primary1); + _label->setFgColor(Scheme::sc_primary3); + } + else + if(rowSelected) + { + _label->setBgColor(Scheme::sc_primary2); + _label->setFgColor(Scheme::sc_primary1); + } + else + { + _label->setBgColor(Scheme::sc_primary3); + _label->setFgColor(Scheme::sc_primary1); + } + + switch(column) + { + case 0: + { + _label->setText(desc); + _label->setContentAlignment(Label::a_west); + break; + } + case 1: + { + _controlConfigPanel->GetCVarBind(cvar,bind,128,bindAlt,128); + _label->setText(bind); + _label->setContentAlignment(Label::a_center); + break; + } + case 2: + { + _controlConfigPanel->GetCVarBind(cvar,bind,128,bindAlt,128); + _label->setText(bindAlt); + _label->setContentAlignment(Label::a_center); + break; + } + default: + { + _label->setText(""); + break; + } + } + + return _label; + } + virtual Panel* startCellEditing(int column,int row) + { + _textEntry->setText("Goat",strlen("Goat")); + _textEntry->requestFocus(); + return _textEntry; + } +}; +} + +ControlConfigPanel::ControlConfigPanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) +{ + setPaintBorderEnabled(false); + setPaintBackgroundEnabled(false); + setPaintEnabled(false); + + _actionLabel=new Label("Action"); + _actionLabel->setBgColor(Scheme::sc_primary3); + _actionLabel->setFgColor(Scheme::sc_primary3); + + _keyButtonLabel=new Label("Key / Button"); + _keyButtonLabel->setBgColor(Scheme::sc_primary3); + _keyButtonLabel->setFgColor(Scheme::sc_primary3); + + _alternateLabel=new Label("Alternate"); + _alternateLabel->setBgColor(Scheme::sc_primary3); + _alternateLabel->setFgColor(Scheme::sc_primary3); + + _headerPanel=new HeaderPanel(0,0,wide,20); + _headerPanel->setParent(this); + + _headerPanel->addSectionPanel(_actionLabel); + _headerPanel->addSectionPanel(_keyButtonLabel); + _headerPanel->addSectionPanel(_alternateLabel); + + _headerPanel->setSliderPos( 0, wide/2 ); + _headerPanel->setSliderPos( 1, (wide/2) + (wide/4) ); + _headerPanel->setSliderPos( 2, wide ); + + _scrollPanel=new ScrollPanel(0,20,wide,tall-20); + _scrollPanel->setParent(this); + _scrollPanel->setPaintBorderEnabled(false); + _scrollPanel->setPaintBackgroundEnabled(false); + _scrollPanel->setPaintEnabled(false); + _scrollPanel->getClient()->setPaintBorderEnabled(false); + _scrollPanel->getClient()->setPaintBackgroundEnabled(false); + _scrollPanel->getClient()->setPaintEnabled(false); + _scrollPanel->setScrollBarVisible(false,true); + + _tablePanel=new FooTablePanel(this,0,0,_scrollPanel->getClient()->getWide(),800, 3); + _tablePanel->setParent(_scrollPanel->getClient()); + _tablePanel->setHeaderPanel(_headerPanel); + _tablePanel->setBgColor(Color(200,0,0,255)); + _tablePanel->setFgColor(Color(Scheme::sc_primary2)); + _tablePanel->setGridVisible(true,true); + _tablePanel->setGridSize(1,1); +} + +void ControlConfigPanel::AddCVar(const char* cvar,const char* desc) +{ + _cvarDar.addElement(vgui_strdup(cvar)); + _descDar.addElement(vgui_strdup(desc)); +} + +int ControlConfigPanel::GetCVarCount() +{ + return _cvarDar.getCount(); +} + +void ControlConfigPanel::GetCVar(int index,char* cvar,int cvarLen,char* desc,int descLen) +{ + vgui_strcpy(cvar,cvarLen,_cvarDar[index]); + vgui_strcpy(desc,descLen,_descDar[index]); +} + +void ControlConfigPanel::AddCVarFromInputStream(InputStream* is) +{ + if(is==null) + { + return; + } + + DataInputStream dis(is); + + bool success; + + while(1) + { + char buf[256],cvar[128],desc[128]; + dis.readLine(buf,256,success); + if(!success) + { + break; + } + if(sscanf(buf,"\"%[^\"]\" \"%[^\"]\"",cvar,desc)==2) + { + AddCVar(cvar,desc); + } + } +} + +void ControlConfigPanel::GetCVarBind(const char* cvar,char* bind,int bindLen,char* bindAlt,int bindAltLen) +{ + sprintf(bind,"%s : Bind",cvar); + sprintf(bindAlt,"%s : BindAlt",cvar); +} + +void ControlConfigPanel::SetCVarBind(const char* cvar,const char* bind,const char* bindAlt) +{ +} + diff --git a/hlsdk/ricochet/cl_dll/vgui_ControlConfigPanel.h b/hlsdk/ricochet/cl_dll/vgui_ControlConfigPanel.h new file mode 100644 index 00000000..1c362c4a --- /dev/null +++ b/hlsdk/ricochet/cl_dll/vgui_ControlConfigPanel.h @@ -0,0 +1,41 @@ + +#ifndef CONTROLCONFIGPANEL_H +#define CONTROLCONFIGPANEL_H + +#include +#include + +namespace vgui +{ +class HeaderPanel; +class TablePanel; +class ScrollPanel; +class InputStream; +class Label; +} + +class ControlConfigPanel : public vgui::Panel +{ +private: + vgui::HeaderPanel* _headerPanel; + vgui::TablePanel* _tablePanel; + vgui::ScrollPanel* _scrollPanel; + vgui::Dar _cvarDar; + vgui::Dar _descDar; + vgui::Label* _actionLabel; + vgui::Label* _keyButtonLabel; + vgui::Label* _alternateLabel; +public: + ControlConfigPanel(int x,int y,int wide,int tall); +public: + void AddCVar(const char* cvar,const char* desc); + void AddCVarFromInputStream(vgui::InputStream* is); + int GetCVarCount(); + void GetCVar(int index,char* cvar,int cvarLen,char* desc,int descLen); + void GetCVarBind(const char* cvar,char* bind,int bindLen,char* bindAlt,int bindAltLen); + void SetCVarBind(const char* cvar,const char* bind,const char* bindAlt); +}; + + + +#endif \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/vgui_CustomObjects.cpp b/hlsdk/ricochet/cl_dll/vgui_CustomObjects.cpp new file mode 100644 index 00000000..869881a2 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/vgui_CustomObjects.cpp @@ -0,0 +1,501 @@ +//=========== (C) Copyright 1999 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: Contains implementation of various VGUI-derived objects +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "VGUI_Font.h" + +#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 "vgui_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ServerBrowser.h" +#include "vgui_LoadTGA.h" + +// Arrow filenames +char *sArrowFilenames[] = +{ + "arrowup", + "arrowdn", + "arrowlt", + "arrowrt", +}; + +// Get the name of TGA file, without a gamedir +char *GetTGANameForRes(const char *pszName) +{ + int i; + char sz[256]; + static char gd[256]; + if (ScreenWidth < 640) + i = 320; + else + i = 640; + sprintf(sz, pszName, i); + sprintf(gd, "gfx/vgui/%s.tga", sz); + return gd; +} + +//----------------------------------------------------------------------------- +// Purpose: Loads a .tga file and returns a pointer to the VGUI tga object +//----------------------------------------------------------------------------- +BitmapTGA *LoadTGAForRes( const char* pImageName ) +{ + BitmapTGA *pTGA; + + char sz[256]; + sprintf(sz, "%%d_%s", pImageName); + pTGA = vgui_LoadTGA(GetTGANameForRes(sz)); + + return pTGA; +} + +//=========================================================== +// All TFC Hud buttons are derived from this one. +CommandButton::CommandButton( const char* text,int x,int y,int wide,int tall, bool bNoHighlight) : Button("",x,y,wide,tall) +{ + m_iPlayerClass = 0; + m_bNoHighlight = bNoHighlight; + Init(); + setText( text ); +} + +CommandButton::CommandButton( int iPlayerClass, const char* text,int x,int y,int wide,int tall) : Button("",x,y,wide,tall) +{ + m_iPlayerClass = iPlayerClass; + m_bNoHighlight = false; + Init(); + setText( text ); +} + +void CommandButton::Init( void ) +{ + m_pSubMenu = NULL; + m_pSubLabel = NULL; + m_pParentMenu = NULL; + + // Set text color to orange + setFgColor(Scheme::sc_primary1); + + // left align + setContentAlignment( vgui::Label::a_west ); + + // Add the Highlight signal + if (!m_bNoHighlight) + addInputSignal( new CHandler_CommandButtonHighlight(this) ); + + // not bound to any button yet + m_cBoundKey = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Prepends the button text with the current bound key +// if no bound key, then a clear space ' ' instead +//----------------------------------------------------------------------------- +void CommandButton::RecalculateText( void ) +{ + char szBuf[128]; + + if ( m_cBoundKey != 0 ) + { + sprintf( szBuf, " %c %s", m_cBoundKey, m_sMainText ); + szBuf[MAX_BUTTON_SIZE-1] = 0; + } + else + { + // just draw a space if no key bound + sprintf( szBuf, " %s", m_sMainText ); + szBuf[MAX_BUTTON_SIZE-1] = 0; + } + + Button::setText( szBuf ); +} + +void CommandButton::setText( const char *text ) +{ + strncpy( m_sMainText, text, MAX_BUTTON_SIZE ); + m_sMainText[MAX_BUTTON_SIZE-1] = 0; + + RecalculateText(); +} + +void CommandButton::setBoundKey( char boundKey ) +{ + m_cBoundKey = boundKey; + RecalculateText(); +} + +char CommandButton::getBoundKey( void ) +{ + return m_cBoundKey; +} + +void CommandButton::AddSubMenu( CCommandMenu *pNewMenu ) +{ + m_pSubMenu = pNewMenu; + + // Prevent this button from being pushed + setMouseClickEnabled( MOUSE_LEFT, false ); +} + +void CommandButton::UpdateSubMenus( int iAdjustment ) +{ + if ( m_pSubMenu ) + m_pSubMenu->RecalculatePositions( iAdjustment ); +} + +void CommandButton::paint() +{ + // Make the sub label paint the same as the button + if ( m_pSubLabel ) + { + if ( isSelected() ) + m_pSubLabel->PushDown(); + else + m_pSubLabel->PushUp(); + } + + // draw armed button text in white + if ( isArmed() ) + { + setFgColor( Scheme::sc_secondary2 ); + } + else + { + setFgColor( Scheme::sc_primary1 ); + } + + Button::paint(); +} + +void CommandButton::paintBackground() +{ + if ( isArmed() ) + { + // Orange highlight background + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect(0,0,_size[0],_size[1]); + } + + // Orange Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect(0,0,_size[0],_size[1]); +} + +//----------------------------------------------------------------------------- +// Purpose: Highlights the current button, and all it's parent menus +//----------------------------------------------------------------------------- +void CommandButton::cursorEntered( void ) +{ + // unarm all the other buttons in this menu + CCommandMenu *containingMenu = getParentMenu(); + if ( containingMenu ) + { + containingMenu->ClearButtonsOfArmedState(); + + // make all our higher buttons armed + CCommandMenu *pCParent = containingMenu->GetParentMenu(); + if ( pCParent ) + { + CommandButton *pParentButton = pCParent->FindButtonWithSubmenu( containingMenu ); + + pParentButton->cursorEntered(); + } + } + + // arm ourselves + setArmed( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CommandButton::cursorExited( void ) +{ + // only clear ourselves if we have do not have a containing menu + // only stay armed if we have a sub menu + // the buttons only unarm themselves when another button is armed instead + if ( !getParentMenu() || !GetSubMenu() ) + { + setArmed( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the command menu that the button is part of, if any +// Output : CCommandMenu * +//----------------------------------------------------------------------------- +CCommandMenu *CommandButton::getParentMenu( void ) +{ + return m_pParentMenu; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the menu that contains this button +// Input : *pParentMenu - +//----------------------------------------------------------------------------- +void CommandButton::setParentMenu( CCommandMenu *pParentMenu ) +{ + m_pParentMenu = pParentMenu; +} + + +//=========================================================== +int ClassButton::IsNotValid() +{ + // If this is the main ChangeClass button, remove it if the player's only able to be civilians + if ( m_iPlayerClass == -1 ) + { + if (gViewPort->GetValidClasses(g_iTeamNumber) == -1) + return true; + + return false; + } + + // Is it an illegal class? + if ((gViewPort->GetValidClasses(0) & sTFValidClassInts[ m_iPlayerClass ]) || (gViewPort->GetValidClasses(g_iTeamNumber) & sTFValidClassInts[ m_iPlayerClass ])) + return true; + + // Only check current class if they've got autokill on + bool bAutoKill = CVAR_GET_FLOAT( "hud_classautokill" ) != 0; + if ( bAutoKill ) + { + // Is it the player's current class? + if ( (gViewPort->IsRandomPC() && m_iPlayerClass == PC_RANDOM) || (!gViewPort->IsRandomPC() && (m_iPlayerClass == g_iPlayerClass)) ) + return true; + } + + return false; +} + +//=========================================================== +// Button with Class image beneath it +CImageLabel::CImageLabel( const char* pImageName,int x,int y ) : Label( "", x,y ) +{ + setContentFitted(true); + m_pTGA = LoadTGAForRes(pImageName); + setImage( m_pTGA ); +} + +CImageLabel::CImageLabel( const char* pImageName,int x,int y,int wide,int tall ) : Label( "", x,y,wide,tall ) +{ + setContentFitted(true); + m_pTGA = LoadTGAForRes(pImageName); + setImage( m_pTGA ); +} + +//=========================================================== +// Image size +int CImageLabel::getImageWide( void ) +{ + if( m_pTGA ) + { + int iXSize, iYSize; + m_pTGA->getSize( iXSize, iYSize ); + return iXSize; + } + else + { + return 1; + } +} + +int CImageLabel::getImageTall( void ) +{ + if( m_pTGA ) + { + int iXSize, iYSize; + m_pTGA->getSize( iXSize, iYSize ); + return iYSize; + } + else + { + return 1; + } +} + +void CImageLabel::LoadImage(const char * pImageName) +{ + if ( m_pTGA ) + delete m_pTGA; + + // Load the Image + m_pTGA = LoadTGAForRes(pImageName); + + if ( m_pTGA == NULL ) + { + // we didn't find a matching image file for this resolution + // try to load file resolution independent + + char sz[256]; + sprintf(sz, "%s/%s",gEngfuncs.pfnGetGameDirectory(), pImageName ); + FileInputStream* fis = new FileInputStream( sz, false ); + m_pTGA = new BitmapTGA(fis,true); + fis->close(); + } + + if ( m_pTGA == NULL ) + return; // unable to load image + + int w,t; + + m_pTGA->getSize( w, t ); + + setSize( XRES (w),YRES (t) ); + setImage( m_pTGA ); +} + +//=========================================================== +// Various overloaded paint functions for Custom VGUI objects +void CCommandMenu::paintBackground() +{ + // Transparent black background + drawSetColor(Scheme::sc_primary3); + drawFilledRect(0,0,_size[0],_size[1]); +} + +//================================================================================= +// CUSTOM SCROLLPANEL +//================================================================================= +CTFScrollButton::CTFScrollButton(int iArrow, const char* text,int x,int y,int wide,int tall) : CommandButton(text,x,y,wide,tall) +{ + // Set text color to orange + setFgColor(Scheme::sc_primary1); + + // Load in the arrow + m_pTGA = LoadTGAForRes( sArrowFilenames[iArrow] ); + setImage( m_pTGA ); + + // Highlight signal + InputSignal *pISignal = new CHandler_CommandButtonHighlight(this); + addInputSignal(pISignal); +} + +void CTFScrollButton::paint( void ) +{ + if (!m_pTGA) + return; + // draw armed button text in white + if ( isArmed() ) + { + m_pTGA->setColor( Color(255,255,255, 0) ); + } + else + { + m_pTGA->setColor( Color(255,255,255, 128) ); + } + + m_pTGA->doPaint(this); +} + +void CTFScrollButton::paintBackground( void ) +{ +/* + if ( isArmed() ) + { + // Orange highlight background + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect(0,0,_size[0],_size[1]); + } + + // Orange Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect(0,0,_size[0]-1,_size[1]); +*/ +} + +void CTFSlider::paintBackground( void ) +{ + int wide,tall,nobx,noby; + getPaintSize(wide,tall); + getNobPos(nobx,noby); + + // Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect( 0,0,wide,tall ); + + if( isVertical() ) + { + // Nob Fill + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect( 0,nobx,wide,noby ); + + // Nob Outline + drawSetColor( Scheme::sc_primary1 ); + drawOutlinedRect( 0,nobx,wide,noby ); + } + else + { + // Nob Fill + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect( nobx,0,noby,tall ); + + // Nob Outline + drawSetColor( Scheme::sc_primary1 ); + drawOutlinedRect( nobx,0,noby,tall ); + } +} + +CTFScrollPanel::CTFScrollPanel(int x,int y,int wide,int tall) : ScrollPanel(x,y,wide,tall) +{ + ScrollBar *pScrollBar = getVerticalScrollBar(); + pScrollBar->setButton( new CTFScrollButton( ARROW_UP, "", 0,0,16,16 ), 0 ); + pScrollBar->setButton( new CTFScrollButton( ARROW_DOWN, "", 0,0,16,16 ), 1 ); + pScrollBar->setSlider( new CTFSlider(0,wide-1,wide,(tall-(wide*2))+2,true) ); + pScrollBar->setPaintBorderEnabled(false); + pScrollBar->setPaintBackgroundEnabled(false); + pScrollBar->setPaintEnabled(false); + + pScrollBar = getHorizontalScrollBar(); + pScrollBar->setButton( new CTFScrollButton( ARROW_LEFT, "", 0,0,16,16 ), 0 ); + pScrollBar->setButton( new CTFScrollButton( ARROW_RIGHT, "", 0,0,16,16 ), 1 ); + pScrollBar->setSlider( new CTFSlider(tall,0,wide-(tall*2),tall,false) ); + pScrollBar->setPaintBorderEnabled(false); + pScrollBar->setPaintBackgroundEnabled(false); + pScrollBar->setPaintEnabled(false); +} + + +//================================================================================= +// CUSTOM HANDLERS +//================================================================================= +void CHandler_MenuButtonOver::cursorEntered(Panel *panel) +{ + if ( gViewPort && m_pMenuPanel ) + { + m_pMenuPanel->SetActiveInfo( m_iButton ); + } +} + +void CMenuHandler_StringCommandClassSelect::actionPerformed(Panel* panel) +{ + CMenuHandler_StringCommand::actionPerformed( panel ); + + bool bAutoKill = CVAR_GET_FLOAT( "hud_classautokill" ) != 0; + if ( bAutoKill && g_iPlayerClass != 0 ) + gEngfuncs.pfnClientCmd("kill"); +} + + diff --git a/hlsdk/ricochet/cl_dll/vgui_MOTDWindow.cpp b/hlsdk/ricochet/cl_dll/vgui_MOTDWindow.cpp new file mode 100644 index 00000000..ae2f026c --- /dev/null +++ b/hlsdk/ricochet/cl_dll/vgui_MOTDWindow.cpp @@ -0,0 +1,154 @@ +//=========== (C) Copyright 1999 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: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "VGUI_Font.h" +#include "VGUI_ScrollPanel.h" +#include "VGUI_TextImage.h" + +#include + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "const.h" + +#include "vgui_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ServerBrowser.h" + +#define MOTD_TITLE_X XRES(16) +#define MOTD_TITLE_Y YRES(16) + +#define MOTD_WINDOW_X XRES(112) +#define MOTD_WINDOW_Y YRES(80) +#define MOTD_WINDOW_SIZE_X XRES(424) +#define MOTD_WINDOW_SIZE_Y YRES(312) + +//----------------------------------------------------------------------------- +// Purpose: Displays the MOTD and basic server information +//----------------------------------------------------------------------------- +class CMessageWindowPanel : public CMenuPanel +{ +public: + CMessageWindowPanel( const char *szMOTD, const char *szTitle, int iShadeFullScreen, int iRemoveMe, int x, int y, int wide, int tall ); + +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 ) +{ + return new CMessageWindowPanel( szMOTD, szTitle, iShadeFullscreen, iRemoveMe, 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 ) +{ + // 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; + + // 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->setVisible( true ); + + int iXSize,iYSize,iXPos,iYPos; + m_pBackgroundPanel->getPos( iXPos,iYPos ); + m_pBackgroundPanel->getSize( iXSize,iYSize ); + + // Create the title + Label *pLabel = new Label( "", iXPos + MOTD_TITLE_X, iYPos + MOTD_TITLE_Y ); + pLabel->setParent( this ); + pLabel->setFont( pSchemes->getFont(hTitleScheme) ); + pLabel->setFont( Scheme::sf_primary1 ); + + pSchemes->getFgColor( hTitleScheme, r, g, b, a ); + pLabel->setFgColor( r, g, b, a ); + pLabel->setFgColor( Scheme::sc_primary1 ); + pSchemes->getBgColor( hTitleScheme, r, g, b, a ); + pLabel->setBgColor( r, g, b, a ); + pLabel->setContentAlignment( vgui::Label::a_west ); + pLabel->setText(szTitle); + + // Create the Scroll panel + ScrollPanel *pScrollPanel = new CTFScrollPanel( iXPos + XRES(16), iYPos + MOTD_TITLE_Y*2 + YRES(16), iXSize - XRES(32), iYSize - (YRES(48) + BUTTON_SIZE_Y*2) ); + pScrollPanel->setParent(this); + + //force the scrollbars on so clientClip will take them in account after the validate + pScrollPanel->setScrollBarAutoVisible(false, false); + pScrollPanel->setScrollBarVisible(true, true); + pScrollPanel->validate(); + + // Create the text panel + TextPanel *pText = new TextPanel( "", 0,0, 64,64); + pText->setParent( pScrollPanel->getClient() ); + + // get the font and colors from the scheme + pText->setFont( pSchemes->getFont(hMOTDText) ); + pSchemes->getFgColor( hMOTDText, r, g, b, a ); + pText->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hMOTDText, r, g, b, a ); + pText->setBgColor( r, g, b, a ); + pText->setText(szMOTD); + + // 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 wdith 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. + pText->getTextImage()->setSize(pScrollPanel->getClientClip()->getWide(), pScrollPanel->getClientClip()->getTall()); + pText->getTextImage()->getTextSizeWrapped( iScrollSizeX, iScrollSizeY ); + + // Now resize the textpanel to fit the scrolled size + pText->setSize( iScrollSizeX , iScrollSizeY ); + + //turn the scrollbars back into automode + pScrollPanel->setScrollBarAutoVisible(true, true); + pScrollPanel->setScrollBarVisible(false, false); + + 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); + +} + + + + + + diff --git a/hlsdk/ricochet/cl_dll/vgui_SchemeManager.cpp b/hlsdk/ricochet/cl_dll/vgui_SchemeManager.cpp new file mode 100644 index 00000000..7f6cecd3 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/vgui_SchemeManager.cpp @@ -0,0 +1,544 @@ +//=========== (C) Copyright 1999 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: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "hud.h" +#include "vgui_SchemeManager.h" +#include "cvardef.h" + +#include + + +cvar_t *g_CV_BitmapFonts; + + +void Scheme_Init() +{ + g_CV_BitmapFonts = gEngfuncs.pfnRegisterVariable("bitmapfonts", "1", 0); +} + + + +//----------------------------------------------------------------------------- +// Purpose: Scheme managers data container +//----------------------------------------------------------------------------- +class CSchemeManager::CScheme +{ +public: + enum { + SCHEME_NAME_LENGTH = 32, + FONT_NAME_LENGTH = 48, + FONT_FILENAME_LENGTH = 64, + }; + + // name + char schemeName[SCHEME_NAME_LENGTH]; + + // font + char fontName[FONT_NAME_LENGTH]; + + int fontSize; + int fontWeight; + + vgui::Font *font; + int ownFontPointer; // true if the font is ours to delete + + // scheme + byte fgColor[4]; + byte bgColor[4]; + byte armedFgColor[4]; + byte armedBgColor[4]; + byte mousedownFgColor[4]; + byte mousedownBgColor[4]; + byte borderColor[4]; + + // construction/destruction + CScheme(); + ~CScheme(); +}; + +CSchemeManager::CScheme::CScheme() +{ + schemeName[0] = 0; + fontName[0] = 0; + fontSize = 0; + fontWeight = 0; + font = NULL; + ownFontPointer = false; +} + +CSchemeManager::CScheme::~CScheme() +{ + // only delete our font pointer if we own it + if ( ownFontPointer ) + { + delete font; + } +} + +//----------------------------------------------------------------------------- +// Purpose: resolution information +// !! needs to be shared out +//----------------------------------------------------------------------------- +static int g_ResArray[] = +{ + 320, + 400, + 512, + 640, + 800, + 1024, + 1152, + 1280, + 1600 +}; +static int g_NumReses = sizeof(g_ResArray) / sizeof(int); + +static byte *LoadFileByResolution( const char *filePrefix, int xRes, const char *filePostfix ) +{ + // find our resolution in the res array + int resNum = g_NumReses - 1; + while ( g_ResArray[resNum] > xRes ) + { + resNum--; + + if ( resNum < 0 ) + return NULL; + } + + // try open the file + byte *pFile = NULL; + while ( 1 ) + { + + // try load + char fname[256]; + sprintf( fname, "%s%d%s", filePrefix, g_ResArray[resNum], filePostfix ); + pFile = gEngfuncs.COM_LoadFile( fname, 5, NULL ); + + if ( pFile ) + break; + + if ( resNum == 0 ) + return NULL; + + resNum--; + }; + + return pFile; +} + +static void ParseRGBAFromString( byte colorArray[4], const char *colorVector ) +{ + int r, g, b, a; + sscanf( colorVector, "%d %d %d %d", &r, &g, &b, &a ); + colorArray[0] = r; + colorArray[1] = g; + colorArray[2] = b; + colorArray[3] = a; +} + +//----------------------------------------------------------------------------- +// Purpose: initializes the scheme manager +// loading the scheme files for the current resolution +// Input : xRes - +// yRes - dimensions of output window +//----------------------------------------------------------------------------- +CSchemeManager::CSchemeManager( int xRes, int yRes ) +{ + // basic setup + m_pSchemeList = NULL; + m_iNumSchemes = 0; + + // find the closest matching scheme file to our resolution + char token[1024]; + char *pFile = (char*)LoadFileByResolution( "", xRes, "_textscheme.txt" ); + m_xRes = xRes; + + char *pFileStart = pFile; + + byte *pFontData; + int fontFileLength; + char fontFilename[512]; + + // + // Read the scheme descriptions from the text file, into a temporary array + // format is simply: + // = + // + // a of "SchemeName" signals a new scheme is being described + // + + const static int numTmpSchemes = 64; + static CScheme tmpSchemes[numTmpSchemes]; + memset( tmpSchemes, 0, sizeof(tmpSchemes) ); + int currentScheme = -1; + CScheme *pScheme = NULL; + + if ( !pFile ) + { + gEngfuncs.Con_DPrintf( "Unable to find *_textscheme.txt\n"); + goto buildDefaultFont; + } + + // record what has been entered so we can create defaults from the different values + bool hasFgColor, hasBgColor, hasArmedFgColor, hasArmedBgColor, hasMouseDownFgColor, hasMouseDownBgColor; + + pFile = gEngfuncs.COM_ParseFile( pFile, token ); + while ( strlen(token) > 0 && (currentScheme < numTmpSchemes) ) + { + // get the paramName name + static const int tokenSize = 64; + char paramName[tokenSize], paramValue[tokenSize]; + + strncpy( paramName, token, tokenSize ); + paramName[tokenSize-1] = 0; // ensure null termination + + // get the '=' character + pFile = gEngfuncs.COM_ParseFile( pFile, token ); + if ( stricmp( token, "=" ) ) + { + if ( currentScheme < 0 ) + { + gEngfuncs.Con_Printf( "error parsing font scheme text file at file start - expected '=', found '%s''\n", token ); + } + else + { + gEngfuncs.Con_Printf( "error parsing font scheme text file at scheme '%s' - expected '=', found '%s''\n", tmpSchemes[currentScheme].schemeName, token ); + } + break; + } + + // get paramValue + pFile = gEngfuncs.COM_ParseFile( pFile, token ); + strncpy( paramValue, token, tokenSize ); + paramValue[tokenSize-1] = 0; // ensure null termination + + // is this a new scheme? + if ( !stricmp(paramName, "SchemeName") ) + { + // setup the defaults for the current scheme + if ( pScheme ) + { + // foreground color defaults (normal -> armed -> mouse down) + if ( !hasFgColor ) + { + pScheme->fgColor[0] = pScheme->fgColor[1] = pScheme->fgColor[2] = pScheme->fgColor[3] = 255; + } + if ( !hasArmedFgColor ) + { + memcpy( pScheme->armedFgColor, pScheme->fgColor, sizeof(pScheme->armedFgColor) ); + } + if ( !hasMouseDownFgColor ) + { + memcpy( pScheme->mousedownFgColor, pScheme->armedFgColor, sizeof(pScheme->mousedownFgColor) ); + } + + // background color (normal -> armed -> mouse down) + if ( !hasBgColor ) + { + pScheme->bgColor[0] = pScheme->bgColor[1] = pScheme->bgColor[2] = pScheme->bgColor[3] = 0; + } + if ( !hasArmedBgColor ) + { + memcpy( pScheme->armedBgColor, pScheme->bgColor, sizeof(pScheme->armedBgColor) ); + } + if ( !hasMouseDownBgColor ) + { + memcpy( pScheme->mousedownBgColor, pScheme->armedBgColor, sizeof(pScheme->mousedownBgColor) ); + } + + // font size + if ( !pScheme->fontSize ) + { + pScheme->fontSize = 17; + } + if ( !pScheme->fontName[0] ) + { + strcpy( pScheme->fontName, "Arial" ); + } + } + + // create the new scheme + currentScheme++; + pScheme = &tmpSchemes[currentScheme]; + hasFgColor = hasBgColor = hasArmedFgColor = hasArmedBgColor = hasMouseDownFgColor = hasMouseDownBgColor = false; + + strncpy( pScheme->schemeName, paramValue, CScheme::SCHEME_NAME_LENGTH ); + pScheme->schemeName[CScheme::SCHEME_NAME_LENGTH-1] = '\0'; // ensure null termination of string + } + + if ( !pScheme ) + { + gEngfuncs.Con_Printf( "font scheme text file MUST start with a 'SchemeName'\n"); + break; + } + + // pull the data out into the scheme + if ( !stricmp(paramName, "FontName") ) + { + strncpy( pScheme->fontName, paramValue, CScheme::FONT_NAME_LENGTH ); + pScheme->fontName[CScheme::FONT_NAME_LENGTH-1] = 0; + } + else if ( !stricmp(paramName, "FontSize") ) + { + pScheme->fontSize = atoi( paramValue ); + } + else if ( !stricmp(paramName, "FontWeight") ) + { + pScheme->fontWeight = atoi( paramValue ); + } + else if ( !stricmp(paramName, "FgColor") ) + { + ParseRGBAFromString( pScheme->fgColor, paramValue ); + hasFgColor = true; + } + else if ( !stricmp(paramName, "BgColor") ) + { + ParseRGBAFromString( pScheme->bgColor, paramValue ); + hasBgColor = true; + } + else if ( !stricmp(paramName, "FgColorArmed") ) + { + ParseRGBAFromString( pScheme->armedFgColor, paramValue ); + hasArmedFgColor = true; + } + else if ( !stricmp(paramName, "BgColorArmed") ) + { + ParseRGBAFromString( pScheme->armedBgColor, paramValue ); + hasArmedBgColor = true; + } + else if ( !stricmp(paramName, "FgColorMousedown") ) + { + ParseRGBAFromString( pScheme->mousedownFgColor, paramValue ); + hasMouseDownFgColor = true; + } + else if ( !stricmp(paramName, "BgColorMousedown") ) + { + ParseRGBAFromString( pScheme->mousedownBgColor, paramValue ); + hasMouseDownBgColor = true; + } + else if ( !stricmp(paramName, "BorderColor") ) + { + ParseRGBAFromString( pScheme->borderColor, paramValue ); + hasMouseDownBgColor = true; + } + + // get the new token last, so we now if the loop needs to be continued or not + pFile = gEngfuncs.COM_ParseFile( pFile, token ); + } + + // free the file + gEngfuncs.COM_FreeFile( pFileStart ); + + +buildDefaultFont: + + // make sure we have at least 1 valid font + if ( currentScheme < 0 ) + { + currentScheme = 0; + strcpy( tmpSchemes[0].schemeName, "Default Scheme" ); + strcpy( tmpSchemes[0].fontName, "Arial" ); + tmpSchemes[0].fontSize = 0; + tmpSchemes[0].fgColor[0] = tmpSchemes[0].fgColor[1] = tmpSchemes[0].fgColor[2] = tmpSchemes[0].fgColor[3] = 255; + tmpSchemes[0].armedFgColor[0] = tmpSchemes[0].armedFgColor[1] = tmpSchemes[0].armedFgColor[2] = tmpSchemes[0].armedFgColor[3] = 255; + tmpSchemes[0].mousedownFgColor[0] = tmpSchemes[0].mousedownFgColor[1] = tmpSchemes[0].mousedownFgColor[2] = tmpSchemes[0].mousedownFgColor[3] = 255; + } + + // we have the full list of schemes in the tmpSchemes array + // now allocate the correct sized list + m_iNumSchemes = currentScheme + 1; // 0-based index + m_pSchemeList = new CScheme[ m_iNumSchemes ]; + + // copy in the data + memcpy( m_pSchemeList, tmpSchemes, sizeof(CScheme) * m_iNumSchemes ); + + // create the fonts + for ( int i = 0; i < m_iNumSchemes; i++ ) + { + m_pSchemeList[i].font = NULL; + + // see if the current font values exist in a previously loaded font + for ( int j = 0; j < i; j++ ) + { + // check if the font name, size, and weight are the same + if ( !stricmp(m_pSchemeList[i].fontName, m_pSchemeList[j].fontName) + && m_pSchemeList[i].fontSize == m_pSchemeList[j].fontSize + && m_pSchemeList[i].fontWeight == m_pSchemeList[j].fontWeight ) + { + // copy the pointer, but mark i as not owning it + m_pSchemeList[i].font = m_pSchemeList[j].font; + m_pSchemeList[i].ownFontPointer = false; + } + } + + // if we haven't found the font already, load it ourselves + if ( !m_pSchemeList[i].font ) + { + fontFileLength = -1; + pFontData = NULL; + + if(g_CV_BitmapFonts && g_CV_BitmapFonts->value) + { + sprintf(fontFilename, "gfx\\vgui\\fonts\\%d_%s.tga", m_xRes, m_pSchemeList[i].schemeName); + pFontData = gEngfuncs.COM_LoadFile( fontFilename, 5, &fontFileLength ); + if(!pFontData) + gEngfuncs.Con_Printf("Missing bitmap font: %s\n", fontFilename); + } + + m_pSchemeList[i].font = new vgui::Font( + m_pSchemeList[i].fontName, + pFontData, + fontFileLength, + m_pSchemeList[i].fontSize, + 0, + 0, + m_pSchemeList[i].fontWeight, + false, + false, + false, + false); + + m_pSchemeList[i].ownFontPointer = true; + } + + // fix up alpha values; VGUI uses 1-A (A=0 being solid, A=255 transparent) + m_pSchemeList[i].fgColor[3] = 255 - m_pSchemeList[i].fgColor[3]; + m_pSchemeList[i].bgColor[3] = 255 - m_pSchemeList[i].bgColor[3]; + m_pSchemeList[i].armedFgColor[3] = 255 - m_pSchemeList[i].armedFgColor[3]; + m_pSchemeList[i].armedBgColor[3] = 255 - m_pSchemeList[i].armedBgColor[3]; + m_pSchemeList[i].mousedownFgColor[3] = 255 - m_pSchemeList[i].mousedownFgColor[3]; + m_pSchemeList[i].mousedownBgColor[3] = 255 - m_pSchemeList[i].mousedownBgColor[3]; + } +} + +//----------------------------------------------------------------------------- +// Purpose: frees all the memory used by the scheme manager +//----------------------------------------------------------------------------- +CSchemeManager::~CSchemeManager() +{ + delete [] m_pSchemeList; + m_iNumSchemes = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Finds a scheme in the list, by name +// Input : char *schemeName - string name of the scheme +// Output : SchemeHandle_t handle to the scheme +//----------------------------------------------------------------------------- +SchemeHandle_t CSchemeManager::getSchemeHandle( const char *schemeName ) +{ + // iterate through the list + for ( int i = 0; i < m_iNumSchemes; i++ ) + { + if ( !stricmp(schemeName, m_pSchemeList[i].schemeName) ) + return i; + } + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: always returns a valid scheme handle +// Input : schemeHandle - +// Output : CScheme +//----------------------------------------------------------------------------- +CSchemeManager::CScheme *CSchemeManager::getSafeScheme( SchemeHandle_t schemeHandle ) +{ + if ( schemeHandle < m_iNumSchemes ) + return m_pSchemeList + schemeHandle; + + return m_pSchemeList; +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns the schemes pointer to a font +// Input : schemeHandle - +// Output : vgui::Font +//----------------------------------------------------------------------------- +vgui::Font *CSchemeManager::getFont( SchemeHandle_t schemeHandle ) +{ + return getSafeScheme( schemeHandle )->font; +} + +void CSchemeManager::getFgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->fgColor[0]; + g = pScheme->fgColor[1]; + b = pScheme->fgColor[2]; + a = pScheme->fgColor[3]; +} + +void CSchemeManager::getBgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->bgColor[0]; + g = pScheme->bgColor[1]; + b = pScheme->bgColor[2]; + a = pScheme->bgColor[3]; +} + +void CSchemeManager::getFgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->armedFgColor[0]; + g = pScheme->armedFgColor[1]; + b = pScheme->armedFgColor[2]; + a = pScheme->armedFgColor[3]; +} + +void CSchemeManager::getBgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->armedBgColor[0]; + g = pScheme->armedBgColor[1]; + b = pScheme->armedBgColor[2]; + a = pScheme->armedBgColor[3]; +} + +void CSchemeManager::getFgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->mousedownFgColor[0]; + g = pScheme->mousedownFgColor[1]; + b = pScheme->mousedownFgColor[2]; + a = pScheme->mousedownFgColor[3]; +} + +void CSchemeManager::getBgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->mousedownBgColor[0]; + g = pScheme->mousedownBgColor[1]; + b = pScheme->mousedownBgColor[2]; + a = pScheme->mousedownBgColor[3]; +} + +void CSchemeManager::getBorderColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->borderColor[0]; + g = pScheme->borderColor[1]; + b = pScheme->borderColor[2]; + a = pScheme->borderColor[3]; +} + + + diff --git a/hlsdk/ricochet/cl_dll/vgui_SchemeManager.h b/hlsdk/ricochet/cl_dll/vgui_SchemeManager.h new file mode 100644 index 00000000..b1da6398 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/vgui_SchemeManager.h @@ -0,0 +1,47 @@ +#include + + +// handle to an individual scheme +typedef int SchemeHandle_t; + + +// Register console variables, etc.. +void Scheme_Init(); + + +//----------------------------------------------------------------------------- +// Purpose: Handles the loading of text scheme description from disk +// supports different font/color/size schemes at different resolutions +//----------------------------------------------------------------------------- +class CSchemeManager +{ +public: + // initialization + CSchemeManager( int xRes, int yRes ); + virtual ~CSchemeManager(); + + // scheme handling + SchemeHandle_t getSchemeHandle( const char *schemeName ); + + // getting info from schemes + vgui::Font *getFont( SchemeHandle_t schemeHandle ); + void getFgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getFgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getFgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBorderColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + +private: + class CScheme; + CScheme *m_pSchemeList; + int m_iNumSchemes; + + // Resolution we were initted at. + int m_xRes; + + CScheme *getSafeScheme( SchemeHandle_t schemeHandle ); +}; + + diff --git a/hlsdk/ricochet/cl_dll/vgui_ScorePanel.cpp b/hlsdk/ricochet/cl_dll/vgui_ScorePanel.cpp new file mode 100644 index 00000000..2547de87 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/vgui_ScorePanel.cpp @@ -0,0 +1,1111 @@ +//=========== (C) Copyright 1999 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: VGUI scoreboard +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + + +#include + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ScorePanel.h" +#include "vgui_helpers.h" +#include "vgui_loadtga.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 +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 ){ return 1; } + +int iNumberOfTeamColors = 33; + +// Scoreboard dimensions +#define SBOARD_TITLE_SIZE_Y YRES(22) + +#define X_BORDER XRES(4) + +// Column sizes +class SBColumnInfo +{ +public: + char *m_pTitle; // If null, ignore, if starts with #, it's localized, otherwise use the string directly. + int m_Width; // Based on 640 width. Scaled to fit other resolutions. + Label::Alignment m_Alignment; +}; + +// grid size is marked out for 640x480 screen + +SBColumnInfo g_ColumnInfo[NUM_COLUMNS] = +{ + {NULL, 24, Label::a_east}, + {NULL, 140, Label::a_east}, // name + {"#QUEUE", 56, Label::a_east}, // class + {"#WINS", 40, Label::a_east}, + {"#POINTS", 46, Label::a_east}, + {"#LATENCY", 46, Label::a_east}, + {"#VOICE", 40, Label::a_east}, + {NULL, 2, Label::a_east}, // blank column to take up the slack +}; + +extern float g_iaDiscColors[33][3]; + +#define TEAM_NO 0 +#define TEAM_YES 1 +#define TEAM_UNASSIGNED 2 +#define TEAM_SPECTATORS 3 + + +//----------------------------------------------------------------------------- +// ScorePanel::HitTestPanel. +//----------------------------------------------------------------------------- + +void ScorePanel::HitTestPanel::internalMousePressed(MouseCode code) +{ + for(int i=0;i<_inputSignalDar.getCount();i++) + { + _inputSignalDar[i]->mousePressed(code,this); + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: Create the ScoreBoard panel +//----------------------------------------------------------------------------- +ScorePanel::ScorePanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) +{ + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle("Scoreboard Title Text"); + SchemeHandle_t hSmallScheme = pSchemes->getSchemeHandle("Scoreboard Small Text"); + Font *tfont = pSchemes->getFont(hTitleScheme); + Font *smallfont = pSchemes->getFont(hSmallScheme); + + setBgColor(0, 0, 0, 96); + m_pCurrentHighlightLabel = NULL; + m_iHighlightRow = -1; + + // Initialize the top title. + m_TitleLabel.setFont(tfont); + m_TitleLabel.setText(""); + m_TitleLabel.setBgColor( 0, 0, 0, 255 ); + 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) + { + // only expand column size for res greater than 640 + xpos = XRES(xpos); + } + m_TitleLabel.setBounds(xpos, 4, wide, SBOARD_TITLE_SIZE_Y); + m_TitleLabel.setContentFitted(false); + m_TitleLabel.setParent(this); + + // Setup the header (labels like "name", "class", etc..). + m_HeaderGrid.SetDimensions(NUM_COLUMNS, 1); + m_HeaderGrid.SetSpacing(0, 0); + + for(int i=0; i < NUM_COLUMNS; i++) + { + if (g_ColumnInfo[i].m_pTitle && g_ColumnInfo[i].m_pTitle[0] == '#') + m_HeaderLabels[i].setText(CHudTextMessage::BufferedLocaliseTextString(g_ColumnInfo[i].m_pTitle)); + else if(g_ColumnInfo[i].m_pTitle) + m_HeaderLabels[i].setText(g_ColumnInfo[i].m_pTitle); + + int xwide = g_ColumnInfo[i].m_Width; + if (ScreenWidth >= 640) + { + xwide = XRES(xwide); + } + else if (ScreenWidth == 400) + { + // hack to make 400x300 resolution scoreboard fit + if (i == 1) + { + // reduces size of player name cell + xwide -= 28; + } + else if (i == 0) + { + xwide -= 8; + } + } + + m_HeaderGrid.SetColumnWidth(i, xwide); + m_HeaderGrid.SetEntry(i, 0, &m_HeaderLabels[i]); + + m_HeaderLabels[i].setBgColor(0,0,0,255); + m_HeaderLabels[i].setFgColor(Scheme::sc_primary1); + m_HeaderLabels[i].setFont(smallfont); + m_HeaderLabels[i].setContentAlignment(g_ColumnInfo[i].m_Alignment); + + int yres = 12; + if (ScreenHeight >= 480) + { + yres = YRES(yres); + } + m_HeaderLabels[i].setSize(50, yres); + } + + // Set the width of the last column to be the remaining space. + int ex, ey, ew, eh; + m_HeaderGrid.GetEntryBox(NUM_COLUMNS - 2, 0, ex, ey, ew, eh); + m_HeaderGrid.SetColumnWidth(NUM_COLUMNS - 1, (wide - X_BORDER) - (ex + ew)); + + m_HeaderGrid.AutoSetRowHeights(); + m_HeaderGrid.setBounds(X_BORDER, SBOARD_TITLE_SIZE_Y, wide - X_BORDER*2, m_HeaderGrid.GetRowHeight(0)); + m_HeaderGrid.setParent(this); + m_HeaderGrid.setBgColor(0,0,0,255); + + + // Now setup the listbox with the actual player data in it. + int headerX, headerY, headerWidth, headerHeight; + m_HeaderGrid.getBounds(headerX, headerY, headerWidth, headerHeight); + m_PlayerList.setBounds(headerX, headerY+headerHeight, headerWidth, tall - headerY - headerHeight - 6); + m_PlayerList.setBgColor(0,0,0,255); + m_PlayerList.setParent(this); + + for(int row=0; row < NUM_ROWS; row++) + { + CGrid *pGridRow = &m_PlayerGrids[row]; + + pGridRow->SetDimensions(NUM_COLUMNS, 1); + + for(int col=0; col < NUM_COLUMNS; col++) + { + m_PlayerEntries[col][row].setContentFitted(false); + m_PlayerEntries[col][row].setRow(row); + m_PlayerEntries[col][row].addInputSignal(this); + pGridRow->SetEntry(col, 0, &m_PlayerEntries[col][row]); + } + + pGridRow->setBgColor(0,0,0,255); + pGridRow->SetSpacing(0, 0); + pGridRow->CopyColumnWidths(&m_HeaderGrid); + pGridRow->AutoSetRowHeights(); + pGridRow->setSize(PanelWidth(pGridRow), pGridRow->CalcDrawHeight()); + pGridRow->RepositionContents(); + + m_PlayerList.AddItem(pGridRow); + } + + + // Add the hit test panel. It is invisible and traps mouse clicks so we can go into squelch mode. + m_HitTestPanel.setBgColor(0,0,0,255); + m_HitTestPanel.setParent(this); + m_HitTestPanel.setBounds(0, 0, wide, tall); + m_HitTestPanel.addInputSignal(this); + +/* m_pCloseButton = new CommandButton( "x", wide-XRES(12 + 4), YRES(2), XRES( 12 ) , YRES( 12 ) ); + m_pCloseButton->setParent( this ); + m_pCloseButton->addActionSignal( new CMenuHandler_StringCommandWatch( "-showscores", true ) ); + m_pCloseButton->setBgColor(0,0,0,255); + m_pCloseButton->setFgColor( 255, 255, 255, 0 ); + m_pCloseButton->setFont(tfont); + m_pCloseButton->setBoundKey( (char)255 ); + m_pCloseButton->setContentAlignment(Label::a_center);*/ + + + Initialize(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Called each time a new level is started. +//----------------------------------------------------------------------------- +void ScorePanel::Initialize( void ) +{ + // Clear out scoreboard data + m_iLastKilledBy = 0; + m_fLastKillTime = 0; + m_iPlayerNum = 0; + m_iNumTeams = 0; + memset( g_PlayerExtraInfo, 0, sizeof g_PlayerExtraInfo ); + memset( g_TeamInfo, 0, sizeof g_TeamInfo ); +} + +bool HACK_GetPlayerUniqueID( int iPlayer, char playerID[16] ) +{ + return !!gEngfuncs.GetPlayerUniqueID( iPlayer, playerID ); // TODO remove after testing +} + +extern int g_iArenaMode; + +//----------------------------------------------------------------------------- +// Purpose: Recalculate the internal scoreboard data +//----------------------------------------------------------------------------- +void ScorePanel::Update() +{ + // Set the title + if (gViewPort->m_szServerName) + { + char sz[MAX_SERVERNAME_LENGTH + 16]; + sprintf(sz, "%s", gViewPort->m_szServerName ); + m_TitleLabel.setText(sz); + } + + m_iRows = 0; + gViewPort->GetAllPlayersInfo(); + + + //Hide or show the QUEUE and WINS labels. + if ( g_iArenaMode == TRUE ) + { + m_HeaderLabels[2].setVisible ( true ); + m_HeaderLabels[3].setVisible ( true ); + } + else + { + m_HeaderLabels[2].setVisible ( false ); + m_HeaderLabels[3].setVisible ( false ); + } + + // Clear out sorts + for (int i = 0; i < NUM_ROWS; i++) + { + m_iSortedRows[i] = 0; + m_iIsATeam[i] = TEAM_NO; + m_bHasBeenSorted[i] = false; + } + + SortTeams(); + + // If it's not teamplay, sort all the players. Otherwise, sort the teams. +// if ( !gHUD.m_Teamplay ) + SortPlayers( 0, NULL ); +// else + + + // set scrollbar range + m_PlayerList.SetScrollRange(m_iRows); + + FillGrid(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sort all the teams +//----------------------------------------------------------------------------- +void ScorePanel::SortTeams() +{ + // clear out team scores + for ( int i = 1; i <= m_iNumTeams; i++ ) + { + if ( !g_TeamInfo[i].scores_overriden ) + g_TeamInfo[i].frags = g_TeamInfo[i].deaths = 0; + g_TeamInfo[i].ping = g_TeamInfo[i].packetloss = 0; + } + + // recalc the team scores, then draw them + for ( int i = 1; i < MAX_PLAYERS; i++ ) + { + if ( g_PlayerInfoList[i].name == NULL ) + continue; // empty player slot, skip + + if ( g_PlayerExtraInfo[i].teamname[0] == 0 ) + continue; // skip over players who are not in a team + + // find what team this player is in + int j; + for ( j = 1; j <= m_iNumTeams; j++ ) + { + if ( !stricmp( g_PlayerExtraInfo[i].teamname, g_TeamInfo[j].name ) ) + break; + } + if ( j > m_iNumTeams ) // player is not in a team, skip to the next guy + continue; + + if ( !g_TeamInfo[j].scores_overriden ) + { + g_TeamInfo[j].frags += g_PlayerExtraInfo[i].frags; + g_TeamInfo[j].deaths += g_PlayerExtraInfo[i].deaths; + } + + g_TeamInfo[j].ping += g_PlayerInfoList[i].ping; + g_TeamInfo[j].packetloss += g_PlayerInfoList[i].packetloss; + + if ( g_PlayerInfoList[i].thisplayer ) + g_TeamInfo[j].ownteam = TRUE; + else + g_TeamInfo[j].ownteam = FALSE; + + // Set the team's number (used for team colors) + g_TeamInfo[j].teamnumber = g_PlayerExtraInfo[i].teamnumber; + } + + // find team ping/packetloss averages + for ( int i = 1; i <= m_iNumTeams; i++ ) + { + g_TeamInfo[i].already_drawn = FALSE; + + if ( g_TeamInfo[i].players > 0 ) + { + g_TeamInfo[i].ping /= g_TeamInfo[i].players; // use the average ping of all the players in the team as the teams ping + g_TeamInfo[i].packetloss /= g_TeamInfo[i].players; // use the average ping of all the players in the team as the teams ping + } + } + + // Draw the teams + while ( 1 ) + { + int highest_frags = -99999; int lowest_deaths = 99999; + int best_team = 0; + + for ( int i = 1; i <= m_iNumTeams; i++ ) + { + if ( g_TeamInfo[i].players < 1 ) + continue; + + if ( !g_TeamInfo[i].already_drawn && g_TeamInfo[i].frags >= highest_frags ) + { + if ( g_TeamInfo[i].frags > highest_frags || g_TeamInfo[i].deaths < lowest_deaths ) + { + best_team = i; + lowest_deaths = g_TeamInfo[i].deaths; + highest_frags = g_TeamInfo[i].frags; + } + } + } + + // draw the best team on the scoreboard + if ( !best_team ) + break; + + // Put this team in the sorted list + m_iSortedRows[ m_iRows ] = best_team; + m_iIsATeam[ m_iRows ] = TEAM_YES; + g_TeamInfo[best_team].already_drawn = TRUE; // set the already_drawn to be TRUE, so this team won't get sorted again + m_iRows++; + + // Now sort all the players on this team + SortPlayers( 0, g_TeamInfo[best_team].name ); + } + + // Add all the players who aren't in a team yet into spectators + SortPlayers( TEAM_SPECTATORS, NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sort a list of players +//----------------------------------------------------------------------------- +void ScorePanel::SortPlayers( int iTeam, char *team ) +{ + bool bCreatedTeam = false; + + // draw the players, in order, and restricted to team if set + while ( 1 ) + { + // Find the top ranking player + int highest_frags = -99999; int lowest_deaths = 99999; + int best_player; + best_player = 0; + + for ( int i = 1; i < MAX_PLAYERS; i++ ) + { + if ( m_bHasBeenSorted[i] == false && g_PlayerInfoList[i].name && g_PlayerExtraInfo[i].frags >= highest_frags ) + { + cl_entity_t *ent = gEngfuncs.GetEntityByIndex( i ); + + if ( ent && !(team && stricmp(g_PlayerExtraInfo[i].teamname, team)) ) + { + extra_player_info_t *pl_info = &g_PlayerExtraInfo[i]; + if ( pl_info->frags > highest_frags || pl_info->deaths < lowest_deaths ) + { + best_player = i; + lowest_deaths = pl_info->deaths; + highest_frags = pl_info->frags; + } + } + } + } + + if ( !best_player ) + break; + + // If we haven't created the Team yet, do it first + if (!bCreatedTeam && iTeam) + { + m_iIsATeam[ m_iRows ] = iTeam; + m_iRows++; + + bCreatedTeam = true; + } + + // Put this player in the sorted list + m_iSortedRows[ m_iRows ] = best_player; + m_bHasBeenSorted[ best_player ] = true; + m_iRows++; + } + + if (team) + { + m_iIsATeam[m_iRows++] = TEAM_SPECTATORS; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Recalculate the existing teams in the match +//----------------------------------------------------------------------------- +void ScorePanel::RebuildTeams() +{ + // clear out player counts from teams + for ( int i = 1; i <= m_iNumTeams; i++ ) + { + g_TeamInfo[i].players = 0; + } + + // rebuild the team list + gViewPort->GetAllPlayersInfo(); + m_iNumTeams = 0; + for ( int i = 1; i < MAX_PLAYERS; i++ ) + { + if ( g_PlayerInfoList[i].name == NULL ) + continue; + + if ( g_PlayerExtraInfo[i].teamname[0] == 0 ) + continue; // skip over players who are not in a team + + // is this player in an existing team? + int j; + for ( j = 1; j <= m_iNumTeams; j++ ) + { + if ( g_TeamInfo[j].name[0] == '\0' ) + break; + + if ( !stricmp( g_PlayerExtraInfo[i].teamname, g_TeamInfo[j].name ) ) + break; + } + + if ( j > m_iNumTeams ) + { // they aren't in a listed team, so make a new one + // search through for an empty team slot + for ( j = 1; j <= m_iNumTeams; j++ ) + { + if ( g_TeamInfo[j].name[0] == '\0' ) + break; + } + m_iNumTeams = max( j, m_iNumTeams ); + + strncpy( g_TeamInfo[j].name, g_PlayerExtraInfo[i].teamname, MAX_TEAM_NAME ); + g_TeamInfo[j].players = 0; + } + + g_TeamInfo[j].players++; + } + + // clear out any empty teams + for ( int i = 1; i <= m_iNumTeams; i++ ) + { + if ( g_TeamInfo[i].players < 1 ) + memset( &g_TeamInfo[i], 0, sizeof(team_info_t) ); + } + + // Update the scoreboard + Update(); +} + + +void ScorePanel::FillGrid() +{ + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + SchemeHandle_t hScheme = pSchemes->getSchemeHandle("Scoreboard Text"); + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle("Scoreboard Title Text"); + SchemeHandle_t hSmallScheme = pSchemes->getSchemeHandle("Scoreboard Small Text"); + + Font *sfont = pSchemes->getFont(hScheme); + Font *tfont = pSchemes->getFont(hTitleScheme); + Font *smallfont = pSchemes->getFont(hSmallScheme); + int i = 0; + + // update highlight position + int x, y; + getApp()->getCursorPos(x, y); + cursorMoved(x, y, this); + + // remove highlight row if we're not in squelch mode + if (!GetClientVoiceMgr()->IsInSquelchMode()) + { + m_iHighlightRow = -1; + } + + bool bNextRowIsGap = false; + + for(int row=0; row < NUM_ROWS; row++) + { + CGrid *pGridRow = &m_PlayerGrids[row]; + pGridRow->SetRowUnderline(0, false, 0, 0, 0, 0, 0); + + if(row >= m_iRows) + { + for(int col=0; col < NUM_COLUMNS; col++) + m_PlayerEntries[col][row].setVisible(false); + + continue; + } + + bool bRowIsGap = false; + if (bNextRowIsGap) + { + bNextRowIsGap = false; + bRowIsGap = true; + } + + for(int col=0; col < NUM_COLUMNS; col++) + { + CLabelHeader *pLabel = &m_PlayerEntries[col][row]; + + pLabel->setVisible(true); + pLabel->setText2(""); + pLabel->setImage(NULL); + pLabel->setFont(sfont); + pLabel->setTextOffset(0, 0); + + int rowheight = 13; + if (ScreenHeight > 480) + { + rowheight = YRES(rowheight); + } + else + { + // more tweaking, make sure icons fit at low res + rowheight = 15; + } + pLabel->setSize(pLabel->getWide(), rowheight); + pLabel->setBgColor(0, 0, 0, 255); + + char sz[128]; + hud_player_info_t *pl_info = NULL; + team_info_t *team_info = NULL; + + if (m_iIsATeam[row] == TEAM_SPECTATORS) + { + pLabel->setText(" "); + continue; + } + else if ( m_iIsATeam[row] == TEAM_YES ) + { + // Get the team's data + team_info = &g_TeamInfo[ m_iSortedRows[row] ]; + + // team color text for team names + pLabel->setFgColor( iTeamColors[team_info->teamnumber % 5][0], + iTeamColors[team_info->teamnumber % 5][1], + iTeamColors[team_info->teamnumber % 5][2], + 0 ); + + // different height for team header rows + rowheight = 20; + if (ScreenHeight >= 480) + { + rowheight = YRES(rowheight); + } + pLabel->setSize(pLabel->getWide(), rowheight); + pLabel->setFont(tfont); + + pGridRow->SetRowUnderline( 0, + true, + YRES(3), + iTeamColors[team_info->teamnumber % 5][0], + iTeamColors[team_info->teamnumber % 5][1], + iTeamColors[team_info->teamnumber % 5][2], + 0 ); + } + else if ( m_iIsATeam[row] == TEAM_SPECTATORS ) + { + // grey text for spectators + pLabel->setFgColor(100, 100, 100, 0); + + // different height for team header rows + rowheight = 20; + if (ScreenHeight >= 480) + { + rowheight = YRES(rowheight); + } + pLabel->setSize(pLabel->getWide(), rowheight); + pLabel->setFont(tfont); + + pGridRow->SetRowUnderline(0, true, YRES(3), 100, 100, 100, 0); + } + else + { + // team color text for player names + pLabel->setFgColor( g_iaDiscColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][0], + g_iaDiscColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][1], + g_iaDiscColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][2], + 0 ); + + // Get the player's data + pl_info = &g_PlayerInfoList[ m_iSortedRows[row] ]; + + // Set background color + if ( pl_info->thisplayer ) // if it is their name, draw it a different color + { + // Highlight this player + pLabel->setFgColor(Scheme::sc_white); + pLabel->setBgColor( g_iaDiscColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][0], + g_iaDiscColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][1], + g_iaDiscColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][2], + 196 ); + } + else if ( m_iSortedRows[row] == m_iLastKilledBy && m_fLastKillTime && m_fLastKillTime > gHUD.m_flTime ) + { + // Killer's name + pLabel->setBgColor( 255,0,0, 255 - ((float)15 * (float)(m_fLastKillTime - gHUD.m_flTime)) ); + } + } + + // Align + if (col == COLUMN_NAME ) + { + pLabel->setContentAlignment( vgui::Label::a_west ); + } + else if (col == COLUMN_TRACKER) + { + pLabel->setContentAlignment( vgui::Label::a_center ); + } + else + { + pLabel->setContentAlignment( vgui::Label::a_east ); + } + + // Fill out with the correct data + strcpy(sz, ""); + if ( m_iIsATeam[row] ) + { + char sz2[128]; + + switch (col) + { + case COLUMN_NAME: + if ( m_iIsATeam[row] == TEAM_UNASSIGNED ) + { + sprintf( sz2, CHudTextMessage::BufferedLocaliseTextString( "#Queue" ) ); + } + else if ( m_iIsATeam[row] == TEAM_SPECTATORS ) + { + sprintf( sz2, CHudTextMessage::BufferedLocaliseTextString( "#Spectators" ) ); + } + else + { + sprintf( sz2, CHudTextMessage::BufferedLocaliseTextString( team_info->name ) ); + } + + // Uppercase it + for ( i = 0; i < (int)strlen(sz2); i++) + { + if ( *(sz2 + i) ) + sz[i] = toupper( *(sz2 + i) ); + } + sz[i] = '\0'; + + // Append the number of players + if ( m_iIsATeam[row] == TEAM_YES ) + { + char *pszTemp = NULL; + + if ( team_info->players == 1 ) + { + pszTemp = "#Player"; + } + else + { + pszTemp = "#Player_plural"; + } + + sprintf(sz, "%s (%d %s)", sz, team_info->players, CHudTextMessage::BufferedLocaliseTextString( pszTemp ) ); + } + + break; + case COLUMN_VOICE: + break; + case COLUMN_CLASS: + break; + case COLUMN_KILLS: //Wins + if ( g_iArenaMode == FALSE ) + strcpy ( sz, " " ); + else + { + if ( m_iIsATeam[row] == TEAM_YES ) + sprintf(sz, "%d", team_info->deaths ); + } + break; + case COLUMN_DEATHS: //Points + if ( m_iIsATeam[row] == TEAM_YES ) + sprintf(sz, "%d", team_info->frags ); + break; + case COLUMN_LATENCY: + if ( m_iIsATeam[row] == TEAM_YES ) + sprintf(sz, "%d", team_info->ping ); + break; + default: + break; + } + } + else + { + bool bShowClass = false; + + switch (col) + { + case COLUMN_NAME: + + sprintf(sz, "%s ", pl_info->name); + break; + case COLUMN_VOICE: + sz[0] = 0; + // in HLTV mode allow spectator to turn on/off commentator voice + if (!pl_info->thisplayer || gEngfuncs.IsSpectateOnly() ) + { + GetClientVoiceMgr()->UpdateSpeakerImage(pLabel, m_iSortedRows[row]); + } + break; + case COLUMN_CLASS: + + if ( g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber == 0 && g_iArenaMode == TRUE ) + { + sprintf(sz, "%d", g_PlayerExtraInfo[ m_iSortedRows[row] ].playerclass ); + } + else + { + strcpy(sz, ""); + } + + break; + + case COLUMN_TRACKER: + break; + + case COLUMN_KILLS: //Wins + if ( g_iArenaMode == TRUE ) + sprintf(sz, "%d", g_PlayerExtraInfo[ m_iSortedRows[row] ].deaths ); + break; + case COLUMN_DEATHS: //Points + sprintf(sz, "%d", g_PlayerExtraInfo[ m_iSortedRows[row] ].frags ); + break; + case COLUMN_LATENCY: + sprintf(sz, "%d", g_PlayerInfoList[ m_iSortedRows[row] ].ping ); + break; + default: + break; + } + } + + pLabel->setText(sz); + } + } + + for(int row = 0; row < NUM_ROWS; row++) + { + CGrid *pGridRow = &m_PlayerGrids[row]; + + pGridRow->AutoSetRowHeights(); + pGridRow->setSize(PanelWidth(pGridRow), pGridRow->CalcDrawHeight()); + pGridRow->RepositionContents(); + } + + // hack, for the thing to resize + m_PlayerList.getSize(x, y); + m_PlayerList.setSize(x, y); +} + + +//----------------------------------------------------------------------------- +// Purpose: Setup highlights for player names in scoreboard +//----------------------------------------------------------------------------- +void ScorePanel::DeathMsg( int killer, int victim ) +{ + // if we were the one killed, or the world killed us, set the scoreboard to indicate suicide + if ( victim == m_iPlayerNum || killer == 0 ) + { + m_iLastKilledBy = killer ? killer : m_iPlayerNum; + m_fLastKillTime = gHUD.m_flTime + 10; // display who we were killed by for 10 seconds + + if ( killer == m_iPlayerNum ) + m_iLastKilledBy = m_iPlayerNum; + } +} + + +void ScorePanel::Open( void ) +{ + RebuildTeams(); + setVisible(true); + m_HitTestPanel.setVisible(true); +} + + +void ScorePanel::mousePressed(MouseCode code, Panel* panel) +{ + if(gHUD.m_iIntermission) + return; + + if (!GetClientVoiceMgr()->IsInSquelchMode()) + { + GetClientVoiceMgr()->StartSquelchMode(); + m_HitTestPanel.setVisible(false); + } + else if (m_iHighlightRow >= 0) + { + // mouse has been pressed, toggle mute state + int iPlayer = m_iSortedRows[m_iHighlightRow]; + if (iPlayer > 0) + { + // print text message + hud_player_info_t *pl_info = &g_PlayerInfoList[iPlayer]; + + if (pl_info && pl_info->name && pl_info->name[0]) + { + char string[256]; + if (GetClientVoiceMgr()->IsPlayerBlocked(iPlayer)) + { + char string1[1024]; + + // remove mute + GetClientVoiceMgr()->SetPlayerBlockedState(iPlayer, false); + + sprintf( string1, CHudTextMessage::BufferedLocaliseTextString( "#Unmuted" ), pl_info->name ); + sprintf( string, "%c** %s\n", HUD_PRINTTALK, string1 ); + + gHUD.m_TextMessage.MsgFunc_TextMsg(NULL, strlen(string)+1, string ); + } + else + { + char string1[1024]; + char string2[1024]; + + // mute the player + GetClientVoiceMgr()->SetPlayerBlockedState(iPlayer, true); + + sprintf( string1, CHudTextMessage::BufferedLocaliseTextString( "#Muted" ), pl_info->name ); + sprintf( string2, CHudTextMessage::BufferedLocaliseTextString( "#No_longer_hear_that_player" ) ); + sprintf( string, "%c** %s %s\n", HUD_PRINTTALK, string1, string2 ); + + gHUD.m_TextMessage.MsgFunc_TextMsg(NULL, strlen(string)+1, string ); + } + } + } + } +} + +void ScorePanel::cursorMoved(int x, int y, Panel *panel) +{ + if (GetClientVoiceMgr()->IsInSquelchMode()) + { + // look for which cell the mouse is currently over + for (int i = 0; i < NUM_ROWS; i++) + { + int row, col; + if (m_PlayerGrids[i].getCellAtPoint(x, y, row, col)) + { + MouseOverCell(i, col); + return; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handles mouse movement over a cell +// Input : row - +// col - +//----------------------------------------------------------------------------- +void ScorePanel::MouseOverCell(int row, int col) +{ + CLabelHeader *label = &m_PlayerEntries[col][row]; + + // clear the previously highlighted label + if (m_pCurrentHighlightLabel != label) + { + m_pCurrentHighlightLabel = NULL; + m_iHighlightRow = -1; + } + if (!label) + return; + + // don't act on teams + if (m_iIsATeam[row] != TEAM_NO) + return; + + // don't act on disconnected players or ourselves + hud_player_info_t *pl_info = &g_PlayerInfoList[ m_iSortedRows[row] ]; + if (!pl_info->name || !pl_info->name[0]) + return; + + if (pl_info->thisplayer && !gEngfuncs.IsSpectateOnly() ) + return; + + // setup the new highlight + m_pCurrentHighlightLabel = label; + m_iHighlightRow = row; +} + +//----------------------------------------------------------------------------- +// Purpose: Label paint functions - take into account current highligh status +//----------------------------------------------------------------------------- +void CLabelHeader::paintBackground() +{ + Color oldBg; + getBgColor(oldBg); + + if (gViewPort->GetScoreBoard()->m_iHighlightRow == _row) + { + setBgColor(134, 91, 19, 0); + } + + Panel::paintBackground(); + + setBgColor(oldBg); +} + + +//----------------------------------------------------------------------------- +// Purpose: Label paint functions - take into account current highligh status +//----------------------------------------------------------------------------- +void CLabelHeader::paint() +{ + Color oldFg; + getFgColor(oldFg); + + if (gViewPort->GetScoreBoard()->m_iHighlightRow == _row) + { + setFgColor(255, 255, 255, 0); + } + + // draw text + int x, y, iwide, itall; + getTextSize(iwide, itall); + calcAlignment(iwide, itall, x, y); + _dualImage->setPos(x, y); + + int x1, y1; + _dualImage->GetImage(1)->getPos(x1, y1); + _dualImage->GetImage(1)->setPos(_gap, y1); + + _dualImage->doPaint(this); + + // get size of the panel and the image + if (_image) + { + Color imgColor; + getFgColor( imgColor ); + if( _useFgColorAsImageColor ) + { + _image->setColor( imgColor ); + } + + _image->getSize(iwide, itall); + calcAlignment(iwide, itall, x, y); + _image->setPos(x, y); + _image->doPaint(this); + } + + setFgColor(oldFg[0], oldFg[1], oldFg[2], oldFg[3]); +} + + +void CLabelHeader::calcAlignment(int iwide, int itall, int &x, int &y) +{ + // calculate alignment ourselves, since vgui is so broken + int wide, tall; + getSize(wide, tall); + + x = 0, y = 0; + + // align left/right + switch (_contentAlignment) + { + // left + case Label::a_northwest: + case Label::a_west: + case Label::a_southwest: + { + x = 0; + break; + } + + // center + case Label::a_north: + case Label::a_center: + case Label::a_south: + { + x = (wide - iwide) / 2; + break; + } + + // right + case Label::a_northeast: + case Label::a_east: + case Label::a_southeast: + { + x = wide - iwide; + break; + } + } + + // top/down + switch (_contentAlignment) + { + // top + case Label::a_northwest: + case Label::a_north: + case Label::a_northeast: + { + y = 0; + break; + } + + // center + case Label::a_west: + case Label::a_center: + case Label::a_east: + { + y = (tall - itall) / 2; + break; + } + + // south + case Label::a_southwest: + case Label::a_south: + case Label::a_southeast: + { + y = tall - itall; + break; + } + } + +// don't clip to Y +// if (y < 0) +// { +// y = 0; +// } + if (x < 0) + { + x = 0; + } + + x += _offset[0]; + y += _offset[1]; +} diff --git a/hlsdk/ricochet/cl_dll/vgui_ScorePanel.h b/hlsdk/ricochet/cl_dll/vgui_ScorePanel.h new file mode 100644 index 00000000..a91e6fca --- /dev/null +++ b/hlsdk/ricochet/cl_dll/vgui_ScorePanel.h @@ -0,0 +1,304 @@ + +#ifndef SCOREPANEL_H +#define SCOREPANEL_H + +#include +#include +#include +#include +#include +#include +#include "vgui_listbox.h" + +#include + +#define MAX_SCORES 10 +#define MAX_SCOREBOARD_TEAMS 5 + +// 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 NUM_ROWS (MAX_PLAYERS + (MAX_SCOREBOARD_TEAMS * 2)) + +using namespace vgui; + +class CTextImage2 : public Image +{ +public: + CTextImage2() + { + _image[0] = new TextImage(""); + _image[1] = new TextImage(""); + } + + ~CTextImage2() + { + delete _image[0]; + delete _image[1]; + } + + TextImage *GetImage(int image) + { + return _image[image]; + } + + void getSize(int &wide, int &tall) + { + int w1, w2, t1, t2; + _image[0]->getTextSize(w1, t1); + _image[1]->getTextSize(w2, t2); + + wide = w1 + w2; + tall = max(t1, t2); + setSize(wide, tall); + } + + void doPaint(Panel *panel) + { + _image[0]->doPaint(panel); + _image[1]->doPaint(panel); + } + + void setPos(int x, int y) + { + _image[0]->setPos(x, y); + + int swide, stall; + _image[0]->getSize(swide, stall); + + int wide, tall; + _image[1]->getSize(wide, tall); + _image[1]->setPos(x + wide, y + (stall * 0.9) - tall); + } + + void setColor(Color color) + { + _image[0]->setColor(color); + } + + void setColor2(Color color) + { + _image[1]->setColor(color); + } + +private: + TextImage *_image[2]; + +}; + +//----------------------------------------------------------------------------- +// Purpose: Custom label for cells in the Scoreboard's Table Header +//----------------------------------------------------------------------------- +class CLabelHeader : public Label +{ +public: + CLabelHeader() : Label("") + { + _dualImage = new CTextImage2(); + _dualImage->setColor2(Color(255, 170, 0, 0)); + _row = -2; + _useFgColorAsImageColor = true; + _offset[0] = 0; + _offset[1] = 0; + } + + ~CLabelHeader() + { + delete _dualImage; + } + + void setRow(int row) + { + _row = row; + } + + void setFgColorAsImageColor(bool state) + { + _useFgColorAsImageColor = state; + } + + virtual void setText(int textBufferLen, const char* text) + { + _dualImage->GetImage(0)->setText(text); + + // calculate the text size + Font *font = _dualImage->GetImage(0)->getFont(); + _gap = 0; + for (const char *ch = text; *ch != 0; ch++) + { + int a, b, c; + font->getCharABCwide(*ch, a, b, c); + _gap += (a + b + c); + } + + _gap += XRES(5); + } + + virtual void setText(const char* text) + { + // strip any non-alnum characters from the end + char buf[512]; + strcpy(buf, text); + + int len = strlen(buf); + while (len && isspace(buf[--len])) + { + buf[len] = 0; + } + + CLabelHeader::setText(0, buf); + } + + void setText2(const char *text) + { + _dualImage->GetImage(1)->setText(text); + } + + void getTextSize(int &wide, int &tall) + { + _dualImage->getSize(wide, tall); + } + + void setFgColor(int r,int g,int b,int a) + { + Label::setFgColor(r,g,b,a); + Color color(r,g,b,a); + _dualImage->setColor(color); + _dualImage->setColor2(color); + repaint(); + } + + void setFgColor(Scheme::SchemeColor sc) + { + int r, g, b, a; + Label::setFgColor(sc); + Label::getFgColor( r, g, b, a ); + + // Call the r,g,b,a version so it sets the color in the dualImage.. + setFgColor( r, g, b, a ); + } + + void setFont(Font *font) + { + _dualImage->GetImage(0)->setFont(font); + } + + void setFont2(Font *font) + { + _dualImage->GetImage(1)->setFont(font); + } + + // this adjust the absolute position of the text after alignment is calculated + void setTextOffset(int x, int y) + { + _offset[0] = x; + _offset[1] = y; + } + + void paint(); + void paintBackground(); + void calcAlignment(int iwide, int itall, int &x, int &y); + +private: + CTextImage2 *_dualImage; + int _row; + int _gap; + int _offset[2]; + bool _useFgColorAsImageColor; +}; + +class ScoreTablePanel; + +#include "vgui_grid.h" +#include "vgui_defaultinputsignal.h" + +//----------------------------------------------------------------------------- +// Purpose: Scoreboard back panel +//----------------------------------------------------------------------------- +class ScorePanel : public Panel, public vgui::CDefaultInputSignal +{ +private: + // Default panel implementation doesn't forward mouse messages when there is no cursor and we need them. + class HitTestPanel : public Panel + { + public: + virtual void internalMousePressed(MouseCode code); + }; + + +private: + + Label m_TitleLabel; + + // Here is how these controls are arranged hierarchically. + // m_HeaderGrid + // m_HeaderLabels + + // m_PlayerGridScroll + // m_PlayerGrid + // m_PlayerEntries + + CGrid m_HeaderGrid; + CLabelHeader m_HeaderLabels[NUM_COLUMNS]; // Labels above the + CLabelHeader *m_pCurrentHighlightLabel; + int m_iHighlightRow; + + 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. + + ScorePanel::HitTestPanel m_HitTestPanel; +// CommandButton *m_pCloseButton; + CLabelHeader* GetPlayerEntry(int x, int y) {return &m_PlayerEntries[x][y];} + +public: + + int m_iNumTeams; + int m_iPlayerNum; + int m_iShowscoresHeld; + + int m_iRows; + int m_iSortedRows[NUM_ROWS]; + int m_iIsATeam[NUM_ROWS]; + bool m_bHasBeenSorted[MAX_PLAYERS]; + int m_iLastKilledBy; + int m_fLastKillTime; + + +public: + + ScorePanel(int x,int y,int wide,int tall); + + void Update( void ); + + void SortTeams( void ); + void SortPlayers( int iTeam, char *team ); + void RebuildTeams( void ); + + void FillGrid(); + + void DeathMsg( int killer, int victim ); + + void Initialize( void ); + + void Open( void ); + + void MouseOverCell(int row, int col); + +// InputSignal overrides. +public: + + virtual void mousePressed(MouseCode code, Panel* panel); + virtual void cursorMoved(int x, int y, Panel *panel); + + friend CLabelHeader; +}; + +#endif diff --git a/hlsdk/ricochet/cl_dll/vgui_ServerBrowser.cpp b/hlsdk/ricochet/cl_dll/vgui_ServerBrowser.cpp new file mode 100644 index 00000000..009b1370 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/vgui_ServerBrowser.cpp @@ -0,0 +1,616 @@ + +#include +#include +#include +#include +#include +#include + +#include "hud.h" +#include "cl_util.h" +#include "hud_servers.h" +#include "net_api.h" + +#include "vgui_TeamFortressViewport.h" +#include "vgui_ServerBrowser.h" + +using namespace vgui; + +namespace +{ + +#define MAX_SB_ROWS 24 + +#define NUM_COLUMNS 5 + +#define HEADER_SIZE_Y YRES(18) + +// Column sizes +#define CSIZE_ADDRESS XRES(200) +#define CSIZE_SERVER XRES(400) +#define CSIZE_MAP XRES(500) +#define CSIZE_CURRENT XRES(570) +#define CSIZE_PING XRES(640) + +#define CELL_HEIGHT YRES(15) + +class ServerBrowserTablePanel; + +class CBrowser_InputSignal : public InputSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; +public: + CBrowser_InputSignal( ServerBrowserTablePanel *pBrowser ) + { + m_pBrowser = pBrowser; + } + + 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); + + 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 ServerBrowserTablePanel : public TablePanel +{ +private: + Label *m_pLabel; + int m_nMouseOverRow; + +public: + + ServerBrowserTablePanel( int x,int y,int wide,int tall,int columnCount) : TablePanel( x,y,wide,tall,columnCount) + { + m_pLabel = new Label( "", 0, 0 /*,wide, tall*/ ); + + m_nMouseOverRow = 0; + } + +public: + void setMouseOverRow( int row ) + { + m_nMouseOverRow = row; + } + + void DoSort( char *sortkey ) + { + // Request server list and refresh servers... + SortServers( sortkey ); + } + + void DoRefresh( void ) + { + // Request server list and refresh servers... + ServersList(); + BroadcastServersList( 0 ); + } + + void DoBroadcastRefresh( void ) + { + // Request server list and refresh servers... + BroadcastServersList( 1 ); + } + + void DoStop( void ) + { + // Stop requesting + ServersCancel(); + } + + void DoCancel( void ) + { + ClientCmd( "togglebrowser\n" ); + } + + void DoConnect( void ) + { + const char *info; + const char *address; + char sz[ 256 ]; + + info = ServersGetInfo( m_nMouseOverRow ); + if ( !info ) + return; + + address = gEngfuncs.pNetAPI->ValueForKey( info, "address" ); + //gEngfuncs.Con_Printf( "Connecting to %s\n", address ); + + sprintf( sz, "connect %s\n", address ); + + ClientCmd( sz ); + + DoCancel(); + } + + void DoPing( void ) + { + ServerPing( 0 ); + ServerRules( 0 ); + ServerPlayers( 0 ); + } + + virtual int getRowCount() + { + int rowcount; + int height, width; + + getSize( width, height ); + + // Space for buttons + height -= YRES(20); + height = max( 0, height ); + + rowcount = height / CELL_HEIGHT; + + return rowcount; + } + + virtual int getCellTall(int row) + { + return CELL_HEIGHT - 2; + } + + virtual Panel* getCellRenderer(int column,int row,bool columnSelected,bool rowSelected,bool cellSelected) + { + const char *info; + const char *val, *val2; + char sz[ 32 ]; + + info = ServersGetInfo( row ); + + if ( row == m_nMouseOverRow ) + { + m_pLabel->setFgColor( 200, 240, 63, 100 ); + } + else + { + m_pLabel->setFgColor( 255, 255, 255, 0 ); + } + m_pLabel->setBgColor( 0, 0, 0, 200 ); + m_pLabel->setContentAlignment( vgui::Label::a_west ); + m_pLabel->setFont( Scheme::sf_primary2 ); + + if ( info ) + { + // Fill out with the correct data + switch ( column ) + { + case 0: + val = gEngfuncs.pNetAPI->ValueForKey( info, "address" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Name; + m_pLabel->setText( sz ); + } + break; + case 1: + val = gEngfuncs.pNetAPI->ValueForKey( info, "hostname" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Map; + m_pLabel->setText( sz ); + } + break; + case 2: + val = gEngfuncs.pNetAPI->ValueForKey( info, "map" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Name; + m_pLabel->setText( sz ); + } + break; + case 3: + val = gEngfuncs.pNetAPI->ValueForKey( info, "current" ); + val2 = gEngfuncs.pNetAPI->ValueForKey( info, "max" ); + if ( val && val2 ) + { + sprintf( sz, "%s/%s", val, val2 ); + sz[ 31 ] = '\0'; + // Server Map; + m_pLabel->setText( sz ); + } + break; + case 4: + val = gEngfuncs.pNetAPI->ValueForKey( info, "ping" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Name; + m_pLabel->setText( sz ); + } + break; + default: + break; + } + } + else + { + if ( !row && !column ) + { + if ( ServersIsQuerying() ) + { + m_pLabel->setText( "Waiting for servers to respond..." ); + } + else + { + m_pLabel->setText( "Press 'Refresh' to search for servers..." ); + } + } + else + { + m_pLabel->setText( "" ); + } + } + + return m_pLabel; + } + + virtual Panel* startCellEditing(int column,int row) + { + return null; + } + +}; + +class ConnectHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + ConnectHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoConnect(); + } +}; + +class RefreshHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + RefreshHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoRefresh(); + } +}; + +class BroadcastRefreshHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + BroadcastRefreshHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoBroadcastRefresh(); + } +}; + +class StopHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + StopHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoStop(); + } +}; + +class CancelHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + CancelHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoCancel(); + } +}; + +class PingHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + PingHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoPing(); + } +}; + +class SortHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + SortHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoSort( "map" ); + } +}; + +} + +class LabelSortInputHandler : public InputSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + char m_szSortKey[ 64 ]; + +public: + LabelSortInputHandler( ServerBrowserTablePanel *pBrowser, char *name ) + { + m_pBrowser = pBrowser; + strcpy( m_szSortKey, name ); + } + + 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) + { + m_pBrowser->DoSort( m_szSortKey ); + } + + virtual void mouseDoublePressed(MouseCode code,Panel* panel) + { + m_pBrowser->DoSort( m_szSortKey ); + } + + 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 CSBLabel : public Label +{ + +private: + char m_szSortKey[ 64 ]; + ServerBrowserTablePanel *m_pBrowser; + +public: + CSBLabel( char *name, char *sortkey ) : Label( name ) + { + m_pBrowser = NULL; + + strcpy( m_szSortKey, sortkey ); + + int label_bg_r = 120, + label_bg_g = 75, + label_bg_b = 32, + label_bg_a = 200; + + int label_fg_r = 255, + label_fg_g = 0, + label_fg_b = 0, + label_fg_a = 0; + + setContentAlignment( vgui::Label::a_west ); + setFgColor( label_fg_r, label_fg_g, label_fg_b, label_fg_a ); + setBgColor( label_bg_r, label_bg_g, label_bg_b, label_bg_a ); + setFont( Scheme::sf_primary2 ); + + } + + void setTable( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + + addInputSignal( new LabelSortInputHandler( (ServerBrowserTablePanel * )m_pBrowser, m_szSortKey ) ); + } +}; + +ServerBrowser::ServerBrowser(int x,int y,int wide,int tall) : CTransparentPanel( 100, x,y,wide,tall ) +{ + int i; + + _headerPanel = new HeaderPanel(0,0,wide,HEADER_SIZE_Y); + _headerPanel->setParent(this); + _headerPanel->setFgColor( 100,100,100, 100 ); + _headerPanel->setBgColor( 0, 0, 0, 100 ); + + CSBLabel *pLabel[5]; + + pLabel[0] = new CSBLabel( "Address", "address" ); + pLabel[1] = new CSBLabel( "Server", "hostname" ); + pLabel[2] = new CSBLabel( "Map", "map" ); + pLabel[3] = new CSBLabel( "Current", "current" ); + pLabel[4] = new CSBLabel( "Latency", "ping" ); + + for ( i = 0; i < 5; i++ ) + { + _headerPanel->addSectionPanel( pLabel[i] ); + } + + // _headerPanel->setFont( Scheme::sf_primary1 ); + + _headerPanel->setSliderPos( 0, CSIZE_ADDRESS ); + _headerPanel->setSliderPos( 1, CSIZE_SERVER ); + _headerPanel->setSliderPos( 2, CSIZE_MAP ); + _headerPanel->setSliderPos( 3, CSIZE_CURRENT ); + _headerPanel->setSliderPos( 4, CSIZE_PING ); + + _tablePanel = new ServerBrowserTablePanel( 0, HEADER_SIZE_Y, wide, tall - HEADER_SIZE_Y, NUM_COLUMNS ); + _tablePanel->setParent(this); + _tablePanel->setHeaderPanel(_headerPanel); + _tablePanel->setFgColor( 100,100,100, 100 ); + _tablePanel->setBgColor( 0, 0, 0, 100 ); + + _tablePanel->addInputSignal( new CBrowser_InputSignal( (ServerBrowserTablePanel *)_tablePanel ) ); + + for ( i = 0; i < 5; i++ ) + { + pLabel[i]->setTable( (ServerBrowserTablePanel * )_tablePanel ); + } + + int bw = 80, bh = 15; + int by = tall - HEADER_SIZE_Y; + + int btnx = 10; + + _connectButton = new CommandButton( "Connect", btnx, by, bw, bh ); + _connectButton->setParent( this ); + _connectButton->addActionSignal( new ConnectHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + btnx += bw; + + _refreshButton = new CommandButton( "Refresh", btnx, by, bw, bh ); + _refreshButton->setParent( this ); + _refreshButton->addActionSignal( new RefreshHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + /* + btnx += bw; + + _broadcastRefreshButton = new CommandButton( "LAN", btnx, by, bw, bh ); + _broadcastRefreshButton->setParent( this ); + _broadcastRefreshButton->addActionSignal( new BroadcastRefreshHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + */ + + btnx += bw; + + _stopButton = new CommandButton( "Stop", btnx, by, bw, bh ); + _stopButton->setParent( this ); + _stopButton->addActionSignal( new StopHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + /* + btnx += bw; + + _pingButton = new CommandButton( "Test", btnx, by, bw, bh ); + _pingButton->setParent( this ); + _pingButton->addActionSignal( new PingHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + btnx += bw; + + _sortButton = new CommandButton( "Sort", btnx, by, bw, bh ); + _sortButton->setParent( this ); + _sortButton->addActionSignal( new SortHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + */ + + btnx += bw; + + _cancelButton = new CommandButton( "Close", btnx, by, bw, bh ); + _cancelButton->setParent( this ); + _cancelButton->addActionSignal( new CancelHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + setPaintBorderEnabled(false); + setPaintBackgroundEnabled(false); + setPaintEnabled(false); +} + +void ServerBrowser::setSize(int wide,int tall) +{ + Panel::setSize(wide,tall); + + _headerPanel->setBounds(0,0,wide,HEADER_SIZE_Y); + _tablePanel->setBounds(0,HEADER_SIZE_Y,wide,tall - HEADER_SIZE_Y); + + _connectButton->setBounds( 5, tall - HEADER_SIZE_Y, 75, 15 ); + _refreshButton->setBounds( 85, tall - HEADER_SIZE_Y, 75, 15 ); + /* + _broadcastRefreshButton->setBounds( 165, tall - HEADER_SIZE_Y, 75, 15 ); + */ + _stopButton->setBounds( 165, tall - HEADER_SIZE_Y, 75, 15 ); + /* + _pingButton->setBounds( 325, tall - HEADER_SIZE_Y, 75, 15 ); + */ + _cancelButton->setBounds( 245, tall - HEADER_SIZE_Y, 75, 15 ); +} + +void CBrowser_InputSignal::mousePressed(MouseCode code,Panel* panel) +{ + int x, y; + int therow = 2; + + if ( code != MOUSE_LEFT ) + return; + + panel->getApp()->getCursorPos(x,y); + panel->screenToLocal( x, y ); + + therow = y / CELL_HEIGHT; + + // Figure out which row it's on + m_pBrowser->setMouseOverRow( therow ); +} + +void CBrowser_InputSignal::mouseDoublePressed(MouseCode code,Panel* panel) +{ + int x, y; + int therow = 2; + + if ( code != MOUSE_LEFT ) + return; + + panel->getApp()->getCursorPos(x,y); + panel->screenToLocal( x, y ); + + therow = y / CELL_HEIGHT; + + // Figure out which row it's on + m_pBrowser->setMouseOverRow( therow ); + m_pBrowser->DoConnect(); +} \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/vgui_ServerBrowser.h b/hlsdk/ricochet/cl_dll/vgui_ServerBrowser.h new file mode 100644 index 00000000..b8267705 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/vgui_ServerBrowser.h @@ -0,0 +1,44 @@ + +#ifndef ServerBrowser_H +#define ServerBrowser_H + +#include + +namespace vgui +{ +class Button; +class TablePanel; +class HeaderPanel; +} + +class CTransparentPanel; +class CommandButton; + +// Scoreboard positions +#define SB_X_INDENT (20 * ((float)ScreenHeight / 640)) +#define SB_Y_INDENT (20 * ((float)ScreenHeight / 480)) + +class ServerBrowser : public CTransparentPanel +{ +private: + HeaderPanel * _headerPanel; + TablePanel* _tablePanel; + + CommandButton* _connectButton; + CommandButton* _refreshButton; + CommandButton* _broadcastRefreshButton; + CommandButton* _stopButton; + CommandButton* _sortButton; + CommandButton* _cancelButton; + + CommandButton* _pingButton; + +public: + ServerBrowser(int x,int y,int wide,int tall); +public: + virtual void setSize(int wide,int tall); +}; + + + +#endif \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/vgui_TeamFortressViewport.cpp b/hlsdk/ricochet/cl_dll/vgui_TeamFortressViewport.cpp new file mode 100644 index 00000000..1dd88d43 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/vgui_TeamFortressViewport.cpp @@ -0,0 +1,2212 @@ +//=========== (C) Copyright 1999 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 "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 "../engine/keydefs.h" +#include "demo.h" +#include "demo_api.h" +#include "ammohistory.h" + +#include "vgui_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ServerBrowser.h" +#include "vgui_ScorePanel.h" +#include "vgui_discobjects.h" + +extern WeaponsResource gWR; +extern int g_iVisibleMouse; +class CCommandMenu; +int g_iPlayerClass; +int g_iTeamNumber; +int g_iUser1; +int g_iUser2; + +// 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 x, int y, int wide, int tall ); + +using namespace vgui; + +// Team Colors +int iTeamColors[5][3] = +{ + { 255, 255, 255 }, + { 66, 115, 247 }, + { 220, 51, 38 }, + { 240, 135, 0 }, + { 115, 240, 115 }, +}; + +// Used for Class specific buttons +char *sTFClasses[] = +{ + "", + "SCOUT", + "SNIPER", + "SOLDIER", + "DEMOMAN", + "MEDIC", + "HWGUY", + "PYRO", + "SPY", + "ENGINEER", + "CIVILIAN", +}; + +char *sLocalisedClasses[] = +{ + "#Civilian", + "#Scout", + "#Sniper", + "#Soldier", + "#Demoman", + "#Medic", + "#HWGuy", + "#Pyro", + "#Spy", + "#Engineer", + "#Random", + "#Civilian", +}; + +char *sTFClassSelection[] = +{ + "civilian", + "scout", + "sniper", + "soldier", + "demoman", + "medic", + "hwguy", + "pyro", + "spy", + "engineer", + "randompc", + "civilian", +}; + +int iBuildingCosts[] = +{ + BUILD_COST_DISPENSER, + BUILD_COST_SENTRYGUN +}; + +// 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 *TeamFortressViewport::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 TeamFortressViewport is nulled out +// Input : stAllocateBlock - +// Output : void * +//----------------------------------------------------------------------------- +void *TeamFortressViewport::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) {} +}; + + +//================================================================ +TeamFortressViewport::TeamFortressViewport(int x,int y,int wide,int tall) : Panel(x,y,wide,tall), m_SchemeManager(wide,tall) +{ + gViewPort = this; + m_iInitialized = false; + m_pScoreBoard = NULL; + m_pSpectatorMenu = NULL; + m_pCurrentMenu = NULL; + m_pCurrentCommandMenu = NULL; + + CVAR_CREATE( "hud_classautokill", "1", FCVAR_ARCHIVE ); // 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 + + 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 + CreateScoreBoard(); + CreateCommandMenu(); + CreateServerBrowser(); + CreateSpectatorMenu(); + + // Discwar + CreateDiscIcons(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called everytime a new level is started. Viewport clears out it's data. +//----------------------------------------------------------------------------- +void TeamFortressViewport::Initialize( void ) +{ + // Force each menu to Initialize + if (m_pScoreBoard) + { + m_pScoreBoard->Initialize(); + HideScoreBoard(); + } + if (m_pSpectatorMenu) + { + // Spectator menu doesn't need initializing + m_pSpectatorMenu->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::scu_none) ); +} + +class CException; +//----------------------------------------------------------------------------- +// Purpose: Read the Command Menu structure from the txt file and create the menu. +//----------------------------------------------------------------------------- +void TeamFortressViewport::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 = 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 + + // Detpack strings + gHUD.m_TextMessage.LocaliseTextString( "#DetpackSet_For5Seconds", m_sDetpackStrings[0], MAX_BUTTON_SIZE ); + gHUD.m_TextMessage.LocaliseTextString( "#DetpackSet_For20Seconds", m_sDetpackStrings[1], MAX_BUTTON_SIZE ); + gHUD.m_TextMessage.LocaliseTextString( "#DetpackSet_For50Seconds", m_sDetpackStrings[2], MAX_BUTTON_SIZE ); + + // 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: Creates all the class choices under a spy's disguise menus, and +// maps a command to them +// Output : CCommandMenu +//----------------------------------------------------------------------------- +CCommandMenu *TeamFortressViewport::CreateDisguiseSubmenu( CommandButton *pButton, CCommandMenu *pParentMenu, const char *commandText ) +{ + // create the submenu, under which the class choices will be listed + CCommandMenu *pMenu = CreateSubMenu( pButton, pParentMenu ); + m_pCommandMenus[m_iNumMenus] = pMenu; + m_iNumMenus++; + + // create the class choice buttons + for ( int i = PC_SCOUT; i <= PC_ENGINEER; i++ ) + { + CommandButton *pDisguiseButton = new CommandButton( CHudTextMessage::BufferedLocaliseTextString( sLocalisedClasses[i] ), 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y ); + + char sz[256]; + sprintf(sz, "%s %d", commandText, i ); + pDisguiseButton->addActionSignal(new CMenuHandler_StringCommand(sz)); + + pMenu->AddButton( pDisguiseButton ); + } + + return pMenu; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pButtonText - +// *pButtonName - +// Output : CommandButton +//----------------------------------------------------------------------------- +CommandButton *TeamFortressViewport::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] ); + } + // ChangeClass + else if ( !strcmp( pButtonName, "!CHANGECLASS" ) ) + { + // Create the Change class menu + pButton = new ClassButton(-1, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y, false); + + // ChangeClass Submenu + pMenu = CreateSubMenu(pButton, m_pCurrentCommandMenu); + m_pCommandMenus[m_iNumMenus] = pMenu; + m_iNumMenus++; + + for (int i = PC_SCOUT; i <= PC_RANDOM; i++ ) + { + char sz[256]; + + // ChangeClass buttons + CHudTextMessage::LocaliseTextString( sLocalisedClasses[i], sz, 256 ); + ClassButton *pClassButton = new ClassButton( i, sz, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y, false); + + sprintf(sz, "%s", sTFClassSelection[i]); + pClassButton->addActionSignal(new CMenuHandler_StringCommandClassSelect(sz)); + pMenu->AddButton( pClassButton ); + } + } + // 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) ); + } + // Class Descriptions + else if ( !strcmp( pButtonName, "!CLASSDESC" ) ) + { + 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_CLASSHELP)); + // 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) ); + } + // Spy abilities + else if ( !strcmp( pButtonName, "!SPY" ) ) + { + pButton = new DisguiseButton( 0, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y ); + } + // Feign + else if ( !strcmp( pButtonName, "!FEIGN" ) ) + { + pButton = new FeignButton(FALSE, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand( "feign" )); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Feign Silently + else if ( !strcmp( pButtonName, "!FEIGNSILENT" ) ) + { + pButton = new FeignButton(FALSE, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand( "sfeign" )); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Stop Feigning + else if ( !strcmp( pButtonName, "!FEIGNSTOP" ) ) + { + pButton = new FeignButton(TRUE, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand( "feign" )); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Disguise + else if ( !strcmp( pButtonName, "!DISGUISEENEMY" ) ) + { + // Create the disguise enemy button, which active only if there are 2 teams + pButton = new DisguiseButton(DISGUISE_TEAM2, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + CreateDisguiseSubmenu( pButton, m_pCurrentCommandMenu, "disguise_enemy" ); + } + else if ( !strcmp( pButtonName, "!DISGUISEFRIENDLY" ) ) + { + // Create the disguise friendly button, which active only if there are 1 or 2 teams + pButton = new DisguiseButton(DISGUISE_TEAM1 | DISGUISE_TEAM2, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + CreateDisguiseSubmenu( pButton, m_pCurrentCommandMenu, "disguise_friendly" ); + } + else if ( !strcmp( pButtonName, "!DISGUISE" ) ) + { + // Create the Disguise button + pButton = new DisguiseButton( DISGUISE_TEAM3 | DISGUISE_TEAM4, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + CCommandMenu *pDisguiseMenu = CreateSubMenu( pButton, m_pCurrentCommandMenu ); + m_pCommandMenus[m_iNumMenus] = pDisguiseMenu; + m_iNumMenus++; + + // Disguise Enemy submenu buttons + for ( int i = 1; i <= 4; i++ ) + { + // only show the 4th disguise button if we have 4 teams + m_pDisguiseButtons[i] = new DisguiseButton( ((i < 4) ? DISGUISE_TEAM3 : 0) | DISGUISE_TEAM4, "Disguise", 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + + pDisguiseMenu->AddButton( m_pDisguiseButtons[i] ); + + char sz[256]; + sprintf( sz, "disguise %d", i ); + CreateDisguiseSubmenu( m_pDisguiseButtons[i], pDisguiseMenu, sz ); + } + } + // Start setting a Detpack + else if ( !strcmp( pButtonName, "!DETPACKSTART" ) ) + { + // Detpack Submenu + pButton = new DetpackButton(2, 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++; + + // Set detpack buttons + CommandButton *pDetButton; + pDetButton = new CommandButton(m_sDetpackStrings[0], 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pDetButton->addActionSignal(new CMenuHandler_StringCommand("detstart 5")); + pMenu->AddButton( pDetButton ); + pDetButton = new CommandButton(m_sDetpackStrings[1], 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pDetButton->addActionSignal(new CMenuHandler_StringCommand("detstart 20")); + pMenu->AddButton( pDetButton ); + pDetButton = new CommandButton(m_sDetpackStrings[2], 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pDetButton->addActionSignal(new CMenuHandler_StringCommand("detstart 50")); + pMenu->AddButton( pDetButton ); + } + // Stop setting a Detpack + else if ( !strcmp( pButtonName, "!DETPACKSTOP" ) ) + { + pButton = new DetpackButton(1, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand( "detstop" )); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Engineer building + else if ( !strcmp( pButtonName, "!BUILD" ) ) + { + // only appears if the player is an engineer, and either they have built something or have enough metal to build + pButton = new BuildButton( BUILDSTATE_BASE, 0, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + } + else if ( !strcmp( pButtonName, "!BUILDSENTRY" ) ) + { + pButton = new BuildButton( BUILDSTATE_CANBUILD, BuildButton::SENTRYGUN, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("build 2")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!BUILDDISPENSER" ) ) + { + pButton = new BuildButton( BUILDSTATE_CANBUILD, BuildButton::DISPENSER, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("build 1")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!ROTATESENTRY180" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::SENTRYGUN, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("rotatesentry180")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!ROTATESENTRY" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::SENTRYGUN, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("rotatesentry")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!DISMANTLEDISPENSER" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::DISPENSER, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("dismantle 1")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!DISMANTLESENTRY" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::SENTRYGUN, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("dismantle 2")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!DETONATEDISPENSER" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::DISPENSER, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("detdispenser")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!DETONATESENTRY" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::SENTRYGUN, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("detsentry")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Stop building + else if ( !strcmp( pButtonName, "!BUILDSTOP" ) ) + { + pButton = new BuildButton( BUILDSTATE_BUILDING, 0, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("build")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + + return pButton; +} + +void TeamFortressViewport::ToggleServerBrowser() +{ + if (!m_iInitialized) + return; + + if ( !m_pServerBrowser ) + return; + + if ( m_pServerBrowser->isVisible() ) + { + m_pServerBrowser->setVisible( false ); + } + else + { + m_pServerBrowser->setVisible( true ); + } + + UpdateCursorState(); +} + +//======================================================================= +void TeamFortressViewport::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 TeamFortressViewport::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 TeamFortressViewport::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 TeamFortressViewport::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 TeamFortressViewport::IsScoreBoardVisible( void ) +{ + if (m_pScoreBoard) + return m_pScoreBoard->isVisible(); + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Hide the scoreboard +//----------------------------------------------------------------------------- +void TeamFortressViewport::HideScoreBoard( void ) +{ + // Prevent removal of scoreboard during intermission + if ( gHUD.m_iIntermission ) + return; + + if (m_pScoreBoard) + { + m_pScoreBoard->setVisible(false); + + GetClientVoiceMgr()->StopSquelchMode(); + + UpdateCursorState(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Activate's the player special ability +// called when the player hits their "special" key +//----------------------------------------------------------------------------- +void TeamFortressViewport::InputPlayerSpecial( void ) +{ + if (!m_iInitialized) + return; + + if ( g_iPlayerClass == PC_ENGINEER || g_iPlayerClass == PC_SPY ) + { + ShowCommandMenu(); + + if ( m_pCurrentCommandMenu ) + { + m_pCurrentCommandMenu->KeyInput( '7' ); + } + } + else + { + // if it's any other class, just send the command down to the server + ClientCmd( "_special" ); + } +} + +// Set the submenu of the Command Menu +void TeamFortressViewport::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 TeamFortressViewport::UpdateCommandMenu() +{ + m_pCommandMenus[0]->RecalculateVisibles( 0, false ); + m_pCommandMenus[0]->RecalculatePositions( 0 ); +} + +void TeamFortressViewport::UpdateSpectatorMenu() +{ + char sz[64]; + + if (!m_pSpectatorMenu) + return; + + // Don't pop up if the round windows are up + if ( m_pDiscStartRound && m_pDiscStartRound->isVisible() ) + return; + if ( m_pDiscEndRound && m_pDiscEndRound->isVisible() ) + return; + + if (m_iUser1) + { + m_pSpectatorMenu->setVisible( true ); + + if (m_iUser2 > 0 && m_iUser1 != 4 ) + { + // Locked onto a target, show the player's name + sprintf(sz, "#Spec_Mode%d : %s", m_iUser1, g_PlayerInfoList[ m_iUser2 ].name); + m_pSpectatorLabel->setText( CHudTextMessage::BufferedLocaliseTextString( sz ) ); + } + else + { + sprintf(sz, "#Spec_Mode%d", m_iUser1); + m_pSpectatorLabel->setText( CHudTextMessage::BufferedLocaliseTextString( sz ) ); + } + } + else + { + m_pSpectatorMenu->setVisible( false ); + } +} + +//====================================================================== +void TeamFortressViewport::CreateScoreBoard( void ) +{ + m_pScoreBoard = new ScorePanel(SBOARD_INDENT_X,SBOARD_INDENT_Y, ScreenWidth - (SBOARD_INDENT_X * 2), ScreenHeight - (SBOARD_INDENT_Y * 2)); + m_pScoreBoard->setParent(this); + m_pScoreBoard->setVisible(false); +} + +void TeamFortressViewport::CreateServerBrowser( void ) +{ + m_pServerBrowser = new ServerBrowser( 0, 0, ScreenWidth, ScreenHeight ); + m_pServerBrowser->setParent(this); + m_pServerBrowser->setVisible(false); +} + +//====================================================================== +// Disc vgui objects +void TeamFortressViewport::CreateDiscIcons( void ) +{ + m_iDiscPowerup = 0; + + // Create the disc ammo icons + int iX = (ScreenWidth - DISC_ICON_WIDTH) / 2; + int iXPos = iX - DISC_ICON_SPACER; + for (int i = 0; i < MAX_DISCS; i++) + { + m_pDiscIcons[i] = new CDiscPanel( iXPos + (i * DISC_ICON_SPACER), ScreenHeight - YRES(48), DISC_ICON_WIDTH, YRES(32) ); + m_pDiscIcons[i]->setParent(this); + } + + // Create the Arena Windows + m_pDiscStartRound = new CDiscArena_RoundStart(); + m_pDiscStartRound->setParent(this); + m_pDiscEndRound = new CDiscArena_RoundEnd(); + m_pDiscEndRound->setParent(this); + + // Create the Powerup window + m_pDiscPowerupWindow = new CDiscPowerups(); + m_pDiscPowerupWindow->setParent(this); + + // Create the Reward window + m_pDiscRewardWindow = new CDiscRewards(); + m_pDiscRewardWindow->setParent(this); +} + +//====================================================================== +// Set the VGUI Menu +void TeamFortressViewport::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* TeamFortressViewport::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, "Half-Life" ); + 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 ); + + if (!pfile) + return NULL; + + cText = pfile; + + strncpy( cTitle, m_sMapName, MAX_TITLE_LENGTH ); + cTitle[MAX_TITLE_LENGTH-1] = 0; + } + else if ( iTextToShow == SHOW_CLASSDESC ) + { + switch ( g_iPlayerClass ) + { + case PC_SCOUT: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_scout" ); + CHudTextMessage::LocaliseTextString( "#Title_scout", cTitle, MAX_TITLE_LENGTH ); break; + case PC_SNIPER: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_sniper" ); + CHudTextMessage::LocaliseTextString( "#Title_sniper", cTitle, MAX_TITLE_LENGTH ); break; + case PC_SOLDIER: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_soldier" ); + CHudTextMessage::LocaliseTextString( "#Title_soldier", cTitle, MAX_TITLE_LENGTH ); break; + case PC_DEMOMAN: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_demoman" ); + CHudTextMessage::LocaliseTextString( "#Title_demoman", cTitle, MAX_TITLE_LENGTH ); break; + case PC_MEDIC: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_medic" ); + CHudTextMessage::LocaliseTextString( "#Title_medic", cTitle, MAX_TITLE_LENGTH ); break; + case PC_HVYWEAP: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_hwguy" ); + CHudTextMessage::LocaliseTextString( "#Title_hwguy", cTitle, MAX_TITLE_LENGTH ); break; + case PC_PYRO: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_pyro" ); + CHudTextMessage::LocaliseTextString( "#Title_pyro", cTitle, MAX_TITLE_LENGTH ); break; + case PC_SPY: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_spy" ); + CHudTextMessage::LocaliseTextString( "#Title_spy", cTitle, MAX_TITLE_LENGTH ); break; + case PC_ENGINEER: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_engineer" ); + CHudTextMessage::LocaliseTextString( "#Title_engineer", cTitle, MAX_TITLE_LENGTH ); break; + case PC_CIVILIAN: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_civilian" ); + CHudTextMessage::LocaliseTextString( "#Title_civilian", cTitle, MAX_TITLE_LENGTH ); break; + default: + return NULL; + } + + if ( g_iPlayerClass == PC_CIVILIAN ) + { + sprintf(sz, "classes/long_civilian.txt"); + } + else + { + sprintf(sz, "classes/long_%s.txt", sTFClassSelection[ g_iPlayerClass ]); + } + char *pfile = (char*)gEngfuncs.COM_LoadFile( sz, 5, NULL ); + if (pfile) + { + cText = pfile; + } + } + + // 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, g_iPlayerClass == PC_UNDEFINED, false, 0, 0, ScreenWidth, ScreenHeight ); + pMOTDPanel->setParent( this ); + + if ( pfile ) + gEngfuncs.COM_FreeFile( pfile ); + + return pMOTDPanel; +} + +//================================================================ +// VGUI Menus +void TeamFortressViewport::ShowVGUIMenu( int iMenu ) +{ + CMenuPanel *pNewMenu = NULL; + + // Don't open menus in demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + 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 ) + { + // 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_CLASSHELP: + pNewMenu = CreateTextWindow( SHOW_CLASSDESC ); + break; + + default: + break; + } + + if (!pNewMenu) + return; + + // Close the Command Menu if it's open + HideCommandMenu(); + + pNewMenu->SetMenuID( iMenu ); + pNewMenu->SetActive( true ); + + // 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 TeamFortressViewport::HideVGUIMenu() +{ + while (m_pCurrentMenu) + { + HideTopMenu(); + } +} + +// Remove the top VGUI menu, and bring up the next one +void TeamFortressViewport::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 TeamFortressViewport::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_CLASS || iId == MENU_INTRO || iId == MENU_CLASSHELP ) + return FALSE; + } + + return TRUE; +} + +//====================================================================================== +// SPECTATOR MENU +//====================================================================================== +// Spectator "Menu" explaining the Spectator buttons +void TeamFortressViewport::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( "Spectator", 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 + Label *pLabel = new Label( CHudTextMessage::BufferedLocaliseTextString( "#Spec_Help" ), 0, YRES(25), ScreenWidth, YRES(15) ); + 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_north ); + + 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 TeamFortressViewport::UpdateOnPlayerInfo() +{ + if (m_pScoreBoard) + m_pScoreBoard->Update(); +} + +void TeamFortressViewport::UpdateCursorState() +{ + // Need cursor if any VGUI window is up + if ( m_pCurrentMenu || m_pServerBrowser->isVisible() || GetClientVoiceMgr()->IsInSquelchMode() ) + { + g_iVisibleMouse = true; + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::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::scu_arrow) ); + return; + } + } + + IN_ResetMouse(); + g_iVisibleMouse = false; + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::scu_none) ); +} + +void TeamFortressViewport::UpdateHighlights() +{ + if (m_pCurrentCommandMenu) + m_pCurrentCommandMenu->MakeVisible( NULL ); +} + +void TeamFortressViewport::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 TeamFortressViewport::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(); + for (int i = 0; i < MAX_DISCS; i++) + { + if ( m_pDiscIcons[i] ) + m_pDiscIcons[i]->Update( i, false, m_iDiscPowerup ); + } + + m_iCurrentTeamNumber = g_iTeamNumber; + } + + if ( g_iPlayerClass != m_iCurrentPlayerClass ) + { + UpdateCommandMenu(); + + m_iCurrentPlayerClass = g_iPlayerClass; + } + + // Update the Reward window + if ( m_flRewardOpenTime && ( m_flRewardOpenTime < gHUD.m_flTime ) ) + { + m_pDiscRewardWindow->setVisible(false); + m_flRewardOpenTime = 0; + } + + // Update the disc icons the player has + bool bVisible = true; + if ( m_pDiscStartRound && m_pDiscStartRound->isVisible() ) + bVisible = false; + if ( m_pDiscEndRound && m_pDiscEndRound->isVisible() ) + bVisible = false; + if ( m_pSpectatorMenu && m_pSpectatorMenu->isVisible() ) + bVisible = false; + for (int i = 0; i < MAX_DISCS; i++) + { + if ( bVisible == false ) + { + m_pDiscIcons[i]->setVisible(false); + } + else + { + m_pDiscIcons[i]->Update(i, true, m_iDiscPowerup); + m_pDiscIcons[i]->setVisible(true); + } + } + + // See if the Spectator Menu needs to be update + if ( g_iUser1 != m_iUser1 || g_iUser2 != m_iUser2 ) + { + m_iUser1 = g_iUser1; + m_iUser2 = g_iUser2; + UpdateSpectatorMenu(); + } + + // 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 TeamFortressViewport::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 TeamFortressViewport::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_SPACE ) + gEngfuncs.pfnClientCmd("ob_next"); + if ( down && keynum == K_ENTER ) + gEngfuncs.pfnClientCmd("ob_mode"); + if ( down && keynum == K_CTRL ) + gEngfuncs.pfnClientCmd("ob_prev"); + } + + // 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 || iMenuID == MENU_CLASS) + { + // Escape gets you out of Team/Class menus if the Cancel button is visible + if ( keynum == K_ESCAPE ) + { + if ( (iMenuID == MENU_TEAM && g_iTeamNumber) || (iMenuID == MENU_CLASS && g_iPlayerClass) ) + { + 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 || iMenuID == MENU_CLASSHELP ) + { + HideTopMenu(); + 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 TeamFortressViewport::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 TeamFortressViewport::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] ); + + // Set the disguise buttons + if (m_pDisguiseButtons[i]) + m_pDisguiseButtons[i]->setText( m_sTeamNames[teamNum] ); + } + + return 1; +} + +int TeamFortressViewport::MsgFunc_Feign(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iIsFeigning = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu(); + + return 1; +} + +int TeamFortressViewport::MsgFunc_Detpack(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iIsSettingDetpack = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu(); + + return 1; +} + +int TeamFortressViewport::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 TeamFortressViewport::MsgFunc_MOTD( const char *pszName, int iSize, void *pbuf ) +{ + if (m_iGotAllMOTD) + m_szMOTD[0] = 0; + + BEGIN_READ( pbuf, iSize ); + + m_iGotAllMOTD = READ_BYTE(); + strncat( m_szMOTD, READ_STRING(), sizeof(m_szMOTD) - strlen(m_szMOTD) ); + m_szMOTD[ sizeof(m_szMOTD)-1 ] = '\0'; + + if ( m_iGotAllMOTD ) + { + ShowVGUIMenu( MENU_INTRO ); + } + + return 1; +} + +int TeamFortressViewport::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 TeamFortressViewport::MsgFunc_RandomPC( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iRandomPC = READ_BYTE(); + + return 1; +} + +int TeamFortressViewport::MsgFunc_ServerName( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + strncpy( m_szServerName, READ_STRING(), MAX_SERVERNAME_LENGTH ); + + return 1; +} + +int TeamFortressViewport::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 playerclass = 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].playerclass = playerclass; + g_PlayerExtraInfo[cl].teamnumber = teamnumber; + + 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 TeamFortressViewport::MsgFunc_TeamScore( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + char *TeamName = READ_STRING(); + + // find the team matching the name + int i; + for ( 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 TeamFortressViewport::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 TeamFortressViewport::DeathMsg( int killer, int victim ) +{ + m_pScoreBoard->DeathMsg(killer,victim); +} + +int TeamFortressViewport::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 TeamFortressViewport::MsgFunc_AllowSpec( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iAllowSpectators = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu(); + + return 1; +} + +int TeamFortressViewport::MsgFunc_StartRnd(const char *pszName, int iSize, void *pbuf ) +{ + if (m_pDiscStartRound) + return m_pDiscStartRound->MsgFunc_GetPlayers( pszName, iSize, pbuf ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_EndRnd(const char *pszName, int iSize, void *pbuf ) +{ + if (m_pDiscEndRound) + return m_pDiscEndRound->MsgFunc_GetPlayers( pszName, iSize, pbuf ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_Frozen(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + int iFrozen = READ_BYTE(); + + if ( iFrozen ) + { + gHUD.m_Health.m_bitsDamage = DMG_FREEZE; + gHUD.m_Health.m_iFlags |= HUD_ACTIVE; + } + else + { + gHUD.m_Health.m_bitsDamage = 0; + gHUD.m_Health.m_iFlags &= ~HUD_ACTIVE; + } + + return 1; +} + +int TeamFortressViewport::MsgFunc_Powerup( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iDiscPowerup = READ_BYTE(); + + // Force the disc icons to update + for (int i = 0; i < MAX_DISCS; i++) + { + if ( m_pDiscIcons[i] ) + m_pDiscIcons[i]->Update( i, false, m_iDiscPowerup ); + } + + // Update the powerup window + m_pDiscPowerupWindow->RecalculateText( m_iDiscPowerup ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_Reward( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int iReward = READ_SHORT(); + + // Update the reward window + m_pDiscRewardWindow->RecalculateText( iReward ); + m_flRewardOpenTime = gHUD.m_flTime + 2.0; + + return 1; +} + + diff --git a/hlsdk/ricochet/cl_dll/vgui_TeamFortressViewport.h b/hlsdk/ricochet/cl_dll/vgui_TeamFortressViewport.h new file mode 100644 index 00000000..b1415cf4 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/vgui_TeamFortressViewport.h @@ -0,0 +1,1207 @@ + +#ifndef TEAMFORTRESSVIEWPORT_H +#define TEAMFORTRESSVIEWPORT_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" + +#include "discwar.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 CDiscPanel; +class CDiscArena_RoundStart; +class CDiscArena_RoundEnd; +class CDiscPowerups; +class CDiscRewards; + +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]; + +#define MAX_SERVERNAME_LENGTH 32 + +// Use this to set any co-ords in 640x480 space +#define XRES(x) (x * ((float)ScreenWidth / 640)) +#define YRES(y) (y * ((float)ScreenHeight / 480)) + +// Command Menu positions +#define MAX_MENUS 40 +#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) + +// Discwar icons +#define DISC_ICON_WIDTH XRES(32) +#define DISC_ICON_SPACER XRES(72) + +// 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: + void LoadImage(const char * pImageName); + 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 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 TeamFortressViewport : 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; + + // VGUI Menus + void CreateSpectatorMenu( 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" + Label *m_pSpectatorLabel; + int m_iAllowSpectators; + + // Data for specific sections of the Command Menu + int m_iValidClasses[5]; + int m_iIsFeigning; + int m_iIsSettingDetpack; + int m_iNumberOfTeams; + int m_iBuildState; + int m_iRandomPC; + char m_sTeamNames[5][MAX_TEAMNAME_SIZE]; + + // Localisation strings + char m_sDetpackStrings[3][MAX_BUTTON_SIZE]; + + char m_sMapName[64]; +public: + TeamFortressViewport(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 CreateDiscIcons( void ); + + void UpdateCursorState( void ); + void UpdateCommandMenu( void ); + void UpdateOnPlayerInfo( void ); + void UpdateHighlights( void ); + void UpdateSpectatorMenu( void ); + + int KeyInput( int down, int keynum, const char *pszCurrentBinding ); + void InputPlayerSpecial( void ); + 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 GetIsFeigning() { return m_iIsFeigning; }; + int GetIsSettingDetpack() { return m_iIsSettingDetpack; }; + 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_Feign(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_Detpack(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_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 ); + // Discwar + int MsgFunc_StartRnd(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_EndRnd(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_Powerup( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_Reward( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_Frozen( 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; + ScorePanel *m_pScoreBoard; + char m_szServerName[ MAX_SERVERNAME_LENGTH ]; + CDiscPanel *m_pDiscIcons[MAX_DISCS]; + CDiscArena_RoundStart *m_pDiscStartRound; + CDiscArena_RoundEnd *m_pDiscEndRound; + CDiscPowerups *m_pDiscPowerupWindow; + CDiscRewards *m_pDiscRewardWindow; + + CTransparentPanel *m_pSpectatorMenu; + float m_flRewardOpenTime; + int m_iUser1; + int m_iUser2; + int m_iDiscPowerup; +}; + +//============================================================ +// 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_CLASSDESC 2 +#define SHOW_MOTD 3 + +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 FeignButton : public CommandButton +{ +private: + int m_iFeignState; +public: + FeignButton( int iState, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + m_iFeignState = iState; + } + + virtual int IsNotValid() + { + // Only visible for spies + if (g_iPlayerClass != PC_SPY) + return true; + + if (m_iFeignState == gViewPort->GetIsFeigning()) + 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; + } +}; + +class DetpackButton : public CommandButton +{ +private: + int m_iDetpackState; +public: + DetpackButton( int iState, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + m_iDetpackState = iState; + } + + virtual int IsNotValid() + { + // Only visible for demomen + if (g_iPlayerClass != PC_DEMOMAN) + return true; + + if (m_iDetpackState == gViewPort->GetIsSettingDetpack()) + 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]); + } + } +}; + +//================================================================ +// 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); +}; + +//================================================================ +// Specific Menus to handle old HUD sections +class CHealthPanel : public DragNDropPanel +{ +private: + BitmapTGA *m_pHealthTGA; + Label *m_pHealthLabel; +public: + CHealthPanel(int x,int y,int wide,int tall) : DragNDropPanel(x,y,wide,tall) + { + // Load the Health icon + FileInputStream* fis = new FileInputStream( GetVGUITGAName("%d_hud_health"), false); + m_pHealthTGA = new BitmapTGA(fis,true); + fis->close(); + + // Create the Health Label + int iXSize,iYSize; + m_pHealthTGA->getSize(iXSize,iYSize); + m_pHealthLabel = new Label("",0,0,iXSize,iYSize); + m_pHealthLabel->setImage(m_pHealthTGA); + m_pHealthLabel->setParent(this); + + // Set panel dimension + // Shouldn't be needed once Billy's fized setImage not recalculating the size + //setSize( iXSize + 100, gHUD.m_iFontHeight + 10 ); + //m_pHealthLabel->setPos( 10, (getTall() - iYSize) / 2 ); + } + + virtual void paintBackground() + { + } + + void paint() + { + // Get the paint color + int r,g,b,a; + // Has health changed? Flash the health # + if (gHUD.m_Health.m_fFade) + { + gHUD.m_Health.m_fFade -= (gHUD.m_flTimeDelta * 20); + if (gHUD.m_Health.m_fFade <= 0) + { + a = MIN_ALPHA; + gHUD.m_Health.m_fFade = 0; + } + + // Fade the health number back to dim + a = MIN_ALPHA + (gHUD.m_Health.m_fFade/FADE_TIME) * 128; + } + else + a = MIN_ALPHA; + + gHUD.m_Health.GetPainColor( r, g, b ); + ScaleColors(r, g, b, a ); + + // If health is getting low, make it bright red + if (gHUD.m_Health.m_iHealth <= 15) + a = 255; + + int iXSize,iYSize, iXPos, iYPos; + m_pHealthTGA->getSize(iXSize,iYSize); + m_pHealthTGA->getPos(iXPos, iYPos); + + // Paint the player's health + int x = gHUD.DrawHudNumber( iXPos + iXSize + 5, iYPos + 5, DHN_3DIGITS | DHN_DRAWZERO, gHUD.m_Health.m_iHealth, r, g, b); + + // Draw the vertical line + int HealthWidth = gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left; + x += HealthWidth / 2; + FillRGBA(x, iYPos + 5, HealthWidth / 10, gHUD.m_iFontHeight, 255, 160, 0, a); + } +}; + +#endif diff --git a/hlsdk/ricochet/cl_dll/vgui_discobjects.cpp b/hlsdk/ricochet/cl_dll/vgui_discobjects.cpp new file mode 100644 index 00000000..f1773cd9 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/vgui_discobjects.cpp @@ -0,0 +1,593 @@ +//=========== (C) Copyright 1999 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: VGUI objects for Discwar +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "VGUI_Font.h" + +#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 "ammo.h" +#include "string.h" +#include "ammohistory.h" + +#include "vgui_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ServerBrowser.h" +#include "vgui_discobjects.h" + +// Positions and Dimensions +#define ARENAWINDOW_SIZE_X (ScreenWidth) +#define ARENAWINDOW_SIZE_Y YRES(128) +#define ARENAWINDOW_X ((ScreenWidth - ARENAWINDOW_SIZE_X) / 2) +#define ARENAWINDOW_Y (ScreenHeight - ARENAWINDOW_SIZE_Y) + +#define POWERUP_SIZE_X (ScreenWidth) +#define POWERUP_SIZE_Y YRES(32) +#define POWERUP_X ((ScreenWidth - POWERUP_SIZE_X) / 2) +#define POWERUP_Y (ScreenHeight - POWERUP_SIZE_Y) + +#define REWARD_SIZE_X (ScreenWidth) +#define REWARD_SIZE_Y YRES(48) +#define REWARD_X ((ScreenWidth - POWERUP_SIZE_X) / 2) +#define REWARD_Y (ScreenHeight / 6) + +extern WeaponsResource gWR; +int g_iCannotFire; + +//=========================================================== +// Disc ammo icon +CDiscPanel::CDiscPanel(int x,int y,int wide,int tall) : Label("", x,y,wide,tall) +{ + setContentFitted(true); + + // Standard discs + m_pDiscTGA_Red = LoadTGAForRes("discred"); + m_pDiscTGA_RedGlow = LoadTGAForRes("discred2"); + m_pDiscTGA_Blue = LoadTGAForRes("discblue"); + m_pDiscTGA_BlueGlow = LoadTGAForRes("discblue2"); + m_pDiscTGA_Grey = LoadTGAForRes("discgrey"); + + // Powerup discs + m_pDiscTGA_Fast = LoadTGAForRes("fast"); + m_pDiscTGA_Freeze = LoadTGAForRes("freeze"); + m_pDiscTGA_Hard = LoadTGAForRes("hard"); + m_pDiscTGA_Triple = LoadTGAForRes("triple"); + + setImage( m_pDiscTGA_Red ); +} + +void CDiscPanel::Update( int iDiscNo, bool bGlow, int iPowerup ) +{ + int iDiscs = gWR.GetAmmo( 1 ); + + // Grey disc for missing discs + if ( iDiscs < iDiscNo+1 ) + { + setImage( m_pDiscTGA_Grey ); + } + // Powerups override team colored discs + else if ( iPowerup & POW_TRIPLE ) + { + setImage( m_pDiscTGA_Triple ); + } + else if ( iPowerup & POW_FAST ) + { + setImage( m_pDiscTGA_Fast ); + } + else if ( iPowerup & POW_FREEZE ) + { + setImage( m_pDiscTGA_Freeze ); + } + else if ( iPowerup & POW_HARD ) + { + setImage( m_pDiscTGA_Hard ); + } + else if (g_iTeamNumber == 1) + { + if ( gWR.GetAmmo( 1 ) == 3 ) + setImage( m_pDiscTGA_RedGlow ); + else + setImage( m_pDiscTGA_Red ); + } + else + { + if ( gWR.GetAmmo( 1 ) == 3 ) + setImage( m_pDiscTGA_BlueGlow ); + else + setImage( m_pDiscTGA_Blue ); + } +} + +//=========================================================== +// Arena window +CDiscArenaPanel::CDiscArenaPanel( int x, int y, int wide, int tall ) : CTransparentPanel(255, x,y,wide,tall) +{ + m_iNumPlayers = 0; +} + +//=========================================================== +// Message handler. Gets the Ids of the players in the round. +int CDiscArenaPanel::MsgFunc_GetPlayers(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iRoundNumber = READ_BYTE(); + m_iSecondsToGo = READ_BYTE(); + + m_iNumPlayers = READ_BYTE(); + if ( m_iNumPlayers > 0 && m_iNumPlayers <= MAX_PLAYERS ) + { + for (int i = 0; i < m_iNumPlayers; i++) + m_iClients[i] = READ_SHORT(); + } + + RecalculateText(); + + return 1; +} + +//=========================================================== +// Message handler. Gets the Ids of the players in the round. +void CDiscArenaPanel::GetClientList( char *pszString ) +{ + strcpy( pszString, "" ); + for (int i = 0; i < m_iNumPlayers; i++ ) + { + if ( m_iClients[i] <= 0 || m_iClients[i] > MAX_PLAYERS ) + { + gEngfuncs.Con_Printf( "Combatant %d out of range: %d\n", i, m_iClients[i] ); + continue; + } + + if ( g_PlayerInfoList[ m_iClients[i] ].name && g_PlayerInfoList[ m_iClients[i] ].name[0] ) + { + if ( i > 0 ) + { + if ( i == (m_iNumPlayers - 1) ) + { + strcat( pszString, CHudTextMessage::BufferedLocaliseTextString( "#And" ) ); + } + else + { + strcat( pszString, ", " ); + } + } + + strcat( pszString, g_PlayerInfoList[ m_iClients[i] ].name ); + } + } +} + +//=========================================================== +// Round start window +#define ROUND_Y YRES(0) +#define TEAMONE_Y (ROUND_Y + YRES(32)) +#define VERSUS_Y (TEAMONE_Y + YRES(32)) +#define TEAMTWO_Y (VERSUS_Y + YRES(32)) + +CDiscArena_RoundStart::CDiscArena_RoundStart( void ) : CDiscArenaPanel( ARENAWINDOW_X, ARENAWINDOW_Y, ARENAWINDOW_SIZE_X, ARENAWINDOW_SIZE_Y ) +{ + m_pRound = new Label( "Round 1", 0, ROUND_Y, getWide(), YRES(32) ); + m_pRound->setParent( this ); + m_pRound->setBgColor( 0, 0, 0, 128 ); + m_pRound->setFgColor( 255,255,255, 0 ); + m_pRound->setContentAlignment( vgui::Label::a_center ); + + m_pTeamOne = new Label( "Team One", 0, TEAMONE_Y, getWide(), YRES(32) ); + m_pTeamOne->setParent( this ); + m_pTeamOne->setBgColor( 128, 0, 0, 128 ); + m_pTeamOne->setFgColor( 255,255,255, 0 ); + m_pTeamOne->setContentAlignment( vgui::Label::a_center ); + + // Trim the trailing \n from the VS string + char sz[32]; + strcpy( sz, CHudTextMessage::BufferedLocaliseTextString( "#Versus" ) ); + sz[ strlen(sz) - 1 ] = '\0'; + Label *pLabel = new Label( sz, 0, VERSUS_Y, getWide(), YRES(32) ); + pLabel->setParent( this ); + pLabel->setBgColor( 0, 0, 0, 255 ); + pLabel->setFgColor( 255,255,255, 0 ); + pLabel->setContentAlignment( vgui::Label::a_center ); + + m_pTeamTwo = new Label( "Team Two", 0, TEAMTWO_Y, getWide(), YRES(32) ); + m_pTeamTwo->setParent( this ); + m_pTeamTwo->setBgColor( 0, 0, 128, 128 ); + m_pTeamTwo->setFgColor( 255,255,255, 0 ); + m_pTeamTwo->setContentAlignment( vgui::Label::a_center ); + + setVisible(false); +} + +// Recalculate the Text in the window +void CDiscArena_RoundStart::RecalculateText( void ) +{ + char sz[1024]; + char szOpponents[1024]; + char szTemp[256]; + char szTemp2[256]; + char szTemp3[256]; + char *pszLocalized = NULL; + + // Round started? + if (m_iSecondsToGo == 0) + { + setVisible(false); + g_iCannotFire = FALSE; + + // Force spectator menu to update + if (gViewPort) + gViewPort->m_iUser1 = 0; + return; + } + + g_iCannotFire = TRUE; + + // Round Number + if ( m_iSecondsToGo != 1 ) + { + pszLocalized = "#Round_Start_n_Seconds"; + } + else + { + pszLocalized = "#Round_Start_1_Second"; + } + + strncpy( szTemp3, CHudTextMessage::BufferedLocaliseTextString( pszLocalized ), sizeof( szTemp3 ) - 1 ); + szTemp3[ sizeof( szTemp3 ) - 1 ] = '\0'; + sprintf( sz, szTemp3, m_iRoundNumber, m_iSecondsToGo ); + + m_pRound->setText( sz ); + + // We may have just got an update for the time to go. If so, m_iNumPlayers will be 0. + if ( !m_iNumPlayers ) + return; + + if (gViewPort) + gViewPort->GetAllPlayersInfo(); + + // Find out what team this client's on (if a new battle's just starting) + strcpy( szOpponents, "" ); + int iMyTeamNumber = 0; + if ( m_iRoundNumber == 1 ) + { + for (int i = 0; i < m_iNumPlayers; i++ ) + { + if ( g_PlayerInfoList[ m_iClients[i] ].thisplayer ) + iMyTeamNumber = (i < (m_iNumPlayers / 2)) ? 1 : 2; + } + } + + // Team 1 + strcpy( sz, "" ); + int i; + for ( i = 0; i < (m_iNumPlayers / 2); i++ ) + { + if ( g_PlayerInfoList[ m_iClients[i] ].name && g_PlayerInfoList[ m_iClients[i] ].name[0] ) + strcat( sz, g_PlayerInfoList[ m_iClients[i] ].name ); + + if ( iMyTeamNumber == 2 ) + { + strcpy( szTemp, CHudTextMessage::BufferedLocaliseTextString( "#Opponent" ) ); + sprintf( szTemp2, szTemp, g_PlayerInfoList[ m_iClients[i] ].name, g_PlayerExtraInfo[ m_iClients[i] ].deaths, g_PlayerExtraInfo[ m_iClients[i] ].frags ); + strcat( szOpponents, szTemp2 ); + } + } + m_pTeamOne->setText( sz ); + + // Team 2 + strcpy( sz, "" ); + for ( ; i < m_iNumPlayers; i++ ) + { + if ( g_PlayerInfoList[ m_iClients[i] ].name && g_PlayerInfoList[ m_iClients[i] ].name[0] ) + strcat( sz, g_PlayerInfoList[ m_iClients[i] ].name ); + + if ( iMyTeamNumber == 1 ) + { + strcpy( szTemp, CHudTextMessage::BufferedLocaliseTextString( "#Opponent" ) ); + sprintf( szTemp2, szTemp, g_PlayerInfoList[ m_iClients[i] ].name, g_PlayerExtraInfo[ m_iClients[i] ].deaths, g_PlayerExtraInfo[ m_iClients[i] ].frags ); + strcat( szOpponents, szTemp2 ); + } + } + m_pTeamTwo->setText( sz ); + + // Bring up the Opponent details + if (gViewPort) + gViewPort->m_pDiscRewardWindow->SetMessage( szOpponents ); + + // Become visible + setVisible(true); + + // Hide the other windows if it's up + if (gViewPort) + { + gViewPort->m_pSpectatorMenu->setVisible( false ); + gViewPort->m_pDiscPowerupWindow->setVisible( false ); + gViewPort->m_pDiscEndRound->setVisible( false ); + } +} + +//=========================================================== +// Round end window +CDiscArena_RoundEnd::CDiscArena_RoundEnd( void ) : CDiscArenaPanel( ARENAWINDOW_X, ARENAWINDOW_Y, ARENAWINDOW_SIZE_X, ARENAWINDOW_SIZE_Y ) +{ + m_pRound = new Label( "Round 1 Won By", 0, ROUND_Y, getWide(), YRES(32) ); + m_pRound->setParent( this ); + m_pRound->setBgColor( 0, 0, 0, 128 ); + m_pRound->setFgColor( 255,255,255, 0 ); + m_pRound->setContentAlignment( vgui::Label::a_center ); + + m_pWinners = new Label( "Winners", 0, TEAMONE_Y, getWide(), YRES(32) ); + m_pWinners->setParent( this ); + m_pWinners->setBgColor( 128, 0, 0, 128 ); + m_pWinners->setFgColor( 255,255,255, 0 ); + m_pWinners->setContentAlignment( vgui::Label::a_center ); + + m_pWinningTeam = new Label( "Winners", 0, TEAMTWO_Y, getWide(), YRES(32) ); + m_pWinningTeam->setParent( this ); + m_pWinningTeam->setBgColor( 128, 0, 0, 128 ); + m_pWinningTeam->setFgColor( 255,255,255, 0 ); + m_pWinningTeam->setContentAlignment( vgui::Label::a_center ); + + setVisible(false); +} + +// Recalculate the Text in the window +void CDiscArena_RoundEnd::RecalculateText( void ) +{ + char sz[1024]; + char szTemp1[256]; + char szTemp2[256]; + + // Sends down a 0 for time when this should be removed + if (m_iSecondsToGo == 0) + { + setVisible(false); + g_iCannotFire = FALSE; + + // Force spectator menu to update + if (gViewPort) + gViewPort->m_iUser1 = 0; + return; + } + + g_iCannotFire = TRUE; + + // Round Number + strncpy( szTemp1, CHudTextMessage::BufferedLocaliseTextString( "#Round_Won" ), sizeof( szTemp1 ) - 1 ); + szTemp1[ sizeof( szTemp1 ) - 1 ] = '\0'; + sprintf( sz, szTemp1, m_iRoundNumber ); + + m_pRound->setText( sz ); + + if (gViewPort) + gViewPort->GetAllPlayersInfo(); + + // Winners + GetClientList( sz ); + m_pWinners->setText( sz ); + + // Scores + m_iNumPlayers = READ_BYTE(); + if ( m_iNumPlayers >= 0 && m_iNumPlayers <= MAX_PLAYERS ) + { + for (int i = 0; i < m_iNumPlayers; i++) + m_iClients[i] = READ_SHORT(); + + int iWinningScore = READ_BYTE(); + int iLosingScore = READ_BYTE(); + int iBattleOver = READ_BYTE(); + + // Battle over? + if ( iBattleOver ) + { + GetClientList( sz ); + + strncpy( szTemp2, CHudTextMessage::BufferedLocaliseTextString( "#Round_Won_Scores" ), sizeof( szTemp2 ) - 1 ); + szTemp2[ sizeof( szTemp2 ) - 1 ] = '\0'; + + _snprintf( sz, sizeof( sz ) - 1, szTemp2, sz, iWinningScore, iLosingScore ); + } + // Tied? + else if ( iWinningScore == iLosingScore ) + { + strncpy( szTemp2, CHudTextMessage::BufferedLocaliseTextString( "#Round_Tied" ), sizeof( szTemp2 ) - 1 ); + szTemp2[ sizeof( szTemp2 ) - 1 ] = '\0'; + + _snprintf( sz, sizeof( sz ) - 1, szTemp2, iWinningScore ); + } + else + { + char *pszTemp = NULL; + + GetClientList( sz ); + + if ( m_iNumPlayers == 1 ) + { + pszTemp = "#Round_Leads"; + } + else + { + pszTemp = "#Round_Lead"; + } + + strncpy( szTemp2, CHudTextMessage::BufferedLocaliseTextString( pszTemp ), sizeof( szTemp2 ) - 1 ); + szTemp2[ sizeof( szTemp2 ) - 1 ] = '\0'; + + _snprintf( sz, sizeof( sz ) - 1, szTemp2, sz, iWinningScore, iLosingScore ); + } + + sz[ sizeof( sz ) - 1 ] = '\0'; + m_pWinningTeam->setText( sz ); + } + + // Become visible + setVisible(true); + + // Hide the other windows if it's up + if (gViewPort) + { + gViewPort->m_pSpectatorMenu->setVisible( false ); + gViewPort->m_pDiscPowerupWindow->setVisible( false ); + gViewPort->m_pDiscStartRound->setVisible( false ); + } +} + +//=========================================================== +// Powerup name window +CDiscPowerups::CDiscPowerups() : CTransparentPanel( 255, POWERUP_X, POWERUP_Y, POWERUP_SIZE_X, POWERUP_SIZE_Y ) +{ + m_pLabel = new Label( "Powerups Go Here", 0, ROUND_Y, getWide(), YRES(32) ); + m_pLabel->setParent( this ); + m_pLabel->setBgColor( 0, 0, 0, 255 ); + m_pLabel->setFgColor( 255,255,255, 0 ); + m_pLabel->setContentAlignment( vgui::Label::a_center ); + setVisible(false); +}; + +void CDiscPowerups::RecalculateText( int iPowerup ) +{ + char sz[512]; + bool bAnd = false; + + // Don't appear if a round message is up + if (gViewPort) + { + if ( gViewPort->m_pDiscStartRound->isVisible() || gViewPort->m_pDiscEndRound->isVisible() ) + return; + } + + sprintf(sz, ""); + + if ( iPowerup & POW_TRIPLE ) + { + strcat(sz, CHudTextMessage::BufferedLocaliseTextString("#Triple") ); + bAnd = true; + } + + if ( iPowerup & POW_FAST ) + { + if (bAnd) + strcat(sz, ", "); + strcat(sz, CHudTextMessage::BufferedLocaliseTextString("#Fast") ); + bAnd = true; + } + + if ( iPowerup & POW_FREEZE ) + { + if (bAnd) + strcat(sz, ", "); + strcat(sz, CHudTextMessage::BufferedLocaliseTextString("#Freeze") ); + bAnd = true; + } + + if ( iPowerup & POW_HARD ) + { + if (bAnd) + strcat(sz, ", "); + strcat(sz, CHudTextMessage::BufferedLocaliseTextString("#Hard") ); + } + + m_pLabel->setText( sz ); + + // Become visible + if (sz && sz[0]) + setVisible(true); + else + setVisible(false); +} + +//=========================================================== +// Reward menu +CDiscRewards::CDiscRewards() : CTransparentPanel( 255, REWARD_X, REWARD_Y, REWARD_SIZE_X, REWARD_SIZE_Y ) +{ + m_pReward = new Label( "Well Done!", 0, ROUND_Y, getWide(), (REWARD_SIZE_Y / 2) ); + m_pReward->setParent( this ); + m_pReward->setBgColor( 0, 0, 0, 255 ); + m_pReward->setFgColor( 255,255,255, 0 ); + m_pReward->setContentAlignment( vgui::Label::a_center ); + setVisible(false); + + m_pTeleBonus = new Label( CHudTextMessage::BufferedLocaliseTextString( "#Hit_Tele" ), 0, (REWARD_SIZE_Y / 2), getWide(), (REWARD_SIZE_Y / 2) ); + m_pTeleBonus->setParent( this ); + m_pTeleBonus->setBgColor( 0, 0, 0, 255 ); + m_pTeleBonus->setFgColor( 255,255,255, 0 ); + m_pTeleBonus->setContentAlignment( vgui::Label::a_center ); +}; + +void CDiscRewards::RecalculateText( int iReward ) +{ + char sz[512]; + + // Don't appear if a round message is up + if (gViewPort) + { + if ( gViewPort->m_pDiscStartRound->isVisible() || gViewPort->m_pDiscEndRound->isVisible() ) + return; + } + + if ( !iReward ) + { + setVisible( false ); + return; + } + + // Rewards + if ( iReward & REWARD_BOUNCE_NONE ) + sprintf( sz, CHudTextMessage::BufferedLocaliseTextString( "#Hit_Direct" ) ); + if ( iReward & REWARD_BOUNCE_ONE ) + sprintf( sz, CHudTextMessage::BufferedLocaliseTextString( "#Hit_One" ) ); + if ( iReward & REWARD_BOUNCE_TWO ) + sprintf( sz, CHudTextMessage::BufferedLocaliseTextString( "#Hit_Two" ) ); + if ( iReward & REWARD_BOUNCE_THREE ) + sprintf( sz, CHudTextMessage::BufferedLocaliseTextString( "#Hit_Three" ) ); + if ( iReward & REWARD_DECAPITATE ) + sprintf( sz, CHudTextMessage::BufferedLocaliseTextString( "#Hit_Decap" ) ); + if ( iReward & REWARD_DOUBLEKILL ) + sprintf( sz, CHudTextMessage::BufferedLocaliseTextString( "#Hit_Multiple" ) ); + + if ( iReward & REWARD_TELEPORT ) + m_pTeleBonus->setVisible( true ); + else + m_pTeleBonus->setVisible( false ); + + m_pReward->setText( sz ); + setVisible( true ); +} + +void CDiscRewards::SetMessage( char *pMessage ) +{ + if (!pMessage) + { + setVisible(false); + return; + } + + m_pTeleBonus->setVisible( false ); + m_pReward->setText( pMessage ); + setVisible( true ); + + if (gViewPort) + gViewPort->m_flRewardOpenTime = gHUD.m_flTime + 5.0; +} \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/vgui_discobjects.h b/hlsdk/ricochet/cl_dll/vgui_discobjects.h new file mode 100644 index 00000000..0290a59e --- /dev/null +++ b/hlsdk/ricochet/cl_dll/vgui_discobjects.h @@ -0,0 +1,109 @@ +//=========== (C) Copyright 1999 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: VGUI objects for Discwar +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_DISCOBJECTS_H +#define VGUI_DISCOBJECTS_H +#pragma once + +//=========================================================== +// Disc ammo icon +class CDiscPanel : public Label +{ +private: + BitmapTGA *m_pDiscTGA_Red; + BitmapTGA *m_pDiscTGA_RedGlow; + BitmapTGA *m_pDiscTGA_Blue; + BitmapTGA *m_pDiscTGA_BlueGlow; + BitmapTGA *m_pDiscTGA_Grey; + BitmapTGA *m_pDiscTGA_Fast; + BitmapTGA *m_pDiscTGA_Freeze; + BitmapTGA *m_pDiscTGA_Hard; + BitmapTGA *m_pDiscTGA_Triple; +public: + CDiscPanel(int x,int y,int wide,int tall); + void Update( int iDiscNo, bool bGlow, int iPowerup ); + + virtual void paintBackground() + { + // Do nothing, so the background's left transparent. + } +}; + +//=========================================================== +// Powerup +class CDiscPowerups : public CTransparentPanel +{ +public: + CDiscPowerups(); + + void RecalculateText( int iPowerup ); + Label *m_pLabel; +}; + +class CDiscRewards : public CTransparentPanel +{ +public: + CDiscRewards(); + + void RecalculateText( int iReward ); + void SetMessage( char *pMessage ); + Label *m_pReward; + Label *m_pTeleBonus; +}; + +//=========================================================== +// Arena windows +class CDiscArenaPanel : public CTransparentPanel +{ +public: + CDiscArenaPanel( int x, int y, int wide, int tall ); + int MsgFunc_GetPlayers(const char *pszName, int iSize, void *pbuf ); + virtual void RecalculateText( void ) {}; + void GetClientList( char *pszString ); + + int m_iNumPlayers; + int m_iClients[ MAX_PLAYERS ]; + int m_iRoundNumber; + int m_iSecondsToGo; +}; + +class CDiscArena_RoundStart : public CDiscArenaPanel +{ +public: + CDiscArena_RoundStart(); + + void RecalculateText( void ); + + Label *m_pRound; + Label *m_pTeamOne; + Label *m_pTeamTwo; +}; + +class CDiscArena_RoundEnd : public CDiscArenaPanel +{ +public: + CDiscArena_RoundEnd(); + + void RecalculateText( void ); + + Label *m_pRound; + Label *m_pWinners; + Label *m_pWinningTeam; +}; + +#endif // VGUI_DISCOBJECTS_H diff --git a/hlsdk/ricochet/cl_dll/vgui_int.cpp b/hlsdk/ricochet/cl_dll/vgui_int.cpp new file mode 100644 index 00000000..cb305615 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/vgui_int.cpp @@ -0,0 +1,122 @@ + +#include"vgui_int.h" +#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 "vgui_TeamFortressViewport.h" +#include "vgui_ControlConfigPanel.h" + +namespace +{ + +class TexturePanel : public Panel , public ActionSignal +{ +private: + int _bindIndex; + TextEntry* _textEntry; +public: + TexturePanel() : Panel(0,0,256,276) + { + _bindIndex=2700; + _textEntry=new TextEntry("2700",0,0,128,20); + _textEntry->setParent(this); + _textEntry->addActionSignal(this); + } +public: + virtual bool isWithin(int x,int y) + { + return _textEntry->isWithin(x,y); + } +public: + virtual void actionPerformed(Panel* panel) + { + char buf[256]; + _textEntry->getText(0,buf,256); + sscanf(buf,"%d",&_bindIndex); + } +protected: + virtual void paintBackground() + { + Panel::paintBackground(); + + int wide,tall; + getPaintSize(wide,tall); + + drawSetColor(0,0,255,0); + drawSetTexture(_bindIndex); + drawTexturedRect(0,19,257,257); + } + +}; + +} + +using namespace vgui; + +void VGui_ViewportPaintBackground(int extents[4]) +{ + gEngfuncs.VGui_ViewportPaintBackground(extents); +} + +void* VGui_GetPanel() +{ + return (Panel*)gEngfuncs.VGui_GetPanel(); +} + +void VGui_Startup() +{ + Panel* root=(Panel*)VGui_GetPanel(); + root->setBgColor(128,128,0,0); + //root->setNonPainted(false); + //root->setBorder(new LineBorder()); + root->setLayout(new BorderLayout(0)); + + + //root->getSurfaceBase()->setEmulatedCursorVisible(true); + + if (gViewPort != NULL) + { +// root->removeChild(gViewPort); + + // free the memory +// delete gViewPort; +// gViewPort = NULL; + + gViewPort->Initialize(); + } + else + { + gViewPort = new TeamFortressViewport(0,0,root->getWide(),root->getTall()); + gViewPort->setParent(root); + } + + /* + TexturePanel* texturePanel=new TexturePanel(); + texturePanel->setParent(gViewPort); + */ + +} + +void VGui_Shutdown() +{ + delete gViewPort; + gViewPort = NULL; +} + + + + + diff --git a/hlsdk/ricochet/cl_dll/vgui_int.h b/hlsdk/ricochet/cl_dll/vgui_int.h new file mode 100644 index 00000000..a90f80c2 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/vgui_int.h @@ -0,0 +1,15 @@ + +#ifndef VGUI_INT_H +#define VGUI_INT_H + +extern "C" +{ +void VGui_Startup(); +void VGui_Shutdown(); + +//Only safe to call from inside subclass of Panel::paintBackground +void VGui_ViewportPaintBackground(int extents[4]); +} + + +#endif \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/view.cpp b/hlsdk/ricochet/cl_dll/view.cpp new file mode 100644 index 00000000..3e726697 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/view.cpp @@ -0,0 +1,1061 @@ +// view/refresh setup functions + +#include "hud.h" +#include "cl_util.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" + +#include "entity_state.h" +#include "cl_entity.h" +#include "ref_params.h" +#include "in_defs.h" // PITCH YAW ROLL +#include "pm_movevars.h" +#include "pm_shared.h" +#include "pmtrace.h" +#include "screenfade.h" +#include "shake.h" + +// Spectator Mode +extern "C" +{ + float vecNewViewAngles[3]; + int iHasNewViewAngles; + float vecNewViewOrigin[3]; + int iHasNewViewOrigin; + int iIsSpectator; +} + +extern float g_flStartScaleTime; +extern int iMouseInUse; +void CAM_ToThirdPerson(void); +void CAM_ToFirstPerson(void); + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +extern "C" +{ + int CL_IsThirdPerson( void ); + void CL_CameraOffset( float *ofs ); + + void DLLEXPORT V_CalcRefdef( struct ref_params_s *pparams ); + + void PM_ParticleLine( float *start, float *end, int pcolor, float life, float vert); + int PM_GetInfo( int ent ); + +} + +void V_DropPunchAngle ( float frametime, float *ev_punchangle ); +void VectorAngles( const float *forward, float *angles ); + +/* +The view is allowed to move slightly from it's true position for bobbing, +but if it exceeds 8 pixels linear distance (spherical, not box), the list of +entities sent from the server may not include everything in the pvs, especially +when crossing a water boudnary. +*/ + +extern cvar_t *cl_forwardspeed; +extern cvar_t *chase_active; +extern cvar_t *scr_ofsx, *scr_ofsy, *scr_ofsz; +extern cvar_t *cl_vsmoothing; + +vec3_t v_origin, v_angles; + +vec3_t ev_punchangle; + +cvar_t *scr_ofsx; +cvar_t *scr_ofsy; +cvar_t *scr_ofsz; + +cvar_t *v_centermove; +cvar_t *v_centerspeed; + +cvar_t *cl_bobcycle; +cvar_t *cl_bob; +cvar_t *cl_bobup; +cvar_t *cl_waterdist; + +// These cvars are not registered (so users can't cheat), so set the ->value field directly +// Register these cvars in V_Init() if needed for easy tweaking +cvar_t v_iyaw_cycle = {"v_iyaw_cycle", "2", 0, 2}; +cvar_t v_iroll_cycle = {"v_iroll_cycle", "0.5", 0, 0.5}; +cvar_t v_ipitch_cycle = {"v_ipitch_cycle", "1", 0, 1}; +cvar_t v_iyaw_level = {"v_iyaw_level", "0.3", 0, 0.3}; +cvar_t v_iroll_level = {"v_iroll_level", "0.1", 0, 0.1}; +cvar_t v_ipitch_level = {"v_ipitch_level", "0.3", 0, 0.3}; + +float v_idlescale; // used by TFC for concussion grenade effect + +//============================================================================= +void V_NormalizeAngles( float *angles ) +{ + int i; + // Normalize angles + for ( i = 0; i < 3; i++ ) + { + if ( angles[i] > 180.0 ) + { + angles[i] -= 360.0; + } + else if ( angles[i] < -180.0 ) + { + angles[i] += 360.0; + } + } +} + +/* +=================== +V_InterpolateAngles + +Interpolate Euler angles. +FIXME: Use Quaternions to avoid discontinuities +Frac is 0.0 to 1.0 ( i.e., should probably be clamped, but doesn't have to be ) +=================== +*/ +void V_InterpolateAngles( float *start, float *end, float *output, float frac ) +{ + int i; + float ang1, ang2; + float d; + + V_NormalizeAngles( start ); + V_NormalizeAngles( end ); + + for ( i = 0 ; i < 3 ; i++ ) + { + ang1 = start[i]; + ang2 = end[i]; + + d = ang2 - ang1; + if ( d > 180 ) + { + d -= 360; + } + else if ( d < -180 ) + { + d += 360; + } + + output[i] = ang1 + d * frac; + } + + V_NormalizeAngles( output ); +} + +// Quakeworld bob code, this fixes jitters in the mutliplayer since the clock (pparams->time) isn't quite linear +float V_CalcBob ( struct ref_params_s *pparams ) +{ + static double bobtime; + static float bob; + float cycle; + static float lasttime; + vec3_t vel; + + if ( pparams->spectator || iIsSpectator ) + return 0; + + if ( pparams->onground == -1 || + pparams->time == lasttime ) + { + // just use old value + return bob; + } + + lasttime = pparams->time; + + bobtime += pparams->frametime; + cycle = bobtime - (int)( bobtime / cl_bobcycle->value ) * cl_bobcycle->value; + cycle /= cl_bobcycle->value; + + if ( cycle < cl_bobup->value ) + { + cycle = M_PI * cycle / cl_bobup->value; + } + else + { + cycle = M_PI + M_PI * ( cycle - cl_bobup->value )/( 1.0 - cl_bobup->value ); + } + + // bob is proportional to simulated velocity in the xy plane + // (don't count Z, or jumping messes it up) + VectorCopy( pparams->simvel, vel ); + vel[2] = 0; + + bob = sqrt( vel[0] * vel[0] + vel[1] * vel[1] ) * cl_bob->value; + bob = bob * 0.3 + bob * 0.7 * sin(cycle); + bob = min( bob, 4 ); + bob = max( bob, -7 ); + return bob; + +} + +/* +=============== +V_CalcRoll +Used by view and sv_user +=============== +*/ +float V_CalcRoll (vec3_t angles, vec3_t velocity, float rollangle, float rollspeed ) +{ + float sign; + float side; + float value; + vec3_t forward, right, up; + + AngleVectors ( angles, forward, right, up ); + + side = DotProduct (velocity, right); + sign = side < 0 ? -1 : 1; + side = fabs( side ); + + value = rollangle; + if (side < rollspeed) + { + side = side * value / rollspeed; + } + else + { + side = value; + } + return side * sign; +} + +typedef struct pitchdrift_s +{ + float pitchvel; + int nodrift; + float driftmove; + double laststop; +} pitchdrift_t; + +static pitchdrift_t pd; + +void V_StartPitchDrift( void ) +{ + if ( pd.laststop == gEngfuncs.GetClientTime() ) + { + return; // something else is keeping it from drifting + } + + if ( pd.nodrift || !pd.pitchvel ) + { + pd.pitchvel = v_centerspeed->value; + pd.nodrift = 0; + pd.driftmove = 0; + } +} + +void V_StopPitchDrift ( void ) +{ + pd.laststop = gEngfuncs.GetClientTime(); + pd.nodrift = 1; + pd.pitchvel = 0; +} + +/* +=============== +V_DriftPitch + +Moves the client pitch angle towards idealpitch sent by the server. + +If the user is adjusting pitch manually, either with lookup/lookdown, +mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped. +=============== +*/ +void V_DriftPitch ( struct ref_params_s *pparams ) +{ + float delta, move; + + if ( gEngfuncs.IsNoClipping() || !pparams->onground || pparams->demoplayback || pparams->spectator ) + { + pd.driftmove = 0; + pd.pitchvel = 0; + return; + } + + // don't count small mouse motion + if (pd.nodrift) + { + if ( fabs( pparams->cmd->forwardmove ) < cl_forwardspeed->value ) + pd.driftmove = 0; + else + pd.driftmove += pparams->frametime; + + if ( pd.driftmove > v_centermove->value) + { + V_StartPitchDrift (); + } + return; + } + + delta = pparams->idealpitch - pparams->cl_viewangles[PITCH]; + + if (!delta) + { + pd.pitchvel = 0; + return; + } + + move = pparams->frametime * pd.pitchvel; + pd.pitchvel += pparams->frametime * v_centerspeed->value; + +//Con_Printf ("move: %f (%f)\n", move, pparams->frametime); + + if (delta > 0) + { + if (move > delta) + { + pd.pitchvel = 0; + move = delta; + } + pparams->cl_viewangles[PITCH] += move; + } + else if (delta < 0) + { + if (move > -delta) + { + pd.pitchvel = 0; + move = -delta; + } + pparams->cl_viewangles[PITCH] -= move; + } +} + +/* +============================================================================== + VIEW RENDERING +============================================================================== +*/ + +/* +================== +V_CalcGunAngle +================== +*/ +void V_CalcGunAngle ( struct ref_params_s *pparams ) +{ + cl_entity_t *viewent; + + viewent = gEngfuncs.GetViewModel(); + if ( !viewent ) + return; + + viewent->angles[YAW] = pparams->viewangles[YAW] + pparams->crosshairangle[YAW]; + viewent->angles[PITCH] = -pparams->viewangles[PITCH] + pparams->crosshairangle[PITCH] * 0.25; + viewent->angles[ROLL] -= v_idlescale * sin(pparams->time*v_iroll_cycle.value) * v_iroll_level.value; + + // don't apply all of the v_ipitch to prevent normally unseen parts of viewmodel from coming into view. + viewent->angles[PITCH] -= v_idlescale * sin(pparams->time*v_ipitch_cycle.value) * (v_ipitch_level.value * 0.5); + viewent->angles[YAW] -= v_idlescale * sin(pparams->time*v_iyaw_cycle.value) * v_iyaw_level.value; + + VectorCopy( viewent->angles, viewent->curstate.angles ); + VectorCopy( viewent->angles, viewent->latched.prevangles ); +} + +/* +============== +V_AddIdle + +Idle swaying +============== +*/ +void V_AddIdle ( struct ref_params_s *pparams ) +{ + pparams->viewangles[ROLL] += v_idlescale * sin(pparams->time*v_iroll_cycle.value) * v_iroll_level.value; + pparams->viewangles[PITCH] += v_idlescale * sin(pparams->time*v_ipitch_cycle.value) * v_ipitch_level.value; + pparams->viewangles[YAW] += v_idlescale * sin(pparams->time*v_iyaw_cycle.value) * v_iyaw_level.value; +} + + +/* +============== +V_CalcViewRoll + +Roll is induced by movement and damage +============== +*/ +void V_CalcViewRoll ( struct ref_params_s *pparams ) +{ + float side; + cl_entity_t *viewentity; + + viewentity = gEngfuncs.GetEntityByIndex( pparams->viewentity ); + if ( !viewentity ) + return; + + side = V_CalcRoll ( viewentity->angles, pparams->simvel, pparams->movevars->rollangle, pparams->movevars->rollspeed ); + + pparams->viewangles[ROLL] += side; + + if ( pparams->health <= 0 && ( pparams->viewheight[2] != 0 ) ) + { + // only roll the view if the player is dead and the viewheight[2] is nonzero + // this is so deadcam in multiplayer will work. + pparams->viewangles[ROLL] = 80; // dead view angle + return; + } +} + + +/* +================== +V_CalcIntermissionRefdef + +================== +*/ +void V_CalcIntermissionRefdef ( struct ref_params_s *pparams ) +{ + cl_entity_t *ent, *view; + float old; + +// don't allow cheats in multiplayer +#if !defined( _DEBUG ) + if ( pparams->maxclients > 1 ) + { + gEngfuncs.Cvar_SetValue ("scr_ofsx", 0); + gEngfuncs.Cvar_SetValue ("scr_ofsy", 0); + gEngfuncs.Cvar_SetValue ("scr_ofsz", 0); + } +#endif + + // ent is the player model ( visible when out of body ) + ent = gEngfuncs.GetLocalPlayer(); + + // view is the weapon model (only visible from inside body ) + view = gEngfuncs.GetViewModel(); + + VectorCopy ( pparams->simorg, pparams->vieworg ); + VectorCopy ( pparams->cl_viewangles, pparams->viewangles ); + + view->model = NULL; + + // allways idle in intermission + old = v_idlescale; + v_idlescale = 1; + + V_AddIdle ( pparams ); + + v_idlescale = old; + + v_origin = pparams->vieworg; + v_angles = pparams->viewangles; +} + +#define ORIGIN_BACKUP 64 +#define ORIGIN_MASK ( ORIGIN_BACKUP - 1 ) + +typedef struct +{ + float Origins[ ORIGIN_BACKUP ][3]; + float OriginTime[ ORIGIN_BACKUP ]; + + float Angles[ ORIGIN_BACKUP ][3]; + float AngleTime[ ORIGIN_BACKUP ]; + + int CurrentOrigin; + int CurrentAngle; +} viewinterp_t; + +/* +================== +V_CalcRefdef + +================== +*/ +void V_CalcNormalRefdef ( struct ref_params_s *pparams ) +{ + cl_entity_t *ent, *view; + int i; + vec3_t angles; + float bob, waterOffset; + static viewinterp_t ViewInterp; + + static float oldz = 0; + static float lasttime; + + static float lastang[3]; + vec3_t angdelta; + + vec3_t camAngles, camForward, camRight, camUp; + cl_entity_t *pwater; + + // don't allow cheats in multiplayer + if ( pparams->maxclients > 1 ) + { + scr_ofsx->value = 0.0; + scr_ofsy->value = 0.0; + scr_ofsz->value = 0.0; + } + + + V_DriftPitch ( pparams ); + + // ent is the player model ( visible when out of body ) + ent = gEngfuncs.GetLocalPlayer(); + + // view is the weapon model (only visible from inside body ) + view = gEngfuncs.GetViewModel(); + + // transform the view offset by the model's matrix to get the offset from + // model origin for the view + bob = V_CalcBob ( pparams ); + + // Observer angle capturing and smoothing + if ( iHasNewViewOrigin ) + { + // Get the angles from the physics code + VectorCopy( vecNewViewOrigin, pparams->vieworg ); + VectorCopy( vecNewViewOrigin, pparams->simorg ); + } + + // refresh position + VectorCopy ( pparams->simorg, pparams->vieworg ); + pparams->vieworg[2] += ( bob ); + VectorAdd( pparams->vieworg, pparams->viewheight, pparams->vieworg ); + + // Observer angle capturing and smoothing + if ( iHasNewViewAngles ) + { + // Get the angles from the physics code + VectorCopy( vecNewViewAngles, pparams->cl_viewangles ); + } + else if ( pparams->health == -5 ) + { + CAM_ToThirdPerson(); + + // Lock mouse movement + iMouseInUse=1; + + pparams->cl_viewangles[0] = 89; + + // Spin the view + float flTimeDelta = (pparams->time - g_flStartScaleTime); + if ( flTimeDelta > 0 ) + { + float flROFSpin = 1.0 + (flTimeDelta * 2.0); + float flSpin = flTimeDelta * 45; + + pparams->cl_viewangles[1] = flSpin * flROFSpin; + } + } + else + { + CAM_ToFirstPerson(); + + // Unlock mouse movement + iMouseInUse=0; + } + + VectorSubtract( pparams->cl_viewangles, lastang, angdelta ); + if ( Length( angdelta ) != 0.0 ) + { + VectorCopy( pparams->cl_viewangles, ViewInterp.Angles[ ViewInterp.CurrentAngle & ORIGIN_MASK ] ); + ViewInterp.AngleTime[ ViewInterp.CurrentAngle & ORIGIN_MASK ] = pparams->time; + ViewInterp.CurrentAngle++; + + VectorCopy( pparams->cl_viewangles, lastang ); + } + + if ( cl_vsmoothing && cl_vsmoothing->value && ( iIsSpectator & SPEC_SMOOTH_ANGLES ) ) + { + int foundidx; + int i; + float t; + + if ( cl_vsmoothing->value < 0.0 ) + { + gEngfuncs.Cvar_SetValue( "cl_vsmoothing", 0.0 ); + } + + t = pparams->time - cl_vsmoothing->value; + + for ( i = 1; i < ORIGIN_MASK; i++ ) + { + foundidx = ViewInterp.CurrentAngle - 1 - i; + if ( ViewInterp.AngleTime[ foundidx & ORIGIN_MASK ] <= t ) + break; + } + + if ( i < ORIGIN_MASK && ViewInterp.AngleTime[ foundidx & ORIGIN_MASK ] != 0.0 ) + { + // Interpolate + double dt; + + dt = ViewInterp.AngleTime[ (foundidx + 1) & ORIGIN_MASK ] - ViewInterp.AngleTime[ foundidx & ORIGIN_MASK ]; + if ( dt > 0.0 ) + { + double frac; + + frac = ( t - ViewInterp.AngleTime[ foundidx & ORIGIN_MASK] ) / dt; + frac = min( 1.0, frac ); + + // interpolate angles + V_InterpolateAngles( ViewInterp.Angles[ foundidx & ORIGIN_MASK ], ViewInterp.Angles[ (foundidx + 1) & ORIGIN_MASK ], pparams->cl_viewangles, frac ); + + VectorCopy( pparams->cl_viewangles, vecNewViewAngles ); + } + } + } + + VectorCopy ( pparams->cl_viewangles, pparams->viewangles ); + + gEngfuncs.V_CalcShake(); + gEngfuncs.V_ApplyShake( pparams->vieworg, pparams->viewangles, 1.0 ); + + // never let view origin sit exactly on a node line, because a water plane can + // dissapear when viewed with the eye exactly on it. + // FIXME, we send origin at 1/128 now, change this? + // the server protocol only specifies to 1/16 pixel, so add 1/32 in each axis + + pparams->vieworg[0] += 1.0/32; + pparams->vieworg[1] += 1.0/32; + pparams->vieworg[2] += 1.0/32; + + // Check for problems around water, move the viewer artificially if necessary + // -- this prevents drawing errors in GL due to waves + + waterOffset = 0; + if ( pparams->waterlevel >= 2 ) + { + int i, contents, waterDist, waterEntity; + vec3_t point; + waterDist = cl_waterdist->value; + + if ( pparams->hardware ) + { + waterEntity = gEngfuncs.PM_WaterEntity( pparams->simorg ); + if ( waterEntity >= 0 && waterEntity < pparams->max_entities ) + { + pwater = gEngfuncs.GetEntityByIndex( waterEntity ); + if ( pwater && ( pwater->model != NULL ) ) + { + waterDist += ( pwater->curstate.scale * 16 ); // Add in wave height + } + } + } + else + { + waterEntity = 0; // Don't need this in software + } + + VectorCopy( pparams->vieworg, point ); + + // Eyes are above water, make sure we're above the waves + if ( pparams->waterlevel == 2 ) + { + point[2] -= waterDist; + for ( i = 0; i < waterDist; i++ ) + { + contents = gEngfuncs.PM_PointContents( point, NULL ); + if ( contents > CONTENTS_WATER ) + break; + point[2] += 1; + } + waterOffset = (point[2] + waterDist) - pparams->vieworg[2]; + } + else + { + // eyes are under water. Make sure we're far enough under + point[2] += waterDist; + + for ( i = 0; i < waterDist; i++ ) + { + contents = gEngfuncs.PM_PointContents( point, NULL ); + if ( contents <= CONTENTS_WATER ) + break; + point[2] -= 1; + } + waterOffset = (point[2] - waterDist) - pparams->vieworg[2]; + } + } + + pparams->vieworg[2] += waterOffset; + + V_CalcViewRoll ( pparams ); + + V_AddIdle ( pparams ); + + // offsets + VectorCopy( pparams->cl_viewangles, angles ); + + AngleVectors ( angles, pparams->forward, pparams->right, pparams->up ); + + for ( i=0 ; i<3 ; i++ ) + { + pparams->vieworg[i] += scr_ofsx->value*pparams->forward[i] + scr_ofsy->value*pparams->right[i] + scr_ofsz->value*pparams->up[i]; + } + + // Treating cam_ofs[2] as the distance + if( CL_IsThirdPerson() ) + { + vec3_t ofs; + ofs[0] = ofs[1] = ofs[2] = 0.0; + CL_CameraOffset( (float *)&ofs ); + + VectorCopy( ofs, camAngles ); + camAngles[ ROLL ] = 0; + AngleVectors( camAngles, camForward, camRight, camUp ); + + // Is the player in a falling anim? If so, raise camera above and look down + if ( pparams->health == -5 ) + { + pparams->vieworg[2] += 92; + } + else + { + for ( i = 0; i < 3; i++ ) + { + pparams->vieworg[ i ] += -ofs[2] * camForward[ i ]; + } + + pparams->vieworg[2] += 20; + } + } + + // Give gun our viewangles + VectorCopy ( pparams->cl_viewangles, view->angles ); + + // set up gun position + V_CalcGunAngle ( pparams ); + + // Use predicted origin as view origin. + VectorCopy ( pparams->simorg, view->origin ); + view->origin[2] += ( waterOffset ); + VectorAdd( view->origin, pparams->viewheight, view->origin ); + + // Let the viewmodel shake at about 10% of the amplitude + gEngfuncs.V_ApplyShake( view->origin, view->angles, 0.9 ); + + for ( i = 0; i < 3; i++ ) + { + view->origin[ i ] += bob * 0.4 * pparams->forward[ i ]; + } + view->origin[2] += bob; + + // throw in a little tilt. + view->angles[YAW] -= bob * 0.5; + view->angles[ROLL] -= bob * 1; + view->angles[PITCH] -= bob * 0.3; + + // pushing the view origin down off of the same X/Z plane as the ent's origin will give the + // gun a very nice 'shifting' effect when the player looks up/down. If there is a problem + // with view model distortion, this may be a cause. (SJB). + view->origin[2] -= 1; + + // fudge position around to keep amount of weapon visible + // roughly equal with different FOV + if (pparams->viewsize == 110) + { + view->origin[2] += 1; + } + else if (pparams->viewsize == 100) + { + view->origin[2] += 2; + } + else if (pparams->viewsize == 90) + { + view->origin[2] += 1; + } + else if (pparams->viewsize == 80) + { + view->origin[2] += 0.5; + } + + // Add in the punchangle, if any + VectorAdd ( pparams->viewangles, pparams->punchangle, pparams->viewangles ); + + // Include client side punch, too + VectorAdd ( pparams->viewangles, (float *)&ev_punchangle, pparams->viewangles); + + V_DropPunchAngle ( pparams->frametime, (float *)&ev_punchangle ); + + // smooth out stair step ups +#if 1 + if ( !pparams->smoothing && pparams->onground && pparams->simorg[2] - oldz > 0) + { + float steptime; + + steptime = pparams->time - lasttime; + if (steptime < 0) + //FIXME I_Error ("steptime < 0"); + steptime = 0; + + oldz += steptime * 150; + if (oldz > pparams->simorg[2]) + oldz = pparams->simorg[2]; + if (pparams->simorg[2] - oldz > 18) + oldz = pparams->simorg[2]- 18; + pparams->vieworg[2] += oldz - pparams->simorg[2]; + view->origin[2] += oldz - pparams->simorg[2]; + } + else + { + oldz = pparams->simorg[2]; + } +#endif + + { + static float lastorg[3]; + vec3_t delta; + + VectorSubtract( pparams->simorg, lastorg, delta ); + + if ( Length( delta ) != 0.0 ) + { + VectorCopy( pparams->simorg, ViewInterp.Origins[ ViewInterp.CurrentOrigin & ORIGIN_MASK ] ); + ViewInterp.OriginTime[ ViewInterp.CurrentOrigin & ORIGIN_MASK ] = pparams->time; + ViewInterp.CurrentOrigin++; + + VectorCopy( pparams->simorg, lastorg ); + } + } + + // Smooth out whole view in multiplayer when on trains, lifts + if ( cl_vsmoothing && cl_vsmoothing->value && + ( ( iIsSpectator & SPEC_SMOOTH_ORIGIN ) || (pparams->smoothing && ( pparams->maxclients > 1 ) ) ) ) + { + int foundidx; + int i; + float t; + + if ( cl_vsmoothing->value < 0.0 ) + { + gEngfuncs.Cvar_SetValue( "cl_vsmoothing", 0.0 ); + } + + t = pparams->time - cl_vsmoothing->value; + + for ( i = 1; i < ORIGIN_MASK; i++ ) + { + foundidx = ViewInterp.CurrentOrigin - 1 - i; + if ( ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ] <= t ) + break; + } + + if ( i < ORIGIN_MASK && ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ] != 0.0 ) + { + // Interpolate + vec3_t delta; + double frac; + double dt; + vec3_t neworg; + + dt = ViewInterp.OriginTime[ (foundidx + 1) & ORIGIN_MASK ] - ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ]; + if ( dt > 0.0 ) + { + frac = ( t - ViewInterp.OriginTime[ foundidx & ORIGIN_MASK] ) / dt; + frac = min( 1.0, frac ); + VectorSubtract( ViewInterp.Origins[ ( foundidx + 1 ) & ORIGIN_MASK ], ViewInterp.Origins[ foundidx & ORIGIN_MASK ], delta ); + VectorMA( ViewInterp.Origins[ foundidx & ORIGIN_MASK ], frac, delta, neworg ); + + // Dont interpolate large changes + if ( Length( delta ) < 64 ) + { + VectorSubtract( neworg, pparams->simorg, delta ); + + VectorAdd( pparams->simorg, delta, pparams->simorg ); + VectorAdd( pparams->vieworg, delta, pparams->vieworg ); + VectorAdd( view->origin, delta, view->origin ); + + VectorCopy( pparams->simorg, vecNewViewOrigin ); + } + } + } + } + + // Store off v_angles before munging for third person + v_angles = pparams->viewangles; + + if ( CL_IsThirdPerson() ) + { + VectorCopy( camAngles, pparams->viewangles); + } + + // override all previous settings if the viewent isn't the client + if ( pparams->viewentity > pparams->maxclients ) + { + cl_entity_t *viewentity; + viewentity = gEngfuncs.GetEntityByIndex( pparams->viewentity ); + if ( viewentity ) + { + VectorCopy( viewentity->origin, pparams->vieworg ); + VectorCopy( viewentity->angles, pparams->viewangles ); + + // Store off overridden viewangles + v_angles = pparams->viewangles; + } + } + + lasttime = pparams->time; + + v_origin = pparams->vieworg; +} + +void DLLEXPORT V_CalcRefdef( struct ref_params_s *pparams ) +{ + // intermission / finale rendering + if ( pparams->intermission ) + { + V_CalcIntermissionRefdef ( pparams ); + } + else if ( !pparams->paused ) + { + V_CalcNormalRefdef ( pparams ); + } + +/* +// Example of how to overlay the whole screen with red at 50 % alpha +#define SF_TEST +#if defined SF_TEST + { + screenfade_t sf; + gEngfuncs.pfnGetScreenFade( &sf ); + + sf.fader = 255; + sf.fadeg = 0; + sf.fadeb = 0; + sf.fadealpha = 128; + sf.fadeFlags = FFADE_STAYOUT | FFADE_OUT; + + gEngfuncs.pfnSetScreenFade( &sf ); + } +#endif +*/ +} + +/* +============= +V_DropPunchAngle + +============= +*/ +void V_DropPunchAngle ( float frametime, float *ev_punchangle ) +{ + float len; + + len = VectorNormalize ( ev_punchangle ); + len -= (10.0 + len * 0.5) * frametime; + len = max( len, 0.0 ); + VectorScale ( ev_punchangle, len, ev_punchangle ); +} + +/* +============= +V_PunchAxis + +Client side punch effect +============= +*/ +void V_PunchAxis( int axis, float punch ) +{ + ev_punchangle[ axis ] = punch; +} + +/* +============= +V_Init +============= +*/ +void V_Init (void) +{ + gEngfuncs.pfnAddCommand ("centerview", V_StartPitchDrift ); + + scr_ofsx = gEngfuncs.pfnRegisterVariable( "scr_ofsx","0", 0 ); + scr_ofsy = gEngfuncs.pfnRegisterVariable( "scr_ofsy","0", 0 ); + scr_ofsz = gEngfuncs.pfnRegisterVariable( "scr_ofsz","0", 0 ); + + v_centermove = gEngfuncs.pfnRegisterVariable( "v_centermove", "0.15", 0 ); + v_centerspeed = gEngfuncs.pfnRegisterVariable( "v_centerspeed","500", 0 ); + + cl_bobcycle = gEngfuncs.pfnRegisterVariable( "cl_bobcycle","0.8", 0 );// best default for my experimental gun wag (sjb) + cl_bob = gEngfuncs.pfnRegisterVariable( "cl_bob","0.01", 0 );// best default for my experimental gun wag (sjb) + cl_bobup = gEngfuncs.pfnRegisterVariable( "cl_bobup","0.5", 0 ); + cl_waterdist = gEngfuncs.pfnRegisterVariable( "cl_waterdist","4", 0 ); +} + + +//#define TRACE_TEST +#if defined( TRACE_TEST ) + +extern float in_fov; +/* +==================== +CalcFov +==================== +*/ +float CalcFov (float fov_x, float width, float height) +{ + float a; + float x; + + if (fov_x < 1 || fov_x > 179) + fov_x = 90; // error, set to 90 + + x = width/tan(fov_x/360*M_PI); + + a = atan (height/x); + + a = a*360/M_PI; + + return a; +} + +int hitent = -1; + +void V_Move( int mx, int my ) +{ + float fov; + float fx, fy; + float dx, dy; + float c_x, c_y; + float dX, dY; + vec3_t forward, up, right; + vec3_t newangles; + + vec3_t farpoint; + pmtrace_t tr; + + fov = CalcFov( in_fov, (float)ScreenWidth, (float)ScreenHeight ); + + c_x = (float)ScreenWidth / 2.0; + c_y = (float)ScreenHeight / 2.0; + + dx = (float)mx - c_x; + dy = (float)my - c_y; + + // Proportion we moved in each direction + fx = dx / c_x; + fy = dy / c_y; + + dX = fx * in_fov / 2.0 ; + dY = fy * fov / 2.0; + + newangles = v_angles; + + newangles[ YAW ] -= dX; + newangles[ PITCH ] += dY; + + // Now rotate v_forward around that point + AngleVectors ( newangles, forward, right, up ); + + farpoint = v_origin + 8192 * forward; + + // Trace + tr = *(gEngfuncs.PM_TraceLine( (float *)&v_origin, (float *)&farpoint, PM_TRACELINE_PHYSENTSONLY, 2 /*point sized hull*/, -1 )); + + if ( tr.fraction != 1.0 && tr.ent != 0 ) + { + hitent = PM_GetInfo( tr.ent ); + PM_ParticleLine( (float *)&v_origin, (float *)&tr.endpos, 5, 1.0, 0.0 ); + } + else + { + hitent = -1; + } +} + +#endif \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/view.h b/hlsdk/ricochet/cl_dll/view.h new file mode 100644 index 00000000..24d444fc --- /dev/null +++ b/hlsdk/ricochet/cl_dll/view.h @@ -0,0 +1,22 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +#if !defined ( VIEWH ) +#define VIEWH +#pragma once + +void V_StartPitchDrift( void ); +void V_StopPitchDrift( void ); + +#endif // !VIEWH \ No newline at end of file diff --git a/hlsdk/ricochet/cl_dll/wrect.h b/hlsdk/ricochet/cl_dll/wrect.h new file mode 100644 index 00000000..f2c4f127 --- /dev/null +++ b/hlsdk/ricochet/cl_dll/wrect.h @@ -0,0 +1,23 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +#if !defined( WRECTH ) +#define WRECTH + +typedef struct rect_s +{ + int left, right, top, bottom; +} wrect_t; + +#endif \ No newline at end of file diff --git a/hlsdk/ricochet/dlls/Makefile b/hlsdk/ricochet/dlls/Makefile new file mode 100644 index 00000000..54787606 --- /dev/null +++ b/hlsdk/ricochet/dlls/Makefile @@ -0,0 +1,137 @@ +# +# Half-Life Ricochet SDK 2.3 ricochet_i386.so Makefile for x86 Linux +# +# October 2002 by Leon Hartwig (hartwig@valvesoftware.com) +# + +DLLNAME=ricochet + +ARCH=i386 + +#make sure this is the correct compiler for your system +CC=gcc + +DLL_SRCDIR=. +ENGINE_SRCDIR=../../engine +COMMON_SRCDIR=../../common +PM_SHARED_SRCDIR=../pm_shared +GAME_SHARED_SRCDIR=../../game_shared +WPN_SRC_DIR=$(DLL_SRCDIR)/wpn_shared + +DLL_OBJDIR=$(DLL_SRCDIR)/obj +PM_SHARED_OBJDIR=$(DLL_OBJDIR)/pm_shared +GAME_SHARED_OBJDIR=$(DLL_OBJDIR)/game_shared +WPN_OBJ_DIR=$(DLL_OBJDIR)/wpn_shared + +BASE_CFLAGS=-Dstricmp=strcasecmp -D_strnicmp=strncasecmp -Dstrnicmp=strncasecmp -D_vsnprintf=vsnprintf + +#safe optimization +CFLAGS=$(BASE_CFLAGS) -Wall -Wno-non-virtual-dtor -Wno-invalid-offsetof -Werror -m486 -O1 +C_FLAGS=$(BASE_CFLAGS) -Wall -Werror -m486 -O1 + +#full optimization +#CFLAGS=$(BASE_CFLAGS) -Wall -Wno-non-virtual-dtor -Wno-invalid-offsetof -Werror -O1 -m486 -ffast-math -funroll-loops \ + -fomit-frame-pointer -fexpensive-optimizations \ + -malign-loops=2 -malign-jumps=2 -malign-functions=2 + +#use these when debugging +#CFLAGS=$(BASE_CFLAGS) -g + +INCLUDEDIRS=-I. -I$(ENGINE_SRCDIR) -I$(COMMON_SRCDIR) -I$(PM_SHARED_SRCDIR) -I$(GAME_SHARED_SRCDIR) + +LDFLAGS= + +SHLIBEXT=so +SHLIBCFLAGS=-fPIC +SHLIBLDFLAGS=-shared + +DO_CC=$(CC) $(CFLAGS) $(SHLIBCFLAGS) $(INCLUDEDIRS) -o $@ -c $< +DO_C=$(CC) $(C_FLAGS) $(SHLIBCFLAGS) $(INCLUDEDIRS) -o $@ -c $< + +############################################################################# +# SETUP AND BUILD +# GAME +############################################################################# + +$(DLL_OBJDIR)/%.o: $(DLL_SRCDIR)/%.cpp + $(DO_CC) + +$(GAME_SHARED_OBJDIR)/%.o: $(GAME_SHARED_SRCDIR)/%.cpp + $(DO_CC) + +$(PM_SHARED_OBJDIR)/%.o: $(PM_SHARED_SRCDIR)/%.c + $(DO_C) + +$(WPN_OBJ_DIR)/%.o : $(WPN_SRC_DIR)/%.cpp + $(DO_CC) + +OBJ = \ + $(DLL_OBJDIR)/airtank.o \ + $(DLL_OBJDIR)/animating.o \ + $(DLL_OBJDIR)/animation.o \ + $(DLL_OBJDIR)/bmodels.o \ + $(DLL_OBJDIR)/buttons.o \ + $(DLL_OBJDIR)/cbase.o \ + $(DLL_OBJDIR)/client.o \ + $(DLL_OBJDIR)/combat.o \ + $(DLL_OBJDIR)/disc_arena.o \ + $(DLL_OBJDIR)/disc_powerups.o \ + $(DLL_OBJDIR)/doors.o \ + $(DLL_OBJDIR)/effects.o \ + $(DLL_OBJDIR)/explode.o \ + $(DLL_OBJDIR)/func_break.o \ + $(DLL_OBJDIR)/func_tank.o \ + $(DLL_OBJDIR)/game.o \ + $(DLL_OBJDIR)/gamerules.o \ + $(DLL_OBJDIR)/ggrenade.o \ + $(DLL_OBJDIR)/globals.o \ + $(DLL_OBJDIR)/h_ai.o \ + $(DLL_OBJDIR)/h_battery.o \ + $(DLL_OBJDIR)/h_cycler.o \ + $(DLL_OBJDIR)/h_export.o \ + $(DLL_OBJDIR)/healthkit.o \ + $(DLL_OBJDIR)/items.o \ + $(DLL_OBJDIR)/lights.o \ + $(DLL_OBJDIR)/maprules.o \ + $(DLL_OBJDIR)/mortar.o \ + $(DLL_OBJDIR)/mpstubb.o \ + $(DLL_OBJDIR)/multiplay_gamerules.o \ + $(DLL_OBJDIR)/observer.o \ + $(DLL_OBJDIR)/pathcorner.o \ + $(DLL_OBJDIR)/plane.o \ + $(DLL_OBJDIR)/plats.o \ + $(DLL_OBJDIR)/player.o \ + $(DLL_OBJDIR)/singleplay_gamerules.o \ + $(DLL_OBJDIR)/skill.o \ + $(DLL_OBJDIR)/sound.o \ + $(DLL_OBJDIR)/soundent.o \ + $(DLL_OBJDIR)/spectator.o \ + $(DLL_OBJDIR)/subs.o \ + $(DLL_OBJDIR)/teamplay_gamerules.o \ + $(DLL_OBJDIR)/triggers.o \ + $(DLL_OBJDIR)/util.o \ + $(DLL_OBJDIR)/weapons.o \ + $(DLL_OBJDIR)/world.o \ + $(DLL_OBJDIR)/xen.o \ + $(WPN_OBJ_DIR)/disc_weapon_disc.o \ + $(PM_SHARED_OBJDIR)/pm_shared.o \ + $(PM_SHARED_OBJDIR)/pm_math.o \ + $(PM_SHARED_OBJDIR)/pm_debug.o \ + $(GAME_SHARED_OBJDIR)/voice_gamemgr.o + +$(DLLNAME)_$(ARCH).$(SHLIBEXT) : neat $(OBJ) + $(CC) $(CFLAGS) $(SHLIBLDFLAGS) $(LDFLAGS) -o $@ $(OBJ) + +neat: + -mkdir -p $(DLL_OBJDIR) + -mkdir -p $(GAME_SHARED_OBJDIR) + -mkdir -p $(PM_SHARED_OBJDIR) + -mkdir -p $(WPN_OBJ_DIR) +clean: + -rm -f $(OBJ) + -rm -f $(DLLNAME)_$(ARCH).$(SHLIBEXT) +spotless: clean + -rm -rf $(WPN_OBJ_DIR) + -rm -rf $(GAME_SHARED_OBJDIR) + -rm -rf $(PM_SHARED_OBJDIR) + -rm -rf $(DLL_OBJDIR) diff --git a/hlsdk/ricochet/dlls/activity.h b/hlsdk/ricochet/dlls/activity.h new file mode 100644 index 00000000..36872047 --- /dev/null +++ b/hlsdk/ricochet/dlls/activity.h @@ -0,0 +1,79 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef ACTIVITY_H +#define ACTIVITY_H + + +typedef enum { + ACT_RESET = 0, // Set m_Activity to this invalid value to force a reset to m_IdealActivity + ACT_IDLE, + ACT_HOP, + ACT_HOP_LEFT_FOOT, + ACT_LEAP, + + ACT_TURN_LEFT, + ACT_TURN_RIGHT, + + ACT_BASE_STAND, + ACT_BASE_STAND_THROW, + ACT_FREEZE_STAND, + ACT_FREEZE_STAND_THROW, + ACT_HARD_STAND, + ACT_HARD_STAND_THROW, + ACT_TRIPLE_STAND, + ACT_TRIPLE_STAND_THROW, + + ACT_UNARMED_WALK, + ACT_UNARMED_RUN, + ACT_UNARMED_BACKPEDAL, + + ACT_BASE_WALK, + ACT_BASE_RUN, + ACT_BASE_THROW, + ACT_BASE_BACKUP, + ACT_BASE_BACKUP_THROW, + + ACT_BASE_REVERSE, + ACT_BASE_REVERSE_THROW, + + ACT_FALL, + ACT_FALL_FORWARD, + ACT_FALL_BACKWARD, + ACT_FALL_LEFT, + ACT_FALL_RIGHT, + + ACT_FLINCH_CLOCKWISE, + ACT_FLINCH_COUNTERCLOCKWISE, + ACT_FLINCH_BACK, + ACT_FLINCH_LEFT, + ACT_FLINCH_RIGHT, + ACT_FLINCH_FORWARD, + + ACT_DIE_HEADSHOT, + ACT_DIEFORWARD, + ACT_DIEBACKWARD, +} Activity; + + +typedef struct { + int type; + char *name; +} activity_map_t; + +extern activity_map_t activity_map[]; + + +#endif //ACTIVITY_H diff --git a/hlsdk/ricochet/dlls/activitymap.h b/hlsdk/ricochet/dlls/activitymap.h new file mode 100644 index 00000000..b044b1ee --- /dev/null +++ b/hlsdk/ricochet/dlls/activitymap.h @@ -0,0 +1,37 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#define _A( a ) { a, #a } + +activity_map_t activity_map[] = +{ +_A( ACT_IDLE ), +_A( ACT_HOP ), + +_A( ACT_FALL_FORWARD ), +_A( ACT_FALL_BACKWARD ), +_A( ACT_FALL_LEFT ), +_A( ACT_FALL_RIGHT ), + +_A( ACT_FLINCH_BACK ), +_A( ACT_FLINCH_LEFT ), +_A( ACT_FLINCH_RIGHT ), +_A( ACT_FLINCH_FORWARD ), + +_A( ACT_DIE_HEADSHOT ), +_A( ACT_DIEFORWARD ), +_A( ACT_DIEBACKWARD ), +{ 0, NULL } +}; diff --git a/hlsdk/ricochet/dlls/airtank.cpp b/hlsdk/ricochet/dlls/airtank.cpp new file mode 100644 index 00000000..9a914021 --- /dev/null +++ b/hlsdk/ricochet/dlls/airtank.cpp @@ -0,0 +1,118 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" + +class CAirtank : public CGrenade +{ + void Spawn( void ); + void Precache( void ); + void EXPORT TankThink( void ); + void EXPORT TankTouch( CBaseEntity *pOther ); + int BloodColor( void ) { return DONT_BLEED; }; + void Killed( entvars_t *pevAttacker, int iGib ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_state; +}; + + +LINK_ENTITY_TO_CLASS( item_airtank, CAirtank ); +TYPEDESCRIPTION CAirtank::m_SaveData[] = +{ + DEFINE_FIELD( CAirtank, m_state, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CAirtank, CGrenade ); + + +void CAirtank :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/w_oxygen.mdl"); + UTIL_SetSize(pev, Vector( -16, -16, 0), Vector(16, 16, 36)); + UTIL_SetOrigin( pev, pev->origin ); + + SetTouch( &CAirtank::TankTouch ); + SetThink( &CAirtank::TankThink ); + + pev->flags |= FL_MONSTER; + pev->takedamage = DAMAGE_YES; + pev->health = 20; + pev->dmg = 50; + m_state = 1; +} + +void CAirtank::Precache( void ) +{ + PRECACHE_MODEL("models/w_oxygen.mdl"); + PRECACHE_SOUND("doors/aliendoor3.wav"); +} + + +void CAirtank :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->owner = ENT( pevAttacker ); + + // UNDONE: this should make a big bubble cloud, not an explosion + + Explode( pev->origin, Vector( 0, 0, -1 ) ); +} + + +void CAirtank::TankThink( void ) +{ + // Fire trigger + m_state = 1; + SUB_UseTargets( this, USE_TOGGLE, 0 ); +} + + +void CAirtank::TankTouch( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + return; + + if (!m_state) + { + // "no oxygen" sound + EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_swim2.wav", 1.0, ATTN_NORM ); + return; + } + + // give player 12 more seconds of air + pOther->pev->air_finished = gpGlobals->time + 12; + + // suit recharge sound + EMIT_SOUND( ENT(pev), CHAN_VOICE, "doors/aliendoor3.wav", 1.0, ATTN_NORM ); + + // recharge airtank in 30 seconds + pev->nextthink = gpGlobals->time + 30; + m_state = 0; + SUB_UseTargets( this, USE_TOGGLE, 1 ); +} diff --git a/hlsdk/ricochet/dlls/animating.cpp b/hlsdk/ricochet/dlls/animating.cpp new file mode 100644 index 00000000..2da7536c --- /dev/null +++ b/hlsdk/ricochet/dlls/animating.cpp @@ -0,0 +1,313 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== monsters.cpp ======================================================== + + Monster-related utility code + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "animation.h" +#include "saverestore.h" + +TYPEDESCRIPTION CBaseAnimating::m_SaveData[] = +{ + DEFINE_FIELD( CBaseMonster, m_flFrameRate, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flGroundSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flLastEventCheck, FIELD_TIME ), + DEFINE_FIELD( CBaseMonster, m_fSequenceFinished, FIELD_BOOLEAN ), + DEFINE_FIELD( CBaseMonster, m_fSequenceLoops, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CBaseAnimating, CBaseDelay ); + + +//========================================================= +// StudioFrameAdvance - advance the animation frame up to the current time +// if an flInterval is passed in, only advance animation that number of seconds +//========================================================= +float CBaseAnimating :: StudioFrameAdvance ( float flInterval ) +{ + if (flInterval == 0.0) + { + flInterval = (gpGlobals->time - pev->animtime); + if (flInterval <= 0.001) + { + pev->animtime = gpGlobals->time; + return 0.0; + } + } + if (! pev->animtime) + flInterval = 0.0; + + pev->frame += flInterval * m_flFrameRate * pev->framerate; + pev->animtime = gpGlobals->time; + + if (pev->frame < 0.0 || pev->frame >= 256.0) + { + if (m_fSequenceLoops) + pev->frame -= (int)(pev->frame / 256.0) * 256.0; + else + pev->frame = (pev->frame < 0.0) ? 0 : 255; + m_fSequenceFinished = TRUE; // just in case it wasn't caught in GetEvents + } + + return flInterval; +} + +//========================================================= +// LookupActivity +//========================================================= +int CBaseAnimating :: LookupActivity ( int activity ) +{ + ASSERT( activity != 0 ); + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::LookupActivity( pmodel, pev, activity ); +} + +//========================================================= +// LookupActivityHeaviest +// +// Get activity with highest 'weight' +// +//========================================================= +int CBaseAnimating :: LookupActivityHeaviest ( int activity ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::LookupActivityHeaviest( pmodel, pev, activity ); +} + +//========================================================= +//========================================================= +int CBaseAnimating :: LookupSequence ( const char *label ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::LookupSequence( pmodel, label ); +} + + +//========================================================= +//========================================================= +void CBaseAnimating :: ResetSequenceInfo ( ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + GetSequenceInfo( pmodel, pev, &m_flFrameRate, &m_flGroundSpeed ); + m_fSequenceLoops = ((GetSequenceFlags() & STUDIO_LOOPING) != 0); + pev->animtime = gpGlobals->time; + pev->framerate = 1.0; + m_fSequenceFinished = FALSE; + m_flLastEventCheck = gpGlobals->time; +} + + + +//========================================================= +//========================================================= +BOOL CBaseAnimating :: GetSequenceFlags( ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::GetSequenceFlags( pmodel, pev ); +} + +//========================================================= +// DispatchAnimEvents +//========================================================= +void CBaseAnimating :: DispatchAnimEvents ( float flInterval ) +{ + MonsterEvent_t event; + + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + if ( !pmodel ) + { + ALERT( at_aiconsole, "Gibbed monster is thinking!\n" ); + return; + } + + // FIXME: I have to do this or some events get missed, and this is probably causing the problem below + flInterval = 0.1; + + // FIX: this still sometimes hits events twice + float flStart = pev->frame + (m_flLastEventCheck - pev->animtime) * m_flFrameRate * pev->framerate; + float flEnd = pev->frame + flInterval * m_flFrameRate * pev->framerate; + m_flLastEventCheck = pev->animtime + flInterval; + + m_fSequenceFinished = FALSE; + if (flEnd >= 256 || flEnd <= 0.0) + m_fSequenceFinished = TRUE; + + int index = 0; + + while ( (index = GetAnimationEvent( pmodel, pev, &event, flStart, flEnd, index ) ) != 0 ) + { + HandleAnimEvent( &event ); + } +} + + +//========================================================= +//========================================================= +float CBaseAnimating :: SetBoneController ( int iController, float flValue ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return SetController( pmodel, pev, iController, flValue ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: InitBoneControllers ( void ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + SetController( pmodel, pev, 0, 0.0 ); + SetController( pmodel, pev, 1, 0.0 ); + SetController( pmodel, pev, 2, 0.0 ); + SetController( pmodel, pev, 3, 0.0 ); +} + +//========================================================= +//========================================================= +float CBaseAnimating :: SetBlending ( int iBlender, float flValue ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::SetBlending( pmodel, pev, iBlender, flValue ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: GetBonePosition ( int iBone, Vector &origin, Vector &angles ) +{ + GET_BONE_POSITION( ENT(pev), iBone, origin, angles ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: GetAttachment ( int iAttachment, Vector &origin, Vector &angles ) +{ + GET_ATTACHMENT( ENT(pev), iAttachment, origin, angles ); +} + +//========================================================= +//========================================================= +int CBaseAnimating :: FindTransition( int iEndingSequence, int iGoalSequence, int *piDir ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + if (piDir == NULL) + { + int iDir; + int sequence = ::FindTransition( pmodel, iEndingSequence, iGoalSequence, &iDir ); + if (iDir != 1) + return -1; + else + return sequence; + } + + return ::FindTransition( pmodel, iEndingSequence, iGoalSequence, piDir ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: GetAutomovement( Vector &origin, Vector &angles, float flInterval ) +{ + +} + +void CBaseAnimating :: SetBodygroup( int iGroup, int iValue ) +{ + ::SetBodygroup( GET_MODEL_PTR( ENT(pev) ), pev, iGroup, iValue ); +} + +int CBaseAnimating :: GetBodygroup( int iGroup ) +{ + return ::GetBodygroup( GET_MODEL_PTR( ENT(pev) ), pev, iGroup ); +} + + +int CBaseAnimating :: ExtractBbox( int sequence, float *mins, float *maxs ) +{ + return ::ExtractBbox( GET_MODEL_PTR( ENT(pev) ), sequence, mins, maxs ); +} + +//========================================================= +//========================================================= + +void CBaseAnimating :: SetSequenceBox( void ) +{ + Vector mins, maxs; + + // Get sequence bbox + if ( ExtractBbox( pev->sequence, mins, maxs ) ) + { + // expand box for rotation + // find min / max for rotations + float yaw = pev->angles.y * (M_PI / 180.0); + + Vector xvector, yvector; + xvector.x = cos(yaw); + xvector.y = sin(yaw); + yvector.x = -sin(yaw); + yvector.y = cos(yaw); + Vector bounds[2]; + + bounds[0] = mins; + bounds[1] = maxs; + + Vector rmin( 9999, 9999, 9999 ); + Vector rmax( -9999, -9999, -9999 ); + Vector base, transformed; + + for (int i = 0; i <= 1; i++ ) + { + base.x = bounds[i].x; + for ( int j = 0; j <= 1; j++ ) + { + base.y = bounds[j].y; + for ( int k = 0; k <= 1; k++ ) + { + base.z = bounds[k].z; + + // transform the point + transformed.x = xvector.x*base.x + yvector.x*base.y; + transformed.y = xvector.y*base.x + yvector.y*base.y; + transformed.z = base.z; + + for ( int l = 0; l < 3; l++ ) + { + if (transformed[l] < rmin[l]) + rmin[l] = transformed[l]; + if (transformed[l] > rmax[l]) + rmax[l] = transformed[l]; + } + } + } + } + rmin.z = 0; + rmax.z = rmin.z + 1; + UTIL_SetSize( pev, rmin, rmax ); + } +} + diff --git a/hlsdk/ricochet/dlls/animation.cpp b/hlsdk/ricochet/dlls/animation.cpp new file mode 100644 index 00000000..500dde1b --- /dev/null +++ b/hlsdk/ricochet/dlls/animation.cpp @@ -0,0 +1,533 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include +#include +#include + +#if defined _MSC_VER && _MSC_VER >= 1400 + #ifndef _CRT_SECURE_NO_DEPRECATE + #define _CRT_SECURE_NO_DEPRECATE + #endif + + #pragma warning(disable: 4996) // deprecated functions +#endif + +typedef bool BOOL; + +// hack into header files that we can ship +typedef int qboolean; +typedef unsigned char byte; +#include "../utils/common/mathlib.h" +#include "const.h" +#include "progdefs.h" +#include "edict.h" +#include "eiface.h" + +#include "studio.h" + +#include "../engine/studio.h" + +#ifndef ACTIVITY_H +#include "activity.h" +#endif + +#include "activitymap.h" + +#ifndef ANIMATION_H +#include "animation.h" +#endif + +#ifndef SCRIPTEVENT_H +#include "scriptevent.h" +#endif + +#ifndef ENGINECALLBACK_H +#include "enginecallback.h" +#endif + +extern globalvars_t *gpGlobals; + +#ifdef _MSC_VER +#pragma warning( disable : 4244 ) +#endif + + +int ExtractBbox( void *pmodel, int sequence, float *mins, float *maxs ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + mins[0] = pseqdesc[ sequence ].bbmin[0]; + mins[1] = pseqdesc[ sequence ].bbmin[1]; + mins[2] = pseqdesc[ sequence ].bbmin[2]; + + maxs[0] = pseqdesc[ sequence ].bbmax[0]; + maxs[1] = pseqdesc[ sequence ].bbmax[1]; + maxs[2] = pseqdesc[ sequence ].bbmax[2]; + + return 1; +} + + +int LookupActivity( void *pmodel, entvars_t *pev, int activity ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + int weighttotal = 0; + int seq = ACTIVITY_NOT_AVAILABLE; + for (int i = 0; i < pstudiohdr->numseq; i++) + { + if (pseqdesc[i].activity == activity) + { + weighttotal += pseqdesc[i].actweight; + if (!weighttotal || RANDOM_LONG(0,weighttotal-1) < pseqdesc[i].actweight) + seq = i; + } + } + + return seq; +} + + +int LookupActivityHeaviest( void *pmodel, entvars_t *pev, int activity ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr ) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + int weight = 0; + int seq = ACTIVITY_NOT_AVAILABLE; + for (int i = 0; i < pstudiohdr->numseq; i++) + { + if (pseqdesc[i].activity == activity) + { + if ( pseqdesc[i].actweight > weight ) + { + weight = pseqdesc[i].actweight; + seq = i; + } + } + } + + return seq; +} + +void GetEyePosition ( void *pmodel, float *vecEyePosition ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + + if ( !pstudiohdr ) + { + ALERT ( at_console, "GetEyePosition() Can't get pstudiohdr ptr!\n" ); + return; + } + + VectorCopy ( pstudiohdr->eyeposition, vecEyePosition ); +} + +int LookupSequence( void *pmodel, const char *label ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + for (int i = 0; i < pstudiohdr->numseq; i++) + { + if (stricmp( pseqdesc[i].label, label ) == 0) + return i; + } + + return -1; +} + + +int IsSoundEvent( int eventNumber ) +{ + if ( eventNumber == SCRIPT_EVENT_SOUND || eventNumber == SCRIPT_EVENT_SOUND_VOICE ) + return 1; + return 0; +} + + +void SequencePrecache( void *pmodel, const char *pSequenceName ) +{ + int index = LookupSequence( pmodel, pSequenceName ); + if ( index >= 0 ) + { + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || index >= pstudiohdr->numseq ) + return; + + mstudioseqdesc_t *pseqdesc; + mstudioevent_t *pevent; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + index; + pevent = (mstudioevent_t *)((byte *)pstudiohdr + pseqdesc->eventindex); + + for (int i = 0; i < pseqdesc->numevents; i++) + { + // Don't send client-side events to the server AI + if ( pevent[i].event >= EVENT_CLIENT ) + continue; + + // UNDONE: Add a callback to check to see if a sound is precached yet and don't allocate a copy + // of it's name if it is. + if ( IsSoundEvent( pevent[i].event ) ) + { + if ( !strlen(pevent[i].options) ) + { + ALERT( at_error, "Bad sound event %d in sequence %s :: %s (sound is \"%s\")\n", pevent[i].event, pstudiohdr->name, pSequenceName, pevent[i].options ); + } + + PRECACHE_SOUND( (char *)(gpGlobals->pStringBase + ALLOC_STRING(pevent[i].options) ) ); + } + } + } +} + + + +void GetSequenceInfo( void *pmodel, entvars_t *pev, float *pflFrameRate, float *pflGroundSpeed ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return; + + mstudioseqdesc_t *pseqdesc; + + if (pev->sequence >= pstudiohdr->numseq) + { + *pflFrameRate = 0.0; + *pflGroundSpeed = 0.0; + return; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + if (pseqdesc->numframes > 1) + { + *pflFrameRate = 256 * pseqdesc->fps / (pseqdesc->numframes - 1); + *pflGroundSpeed = sqrt( pseqdesc->linearmovement[0]*pseqdesc->linearmovement[0]+ pseqdesc->linearmovement[1]*pseqdesc->linearmovement[1]+ pseqdesc->linearmovement[2]*pseqdesc->linearmovement[2] ); + *pflGroundSpeed = *pflGroundSpeed * pseqdesc->fps / (pseqdesc->numframes - 1); + } + else + { + *pflFrameRate = 256.0; + *pflGroundSpeed = 0.0; + } +} + + +int GetSequenceFlags( void *pmodel, entvars_t *pev ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || pev->sequence >= pstudiohdr->numseq ) + return 0; + + mstudioseqdesc_t *pseqdesc; + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + return pseqdesc->flags; +} + + +int GetAnimationEvent( void *pmodel, entvars_t *pev, MonsterEvent_t *pMonsterEvent, float flStart, float flEnd, int index ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || pev->sequence >= pstudiohdr->numseq || !pMonsterEvent ) + return 0; + + mstudioseqdesc_t *pseqdesc; + mstudioevent_t *pevent; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + pevent = (mstudioevent_t *)((byte *)pstudiohdr + pseqdesc->eventindex); + + if (pseqdesc->numevents == 0 || index > pseqdesc->numevents ) + return 0; + + if (pseqdesc->numframes > 1) + { + flStart *= (pseqdesc->numframes - 1) / 256.0; + flEnd *= (pseqdesc->numframes - 1) / 256.0; + } + else + { + flStart = 0; + flEnd = 1.0; + } + + for (; index < pseqdesc->numevents; index++) + { + // Don't send client-side events to the server AI + if ( pevent[index].event >= EVENT_CLIENT ) + continue; + + if ( (pevent[index].frame >= flStart && pevent[index].frame < flEnd) || + ((pseqdesc->flags & STUDIO_LOOPING) && flEnd >= pseqdesc->numframes - 1 && pevent[index].frame < flEnd - pseqdesc->numframes + 1) ) + { + pMonsterEvent->event = pevent[index].event; + pMonsterEvent->options = pevent[index].options; + return index + 1; + } + } + return 0; +} + +float SetController( void *pmodel, entvars_t *pev, int iController, float flValue ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return flValue; + + mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *)((byte *)pstudiohdr + pstudiohdr->bonecontrollerindex); + + int i = 0; + // find first controller that matches the index + for (i = 0; i < pstudiohdr->numbonecontrollers; i++, pbonecontroller++) + { + if (pbonecontroller->index == iController) + break; + } + if (i >= pstudiohdr->numbonecontrollers) + return flValue; + + // wrap 0..360 if it's a rotational controller + + if (pbonecontroller->type & (STUDIO_XR | STUDIO_YR | STUDIO_ZR)) + { + // ugly hack, invert value if end < start + if (pbonecontroller->end < pbonecontroller->start) + flValue = -flValue; + + // does the controller not wrap? + if (pbonecontroller->start + 359.0 >= pbonecontroller->end) + { + if (flValue > ((pbonecontroller->start + pbonecontroller->end) / 2.0) + 180) + flValue = flValue - 360; + if (flValue < ((pbonecontroller->start + pbonecontroller->end) / 2.0) - 180) + flValue = flValue + 360; + } + else + { + if (flValue > 360) + flValue = flValue - (int)(flValue / 360.0) * 360.0; + else if (flValue < 0) + flValue = flValue + (int)((flValue / -360.0) + 1) * 360.0; + } + } + + int setting = static_cast(255 * (flValue - pbonecontroller->start) / (pbonecontroller->end - pbonecontroller->start)); + + if (setting < 0) setting = 0; + if (setting > 255) setting = 255; + pev->controller[iController] = setting; + + return setting * (1.0 / 255.0) * (pbonecontroller->end - pbonecontroller->start) + pbonecontroller->start; +} + + +float SetBlending( void *pmodel, entvars_t *pev, int iBlender, float flValue ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return flValue; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + if (pseqdesc->blendtype[iBlender] == 0) + return flValue; + + if (pseqdesc->blendtype[iBlender] & (STUDIO_XR | STUDIO_YR | STUDIO_ZR)) + { + // ugly hack, invert value if end < start + if (pseqdesc->blendend[iBlender] < pseqdesc->blendstart[iBlender]) + flValue = -flValue; + + // does the controller not wrap? + if (pseqdesc->blendstart[iBlender] + 359.0 >= pseqdesc->blendend[iBlender]) + { + if (flValue > ((pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender]) / 2.0) + 180) + flValue = flValue - 360; + if (flValue < ((pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender]) / 2.0) - 180) + flValue = flValue + 360; + } + } + + int setting = static_cast(255 * (flValue - pseqdesc->blendstart[iBlender]) / (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender])); + + if (setting < 0) setting = 0; + if (setting > 255) setting = 255; + + pev->blending[iBlender] = setting; + + return setting * (1.0 / 255.0) * (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender]) + pseqdesc->blendstart[iBlender]; +} + + + + +int FindTransition( void *pmodel, int iEndingAnim, int iGoalAnim, int *piDir ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return iGoalAnim; + + mstudioseqdesc_t *pseqdesc; + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + // bail if we're going to or from a node 0 + if (pseqdesc[iEndingAnim].entrynode == 0 || pseqdesc[iGoalAnim].entrynode == 0) + { + return iGoalAnim; + } + + int iEndNode; + + // ALERT( at_console, "from %d to %d: ", pEndNode->iEndNode, pGoalNode->iStartNode ); + + if (*piDir > 0) + { + iEndNode = pseqdesc[iEndingAnim].exitnode; + } + else + { + iEndNode = pseqdesc[iEndingAnim].entrynode; + } + + if (iEndNode == pseqdesc[iGoalAnim].entrynode) + { + *piDir = 1; + return iGoalAnim; + } + + byte *pTransition = ((byte *)pstudiohdr + pstudiohdr->transitionindex); + + int iInternNode = pTransition[(iEndNode-1)*pstudiohdr->numtransitions + (pseqdesc[iGoalAnim].entrynode-1)]; + + if (iInternNode == 0) + return iGoalAnim; + + int i; + + // look for someone going + for (i = 0; i < pstudiohdr->numseq; i++) + { + if (pseqdesc[i].entrynode == iEndNode && pseqdesc[i].exitnode == iInternNode) + { + *piDir = 1; + return i; + } + if (pseqdesc[i].nodeflags) + { + if (pseqdesc[i].exitnode == iEndNode && pseqdesc[i].entrynode == iInternNode) + { + *piDir = -1; + return i; + } + } + } + + ALERT( at_console, "error in transition graph" ); + return iGoalAnim; +} + +void SetBodygroup( void *pmodel, entvars_t *pev, int iGroup, int iValue ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return; + + if (iGroup > pstudiohdr->numbodyparts) + return; + + mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex) + iGroup; + + if (iValue >= pbodypart->nummodels) + return; + + int iCurrent = (pev->body / pbodypart->base) % pbodypart->nummodels; + + pev->body = (pev->body - (iCurrent * pbodypart->base) + (iValue * pbodypart->base)); +} + + +int GetBodygroup( void *pmodel, entvars_t *pev, int iGroup ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + if (iGroup > pstudiohdr->numbodyparts) + return 0; + + mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex) + iGroup; + + if (pbodypart->nummodels <= 1) + return 0; + + int iCurrent = (pev->body / pbodypart->base) % pbodypart->nummodels; + + return iCurrent; +} diff --git a/hlsdk/ricochet/dlls/animation.h b/hlsdk/ricochet/dlls/animation.h new file mode 100644 index 00000000..3da74cb6 --- /dev/null +++ b/hlsdk/ricochet/dlls/animation.h @@ -0,0 +1,47 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +#ifndef ANIMATION_H +#define ANIMATION_H + +#define ACTIVITY_NOT_AVAILABLE -1 + +#ifndef MONSTEREVENT_H +#include "monsterevent.h" +#endif + +extern int IsSoundEvent( int eventNumber ); + +int LookupActivity( void *pmodel, entvars_t *pev, int activity ); +int LookupActivityHeaviest( void *pmodel, entvars_t *pev, int activity ); +int LookupSequence( void *pmodel, const char *label ); +void GetSequenceInfo( void *pmodel, entvars_t *pev, float *pflFrameRate, float *pflGroundSpeed ); +int GetSequenceFlags( void *pmodel, entvars_t *pev ); +int LookupAnimationEvents( void *pmodel, entvars_t *pev, float flStart, float flEnd ); +float SetController( void *pmodel, entvars_t *pev, int iController, float flValue ); +float SetBlending( void *pmodel, entvars_t *pev, int iBlender, float flValue ); +void GetEyePosition( void *pmodel, float *vecEyePosition ); +void SequencePrecache( void *pmodel, const char *pSequenceName ); +int FindTransition( void *pmodel, int iEndingAnim, int iGoalAnim, int *piDir ); +void SetBodygroup( void *pmodel, entvars_t *pev, int iGroup, int iValue ); +int GetBodygroup( void *pmodel, entvars_t *pev, int iGroup ); + +int GetAnimationEvent( void *pmodel, entvars_t *pev, MonsterEvent_t *pMonsterEvent, float flStart, float flEnd, int index ); +int ExtractBbox( void *pmodel, int sequence, float *mins, float *maxs ); + +// From /engine/studio.h +#define STUDIO_LOOPING 0x0001 + + +#endif //ANIMATION_H diff --git a/hlsdk/ricochet/dlls/basemonster.h b/hlsdk/ricochet/dlls/basemonster.h new file mode 100644 index 00000000..ab516a15 --- /dev/null +++ b/hlsdk/ricochet/dlls/basemonster.h @@ -0,0 +1,94 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +#ifndef BASEMONSTER_H +#define BASEMONSTER_H + +class CBaseMonster : public CBaseToggle +{ +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 + Vector m_vecEnemyLKP;// last known position of enemy. (enemy's origin) + + + void KeyValue( KeyValueData *pkvd ); + + void MakeIdealYaw( Vector vecTarget ); + 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 ); + virtual Activity GetDeathActivity ( void ); + Activity GetSmallFlinchActivity( void ); + virtual void BecomeDead( 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 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 ); + + 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; } + + 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); } + +}; + + +#endif diff --git a/hlsdk/ricochet/dlls/bmodels.cpp b/hlsdk/ricochet/dlls/bmodels.cpp new file mode 100644 index 00000000..39c60a70 --- /dev/null +++ b/hlsdk/ricochet/dlls/bmodels.cpp @@ -0,0 +1,958 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== bmodels.cpp ======================================================== + + spawn, think, and use functions for entities that use brush models + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "doors.h" + +extern DLL_GLOBAL Vector g_vecAttackDir; + +#define SF_BRUSH_ACCDCC 16// brush should accelerate and decelerate when toggled +#define SF_BRUSH_HURT 32// rotating brush that inflicts pain based on rotation speed +#define SF_ROTATING_NOT_SOLID 64 // some special rotating objects are not solid. + +// covering cheesy noise1, noise2, & noise3 fields so they make more sense (for rotating fans) +#define noiseStart noise1 +#define noiseStop noise2 +#define noiseRunning noise3 + +#define SF_PENDULUM_SWING 2 // spawnflag that makes a pendulum a rope swing. +// +// BModelOrigin - calculates origin of a bmodel from absmin/size because all bmodel origins are 0 0 0 +// +Vector VecBModelOrigin( entvars_t* pevBModel ) +{ + return pevBModel->absmin + ( pevBModel->size * 0.5 ); +} + +// =================== FUNC_WALL ============================================== + +/*QUAKED func_wall (0 .5 .8) ? +This is just a solid wall if not inhibited +*/ +class CFuncWall : public CBaseEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + // Bmodels don't go across transitions + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +LINK_ENTITY_TO_CLASS( func_wall, CFuncWall ); + +void CFuncWall :: Spawn( void ) +{ + pev->angles = g_vecZero; + pev->movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + pev->solid = SOLID_BSP; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + // If it can't move/go away, it's really part of the world + pev->flags |= FL_WORLDBRUSH; +} + + +void CFuncWall :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( ShouldToggle( useType, (int)(pev->frame)) ) + pev->frame = 1 - pev->frame; +} + + +#define SF_WALL_START_OFF 0x0001 + +class CFuncWallToggle : public CFuncWall +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void TurnOff( void ); + void TurnOn( void ); + BOOL IsOn( void ); +}; + +LINK_ENTITY_TO_CLASS( func_wall_toggle, CFuncWallToggle ); + +void CFuncWallToggle :: Spawn( void ) +{ + CFuncWall::Spawn(); + if ( pev->spawnflags & SF_WALL_START_OFF ) + TurnOff(); +} + + +void CFuncWallToggle :: TurnOff( void ) +{ + pev->solid = SOLID_NOT; + pev->effects |= EF_NODRAW; + UTIL_SetOrigin( pev, pev->origin ); +} + + +void CFuncWallToggle :: TurnOn( void ) +{ + pev->solid = SOLID_BSP; + pev->effects &= ~EF_NODRAW; + UTIL_SetOrigin( pev, pev->origin ); +} + + +BOOL CFuncWallToggle :: IsOn( void ) +{ + if ( pev->solid == SOLID_NOT ) + return FALSE; + return TRUE; +} + + +void CFuncWallToggle :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int status = IsOn(); + + if ( ShouldToggle( useType, status ) ) + { + if ( status ) + TurnOff(); + else + TurnOn(); + } +} + + +#define SF_CONVEYOR_VISUAL 0x0001 +#define SF_CONVEYOR_NOTSOLID 0x0002 + +class CFuncConveyor : public CFuncWall +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void UpdateSpeed( float speed ); +}; + +LINK_ENTITY_TO_CLASS( func_conveyor, CFuncConveyor ); +void CFuncConveyor :: Spawn( void ) +{ + SetMovedir( pev ); + CFuncWall::Spawn(); + + if ( !(pev->spawnflags & SF_CONVEYOR_VISUAL) ) + SetBits( pev->flags, FL_CONVEYOR ); + + // HACKHACK - This is to allow for some special effects + if ( pev->spawnflags & SF_CONVEYOR_NOTSOLID ) + { + pev->solid = SOLID_NOT; + pev->skin = 0; // Don't want the engine thinking we've got special contents on this brush + } + + if ( pev->speed == 0 ) + pev->speed = 100; + + UpdateSpeed( pev->speed ); +} + + +// HACKHACK -- This is ugly, but encode the speed in the rendercolor to avoid adding more data to the network stream +void CFuncConveyor :: UpdateSpeed( float speed ) +{ + // Encode it as an integer with 4 fractional bits + int speedCode = (int)(fabs(speed) * 16.0); + + if ( speed < 0 ) + pev->rendercolor.x = 1; + else + pev->rendercolor.x = 0; + + pev->rendercolor.y = (speedCode >> 8); + pev->rendercolor.z = (speedCode & 0xFF); +} + + +void CFuncConveyor :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + pev->speed = -pev->speed; + UpdateSpeed( pev->speed ); +} + + + +// =================== FUNC_ILLUSIONARY ============================================== + + +/*QUAKED func_illusionary (0 .5 .8) ? +A simple entity that looks solid but lets you walk through it. +*/ +class CFuncIllusionary : public CBaseToggle +{ +public: + void Spawn( void ); + void EXPORT SloshTouch( CBaseEntity *pOther ); + void KeyValue( KeyValueData *pkvd ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +LINK_ENTITY_TO_CLASS( func_illusionary, CFuncIllusionary ); + +void CFuncIllusionary :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "skin"))//skin is used for content type + { + pev->skin = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CFuncIllusionary :: Spawn( void ) +{ + pev->angles = g_vecZero; + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT;// always solid_not + SET_MODEL( ENT(pev), STRING(pev->model) ); + + // I'd rather eat the network bandwidth of this than figure out how to save/restore + // these entities after they have been moved to the client, or respawn them ala Quake + // Perhaps we can do this in deathmatch only. + // MAKE_STATIC(ENT(pev)); +} + + +// ------------------------------------------------------------------------------- +// +// Monster only clip brush +// +// This brush will be solid for any entity who has the FL_MONSTERCLIP flag set +// in pev->flags +// +// otherwise it will be invisible and not solid. This can be used to keep +// specific monsters out of certain areas +// +// ------------------------------------------------------------------------------- +class CFuncMonsterClip : public CFuncWall +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) {} // Clear out func_wall's use function +}; + +LINK_ENTITY_TO_CLASS( func_monsterclip, CFuncMonsterClip ); + +void CFuncMonsterClip::Spawn( void ) +{ + CFuncWall::Spawn(); + if ( CVAR_GET_FLOAT("showtriggers") == 0 ) + pev->effects = EF_NODRAW; + pev->flags |= FL_MONSTERCLIP; +} + + +// =================== FUNC_ROTATING ============================================== +class CFuncRotating : public CBaseEntity +{ +public: + // basic functions + void Spawn( void ); + void Precache( void ); + void EXPORT SpinUp ( void ); + void EXPORT SpinDown ( void ); + void KeyValue( KeyValueData* pkvd); + void EXPORT HurtTouch ( CBaseEntity *pOther ); + void EXPORT RotatingUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Rotate( void ); + void RampPitchVol (int fUp ); + void Blocked( CBaseEntity *pOther ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flFanFriction; + float m_flAttenuation; + float m_flVolume; + float m_pitch; + int m_sounds; +}; + +TYPEDESCRIPTION CFuncRotating::m_SaveData[] = +{ + DEFINE_FIELD( CFuncRotating, m_flFanFriction, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_flAttenuation, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_flVolume, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_pitch, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_sounds, FIELD_INTEGER ) +}; + +IMPLEMENT_SAVERESTORE( CFuncRotating, CBaseEntity ); + + +LINK_ENTITY_TO_CLASS( func_rotating, CFuncRotating ); + +void CFuncRotating :: KeyValue( KeyValueData* pkvd) +{ + if (FStrEq(pkvd->szKeyName, "fanfriction")) + { + m_flFanFriction = atof(pkvd->szValue)/100; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "Volume")) + { + m_flVolume = atof(pkvd->szValue)/10.0; + + if (m_flVolume > 1.0) + m_flVolume = 1.0; + if (m_flVolume < 0.0) + m_flVolume = 0.0; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spawnorigin")) + { + Vector tmp; + UTIL_StringToVector( (float *)tmp, pkvd->szValue ); + if ( tmp != g_vecZero ) + pev->origin = tmp; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +/*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS +You need to have an origin brush as part of this entity. The +center of that brush will be +the point around which it is rotated. It will rotate around the Z +axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"speed" determines how fast it moves; default value is 100. +"dmg" damage to inflict when blocked (2 default) + +REVERSE will cause the it to rotate in the opposite direction. +*/ + + +void CFuncRotating :: Spawn( ) +{ + // set final pitch. Must not be PITCH_NORM, since we + // plan on pitch shifting later. + + m_pitch = PITCH_NORM - 1; + + // maintain compatibility with previous maps + if (m_flVolume == 0.0) + m_flVolume = 1.0; + + // if the designer didn't set a sound attenuation, default to one. + m_flAttenuation = ATTN_NORM; + + if ( FBitSet ( pev->spawnflags, SF_BRUSH_ROTATE_SMALLRADIUS) ) + { + m_flAttenuation = ATTN_IDLE; + } + else if ( FBitSet ( pev->spawnflags, SF_BRUSH_ROTATE_MEDIUMRADIUS) ) + { + m_flAttenuation = ATTN_STATIC; + } + else if ( FBitSet ( pev->spawnflags, SF_BRUSH_ROTATE_LARGERADIUS) ) + { + m_flAttenuation = ATTN_NORM; + } + + // prevent divide by zero if level designer forgets friction! + if ( m_flFanFriction == 0 ) + { + m_flFanFriction = 1; + } + + if ( FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_Z_AXIS) ) + pev->movedir = Vector(0,0,1); + else if ( FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_X_AXIS) ) + pev->movedir = Vector(1,0,0); + else + pev->movedir = Vector(0,1,0); // y-axis + + // check for reverse rotation + if ( FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_BACKWARDS) ) + pev->movedir = pev->movedir * -1; + + // some rotating objects like fake volumetric lights will not be solid. + if ( FBitSet(pev->spawnflags, SF_ROTATING_NOT_SOLID) ) + { + pev->solid = SOLID_NOT; + pev->skin = CONTENTS_EMPTY; + pev->movetype = MOVETYPE_PUSH; + } + else + { + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + } + + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + SetUse( &CFuncRotating::RotatingUse ); + // did level designer forget to assign speed? + if (pev->speed <= 0) + pev->speed = 0; + + // Removed this per level designers request. -- JAY + // if (pev->dmg == 0) + // pev->dmg = 2; + + // instant-use brush? + if ( FBitSet( pev->spawnflags, SF_BRUSH_ROTATE_INSTANT) ) + { + SetThink( &CBaseEntity::SUB_CallUseToggle ); + pev->nextthink = pev->ltime + 1.5; // leave a magic delay for client to start up + } + // can this brush inflict pain? + if ( FBitSet (pev->spawnflags, SF_BRUSH_HURT) ) + { + SetTouch( &CFuncRotating::HurtTouch ); + } + + Precache( ); +} + + +void CFuncRotating :: Precache( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + + // set up fan sounds + + if (!FStringNull( pev->message ) && strlen( szSoundFile ) > 0) + { + // if a path is set for a wave, use it + + PRECACHE_SOUND(szSoundFile); + + pev->noiseRunning = ALLOC_STRING(szSoundFile); + } else + { + // otherwise use preset sound + switch (m_sounds) + { + case 1: + PRECACHE_SOUND ("fans/fan1.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan1.wav"); + break; + case 2: + PRECACHE_SOUND ("fans/fan2.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan2.wav"); + break; + case 3: + PRECACHE_SOUND ("fans/fan3.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan3.wav"); + break; + case 4: + PRECACHE_SOUND ("fans/fan4.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan4.wav"); + break; + case 5: + PRECACHE_SOUND ("fans/fan5.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan5.wav"); + break; + + case 0: + default: + if (!FStringNull( pev->message ) && strlen( szSoundFile ) > 0) + { + PRECACHE_SOUND(szSoundFile); + + pev->noiseRunning = ALLOC_STRING(szSoundFile); + break; + } else + { + pev->noiseRunning = ALLOC_STRING("common/null.wav"); + break; + } + } + } + + if (pev->avelocity != g_vecZero ) + { + // if fan was spinning, and we went through transition or save/restore, + // make sure we restart the sound. 1.5 sec delay is magic number. KDB + + SetThink ( &CFuncRotating::SpinUp ); + pev->nextthink = pev->ltime + 1.5; + } +} + + + +// +// Touch - will hurt others based on how fast the brush is spinning +// +void CFuncRotating :: HurtTouch ( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + // we can't hurt this thing, so we're not concerned with it + if ( !pevOther->takedamage ) + return; + + // calculate damage based on rotation speed + pev->dmg = pev->avelocity.Length() / 10; + + pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH); + + pevOther->velocity = (pevOther->origin - VecBModelOrigin(pev) ).Normalize() * pev->dmg; +} + +// +// RampPitchVol - ramp pitch and volume up to final values, based on difference +// between how fast we're going vs how fast we plan to go +// +#define FANPITCHMIN 30 +#define FANPITCHMAX 100 + +void CFuncRotating :: RampPitchVol (int fUp) +{ + + Vector vecAVel = pev->avelocity; + vec_t vecCur; + vec_t vecFinal; + float fpct; + float fvol; + float fpitch; + int pitch; + + // get current angular velocity + + vecCur = abs(vecAVel.x != 0 ? static_cast(vecAVel.x) : static_cast(vecAVel.y != 0 ? vecAVel.y : vecAVel.z)); + + // get target angular velocity + + vecFinal = (pev->movedir.x != 0 ? pev->movedir.x : (pev->movedir.y != 0 ? pev->movedir.y : pev->movedir.z)); + vecFinal *= pev->speed; + vecFinal = abs(static_cast(vecFinal)); + + // calc volume and pitch as % of final vol and pitch + + fpct = vecCur / vecFinal; +// if (fUp) +// fvol = m_flVolume * (0.5 + fpct/2.0); // spinup volume ramps up from 50% max vol +// else + fvol = m_flVolume * fpct; // slowdown volume ramps down to 0 + + fpitch = FANPITCHMIN + (FANPITCHMAX - FANPITCHMIN) * fpct; + + pitch = (int) fpitch; + if (pitch == PITCH_NORM) + pitch = PITCH_NORM-1; + + // change the fan's vol and pitch + + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + fvol, m_flAttenuation, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch); + +} + +// +// SpinUp - accelerates a non-moving func_rotating up to it's speed +// +void CFuncRotating :: SpinUp( void ) +{ + Vector vecAVel;//rotational velocity + + pev->nextthink = pev->ltime + 0.1; + pev->avelocity = pev->avelocity + ( pev->movedir * ( pev->speed * m_flFanFriction ) ); + + vecAVel = pev->avelocity;// cache entity's rotational velocity + + // if we've met or exceeded target speed, set target speed and stop thinking + if ( abs(static_cast(vecAVel.x)) >= abs(static_cast(pev->movedir.x * pev->speed)) && + abs(static_cast(vecAVel.y)) >= abs(static_cast(pev->movedir.y * pev->speed)) && + abs(static_cast(vecAVel.z)) >= abs(static_cast(pev->movedir.z * pev->speed)) ) + { + pev->avelocity = pev->movedir * pev->speed;// set speed in case we overshot + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + m_flVolume, m_flAttenuation, SND_CHANGE_PITCH | SND_CHANGE_VOL, FANPITCHMAX); + + SetThink( &CFuncRotating::Rotate ); + Rotate(); + } + else + { + RampPitchVol(TRUE); + } +} + +// +// SpinDown - decelerates a moving func_rotating to a standstill. +// +void CFuncRotating :: SpinDown( void ) +{ + Vector vecAVel;//rotational velocity + vec_t vecdir; + + pev->nextthink = pev->ltime + 0.1; + + pev->avelocity = pev->avelocity - ( pev->movedir * ( pev->speed * m_flFanFriction ) );//spin down slower than spinup + + vecAVel = pev->avelocity;// cache entity's rotational velocity + + if (pev->movedir.x != 0) + vecdir = pev->movedir.x; + else if (pev->movedir.y != 0) + vecdir = pev->movedir.y; + else + vecdir = pev->movedir.z; + + // if we've met or exceeded target speed, set target speed and stop thinking + // (note: must check for movedir > 0 or < 0) + if (((vecdir > 0) && (vecAVel.x <= 0 && vecAVel.y <= 0 && vecAVel.z <= 0)) || + ((vecdir < 0) && (vecAVel.x >= 0 && vecAVel.y >= 0 && vecAVel.z >= 0))) + { + pev->avelocity = g_vecZero;// set speed in case we overshot + + // stop sound, we're done + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning /* Stop */), + 0, 0, SND_STOP, static_cast(m_pitch)); + + SetThink( &CFuncRotating::Rotate ); + Rotate(); + } + else + { + RampPitchVol(FALSE); + } +} + +void CFuncRotating :: Rotate( void ) +{ + pev->nextthink = pev->ltime + 10; +} + +//========================================================= +// Rotating Use - when a rotating brush is triggered +//========================================================= +void CFuncRotating :: RotatingUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // is this a brush that should accelerate and decelerate when turned on/off (fan)? + if ( FBitSet ( pev->spawnflags, SF_BRUSH_ACCDCC ) ) + { + // fan is spinning, so stop it. + if ( pev->avelocity != g_vecZero ) + { + SetThink ( &CFuncRotating::SpinDown ); + //EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, (char *)STRING(pev->noiseStop), + // m_flVolume, m_flAttenuation, 0, m_pitch); + + pev->nextthink = pev->ltime + 0.1; + } + else// fan is not moving, so start it + { + SetThink ( &CFuncRotating::SpinUp ); + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + 0.01, m_flAttenuation, 0, FANPITCHMIN); + + pev->nextthink = pev->ltime + 0.1; + } + } + else if ( !FBitSet ( pev->spawnflags, SF_BRUSH_ACCDCC ) )//this is a normal start/stop brush. + { + if ( pev->avelocity != g_vecZero ) + { + // play stopping sound here + SetThink ( &CFuncRotating::SpinDown ); + + // EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, (char *)STRING(pev->noiseStop), + // m_flVolume, m_flAttenuation, 0, m_pitch); + + pev->nextthink = pev->ltime + 0.1; + // pev->avelocity = g_vecZero; + } + else + { + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + m_flVolume, m_flAttenuation, 0, FANPITCHMAX); + pev->avelocity = pev->movedir * pev->speed; + + SetThink( &CFuncRotating::Rotate ); + Rotate(); + } + } +} + + +// +// RotatingBlocked - An entity has blocked the brush +// +void CFuncRotating :: Blocked( CBaseEntity *pOther ) + +{ + pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH); +} + + + + + + +//#endif + + +class CPendulum : public CBaseEntity +{ +public: + void Spawn ( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT Swing( void ); + void EXPORT PendulumUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Stop( void ); + void Touch( CBaseEntity *pOther ); + void EXPORT RopeTouch ( CBaseEntity *pOther );// this touch func makes the pendulum a rope + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + void Blocked( CBaseEntity *pOther ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_accel; // Acceleration + float m_distance; // + float m_time; + float m_damp; + float m_maxSpeed; + float m_dampSpeed; + vec3_t m_center; + vec3_t m_start; +}; + +LINK_ENTITY_TO_CLASS( func_pendulum, CPendulum ); + +TYPEDESCRIPTION CPendulum::m_SaveData[] = +{ + DEFINE_FIELD( CPendulum, m_accel, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_distance, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_time, FIELD_TIME ), + DEFINE_FIELD( CPendulum, m_damp, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_maxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_dampSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_center, FIELD_VECTOR ), + DEFINE_FIELD( CPendulum, m_start, FIELD_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CPendulum, CBaseEntity ); + + + +void CPendulum :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "distance")) + { + m_distance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damp")) + { + m_damp = atof(pkvd->szValue) * 0.001; + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +void CPendulum :: Spawn( void ) +{ + // set the axis of rotation + CBaseToggle :: AxisDir( pev ); + + if ( FBitSet (pev->spawnflags, SF_DOOR_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + if ( m_distance == 0 ) + return; + + if (pev->speed == 0) + pev->speed = 100; + + m_accel = (pev->speed * pev->speed) / (2 * fabs(m_distance)); // Calculate constant acceleration from speed and distance + m_maxSpeed = pev->speed; + m_start = pev->angles; + m_center = pev->angles + (m_distance * 0.5) * pev->movedir; + + if ( FBitSet( pev->spawnflags, SF_BRUSH_ROTATE_INSTANT) ) + { + SetThink( &CBaseEntity::SUB_CallUseToggle ); + pev->nextthink = gpGlobals->time + 0.1; + } + pev->speed = 0; + SetUse( &CPendulum::PendulumUse ); + + if ( FBitSet( pev->spawnflags, SF_PENDULUM_SWING ) ) + { + SetTouch ( &CPendulum::RopeTouch ); + } +} + + +void CPendulum :: PendulumUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->speed ) // Pendulum is moving, stop it and auto-return if necessary + { + if ( FBitSet( pev->spawnflags, SF_PENDULUM_AUTO_RETURN ) ) + { + float delta; + + delta = CBaseToggle :: AxisDelta( pev->spawnflags, pev->angles, m_start ); + + pev->avelocity = m_maxSpeed * pev->movedir; + pev->nextthink = pev->ltime + (delta / m_maxSpeed); + SetThink( &CPendulum::Stop ); + } + else + { + pev->speed = 0; // Dead stop + SetThink( NULL ); + pev->avelocity = g_vecZero; + } + } + else + { + pev->nextthink = pev->ltime + 0.1; // Start the pendulum moving + m_time = gpGlobals->time; // Save time to calculate dt + SetThink( &CPendulum::Swing ); + m_dampSpeed = m_maxSpeed; + } +} + + +void CPendulum :: Stop( void ) +{ + pev->angles = m_start; + pev->speed = 0; + SetThink( NULL ); + pev->avelocity = g_vecZero; +} + + +void CPendulum::Blocked( CBaseEntity *pOther ) +{ + m_time = gpGlobals->time; +} + + +void CPendulum :: Swing( void ) +{ + float delta, dt; + + delta = CBaseToggle :: AxisDelta( pev->spawnflags, pev->angles, m_center ); + dt = gpGlobals->time - m_time; // How much time has passed? + m_time = gpGlobals->time; // Remember the last time called + + if ( delta > 0 && m_accel > 0 ) + pev->speed -= m_accel * dt; // Integrate velocity + else + pev->speed += m_accel * dt; + + if ( pev->speed > m_maxSpeed ) + pev->speed = m_maxSpeed; + else if ( pev->speed < -m_maxSpeed ) + pev->speed = -m_maxSpeed; + // scale the destdelta vector by the time spent traveling to get velocity + pev->avelocity = pev->speed * pev->movedir; + + // Call this again + pev->nextthink = pev->ltime + 0.1; + + if ( m_damp ) + { + m_dampSpeed -= m_damp * m_dampSpeed * dt; + if ( m_dampSpeed < 30.0 ) + { + pev->angles = m_center; + pev->speed = 0; + SetThink( NULL ); + pev->avelocity = g_vecZero; + } + else if ( pev->speed > m_dampSpeed ) + pev->speed = m_dampSpeed; + else if ( pev->speed < -m_dampSpeed ) + pev->speed = -m_dampSpeed; + + } +} + + +void CPendulum :: Touch ( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + if ( pev->dmg <= 0 ) + return; + + // we can't hurt this thing, so we're not concerned with it + if ( !pevOther->takedamage ) + return; + + // calculate damage based on rotation speed + float damage = pev->dmg * pev->speed * 0.01; + + if ( damage < 0 ) + damage = -damage; + + pOther->TakeDamage( pev, pev, damage, DMG_CRUSH ); + + pevOther->velocity = (pevOther->origin - VecBModelOrigin(pev) ).Normalize() * damage; +} + +void CPendulum :: RopeTouch ( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + if ( !pOther->IsPlayer() ) + {// not a player! + ALERT ( at_console, "Not a client\n" ); + return; + } + + if ( ENT(pevOther) == pev->enemy ) + {// this player already on the rope. + return; + } + + pev->enemy = pOther->edict(); + pevOther->velocity = g_vecZero; + pevOther->movetype = MOVETYPE_NONE; +} + + diff --git a/hlsdk/ricochet/dlls/buttons.cpp b/hlsdk/ricochet/dlls/buttons.cpp new file mode 100644 index 00000000..2ffe311a --- /dev/null +++ b/hlsdk/ricochet/dlls/buttons.cpp @@ -0,0 +1,1276 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== buttons.cpp ======================================================== + + button-related code + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "doors.h" + + +#define SF_BUTTON_DONTMOVE 1 +#define SF_ROTBUTTON_NOTSOLID 1 +#define SF_BUTTON_TOGGLE 32 // button stays pushed until reactivated +#define SF_BUTTON_SPARK_IF_OFF 64 // button sparks in OFF state +#define SF_BUTTON_TOUCH_ONLY 256 // button only fires as a result of USE key. + +#define SF_GLOBAL_SET 1 // Set global state to initial state on spawn + +class CEnvGlobal : public CPointEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + string_t m_globalstate; + int m_triggermode; + int m_initialstate; +}; + +TYPEDESCRIPTION CEnvGlobal::m_SaveData[] = +{ + DEFINE_FIELD( CEnvGlobal, m_globalstate, FIELD_STRING ), + DEFINE_FIELD( CEnvGlobal, m_triggermode, FIELD_INTEGER ), + DEFINE_FIELD( CEnvGlobal, m_initialstate, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CEnvGlobal, CBaseEntity ); + +LINK_ENTITY_TO_CLASS( env_global, CEnvGlobal ); + +void CEnvGlobal::KeyValue( KeyValueData *pkvd ) +{ + pkvd->fHandled = TRUE; + + if ( FStrEq(pkvd->szKeyName, "globalstate") ) // State name + m_globalstate = ALLOC_STRING( pkvd->szValue ); + else if ( FStrEq(pkvd->szKeyName, "triggermode") ) + m_triggermode = atoi( pkvd->szValue ); + else if ( FStrEq(pkvd->szKeyName, "initialstate") ) + m_initialstate = atoi( pkvd->szValue ); + else + CPointEntity::KeyValue( pkvd ); +} + +void CEnvGlobal::Spawn( void ) +{ + if ( !m_globalstate ) + { + REMOVE_ENTITY( ENT(pev) ); + return; + } + if ( FBitSet( pev->spawnflags, SF_GLOBAL_SET ) ) + { + if ( !gGlobalState.EntityInTable( m_globalstate ) ) + gGlobalState.EntityAdd( m_globalstate, gpGlobals->mapname, (GLOBALESTATE)m_initialstate ); + } +} + + +void CEnvGlobal::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + GLOBALESTATE oldState = gGlobalState.EntityGetState( m_globalstate ); + GLOBALESTATE newState; + + switch( m_triggermode ) + { + case 0: + newState = GLOBAL_OFF; + break; + + case 1: + newState = GLOBAL_ON; + break; + + case 2: + newState = GLOBAL_DEAD; + break; + + default: + case 3: + if ( oldState == GLOBAL_ON ) + newState = GLOBAL_OFF; + else if ( oldState == GLOBAL_OFF ) + newState = GLOBAL_ON; + else + newState = oldState; + } + + if ( gGlobalState.EntityInTable( m_globalstate ) ) + gGlobalState.EntitySetState( m_globalstate, newState ); + else + gGlobalState.EntityAdd( m_globalstate, gpGlobals->mapname, newState ); +} + + + +TYPEDESCRIPTION CMultiSource::m_SaveData[] = +{ + //!!!BUGBUG FIX + DEFINE_ARRAY( CMultiSource, m_rgEntities, FIELD_EHANDLE, MS_MAX_TARGETS ), + DEFINE_ARRAY( CMultiSource, m_rgTriggered, FIELD_INTEGER, MS_MAX_TARGETS ), + DEFINE_FIELD( CMultiSource, m_iTotal, FIELD_INTEGER ), + DEFINE_FIELD( CMultiSource, m_globalstate, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CMultiSource, CBaseEntity ); + +LINK_ENTITY_TO_CLASS( multisource, CMultiSource ); +// +// Cache user-entity-field values until spawn is called. +// + +void CMultiSource::KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "style") || + FStrEq(pkvd->szKeyName, "height") || + FStrEq(pkvd->szKeyName, "killtarget") || + FStrEq(pkvd->szKeyName, "value1") || + FStrEq(pkvd->szKeyName, "value2") || + FStrEq(pkvd->szKeyName, "value3")) + pkvd->fHandled = TRUE; + else if ( FStrEq(pkvd->szKeyName, "globalstate") ) + { + m_globalstate = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +#define SF_MULTI_INIT 1 + +void CMultiSource::Spawn() +{ + // set up think for later registration + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->nextthink = gpGlobals->time + 0.1; + pev->spawnflags |= SF_MULTI_INIT; // Until it's initialized + SetThink(&CMultiSource::Register); +} + +void CMultiSource::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int i = 0; + + // Find the entity in our list + while (i < m_iTotal) + if ( m_rgEntities[i++] == pCaller ) + break; + + // if we didn't find it, report error and leave + if (i > m_iTotal) + { + ALERT(at_console, "MultiSrc:Used by non member %s.\n", STRING(pCaller->pev->classname)); + return; + } + + // CONSIDER: a Use input to the multisource always toggles. Could check useType for ON/OFF/TOGGLE + + m_rgTriggered[i-1] ^= 1; + + // + if ( IsTriggered( pActivator ) ) + { + ALERT( at_aiconsole, "Multisource %s enabled (%d inputs)\n", STRING(pev->targetname), m_iTotal ); + USE_TYPE useType = USE_TOGGLE; + if ( m_globalstate ) + useType = USE_ON; + SUB_UseTargets( NULL, useType, 0 ); + } +} + + +BOOL CMultiSource::IsTriggered( CBaseEntity * ) +{ + // Is everything triggered? + int i = 0; + + // Still initializing? + if ( pev->spawnflags & SF_MULTI_INIT ) + return 0; + + while (i < m_iTotal) + { + if (m_rgTriggered[i] == 0) + break; + i++; + } + + if (i == m_iTotal) + { + if ( !m_globalstate || gGlobalState.EntityGetState( m_globalstate ) == GLOBAL_ON ) + return 1; + } + + return 0; +} + +void CMultiSource::Register(void) +{ + edict_t *pentTarget = NULL; + + m_iTotal = 0; + memset( m_rgEntities, 0, MS_MAX_TARGETS * sizeof(EHANDLE) ); + + SetThink(&CBaseEntity::SUB_DoNothing); + + // search for all entities which target this multisource (pev->targetname) + + pentTarget = FIND_ENTITY_BY_STRING(NULL, "target", STRING(pev->targetname)); + + while (!FNullEnt(pentTarget) && (m_iTotal < MS_MAX_TARGETS)) + { + CBaseEntity *pTarget = CBaseEntity::Instance(pentTarget); + if ( pTarget ) + m_rgEntities[m_iTotal++] = pTarget; + + pentTarget = FIND_ENTITY_BY_STRING( pentTarget, "target", STRING(pev->targetname)); + } + + pentTarget = FIND_ENTITY_BY_STRING(NULL, "classname", "multi_manager"); + while (!FNullEnt(pentTarget) && (m_iTotal < MS_MAX_TARGETS)) + { + CBaseEntity *pTarget = CBaseEntity::Instance(pentTarget); + if ( pTarget && pTarget->HasTarget(pev->targetname) ) + m_rgEntities[m_iTotal++] = pTarget; + + pentTarget = FIND_ENTITY_BY_STRING( pentTarget, "classname", "multi_manager" ); + } + + pev->spawnflags &= ~SF_MULTI_INIT; +} + +// CBaseButton +TYPEDESCRIPTION CBaseButton::m_SaveData[] = +{ + DEFINE_FIELD( CBaseButton, m_fStayPushed, FIELD_BOOLEAN ), + DEFINE_FIELD( CBaseButton, m_fRotating, FIELD_BOOLEAN ), + + DEFINE_FIELD( CBaseButton, m_sounds, FIELD_INTEGER ), + DEFINE_FIELD( CBaseButton, m_bLockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_bLockedSentence, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_bUnlockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_bUnlockedSentence, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_strChangeTarget, FIELD_STRING ), +// DEFINE_FIELD( CBaseButton, m_ls, FIELD_??? ), // This is restored in Precache() +}; + + +IMPLEMENT_SAVERESTORE( CBaseButton, CBaseToggle ); + +void CBaseButton::Precache( void ) +{ + char *pszSound; + + if ( FBitSet ( pev->spawnflags, SF_BUTTON_SPARK_IF_OFF ) )// this button should spark in OFF state + { + PRECACHE_SOUND ("buttons/spark1.wav"); + PRECACHE_SOUND ("buttons/spark2.wav"); + PRECACHE_SOUND ("buttons/spark3.wav"); + PRECACHE_SOUND ("buttons/spark4.wav"); + PRECACHE_SOUND ("buttons/spark5.wav"); + PRECACHE_SOUND ("buttons/spark6.wav"); + } + + // get door button sounds, for doors which require buttons to open + + if (m_bLockedSound) + { + pszSound = ButtonSound( (int)m_bLockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sLockedSound = ALLOC_STRING(pszSound); + } + + if (m_bUnlockedSound) + { + pszSound = ButtonSound( (int)m_bUnlockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sUnlockedSound = ALLOC_STRING(pszSound); + } + + // get sentence group names, for doors which are directly 'touched' to open + + switch (m_bLockedSentence) + { + case 1: m_ls.sLockedSentence = MAKE_STRING("NA"); break; // access denied + case 2: m_ls.sLockedSentence = MAKE_STRING("ND"); break; // security lockout + case 3: m_ls.sLockedSentence = MAKE_STRING("NF"); break; // blast door + case 4: m_ls.sLockedSentence = MAKE_STRING("NFIRE"); break; // fire door + case 5: m_ls.sLockedSentence = MAKE_STRING("NCHEM"); break; // chemical door + case 6: m_ls.sLockedSentence = MAKE_STRING("NRAD"); break; // radiation door + case 7: m_ls.sLockedSentence = MAKE_STRING("NCON"); break; // gen containment + case 8: m_ls.sLockedSentence = MAKE_STRING("NH"); break; // maintenance door + case 9: m_ls.sLockedSentence = MAKE_STRING("NG"); break; // broken door + + default: m_ls.sLockedSentence = 0; break; + } + + switch (m_bUnlockedSentence) + { + case 1: m_ls.sUnlockedSentence = MAKE_STRING("EA"); break; // access granted + case 2: m_ls.sUnlockedSentence = MAKE_STRING("ED"); break; // security door + case 3: m_ls.sUnlockedSentence = MAKE_STRING("EF"); break; // blast door + case 4: m_ls.sUnlockedSentence = MAKE_STRING("EFIRE"); break; // fire door + case 5: m_ls.sUnlockedSentence = MAKE_STRING("ECHEM"); break; // chemical door + case 6: m_ls.sUnlockedSentence = MAKE_STRING("ERAD"); break; // radiation door + case 7: m_ls.sUnlockedSentence = MAKE_STRING("ECON"); break; // gen containment + case 8: m_ls.sUnlockedSentence = MAKE_STRING("EH"); break; // maintenance door + + default: m_ls.sUnlockedSentence = 0; break; + } +} + +// +// Cache user-entity-field values until spawn is called. +// + +void CBaseButton::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "changetarget")) + { + m_strChangeTarget = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sound")) + { + m_bLockedSound = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sentence")) + { + m_bLockedSentence = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sound")) + { + m_bUnlockedSound = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sentence")) + { + m_bUnlockedSentence = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +// +// ButtonShot +// +int CBaseButton::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + BUTTON_CODE code = ButtonResponseToTouch(); + + if ( code == BUTTON_NOTHING ) + return 0; + // Temporarily disable the touch function, until movement is finished. + SetTouch( NULL ); + + m_hActivator = CBaseEntity::Instance( pevAttacker ); + if ( m_hActivator == 0 ) + return 0; + + if ( code == BUTTON_RETURN ) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + // Toggle buttons fire when they get back to their "home" position + if ( !(pev->spawnflags & SF_BUTTON_TOGGLE) ) + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + ButtonReturn(); + } + else // code == BUTTON_ACTIVATE + ButtonActivate( ); + + return 0; +} + +/*QUAKED func_button (0 .5 .8) ? +When a button is touched, it moves some distance in the direction of it's angle, +triggers all of it's targets, waits some time, then returns to it's original position +where it can be triggered again. + +"angle" determines the opening direction +"target" all entities with a matching targetname will be used +"speed" override the default 40 speed +"wait" override the default 1 second wait (-1 = never return) +"lip" override the default 4 pixel lip remaining at end of move +"health" if set, the button must be killed instead of touched +"sounds" +0) steam metal +1) wooden clunk +2) metallic click +3) in-out +*/ +LINK_ENTITY_TO_CLASS( func_button, CBaseButton ); + + +void CBaseButton::Spawn( ) +{ + char *pszSound; + + //---------------------------------------------------- + //determine sounds for buttons + //a sound of 0 should not make a sound + //---------------------------------------------------- + pszSound = ButtonSound( m_sounds ); + PRECACHE_SOUND(pszSound); + pev->noise = ALLOC_STRING(pszSound); + + Precache(); + + if ( FBitSet ( pev->spawnflags, SF_BUTTON_SPARK_IF_OFF ) )// this button should spark in OFF state + { + SetThink ( &CBaseButton::ButtonSpark ); + pev->nextthink = gpGlobals->time + 0.5;// no hurry, make sure everything else spawns + } + + SetMovedir(pev); + + pev->movetype = MOVETYPE_PUSH; + pev->solid = SOLID_BSP; + SET_MODEL(ENT(pev), STRING(pev->model)); + + if (pev->speed == 0) + pev->speed = 40; + + if (pev->health > 0) + { + pev->takedamage = DAMAGE_YES; + } + + if (m_flWait == 0) + m_flWait = 1; + if (m_flLip == 0) + m_flLip = 4; + + m_toggle_state = TS_AT_BOTTOM; + m_vecPosition1 = pev->origin; + // Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big + m_vecPosition2 = m_vecPosition1 + (pev->movedir * (fabs( pev->movedir.x * (pev->size.x-2) ) + fabs( pev->movedir.y * (pev->size.y-2) ) + fabs( pev->movedir.z * (pev->size.z-2) ) - m_flLip)); + + + // Is this a non-moving button? + if ( ((m_vecPosition2 - m_vecPosition1).Length() < 1) || (pev->spawnflags & SF_BUTTON_DONTMOVE) ) + m_vecPosition2 = m_vecPosition1; + + m_fStayPushed = (m_flWait == -1 ? TRUE : FALSE); + m_fRotating = FALSE; + + // if the button is flagged for USE button activation only, take away it's touch function and add a use function + + if ( FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) // touchable button + { + SetTouch( &CBaseButton::ButtonTouch ); + } + else + { + SetTouch ( NULL ); + SetUse ( &CBaseButton::ButtonUse ); + } +} + + +// Button sound table. +// Also used by CBaseDoor to get 'touched' door lock/unlock sounds + +char *ButtonSound( int sound ) +{ + char *pszSound; + + switch ( sound ) + { + case 0: pszSound = "common/null.wav"; break; + case 1: pszSound = "buttons/button1.wav"; break; + case 2: pszSound = "buttons/button2.wav"; break; + case 3: pszSound = "buttons/button3.wav"; break; + case 4: pszSound = "buttons/button4.wav"; break; + case 5: pszSound = "buttons/button5.wav"; break; + case 6: pszSound = "buttons/button6.wav"; break; + case 7: pszSound = "buttons/button7.wav"; break; + case 8: pszSound = "buttons/button8.wav"; break; + case 9: pszSound = "buttons/button9.wav"; break; + case 10: pszSound = "buttons/button10.wav"; break; + case 11: pszSound = "buttons/button11.wav"; break; + case 12: pszSound = "buttons/latchlocked1.wav"; break; + case 13: pszSound = "buttons/latchunlocked1.wav"; break; + case 14: pszSound = "buttons/lightswitch2.wav";break; + +// next 6 slots reserved for any additional sliding button sounds we may add + + case 21: pszSound = "buttons/lever1.wav"; break; + case 22: pszSound = "buttons/lever2.wav"; break; + case 23: pszSound = "buttons/lever3.wav"; break; + case 24: pszSound = "buttons/lever4.wav"; break; + case 25: pszSound = "buttons/lever5.wav"; break; + + default:pszSound = "buttons/button9.wav"; break; + } + + return pszSound; +} + +// +// Makes flagged buttons spark when turned off +// + +void DoSpark(entvars_t *pev, const Vector &location ) +{ + Vector tmp = location + pev->size * 0.5; + UTIL_Sparks( tmp ); + + float flVolume = RANDOM_FLOAT ( 0.25 , 0.75 ) * 0.4;//random volume range + switch ( (int)(RANDOM_FLOAT(0,1) * 6) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark1.wav", flVolume, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark2.wav", flVolume, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark3.wav", flVolume, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark4.wav", flVolume, ATTN_NORM); break; + case 4: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark5.wav", flVolume, ATTN_NORM); break; + case 5: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark6.wav", flVolume, ATTN_NORM); break; + } +} + +void CBaseButton::ButtonSpark ( void ) +{ + SetThink ( &CBaseButton::ButtonSpark ); + pev->nextthink = gpGlobals->time + ( 0.1 + RANDOM_FLOAT ( 0, 1.5 ) );// spark again at random interval + + DoSpark( pev, pev->mins ); +} + + +// +// Button's Use function +// +void CBaseButton::ButtonUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Ignore touches if button is moving, or pushed-in and waiting to auto-come-out. + // UNDONE: Should this use ButtonResponseToTouch() too? + if (m_toggle_state == TS_GOING_UP || m_toggle_state == TS_GOING_DOWN ) + return; + + m_hActivator = pActivator; + if ( m_toggle_state == TS_AT_TOP) + { + if (!m_fStayPushed && FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE)) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + //SUB_UseTargets( m_eoActivator ); + ButtonReturn(); + } + } + else + ButtonActivate( ); +} + + +CBaseButton::BUTTON_CODE CBaseButton::ButtonResponseToTouch( void ) +{ + // Ignore touches if button is moving, or pushed-in and waiting to auto-come-out. + if (m_toggle_state == TS_GOING_UP || + m_toggle_state == TS_GOING_DOWN || + (m_toggle_state == TS_AT_TOP && !m_fStayPushed && !FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE) ) ) + return BUTTON_NOTHING; + + if (m_toggle_state == TS_AT_TOP) + { + if((FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE) ) && !m_fStayPushed) + { + return BUTTON_RETURN; + } + } + else + return BUTTON_ACTIVATE; + + return BUTTON_NOTHING; +} + + +// +// Touching a button simply "activates" it. +// +void CBaseButton:: ButtonTouch( CBaseEntity *pOther ) +{ + // Ignore touches by anything but players + if (!FClassnameIs(pOther->pev, "player")) + return; + + m_hActivator = pOther; + + BUTTON_CODE code = ButtonResponseToTouch(); + + if ( code == BUTTON_NOTHING ) + return; + + if (!UTIL_IsMasterTriggered(m_sMaster, pOther)) + { + // play button locked sound + PlayLockSounds(pev, &m_ls, TRUE, TRUE); + return; + } + + // Temporarily disable the touch function, until movement is finished. + SetTouch( NULL ); + + if ( code == BUTTON_RETURN ) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + ButtonReturn(); + } + else // code == BUTTON_ACTIVATE + ButtonActivate( ); +} + +// +// Starts the button moving "in/up". +// +void CBaseButton::ButtonActivate( ) +{ + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + { + // button is locked, play locked sound + PlayLockSounds(pev, &m_ls, TRUE, TRUE); + return; + } + else + { + // button is unlocked, play unlocked sound + PlayLockSounds(pev, &m_ls, FALSE, TRUE); + } + + ASSERT(m_toggle_state == TS_AT_BOTTOM); + m_toggle_state = TS_GOING_UP; + + SetMoveDone( &CBaseButton::TriggerAndWait ); + if (!m_fRotating) + LinearMove( m_vecPosition2, pev->speed); + else + AngularMove( m_vecAngle2, pev->speed); +} + +// +// Button has reached the "in/up" position. Activate its "targets", and pause before "popping out". +// +void CBaseButton::TriggerAndWait( void ) +{ + ASSERT(m_toggle_state == TS_GOING_UP); + + if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + return; + + m_toggle_state = TS_AT_TOP; + + // If button automatically comes back out, start it moving out. + // Else re-instate touch method + if (m_fStayPushed || FBitSet ( pev->spawnflags, SF_BUTTON_TOGGLE ) ) + { + if ( !FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) // this button only works if USED, not touched! + { + // ALL buttons are now use only + SetTouch ( NULL ); + } + else + SetTouch( &CBaseButton::ButtonTouch ); + } + else + { + pev->nextthink = pev->ltime + m_flWait; + SetThink( &CBaseButton::ButtonReturn ); + } + + pev->frame = 1; // use alternate textures + + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); +} + + +// +// Starts the button moving "out/down". +// +void CBaseButton::ButtonReturn( void ) +{ + ASSERT(m_toggle_state == TS_AT_TOP); + m_toggle_state = TS_GOING_DOWN; + + SetMoveDone( &CBaseButton::ButtonBackHome ); + if (!m_fRotating) + LinearMove( m_vecPosition1, pev->speed); + else + AngularMove( m_vecAngle1, pev->speed); + + pev->frame = 0; // use normal textures +} + + +// +// Button has returned to start state. Quiesce it. +// +void CBaseButton::ButtonBackHome( void ) +{ + ASSERT(m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_AT_BOTTOM; + + if ( FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE) ) + { + //EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + } + + + if (!FStringNull(pev->target)) + { + edict_t* pentTarget = NULL; + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->target)); + + if (FNullEnt(pentTarget)) + break; + + if (!FClassnameIs(pentTarget, "multisource")) + continue; + CBaseEntity *pTarget = CBaseEntity::Instance( pentTarget ); + + if ( pTarget ) + pTarget->Use( m_hActivator, this, USE_TOGGLE, 0 ); + } + } + +// Re-instate touch method, movement cycle is complete. + if ( !FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) // this button only works if USED, not touched! + { + // All buttons are now use only + SetTouch ( NULL ); + } + else + SetTouch( &CBaseButton::ButtonTouch ); + +// reset think for a sparking button + if ( FBitSet ( pev->spawnflags, SF_BUTTON_SPARK_IF_OFF ) ) + { + SetThink ( &CBaseButton::ButtonSpark ); + pev->nextthink = gpGlobals->time + 0.5;// no hurry. + } +} + + + +// +// Rotating button (aka "lever") +// +class CRotButton : public CBaseButton +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( func_rot_button, CRotButton ); + +void CRotButton::Spawn( void ) +{ + char *pszSound; + //---------------------------------------------------- + //determine sounds for buttons + //a sound of 0 should not make a sound + //---------------------------------------------------- + pszSound = ButtonSound( m_sounds ); + PRECACHE_SOUND(pszSound); + pev->noise = ALLOC_STRING(pszSound); + + // set the axis of rotation + CBaseToggle::AxisDir( pev ); + + // check for clockwise rotation + if ( FBitSet (pev->spawnflags, SF_DOOR_ROTATE_BACKWARDS) ) + pev->movedir = pev->movedir * -1; + + pev->movetype = MOVETYPE_PUSH; + + if ( pev->spawnflags & SF_ROTBUTTON_NOTSOLID ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + + SET_MODEL(ENT(pev), STRING(pev->model)); + + if (pev->speed == 0) + pev->speed = 40; + + if (m_flWait == 0) + m_flWait = 1; + + if (pev->health > 0) + { + pev->takedamage = DAMAGE_YES; + } + + m_toggle_state = TS_AT_BOTTOM; + m_vecAngle1 = pev->angles; + m_vecAngle2 = pev->angles + pev->movedir * m_flMoveDistance; + ASSERTSZ(m_vecAngle1 != m_vecAngle2, "rotating button start/end positions are equal"); + + m_fStayPushed = (m_flWait == -1 ? TRUE : FALSE); + m_fRotating = TRUE; + + // if the button is flagged for USE button activation only, take away it's touch function and add a use function + if ( !FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) + { + SetTouch ( NULL ); + SetUse ( &CBaseButton::ButtonUse ); + } + else // touchable button + SetTouch( &CBaseButton::ButtonTouch ); + + //SetTouch( ButtonTouch ); +} + + +// Make this button behave like a door (HACKHACK) +// This will disable use and make the button solid +// rotating buttons were made SOLID_NOT by default since their were some +// collision problems with them... +#define SF_MOMENTARY_DOOR 0x0001 + +class CMomentaryRotButton : public CBaseToggle +{ +public: + void Spawn ( void ); + void KeyValue( KeyValueData *pkvd ); + virtual int ObjectCaps( void ) + { + int flags = CBaseToggle :: ObjectCaps() & (~FCAP_ACROSS_TRANSITION); + if ( pev->spawnflags & SF_MOMENTARY_DOOR ) + return flags; + return flags | FCAP_CONTINUOUS_USE; + } + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Off( void ); + void EXPORT Return( void ); + void UpdateSelf( float value ); + void UpdateSelfReturn( float value ); + void UpdateAllButtons( float value, int start ); + + void PlaySound( void ); + void UpdateTarget( float value ); + + static CMomentaryRotButton *Instance( edict_t *pent ) { return (CMomentaryRotButton *)GET_PRIVATE(pent);}; + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_lastUsed; + int m_direction; + float m_returnSpeed; + vec3_t m_start; + vec3_t m_end; + int m_sounds; +}; +TYPEDESCRIPTION CMomentaryRotButton::m_SaveData[] = +{ + DEFINE_FIELD( CMomentaryRotButton, m_lastUsed, FIELD_INTEGER ), + DEFINE_FIELD( CMomentaryRotButton, m_direction, FIELD_INTEGER ), + DEFINE_FIELD( CMomentaryRotButton, m_returnSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CMomentaryRotButton, m_start, FIELD_VECTOR ), + DEFINE_FIELD( CMomentaryRotButton, m_end, FIELD_VECTOR ), + DEFINE_FIELD( CMomentaryRotButton, m_sounds, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CMomentaryRotButton, CBaseToggle ); + +LINK_ENTITY_TO_CLASS( momentary_rot_button, CMomentaryRotButton ); + +void CMomentaryRotButton::Spawn( void ) +{ + CBaseToggle::AxisDir( pev ); + + if ( pev->speed == 0 ) + pev->speed = 100; + + if ( m_flMoveDistance < 0 ) + { + m_start = pev->angles + pev->movedir * m_flMoveDistance; + m_end = pev->angles; + m_direction = 1; // This will toggle to -1 on the first use() + m_flMoveDistance = -m_flMoveDistance; + } + else + { + m_start = pev->angles; + m_end = pev->angles + pev->movedir * m_flMoveDistance; + m_direction = -1; // This will toggle to +1 on the first use() + } + + if ( pev->spawnflags & SF_MOMENTARY_DOOR ) + pev->solid = SOLID_BSP; + else + pev->solid = SOLID_NOT; + + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + char *pszSound = ButtonSound( m_sounds ); + PRECACHE_SOUND(pszSound); + pev->noise = ALLOC_STRING(pszSound); + m_lastUsed = 0; +} + +void CMomentaryRotButton::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "returnspeed")) + { + m_returnSpeed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CMomentaryRotButton::PlaySound( void ) +{ + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); +} + +// BUGBUG: This design causes a latentcy. When the button is retriggered, the first impulse +// will send the target in the wrong direction because the parameter is calculated based on the +// current, not future position. +void CMomentaryRotButton::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + pev->ideal_yaw = CBaseToggle::AxisDelta( pev->spawnflags, pev->angles, m_start ) / m_flMoveDistance; + + UpdateAllButtons( pev->ideal_yaw, 1 ); + UpdateTarget( pev->ideal_yaw ); +} + +void CMomentaryRotButton::UpdateAllButtons( float value, int start ) +{ + // Update all rot buttons attached to the same target + edict_t *pentTarget = NULL; + for (;;) + { + + pentTarget = FIND_ENTITY_BY_STRING(pentTarget, "target", STRING(pev->target)); + if (FNullEnt(pentTarget)) + break; + + if ( FClassnameIs( VARS(pentTarget), "momentary_rot_button" ) ) + { + CMomentaryRotButton *pEntity = CMomentaryRotButton::Instance(pentTarget); + if ( pEntity ) + { + if ( start ) + pEntity->UpdateSelf( value ); + else + pEntity->UpdateSelfReturn( value ); + } + } + } +} + +void CMomentaryRotButton::UpdateSelf( float value ) +{ + BOOL fplaysound = FALSE; + + if ( !m_lastUsed ) + { + fplaysound = TRUE; + m_direction = -m_direction; + } + m_lastUsed = 1; + + pev->nextthink = pev->ltime + 0.1; + if ( m_direction > 0 && value >= 1.0 ) + { + pev->avelocity = g_vecZero; + pev->angles = m_end; + return; + } + else if ( m_direction < 0 && value <= 0 ) + { + pev->avelocity = g_vecZero; + pev->angles = m_start; + return; + } + + if (fplaysound) + PlaySound(); + + // HACKHACK -- If we're going slow, we'll get multiple player packets per frame, bump nexthink on each one to avoid stalling + if ( pev->nextthink < pev->ltime ) + pev->nextthink = pev->ltime + 0.1; + else + pev->nextthink += 0.1; + + pev->avelocity = (m_direction * pev->speed) * pev->movedir; + SetThink( &CMomentaryRotButton::Off ); +} + +void CMomentaryRotButton::UpdateTarget( float value ) +{ + if (!FStringNull(pev->target)) + { + edict_t* pentTarget = NULL; + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->target)); + if (FNullEnt(pentTarget)) + break; + CBaseEntity *pEntity = CBaseEntity::Instance(pentTarget); + if ( pEntity ) + { + pEntity->Use( this, this, USE_SET, value ); + } + } + } +} + +void CMomentaryRotButton::Off( void ) +{ + pev->avelocity = g_vecZero; + m_lastUsed = 0; + if ( FBitSet( pev->spawnflags, SF_PENDULUM_AUTO_RETURN ) && m_returnSpeed > 0 ) + { + SetThink( &CMomentaryRotButton::Return ); + pev->nextthink = pev->ltime + 0.1; + m_direction = -1; + } + else + SetThink( NULL ); +} + +void CMomentaryRotButton::Return( void ) +{ + float value = CBaseToggle::AxisDelta( pev->spawnflags, pev->angles, m_start ) / m_flMoveDistance; + + UpdateAllButtons( value, 0 ); // This will end up calling UpdateSelfReturn() n times, but it still works right + if ( value > 0 ) + UpdateTarget( value ); +} + + +void CMomentaryRotButton::UpdateSelfReturn( float value ) +{ + if ( value <= 0 ) + { + pev->avelocity = g_vecZero; + pev->angles = m_start; + pev->nextthink = -1; + SetThink( NULL ); + } + else + { + pev->avelocity = -m_returnSpeed * pev->movedir; + pev->nextthink = pev->ltime + 0.1; + } +} + + +//---------------------------------------------------------------- +// Spark +//---------------------------------------------------------------- + +class CEnvSpark : public CBaseEntity +{ +public: + void Spawn(void); + void Precache(void); + void EXPORT SparkThink(void); + void EXPORT SparkStart(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT SparkStop(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue(KeyValueData *pkvd); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flDelay; +}; + + +TYPEDESCRIPTION CEnvSpark::m_SaveData[] = +{ + DEFINE_FIELD( CEnvSpark, m_flDelay, FIELD_FLOAT), +}; + +IMPLEMENT_SAVERESTORE( CEnvSpark, CBaseEntity ); + +LINK_ENTITY_TO_CLASS(env_spark, CEnvSpark); +LINK_ENTITY_TO_CLASS(env_debris, CEnvSpark); + +void CEnvSpark::Spawn(void) +{ + SetThink( NULL ); + SetUse( NULL ); + + if (FBitSet(pev->spawnflags, 32)) // Use for on/off + { + if (FBitSet(pev->spawnflags, 64)) // Start on + { + SetThink(&CEnvSpark::SparkThink); // start sparking + SetUse(&CEnvSpark::SparkStop); // set up +USE to stop sparking + } + else + SetUse(&CEnvSpark::SparkStart); + } + else + SetThink(&CEnvSpark::SparkThink); + + pev->nextthink = gpGlobals->time + ( 0.1 + RANDOM_FLOAT ( 0, 1.5 ) ); + + if (m_flDelay <= 0) + m_flDelay = 1.5; + + Precache( ); +} + + +void CEnvSpark::Precache(void) +{ + PRECACHE_SOUND( "buttons/spark1.wav" ); + PRECACHE_SOUND( "buttons/spark2.wav" ); + PRECACHE_SOUND( "buttons/spark3.wav" ); + PRECACHE_SOUND( "buttons/spark4.wav" ); + PRECACHE_SOUND( "buttons/spark5.wav" ); + PRECACHE_SOUND( "buttons/spark6.wav" ); +} + +void CEnvSpark::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "MaxDelay")) + { + m_flDelay = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "style") || + FStrEq(pkvd->szKeyName, "height") || + FStrEq(pkvd->szKeyName, "killtarget") || + FStrEq(pkvd->szKeyName, "value1") || + FStrEq(pkvd->szKeyName, "value2") || + FStrEq(pkvd->szKeyName, "value3")) + pkvd->fHandled = TRUE; + else + CBaseEntity::KeyValue( pkvd ); +} + +void EXPORT CEnvSpark::SparkThink(void) +{ + pev->nextthink = gpGlobals->time + 0.1 + RANDOM_FLOAT (0, m_flDelay); + DoSpark( pev, pev->origin ); +} + +void EXPORT CEnvSpark::SparkStart(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetUse(&CEnvSpark::SparkStop); + SetThink(&CEnvSpark::SparkThink); + pev->nextthink = gpGlobals->time + (0.1 + RANDOM_FLOAT ( 0, m_flDelay)); +} + +void EXPORT CEnvSpark::SparkStop(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetUse(&CEnvSpark::SparkStart); + SetThink(NULL); +} + +#define SF_BTARGET_USE 0x0001 +#define SF_BTARGET_ON 0x0002 + +class CButtonTarget : public CBaseEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + int ObjectCaps( void ); + +}; + +LINK_ENTITY_TO_CLASS( button_target, CButtonTarget ); + +void CButtonTarget::Spawn( void ) +{ + pev->movetype = MOVETYPE_PUSH; + pev->solid = SOLID_BSP; + SET_MODEL(ENT(pev), STRING(pev->model)); + pev->takedamage = DAMAGE_YES; + + if ( FBitSet( pev->spawnflags, SF_BTARGET_ON ) ) + pev->frame = 1; +} + +void CButtonTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, (int)pev->frame ) ) + return; + pev->frame = 1-pev->frame; + if ( pev->frame ) + SUB_UseTargets( pActivator, USE_ON, 0 ); + else + SUB_UseTargets( pActivator, USE_OFF, 0 ); +} + + +int CButtonTarget :: ObjectCaps( void ) +{ + int caps = CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; + + if ( FBitSet(pev->spawnflags, SF_BTARGET_USE) ) + return caps | FCAP_IMPULSE_USE; + else + return caps; +} + + +int CButtonTarget::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + Use( Instance(pevAttacker), this, USE_TOGGLE, 0 ); + + return 1; +} diff --git a/hlsdk/ricochet/dlls/cbase.cpp b/hlsdk/ricochet/dlls/cbase.cpp new file mode 100644 index 00000000..ddf3fd24 --- /dev/null +++ b/hlsdk/ricochet/dlls/cbase.cpp @@ -0,0 +1,796 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "client.h" +#include "decals.h" +#include "gamerules.h" +#include "game.h" + +void EntvarsKeyvalue( entvars_t *pev, KeyValueData *pkvd ); + +extern "C" void PM_Move ( struct playermove_s *ppmove, int server ); +extern "C" void PM_Init ( struct playermove_s *ppmove ); +extern "C" char PM_FindTextureType( char *name ); + +void OnFreeEntPrivateData(edict_s *pEdict); + + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); +extern DLL_GLOBAL Vector g_vecAttackDir; +extern DLL_GLOBAL int g_iSkillLevel; + +static DLL_FUNCTIONS gFunctionTable = +{ + GameDLLInit, //pfnGameInit + DispatchSpawn, //pfnSpawn + DispatchThink, //pfnThink + DispatchUse, //pfnUse + DispatchTouch, //pfnTouch + DispatchBlocked, //pfnBlocked + DispatchKeyValue, //pfnKeyValue + DispatchSave, //pfnSave + DispatchRestore, //pfnRestore + DispatchObjectCollsionBox, //pfnAbsBox + + SaveWriteFields, //pfnSaveWriteFields + SaveReadFields, //pfnSaveReadFields + + SaveGlobalState, //pfnSaveGlobalState + RestoreGlobalState, //pfnRestoreGlobalState + ResetGlobalState, //pfnResetGlobalState + + ClientConnect, //pfnClientConnect + ClientDisconnect, //pfnClientDisconnect + ClientKill, //pfnClientKill + ClientPutInServer, //pfnClientPutInServer + ClientCommand, //pfnClientCommand + ClientUserInfoChanged, //pfnClientUserInfoChanged + ServerActivate, //pfnServerActivate + ServerDeactivate, //pfnServerDeactivate + + PlayerPreThink, //pfnPlayerPreThink + PlayerPostThink, //pfnPlayerPostThink + + StartFrame, //pfnStartFrame + ParmsNewLevel, //pfnParmsNewLevel + ParmsChangeLevel, //pfnParmsChangeLevel + + GetGameDescription, //pfnGetGameDescription Returns string describing current .dll game. + PlayerCustomization, //pfnPlayerCustomization Notifies .dll of new customization for player. + + SpectatorConnect, //pfnSpectatorConnect Called when spectator joins server + SpectatorDisconnect, //pfnSpectatorDisconnect Called when spectator leaves the server + SpectatorThink, //pfnSpectatorThink Called when spectator sends a command packet (usercmd_t) + + Sys_Error, //pfnSys_Error Called when engine has encountered an error + + PM_Move, //pfnPM_Move + PM_Init, //pfnPM_Init Server version of player movement initialization + PM_FindTextureType, //pfnPM_FindTextureType + + SetupVisibility, //pfnSetupVisibility Set up PVS and PAS for networking for this client + UpdateClientData, //pfnUpdateClientData Set up data sent only to specific client + AddToFullPack, //pfnAddToFullPack + CreateBaseline, //pfnCreateBaseline Tweak entity baseline for network encoding, allows setup of player baselines, too. + RegisterEncoders, //pfnRegisterEncoders Callbacks for network encoding + GetWeaponData, //pfnGetWeaponData + CmdStart, //pfnCmdStart + CmdEnd, //pfnCmdEnd + ConnectionlessPacket, //pfnConnectionlessPacket + GetHullBounds, //pfnGetHullBounds + CreateInstancedBaselines, //pfnCreateInstancedBaselines + InconsistentFile, //pfnInconsistentFile + AllowLagCompensation, //pfnAllowLagCompensation +}; + +NEW_DLL_FUNCTIONS gNewDLLFunctions = +{ + OnFreeEntPrivateData, //pfnOnFreeEntPrivateData + GameDLLShutdown, //pfnGameShutdown + ShouldCollide, //pfnShouldCollide +}; + + +static void SetObjectCollisionBox( entvars_t *pev ); + +int GetEntityAPI( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion ) +{ + if ( !pFunctionTable || interfaceVersion != INTERFACE_VERSION ) + { + return FALSE; + } + + memcpy( pFunctionTable, &gFunctionTable, sizeof( DLL_FUNCTIONS ) ); + return TRUE; +} + +int GetEntityAPI2( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ) +{ + if ( !pFunctionTable || *interfaceVersion != INTERFACE_VERSION ) + { + // Tell engine what version we had, so it can figure out who is out of date. + *interfaceVersion = INTERFACE_VERSION; + return FALSE; + } + + memcpy( pFunctionTable, &gFunctionTable, sizeof( DLL_FUNCTIONS ) ); + return TRUE; +} + +int GetNewDLLFunctions(NEW_DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion) +{ + if(!pFunctionTable || *interfaceVersion != NEW_DLL_FUNCTIONS_VERSION) + { + *interfaceVersion = NEW_DLL_FUNCTIONS_VERSION; + return FALSE; + } + + memcpy(pFunctionTable, &gNewDLLFunctions, sizeof(gNewDLLFunctions)); + return TRUE; +} + +int DispatchSpawn( edict_t *pent ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if (pEntity) + { + // Initialize these or entities who don't link to the world won't have anything in here + pEntity->pev->absmin = pEntity->pev->origin - Vector(1,1,1); + pEntity->pev->absmax = pEntity->pev->origin + Vector(1,1,1); + + pEntity->Spawn(); + + // Try to get the pointer again, in case the spawn function deleted the entity. + // UNDONE: Spawn() should really return a code to ask that the entity be deleted, but + // that would touch too much code for me to do that right now. + pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if ( pEntity ) + { + if ( g_pGameRules && !g_pGameRules->IsAllowedToSpawn( pEntity ) ) + return -1; // return that this entity should be deleted + if ( pEntity->pev->flags & FL_KILLME ) + return -1; + } + + + // Handle global stuff here + if ( pEntity && pEntity->pev->globalname ) + { + const globalentity_t *pGlobal = gGlobalState.EntityFromTable( pEntity->pev->globalname ); + if ( pGlobal ) + { + // Already dead? delete + if ( pGlobal->state == GLOBAL_DEAD ) + return -1; + else if ( !FStrEq( STRING(gpGlobals->mapname), pGlobal->levelName ) ) + pEntity->MakeDormant(); // Hasn't been moved to this level yet, wait but stay alive + // In this level & not dead, continue on as normal + } + else + { + // Spawned entities default to 'On' + gGlobalState.EntityAdd( pEntity->pev->globalname, gpGlobals->mapname, GLOBAL_ON ); +// ALERT( at_console, "Added global entity %s (%s)\n", STRING(pEntity->pev->classname), STRING(pEntity->pev->globalname) ); + } + } + + } + + return 0; +} + +void DispatchKeyValue( edict_t *pentKeyvalue, KeyValueData *pkvd ) +{ + if ( !pkvd || !pentKeyvalue ) + return; + + EntvarsKeyvalue( VARS(pentKeyvalue), pkvd ); + + // If the key was an entity variable, or there's no class set yet, don't look for the object, it may + // not exist yet. + if ( pkvd->fHandled || pkvd->szClassName == NULL ) + return; + + // Get the actualy entity object + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pentKeyvalue); + + if ( !pEntity ) + return; + + pEntity->KeyValue( pkvd ); +} + + +// HACKHACK -- this is a hack to keep the node graph entity from "touching" things (like triggers) +// while it builds the graph +BOOL gTouchDisabled = FALSE; +void DispatchTouch( edict_t *pentTouched, edict_t *pentOther ) +{ + if ( gTouchDisabled ) + return; + + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pentTouched); + CBaseEntity *pOther = (CBaseEntity *)GET_PRIVATE( pentOther ); + + if ( pEntity && pOther && ! ((pEntity->pev->flags | pOther->pev->flags) & FL_KILLME) ) + pEntity->Touch( pOther ); +} + + +void DispatchUse( edict_t *pentUsed, edict_t *pentOther ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pentUsed); + CBaseEntity *pOther = (CBaseEntity *)GET_PRIVATE(pentOther); + + if (pEntity && !(pEntity->pev->flags & FL_KILLME) ) + pEntity->Use( pOther, pOther, USE_TOGGLE, 0 ); +} + +void DispatchThink( edict_t *pent ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + if (pEntity) + { + if ( FBitSet( pEntity->pev->flags, FL_DORMANT ) ) + ALERT( at_error, "Dormant entity %s is thinking!!\n", STRING(pEntity->pev->classname) ); + + pEntity->Think(); + } +} + +void DispatchBlocked( edict_t *pentBlocked, edict_t *pentOther ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE( pentBlocked ); + CBaseEntity *pOther = (CBaseEntity *)GET_PRIVATE( pentOther ); + + if (pEntity) + pEntity->Blocked( pOther ); +} + +void DispatchSave( edict_t *pent, SAVERESTOREDATA *pSaveData ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if ( pEntity && pSaveData ) + { + ENTITYTABLE *pTable = &pSaveData->pTable[ pSaveData->currentIndex ]; + + if ( pTable->pent != pent ) + ALERT( at_error, "ENTITY TABLE OR INDEX IS WRONG!!!!\n" ); + + if ( pEntity->ObjectCaps() & FCAP_DONT_SAVE ) + return; + + // These don't use ltime & nextthink as times really, but we'll fudge around it. + if ( pEntity->pev->movetype == MOVETYPE_PUSH ) + { + float delta = pEntity->pev->nextthink - pEntity->pev->ltime; + pEntity->pev->ltime = gpGlobals->time; + pEntity->pev->nextthink = pEntity->pev->ltime + delta; + } + + pTable->location = pSaveData->size; // Remember entity position for file I/O + pTable->classname = pEntity->pev->classname; // Remember entity class for respawn + + CSave saveHelper( pSaveData ); + pEntity->Save( saveHelper ); + + pTable->size = pSaveData->size - pTable->location; // Size of entity block is data size written to block + } +} + +void OnFreeEntPrivateData(edict_s *pEdict) +{ + if(pEdict && pEdict->pvPrivateData) + { + ((CBaseEntity*)pEdict->pvPrivateData)->~CBaseEntity(); + } +} + +// Find the matching global entity. Spit out an error if the designer made entities of +// different classes with the same global name +CBaseEntity *FindGlobalEntity( string_t classname, string_t globalname ) +{ + edict_t *pent = FIND_ENTITY_BY_STRING( NULL, "globalname", STRING(globalname) ); + CBaseEntity *pReturn = CBaseEntity::Instance( pent ); + if ( pReturn ) + { + if ( !FClassnameIs( pReturn->pev, STRING(classname) ) ) + { + ALERT( at_console, "Global entity found %s, wrong class %s\n", STRING(globalname), STRING(pReturn->pev->classname) ); + pReturn = NULL; + } + } + + return pReturn; +} + + +int DispatchRestore( edict_t *pent, SAVERESTOREDATA *pSaveData, int globalEntity ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if ( pEntity && pSaveData ) + { + entvars_t tmpVars; + Vector oldOffset; + + CRestore restoreHelper( pSaveData ); + if ( globalEntity ) + { + CRestore tmpRestore( pSaveData ); + tmpRestore.PrecacheMode( 0 ); + tmpRestore.ReadEntVars( "ENTVARS", &tmpVars ); + + // HACKHACK - reset the save pointers, we're going to restore for real this time + pSaveData->size = pSaveData->pTable[pSaveData->currentIndex].location; + pSaveData->pCurrentData = pSaveData->pBaseData + pSaveData->size; + // ------------------- + + + const globalentity_t *pGlobal = gGlobalState.EntityFromTable( tmpVars.globalname ); + + // Don't overlay any instance of the global that isn't the latest + // pSaveData->szCurrentMapName is the level this entity is coming from + // pGlobla->levelName is the last level the global entity was active in. + // If they aren't the same, then this global update is out of date. + if ( !FStrEq( pSaveData->szCurrentMapName, pGlobal->levelName ) ) + return 0; + + // Compute the new global offset + oldOffset = pSaveData->vecLandmarkOffset; + CBaseEntity *pNewEntity = FindGlobalEntity( tmpVars.classname, tmpVars.globalname ); + if ( pNewEntity ) + { +// ALERT( at_console, "Overlay %s with %s\n", STRING(pNewEntity->pev->classname), STRING(tmpVars.classname) ); + // Tell the restore code we're overlaying a global entity from another level + restoreHelper.SetGlobalMode( 1 ); // Don't overwrite global fields + pSaveData->vecLandmarkOffset = (pSaveData->vecLandmarkOffset - pNewEntity->pev->mins) + tmpVars.mins; + pEntity = pNewEntity;// we're going to restore this data OVER the old entity + pent = ENT( pEntity->pev ); + // Update the global table to say that the global definition of this entity should come from this level + gGlobalState.EntityUpdate( pEntity->pev->globalname, gpGlobals->mapname ); + } + else + { + // This entity will be freed automatically by the engine. If we don't do a restore on a matching entity (below) + // or call EntityUpdate() to move it to this level, we haven't changed global state at all. + return 0; + } + + } + + if ( pEntity->ObjectCaps() & FCAP_MUST_SPAWN ) + { + pEntity->Restore( restoreHelper ); + pEntity->Spawn(); + } + else + { + pEntity->Restore( restoreHelper ); + pEntity->Precache( ); + } + + // Again, could be deleted, get the pointer again. + pEntity = (CBaseEntity *)GET_PRIVATE(pent); + +#if 0 + if ( pEntity && pEntity->pev->globalname && globalEntity ) + { + ALERT( at_console, "Global %s is %s\n", STRING(pEntity->pev->globalname), STRING(pEntity->pev->model) ); + } +#endif + + // Is this an overriding global entity (coming over the transition), or one restoring in a level + if ( globalEntity ) + { +// ALERT( at_console, "After: %f %f %f %s\n", pEntity->pev->origin.x, pEntity->pev->origin.y, pEntity->pev->origin.z, STRING(pEntity->pev->model) ); + pSaveData->vecLandmarkOffset = oldOffset; + if ( pEntity ) + { + UTIL_SetOrigin( pEntity->pev, pEntity->pev->origin ); + pEntity->OverrideReset(); + } + } + else if ( pEntity && pEntity->pev->globalname ) + { + const globalentity_t *pGlobal = gGlobalState.EntityFromTable( pEntity->pev->globalname ); + if ( pGlobal ) + { + // Already dead? delete + if ( pGlobal->state == GLOBAL_DEAD ) + return -1; + else if ( !FStrEq( STRING(gpGlobals->mapname), pGlobal->levelName ) ) + { + pEntity->MakeDormant(); // Hasn't been moved to this level yet, wait but stay alive + } + // In this level & not dead, continue on as normal + } + else + { + ALERT( at_error, "Global Entity %s (%s) not in table!!!\n", STRING(pEntity->pev->globalname), STRING(pEntity->pev->classname) ); + // Spawned entities default to 'On' + gGlobalState.EntityAdd( pEntity->pev->globalname, gpGlobals->mapname, GLOBAL_ON ); + } + } + } + return 0; +} + + +void DispatchObjectCollsionBox( edict_t *pent ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + if (pEntity) + { + pEntity->SetObjectCollisionBox(); + } + else + SetObjectCollisionBox( &pent->v ); +} + + +void SaveWriteFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + CSave saveHelper( pSaveData ); + saveHelper.WriteFields( pname, pBaseData, pFields, fieldCount ); +} + + +void SaveReadFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + CRestore restoreHelper( pSaveData ); + restoreHelper.ReadFields( pname, pBaseData, pFields, fieldCount ); +} + + +edict_t * EHANDLE::Get( void ) +{ + if (m_pent) + { + if (m_pent->serialnumber == m_serialnumber) + return m_pent; + else + return NULL; + } + return NULL; +}; + +edict_t * EHANDLE::Set( edict_t *pent ) +{ + m_pent = pent; + if (pent) + m_serialnumber = m_pent->serialnumber; + return pent; +}; + + +EHANDLE :: operator CBaseEntity *() +{ + return (CBaseEntity *)GET_PRIVATE( Get( ) ); +}; + + +CBaseEntity * EHANDLE :: operator = (CBaseEntity *pEntity) +{ + if (pEntity) + { + m_pent = ENT( pEntity->pev ); + if (m_pent) + m_serialnumber = m_pent->serialnumber; + } + else + { + m_pent = NULL; + m_serialnumber = 0; + } + return pEntity; +} + +EHANDLE :: operator int () +{ + return Get() != NULL; +} + +CBaseEntity * EHANDLE :: operator -> () +{ + return (CBaseEntity *)GET_PRIVATE( Get( ) ); +} + + +// give health +int CBaseEntity :: TakeHealth( float flHealth, int bitsDamageType ) +{ + if (!pev->takedamage) + return 0; + +// heal + if ( pev->health >= pev->max_health ) + return 0; + + pev->health += flHealth; + + if (pev->health > pev->max_health) + pev->health = pev->max_health; + + return 1; +} + +// inflict damage on this entity. bitsDamageType indicates type of damage inflicted, ie: DMG_CRUSH + +int CBaseEntity :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + Vector vecTemp; + + if (!pev->takedamage) + return 0; + + // UNDONE: some entity types may be immune or resistant to some bitsDamageType + + // if Attacker == Inflictor, the attack was a melee or other instant-hit attack. + // (that is, no actual entity projectile was involved in the attack so use the shooter's origin). + if ( pevAttacker == pevInflictor ) + { + vecTemp = pevInflictor->origin - ( VecBModelOrigin(pev) ); + } + else + // an actual missile was involved. + { + vecTemp = pevInflictor->origin - ( VecBModelOrigin(pev) ); + } + +// this global is still used for glass and other non-monster killables, along with decals. + g_vecAttackDir = vecTemp.Normalize(); + +// save damage based on the target's armor level + +// figure momentum add (don't let hurt brushes or other triggers move player) + if ((!FNullEnt(pevInflictor)) && (pev->movetype == MOVETYPE_WALK || pev->movetype == MOVETYPE_STEP) && (pevAttacker->solid != SOLID_TRIGGER) ) + { + Vector vecDir = pev->origin - (pevInflictor->absmin + pevInflictor->absmax) * 0.5; + vecDir = vecDir.Normalize(); + + float flForce = flDamage * ((32 * 32 * 72.0) / (pev->size.x * pev->size.y * pev->size.z)) * 5; + + if (flForce > 1000.0) + flForce = 1000.0; + pev->velocity = pev->velocity + vecDir * flForce; + } + +// do the damage + pev->health -= flDamage; + if (pev->health <= 0) + { + Killed( pevAttacker, GIB_NORMAL ); + return 0; + } + + return 1; +} + + +void CBaseEntity :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->takedamage = DAMAGE_NO; + pev->deadflag = DEAD_DEAD; + UTIL_Remove( this ); +} + + +CBaseEntity *CBaseEntity::GetNextTarget( void ) +{ + if ( FStringNull( pev->target ) ) + return NULL; + edict_t *pTarget = FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(pev->target) ); + if ( FNullEnt(pTarget) ) + return NULL; + + return Instance( pTarget ); +} + +// Global Savedata for Delay +TYPEDESCRIPTION CBaseEntity::m_SaveData[] = +{ + DEFINE_FIELD( CBaseEntity, m_pGoalEnt, FIELD_CLASSPTR ), + + DEFINE_FIELD( CBaseEntity, m_pfnThink, FIELD_FUNCTION ), // UNDONE: Build table of these!!! + DEFINE_FIELD( CBaseEntity, m_pfnTouch, FIELD_FUNCTION ), + DEFINE_FIELD( CBaseEntity, m_pfnUse, FIELD_FUNCTION ), + DEFINE_FIELD( CBaseEntity, m_pfnBlocked, FIELD_FUNCTION ), +}; + + +int CBaseEntity::Save( CSave &save ) +{ + if ( save.WriteEntVars( "ENTVARS", pev ) ) + return save.WriteFields( "BASE", this, m_SaveData, ARRAYSIZE(m_SaveData) ); + + return 0; +} + +int CBaseEntity::Restore( CRestore &restore ) +{ + int status; + + status = restore.ReadEntVars( "ENTVARS", pev ); + if ( status ) + status = restore.ReadFields( "BASE", this, m_SaveData, ARRAYSIZE(m_SaveData) ); + + if ( pev->modelindex != 0 && !FStringNull(pev->model) ) + { + Vector mins, maxs; + mins = pev->mins; // Set model is about to destroy these + maxs = pev->maxs; + + + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL(ENT(pev), STRING(pev->model)); + UTIL_SetSize(pev, mins, maxs); // Reset them + } + + return status; +} + + +// Initialize absmin & absmax to the appropriate box +void SetObjectCollisionBox( entvars_t *pev ) +{ + if ( (pev->solid == SOLID_BSP) && + (pev->angles.x || pev->angles.y|| pev->angles.z) ) + { // expand for rotation + float max, v; + int i; + + max = 0; + for (i=0 ; i<3 ; i++) + { + v = fabs( pev->mins[i]); + if (v > max) + max = v; + v = fabs( pev->maxs[i]); + if (v > max) + max = v; + } + for (i=0 ; i<3 ; i++) + { + pev->absmin[i] = pev->origin[i] - max; + pev->absmax[i] = pev->origin[i] + max; + } + } + else + { + pev->absmin = pev->origin + pev->mins; + pev->absmax = pev->origin + pev->maxs; + } + + pev->absmin.x -= 1; + pev->absmin.y -= 1; + pev->absmin.z -= 1; + pev->absmax.x += 1; + pev->absmax.y += 1; + pev->absmax.z += 1; +} + + +void CBaseEntity::SetObjectCollisionBox( void ) +{ + ::SetObjectCollisionBox( pev ); +} + + +int CBaseEntity :: Intersects( CBaseEntity *pOther ) +{ + if ( pOther->pev->absmin.x > pev->absmax.x || + pOther->pev->absmin.y > pev->absmax.y || + pOther->pev->absmin.z > pev->absmax.z || + pOther->pev->absmax.x < pev->absmin.x || + pOther->pev->absmax.y < pev->absmin.y || + pOther->pev->absmax.z < pev->absmin.z ) + return 0; + return 1; +} + +void CBaseEntity :: MakeDormant( void ) +{ + SetBits( pev->flags, FL_DORMANT ); + + // Don't touch + pev->solid = SOLID_NOT; + // Don't move + pev->movetype = MOVETYPE_NONE; + // Don't draw + SetBits( pev->effects, EF_NODRAW ); + // Don't think + pev->nextthink = 0; + // Relink + UTIL_SetOrigin( pev, pev->origin ); +} + +int CBaseEntity :: IsDormant( void ) +{ + return FBitSet( pev->flags, FL_DORMANT ); +} + +BOOL CBaseEntity :: IsInWorld( void ) +{ + // position + if (pev->origin.x >= 4096) return FALSE; + if (pev->origin.y >= 4096) return FALSE; + if (pev->origin.z >= 4096) return FALSE; + if (pev->origin.x <= -4096) return FALSE; + if (pev->origin.y <= -4096) return FALSE; + if (pev->origin.z <= -4096) return FALSE; + // speed + if (pev->velocity.x >= 2000) return FALSE; + if (pev->velocity.y >= 2000) return FALSE; + if (pev->velocity.z >= 2000) return FALSE; + if (pev->velocity.x <= -2000) return FALSE; + if (pev->velocity.y <= -2000) return FALSE; + if (pev->velocity.z <= -2000) return FALSE; + + return TRUE; +} + +int CBaseEntity::ShouldToggle( USE_TYPE useType, BOOL currentState ) +{ + if ( useType != USE_TOGGLE && useType != USE_SET ) + { + if ( (currentState && useType == USE_ON) || (!currentState && useType == USE_OFF) ) + return 0; + } + return 1; +} + + +int CBaseEntity :: DamageDecal( int bitsDamageType ) +{ + if ( pev->rendermode == kRenderTransAlpha ) + return -1; + + if ( pev->rendermode != kRenderNormal ) + return DECAL_BPROOF1; + + return DECAL_GUNSHOT1 + RANDOM_LONG(0,4); +} + + + +// NOTE: szName must be a pointer to constant memory, e.g. "monster_class" because the entity +// will keep a pointer to it after this call. +CBaseEntity * CBaseEntity::Create( char *szName, const Vector &vecOrigin, const Vector &vecAngles, edict_t *pentOwner ) +{ + edict_t *pent; + CBaseEntity *pEntity; + + pent = CREATE_NAMED_ENTITY( MAKE_STRING( szName )); + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in Create!\n" ); + return NULL; + } + pEntity = Instance( pent ); + pEntity->pev->owner = pentOwner; + pEntity->pev->origin = vecOrigin; + pEntity->pev->angles = vecAngles; + DispatchSpawn( pEntity->edict() ); + return pEntity; +} + + diff --git a/hlsdk/ricochet/dlls/cbase.h b/hlsdk/ricochet/dlls/cbase.h new file mode 100644 index 00000000..e1cb909b --- /dev/null +++ b/hlsdk/ricochet/dlls/cbase.h @@ -0,0 +1,798 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +Class Hierachy + +CBaseEntity + CBaseDelay + CBaseToggle + CBaseItem + CBaseMonster + CBaseCycler + CBasePlayer + CBaseGroup +*/ + +#define MAX_PATH_SIZE 10 // max number of nodes available for a path. + +// These are caps bits to indicate what an object's capabilities (currently used for save/restore and level transitions) +#define FCAP_CUSTOMSAVE 0x00000001 +#define FCAP_ACROSS_TRANSITION 0x00000002 // should transfer between transitions +#define FCAP_MUST_SPAWN 0x00000004 // Spawn after restore +#define FCAP_DONT_SAVE 0x80000000 // Don't save this +#define FCAP_IMPULSE_USE 0x00000008 // can be used by the player +#define FCAP_CONTINUOUS_USE 0x00000010 // can be used by the player +#define FCAP_ONOFF_USE 0x00000020 // can be used by the player +#define FCAP_DIRECTIONAL_USE 0x00000040 // Player sends +/- 1 when using (currently only tracktrains) +#define FCAP_MASTER 0x00000080 // Can be used to "master" other entities (like multisource) + +// UNDONE: This will ignore transition volumes (trigger_transition), but not the PVS!!! +#define FCAP_FORCE_TRANSITION 0x00000080 // ALWAYS goes across transitions + +#include "saverestore.h" +#include "schedule.h" + +#ifndef MONSTEREVENT_H +#include "monsterevent.h" +#endif + +// C functions for external declarations that call the appropriate C++ methods + +#ifdef _WIN32 +#define EXPORT _declspec( dllexport ) +#else +#define EXPORT /* */ +#endif + +extern "C" EXPORT int GetEntityAPI( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion ); +extern "C" EXPORT int GetEntityAPI2( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ); +extern "C" EXPORT int GetNewDLLFunctions( NEW_DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ); + +extern int DispatchSpawn( edict_t *pent ); +extern void DispatchKeyValue( edict_t *pentKeyvalue, KeyValueData *pkvd ); +extern void DispatchTouch( edict_t *pentTouched, edict_t *pentOther ); +extern void DispatchUse( edict_t *pentUsed, edict_t *pentOther ); +extern void DispatchThink( edict_t *pent ); +extern void DispatchBlocked( edict_t *pentBlocked, edict_t *pentOther ); +extern void DispatchSave( edict_t *pent, SAVERESTOREDATA *pSaveData ); +extern int DispatchRestore( edict_t *pent, SAVERESTOREDATA *pSaveData, int globalEntity ); +extern void DispatchObjectCollsionBox( edict_t *pent ); +extern void SaveWriteFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); +extern void SaveReadFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); +extern void SaveGlobalState( SAVERESTOREDATA *pSaveData ); +extern void RestoreGlobalState( SAVERESTOREDATA *pSaveData ); +extern void ResetGlobalState( void ); + +typedef enum { USE_OFF = 0, USE_ON = 1, USE_SET = 2, USE_TOGGLE = 3 } USE_TYPE; + +extern void FireTargets( const char *targetName, CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +typedef void (CBaseEntity::*BASEPTR)(void); +typedef void (CBaseEntity::*ENTITYFUNCPTR)(CBaseEntity *pOther ); +typedef void (CBaseEntity::*USEPTR)( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +// For CLASSIFY +#define CLASS_NONE 0 +#define CLASS_MACHINE 1 +#define CLASS_PLAYER 2 +#define CLASS_HUMAN_PASSIVE 3 +#define CLASS_HUMAN_MILITARY 4 +#define CLASS_ALIEN_MILITARY 5 +#define CLASS_ALIEN_PASSIVE 6 +#define CLASS_ALIEN_MONSTER 7 +#define CLASS_ALIEN_PREY 8 +#define CLASS_ALIEN_PREDATOR 9 +#define CLASS_INSECT 10 +#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; +class CBasePlayerItem; +class CSquadMonster; + + +#define SF_NORESPAWN ( 1 << 30 )// !!!set this bit on guns and stuff that should never respawn. + +// +// EHANDLE. Safe way to point to CBaseEntities who may die between frames +// +class EHANDLE +{ +private: + edict_t *m_pent; + int m_serialnumber; +public: + edict_t *Get( void ); + edict_t *Set( edict_t *pent ); + + operator int (); + + operator CBaseEntity *(); + + CBaseEntity * operator = (CBaseEntity *pEntity); + CBaseEntity * operator ->(); +}; + + +// +// Base Entity. All entity types derive from this +// +class CBaseEntity +{ +public: + // Constructor. Set engine to use C/C++ callback functions + // pointers to engine data + entvars_t *pev; // Don't need to save/restore this pointer, the engine resets it + + // path corners + CBaseEntity *m_pGoalEnt;// path corner we are heading towards + CBaseEntity *m_pLink;// used for temporary link-list operations. + + // initialization functions + virtual void Spawn( void ) { return; } + virtual void Precache( void ) { return; } + virtual void KeyValue( KeyValueData* pkvd) { pkvd->fHandled = FALSE; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual int ObjectCaps( void ) { return FCAP_ACROSS_TRANSITION; } + virtual void Activate( void ) {} + + // Setup the object->object collision box (pev->mins / pev->maxs is the object->world collision box) + virtual void SetObjectCollisionBox( void ); + +// Classify - returns the type of group (i.e, "houndeye", or "human military" so that monsters with different classnames +// still realize that they are teammates. (overridden for monsters that form groups) + virtual int Classify ( void ) { return CLASS_NONE; }; + virtual void DeathNotice ( entvars_t *pevChild ) {}// monster maker children use this to tell the monster maker that they have died. + + + static TYPEDESCRIPTION m_SaveData[]; + + virtual void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + 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 ); + virtual int BloodColor( void ) { return DONT_BLEED; } + virtual void TraceBleed( float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); + virtual BOOL IsTriggered( CBaseEntity *pActivator ) {return TRUE;} + virtual CBaseMonster *MyMonsterPointer( void ) { return NULL;} + virtual CSquadMonster *MySquadMonsterPointer( void ) { return NULL;} + virtual int GetToggleState( void ) { return TS_AT_TOP; } + virtual void AddPoints( int score, BOOL bAllowNegativeScore ) {} + virtual void AddPointsToTeam( int score, BOOL bAllowNegativeScore ) {} + virtual BOOL AddPlayerItem( CBasePlayerItem *pItem ) { return 0; } + virtual BOOL RemovePlayerItem( CBasePlayerItem *pItem ) { return 0; } + virtual int GiveAmmo( int iAmount, char *szName, int iMax ) { return -1; }; + virtual float GetDelay( void ) { return 0; } + virtual int IsMoving( void ) { return pev->velocity != g_vecZero; } + virtual void OverrideReset( void ) {} + virtual int DamageDecal( int bitsDamageType ); + // This is ONLY used by the node graph to test movement through a door + virtual void SetToggleState( int state ) {} + virtual void StartSneaking( void ) {} + virtual void StopSneaking( void ) {} + virtual BOOL OnControls( entvars_t *pev ) { return FALSE; } + virtual BOOL IsSneaking( void ) { return FALSE; } + virtual BOOL IsAlive( void ) { return (pev->deadflag == DEAD_NO) && pev->health > 0; } + virtual BOOL IsBSPModel( void ) { return pev->solid == SOLID_BSP || pev->movetype == MOVETYPE_PUSHSTEP; } + virtual BOOL ReflectGauss( void ) { return ( IsBSPModel() && !pev->takedamage ); } + virtual BOOL HasTarget( string_t targetname ) { return FStrEq(STRING(targetname), STRING(pev->targetname) ); } + virtual BOOL IsInWorld( void ); + virtual BOOL IsPlayer( void ) { return FALSE; } + virtual BOOL IsNetClient( void ) { return FALSE; } + virtual const char *TeamID( void ) { return ""; } + + virtual BOOL IsDisc( void ) { return FALSE; }; + +// virtual void SetActivator( CBaseEntity *pActivator ) {} + virtual CBaseEntity *GetNextTarget( void ); + + // fundamental callbacks + void (CBaseEntity ::*m_pfnThink)(void); + void (CBaseEntity ::*m_pfnTouch)( CBaseEntity *pOther ); + void (CBaseEntity ::*m_pfnUse)( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void (CBaseEntity ::*m_pfnBlocked)( CBaseEntity *pOther ); + + virtual void Think( void ) { if (m_pfnThink) (this->*m_pfnThink)(); }; + virtual void Touch( CBaseEntity *pOther ) { if (m_pfnTouch) (this->*m_pfnTouch)( pOther ); }; + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) + { + if (m_pfnUse) + (this->*m_pfnUse)( pActivator, pCaller, useType, value ); + } + virtual void Blocked( CBaseEntity *pOther ) { if (m_pfnBlocked) (this->*m_pfnBlocked)( pOther ); }; + + // allow engine to allocate instance data + void *operator new( size_t stAllocateBlock, entvars_t *pev ) + { + return (void *)ALLOC_PRIVATE(ENT(pev), stAllocateBlock); + }; + + // don't use this. +#if _MSC_VER >= 1200 // only build this code if MSVC++ 6.0 or higher + void operator delete(void *pMem, entvars_t *pev) + { + pev->flags |= FL_KILLME; + }; +#endif + + void UpdateOnRemove( void ); + + // common member functions + void EXPORT SUB_Remove( void ); + void EXPORT SUB_DoNothing( void ); + void EXPORT SUB_StartFadeOut ( void ); + void EXPORT SUB_FadeOut ( void ); + 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 ); + + virtual CBaseEntity *Respawn( void ) { return NULL; } + + void SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ); + // Do the bounding boxes of these two intersect? + int Intersects( CBaseEntity *pOther ); + void MakeDormant( void ); + int IsDormant( void ); + BOOL IsLockedByMaster( void ) { return FALSE; } + +#ifdef _DEBUG + static CBaseEntity *Instance( edict_t *pent ) + { + if ( !pent ) + pent = ENT(0); + CBaseEntity *pEnt = (CBaseEntity *)GET_PRIVATE(pent); + ASSERT(pEnt!=NULL); + return pEnt; + } +#else + static CBaseEntity *Instance( edict_t *pent ) + { + if ( !pent ) + pent = ENT(0); + CBaseEntity *pEnt = (CBaseEntity *)GET_PRIVATE(pent); + return pEnt; + } +#endif + + static CBaseEntity *Instance( entvars_t *pev ) { return Instance( ENT( pev ) ); } + static CBaseEntity *Instance( int eoffset) { return Instance( ENT( eoffset) ); } + + CBaseMonster *GetMonsterPointer( entvars_t *pevMonster ) + { + CBaseEntity *pEntity = Instance( pevMonster ); + if ( pEntity ) + return pEntity->MyMonsterPointer(); + return NULL; + } + CBaseMonster *GetMonsterPointer( edict_t *pentMonster ) + { + CBaseEntity *pEntity = Instance( pentMonster ); + if ( pEntity ) + return pEntity->MyMonsterPointer(); + return NULL; + } + + + // Ugly code to lookup all functions to make sure they are exported when set. +#ifdef _DEBUG + void FunctionCheck( void *pFunction, char *name ) + { +#ifdef _WIN32 + if (pFunction && !NAME_FOR_FUNCTION((unsigned long)(pFunction)) ) + ALERT( at_error, "No EXPORT: %s:%s (%08lx)\n", STRING(pev->classname), name, (unsigned long)pFunction ); +#endif // _WIN32 + } + + BASEPTR ThinkSet( BASEPTR func, char *name ) + { + m_pfnThink = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnThink)))), name ); + return func; + } + ENTITYFUNCPTR TouchSet( ENTITYFUNCPTR func, char *name ) + { + m_pfnTouch = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnTouch)))), name ); + return func; + } + USEPTR UseSet( USEPTR func, char *name ) + { + m_pfnUse = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnUse)))), name ); + return func; + } + ENTITYFUNCPTR BlockedSet( ENTITYFUNCPTR func, char *name ) + { + m_pfnBlocked = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnBlocked)))), name ); + return func; + } + +#endif + + + // virtual functions used by a few classes + + // used by monsters that are created by the MonsterMaker + virtual void UpdateOwner( void ) { return; }; + + + // + static CBaseEntity *Create( char *szName, const Vector &vecOrigin, const Vector &vecAngles, edict_t *pentOwner = NULL ); + + virtual BOOL FBecomeProne( void ) {return FALSE;}; + edict_t *edict() { return ENT( pev ); }; + EOFFSET eoffset( ) { return OFFSET( pev ); }; + int entindex( ) { return ENTINDEX( edict() ); }; + + virtual Vector Center( ) { return (pev->absmax + pev->absmin) * 0.5; }; // center point of entity + virtual Vector EyePosition( ) { return pev->origin + pev->view_ofs; }; // position of eyes + virtual Vector EarPosition( ) { return pev->origin + pev->view_ofs; }; // position of ears + virtual Vector BodyTarget( const Vector &posSrc ) { return Center( ); }; // position to shoot at + + virtual int Illumination( ) { return GETENTITYILLUM( ENT( pev ) ); }; + + virtual BOOL FVisible ( CBaseEntity *pEntity ); + virtual BOOL FVisible ( const Vector &vecOrigin ); + + // Last touched by Jump pad + float m_flTouchedByJumpPad; +}; + + + +// 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. + +#ifdef _DEBUG + +#define SetThink( a ) ThinkSet( static_cast (a), #a ) +#define SetTouch( a ) TouchSet( static_cast (a), #a ) +#define SetUse( a ) UseSet( static_cast (a), #a ) +#define SetBlocked( a ) BlockedSet( static_cast (a), #a ) + +#else + +#define SetThink( a ) m_pfnThink = static_cast (a) +#define SetTouch( a ) m_pfnTouch = static_cast (a) +#define SetUse( a ) m_pfnUse = static_cast (a) +#define SetBlocked( a ) m_pfnBlocked = static_cast (a) + +#endif + + +class CPointEntity : public CBaseEntity +{ +public: + void Spawn( void ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +private: +}; + + +typedef struct locksounds // sounds that doors and buttons make when locked/unlocked +{ + string_t sLockedSound; // sound a door makes when it's locked + string_t sLockedSentence; // sentence group played when door is locked + string_t sUnlockedSound; // sound a door makes when it's unlocked + string_t sUnlockedSentence; // sentence group played when door is unlocked + + int iLockedSentence; // which sentence in sentence group to play next + int iUnlockedSentence; // which sentence in sentence group to play next + + float flwaitSound; // time delay between playing consecutive 'locked/unlocked' sounds + float flwaitSentence; // time delay between playing consecutive sentences + BYTE bEOFLocked; // true if hit end of list of locked sentences + BYTE bEOFUnlocked; // true if hit end of list of unlocked sentences +} locksound_t; + +void PlayLockSounds(entvars_t *pev, locksound_t *pls, int flocked, int fbutton); + +// +// MultiSouce +// + +#define MAX_MULTI_TARGETS 16 // maximum number of targets a single multi_manager entity may be assigned. +#define MS_MAX_TARGETS 32 + +class CMultiSource : public CPointEntity +{ +public: + void Spawn( ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int ObjectCaps( void ) { return (CPointEntity::ObjectCaps() | FCAP_MASTER); } + BOOL IsTriggered( CBaseEntity *pActivator ); + void EXPORT Register( void ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + EHANDLE m_rgEntities[MS_MAX_TARGETS]; + int m_rgTriggered[MS_MAX_TARGETS]; + + int m_iTotal; + string_t m_globalstate; +}; + + +// +// generic Delay entity. +// +class CBaseDelay : public CBaseEntity +{ +public: + float m_flDelay; + int m_iszKillTarget; + + virtual void KeyValue( KeyValueData* pkvd); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + // common member functions + void SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ); + void EXPORT DelayThink( void ); +}; + + +class CBaseAnimating : public CBaseDelay +{ +public: + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + // Basic Monster Animation functions + float StudioFrameAdvance( float flInterval = 0.0 ); // accumulate animation frame time from last time called until now + int GetSequenceFlags( void ); + int LookupActivity ( int activity ); + int LookupActivityHeaviest ( int activity ); + int LookupSequence ( const char *label ); + void ResetSequenceInfo ( ); + void DispatchAnimEvents ( float flFutureInterval = 0.1 ); // Handle events that have happend since last time called up until X seconds into the future + virtual void HandleAnimEvent( MonsterEvent_t *pEvent ) { return; }; + float SetBoneController ( int iController, float flValue ); + void InitBoneControllers ( void ); + float SetBlending ( int iBlender, float flValue ); + void GetBonePosition ( int iBone, Vector &origin, Vector &angles ); + void GetAutomovement( Vector &origin, Vector &angles, float flInterval = 0.1 ); + int FindTransition( int iEndingSequence, int iGoalSequence, int *piDir ); + void GetAttachment ( int iAttachment, Vector &origin, Vector &angles ); + void SetBodygroup( int iGroup, int iValue ); + int GetBodygroup( int iGroup ); + int ExtractBbox( int sequence, float *mins, float *maxs ); + void SetSequenceBox( void ); + + // animation needs + float m_flFrameRate; // computed FPS for current sequence + float m_flGroundSpeed; // computed linear movement rate for current sequence + float m_flLastEventCheck; // last time the event list was checked + BOOL m_fSequenceFinished;// flag set when StudioAdvanceFrame moves across a frame boundry + BOOL m_fSequenceLoops; // true if the sequence loops +}; + + +// +// generic Toggle entity. +// +#define SF_ITEM_USE_ONLY 256 // ITEM_USE_ONLY = BUTTON_USE_ONLY = DOOR_USE_ONLY!!! + +class CBaseToggle : public CBaseAnimating +{ +public: + void KeyValue( KeyValueData *pkvd ); + + TOGGLE_STATE m_toggle_state; + float m_flActivateFinished;//like attack_finished, but for doors + float m_flMoveDistance;// how far a door should slide or rotate + float m_flWait; + float m_flLip; + float m_flTWidth;// for plats + float m_flTLength;// for plats + + Vector m_vecPosition1; + Vector m_vecPosition2; + Vector m_vecAngle1; + Vector m_vecAngle2; + + int m_cTriggersLeft; // trigger_counter only, # of activations remaining + float m_flHeight; + EHANDLE m_hActivator; + void (CBaseToggle::*m_pfnCallWhenMoveDone)(void); + Vector m_vecFinalDest; + Vector m_vecFinalAngle; + + int m_bitsDamageInflict; // DMG_ damage type that the door or tigger does + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + virtual int GetToggleState( void ) { return m_toggle_state; } + virtual float GetDelay( void ) { return m_flWait; } + + // common member functions + void LinearMove( Vector vecDest, float flSpeed ); + void EXPORT LinearMoveDone( void ); + void AngularMove( Vector vecDestAngle, float flSpeed ); + void EXPORT AngularMoveDone( void ); + BOOL IsLockedByMaster( void ); + + static float AxisValue( int flags, const Vector &angles ); + static void AxisDir( entvars_t *pev ); + static float AxisDelta( int flags, const Vector &angle1, const Vector &angle2 ); + + string_t m_sMaster; // If this button has a master switch, this is the targetname. + // A master switch must be of the multisource type. If all + // of the switches in the multisource have been triggered, then + // the button will be allowed to operate. Otherwise, it will be + // deactivated. +}; +#define SetMoveDone( a ) m_pfnCallWhenMoveDone = static_cast (a) + + +// people gib if their health is <= this at the time of death +#define GIB_HEALTH_VALUE -30 + +#define ROUTE_SIZE 8 // how many waypoints a monster can store at one time +#define MAX_OLD_ENEMIES 4 // how many old enemies to remember + +#define bits_CAP_DUCK ( 1 << 0 )// crouch +#define bits_CAP_JUMP ( 1 << 1 )// jump/leap +#define bits_CAP_STRAFE ( 1 << 2 )// strafe ( walk/run sideways) +#define bits_CAP_SQUAD ( 1 << 3 )// can form squads +#define bits_CAP_SWIM ( 1 << 4 )// proficiently navigate in water +#define bits_CAP_CLIMB ( 1 << 5 )// climb ladders/ropes +#define bits_CAP_USE ( 1 << 6 )// open doors/push buttons/pull levers +#define bits_CAP_HEAR ( 1 << 7 )// can hear forced sounds +#define bits_CAP_AUTO_DOORS ( 1 << 8 )// can trigger auto doors +#define bits_CAP_OPEN_DOORS ( 1 << 9 )// can open manual doors +#define bits_CAP_TURN_HEAD ( 1 << 10)// can turn head, always bone controller 0 + +#define bits_CAP_RANGE_ATTACK1 ( 1 << 11)// can do a range attack 1 +#define bits_CAP_RANGE_ATTACK2 ( 1 << 12)// can do a range attack 2 +#define bits_CAP_MELEE_ATTACK1 ( 1 << 13)// can do a melee attack 1 +#define bits_CAP_MELEE_ATTACK2 ( 1 << 14)// can do a melee attack 2 + +#define bits_CAP_FLY ( 1 << 15)// can fly, move all around + +#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 +#define DMG_CRUSH (1 << 0) // crushed by falling or moving object +#define DMG_BULLET (1 << 1) // shot +#define DMG_SLASH (1 << 2) // cut, clawed, stabbed +#define DMG_BURN (1 << 3) // heat burned +#define DMG_FREEZE (1 << 4) // frozen +#define DMG_FALL (1 << 5) // fell too far +#define DMG_BLAST (1 << 6) // explosive blast damage +#define DMG_CLUB (1 << 7) // crowbar, punch, headbutt +#define DMG_SHOCK (1 << 8) // electric shock +#define DMG_SONIC (1 << 9) // sound pulse shockwave +#define DMG_ENERGYBEAM (1 << 10) // laser or other high energy beam +#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. +#define DMG_DROWN (1 << 14) // Drowning +// time-based damage +#define DMG_TIMEBASED (~(0x3fff)) // mask for time-based damage + +#define DMG_PARALYZE (1 << 15) // slows affected creature down +#define DMG_NERVEGAS (1 << 16) // nerve toxins, very bad +#define DMG_POISON (1 << 17) // blood poisioning +#define DMG_RADIATION (1 << 18) // radiation exposure +#define DMG_DROWNRECOVER (1 << 19) // drowning recovery +#define DMG_ACID (1 << 20) // toxic chemicals or acid burns +#define DMG_SLOWBURN (1 << 21) // in an oven +#define DMG_SLOWFREEZE (1 << 22) // in a subzero freezer +#define DMG_MORTAR (1 << 23) // Hit by air raid (done to distinguish grenade from mortar) + +// 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 ) + +// these are the damage types that have client hud art +#define DMG_SHOWNHUD (DMG_POISON | DMG_ACID | DMG_FREEZE | DMG_SLOWFREEZE | DMG_DROWN | DMG_BURN | DMG_SLOWBURN | DMG_NERVEGAS | DMG_RADIATION | DMG_SHOCK) + +// NOTE: tweak these values based on gameplay feedback: + +#define PARALYZE_DURATION 2 // number of 2 second intervals to take damage +#define PARALYZE_DAMAGE 1.0 // damage to take each 2 second interval + +#define NERVEGAS_DURATION 2 +#define NERVEGAS_DAMAGE 5.0 + +#define POISON_DURATION 5 +#define POISON_DAMAGE 2.0 + +#define RADIATION_DURATION 2 +#define RADIATION_DAMAGE 1.0 + +#define ACID_DURATION 2 +#define ACID_DAMAGE 5.0 + +#define SLOWBURN_DURATION 2 +#define SLOWBURN_DAMAGE 1.0 + +#define SLOWFREEZE_DURATION 2 +#define SLOWFREEZE_DAMAGE 1.0 + + +#define itbd_Paralyze 0 +#define itbd_NerveGas 1 +#define itbd_Poison 2 +#define itbd_Radiation 3 +#define itbd_DrownRecover 4 +#define itbd_Acid 5 +#define itbd_SlowBurn 6 +#define itbd_SlowFreeze 7 +#define CDMG_TIMEBASED 8 + +// when calling KILLED(), a value that governs gib behavior is expected to be +// one of these three values +#define GIB_NORMAL 0// gib if entity was overkilled +#define GIB_NEVER 1// never gib, no matter how much death damage is done ( freezing, etc ) +#define GIB_ALWAYS 2// always gib ( Houndeye Shock, Barnacle Bite ) + +class CBaseMonster; +class CCineMonster; +class CSound; + +#include "basemonster.h" + + +char *ButtonSound( int sound ); // get string of button sound number + + +// +// Generic Button +// +class CBaseButton : public CBaseToggle +{ +public: + void Spawn( void ); + virtual void Precache( void ); + void RotSpawn( void ); + virtual void KeyValue( KeyValueData* pkvd); + + void ButtonActivate( ); + void SparkSoundCache( void ); + + void EXPORT ButtonShot( void ); + void EXPORT ButtonTouch( CBaseEntity *pOther ); + void EXPORT ButtonSpark ( void ); + void EXPORT TriggerAndWait( void ); + void EXPORT ButtonReturn( void ); + void EXPORT ButtonBackHome( void ); + void EXPORT ButtonUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + enum BUTTON_CODE { BUTTON_NOTHING, BUTTON_ACTIVATE, BUTTON_RETURN }; + BUTTON_CODE ButtonResponseToTouch( void ); + + static TYPEDESCRIPTION m_SaveData[]; + // Buttons that don't take damage can be IMPULSE used + virtual int ObjectCaps( void ) { return (CBaseToggle:: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | (pev->takedamage?0:FCAP_IMPULSE_USE); } + + BOOL m_fStayPushed; // button stays pushed in until touched again? + BOOL m_fRotating; // a rotating button? default is a sliding button. + + string_t m_strChangeTarget; // if this field is not null, this is an index into the engine string array. + // when this button is touched, it's target entity's TARGET field will be set + // to the button's ChangeTarget. This allows you to make a func_train switch paths, etc. + + locksound_t m_ls; // door lock sounds + + BYTE m_bLockedSound; // ordinals from entity selection + BYTE m_bLockedSentence; + BYTE m_bUnlockedSound; + BYTE m_bUnlockedSentence; + int m_sounds; +}; + +// +// Weapons +// + +#define BAD_WEAPON 0x00007FFF + +// +// Converts a entvars_t * to a class pointer +// It will allocate the class and entity if necessary +// +template T * GetClassPtr( T *a ) +{ + entvars_t *pev = (entvars_t *)a; + + // allocate entity if necessary + if (pev == NULL) + pev = VARS(CREATE_ENTITY()); + + // get the private data + a = (T *)GET_PRIVATE(ENT(pev)); + + if (a == NULL) + { + // allocate private data + a = new(pev) T; + a->pev = pev; + } + return a; +} + + +/* +bit_PUSHBRUSH_DATA | bit_TOGGLE_DATA +bit_MONSTER_DATA +bit_DELAY_DATA +bit_TOGGLE_DATA | bit_DELAY_DATA | bit_MONSTER_DATA +bit_PLAYER_DATA | bit_MONSTER_DATA +bit_MONSTER_DATA | CYCLER_DATA +bit_LIGHT_DATA +path_corner_data +bit_MONSTER_DATA | wildcard_data +bit_MONSTER_DATA | bit_GROUP_DATA +boid_flock_data +boid_data +CYCLER_DATA +bit_ITEM_DATA +bit_ITEM_DATA | func_hud_data +bit_TOGGLE_DATA | bit_ITEM_DATA +EOFFSET +env_sound_data +env_sound_data +push_trigger_data +*/ + +#define TRACER_FREQ 4 // Tracers fire every 4 bullets + +typedef struct _SelAmmo +{ + BYTE Ammo1Type; + BYTE Ammo1; + BYTE Ammo2Type; + BYTE Ammo2; +} SelAmmo; + + +// this moved here from world.cpp, to allow classes to be derived from it +//======================= +// CWorld +// +// This spawns first when each level begins. +//======================= +class CWorld : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + int m_iArenaOff; +}; diff --git a/hlsdk/ricochet/dlls/cdll_dll.h b/hlsdk/ricochet/dlls/cdll_dll.h new file mode 100644 index 00000000..920fa290 --- /dev/null +++ b/hlsdk/ricochet/dlls/cdll_dll.h @@ -0,0 +1,46 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cdll_dll.h + +// this file is included by both the game-dll and the client-dll, + +#ifndef CDLL_DLL_H +#define CDLL_DLL_H + +#define MAX_WEAPONS 32 // ??? + +#define MAX_WEAPON_SLOTS 5 // hud item selection slots +#define MAX_ITEM_TYPES 6 // hud item selection slots + +#define MAX_ITEMS 5 // 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 MAX_AMMO_TYPES 32 // ??? +#define MAX_AMMO_SLOTS 32 // not really slots + +#define HUD_PRINTNOTIFY 1 +#define HUD_PRINTCONSOLE 2 +#define HUD_PRINTTALK 3 +#define HUD_PRINTCENTER 4 + + +#define WEAPON_SUIT 31 + +#endif diff --git a/hlsdk/ricochet/dlls/client.cpp b/hlsdk/ricochet/dlls/client.cpp new file mode 100644 index 00000000..bb7a2de0 --- /dev/null +++ b/hlsdk/ricochet/dlls/client.cpp @@ -0,0 +1,1807 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Robin, 4-22-98: Moved set_suicide_frame() here from player.cpp to allow us to +// have one without a hardcoded player.mdl in tf_client.cpp + +/* + +===== client.cpp ======================================================== + + client/server game specific stuff + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "player.h" +#include "spectator.h" +#include "client.h" +#include "soundent.h" +#include "gamerules.h" +#include "customentity.h" +#include "weapons.h" +#include "weaponinfo.h" +#include "usercmd.h" +#include "netadr.h" +#include "game.h" +#include "disc_objects.h" +edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ); + +#include "discwar.h" +#include "disc_arena.h" + +extern DLL_GLOBAL ULONG g_ulModelIndexPlayer; +extern DLL_GLOBAL BOOL g_fGameOver; +extern DLL_GLOBAL int g_iSkillLevel; +extern DLL_GLOBAL ULONG g_ulFrameCount; + +extern void CopyToBodyQue(entvars_t* pev); +extern int giPrecacheGrunt; +extern int gmsgSayText; +extern int g_iNextArenaGroupInfo; +extern void AddClientToArena( CBasePlayer *pPlayer ); + +#define SUIT_HUE_START 192 +#define SUIT_HUE_END 223 +#define PLATE_HUE_START 160 +#define PLATE_HUE_END 191 + +int GetHueFromRGB( float r, float g, float b ) +{ + float fMax = max( max( r, g ) , b ); + float fMin = min( min( r, g ) , b ); + float fSaturation = 0; + + if ( fMax != 0 ) + fSaturation = (fMax - fMin) / fMax; + + if ( fSaturation == 0 ) + return 0; + + float fDelta = fMax - fMin; + float fHue = 0; + + if ( r == fMax ) + fHue = ( g - b ) / fDelta; + else if ( g == fMax ) + fHue = 2 + ( b - r ) / fDelta; + else if ( b == fMax ) + fHue = 4 + ( r - g ) / fDelta; + + fHue *= 60; + if ( fHue < 0 ) + fHue += 360; + + // Map it to 0-255 + fHue = fHue / 360; + fHue = (255 * fHue); + + return static_cast(fHue); +} + +void LinkUserMessages( void ); +/* + * used by kill command and disconnect command + * ROBIN: Moved here from player.cpp, to allow multiple player models + */ +void set_suicide_frame(entvars_t* pev) +{ + if ( !FStrEq(STRING(pev->model), "models/player/female/female.mdl") && !FStrEq(STRING(pev->model), "models/player/male/male.mdl") ) + return; // allready gibbed + +// pev->frame = $deatha11; + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_TOSS; + pev->deadflag = DEAD_DEAD; + pev->nextthink = -1; +} + + +/* +=========== +ClientConnect + +called when a player connects to a server +============ +*/ +BOOL ClientConnect( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) +{ + return g_pGameRules->ClientConnected( pEntity, pszName, pszAddress, szRejectReason ); + +// a client connecting during an intermission can cause problems +// if (intermission_running) +// ExitIntermission (); + +} + + +/* +=========== +ClientDisconnect + +called when a player disconnects from a server + +GLOBALS ASSUMED SET: g_fGameOver +============ +*/ +void ClientDisconnect( edict_t *pEntity ) +{ + // If they're in an arena, remove them + CBasePlayer *pPlayer = (CBasePlayer *)GET_PRIVATE(pEntity); + if ( pPlayer->m_pCurrentArena ) + pPlayer->m_pCurrentArena->RemoveClient( pPlayer ); + + if (g_fGameOver) + return; + +// char text[256]; +// sprintf( text, "- %s has left the game\n", STRING(pEntity->v.netname) ); +// MESSAGE_BEGIN( MSG_BROADCAST, gmsgSayText, NULL ); +// WRITE_BYTE( ENTINDEX(pEntity) ); +// WRITE_STRING( text ); +// MESSAGE_END(); + + // notify other clients the player has left + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "#Game_disconnected", STRING(pEntity->v.netname) ); + + CSound *pSound; + pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( pEntity ) ); + { + // since this client isn't around to think anymore, reset their sound. + if ( pSound ) + { + pSound->Reset(); + } + } + +// since the edict doesn't get deleted, fix it so it doesn't interfere. + pEntity->v.takedamage = DAMAGE_NO;// don't attract autoaim + pEntity->v.solid = SOLID_NOT;// nonsolid + UTIL_SetOrigin ( &pEntity->v, pEntity->v.origin ); + + g_pGameRules->ClientDisconnected( pEntity ); + + pPlayer->m_bHasDisconnected = true; +} + + +// called by ClientKill and DeadThink +void respawn(entvars_t* pev, BOOL fCopyCorpse) +{ + if (gpGlobals->coop || gpGlobals->deathmatch) + { + if ( fCopyCorpse ) + { + // make a copy of the dead body for appearances sake + CopyToBodyQue(pev); + } + + // respawn player + GetClassPtr( (CBasePlayer *)pev)->Spawn( ); + } + else + { // restart the entire server + SERVER_COMMAND("reload\n"); + } +} + +/* +============ +ClientKill + +Player entered the suicide command + +GLOBALS ASSUMED SET: g_ulModelIndexPlayer +============ +*/ +void ClientKill( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + + // Don't allow the suicide command + return; + + CBasePlayer *pl = (CBasePlayer*) CBasePlayer::Instance( pev ); + + if ( pl->m_fNextSuicideTime > gpGlobals->time ) + return; // prevent suiciding too ofter + + pl->m_fNextSuicideTime = gpGlobals->time + 1; // don't let them suicide for 5 seconds after suiciding + + // have the player kill themself + pev->health = 0; + pl->Killed( pev, GIB_NEVER ); + +// pev->modelindex = g_ulModelIndexPlayer; +// pev->frags -= 2; // extra penalty +// respawn( pev ); +} + +/* +=========== +ClientPutInServer + +called when the player is first put in the server +============ +*/ +void ClientPutInServer( edict_t *pEntity ) +{ + CBasePlayer *pPlayer; + + entvars_t *pev = &pEntity->v; + + pPlayer = GetClassPtr((CBasePlayer *)pev); + pPlayer->SetCustomDecalFrames(-1); // Assume none; + + // Allocate a CBasePlayer for pev, and call spawn + pPlayer->Spawn(); + + pPlayer->m_bHasDisconnected = FALSE; + + // Reset interpolation during first frame + pPlayer->pev->effects |= EF_NOINTERP; + pPlayer->m_pCurrentArena = NULL; + pPlayer->m_flKnownItemTime = gpGlobals->time + 0.3; + pPlayer->m_iLastGameResult = GAME_DIDNTPLAY; + + // Add to an Arena (1 maxplayer allows mapmakers to run around their map) + if ( InArenaMode() ) + { + AddClientToArena( pPlayer ); + } + else + { + // Put everyone on different teams + pPlayer->pev->team = ENTINDEX( pEntity ); + pPlayer->pev->iuser4 = pPlayer->pev->team; + + // Set colors + int iHue = GetHueFromRGB( g_iaDiscColors[ pPlayer->pev->team][0] / 255, g_iaDiscColors[pPlayer->pev->team][1] / 255, g_iaDiscColors[pPlayer->pev->team][2] / 255 ); + g_engfuncs.pfnSetClientKeyValue( ENTINDEX( pEntity ), g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "topcolor", UTIL_VarArgs("%d", iHue) ); + g_engfuncs.pfnSetClientKeyValue( ENTINDEX( pEntity ), g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "bottomcolor", UTIL_VarArgs("%d", iHue - 10) ); + } + + static char sName[128]; + strcpy(sName,STRING(pPlayer->pev->netname)); + + // First parse the name and remove any %'s + for ( char *pApersand = sName; pApersand != NULL && *pApersand != 0; pApersand++ ) + { + // Replace it with a space + if ( *pApersand == '%' ) + *pApersand = ' '; + } + + // notify other clients of player joining the game + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "#Game_connected", sName[0] != 0 ? sName : "" ); +} + +#include "voice_gamemgr.h" +extern CVoiceGameMgr g_VoiceGameMgr; + +//// HOST_SAY +// String comes in as +// say blah blah blah +// or as +// blah blah blah +// +void Host_Say( edict_t *pEntity, int teamonly ) +{ + CBasePlayer *client; + int j; + char *p; + char text[128]; + char szTemp[256]; + const char *cpSay = "say"; + const char *cpSayTeam = "say_team"; + const char *pcmd = CMD_ARGV(0); + + entvars_t *pev = &pEntity->v; + CBasePlayer* player = GetClassPtr((CBasePlayer *)pev); + + // We can get a raw string now, without the "say " prepended + if ( CMD_ARGC() == 0 ) + return; + + if ( !stricmp( pcmd, cpSay) || !stricmp( pcmd, cpSayTeam ) ) + { + if ( CMD_ARGC() >= 2 ) + { + p = (char *)CMD_ARGS(); + } + else + { + // say with a blank message, nothing to do + return; + } + } + else // Raw text, need to prepend argv[0] + { + if ( CMD_ARGC() >= 2 ) + { + sprintf( szTemp, "%s %s", ( char * )pcmd, (char *)CMD_ARGS() ); + } + else + { + // Just a one word command, use the first word...sigh + sprintf( szTemp, "%s", ( char * )pcmd ); + } + p = szTemp; + } + +// remove quotes if present + if (*p == '"') + { + p++; + p[strlen(p)-1] = 0; + } + +// make sure the text has content + char *pc = NULL; + for ( pc = p; pc != NULL && *pc != 0; pc++ ) + { + if ( isprint( *pc ) && !isspace( *pc ) ) + { + pc = NULL; // we've found an alphanumeric character, so text is valid + break; + } + } + if ( pc != NULL ) + return; // no character found, so say nothing + +// turn on color set 2 (color on, no sound) + if ( teamonly ) + sprintf( text, "%c(TEAM) %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 ) + p[j] = 0; + + strcat( text, p ); + strcat( text, "\n" ); + + // loop through all players + // Start with the first player. + // 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; + + if ( !(client->IsNetClient()) ) // Not a client ? (should never be true) + continue; + + // can the receiver hear the sender? or has he muted him? + if ( g_VoiceGameMgr.PlayerHasBlockedPlayer( client, player ) ) + 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(); + + } + + // print to the sending client + MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, &pEntity->v ); + WRITE_BYTE( ENTINDEX(pEntity) ); + WRITE_STRING( text ); + MESSAGE_END(); + + // echo to server console + g_engfuncs.pfnServerPrint( text ); + + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" %s \"%s\"\n", + STRING( pEntity->v.netname ), + GETPLAYERUSERID( pEntity ), + GETPLAYERAUTHID( pEntity ), + GETPLAYERUSERID( pEntity ), + teamonly ? cpSayTeam : cpSay, + p ); +} + + +/* +=========== +ClientCommand +called each time a player uses a "cmd" command +============ +*/ +extern float g_flWeaponCheat; + +// Use CMD_ARGV, CMD_ARGV, and CMD_ARGC to get pointers the character string command. +void ClientCommand( edict_t *pEntity ) +{ + const char *pcmd = CMD_ARGV(0); + const char *pstr; + + // Is the client spawned yet? + if ( !pEntity->pvPrivateData ) + return; + + entvars_t *pev = &pEntity->v; + + if ( FStrEq(pcmd, "say" ) ) + { + Host_Say( pEntity, 0 ); + } + else if ( FStrEq(pcmd, "say_team" ) ) + { + Host_Say( pEntity, 1 ); + } + else if ( FStrEq(pcmd, "spectate" ) ) + { + // Prevent this is the cvar is set + if ( allow_spectators.value ) + { + CBasePlayer *pPlayer = GetClassPtr((CBasePlayer *)pev); + edict_t *pentSpawnSpot = EntSelectSpawnPoint( pPlayer ); + pPlayer->StartObserver( VARS(pentSpawnSpot)->origin, VARS(pentSpawnSpot)->angles); + } + } + + else if ( FStrEq(pcmd, "ob_mode" ) ) + { + CBasePlayer *pPlayer = GetClassPtr((CBasePlayer *)pev); + pPlayer->ObserverInput_ChangeMode(); + } + else if ( FStrEq(pcmd, "ob_prev" ) ) + { + CBasePlayer *pPlayer = GetClassPtr((CBasePlayer *)pev); + pPlayer->ObserverInput_PrevPlayer(); + } + else if ( FStrEq(pcmd, "ob_next" ) ) + { + CBasePlayer *pPlayer = GetClassPtr((CBasePlayer *)pev); + pPlayer->ObserverInput_NextPlayer(); + } + + else if ( FStrEq(pcmd, "give" ) ) + { + if ( g_flWeaponCheat != 0.0) + { + int iszItem = ALLOC_STRING( CMD_ARGV(1) ); // Make a copy of the classname + GetClassPtr((CBasePlayer *)pev)->GiveNamedItem( STRING(iszItem) ); + } + } + + else if ( FStrEq(pcmd, "drop" ) ) + { + // player is dropping an item. + GetClassPtr((CBasePlayer *)pev)->DropPlayerItem((char *)CMD_ARGV(1)); + } + else if ( FStrEq(pcmd, "fov" ) ) + { + if ( g_flWeaponCheat && CMD_ARGC() > 1) + { + GetClassPtr((CBasePlayer *)pev)->m_iFOV = atoi( CMD_ARGV(1) ); + } + else + { + CLIENT_PRINTF( pEntity, print_console, UTIL_VarArgs( "\"fov\" is \"%d\"\n", (int)GetClassPtr((CBasePlayer *)pev)->m_iFOV ) ); + } + } + else if ( FStrEq(pcmd, "use" ) ) + { + GetClassPtr((CBasePlayer *)pev)->SelectItem((char *)CMD_ARGV(1)); + } + else if (((pstr = strstr(pcmd, "weapon_")) != NULL) && (pstr == pcmd)) + { + GetClassPtr((CBasePlayer *)pev)->SelectItem(pcmd); + } + else if (FStrEq(pcmd, "lastinv" )) + { + GetClassPtr((CBasePlayer *)pev)->SelectLastItem(); + } + else if ( g_pGameRules->ClientCommand( GetClassPtr((CBasePlayer *)pev), pcmd ) ) + { + // MenuSelect returns true only if the command is properly handled, so don't print a warning + } + else + { + // tell the user they entered an unknown command + char command[128]; + + // check the length of the command (prevents crash) + // max total length is 192 ...and we're adding a string below (#Game_unknown_command) + strncpy( command, pcmd, 127 ); + command[127] = '\0'; + + ClientPrint( &pEntity->v, HUD_PRINTCONSOLE, "#Game_unknown_command", command ); + } +} + +/* +======================== +ClientUserInfoChanged + +called after the player changes +userinfo - gives dll a chance to modify it before +it gets sent into the rest of the engine. +======================== +*/ +void ClientUserInfoChanged( edict_t *pEntity, char *infobuffer ) +{ + // Is the client spawned yet? + if ( !pEntity->pvPrivateData ) + return; + + // msg everyone if someone changes their name, and it isn't the first time (changing no name to current name) + if ( pEntity->v.netname && STRING(pEntity->v.netname)[0] != 0 && !FStrEq( STRING(pEntity->v.netname), g_engfuncs.pfnInfoKeyValue( infobuffer, "name" )) ) + { + char sName[256]; + char *pName = g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ); + strncpy( sName, pName, sizeof(sName) - 1 ); + sName[ sizeof(sName) - 1 ] = '\0'; + + // First parse the name and remove any %'s + for ( char *pApersand = sName; pApersand != NULL && *pApersand != 0; pApersand++ ) + { + // Replace it with a space + if ( *pApersand == '%' ) + *pApersand = ' '; + } + + // Set the name + g_engfuncs.pfnSetClientKeyValue( ENTINDEX(pEntity), infobuffer, "name", sName ); + + char text[256]; + sprintf( text, "* %s changed name to %s\n", STRING(pEntity->v.netname), g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ) ); + MESSAGE_BEGIN( MSG_ALL, gmsgSayText, NULL ); + WRITE_BYTE( ENTINDEX(pEntity) ); + WRITE_STRING( text ); + MESSAGE_END(); + + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" changed name to \"%s\"\n", + STRING( pEntity->v.netname ), + GETPLAYERUSERID( pEntity ), + GETPLAYERAUTHID( pEntity ), + GETPLAYERUSERID( pEntity ), + g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ) ); + } + + g_pGameRules->ClientUserInfoChanged( GetClassPtr((CBasePlayer *)&pEntity->v), infobuffer ); + + // Override model + if ( (!strcmp( "models/player/female/female.mdl", g_engfuncs.pfnInfoKeyValue( infobuffer, "model" ) )) )//&& (!strcmp( "models/player/hgrunt/hgrunt.mdl" )) ) + SET_MODEL( pEntity, "models/player/male/male.mdl" ); + g_engfuncs.pfnSetClientKeyValue( ENTINDEX( pEntity ), infobuffer, "model", "male" ); + + // Set colors + int iHue = GetHueFromRGB( g_iaDiscColors[ pEntity->v.team][0] / 255, g_iaDiscColors[pEntity->v.team][1] / 255, g_iaDiscColors[pEntity->v.team][2] / 255 ); + g_engfuncs.pfnSetClientKeyValue( ENTINDEX( pEntity ), infobuffer, "topcolor", UTIL_VarArgs("%d", iHue) ); + g_engfuncs.pfnSetClientKeyValue( ENTINDEX( pEntity ), infobuffer, "bottomcolor", UTIL_VarArgs("%d", iHue - 10) ); +} + +static int g_serveractive = 0; + +void ServerDeactivate( void ) +{ + // It's possible that the engine will call this function more times than is necessary + // Therefore, only run it one time for each call to ServerActivate + if ( g_serveractive != 1 ) + { + return; + } + + g_serveractive = 0; + + // Peform any shutdown operations here... + // +} + +void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) +{ + int i; + CBaseEntity *pClass; + + // Every call to ServerActivate should be matched by a call to ServerDeactivate + g_serveractive = 1; + + // Clients have not been initialized yet + for ( i = 0; i < edictCount; i++ ) + { + if ( pEdictList[i].free ) + continue; + + // Clients aren't necessarily initialized until ClientPutInServer() + if ( i < clientMax || !pEdictList[i].pvPrivateData ) + continue; + + pClass = CBaseEntity::Instance( &pEdictList[i] ); + // Activate this entity if it's got a class & isn't dormant + if ( pClass && !(pClass->pev->flags & FL_DORMANT) ) + { + pClass->Activate(); + } + else + { + ALERT( at_console, "Can't instance %s\n", STRING(pEdictList[i].v.classname) ); + } + } + + // Link user messages here to make sure first client can get them... + LinkUserMessages(); + + // Set max speed + SERVER_COMMAND( "sv_maxspeed 320\n" ); + + // Reset Arena Count + g_iNextArenaGroupInfo = 0; +} + + +/* +================ +PlayerPreThink + +Called every frame before physics are run +================ +*/ +void PlayerPreThink( edict_t *pEntity ) +{ + CBasePlayer *pPlayer = (CBasePlayer *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->PreThink( ); +} + +/* +================ +PlayerPostThink + +Called every frame after physics are run +================ +*/ +void PlayerPostThink( edict_t *pEntity ) +{ + CBasePlayer *pPlayer = (CBasePlayer *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->PostThink( ); +} + + + +void ParmsNewLevel( void ) +{ +} + + +void ParmsChangeLevel( void ) +{ + // retrieve the pointer to the save data + SAVERESTOREDATA *pSaveData = (SAVERESTOREDATA *)gpGlobals->pSaveData; + + if ( pSaveData ) + pSaveData->connectionCount = BuildChangeList( pSaveData->levelList, MAX_LEVEL_CONNECTIONS ); +} + + +// +// GLOBALS ASSUMED SET: g_ulFrameCount +// +void StartFrame( void ) +{ + if ( g_pGameRules ) + g_pGameRules->Think(); + + if ( g_fGameOver ) + return; + + gpGlobals->teamplay = CVAR_GET_FLOAT("teamplay"); + g_iSkillLevel = static_cast(CVAR_GET_FLOAT("skill")); + g_ulFrameCount++; +} + + +void ClientPrecache( void ) +{ + // setup precaches always needed + PRECACHE_SOUND("player/sprayer.wav"); // spray paint sound for PreAlpha + + // PRECACHE_SOUND("player/pl_jumpland2.wav"); // UNDONE: play 2x step sound + + PRECACHE_SOUND("player/pl_fallpain2.wav"); + PRECACHE_SOUND("player/pl_fallpain3.wav"); + + PRECACHE_SOUND("player/pl_step1.wav"); // walk on concrete + PRECACHE_SOUND("player/pl_step2.wav"); + PRECACHE_SOUND("player/pl_step3.wav"); + PRECACHE_SOUND("player/pl_step4.wav"); + + PRECACHE_SOUND("common/npc_step1.wav"); // NPC walk on concrete + PRECACHE_SOUND("common/npc_step2.wav"); + PRECACHE_SOUND("common/npc_step3.wav"); + PRECACHE_SOUND("common/npc_step4.wav"); + + PRECACHE_SOUND("player/pl_metal1.wav"); // walk on metal + PRECACHE_SOUND("player/pl_metal2.wav"); + PRECACHE_SOUND("player/pl_metal3.wav"); + PRECACHE_SOUND("player/pl_metal4.wav"); + + PRECACHE_SOUND("player/pl_dirt1.wav"); // walk on dirt + PRECACHE_SOUND("player/pl_dirt2.wav"); + PRECACHE_SOUND("player/pl_dirt3.wav"); + PRECACHE_SOUND("player/pl_dirt4.wav"); + + PRECACHE_SOUND("player/pl_duct1.wav"); // walk in duct + PRECACHE_SOUND("player/pl_duct2.wav"); + PRECACHE_SOUND("player/pl_duct3.wav"); + PRECACHE_SOUND("player/pl_duct4.wav"); + + PRECACHE_SOUND("player/pl_grate1.wav"); // walk on grate + PRECACHE_SOUND("player/pl_grate2.wav"); + PRECACHE_SOUND("player/pl_grate3.wav"); + PRECACHE_SOUND("player/pl_grate4.wav"); + + PRECACHE_SOUND("player/pl_slosh1.wav"); // walk in shallow water + PRECACHE_SOUND("player/pl_slosh2.wav"); + PRECACHE_SOUND("player/pl_slosh3.wav"); + PRECACHE_SOUND("player/pl_slosh4.wav"); + + PRECACHE_SOUND("player/pl_tile1.wav"); // walk on tile + PRECACHE_SOUND("player/pl_tile2.wav"); + PRECACHE_SOUND("player/pl_tile3.wav"); + PRECACHE_SOUND("player/pl_tile4.wav"); + PRECACHE_SOUND("player/pl_tile5.wav"); + + PRECACHE_SOUND("player/pl_swim1.wav"); // breathe bubbles + PRECACHE_SOUND("player/pl_swim2.wav"); + PRECACHE_SOUND("player/pl_swim3.wav"); + PRECACHE_SOUND("player/pl_swim4.wav"); + + PRECACHE_SOUND("player/pl_ladder1.wav"); // climb ladder rung + PRECACHE_SOUND("player/pl_ladder2.wav"); + PRECACHE_SOUND("player/pl_ladder3.wav"); + PRECACHE_SOUND("player/pl_ladder4.wav"); + + PRECACHE_SOUND("player/pl_wade1.wav"); // wade in water + PRECACHE_SOUND("player/pl_wade2.wav"); + PRECACHE_SOUND("player/pl_wade3.wav"); + PRECACHE_SOUND("player/pl_wade4.wav"); + + PRECACHE_SOUND("debris/wood1.wav"); // hit wood texture + PRECACHE_SOUND("debris/wood2.wav"); + PRECACHE_SOUND("debris/wood3.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( SOUND_FLASHLIGHT_ON ); + PRECACHE_SOUND( SOUND_FLASHLIGHT_OFF ); + +// player gib sounds + PRECACHE_SOUND("common/bodysplat.wav"); + +// player pain sounds + PRECACHE_SOUND("player/pl_pain2.wav"); + PRECACHE_SOUND("player/pl_pain4.wav"); + PRECACHE_SOUND("player/pl_pain5.wav"); + PRECACHE_SOUND("player/pl_pain6.wav"); + PRECACHE_SOUND("player/pl_pain7.wav"); + + //PRECACHE_MODEL("models/player/female/female.mdl"); + PRECACHE_MODEL("models/player/male/male.mdl"); + + // hud sounds + + 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"); + + + // geiger sounds + + PRECACHE_SOUND("player/geiger6.wav"); + PRECACHE_SOUND("player/geiger5.wav"); + PRECACHE_SOUND("player/geiger4.wav"); + PRECACHE_SOUND("player/geiger3.wav"); + PRECACHE_SOUND("player/geiger2.wav"); + PRECACHE_SOUND("player/geiger1.wav"); + + if (giPrecacheGrunt) + UTIL_PrecacheOther("monster_human_grunt"); +} + +/* +=============== +const char *GetGameDescription() + +Returns the descriptive name of this .dll. E.g., Half-Life, or Team Fortress 2 +=============== +*/ +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 "Ricochet"; +} + +/* +================ +Sys_Error + +Engine is going to shut down, allows setting a breakpoint in game .dll to catch that occasion +================ +*/ +void Sys_Error( const char *error_string ) +{ + // Default case, do nothing. MOD AUTHORS: Add code ( e.g., _asm { int 3 }; here to cause a breakpoint for debugging your game .dlls +} + +/* +================ +PlayerCustomization + +A new player customization has been registered on the server +UNDONE: This only sets the # of frames of the spray can logo +animation right now. +================ +*/ +void PlayerCustomization( edict_t *pEntity, customization_t *pCust ) +{ + CBasePlayer *pPlayer = (CBasePlayer *)GET_PRIVATE(pEntity); + + if (!pPlayer) + { + ALERT(at_console, "PlayerCustomization: Couldn't get player!\n"); + return; + } + + if (!pCust) + { + ALERT(at_console, "PlayerCustomization: NULL customization!\n"); + return; + } + + switch (pCust->resource.type) + { + case t_decal: + pPlayer->SetCustomDecalFrames(pCust->nUserData2); // Second int is max # of frames. + break; + case t_sound: + case t_skin: + case t_model: + // Ignore for now. + break; + default: + ALERT(at_console, "PlayerCustomization: Unknown customization type!\n"); + break; + } +} + +/* +================ +SpectatorConnect + +A spectator has joined the game +================ +*/ +void SpectatorConnect( edict_t *pEntity ) +{ + CBaseSpectator *pPlayer = (CBaseSpectator *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->SpectatorConnect( ); +} + +/* +================ +SpectatorConnect + +A spectator has left the game +================ +*/ +void SpectatorDisconnect( edict_t *pEntity ) +{ + CBaseSpectator *pPlayer = (CBaseSpectator *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->SpectatorDisconnect( ); +} + +/* +================ +SpectatorConnect + +A spectator has sent a usercmd +================ +*/ +void SpectatorThink( edict_t *pEntity ) +{ + CBaseSpectator *pPlayer = (CBaseSpectator *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->SpectatorThink( ); +} + + +//////////////////////////////////////////////////////// +// PAS and PVS routines for client messaging +// + +/* +================ +SetupVisibility + +A client can have a separate "view entity" indicating that his/her view should depend on the origin of that +view entity. If that's the case, then pViewEntity will be non-NULL and will be used. Otherwise, the current +entity's origin is used. Either is offset by the view_ofs to get the eye position. + +From the eye position, we set up the PAS and PVS to use for filtering network messages to the client. At this point, we could + override the actual PAS or PVS values, or use a different origin. + +NOTE: Do not cache the values of pas and pvs, as they depend on reusable memory in the engine, they are only good for this one frame +================ +*/ +void SetupVisibility( edict_t *pViewEntity, edict_t *pClient, unsigned char **pvs, unsigned char **pas ) +{ + Vector org; + edict_t *pView = pClient; + + // Find the client's PVS + if ( pViewEntity ) + { + pView = pViewEntity; + } + + // Tracking Spectators use the visibility of their target + CBasePlayer *pPlayer = (CBasePlayer *)CBaseEntity::Instance( pClient ); + if ( (pPlayer->pev->iuser2 != 0) && (pPlayer->m_hObserverTarget != 0) ) + { + pView = pPlayer->m_hObserverTarget->edict(); + } + + org = pView->v.origin + pView->v.view_ofs; + if ( pView->v.flags & FL_DUCKING ) + { + org = org + ( VEC_HULL_MIN - VEC_DUCK_HULL_MIN ); + } + + *pvs = ENGINE_SET_PVS ( (float *)&org ); + *pas = ENGINE_SET_PAS ( (float *)&org ); +} + +#include "entity_state.h" + +/* +AddToFullPack + +Return 1 if the entity state has been filled in for the ent and the entity will be propagated to the client, 0 otherwise + +state is the server maintained copy of the state info that is transmitted to the client +a MOD could alter values copied into state to send the "host" a different look for a particular entity update, etc. +e and ent are the entity that is being added to the update, if 1 is returned +host is the player's edict of the player whom we are sending the update to +player is 1 if the ent/e is a player and 0 otherwise +pSet is either the PAS or PVS that we previous set up. We can use it to ask the engine to filter the entity against the PAS or PVS. +we could also use the pas/ pvs that we set in SetupVisibility, if we wanted to. Caching the value is valid in that case, but still only for the current frame +*/ +int AddToFullPack( struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet ) +{ + int i; + + // don't send if flagged for NODRAW and it's not the host getting the message + if ( ( ent->v.effects == EF_NODRAW ) && + ( ent != host ) ) + return 0; + + // Ignore ents without valid / visible models + if ( !ent->v.modelindex || !STRING( ent->v.model ) ) + return 0; + + // Don't send spectators to other players + if ( ( ent->v.flags & FL_SPECTATOR ) && ( ent != host ) ) + { + return 0; + } + + // Ignore if not the host and not touching a PVS/PAS leaf + // If pSet is NULL, then the test will always succeed and the entity will be added to the update + if ( ent != host ) + { + if ( !ENGINE_CHECK_VISIBILITY( (const struct edict_s *)ent, pSet ) ) + { + return 0; + } + } + + + // Don't send entity to local client if the client says it's predicting the entity itself. + if ( ent->v.flags & FL_SKIPLOCALHOST ) + { + if ( ( hostflags & 1 ) && ( ent->v.owner == host ) ) + return 0; + } + + if ( host->v.groupinfo ) + { + UTIL_SetGroupTrace( host->v.groupinfo, GROUP_OP_AND ); + + // Should always be set, of course + if ( ent->v.groupinfo ) + { + if ( g_groupop == GROUP_OP_AND ) + { + if ( !(ent->v.groupinfo & host->v.groupinfo ) ) + return 0; + } + else if ( g_groupop == GROUP_OP_NAND ) + { + if ( ent->v.groupinfo & host->v.groupinfo ) + return 0; + } + } + + UTIL_UnsetGroupTrace(); + } + + memset( state, 0, sizeof( *state ) ); + + // Assign index so we can track this entity from frame to frame and + // delta from it. + state->number = e; + state->entityType = ENTITY_NORMAL; + + // Flag custom entities. + if ( ent->v.flags & FL_CUSTOMENTITY ) + { + state->entityType = ENTITY_BEAM; + } + + // + // Copy state data + // + + // Round animtime to nearest millisecond + state->animtime = (int)(1000.0 * ent->v.animtime ) / 1000.0; + + memcpy( state->origin, ent->v.origin, 3 * sizeof( float ) ); + memcpy( state->angles, ent->v.angles, 3 * sizeof( float ) ); + memcpy( state->mins, ent->v.mins, 3 * sizeof( float ) ); + memcpy( state->maxs, ent->v.maxs, 3 * sizeof( float ) ); + + memcpy( state->startpos, ent->v.startpos, 3 * sizeof( float ) ); + memcpy( state->endpos, ent->v.endpos, 3 * sizeof( float ) ); + + state->impacttime = ent->v.impacttime; + state->starttime = ent->v.starttime; + + state->modelindex = ent->v.modelindex; + + state->frame = ent->v.frame; + + state->skin = ent->v.skin; + state->effects = ent->v.effects; + + // This non-player entity is being moved by the game .dll and not the physics simulation system + // make sure that we interpolate it's position on the client if it moves + if ( !player && + ent->v.animtime && + ent->v.velocity[ 0 ] == 0 && + ent->v.velocity[ 1 ] == 0 && + ent->v.velocity[ 2 ] == 0 ) + { + state->eflags |= EFLAG_SLERP; + } + + state->scale = ent->v.scale; + state->solid = ent->v.solid; + state->colormap = ent->v.colormap; + state->movetype = ent->v.movetype; + state->sequence = ent->v.sequence; + state->framerate = ent->v.framerate; + state->body = ent->v.body; + + for (i = 0; i < 4; i++) + { + state->controller[i] = ent->v.controller[i]; + } + + for (i = 0; i < 2; i++) + { + state->blending[i] = ent->v.blending[i]; + } + + state->rendermode = ent->v.rendermode; + state->renderamt = static_cast(ent->v.renderamt); + state->renderfx = ent->v.renderfx; + state->rendercolor.r = static_cast(ent->v.rendercolor.x); + state->rendercolor.g = static_cast(ent->v.rendercolor.y); + state->rendercolor.b = static_cast(ent->v.rendercolor.z); + + state->aiment = 0; + if ( ent->v.aiment ) + { + state->aiment = ENTINDEX( ent->v.aiment ); + } + + state->owner = 0; + if ( ent->v.owner ) + { + int owner = ENTINDEX( ent->v.owner ); + + // Only care if owned by a player + if ( owner >= 1 && owner <= gpGlobals->maxClients ) + { + state->owner = owner; + } + } + + // Special stuff for players only + if ( player ) + { + memcpy( state->basevelocity, ent->v.basevelocity, 3 * sizeof( float ) ); + + state->weaponmodel = MODEL_INDEX( STRING( ent->v.weaponmodel ) ); + state->gaitsequence = ent->v.gaitsequence; + state->spectator = ent->v.flags & FL_SPECTATOR; + state->friction = ent->v.friction; + + state->gravity = ent->v.gravity; + state->team = ent->v.team; + state->playerclass = ent->v.playerclass; + state->usehull = ( ent->v.flags & FL_DUCKING ) ? 1 : 0; + state->health = static_cast(ent->v.health); + } + + return 1; +} + +// defaults for clientinfo messages +#define DEFAULT_VIEWHEIGHT 28 + +/* +=================== +CreateBaseline + +Creates baselines used for network encoding, especially for player data since players are not spawned until connect time. +=================== +*/ +void CreateBaseline( int player, int eindex, struct entity_state_s *baseline, struct edict_s *entity, int playermodelindex, vec3_t player_mins, vec3_t player_maxs ) +{ + baseline->origin = entity->v.origin; + baseline->angles = entity->v.angles; + baseline->frame = entity->v.frame; + baseline->skin = (short)entity->v.skin; + + // render information + baseline->rendermode = (byte)entity->v.rendermode; + baseline->renderamt = (byte)entity->v.renderamt; + baseline->rendercolor.r = (byte)entity->v.rendercolor.x; + baseline->rendercolor.g = (byte)entity->v.rendercolor.y; + baseline->rendercolor.b = (byte)entity->v.rendercolor.z; + baseline->renderfx = (byte)entity->v.renderfx; + + if ( player ) + { + baseline->mins = player_mins; + baseline->maxs = player_maxs; + + baseline->colormap = eindex; + baseline->modelindex = playermodelindex; + baseline->friction = 1.0; + baseline->movetype = MOVETYPE_WALK; + + baseline->scale = entity->v.scale; + baseline->solid = SOLID_SLIDEBOX; + baseline->framerate = 1.0; + baseline->gravity = 1.0; + + baseline->playerclass = entity->v.playerclass; + } + else + { + baseline->mins = entity->v.mins; + baseline->maxs = entity->v.maxs; + + baseline->colormap = 0; + baseline->modelindex = entity->v.modelindex;//SV_ModelIndex(pr_strings + entity->v.model); + baseline->movetype = entity->v.movetype; + + baseline->scale = entity->v.scale; + baseline->solid = entity->v.solid; + baseline->framerate = entity->v.framerate; + baseline->gravity = entity->v.gravity; + } +} + +typedef struct +{ + char name[32]; + int field; +} entity_field_alias_t; + +#define FIELD_ORIGIN0 0 +#define FIELD_ORIGIN1 1 +#define FIELD_ORIGIN2 2 +#define FIELD_ANGLES0 3 +#define FIELD_ANGLES1 4 +#define FIELD_ANGLES2 5 + +static entity_field_alias_t entity_field_alias[]= +{ + { "origin[0]", 0 }, + { "origin[1]", 0 }, + { "origin[2]", 0 }, + { "angles[0]", 0 }, + { "angles[1]", 0 }, + { "angles[2]", 0 }, +}; + +void Entity_FieldInit( struct delta_s *pFields ) +{ + entity_field_alias[ FIELD_ORIGIN0 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ORIGIN0 ].name ); + entity_field_alias[ FIELD_ORIGIN1 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ORIGIN1 ].name ); + entity_field_alias[ FIELD_ORIGIN2 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ORIGIN2 ].name ); + entity_field_alias[ FIELD_ANGLES0 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ANGLES0 ].name ); + entity_field_alias[ FIELD_ANGLES1 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ANGLES1 ].name ); + entity_field_alias[ FIELD_ANGLES2 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ANGLES2 ].name ); +} + +/* +================== +Entity_Encode + +Callback for sending entity_state_t info over network. +FIXME: Move to script +================== +*/ +void Entity_Encode( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) +{ + entity_state_t *f, *t; + + int localplayer = 0; + static int initialized = 0; + + if ( !initialized ) + { + Entity_FieldInit( pFields ); + initialized = 1; + } + + f = (entity_state_t *)from; + t = (entity_state_t *)to; + + // Never send origin to local player, it's sent with more resolution in clientdata_t structure + localplayer = ( t->number - 1 ) == ENGINE_CURRENT_PLAYER(); + if ( localplayer ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + + if ( ( t->impacttime != 0 ) && ( t->starttime != 0 ) ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ANGLES0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ANGLES1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ANGLES2 ].field ); + } + + if ( ( t->movetype == MOVETYPE_FOLLOW ) && + ( t->aiment != 0 ) ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + else if ( t->aiment != f->aiment ) + { + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } +} + +static entity_field_alias_t player_field_alias[]= +{ + { "origin[0]", 0 }, + { "origin[1]", 0 }, + { "origin[2]", 0 }, +}; + +void Player_FieldInit( struct delta_s *pFields ) +{ + player_field_alias[ FIELD_ORIGIN0 ].field = DELTA_FINDFIELD( pFields, player_field_alias[ FIELD_ORIGIN0 ].name ); + player_field_alias[ FIELD_ORIGIN1 ].field = DELTA_FINDFIELD( pFields, player_field_alias[ FIELD_ORIGIN1 ].name ); + player_field_alias[ FIELD_ORIGIN2 ].field = DELTA_FINDFIELD( pFields, player_field_alias[ FIELD_ORIGIN2 ].name ); +} + +/* +================== +Player_Encode + +Callback for sending entity_state_t for players info over network. +================== +*/ +void Player_Encode( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) +{ + entity_state_t *f, *t; + int localplayer = 0; + static int initialized = 0; + + if ( !initialized ) + { + Player_FieldInit( pFields ); + initialized = 1; + } + + f = (entity_state_t *)from; + t = (entity_state_t *)to; + + // Never send origin to local player, it's sent with more resolution in clientdata_t structure + localplayer = ( t->number - 1 ) == ENGINE_CURRENT_PLAYER(); + if ( localplayer ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + + if ( ( t->movetype == MOVETYPE_FOLLOW ) && + ( t->aiment != 0 ) ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + else if ( t->aiment != f->aiment ) + { + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } +} + +#define CUSTOMFIELD_ORIGIN0 0 +#define CUSTOMFIELD_ORIGIN1 1 +#define CUSTOMFIELD_ORIGIN2 2 +#define CUSTOMFIELD_ANGLES0 3 +#define CUSTOMFIELD_ANGLES1 4 +#define CUSTOMFIELD_ANGLES2 5 +#define CUSTOMFIELD_SKIN 6 +#define CUSTOMFIELD_SEQUENCE 7 +#define CUSTOMFIELD_ANIMTIME 8 + +entity_field_alias_t custom_entity_field_alias[]= +{ + { "origin[0]", 0 }, + { "origin[1]", 0 }, + { "origin[2]", 0 }, + { "angles[0]", 0 }, + { "angles[1]", 0 }, + { "angles[2]", 0 }, + { "skin", 0 }, + { "sequence", 0 }, + { "animtime", 0 }, +}; + +void Custom_Entity_FieldInit( struct delta_s *pFields ) +{ + custom_entity_field_alias[ CUSTOMFIELD_ORIGIN0 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN0 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ORIGIN1 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN1 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ORIGIN2 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN2 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANGLES0 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES0 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANGLES1 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES1 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANGLES2 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES2 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_SKIN ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_SKIN ].name ); + custom_entity_field_alias[ CUSTOMFIELD_SEQUENCE ].field= DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_SEQUENCE ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANIMTIME ].field= DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANIMTIME ].name ); +} + +/* +================== +Custom_Encode + +Callback for sending entity_state_t info ( for custom entities ) over network. +FIXME: Move to script +================== +*/ +void Custom_Encode( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) +{ + entity_state_t *f, *t; + int beamType; + static int initialized = 0; + + if ( !initialized ) + { + Custom_Entity_FieldInit( pFields ); + initialized = 1; + } + + f = (entity_state_t *)from; + t = (entity_state_t *)to; + + beamType = t->rendermode & 0x0f; + + if ( beamType != BEAM_POINTS && beamType != BEAM_ENTPOINT ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN2 ].field ); + } + + if ( beamType != BEAM_POINTS ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES0 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES1 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES2 ].field ); + } + + if ( beamType != BEAM_ENTS && beamType != BEAM_ENTPOINT ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_SKIN ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_SEQUENCE ].field ); + } + + // animtime is compared by rounding first + // see if we really shouldn't actually send it + if ( (int)f->animtime == (int)t->animtime ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANIMTIME ].field ); + } +} + +/* +================= +RegisterEncoders + +Allows game .dll to override network encoding of certain types of entities and tweak values, etc. +================= +*/ +void RegisterEncoders( void ) +{ + DELTA_ADDENCODER( "Entity_Encode", Entity_Encode ); + DELTA_ADDENCODER( "Custom_Encode", Custom_Encode ); + DELTA_ADDENCODER( "Player_Encode", Player_Encode ); +} + +int GetWeaponData( struct edict_s *player, struct weapon_data_s *info ) +{ + int i; + weapon_data_t *item; + entvars_t *pev = &player->v; + CBasePlayer *pl = ( CBasePlayer *) CBasePlayer::Instance( pev ); + CBasePlayerWeapon *gun; + + ItemInfo II; + + memset( info, 0, 32 * sizeof( weapon_data_t ) ); + + if ( !pl ) + return 1; + + // go through all of the weapons and make a list of the ones to pack + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( pl->m_rgpPlayerItems[ i ] ) + { + // there's a weapon here. Should I pack it? + CBasePlayerItem *pPlayerItem = pl->m_rgpPlayerItems[ i ]; + + while ( pPlayerItem ) + { + gun = (CBasePlayerWeapon *)pPlayerItem->GetWeaponPtr(); + if ( gun && gun->UseDecrement() ) + { + // Get The ID. + memset( &II, 0, sizeof( II ) ); + gun->GetItemInfo( &II ); + + if ( II.iId >= 0 && II.iId < 32 ) + { + item = &info[ II.iId ]; + + item->m_iId = II.iId; + item->m_iClip = pl->m_rgAmmo[gun->m_iPrimaryAmmoType];//gun->m_iClip; + + item->m_flTimeWeaponIdle = max( gun->m_flTimeWeaponIdle, -0.001 ); + item->m_flNextPrimaryAttack = max( gun->m_flNextPrimaryAttack, -0.001 ); + item->m_flNextSecondaryAttack = max( gun->m_flNextSecondaryAttack, -0.001 ); + item->m_fInReload = gun->m_fInReload; + } + } + pPlayerItem = pPlayerItem->m_pNext; + } + } + } + return 1; +} + +/* +================= +UpdateClientData + +Data sent to current client only +engine sets cd to 0 before calling. +================= +*/ +void UpdateClientData ( const struct edict_s *ent, int sendweapons, struct clientdata_s *cd ) +{ + cd->flags = ent->v.flags; + cd->health = ent->v.health; + + cd->viewmodel = MODEL_INDEX( STRING( ent->v.viewmodel ) ); + cd->waterlevel = ent->v.waterlevel; + cd->watertype = ent->v.watertype; + cd->weapons = ent->v.weapons; + + // Vectors + cd->origin = ent->v.origin; + cd->velocity = ent->v.velocity; + cd->view_ofs = ent->v.view_ofs; + cd->punchangle = ent->v.punchangle; + + cd->bInDuck = ent->v.bInDuck; + cd->flTimeStepSound = ent->v.flTimeStepSound; + cd->flDuckTime = ent->v.flDuckTime; + cd->flSwimTime = ent->v.flSwimTime; + cd->waterjumptime = static_cast(ent->v.teleport_time); + + strcpy( cd->physinfo, ENGINE_GETPHYSINFO( ent ) ); + + cd->maxspeed = ent->v.maxspeed; + cd->fov = ent->v.fov; + cd->weaponanim = ent->v.weaponanim; + + cd->pushmsec = ent->v.pushmsec; + + // Spectator + cd->iuser1 = ent->v.iuser1; + cd->iuser2 = ent->v.iuser2; + + if ( sendweapons ) + { + entvars_t *pev = (entvars_t *)&ent->v; + CBasePlayer *pl = ( CBasePlayer *) CBasePlayer::Instance( pev ); + + if ( pl ) + { + cd->m_flNextAttack = pl->m_flNextAttack; + + if ( pl->m_pActiveItem ) + { + CBasePlayerWeapon *gun; + gun = (CBasePlayerWeapon *)pl->m_pActiveItem->GetWeaponPtr(); + if ( gun && gun->UseDecrement() ) + { + ItemInfo II; + memset( &II, 0, sizeof( II ) ); + gun->GetItemInfo( &II ); + + cd->m_iId = II.iId; + } + } + } + } +} + +/* +================= +CmdStart + +We're about to run this usercmd for the specified player. We can set up groupinfo and masking here, etc. +This is the time to examine the usercmd for anything extra. This call happens even if think does not. +================= +*/ +void CmdStart( const edict_t *player, const struct usercmd_s *cmd, unsigned int random_seed ) +{ + entvars_t *pev = (entvars_t *)&player->v; + CBasePlayer *pl = ( CBasePlayer *) CBasePlayer::Instance( pev ); + + if( !pl ) + return; + + if ( pl->pev->groupinfo != 0 ) + { + UTIL_SetGroupTrace( pl->pev->groupinfo, GROUP_OP_AND ); + } + + pl->random_seed = random_seed; +} + +/* +================= +CmdEnd + +Each cmdstart is exactly matched with a cmd end, clean up any group trace flags, etc. here +================= +*/ +void CmdEnd ( const edict_t *player ) +{ + entvars_t *pev = (entvars_t *)&player->v; + CBasePlayer *pl = ( CBasePlayer *) CBasePlayer::Instance( pev ); + + if( !pl ) + return; + if ( pl->pev->groupinfo != 0 ) + { + UTIL_UnsetGroupTrace(); + } +} + +/* +================================ +ConnectionlessPacket + + Return 1 if the packet is valid. Set response_buffer_size if you want to send a response packet. Incoming, it holds the max + size of the response_buffer, so you must zero it out if you choose not to respond. +================================ +*/ +int ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ) +{ + // Zero it out since we aren't going to respond. + // If we wanted to response, we'd write data into response_buffer + *response_buffer_size = 0; + + // Since we don't listen for anything here, just respond that it's a bogus message + // If we didn't reject the message, we'd return 1 for success instead. + return 0; +} + +/* +================================ +GetHullBounds + + Engine calls this to enumerate player collision hulls, for prediction. Return 0 if the hullnumber doesn't exist. +================================ +*/ +int GetHullBounds( int hullnumber, float *mins, float *maxs ) +{ + int iret = 0; + + switch ( hullnumber ) + { + case 0: // Normal player + mins = VEC_HULL_MIN; + maxs = VEC_HULL_MAX; + iret = 1; + break; + case 1: // Crouched player + mins = VEC_DUCK_HULL_MIN; + maxs = VEC_DUCK_HULL_MAX; + iret = 1; + break; + case 2: // Point based hull + mins = Vector( 0, 0, 0 ); + maxs = Vector( 0, 0, 0 ); + iret = 1; + break; + } + + return iret; +} + +/* +================================ +CreateInstancedBaselines + +Create pseudo-baselines for items that aren't placed in the map at spawn time, but which are likely +to be created during play ( e.g., grenades, ammo packs, projectiles, corpses, etc. ) +================================ +*/ +void CreateInstancedBaselines ( void ) +{ + entity_state_t state; + + memset( &state, 0, sizeof( state ) ); + + // Create any additional baselines here for things like grendates, etc. + // iret = ENGINE_INSTANCE_BASELINE( pc->pev->classname, &state ); + + // Destroy objects. + //UTIL_Remove( pc ); +} + +/* +================================ +InconsistentFile + +One of the ENGINE_FORCE_UNMODIFIED files failed the consistency check for the specified player + Return 0 to allow the client to continue, 1 to force immediate disconnection ( with an optional disconnect message of up to 256 characters ) +================================ +*/ +int InconsistentFile( const edict_t *player, const char *filename, char *disconnect_message ) +{ + // Server doesn't care? + if ( CVAR_GET_FLOAT( "mp_consistency" ) != 1 ) + return 0; + + // Default behavior is to kick the player + sprintf( disconnect_message, "Server is enforcing file consistency for %s\n", filename ); + + // Kick now with specified disconnect message. + return 1; +} + +/* +================================ +AllowLagCompensation + + The game .dll should return 1 if lag compensation should be allowed ( could also just set + the sv_unlag cvar. + Most games right now should return 0, until client-side weapon prediction code is written + and tested for them ( note you can predict weapons, but not do lag compensation, too, + if you want. +================================ +*/ +int AllowLagCompensation( void ) +{ + return 0; +} + + +/* +================================ +ShouldCollide + + Called when the engine believes two entities are about to collide. Return 0 if you + want the two entities to just pass through each other without colliding or calling the + touch function. +================================ +*/ +int ShouldCollide( edict_t *pentTouched, edict_t *pentOther ) +{ + // To make this a fast check, use iuser4 for the discs to know what other discs they should collide with + if ( pentTouched->v.iuser4 != 0 && pentOther->v.iuser4 != 0 ) + { + // Two friendly discs will have matching iuser4's + if ( pentTouched->v.iuser4 == pentOther->v.iuser4 ) + { + return 0; + } + + // Discs hitting their owners + CBaseEntity::Instance(pentTouched); + CBaseEntity::Instance(pentOther); + /* + if ( pOther->IsDisc() && pTouched->IsPlayer() && ((CDisc*)pOther)->m_hOwner == pTouched ) + return 0; + if ( pTouched->IsDisc() && pOther->IsPlayer() && ((CDisc*)pTouched)->m_hOwner == pOther ) + return 0; + */ + //if ( pOther->IsDisc() || pTouched->IsDisc() ) + //return 0; + } + + return 1; +} diff --git a/hlsdk/ricochet/dlls/client.h b/hlsdk/ricochet/dlls/client.h new file mode 100644 index 00000000..703056de --- /dev/null +++ b/hlsdk/ricochet/dlls/client.h @@ -0,0 +1,67 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef CLIENT_H +#define CLIENT_H + +extern void respawn( entvars_t* pev, BOOL fCopyCorpse ); +extern BOOL ClientConnect( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); +extern void ClientDisconnect( edict_t *pEntity ); +extern void ClientKill( edict_t *pEntity ); +extern void ClientPutInServer( edict_t *pEntity ); +extern void ClientCommand( edict_t *pEntity ); +extern void ClientUserInfoChanged( edict_t *pEntity, char *infobuffer ); +extern void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ); +extern void ServerDeactivate( void ); +extern void StartFrame( void ); +extern void PlayerPostThink( edict_t *pEntity ); +extern void PlayerPreThink( edict_t *pEntity ); +extern void ParmsNewLevel( void ); +extern void ParmsChangeLevel( void ); + +extern void ClientPrecache( void ); + +extern const char *GetGameDescription( void ); +extern void PlayerCustomization( edict_t *pEntity, customization_t *pCust ); + +extern void SpectatorConnect ( edict_t *pEntity ); +extern void SpectatorDisconnect ( edict_t *pEntity ); +extern void SpectatorThink ( edict_t *pEntity ); + +extern void Sys_Error( const char *error_string ); + +extern void SetupVisibility( edict_t *pViewEntity, edict_t *pClient, unsigned char **pvs, unsigned char **pas ); +extern void UpdateClientData ( const struct edict_s *ent, int sendweapons, struct clientdata_s *cd ); +extern int AddToFullPack( struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet ); +extern void CreateBaseline( int player, int eindex, struct entity_state_s *baseline, struct edict_s *entity, int playermodelindex, vec3_t player_mins, vec3_t player_maxs ); +extern void RegisterEncoders( void ); + +extern int GetWeaponData( struct edict_s *player, struct weapon_data_s *info ); + +extern void CmdStart( const edict_t *player, const struct usercmd_s *cmd, unsigned int random_seed ); +extern void CmdEnd ( const edict_t *player ); + +extern int ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); + +extern int GetHullBounds( int hullnumber, float *mins, float *maxs ); + +extern void CreateInstancedBaselines ( void ); + +extern int InconsistentFile( const edict_t *player, const char *filename, char *disconnect_message ); + +extern int AllowLagCompensation( void ); + +extern int ShouldCollide( edict_t *pentTouched, edict_t *pentOther ); + +#endif // CLIENT_H diff --git a/hlsdk/ricochet/dlls/combat.cpp b/hlsdk/ricochet/dlls/combat.cpp new file mode 100644 index 00000000..ef550967 --- /dev/null +++ b/hlsdk/ricochet/dlls/combat.cpp @@ -0,0 +1,1448 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== combat.cpp ======================================================== + + functions dealing with damage infliction & death + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "soundent.h" +#include "decals.h" +#include "animation.h" +#include "weapons.h" +#include "func_break.h" + +extern DLL_GLOBAL Vector g_vecAttackDir; +extern DLL_GLOBAL int g_iSkillLevel; + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); +extern entvars_t *g_pevLastInflictor; + +#define GERMAN_GIB_COUNT 4 +#define HUMAN_GIB_COUNT 6 +#define ALIEN_GIB_COUNT 4 + + +// HACKHACK -- The gib velocity equations don't work +void CGib :: LimitVelocity( void ) +{ + float length = pev->velocity.Length(); + + // ceiling at 1500. The gib velocity equation is not bounded properly. Rather than tune it + // in 3 separate places again, I'll just limit it here. + if ( length > 1500.0 ) + pev->velocity = pev->velocity.Normalize() * 1500; // This should really be sv_maxvelocity * 0.75 or something +} + + +void CGib :: SpawnStickyGibs( entvars_t *pevVictim, Vector vecOrigin, int cGibs ) +{ + int i; + + if ( g_Language == LANGUAGE_GERMAN ) + { + // no sticky gibs in germany right now! + return; + } + + for ( i = 0 ; i < cGibs ; i++ ) + { + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + pGib->Spawn( "models/stickygib.mdl" ); + pGib->pev->body = RANDOM_LONG(0,2); + + if ( pevVictim ) + { + pGib->pev->origin.x = vecOrigin.x + RANDOM_FLOAT( -3, 3 ); + pGib->pev->origin.y = vecOrigin.y + RANDOM_FLOAT( -3, 3 ); + pGib->pev->origin.z = vecOrigin.z + RANDOM_FLOAT( -3, 3 ); + + /* + pGib->pev->origin.x = pevVictim->absmin.x + pevVictim->size.x * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.y = pevVictim->absmin.y + pevVictim->size.y * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.z = pevVictim->absmin.z + pevVictim->size.z * (RANDOM_FLOAT ( 0 , 1 ) ); + */ + + // make the gib fly away from the attack vector + pGib->pev->velocity = g_vecAttackDir * -1; + + // mix in some noise + pGib->pev->velocity.x += RANDOM_FLOAT ( -0.15, 0.15 ); + pGib->pev->velocity.y += RANDOM_FLOAT ( -0.15, 0.15 ); + pGib->pev->velocity.z += RANDOM_FLOAT ( -0.15, 0.15 ); + + pGib->pev->velocity = pGib->pev->velocity * 900; + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 250, 400 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 250, 400 ); + + // copy owner's blood color + pGib->m_bloodColor = (CBaseEntity::Instance(pevVictim))->BloodColor(); + + if ( pevVictim->health > -50) + { + pGib->pev->velocity = pGib->pev->velocity * 0.7; + } + else if ( pevVictim->health > -200) + { + pGib->pev->velocity = pGib->pev->velocity * 2; + } + else + { + pGib->pev->velocity = pGib->pev->velocity * 4; + } + + + pGib->pev->movetype = MOVETYPE_TOSS; + pGib->pev->solid = SOLID_BBOX; + UTIL_SetSize ( pGib->pev, Vector ( 0, 0 ,0 ), Vector ( 0, 0, 0 ) ); + pGib->SetTouch ( &CGib::StickyGibTouch ); + pGib->SetThink (NULL); + } + pGib->LimitVelocity(); + } +} + +void CGib :: SpawnHeadGib( entvars_t *pevVictim ) +{ + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + if ( g_Language == LANGUAGE_GERMAN ) + { + pGib->Spawn( "models/germangibs.mdl" );// throw one head + pGib->pev->body = 0; + } + else + { + pGib->Spawn( "models/head.mdl" );// throw one head + } + + if ( pevVictim ) + { + pGib->pev->origin = pevVictim->origin + pevVictim->view_ofs; + + pGib->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(300,400)); + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + + // copy owner's blood color + pGib->m_bloodColor = (CBaseEntity::Instance(pevVictim))->BloodColor(); + + // Set its groupinfo to the player's + pGib->pev->groupinfo = pevVictim->groupinfo; + // Since this only ever happens when a player is hit by a frozen decapitator disc, make the gibs glow + pGib->pev->renderfx = kRenderFxGlowShell; + pGib->pev->rendercolor = Vector( 100,100, 250 ); + pGib->pev->renderamt = 25; + } + pGib->LimitVelocity(); +} + +void CGib :: SpawnRandomGibs( entvars_t *pevVictim, int cGibs, int human ) +{ + int cSplat; + + for ( cSplat = 0 ; cSplat < cGibs ; cSplat++ ) + { + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + if ( g_Language == LANGUAGE_GERMAN ) + { + pGib->Spawn( "models/germangibs.mdl" ); + pGib->pev->body = RANDOM_LONG(0,GERMAN_GIB_COUNT-1); + } + else + { + if ( human ) + { + // human pieces + pGib->Spawn( "models/hgibs.mdl" ); + pGib->pev->body = RANDOM_LONG(1,HUMAN_GIB_COUNT-1);// start at one to avoid throwing random amounts of skulls (0th gib) + } + else + { + // aliens + pGib->Spawn( "models/agibs.mdl" ); + pGib->pev->body = RANDOM_LONG(0,ALIEN_GIB_COUNT-1); + } + } + + if ( pevVictim ) + { + // spawn the gib somewhere in the monster's bounding volume + pGib->pev->origin.x = pevVictim->absmin.x + pevVictim->size.x * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.y = pevVictim->absmin.y + pevVictim->size.y * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.z = pevVictim->absmin.z + pevVictim->size.z * (RANDOM_FLOAT ( 0 , 1 ) ) + 1; // absmin.z is in the floor because the engine subtracts 1 to enlarge the box + + // make the gib fly away from the attack vector + pGib->pev->velocity = g_vecAttackDir * -1; + + // mix in some noise + pGib->pev->velocity.x += RANDOM_FLOAT ( -0.25, 0.25 ); + pGib->pev->velocity.y += RANDOM_FLOAT ( -0.25, 0.25 ); + pGib->pev->velocity.z += RANDOM_FLOAT ( -0.25, 0.25 ); + + pGib->pev->velocity = pGib->pev->velocity * RANDOM_FLOAT ( 600, 700 ); + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + + // copy owner's blood color + pGib->m_bloodColor = (CBaseEntity::Instance(pevVictim))->BloodColor(); + + if ( pevVictim->health > -50) + { + pGib->pev->velocity = pGib->pev->velocity * 0.7; + } + else if ( pevVictim->health > -200) + { + pGib->pev->velocity = pGib->pev->velocity * 2; + } + else + { + pGib->pev->velocity = pGib->pev->velocity * 4; + } + + pGib->pev->solid = SOLID_BBOX; + UTIL_SetSize ( pGib->pev, Vector( 0 , 0 , 0 ), Vector ( 0, 0, 0 ) ); + + // Set its groupinfo to the player's + pGib->pev->groupinfo = pevVictim->groupinfo; + // Since this only ever happens when a player is hit by a frozen decapitator disc, make the gibs glow + pGib->pev->renderfx = kRenderFxGlowShell; + pGib->pev->rendercolor = Vector( 150,150,250 ); + pGib->pev->renderamt = 100; + } + pGib->LimitVelocity(); + } +} + + +BOOL CBaseMonster :: HasHumanGibs( void ) +{ + int myClass = Classify(); + + if ( myClass == CLASS_HUMAN_MILITARY || + myClass == CLASS_PLAYER_ALLY || + myClass == CLASS_HUMAN_PASSIVE || + myClass == CLASS_PLAYER ) + + return TRUE; + + return FALSE; +} + + +BOOL CBaseMonster :: HasAlienGibs( void ) +{ + int myClass = Classify(); + + if ( myClass == CLASS_ALIEN_MILITARY || + myClass == CLASS_ALIEN_MONSTER || + myClass == CLASS_ALIEN_PASSIVE || + myClass == CLASS_INSECT || + myClass == CLASS_ALIEN_PREDATOR || + myClass == CLASS_ALIEN_PREY ) + + return TRUE; + + return FALSE; +} + + +void CBaseMonster::FadeMonster( void ) +{ + StopAnimation(); + pev->velocity = g_vecZero; + pev->movetype = MOVETYPE_NONE; + pev->avelocity = g_vecZero; + pev->animtime = gpGlobals->time; + pev->effects |= EF_NOINTERP; + SUB_StartFadeOut(); +} + +//========================================================= +// GibMonster - create some gore and get rid of a monster's +// model. +//========================================================= +void CBaseMonster :: GibMonster( void ) +{ + TraceResult tr; + BOOL gibbed = FALSE; + + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "common/bodysplat.wav", 1, ATTN_NORM); + + // only humans throw skulls !!!UNDONE - eventually monsters will have their own sets of gibs + if ( HasHumanGibs() ) + { + if ( CVAR_GET_FLOAT("violence_hgibs") != 0 ) // Only the player will ever get here + { + CGib::SpawnHeadGib( pev ); + CGib::SpawnRandomGibs( pev, 4, 1 ); // throw some human gibs. + } + gibbed = TRUE; + } + else if ( HasAlienGibs() ) + { + if ( CVAR_GET_FLOAT("violence_agibs") != 0 ) // Should never get here, but someone might call it directly + { + CGib::SpawnRandomGibs( pev, 4, 0 ); // Throw alien gibs + } + gibbed = TRUE; + } + + if ( !IsPlayer() ) + { + if ( gibbed ) + { + // don't remove players! + SetThink ( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time; + } + else + { + FadeMonster(); + } + } +} + +//========================================================= +// GetDeathActivity - determines the best type of death +// anim to play. +//========================================================= +Activity CBaseMonster :: GetDeathActivity ( void ) +{ + Activity deathActivity; + + if ( pev->deadflag != DEAD_NO ) + { + // don't run this while dying. + return m_IdealActivity; + } + + switch ( m_LastHitGroup ) + { + // try to pick a region-specific death. + case HITGROUP_HEAD: + deathActivity = ACT_DIE_HEADSHOT; + break; + + default: + deathActivity = ACT_DIEFORWARD; + break; + } + + return deathActivity; +} + +//========================================================= +// GetSmallFlinchActivity - determines the best type of flinch +// anim to play. +//========================================================= +Activity CBaseMonster :: GetSmallFlinchActivity ( void ) +{ + Activity flinchActivity; + + flinchActivity = ACT_FLINCH_BACK; + + return flinchActivity; +} + + +void CBaseMonster::BecomeDead( void ) +{ + pev->takedamage = DAMAGE_YES;// don't let autoaim aim at corpses. + + // give the corpse half of the monster's original maximum health. + pev->health = pev->max_health / 2; + pev->max_health = 5; // max_health now becomes a counter for how many blood decals the corpse can place. + + // make the corpse fly away from the attack vector + pev->movetype = MOVETYPE_TOSS; + //pev->flags &= ~FL_ONGROUND; + //pev->origin.z += 2; + //pev->velocity = g_vecAttackDir * -1; + //pev->velocity = pev->velocity * RANDOM_FLOAT( 300, 400 ); +} + + +BOOL CBaseMonster::ShouldGibMonster( int iGib ) +{ + if ( ( iGib == GIB_NORMAL && pev->health < GIB_HEALTH_VALUE ) || ( iGib == GIB_ALWAYS ) ) + return TRUE; + + return FALSE; +} + + +void CBaseMonster::CallGibMonster( void ) +{ + BOOL fade = FALSE; + + if ( HasHumanGibs() ) + { + if ( CVAR_GET_FLOAT("violence_hgibs") == 0 ) + fade = TRUE; + } + else if ( HasAlienGibs() ) + { + if ( CVAR_GET_FLOAT("violence_agibs") == 0 ) + fade = TRUE; + } + + pev->takedamage = DAMAGE_NO; + pev->solid = SOLID_NOT;// do something with the body. while monster blows up + + if ( fade ) + { + FadeMonster(); + } + else + { + pev->effects = EF_NODRAW; // make the model invisible. + GibMonster(); + } + + pev->deadflag = DEAD_DEAD; + FCheckAITrigger(); + + // don't let the status bar glitch for players.with <0 health. + if (pev->health < -99) + { + pev->health = 0; + } + + if ( ShouldFadeOnDeath() && !fade ) + UTIL_Remove(this); +} + + +/* +============ +Killed +============ +*/ +void CBaseMonster :: Killed( entvars_t *pevAttacker, int iGib ) +{ + if ( HasMemory( bits_MEMORY_KILLED ) ) + { + if ( ShouldGibMonster( iGib ) ) + CallGibMonster(); + return; + } + + Remember( bits_MEMORY_KILLED ); + + // clear the deceased's sound channels.(may have been firing or reloading when killed) + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "common/null.wav", 1, ATTN_NORM); + m_IdealMonsterState = MONSTERSTATE_DEAD; + // Make sure this condition is fired too (TakeDamage breaks out before this happens on death) + SetConditions( bits_COND_LIGHT_DAMAGE ); + + // tell owner ( if any ) that we're dead.This is mostly for MonsterMaker functionality. + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + if ( pOwner ) + { + pOwner->DeathNotice( pev ); + } + + if ( ShouldGibMonster( iGib ) ) + { + CallGibMonster(); + return; + } + else if ( pev->flags & FL_MONSTER ) + { + SetTouch( NULL ); + BecomeDead(); + } + + // don't let the status bar glitch for players.with <0 health. + if (pev->health < -99) + { + pev->health = 0; + } + + //pev->enemy = ENT( pevAttacker );//why? (sjb) + + m_IdealMonsterState = MONSTERSTATE_DEAD; +} + +// +// fade out - slowly fades a entity out, then removes it. +// +// DON'T USE ME FOR GIBS AND STUFF IN MULTIPLAYER! +// SET A FUTURE THINK AND A RENDERMODE!! +void CBaseEntity :: SUB_StartFadeOut ( void ) +{ + if (pev->rendermode == kRenderNormal) + { + pev->renderamt = 255; + pev->rendermode = kRenderTransTexture; + } + + pev->solid = SOLID_NOT; + pev->avelocity = g_vecZero; + + pev->nextthink = gpGlobals->time + 0.1; + SetThink ( &CBaseEntity::SUB_FadeOut ); +} + +void CBaseEntity :: SUB_FadeOut ( void ) +{ + if ( pev->renderamt > 7 ) + { + pev->renderamt -= 7; + pev->nextthink = gpGlobals->time + 0.1; + } + else + { + pev->renderamt = 0; + pev->nextthink = gpGlobals->time + 0.2; + SetThink ( &CBaseEntity::SUB_Remove ); + } +} + +//========================================================= +// WaitTillLand - in order to emit their meaty scent from +// the proper location, gibs should wait until they stop +// bouncing to emit their scent. That's what this function +// does. +//========================================================= +void CGib :: WaitTillLand ( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + if ( pev->velocity == g_vecZero ) + { + SetThink (&CBaseEntity::SUB_StartFadeOut); + pev->nextthink = gpGlobals->time + m_lifeTime; + + // If you bleed, you stink! + if ( m_bloodColor != DONT_BLEED ) + { + // ok, start stinkin! + CSoundEnt::InsertSound ( bits_SOUND_MEAT, pev->origin, 384, 25 ); + } + } + else + { + // wait and check again in another half second. + pev->nextthink = gpGlobals->time + 0.5; + } +} + +// +// Gib bounces on the ground or wall, sponges some blood down, too! +// +void CGib :: BounceGibTouch ( CBaseEntity *pOther ) +{ + Vector vecSpot; + TraceResult tr; + + //if ( RANDOM_LONG(0,1) ) + // return;// don't bleed everytime + + if (pev->flags & FL_ONGROUND) + { + pev->velocity = pev->velocity * 0.9; + pev->angles.x = 0; + pev->angles.z = 0; + pev->avelocity.x = 0; + pev->avelocity.z = 0; + } + else + { + if ( g_Language != LANGUAGE_GERMAN && m_cBloodDecals > 0 && m_bloodColor != DONT_BLEED ) + { + vecSpot = pev->origin + Vector ( 0 , 0 , 8 );//move up a bit, and trace down. + UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -24 ), ignore_monsters, ENT(pev), & tr); + + UTIL_BloodDecalTrace( &tr, m_bloodColor ); + + m_cBloodDecals--; + } + + if ( m_material != matNone && RANDOM_LONG(0,2) == 0 ) + { + float volume; + float zvel = fabs(pev->velocity.z); + + volume = 0.8 * min(1.0, ((float)zvel) / 450.0); + + CBreakable::MaterialSoundRandom( edict(), (Materials)m_material, volume ); + } + } +} + +// +// Sticky gib puts blood on the wall and stays put. +// +void CGib :: StickyGibTouch ( CBaseEntity *pOther ) +{ + Vector vecSpot; + TraceResult tr; + + SetThink ( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time + 10; + + if ( !FClassnameIs( pOther->pev, "worldspawn" ) ) + { + pev->nextthink = gpGlobals->time; + return; + } + + UTIL_TraceLine ( pev->origin, pev->origin + pev->velocity * 32, ignore_monsters, ENT(pev), & tr); + + UTIL_BloodDecalTrace( &tr, m_bloodColor ); + + pev->velocity = tr.vecPlaneNormal * -1; + pev->angles = UTIL_VecToAngles ( pev->velocity ); + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + pev->movetype = MOVETYPE_NONE; +} + +// +// Throw a chunk +// +void CGib :: Spawn( const char *szGibModel ) +{ + pev->movetype = MOVETYPE_BOUNCE; + pev->friction = 0.55; // deading the bounce a bit + + // sometimes an entity inherits the edict from a former piece of glass, + // and will spawn using the same render FX or rendermode! bad! + pev->renderamt = 255; + pev->rendermode = kRenderNormal; + pev->renderfx = kRenderFxNone; + pev->solid = SOLID_SLIDEBOX;/// hopefully this will fix the VELOCITY TOO LOW crap + pev->classname = MAKE_STRING("gib"); + + SET_MODEL(ENT(pev), szGibModel); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + + pev->nextthink = gpGlobals->time + 4; + m_lifeTime = 10; + SetThink ( &CGib::WaitTillLand ); + SetTouch ( &CGib::BounceGibTouch ); + + m_material = matNone; + m_cBloodDecals = 5;// how many blood decals this gib can place (1 per bounce until none remain). +} + +// take health +int CBaseMonster :: TakeHealth (float flHealth, int bitsDamageType) +{ + if (!pev->takedamage) + return 0; + + // clear out any damage types we healed. + // UNDONE: generic health should not heal any + // UNDONE: time-based damage + + m_bitsDamageType &= ~(bitsDamageType & ~DMG_TIMEBASED); + + return CBaseEntity::TakeHealth(flHealth, bitsDamageType); +} + +/* +============ +TakeDamage + +The damage is coming from inflictor, but get mad at attacker +This should be the only function that ever reduces health. +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 +============ +*/ +int CBaseMonster :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + float flTake; + Vector vecDir; + + if (!pev->takedamage) + return 0; + + if ( !IsAlive() ) + { + return DeadTakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); + } + + if ( pev->deadflag == DEAD_NO ) + { + // no pain sound during death animation. + PainSound();// "Ouch!" + } + + //!!!LATER - make armor consideration here! + flTake = flDamage; + + // set damage type sustained + m_bitsDamageType |= bitsDamageType; + + // 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 )) + { + CBaseEntity *pInflictor = CBaseEntity :: Instance( pevInflictor ); + if (pInflictor) + { + vecDir = ( pInflictor->Center() - Vector ( 0, 0, 10 ) - Center() ).Normalize(); + vecDir = g_vecAttackDir = vecDir.Normalize(); + } + } + + // add to the damage total for clients, which will be sent as a single + // message at the end of the frame + // todo: remove after combining shotgun blasts? + if ( IsPlayer() ) + { + if ( pevInflictor ) + pev->dmg_inflictor = ENT(pevInflictor); + + pev->dmg_take += flTake; + + // check for godmode or invincibility + if ( pev->flags & FL_GODMODE ) + { + return 0; + } + } + + // if this is a player, move him around! + if ( ( !FNullEnt( pevInflictor ) ) && (pev->movetype == MOVETYPE_WALK) && (!pevAttacker || pevAttacker->solid != SOLID_TRIGGER) ) + { + pev->velocity = pev->velocity + vecDir * -DamageForce( flDamage ); + } + + // 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 ) + { + SetConditions( bits_COND_LIGHT_DAMAGE ); + return 0; + } + + if ( pev->health <= 0 ) + { + g_pevLastInflictor = pevInflictor; + + if ( bitsDamageType & DMG_ALWAYSGIB ) + { + Killed( pevAttacker, GIB_ALWAYS ); + } + else if ( bitsDamageType & DMG_NEVERGIB ) + { + Killed( pevAttacker, GIB_NEVER ); + } + else + { + Killed( pevAttacker, GIB_NORMAL ); + } + + g_pevLastInflictor = NULL; + + return 0; + } + + // react to the damage (get mad) + if ( (pev->flags & FL_MONSTER) && !FNullEnt(pevAttacker) ) + { + if ( pevAttacker->flags & (FL_MONSTER | FL_CLIENT) ) + {// only if the attack was a monster or client! + + // enemy's last known position is somewhere down the vector that the attack came from. + if (pevInflictor) + { + if (m_hEnemy == 0 || pevInflictor == m_hEnemy->pev || !HasConditions(bits_COND_SEE_ENEMY)) + { + m_vecEnemyLKP = pevInflictor->origin; + } + } + else + { + m_vecEnemyLKP = pev->origin + ( g_vecAttackDir * 64 ); + } + + MakeIdealYaw( m_vecEnemyLKP ); + + // add pain to the conditions + // !!!HACKHACK - fudged for now. Do we want to have a virtual function to determine what is light and + // heavy damage per monster class? + if ( flDamage > 0 ) + { + SetConditions(bits_COND_LIGHT_DAMAGE); + } + + if ( flDamage >= 20 ) + { + SetConditions(bits_COND_HEAVY_DAMAGE); + } + } + } + + return 1; +} + +//========================================================= +// DeadTakeDamage - takedamage function called when a monster's +// corpse is damaged. +//========================================================= +int CBaseMonster :: DeadTakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + Vector vecDir; + + // 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 )) + { + CBaseEntity *pInflictor = CBaseEntity :: Instance( pevInflictor ); + if (pInflictor) + { + vecDir = ( pInflictor->Center() - Vector ( 0, 0, 10 ) - Center() ).Normalize(); + vecDir = g_vecAttackDir = vecDir.Normalize(); + } + } + +#if 0// turn this back on when the bounding box issues are resolved. + + pev->flags &= ~FL_ONGROUND; + pev->origin.z += 1; + + // let the damage scoot the corpse around a bit. + if ( !FNullEnt(pevInflictor) && (pevAttacker->solid != SOLID_TRIGGER) ) + { + pev->velocity = pev->velocity + vecDir * -DamageForce( flDamage ); + } + +#endif + + // kill the corpse if enough damage was done to destroy the corpse and the damage is of a type that is allowed to destroy the corpse. + if ( bitsDamageType & DMG_GIB_CORPSE ) + { + if ( pev->health <= flDamage ) + { + pev->health = -50; + Killed( pevAttacker, GIB_ALWAYS ); + return 0; + } + // Accumulate corpse gibbing damage, so you can gib with multiple hits + pev->health -= flDamage * 0.1; + } + + return 1; +} + + +float CBaseMonster :: DamageForce( float damage ) +{ + float force = damage * ((32 * 32 * 72.0) / (pev->size.x * pev->size.y * pev->size.z)) * 5; + + if ( force > 1000.0) + { + force = 1000.0; + } + + return force; +} + +// +// RadiusDamage - this entity is exploding, or otherwise needs to inflict damage upon entities within a certain range. +// +// only damage ents that can clearly be seen by the explosion! + + +void RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, float flRadius, int iClassIgnore, int bitsDamageType ) +{ + CBaseEntity *pEntity = NULL; + TraceResult tr; + float flAdjustedDamage, falloff; + Vector vecSpot; + + if ( flRadius ) + falloff = flDamage / flRadius; + else + falloff = 1.0; + + int bInWater = (UTIL_PointContents ( vecSrc ) == CONTENTS_WATER); + + vecSrc.z += 1;// in case grenade is lying on the ground + + if ( !pevAttacker ) + pevAttacker = pevInflictor; + + // iterate on all entities in the vicinity. + while ((pEntity = UTIL_FindEntityInSphere( pEntity, vecSrc, flRadius )) != NULL) + { + if ( pEntity->pev->takedamage != DAMAGE_NO ) + { + // UNDONE: this should check a damage mask, not an ignore + if ( iClassIgnore != CLASS_NONE && pEntity->Classify() == iClassIgnore ) + {// houndeyes don't hurt other houndeyes with their attack + continue; + } + + // blast's don't tavel into or out of water + if (bInWater && pEntity->pev->waterlevel == 0) + continue; + if (!bInWater && pEntity->pev->waterlevel == 3) + continue; + + vecSpot = pEntity->BodyTarget( vecSrc ); + + UTIL_TraceLine ( vecSrc, vecSpot, dont_ignore_monsters, ENT(pevInflictor), &tr ); + + if ( tr.flFraction == 1.0 || tr.pHit == pEntity->edict() ) + {// the explosion can 'see' this entity, so hurt them! + if (tr.fStartSolid) + { + // if we're stuck inside them, fixup the position and distance + tr.vecEndPos = vecSrc; + tr.flFraction = 0.0; + } + + // decrease damage for an ent that's farther from the bomb. + flAdjustedDamage = ( vecSrc - tr.vecEndPos ).Length() * falloff; + flAdjustedDamage = flDamage - flAdjustedDamage; + + if ( flAdjustedDamage < 0 ) + { + flAdjustedDamage = 0; + } + + // ALERT( at_console, "hit %s\n", STRING( pEntity->pev->classname ) ); + if (tr.flFraction != 1.0) + { + ClearMultiDamage( ); + pEntity->TraceAttack( pevInflictor, flAdjustedDamage, (tr.vecEndPos - vecSrc).Normalize( ), &tr, bitsDamageType ); + ApplyMultiDamage( pevInflictor, pevAttacker ); + } + else + { + pEntity->TakeDamage ( pevInflictor, pevAttacker, flAdjustedDamage, bitsDamageType ); + } + } + } + } +} + + +void CBaseMonster :: RadiusDamage(entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) +{ + ::RadiusDamage( pev->origin, pevInflictor, pevAttacker, flDamage, flDamage * 2.5, iClassIgnore, bitsDamageType ); +} + + +void CBaseMonster :: RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) +{ + ::RadiusDamage( vecSrc, pevInflictor, pevAttacker, flDamage, flDamage * 2.5, iClassIgnore, bitsDamageType ); +} + + +//========================================================= +// CheckTraceHullAttack - expects a length to trace, amount +// of damage to do, and damage type. Returns a pointer to +// the damaged entity in case the monster wishes to do +// other stuff to the victim (punchangle, etc) +// +// Used for many contact-range melee attacks. Bites, claws, etc. +//========================================================= +CBaseEntity* CBaseMonster :: CheckTraceHullAttack( float flDist, int iDamage, int iDmgType ) +{ + TraceResult tr; + + if (IsPlayer()) + UTIL_MakeVectors( pev->angles ); + else + UTIL_MakeAimVectors( pev->angles ); + + Vector vecStart = pev->origin; + vecStart.z += pev->size.z * 0.5; + Vector vecEnd = vecStart + (gpGlobals->v_forward * flDist ); + + UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT(pev), &tr ); + + if ( tr.pHit ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + + if ( iDamage > 0 ) + { + pEntity->TakeDamage( pev, pev, iDamage, iDmgType ); + } + + return pEntity; + } + + return NULL; +} + + +//========================================================= +// FInViewCone - returns true is the passed ent is in +// the caller's forward view cone. The dot product is performed +// in 2d, making the view cone infinitely tall. +//========================================================= +BOOL CBaseMonster :: FInViewCone ( CBaseEntity *pEntity ) +{ + Vector2D vec2LOS; + float flDot; + + UTIL_MakeVectors ( pev->angles ); + + vec2LOS = ( pEntity->pev->origin - pev->origin ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + flDot = DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() ); + + if ( flDot > m_flFieldOfView ) + { + return TRUE; + } + else + { + return FALSE; + } +} + +//========================================================= +// FInViewCone - returns true is the passed vector is in +// the caller's forward view cone. The dot product is performed +// in 2d, making the view cone infinitely tall. +//========================================================= +BOOL CBaseMonster :: FInViewCone ( Vector *pOrigin ) +{ + Vector2D vec2LOS; + float flDot; + + UTIL_MakeVectors ( pev->angles ); + + vec2LOS = ( *pOrigin - pev->origin ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + flDot = DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() ); + + if ( flDot > m_flFieldOfView ) + { + return TRUE; + } + else + { + return FALSE; + } +} + +//========================================================= +// FVisible - returns true if a line can be traced from +// the caller's eyes to the target +//========================================================= +BOOL CBaseEntity :: FVisible ( CBaseEntity *pEntity ) +{ + TraceResult tr; + Vector vecLookerOrigin; + Vector vecTargetOrigin; + + if (FBitSet( pEntity->pev->flags, FL_NOTARGET )) + return FALSE; + + // don't look through water + if ((pev->waterlevel != 3 && pEntity->pev->waterlevel == 3) + || (pev->waterlevel == 3 && pEntity->pev->waterlevel == 0)) + return FALSE; + + vecLookerOrigin = pev->origin + pev->view_ofs;//look through the caller's 'eyes' + vecTargetOrigin = pEntity->EyePosition(); + + UTIL_TraceLine(vecLookerOrigin, vecTargetOrigin, ignore_monsters, ignore_glass, ENT(pev)/*pentIgnore*/, &tr); + + if (tr.flFraction != 1.0) + { + return FALSE;// Line of sight is not established + } + else + { + return TRUE;// line of sight is valid. + } +} + +//========================================================= +// FVisible - returns true if a line can be traced from +// the caller's eyes to the target vector +//========================================================= +BOOL CBaseEntity :: FVisible ( const Vector &vecOrigin ) +{ + TraceResult tr; + Vector vecLookerOrigin; + + vecLookerOrigin = EyePosition();//look through the caller's 'eyes' + + UTIL_TraceLine(vecLookerOrigin, vecOrigin, ignore_monsters, ignore_glass, ENT(pev)/*pentIgnore*/, &tr); + + if (tr.flFraction != 1.0) + { + return FALSE;// Line of sight is not established + } + else + { + return TRUE;// line of sight is valid. + } +} + +/* +================ +TraceAttack +================ +*/ +void CBaseEntity::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + Vector vecOrigin = ptr->vecEndPos - vecDir * 4; + + if ( pev->takedamage ) + { + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + + int blood = BloodColor(); + + if ( blood != DONT_BLEED ) + { + SpawnBlood(vecOrigin, blood, flDamage);// a little surface blood. + TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); + } + } +} + + +/* +//========================================================= +// TraceAttack +//========================================================= +void CBaseMonster::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + Vector vecOrigin = ptr->vecEndPos - vecDir * 4; + + ALERT ( at_console, "%d\n", ptr->iHitgroup ); + + + if ( pev->takedamage ) + { + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + + int blood = BloodColor(); + + if ( blood != DONT_BLEED ) + { + SpawnBlood(vecOrigin, blood, flDamage);// a little surface blood. + } + } +} +*/ + +//========================================================= +// TraceAttack +//========================================================= +void CBaseMonster :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if ( pev->takedamage ) + { + SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage);// a little surface blood. + TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + } +} + +/* +================ +FireBullets + +Go to the trouble of combining multiple pellets into a single damage call. +================ +*/ +void CBaseEntity::FireBullets(ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq, int iDamage, entvars_t *pevAttacker ) +{ + static int tracerCount; + int tracer; + TraceResult tr; + Vector vecRight = gpGlobals->v_right; + Vector vecUp = gpGlobals->v_up; + + if ( pevAttacker == NULL ) + pevAttacker = pev; // the default attacker is ourselves + + // 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); + ClearMultiDamage(); + gMultiDamage.type = DMG_BULLET | DMG_NEVERGIB; + + for (ULONG iShot = 1; iShot <= cShots; iShot++) + { + // get circular gaussian spread + float x, y, z; + 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); + + 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); + + tracer = 0; + if (iTracerFreq != 0 && (tracerCount++ % iTracerFreq) == 0) + { + Vector vecTracerSrc; + + if ( IsPlayer() ) + {// adjust tracer position for player + vecTracerSrc = vecSrc + Vector ( 0 , 0 , -4 ) + gpGlobals->v_right * 2 + gpGlobals->v_forward * 16; + } + else + { + vecTracerSrc = vecSrc; + } + + if ( iTracerFreq != 1 ) // guns that always trace also always decal + tracer = 1; + switch( iBulletType ) + { + case BULLET_PLAYER_MP5: + case BULLET_MONSTER_MP5: + case BULLET_MONSTER_9MM: + case BULLET_MONSTER_12MM: + default: + break; + } + } + // do damage, paint decals + if (tr.flFraction != 1.0) + { + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + + if ( iDamage ) + { + 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; + + 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_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); + 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; + } + } + // make bullet trails + UTIL_BubbleTrail( vecSrc, tr.vecEndPos, static_cast((flDistance * tr.flFraction) / 64.0) ); + } + ApplyMultiDamage(pev, pevAttacker); +} + + +void CBaseEntity :: TraceBleed( float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + if (BloodColor() == DONT_BLEED) + return; + + if (flDamage == 0) + return; + + if (! (bitsDamageType & (DMG_CRUSH | DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB | DMG_MORTAR))) + return; + + // make blood decal on the wall! + TraceResult Bloodtr; + Vector vecTraceDir; + float flNoise; + int cCount; + int i; + +/* + if ( !IsAlive() ) + { + // dealing with a dead monster. + if ( pev->max_health <= 0 ) + { + // no blood decal for a monster that has already decalled its limit. + return; + } + else + { + pev->max_health--; + } + } +*/ + + if (flDamage < 10) + { + flNoise = 0.1; + cCount = 1; + } + else if (flDamage < 25) + { + flNoise = 0.2; + cCount = 2; + } + else + { + flNoise = 0.3; + cCount = 4; + } + + for ( i = 0 ; i < cCount ; i++ ) + { + vecTraceDir = vecDir * -1;// trace in the opposite direction the shot came from (the direction the shot is going) + + vecTraceDir.x += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.y += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.z += RANDOM_FLOAT( -flNoise, flNoise ); + + UTIL_TraceLine( ptr->vecEndPos, ptr->vecEndPos + vecTraceDir * -172, ignore_monsters, ENT(pev), &Bloodtr); + + if ( Bloodtr.flFraction != 1.0 ) + { + UTIL_BloodDecalTrace( &Bloodtr, BloodColor() ); + } + } +} + +//========================================================= +//========================================================= +void CBaseMonster :: MakeDamageBloodDecal ( int cCount, float flNoise, TraceResult *ptr, const Vector &vecDir ) +{ + // make blood decal on the wall! + TraceResult Bloodtr; + Vector vecTraceDir; + int i; + + if ( !IsAlive() ) + { + // dealing with a dead monster. + if ( pev->max_health <= 0 ) + { + // no blood decal for a monster that has already decalled its limit. + return; + } + else + { + pev->max_health--; + } + } + + for ( i = 0 ; i < cCount ; i++ ) + { + vecTraceDir = vecDir; + + vecTraceDir.x += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.y += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.z += RANDOM_FLOAT( -flNoise, flNoise ); + + UTIL_TraceLine( ptr->vecEndPos, ptr->vecEndPos + vecTraceDir * 172, ignore_monsters, ENT(pev), &Bloodtr); + +/* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + WRITE_COORD( ptr->vecEndPos.x ); + WRITE_COORD( ptr->vecEndPos.y ); + WRITE_COORD( ptr->vecEndPos.z ); + + WRITE_COORD( Bloodtr.vecEndPos.x ); + WRITE_COORD( Bloodtr.vecEndPos.y ); + WRITE_COORD( Bloodtr.vecEndPos.z ); + MESSAGE_END(); +*/ + + if ( Bloodtr.flFraction != 1.0 ) + { + UTIL_BloodDecalTrace( &Bloodtr, BloodColor() ); + } + } +} diff --git a/hlsdk/ricochet/dlls/decals.h b/hlsdk/ricochet/dlls/decals.h new file mode 100644 index 00000000..0cb87eef --- /dev/null +++ b/hlsdk/ricochet/dlls/decals.h @@ -0,0 +1,75 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +#ifndef DECALS_H +#define DECALS_H + +// +// Dynamic Decals +// +enum decal_e +{ + DECAL_GUNSHOT1 = 0, + DECAL_GUNSHOT2, + DECAL_GUNSHOT3, + DECAL_GUNSHOT4, + DECAL_GUNSHOT5, + DECAL_LAMBDA1, + DECAL_LAMBDA2, + DECAL_LAMBDA3, + DECAL_LAMBDA4, + DECAL_LAMBDA5, + DECAL_LAMBDA6, + DECAL_SCORCH1, + DECAL_SCORCH2, + DECAL_BLOOD1, + DECAL_BLOOD2, + DECAL_BLOOD3, + DECAL_BLOOD4, + DECAL_BLOOD5, + DECAL_BLOOD6, + DECAL_YBLOOD1, + DECAL_YBLOOD2, + DECAL_YBLOOD3, + DECAL_YBLOOD4, + DECAL_YBLOOD5, + DECAL_YBLOOD6, + DECAL_GLASSBREAK1, + DECAL_GLASSBREAK2, + DECAL_GLASSBREAK3, + DECAL_BIGSHOT1, + DECAL_BIGSHOT2, + DECAL_BIGSHOT3, + DECAL_BIGSHOT4, + DECAL_BIGSHOT5, + DECAL_SPIT1, + DECAL_SPIT2, + DECAL_BPROOF1, // Bulletproof glass decal + DECAL_GARGSTOMP1, // Gargantua stomp crack + DECAL_SMALLSCORCH1, // Small scorch mark + DECAL_SMALLSCORCH2, // Small scorch mark + DECAL_SMALLSCORCH3, // Small scorch mark + DECAL_MOMMABIRTH, // Big momma birth splatter + DECAL_MOMMASPLAT, +}; + +typedef struct +{ + char *name; + int index; +} DLL_DECALLIST; + +extern DLL_DECALLIST gDecals[]; + +#endif // DECALS_H diff --git a/hlsdk/ricochet/dlls/disc_arena.cpp b/hlsdk/ricochet/dlls/disc_arena.cpp new file mode 100644 index 00000000..b640f7ea --- /dev/null +++ b/hlsdk/ricochet/dlls/disc_arena.cpp @@ -0,0 +1,1044 @@ +//=========== (C) Copyright 1999 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: Handles the arena portion of discwar +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "gamerules.h" +#include "weapons.h" +#include "discwar.h" +#include "disc_arena.h" +#include "disc_objects.h" + +int g_iNextArenaGroupInfo = 0; + +void ClientUserInfoChanged( edict_t *pEntity, char *infobuffer ); +edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ); +void respawn(entvars_t* pev, BOOL fCopyCorpse); +extern int gmsgStartRnd; +extern int gmsgEndRnd; +extern int gmsgTeamInfo; +extern int g_iPlayersPerTeam; +extern int g_iMapTurnedOffArena; + +CDiscArena *g_pArenaList[ MAX_ARENAS ]; + +char *g_szCountDownVox[6] = +{ + "die", + "one", + "two", + "three", + "four", +}; + +char *g_szTeamNames[3] = +{ + "", + "red", + "blue", +}; + +int InArenaMode() +{ + if ( g_iMapTurnedOffArena ) + return FALSE; + + if ( gpGlobals->maxClients == 1 || (CVAR_GET_FLOAT("rc_arena") == 0) ) + return FALSE; + + return TRUE; +} + +LINK_ENTITY_TO_CLASS( disc_arena, CDiscArena ); + +// NOTE: +// We store queue position in pev->playerclass, so it's automatically pushed +// down to the client. The scoreboard uses pev->playerclass to display the +// positions of the players in the queue. +void CDiscArena::Spawn( void ) +{ + pev->groupinfo = 1 << (g_iNextArenaGroupInfo++); + Reset(); + + // Initialize + m_iMaxRounds = static_cast(CVAR_GET_FLOAT("rc_rounds")); + m_iPlayersPerTeam = g_iPlayersPerTeam; + SetThink( NULL ); +} + +void CDiscArena::Reset( void ) +{ + // Remove all clients in the queue + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if (pPlayer && (pPlayer->m_pCurrentArena == this) && pPlayer->m_bHasDisconnected != TRUE ) + { + RemoveClient( pPlayer ); + + // Move her into spectator mode + //MoveToSpectator( pPlayer ); + } + } + + m_pPlayerQueue = NULL; + m_iPlayers = 0; + m_flTimeLimitOver = 0; + m_bShownTimeWarning = FALSE; + m_iArenaState = ARENA_WAITING_FOR_PLAYERS; + memset( m_hCombatants, 0, sizeof( m_hCombatants ) ); + + SetThink( NULL ); + pev->nextthink = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Start a battle. Spawn all the players, and begin the countdown. +//----------------------------------------------------------------------------- +void CDiscArena::StartBattle( void ) +{ + m_iCurrRound = 0; + m_iTeamOneScore = m_iTeamTwoScore = 0; + + // First, set all players in this arena to "didn't play" + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if (pPlayer && (pPlayer->m_pCurrentArena == this) && pPlayer->m_bHasDisconnected != TRUE ) + pPlayer->m_iLastGameResult = GAME_DIDNTPLAY; + } + + // Get the players in the battle + for ( int i = 0; i < (m_iPlayersPerTeam * 2); i++ ) + { + CBasePlayer *pCurr; + + // Check to see if this slot's already full + if ( m_hCombatants[ i ] ) + { + pCurr = (CBasePlayer*)(CBaseEntity*)m_hCombatants[ i ]; + } + else + { + // Pop a new player from the queue + pCurr = GetNextPlayer(); + if (!pCurr) + { + // Couldnt get enough players. Reset. + Reset(); + return; + } + } + + // Set her team number + if ( i < m_iPlayersPerTeam ) + pCurr->pev->team = 1; + else + pCurr->pev->team = 2; + pCurr->pev->iuser4 = pCurr->pev->team; + + char sz[128]; + sprintf(sz, "Arena %d", pev->groupinfo ); + MESSAGE_BEGIN( MSG_ALL, gmsgTeamInfo ); + WRITE_BYTE( pCurr->entindex() ); + WRITE_STRING( sz ); + MESSAGE_END(); + + // Add her to the list of combatants + m_hCombatants[ i ] = pCurr; + + // Force her to update her clientinfo, so her colors match her team + ClientUserInfoChanged( pCurr->edict(), g_engfuncs.pfnGetInfoKeyBuffer( pCurr->edict() ) ); + } + + // Start the first round + StartRound(); +} + +//----------------------------------------------------------------------------- +// Purpose: Start the next round in the match +//----------------------------------------------------------------------------- +void CDiscArena::StartRound( void ) +{ + m_iCurrRound++; + m_iSecondsTillStart = ARENA_TIME_PREBATTLE; + m_flTimeLimitOver = gpGlobals->time + ARENA_TIME_ROUNDLIMIT; + + RestoreWorldObjects(); + + // Tell the clients + for ( int iPlayerNum = 1; iPlayerNum <= gpGlobals->maxClients; iPlayerNum++ ) + { + CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( iPlayerNum ); + + if (pPlayer && (pPlayer->pev->groupinfo & pev->groupinfo) && (pPlayer->m_bHasDisconnected != TRUE) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgStartRnd, NULL, pPlayer->edict() ); + WRITE_BYTE( m_iCurrRound ); + WRITE_BYTE( m_iSecondsTillStart ); + WRITE_BYTE( (m_iPlayersPerTeam * 2) ); + + // Send down all the players in the round + for ( int j = 0; j < (m_iPlayersPerTeam * 2); j++ ) + { + if (m_hCombatants[j]) + WRITE_SHORT( ((CBaseEntity*)m_hCombatants[j])->entindex() ); + } + + MESSAGE_END(); + } + } + + // Spawn all the players in the round + for ( int i = 0; i < (m_iPlayersPerTeam * 2); i++ ) + { + CBasePlayer *pPlayer = ((CBasePlayer*)(CBaseEntity*)m_hCombatants[i]); + + if ( pPlayer ) + { + // make sure the player's groupinfo is set the arena's groupinfo + pPlayer->pev->groupinfo = pev->groupinfo; + + // is the player an observer? + if ( pPlayer->IsObserver() ) + { + SpawnCombatant( pPlayer ); + } + + // Remove any powerups + pPlayer->RemoveAllPowerups(); + } + } + + // Start counting down + m_iArenaState = ARENA_COUNTDOWN; + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CDiscArena::CountDownThink ); +} + +//----------------------------------------------------------------------------- +// Purpose: Make sure all the Combatants in the game a valid. Return FALSE if not. +//----------------------------------------------------------------------------- +int CDiscArena::ValidateCombatants( void ) +{ + for ( int i = 0; i < (m_iPlayersPerTeam * 2); i++ ) + { + CBasePlayer *pPlayer = ((CBasePlayer*)(CBaseEntity*)m_hCombatants[i]); + + if ( !pPlayer ) + return FALSE; + if ( pPlayer->m_bHasDisconnected ) + return FALSE; + } + + return TRUE; +} + +//----------------------------------------------------------------------------- +// Purpose: Restore all the world objects +//----------------------------------------------------------------------------- +void CDiscArena::RestoreWorldObjects( void ) +{ + CBaseEntity *pFunc = NULL; + while ((pFunc = UTIL_FindEntityByClassname( pFunc, "func_plat_toggleremove" )) != NULL) + { + ((CPlatToggleRemove*)pFunc)->Reset(); + } + while ((pFunc = UTIL_FindEntityByClassname( pFunc, "func_disctoggle" )) != NULL) + { + ((CDiscTarget*)pFunc)->Reset(); + } + + // Disable powerups + while ((pFunc = UTIL_FindEntityByClassname( pFunc, "item_powerup" )) != NULL) + { + ((CDiscwarPowerup*)pFunc)->Disable(); + } +} + +void CDiscArena::BattleThink( void ) +{ + if ( gpGlobals->time >= m_flTimeLimitOver - 1.0 ) + { + pev->nextthink = gpGlobals->time + 1.0; + SetThink( &CDiscArena::TimeOver ); + return; + } + + if ( !CheckBattleOver() ) + { + pev->nextthink = gpGlobals->time + 1.0; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Countdown to the round start +//----------------------------------------------------------------------------- +void CDiscArena::CountDownThink( void ) +{ + // Freeze everyone until there's 3 seconds to go + if ( m_iSecondsTillStart == 3 ) + { + for ( int i = 0; i < (m_iPlayersPerTeam * 2); i++ ) + { + if (m_hCombatants[i]) + ((CBaseEntity*)m_hCombatants[i])->pev->maxspeed = 320; + } + } + + m_iSecondsTillStart--; + + // Play countdown VOX + if (m_iSecondsTillStart < 5) + { + // Speech + for ( int i = 0; i < (m_iPlayersPerTeam * 2); i++ ) + { + if (m_hCombatants[i]) + ((CBasePlayer*)(CBaseEntity*)m_hCombatants[i])->ClientHearVox( g_szCountDownVox[ m_iSecondsTillStart ] ); + } + } + + // Send the message to the clients in the arena + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if (pPlayer && (pPlayer->pev->groupinfo & pev->groupinfo) && pPlayer->m_bHasDisconnected != TRUE) + { + MESSAGE_BEGIN( MSG_ONE, gmsgStartRnd, NULL, pPlayer->edict() ); + WRITE_BYTE( m_iCurrRound ); + WRITE_BYTE( m_iSecondsTillStart ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + } + } + + if (m_iSecondsTillStart) + { + pev->nextthink = gpGlobals->time + 1.0; + } + else + { + m_iArenaState = ARENA_BATTLE_IN_PROGRESS; + + // Enable powerups + CBaseEntity *pFunc = NULL; + while ((pFunc = UTIL_FindEntityByClassname( pFunc, "item_powerup" )) != NULL) + { + ((CDiscwarPowerup*)pFunc)->Enable(); + } + + pev->nextthink = gpGlobals->time + 1.0; + SetThink( &CDiscArena::BattleThink ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: A player in the battle has died. See if we need to restart the battle. +//----------------------------------------------------------------------------- +void CDiscArena::PlayerKilled( CBasePlayer *pPlayer ) +{ + // Check to see if the battle's over in 5 seconds + if ( m_iArenaState == ARENA_BATTLE_IN_PROGRESS || m_iArenaState == ARENA_COUNTDOWN ) + { + if ( !CheckBattleOver() ) + { + pev->nextthink = gpGlobals->time + 0.5; + SetThink( &CDiscArena::CheckOverThink ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: A player in the battle has respawned. Move them to observer mode. +//----------------------------------------------------------------------------- +void CDiscArena::PlayerRespawned( CBasePlayer *pPlayer ) +{ + if ( m_iArenaState == ARENA_BATTLE_IN_PROGRESS ) + { + // Move the player into Spectator mode + MoveToSpectator( pPlayer ); + } + else if ( m_iArenaState == ARENA_SHOWING_SCORES && ( m_iTeamOneScore >= m_iMaxRounds || m_iTeamTwoScore >= m_iMaxRounds ) ) + { + // Battle is over. If there's only 2 players in the game, just respawn. + // Otherwise, move to spectator. + int iNumPlayers = 0; + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( i ); + if (pPlayer && pPlayer->m_bHasDisconnected != TRUE) + iNumPlayers++; + } + + // Move the player into Spectator mode if there are more players + if ( iNumPlayers > 2 ) + MoveToSpectator( pPlayer ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Move the player to a spectating position +//----------------------------------------------------------------------------- +void CDiscArena::MoveToSpectator( CBasePlayer *pPlayer ) +{ + // Find the spectator spawn position + CBaseEntity *pSpot = UTIL_FindEntityByClassname( NULL, "info_player_spectator"); + if ( pSpot ) + { + pPlayer->StartObserver( pSpot->pev->origin, pSpot->pev->angles); + } + else + { + // Find any spawn point + edict_t *pentSpawnSpot = EntSelectSpawnPoint( pPlayer ); + pPlayer->StartObserver( VARS(pentSpawnSpot)->origin, VARS(pentSpawnSpot)->angles); + } + + pPlayer->pev->team = 0; + pPlayer->Observer_SetMode( OBS_LOCKEDVIEW ); +} + +//----------------------------------------------------------------------------- +// Purpose: Battle is over. +//----------------------------------------------------------------------------- +void CDiscArena::BattleOver( void ) +{ + pev->nextthink = gpGlobals->time + 3; + SetThink( &CDiscArena::FinishedThink ); + + m_iSecondsTillStart = ARENA_TIME_VIEWSCORES; + m_iArenaState = ARENA_SHOWING_SCORES; + + RestoreWorldObjects(); +} + +//----------------------------------------------------------------------------- +// Purpose: Round timelimit has been hit +//----------------------------------------------------------------------------- +void CDiscArena::TimeOver( void ) +{ + // Display the 10 second warning first + if ( !m_bShownTimeWarning ) + { + m_bShownTimeWarning = TRUE; + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if (pPlayer && (pPlayer->pev->groupinfo & pev->groupinfo) && pPlayer->m_bHasDisconnected != TRUE) + ClientPrint( pPlayer->pev, HUD_PRINTCENTER, "#Time_Warning" ); + } + + pev->nextthink = gpGlobals->time + 10; + } + else + { + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if (pPlayer && (pPlayer->pev->groupinfo & pev->groupinfo) && (pPlayer->m_bHasDisconnected != TRUE) ) + ClientPrint( pPlayer->pev, HUD_PRINTCENTER, "#Time_Over" ); + } + + // Increment both scores to force the game to end + m_iTeamOneScore++; + m_iTeamTwoScore++; + + BattleOver(); + } +} + +bool CDiscArena::CheckBattleOver( void ) +{ + bool bTeamOneAlive = false; + bool bTeamTwoAlive = false; + + // See if the battle is finished + for ( int i = 0; i < (m_iPlayersPerTeam * 2); i++ ) + { + if ( m_hCombatants[i] != 0 && ((CBasePlayer*)(CBaseEntity*)m_hCombatants[i])->IsAlive() ) + { + if ( ((CBaseEntity*)m_hCombatants[i])->pev->team == 1 ) + bTeamOneAlive = true; + else if ( ((CBaseEntity*)m_hCombatants[i])->pev->team == 2 ) + bTeamTwoAlive = true; + } + } + + if ( !bTeamOneAlive || !bTeamTwoAlive ) + { + // Battle is finished. + if (bTeamOneAlive) + { + m_iWinningTeam = 1; + m_iTeamOneScore++; + } + else + { + m_iWinningTeam = 2; + m_iTeamTwoScore++; + } + + int iTeamInTheLead = 0; + if ( m_iTeamOneScore > m_iTeamTwoScore ) + iTeamInTheLead = 1; + else if ( m_iTeamOneScore < m_iTeamTwoScore ) + iTeamInTheLead = 2; + + // Send the message to the clients in the arena + for ( int iPlayerNum = 1; iPlayerNum <= gpGlobals->maxClients; iPlayerNum++ ) + { + CBasePlayer *pPlayer = (CBasePlayer*)UTIL_PlayerByIndex( iPlayerNum ); + + if (pPlayer && (pPlayer->pev->groupinfo & pev->groupinfo) && (pPlayer->m_bHasDisconnected != TRUE) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgEndRnd, NULL, pPlayer->edict() ); + WRITE_BYTE( m_iCurrRound ); + WRITE_BYTE( 1 ); + WRITE_BYTE( m_iPlayersPerTeam ); + + // Send down the winners of this round + for (int i = 0; i < (m_iPlayersPerTeam * 2); i++) + { + CBasePlayer *pPlayer = (CBasePlayer*)(CBaseEntity*)m_hCombatants[i]; + if ( !pPlayer || pPlayer->pev->team != m_iWinningTeam ) + continue; + + WRITE_SHORT( pPlayer->entindex() ); + } + + // Send down the team who's winning the battle now + if ( iTeamInTheLead == 0 ) + { + // It's a draw at the moment. + // No need to send down player data. + WRITE_BYTE( 0 ); + } + else + { + WRITE_BYTE( m_iPlayersPerTeam ); + // Send down the winners of this round + for (int i = 0; i < (m_iPlayersPerTeam * 2); i++) + { + CBasePlayer *pPlayer = (CBasePlayer*)(CBaseEntity*)m_hCombatants[i]; + if ( !pPlayer || pPlayer->pev->team != iTeamInTheLead ) + continue; + + WRITE_SHORT( ((CBaseEntity*)m_hCombatants[i])->entindex() ); + } + } + + // Send down the scores + if ( iTeamInTheLead == 1 ) + { + WRITE_BYTE( m_iTeamOneScore ); + WRITE_BYTE( m_iTeamTwoScore ); + } + else + { + WRITE_BYTE( m_iTeamTwoScore ); + WRITE_BYTE( m_iTeamOneScore ); + } + + // Send down over or not + if ( m_iTeamOneScore == m_iMaxRounds || m_iTeamTwoScore == m_iMaxRounds ) + WRITE_BYTE( 1 ); + else + WRITE_BYTE( 0 ); + + MESSAGE_END(); + } + } + + BattleOver(); + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Check to see if the Battle is over +//----------------------------------------------------------------------------- +void CDiscArena::CheckOverThink( void ) +{ + if ( !CheckBattleOver() ) + { + if ( m_iArenaState == ARENA_COUNTDOWN ) + { + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CDiscArena::CountDownThink ); + } + else + { + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CDiscArena::BattleThink ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Show who won, and then restart the round +//----------------------------------------------------------------------------- +void CDiscArena::FinishedThink( void ) +{ + m_iSecondsTillStart--; + + if (m_iSecondsTillStart) + { + pev->nextthink = gpGlobals->time + 1.0; + } + else + { + SetThink( NULL ); + + // Tell the clients to remove the "Won" window + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if (pPlayer && (pPlayer->pev->groupinfo & pev->groupinfo) && pPlayer->m_bHasDisconnected != TRUE) + { + MESSAGE_BEGIN( MSG_ONE, gmsgEndRnd, NULL, pPlayer->edict() ); + WRITE_BYTE( m_iCurrRound ); + WRITE_BYTE( 0 ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + } + } + + // Round is over. See if the match is over too. + if ( m_iTeamOneScore >= m_iMaxRounds || m_iTeamTwoScore >= m_iMaxRounds ) + { + // Remove the losers from the combatants list + for (int i = 0; i < (m_iPlayersPerTeam * 2); i++) + { + CBasePlayer *pPlayer = (CBasePlayer*)(CBaseEntity*)m_hCombatants[i]; + if (!pPlayer) + continue; + if ( pPlayer->pev->team == m_iWinningTeam ) + { + pPlayer->m_iDeaths += 1; + pPlayer->m_iLastGameResult = GAME_WON; + continue; + } + + pPlayer->m_iLastGameResult = GAME_LOST; + m_hCombatants[i] = NULL; + } + + // Then start the next Battle + PostBattle(); + } + else + { + // Start the next round + StartRound(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Spawn a player in this battle +//----------------------------------------------------------------------------- +void CDiscArena::SpawnCombatant( CBasePlayer *pPlayer ) +{ + // Make sure she's out of spectator mode + pPlayer->StopObserver(); + + // Spawn + g_pGameRules->GetPlayerSpawnSpot( pPlayer ); + + // Prevent movement for a couple of seconds + pPlayer->pev->maxspeed = 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Start a Battle +//----------------------------------------------------------------------------- +void CDiscArena::StartBattleThink( void ) +{ + StartBattle(); +} + +//----------------------------------------------------------------------------- +// Purpose: Prevent firing during prematch +//----------------------------------------------------------------------------- +bool CDiscArena::AllowedToFire( void ) +{ + return ( m_iArenaState == ARENA_BATTLE_IN_PROGRESS ); +} + +//----------------------------------------------------------------------------- +// Purpose: New client was added to the arena +//----------------------------------------------------------------------------- +void CDiscArena::AddClient( CBasePlayer *pPlayer, BOOL bCheckStart ) +{ + // Remove them from any arena they're currently in + if ( pPlayer->m_pCurrentArena != NULL ) + pPlayer->m_pCurrentArena->RemoveClient( pPlayer ); + + m_iPlayers++; + + pPlayer->pev->groupinfo = pev->groupinfo; + + // Add her to the queue + AddPlayerToQueue( pPlayer ); + pPlayer->m_pCurrentArena = this; + + // Only check a restart if the flag is set + if ( bCheckStart ) + { + // Start a game if there's none going, and we now have enough players + // Allow starting of games if there aren't enough players, but there's more than 1 on the svr. + //if ( (m_iPlayersPerTeam > 1) && (m_iPlayers > 1) && (m_iPlayers < (m_iPlayersPerTeam * 2)) ) + //m_iPlayersPerTeam = 1; + // If we're in a battle, and the players-per-team isn't the map's setting, restart the battle + if ( (m_iArenaState == ARENA_WAITING_FOR_PLAYERS) && ( m_iPlayers >= (m_iPlayersPerTeam * 2) ) ) + { + // Start a battle in a second to let the clients learn about this new player + SetThink( &CDiscArena::StartBattleThink ); + pev->nextthink = gpGlobals->time + 1.0; + } + else + { + // Move her into spectator mode + MoveToSpectator( pPlayer ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Client was removed from the arena +//----------------------------------------------------------------------------- +void CDiscArena::RemoveClient( CBasePlayer *pPlayer ) +{ + m_iPlayers--; + + pPlayer->pev->groupinfo = 0; + pPlayer->m_pCurrentArena = NULL; + + // Is she in the current battle? + if ( pPlayer->pev->playerclass != 0 ) + { + // No, she's in the queue, so remove her. + RemovePlayerFromQueue( pPlayer ); + } + else if ( m_iArenaState != ARENA_WAITING_FOR_PLAYERS ) + { + // This team loses + m_iWinningTeam = (pPlayer->pev->team == 1) ? 2 : 1; + if ( m_iWinningTeam == 1 ) + m_iTeamOneScore = m_iMaxRounds - 1; // -1 because we'll get 1 point for winning this round in CheckOverThink + else + m_iTeamTwoScore = m_iMaxRounds - 1; // -1 because we'll get 1 point for winning this round in CheckOverThink + + // Find the player in the combatant list + for ( int i = 0; i < (m_iPlayersPerTeam * 2); i++ ) + { + // Check to see if this slot's already full + if ( m_hCombatants[ i ] == pPlayer ) + { + m_hCombatants[i] = NULL; + break; + } + } + + CheckOverThink(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Add a player to the end of the queue +//----------------------------------------------------------------------------- +void CDiscArena::AddPlayerToQueue( CBasePlayer *pPlayer ) +{ + if ( !pPlayer ) + return; + + if ( m_pPlayerQueue ) + { + CBasePlayer *pCurr = (CBasePlayer*)(CBaseEntity*)m_pPlayerQueue; + while ( pCurr->m_pNextPlayer ) + { + pCurr = (CBasePlayer*)(CBaseEntity*)pCurr->m_pNextPlayer; + } + + pCurr->m_pNextPlayer = pPlayer; + pPlayer->pev->playerclass = pCurr->pev->playerclass + 1; + } + else + { + m_pPlayerQueue = pPlayer; + pPlayer->pev->playerclass = 1; + } + + pPlayer->m_pNextPlayer = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Remove a player from the queue +//----------------------------------------------------------------------------- +void CDiscArena::RemovePlayerFromQueue( CBasePlayer *pPlayer ) +{ + bool bFoundHer = false; + + if ( !pPlayer ) + return; + + CBasePlayer *pCurr = (CBasePlayer*)(CBaseEntity*)m_pPlayerQueue; + CBasePlayer *pPrev = NULL; + + while ( pCurr ) + { + if (pCurr == pPlayer ) + { + bFoundHer = true; + if (pPrev) + pPrev->m_pNextPlayer = pCurr->m_pNextPlayer; + else + m_pPlayerQueue = pCurr->m_pNextPlayer; + } + else + { + pPrev = pCurr; + + // Adjust all the following player's queue positions + if (bFoundHer) + pCurr->pev->playerclass--; + } + + pCurr = (CBasePlayer*)(CBaseEntity*)pCurr->m_pNextPlayer; + } + + pPlayer->m_pNextPlayer = NULL; + pPlayer->pev->playerclass = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the next player from the queue, and shuffle the rest up +//----------------------------------------------------------------------------- +CBasePlayer *CDiscArena::GetNextPlayer( void ) +{ + if ( m_pPlayerQueue == 0 ) + return NULL; + + CBasePlayer *pCurr = (CBasePlayer*)(CBaseEntity*)m_pPlayerQueue; + RemovePlayerFromQueue( (CBasePlayer*)(CBaseEntity*)m_pPlayerQueue ); + + return pCurr; +} + +//----------------------------------------------------------------------------- +// Returns TRUE if the Arena is full +int CDiscArena::IsFull( void ) +{ + if ( m_iPlayers < (m_iPlayersPerTeam * 2) ) + return FALSE; + + return TRUE; +} + +//----------------------------------------------------------------------------- +// Returns the first player in the Arena's queue, if any +CBasePlayer *CDiscArena::GetFirstSparePlayer( void ) +{ + if ( m_pPlayerQueue == 0 ) + return NULL; + + return (CBasePlayer*)(CBaseEntity*)m_pPlayerQueue; +} + +//----------------------------------------------------------------------------- +// Add a client to an Arena. Find the first arena that doesn't have two +// players in it, and add this player to that. If we find a third player +// Spectating another arena, grab them and add them to this player's arena. +void AddClientToArena( CBasePlayer *pPlayer ) +{ + // First, find an arena for this player to be put into + for (int i = 0; i < MAX_ARENAS; i++) + { + if ( g_pArenaList[i]->IsFull() == FALSE ) + { + int iArenaNumber = i; + + g_pArenaList[iArenaNumber]->AddClient( pPlayer, TRUE ); + + bool bFoundOne = TRUE; + // Now, if this arena's not full, try to find more player to join her + while ( (g_pArenaList[iArenaNumber]->IsFull() == FALSE) && bFoundOne ) + { + bFoundOne = FALSE; + + // Cycle through all the arenas and find a spare player + for (int j = 0; j < MAX_ARENAS; j++) + { + CBasePlayer *pSparePlayer = g_pArenaList[j]->GetFirstSparePlayer(); + if (pSparePlayer && pSparePlayer != pPlayer) + { + g_pArenaList[j]->RemoveClient( pSparePlayer ); + g_pArenaList[iArenaNumber]->AddClient( pSparePlayer, TRUE ); + bFoundOne = TRUE; + break; + } + } + } + + // If we couldn't find another player for this arena, just add them to an existing arena + if ( g_pArenaList[iArenaNumber]->IsFull() == FALSE ) + { + // Add to the first full arena + for (int j = 0; j < MAX_ARENAS; j++) + { + if ( g_pArenaList[j]->IsFull() ) + { + // Remove from current + g_pArenaList[iArenaNumber]->RemoveClient( pPlayer ); + + // Add to full one + iArenaNumber = j; + g_pArenaList[iArenaNumber]->AddClient( pPlayer, TRUE ); + break; + } + } + } + + //ALERT( at_console, "ADDED %s to Arena %d\n", STRING(pPlayer->pev->netname), iArenaNumber ); + return; + } + } +} + +//----------------------------------------------------------------------------- +// Put the specified group of players into the current arena +int AddPlayers( int iPlayers, int iArenaNum ) +{ + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if (pPlayer && (pPlayer->m_pCurrentArena == NULL) && (pPlayer->m_bHasDisconnected != TRUE) ) + { + if ( pPlayer->m_iLastGameResult != iPlayers ) + continue; + + g_pArenaList[iArenaNum]->AddClient( pPlayer, FALSE ); + if ( g_pArenaList[iArenaNum]->IsFull() ) + iArenaNum++; + } + } + + return iArenaNum; +} + +//----------------------------------------------------------------------------- +// Take all the players not in battles and shuffle them into new groups +void ShufflePlayers( void ) +{ + int iArenaNum = 0; + + // Reset all Arenas + for ( int i = 0; i < MAX_ARENAS; i++) + { + g_pArenaList[i]->Reset(); + } + + // Play the winners off against the other winners first + iArenaNum = AddPlayers( GAME_WON, iArenaNum ); + // First, add the players who didn't play at all + iArenaNum = AddPlayers( GAME_DIDNTPLAY, iArenaNum ); + // Then add the losers + iArenaNum = AddPlayers( GAME_LOST, iArenaNum ); + + // Then tell all full arenas to start + for (int i = 0; i < MAX_ARENAS; i++) + { + if ( g_pArenaList[i]->IsFull() ) + { + g_pArenaList[i]->StartBattle(); + } + else + { + // If the arena has players in it, move them to the first full arena + if ( g_pArenaList[i]->m_iPlayers != 0 ) + { + for ( int j = 1; j <= gpGlobals->maxClients; j++ ) + { + CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( j ); + + if (pPlayer && (pPlayer->m_pCurrentArena == g_pArenaList[i]) && (pPlayer->m_bHasDisconnected != TRUE) ) + { + // Add to the first arena + g_pArenaList[0]->AddClient( pPlayer, TRUE ); + + pPlayer->m_iLastGameResult = GAME_DIDNTPLAY; + } + } + } + } + } + +} + +//----------------------------------------------------------------------------- +// The Battle is over. First, check to see if there are games going on in +// other arenas. If there are, these players go watch one of them for a bit. +// If all games finish in that time, shuffle all the players and start new +// games. Otherwise, shuffle all the players who aren't still playing, and +// start new games with them. +void CDiscArena::PostBattle( void ) +{ + int iOtherGame = -1; + // First, see if there are any other games going on in other arenas + for (int i = 0; i < MAX_ARENAS; i++) + { + if ( g_pArenaList[i]->m_iArenaState != ARENA_WAITING_FOR_PLAYERS && g_pArenaList[i] != this ) + { + iOtherGame = i; + break; + } + } + + // If there are no other games, just shuffle and start again + if ( iOtherGame == -1 ) + { + ShufflePlayers(); + return; + } + + // There's another game going on. Move all the players from this arena to it to spectate. + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if (pPlayer && (pPlayer->pev->groupinfo & pev->groupinfo) && (pPlayer->m_bHasDisconnected != TRUE) ) + { + g_pArenaList[ iOtherGame ]->AddClient( (CBasePlayer*)pPlayer, TRUE ); + } + } + + m_iArenaState = ARENA_WAITING_FOR_PLAYERS; +} diff --git a/hlsdk/ricochet/dlls/disc_arena.h b/hlsdk/ricochet/dlls/disc_arena.h new file mode 100644 index 00000000..9670ef75 --- /dev/null +++ b/hlsdk/ricochet/dlls/disc_arena.h @@ -0,0 +1,111 @@ +//=========== (C) Copyright 1999 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: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#ifndef DISC_ARENA_H +#define DISC_ARENA_H +#pragma once + +#define MAX_ARENAS 16 + +// Arena States +#define ARENA_WAITING_FOR_PLAYERS 0 +#define ARENA_COUNTDOWN 1 +#define ARENA_BATTLE_IN_PROGRESS 2 +#define ARENA_SHOWING_SCORES 3 + +// Arena Times +#define ARENA_TIME_PREBATTLE 5 +#define ARENA_TIME_VIEWSCORES 3 +#define ARENA_TIME_ROUNDLIMIT 120 // Timelimit on rounds + +enum +{ + GAME_LOST = 0, + GAME_WON, + GAME_DIDNTPLAY, +}; + +//----------------------------------------------------------------------------- +// Arena object +class CDiscArena : public CBaseEntity +{ +public: + void Spawn( void ); + void Reset( void ); + + // Battle initialisation + void StartBattle( void ); + void StartRound( void ); + void SpawnCombatant( CBasePlayer *pPlayer ); + void MoveToSpectator( CBasePlayer *pPlayer ); + void EXPORT StartBattleThink( void ); + + // Battle running + void EXPORT CountDownThink( void ); + void PlayerKilled( CBasePlayer *pPlayer ); + void PlayerRespawned( CBasePlayer *pPlayer ); + void BattleOver( void ); + void EXPORT CheckOverThink( void ); + void EXPORT FinishedThink( void ); + void RestoreWorldObjects( void ); + int ValidateCombatants( void ); + void EXPORT TimeOver( void ); + void EXPORT BattleThink( void ); + bool CheckBattleOver( void ); + + // Client handling + void AddClient( CBasePlayer *pPlayer, BOOL bCheckStart ); + void RemoveClient( CBasePlayer *pPlayer ); + void AddPlayerToQueue( CBasePlayer *pPlayer ); + void RemovePlayerFromQueue( CBasePlayer *pPlayer ); + CBasePlayer * GetNextPlayer( void ); + + // Multiple Arena handling + int IsFull( void ); + CBasePlayer *GetFirstSparePlayer( void ); + void PostBattle( void ); + + // Game handling + bool AllowedToFire( void ); + + // Variables + int m_iArenaState; + int m_iPlayers; + int m_iMaxRounds; + int m_iCurrRound; + int m_iPlayersPerTeam; // Current players per team + int m_iSecondsTillStart; + int m_iWinningTeam; + int m_iTeamOneScore; + int m_iTeamTwoScore; + float m_flTimeLimitOver; + BOOL m_bShownTimeWarning; + + // Queue + EHANDLE m_pPlayerQueue; + + // Players in the current battle + EHANDLE m_hCombatants[ 32 ]; +}; + +extern CDiscArena *g_pArenaList[ MAX_ARENAS ]; +extern float g_iaDiscColors[33][3]; + +int InArenaMode(); + +#endif // DISC_ARENA_H diff --git a/hlsdk/ricochet/dlls/disc_objects.h b/hlsdk/ricochet/dlls/disc_objects.h new file mode 100644 index 00000000..067c56ef --- /dev/null +++ b/hlsdk/ricochet/dlls/disc_objects.h @@ -0,0 +1,173 @@ +//=========== (C) Copyright 1999 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: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#ifndef DISK_OBJECTS_H +#define DISK_OBJECTS_H +#pragma once + +// Disc objects +class CDiscWeapon; + +class CDisc : public CGrenade +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT DiscTouch( CBaseEntity *pOther ); + void EXPORT DiscThink( void ); + static CDisc *CreateDisc( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner, CDiscWeapon *pLauncher, bool bDecapitator, int iPowerupFlags ); + + //void SetObjectCollisionBox( void ); + void ReturnToThrower( void ); + + virtual BOOL IsDisc( void ) { return TRUE; }; + + float m_fDontTouchEnemies; // Prevent enemy touches for a bit + float m_fDontTouchOwner; // Prevent friendly touches for a bit + int m_iBounces; // Number of bounces + EHANDLE m_hOwner; // Don't store in pev->owner, because it needs to hit its owner + CDiscWeapon *m_pLauncher; // pointer back to the launcher that fired me. + int m_iTrail; + int m_iSpriteTexture; + bool m_bDecapitate; // True if this is a decapitating shot + bool m_bRemoveSelf; // True if the owner of this disc has died + int m_iPowerupFlags;// Flags for any powerups active on this disc + bool m_bTeleported; // Disc has gone through a teleport + + EHANDLE m_pLockTarget; + + Vector m_vecActualVelocity; + Vector m_vecSideVelocity; + Vector m_vecOrg; +}; + +//=============================================================================== +// DISCWAR OBJECTS +//=============================================================================== +class CBaseTrigger : public CBaseToggle +{ +public: + void EXPORT TeleportTouch ( CBaseEntity *pOther ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT MultiTouch( CBaseEntity *pOther ); + void EXPORT HurtTouch ( CBaseEntity *pOther ); + void EXPORT CDAudioTouch ( CBaseEntity *pOther ); + void ActivateMultiTrigger( CBaseEntity *pActivator ); + void EXPORT MultiWaitOver( void ); + void EXPORT CounterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void InitTrigger( void ); + + virtual int ObjectCaps( void ) { return CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +// Brush that's status gets toggled by a disc hit +#define LAST_HITBY_FRIENDLY 1 +#define LAST_HITBY_ENEMY 2 + +class CDiscTarget : public CBaseTrigger +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Reset( void ); + + void EXPORT DiscToggleTouch( CBaseEntity *pOther ); + + int m_iszFriendlyHit; + int m_iszEnemyHit; + int m_iState; +}; + +//========================================================= +// Powerup object +class CDiscwarPowerup : public CBaseAnimating +{ +public: + void Spawn( void ); + void Activate( void ); + void Precache( void ); + void EXPORT PowerupTouch( CBaseEntity *pOther ); + void EXPORT ChoosePowerupThink( void ); + void EXPORT RemovePowerupThink( void ); + void EXPORT AnimateThink( void ); + void SetObjectCollisionBox( void ); + + void Disable(); + void Enable(); + + EHANDLE m_hPlayerIGaveTo; + int m_iPowerupType; +}; + +//=============================================================================== +// Brush that toggles between gone/there +#define PLAT_FADE_TIME 2.0 +class CPlatToggleRemove : public CBaseEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Reset( void ); + + void EXPORT PlatToggleRemoveUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT PlatRemoveThink( void ); + + float m_flRemoveAt; +}; + +//=============================================================================== +// Trigger that jumps a player to a target point +class CTriggerJump : public CBaseTrigger +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Activate( void ); + void Precache( void ); + void EXPORT JumpTouch( CBaseEntity *pOther ); + void EXPORT JumpUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + Vector m_vecTargetOrg; + float m_flHeight; + int m_iState; + +private: + unsigned short m_usJump; +}; + +//=============================================================================== +// Trigger that returns discs to their thrower immediately +class CTriggerDiscReturn : public CBaseTrigger +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT DiscReturnTouch( CBaseEntity *pOther ); +}; + +//=============================================================================== +// Trigger that starts the fall animation for players +class CTriggerFall : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT FallTouch( CBaseEntity *pOther ); +}; + +#endif // DISK_OBJECTS_H diff --git a/hlsdk/ricochet/dlls/disc_powerups.cpp b/hlsdk/ricochet/dlls/disc_powerups.cpp new file mode 100644 index 00000000..53b9063a --- /dev/null +++ b/hlsdk/ricochet/dlls/disc_powerups.cpp @@ -0,0 +1,233 @@ +//=========== (C) Copyright 1999 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: Code for the various Discwar powerups +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "items.h" +#include "gamerules.h" +#include "discwar.h" +#include "disc_objects.h" +#include "disc_arena.h" + +extern int gmsgPowerup; + +//========================================================= +// POWERUPS +char *szPowerupModels[NUM_POWERUPS] = +{ + "models/pow_triple.mdl", + "models/pow_fast.mdl", + "models/pow_hard.mdl", + "models/pow_freeze.mdl", + //"models/pow_visual.mdl", +}; + +LINK_ENTITY_TO_CLASS( item_powerup, CDiscwarPowerup ); + +//========================================================= +void CDiscwarPowerup::Spawn( void ) +{ + Precache( ); + + // Don't fall down + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_TRIGGER; + UTIL_SetOrigin( pev, pev->origin ); + UTIL_SetSize(pev, Vector(-32, -32, -32), Vector(32, 32, 32)); + + // Use first model for now + SET_MODEL(ENT(pev), szPowerupModels[0]); + pev->effects |= EF_NODRAW; +} + +void CDiscwarPowerup::Activate( void ) +{ + // If Arena mode is on, spawn another powerup for every arena + if ( InArenaMode() ) + { + // If our groupinfo is set, we're not the first powerup + if ( pev->groupinfo == 0 ) + { + // Put this powerup in the first arena + pev->groupinfo = g_pArenaList[0]->pev->groupinfo; + + // Create a powerup for each of the other arenas + for (int i = 1; i < MAX_ARENAS; i++) + { + CBaseEntity * pPowerup; + + pPowerup = CBaseEntity::Create( "item_powerup", pev->origin, pev->angles ); + pPowerup->pev->groupinfo = g_pArenaList[i]->pev->groupinfo; + } + } + } + else + { + // Make the powerup start thinking + Enable(); + } +} + +void CDiscwarPowerup::Precache( void ) +{ + for (int i = 0; i < NUM_POWERUPS; i++) + PRECACHE_MODEL( szPowerupModels[i] ); + PRECACHE_SOUND( "powerup.wav" ); + PRECACHE_SOUND( "pspawn.wav" ); +} + +void CDiscwarPowerup::SetObjectCollisionBox( void ) +{ + pev->absmin = pev->origin + Vector( -64, -64, 0 ); + pev->absmax = pev->origin + Vector( 64, 64, 128 ); +} + +void CDiscwarPowerup::PowerupTouch( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + return; + + CBasePlayer *pPlayer = (CBasePlayer *)pOther; + + // Give the powerup to the player + pPlayer->GivePowerup( m_iPowerupType ); + m_hPlayerIGaveTo = pPlayer; + SetTouch( NULL ); + pev->effects |= EF_NODRAW; + + // Choose another powerup soon + SetThink( &CDiscwarPowerup::ChoosePowerupThink ); + pev->nextthink = gpGlobals->time + DISC_POWERUP_RESPAWN_TIME; + + // Play the powerup sound + EMIT_SOUND_DYN( pOther->edict(), CHAN_STATIC, "powerup.wav", 1.0, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); +} + +// Disappear and don't appear again until enabled +void CDiscwarPowerup::Disable() +{ + pev->effects |= EF_NODRAW; + pev->nextthink = 0; + SetThink( NULL ); + SetTouch( NULL ); +} + +// Come back and pick a new powerup +void CDiscwarPowerup::Enable() +{ + // Pick a powerup + SetThink( &CDiscwarPowerup::ChoosePowerupThink ); + pev->nextthink = gpGlobals->time + (DISC_POWERUP_RESPAWN_TIME / 2); +} + +//========================================================= +// Randomly decide what powerup to be +void CDiscwarPowerup::ChoosePowerupThink( void ) +{ + int iPowerup = RANDOM_LONG(0, NUM_POWERUPS-1); + m_iPowerupType = (1 << iPowerup); + + SET_MODEL( ENT(pev), szPowerupModels[iPowerup] ); + pev->effects &= ~EF_NODRAW; + + SetTouch(&CDiscwarPowerup::PowerupTouch); + + // Start Animating + pev->sequence = 0; + pev->frame = 0; + ResetSequenceInfo(); + + SetThink(&CDiscwarPowerup::AnimateThink); + pev->nextthink = gpGlobals->time + 0.1; + + pev->rendermode = kRenderTransAdd; + pev->renderamt = 150; + + // Play the powerup appear sound + EMIT_SOUND_DYN( edict(), CHAN_STATIC, "pspawn.wav", 1.0, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); +} + +void CDiscwarPowerup::AnimateThink( void ) +{ + StudioFrameAdvance(); + pev->nextthink = gpGlobals->time + 0.1; +} + +// Remove the powerup from the person we gave it to +void CDiscwarPowerup::RemovePowerupThink( void ) +{ + if (m_hPlayerIGaveTo == 0) + return; + + ((CBasePlayer*)(CBaseEntity*)m_hPlayerIGaveTo)->RemovePowerup( m_iPowerupType ); + + // Pick a powerup later + SetThink( &CDiscwarPowerup::ChoosePowerupThink ); + pev->nextthink = gpGlobals->time + DISC_POWERUP_RESPAWN_TIME; +} + +//================================================================================= +// PLAYER HANDLING FOR POWERUPS +//========================================================= +void CBasePlayer::GivePowerup( int iPowerupType ) +{ + m_iPowerups |= iPowerupType; + + if ( m_iPowerups & POW_HARD ) + strcpy( m_szAnimExtention, "models/p_disc_hard.mdl" ); + + MESSAGE_BEGIN( MSG_ONE, gmsgPowerup, NULL, pev ); + WRITE_BYTE( m_iPowerups ); + MESSAGE_END(); + + m_iPowerupDiscs = MAX_DISCS; +} + +void CBasePlayer::RemovePowerup( int iPowerupType ) +{ + if ( iPowerupType & POW_HARD ) + strcpy( m_szAnimExtention, "models/p_disc.mdl" ); + + m_iPowerups &= ~iPowerupType; + + MESSAGE_BEGIN( MSG_ONE, gmsgPowerup, NULL, pev ); + WRITE_BYTE( m_iPowerups ); + MESSAGE_END(); + + m_iPowerupDiscs = 0; +} + +void CBasePlayer::RemoveAllPowerups( void ) +{ + m_iPowerups = 0; + m_iPowerupDiscs = 0; + + MESSAGE_BEGIN( MSG_ONE, gmsgPowerup, NULL, pev ); + WRITE_BYTE( m_iPowerups ); + MESSAGE_END(); +} + +bool CBasePlayer::HasPowerup( int iPowerupType ) +{ + return (m_iPowerups & iPowerupType) != 0; +} diff --git a/hlsdk/ricochet/dlls/disc_weapon.h b/hlsdk/ricochet/dlls/disc_weapon.h new file mode 100644 index 00000000..c9c3f3bb --- /dev/null +++ b/hlsdk/ricochet/dlls/disc_weapon.h @@ -0,0 +1,33 @@ +#if !defined( DISC_WEAPON_H ) +#define DISC_WEAPON_H +#ifdef _WIN32 +#pragma once +#endif + +class CDisc; +class CDiscWeapon : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 5; } + int GetItemInfo(ItemInfo *p); + + int AddDuplicate( CBasePlayerItem *pOriginal ); + CDisc *FireDisc( bool bDecapitator ); + void PrimaryAttack( void ); + void SecondaryAttack( void ); + BOOL Deploy( void ); + BOOL CanHolster( void ); + void Holster( int skiplocal = 0 ); + void WeaponIdle( void ); + + BOOL UseDecrement( void ) { return TRUE; }; + + int m_iSpriteTexture; + int m_iFastShotDiscs; +private: + unsigned short m_usFireDisc; +}; + +#endif // DISC_WEAPON_H diff --git a/hlsdk/ricochet/dlls/discwar.h b/hlsdk/ricochet/dlls/discwar.h new file mode 100644 index 00000000..410038df --- /dev/null +++ b/hlsdk/ricochet/dlls/discwar.h @@ -0,0 +1,60 @@ +//=========== (C) Copyright 1999 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: Header for Discwar +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#ifndef DISCWAR_H +#define DISCWAR_H +#pragma once + +#define WEAPON_DISC 1 +#define MAX_DISCS 3 // Max number of discs a player can carry +#define STARTING_DISCS MAX_DISCS // Number of discs a player starts with +#define NUM_FASTSHOT_DISCS 3 // Number of discs a player gets with the fastshot powerup per normal disc + +#define DISC_VELOCITY 1000 // Velocity multiplier for discs when thrown +#define DISC_PUSH_MULTIPLIER 1200 // Velocity multiplier used to push a player when hit by a disc + +//#define DISC_POWERUP_TIME 5 // Time (in seconds) a powerup lasts for +#define DISC_POWERUP_RESPAWN_TIME 10 // Time (in seconds) it takes after a powerup is picked up before the next one appears + +#define MAX_SCORE_TIME_AFTER_HIT 4.0 // Time (in seconds) in which a player gets a point if the enemy dies within this time + // after being hit by a disc. + +// Powerups +#define POW_TRIPLE (1<<0) +#define POW_FAST (1<<1) +#define POW_HARD (1<<2) +#define POW_FREEZE (1<<3) + +#define POW_VISUALIZE_REBOUNDS (1<<4) // Removing this one for now + +#define NUM_POWERUPS 4 // 4, not 5, because VISUALIZE_REBOUNDS is removed. + +#define FREEZE_TIME 7 +#define FREEZE_SPEED 50 + +// Rewards +#define REWARD_BOUNCE_NONE (1<<1) +#define REWARD_BOUNCE_ONE (1<<2) +#define REWARD_BOUNCE_TWO (1<<3) +#define REWARD_BOUNCE_THREE (1<<4) +#define REWARD_DECAPITATE (1<<5) +#define REWARD_TELEPORT (1<<6) +#define REWARD_DOUBLEKILL (1<<7) + +#endif // DISCWAR_H + diff --git a/hlsdk/ricochet/dlls/doors.cpp b/hlsdk/ricochet/dlls/doors.cpp new file mode 100644 index 00000000..91ba6b5f --- /dev/null +++ b/hlsdk/ricochet/dlls/doors.cpp @@ -0,0 +1,1025 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== doors.cpp ======================================================== + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "doors.h" + + +extern void SetMovedir(entvars_t* ev); + +#define noiseMoving noise1 +#define noiseArrived noise2 + +class CBaseDoor : public CBaseToggle +{ +public: + void Spawn( void ); + void Precache( void ); + virtual void KeyValue( KeyValueData *pkvd ); + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual void Blocked( CBaseEntity *pOther ); + + + virtual int ObjectCaps( void ) + { + if (pev->spawnflags & SF_ITEM_USE_ONLY) + return (CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_IMPULSE_USE; + else + return (CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION); + }; + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + virtual void SetToggleState( int state ); + + // used to selectivly override defaults + void EXPORT DoorTouch( CBaseEntity *pOther ); + + // local functions + int DoorActivate( ); + void EXPORT DoorGoUp( void ); + void EXPORT DoorGoDown( void ); + void EXPORT DoorHitTop( void ); + void EXPORT DoorHitBottom( void ); + + BYTE m_bHealthValue;// some doors are medi-kit doors, they give players health + + BYTE m_bMoveSnd; // sound a door makes while moving + BYTE m_bStopSnd; // sound a door makes when it stops + + locksound_t m_ls; // door lock sounds + + BYTE m_bLockedSound; // ordinals from entity selection + BYTE m_bLockedSentence; + BYTE m_bUnlockedSound; + BYTE m_bUnlockedSentence; +}; + + +TYPEDESCRIPTION CBaseDoor::m_SaveData[] = +{ + DEFINE_FIELD( CBaseDoor, m_bHealthValue, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bMoveSnd, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bStopSnd, FIELD_CHARACTER ), + + DEFINE_FIELD( CBaseDoor, m_bLockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bLockedSentence, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bUnlockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bUnlockedSentence, FIELD_CHARACTER ), + +}; + +IMPLEMENT_SAVERESTORE( CBaseDoor, CBaseToggle ); + + +#define DOOR_SENTENCEWAIT 6 +#define DOOR_SOUNDWAIT 3 +#define BUTTON_SOUNDWAIT 0.5 + +// play door or button locked or unlocked sounds. +// pass in pointer to valid locksound struct. +// if flocked is true, play 'door is locked' sound, +// otherwise play 'door is unlocked' sound +// NOTE: this routine is shared by doors and buttons + +void PlayLockSounds(entvars_t *pev, locksound_t *pls, int flocked, int fbutton) +{ + // LOCKED SOUND + + // CONSIDER: consolidate the locksound_t struct (all entries are duplicates for lock/unlock) + // CONSIDER: and condense this code. + float flsoundwait; + + if (fbutton) + flsoundwait = BUTTON_SOUNDWAIT; + else + flsoundwait = DOOR_SOUNDWAIT; + + if (flocked) + { + int fplaysound = (pls->sLockedSound && gpGlobals->time > pls->flwaitSound); + int fplaysentence = (pls->sLockedSentence && !pls->bEOFLocked && gpGlobals->time > pls->flwaitSentence); + float fvol; + + if (fplaysound && fplaysentence) + fvol = 0.25; + else + fvol = 1.0; + + // if there is a locked sound, and we've debounced, play sound + if (fplaysound) + { + // play 'door locked' sound + EMIT_SOUND(ENT(pev), CHAN_ITEM, (char*)STRING(pls->sLockedSound), fvol, ATTN_NORM); + pls->flwaitSound = gpGlobals->time + flsoundwait; + } + + // if there is a sentence, we've not played all in list, and we've debounced, play sound + if (fplaysentence) + { + // play next 'door locked' sentence in group + int iprev = pls->iLockedSentence; + + pls->iLockedSentence = SENTENCEG_PlaySequentialSz(ENT(pev), STRING(pls->sLockedSentence), + 0.85, ATTN_NORM, 0, 100, pls->iLockedSentence, FALSE); + pls->iUnlockedSentence = 0; + + // make sure we don't keep calling last sentence in list + pls->bEOFLocked = (iprev == pls->iLockedSentence); + + pls->flwaitSentence = gpGlobals->time + DOOR_SENTENCEWAIT; + } + } + else + { + // UNLOCKED SOUND + + int fplaysound = (pls->sUnlockedSound && gpGlobals->time > pls->flwaitSound); + int fplaysentence = (pls->sUnlockedSentence && !pls->bEOFUnlocked && gpGlobals->time > pls->flwaitSentence); + float fvol; + + // if playing both sentence and sound, lower sound volume so we hear sentence + if (fplaysound && fplaysentence) + fvol = 0.25; + else + fvol = 1.0; + + // play 'door unlocked' sound if set + if (fplaysound) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, (char*)STRING(pls->sUnlockedSound), fvol, ATTN_NORM); + pls->flwaitSound = gpGlobals->time + flsoundwait; + } + + // play next 'door unlocked' sentence in group + if (fplaysentence) + { + int iprev = pls->iUnlockedSentence; + + pls->iUnlockedSentence = SENTENCEG_PlaySequentialSz(ENT(pev), STRING(pls->sUnlockedSentence), + 0.85, ATTN_NORM, 0, 100, pls->iUnlockedSentence, FALSE); + pls->iLockedSentence = 0; + + // make sure we don't keep calling last sentence in list + pls->bEOFUnlocked = (iprev == pls->iUnlockedSentence); + pls->flwaitSentence = gpGlobals->time + DOOR_SENTENCEWAIT; + } + } +} + +// +// Cache user-entity-field values until spawn is called. +// + +void CBaseDoor::KeyValue( KeyValueData *pkvd ) +{ + + if (FStrEq(pkvd->szKeyName, "skin"))//skin is used for content type + { + pev->skin = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "movesnd")) + { + m_bMoveSnd = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "stopsnd")) + { + m_bStopSnd = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "healthvalue")) + { + m_bHealthValue = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sound")) + { + m_bLockedSound = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sentence")) + { + m_bLockedSentence = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sound")) + { + m_bUnlockedSound = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sentence")) + { + m_bUnlockedSentence = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "WaveHeight")) + { + pev->scale = atof(pkvd->szValue) * (1.0/8.0); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +/*QUAKED func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK TOGGLE +if two doors touch, they are assumed to be connected and operate as a unit. + +TOGGLE causes the door to wait in both the start and end states for a trigger event. + +START_OPEN causes the door to move to its destination when spawned, and operate in reverse. +It is used to temporarily or permanently close off an area when triggered (not usefull for +touch or takedamage doors). + +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger + field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"lip" lip remaining at end of move (8 default) +"dmg" damage to inflict when blocked (2 default) +"sounds" +0) no sound +1) stone +2) base +3) stone chain +4) screechy metal +*/ + +LINK_ENTITY_TO_CLASS( func_door, CBaseDoor ); +// +// func_water - same as a door. +// +LINK_ENTITY_TO_CLASS( func_water, CBaseDoor ); + + +void CBaseDoor::Spawn( ) +{ + Precache(); + SetMovedir (pev); + + if ( pev->skin == 0 ) + {//normal door + if ( FBitSet (pev->spawnflags, SF_DOOR_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + } + else + {// special contents + pev->solid = SOLID_NOT; + SetBits( pev->spawnflags, SF_DOOR_SILENT ); // water is silent for now + } + + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + if (pev->speed == 0) + pev->speed = 100; + + m_vecPosition1 = pev->origin; + // Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big + m_vecPosition2 = m_vecPosition1 + (pev->movedir * (fabs( pev->movedir.x * (pev->size.x-2) ) + fabs( pev->movedir.y * (pev->size.y-2) ) + fabs( pev->movedir.z * (pev->size.z-2) ) - m_flLip)); + ASSERTSZ(m_vecPosition1 != m_vecPosition2, "door start/end positions are equal"); + if ( FBitSet (pev->spawnflags, SF_DOOR_START_OPEN) ) + { // swap pos1 and pos2, put door at pos2 + UTIL_SetOrigin(pev, m_vecPosition2); + m_vecPosition2 = m_vecPosition1; + m_vecPosition1 = pev->origin; + } + + m_toggle_state = TS_AT_BOTTOM; + + // if the door is flagged for USE button activation only, use NULL touch function + if ( FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) ) + { + SetTouch ( NULL ); + } + else // touchable button + SetTouch( &CBaseDoor::DoorTouch ); +} + + +void CBaseDoor :: SetToggleState( int state ) +{ + if ( state == TS_AT_TOP ) + UTIL_SetOrigin( pev, m_vecPosition2 ); + else + UTIL_SetOrigin( pev, m_vecPosition1 ); +} + + +void CBaseDoor::Precache( void ) +{ + char *pszSound; + +// set the door's "in-motion" sound + switch (m_bMoveSnd) + { + case 0: + pev->noiseMoving = ALLOC_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("doors/doormove1.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove1.wav"); + break; + case 2: + PRECACHE_SOUND ("doors/doormove2.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove2.wav"); + break; + case 3: + PRECACHE_SOUND ("doors/doormove3.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove3.wav"); + break; + case 4: + PRECACHE_SOUND ("doors/doormove4.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove4.wav"); + break; + case 5: + PRECACHE_SOUND ("doors/doormove5.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove5.wav"); + break; + case 6: + PRECACHE_SOUND ("doors/doormove6.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove6.wav"); + break; + case 7: + PRECACHE_SOUND ("doors/doormove7.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove7.wav"); + break; + case 8: + PRECACHE_SOUND ("doors/doormove8.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove8.wav"); + break; + case 9: + PRECACHE_SOUND ("doors/doormove9.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove9.wav"); + break; + case 10: + PRECACHE_SOUND ("doors/doormove10.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove10.wav"); + break; + default: + pev->noiseMoving = ALLOC_STRING("common/null.wav"); + break; + } + +// set the door's 'reached destination' stop sound + switch (m_bStopSnd) + { + case 0: + pev->noiseArrived = ALLOC_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("doors/doorstop1.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop1.wav"); + break; + case 2: + PRECACHE_SOUND ("doors/doorstop2.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop2.wav"); + break; + case 3: + PRECACHE_SOUND ("doors/doorstop3.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop3.wav"); + break; + case 4: + PRECACHE_SOUND ("doors/doorstop4.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop4.wav"); + break; + case 5: + PRECACHE_SOUND ("doors/doorstop5.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop5.wav"); + break; + case 6: + PRECACHE_SOUND ("doors/doorstop6.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop6.wav"); + break; + case 7: + PRECACHE_SOUND ("doors/doorstop7.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop7.wav"); + break; + case 8: + PRECACHE_SOUND ("doors/doorstop8.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop8.wav"); + break; + default: + pev->noiseArrived = ALLOC_STRING("common/null.wav"); + break; + } + + // get door button sounds, for doors which are directly 'touched' to open + + if (m_bLockedSound) + { + pszSound = ButtonSound( (int)m_bLockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sLockedSound = ALLOC_STRING(pszSound); + } + + if (m_bUnlockedSound) + { + pszSound = ButtonSound( (int)m_bUnlockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sUnlockedSound = ALLOC_STRING(pszSound); + } + + // get sentence group names, for doors which are directly 'touched' to open + + switch (m_bLockedSentence) + { + case 1: m_ls.sLockedSentence = ALLOC_STRING("NA"); break; // access denied + case 2: m_ls.sLockedSentence = ALLOC_STRING("ND"); break; // security lockout + case 3: m_ls.sLockedSentence = ALLOC_STRING("NF"); break; // blast door + case 4: m_ls.sLockedSentence = ALLOC_STRING("NFIRE"); break; // fire door + case 5: m_ls.sLockedSentence = ALLOC_STRING("NCHEM"); break; // chemical door + case 6: m_ls.sLockedSentence = ALLOC_STRING("NRAD"); break; // radiation door + case 7: m_ls.sLockedSentence = ALLOC_STRING("NCON"); break; // gen containment + case 8: m_ls.sLockedSentence = ALLOC_STRING("NH"); break; // maintenance door + case 9: m_ls.sLockedSentence = ALLOC_STRING("NG"); break; // broken door + + default: m_ls.sLockedSentence = 0; break; + } + + switch (m_bUnlockedSentence) + { + case 1: m_ls.sUnlockedSentence = ALLOC_STRING("EA"); break; // access granted + case 2: m_ls.sUnlockedSentence = ALLOC_STRING("ED"); break; // security door + case 3: m_ls.sUnlockedSentence = ALLOC_STRING("EF"); break; // blast door + case 4: m_ls.sUnlockedSentence = ALLOC_STRING("EFIRE"); break; // fire door + case 5: m_ls.sUnlockedSentence = ALLOC_STRING("ECHEM"); break; // chemical door + case 6: m_ls.sUnlockedSentence = ALLOC_STRING("ERAD"); break; // radiation door + case 7: m_ls.sUnlockedSentence = ALLOC_STRING("ECON"); break; // gen containment + case 8: m_ls.sUnlockedSentence = ALLOC_STRING("EH"); break; // maintenance door + + default: m_ls.sUnlockedSentence = 0; break; + } +} + +// +// Doors not tied to anything (e.g. button, another door) can be touched, to make them activate. +// +void CBaseDoor::DoorTouch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + + // Ignore touches by anything but players + if (!FClassnameIs(pevToucher, "player")) + return; + + // If door has master, and it's not ready to trigger, + // play 'locked' sound + + if (m_sMaster && !UTIL_IsMasterTriggered(m_sMaster, pOther)) + PlayLockSounds(pev, &m_ls, TRUE, FALSE); + + // If door is somebody's target, then touching does nothing. + // You have to activate the owner (e.g. button). + + if (!FStringNull(pev->targetname)) + { + // play locked sound + PlayLockSounds(pev, &m_ls, TRUE, FALSE); + return; + } + + m_hActivator = pOther;// remember who activated the door + + if (DoorActivate( )) + SetTouch( NULL ); // Temporarily disable the touch function, until movement is finished. +} + + +// +// Used by SUB_UseTargets, when a door is the target of a button. +// +void CBaseDoor::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_hActivator = pActivator; + // if not ready to be used, ignore "use" command. + if (m_toggle_state == TS_AT_BOTTOM || FBitSet(pev->spawnflags, SF_DOOR_NO_AUTO_RETURN) && m_toggle_state == TS_AT_TOP) + DoorActivate(); +} + +// +// Causes the door to "do its thing", i.e. start moving, and cascade activation. +// +int CBaseDoor::DoorActivate( ) +{ + if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + return 0; + + if (FBitSet(pev->spawnflags, SF_DOOR_NO_AUTO_RETURN) && m_toggle_state == TS_AT_TOP) + {// door should close + DoorGoDown(); + } + else + {// door should open + + if ( m_hActivator != 0 && m_hActivator->IsPlayer() ) + {// give health if player opened the door (medikit) + // VARS( m_eoActivator )->health += m_bHealthValue; + + m_hActivator->TakeHealth( m_bHealthValue, DMG_GENERIC ); + + } + + // play door unlock sounds + PlayLockSounds(pev, &m_ls, FALSE, FALSE); + + DoorGoUp(); + } + + return 1; +} + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); + +// +// Starts the door going to its "up" position (simply ToggleData->vecPosition2). +// +void CBaseDoor::DoorGoUp( void ) +{ + entvars_t *pevActivator; + + // It could be going-down, if blocked. + ASSERT(m_toggle_state == TS_AT_BOTTOM || m_toggle_state == TS_GOING_DOWN); + + // emit door moving and stop sounds on CHAN_STATIC so that the multicast doesn't + // filter them out and leave a client stuck with looping door sounds! + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving), 1, ATTN_NORM); + + m_toggle_state = TS_GOING_UP; + + SetMoveDone( &CBaseDoor::DoorHitTop ); + if ( FClassnameIs(pev, "func_door_rotating")) // !!! BUGBUG Triggered doors don't work with this yet + { + float sign = 1.0; + + if ( m_hActivator != 0 ) + { + pevActivator = m_hActivator->pev; + + if ( !FBitSet( pev->spawnflags, SF_DOOR_ONEWAY ) && pev->movedir.y ) // Y axis rotation, move away from the player + { + Vector vec = pevActivator->origin - pev->origin; + Vector angles = pevActivator->angles; + angles.x = 0; + angles.z = 0; + UTIL_MakeVectors (angles); + // Vector vnext = (pevToucher->origin + (pevToucher->velocity * 10)) - pev->origin; + UTIL_MakeVectors ( pevActivator->angles ); + Vector vnext = (pevActivator->origin + (gpGlobals->v_forward * 10)) - pev->origin; + if ( (vec.x*vnext.y - vec.y*vnext.x) < 0 ) + sign = -1.0; + } + } + AngularMove(m_vecAngle2*sign, pev->speed); + } + else + LinearMove(m_vecPosition2, pev->speed); +} + + +// +// The door has reached the "up" position. Either go back down, or wait for another activation. +// +void CBaseDoor::DoorHitTop( void ) +{ + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + { + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving) ); + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseArrived), 1, ATTN_NORM); + } + + ASSERT(m_toggle_state == TS_GOING_UP); + m_toggle_state = TS_AT_TOP; + + // toggle-doors don't come down automatically, they wait for refire. + if (FBitSet(pev->spawnflags, SF_DOOR_NO_AUTO_RETURN)) + { + // Re-instate touch method, movement is complete + if ( !FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) ) + SetTouch( &CBaseDoor::DoorTouch ); + } + else + { + // In flWait seconds, DoorGoDown will fire, unless wait is -1, then door stays open + pev->nextthink = pev->ltime + m_flWait; + SetThink( &CBaseDoor::DoorGoDown ); + + if ( m_flWait == -1 ) + { + pev->nextthink = -1; + } + } + + // Fire the close target (if startopen is set, then "top" is closed) - netname is the close target + if ( pev->netname && (pev->spawnflags & SF_DOOR_START_OPEN) ) + FireTargets( STRING(pev->netname), m_hActivator, this, USE_TOGGLE, 0 ); + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); // this isn't finished +} + + +// +// Starts the door going to its "down" position (simply ToggleData->vecPosition1). +// +void CBaseDoor::DoorGoDown( void ) +{ + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving), 1, ATTN_NORM); + +#ifdef DOOR_ASSERT + ASSERT(m_toggle_state == TS_AT_TOP); +#endif // DOOR_ASSERT + m_toggle_state = TS_GOING_DOWN; + + SetMoveDone( &CBaseDoor::DoorHitBottom ); + if ( FClassnameIs(pev, "func_door_rotating"))//rotating door + AngularMove( m_vecAngle1, pev->speed); + else + LinearMove( m_vecPosition1, pev->speed); +} + +// +// The door has reached the "down" position. Back to quiescence. +// +void CBaseDoor::DoorHitBottom( void ) +{ + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + { + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving) ); + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseArrived), 1, ATTN_NORM); + } + + ASSERT(m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_AT_BOTTOM; + + // Re-instate touch method, cycle is complete + if ( FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) ) + {// use only door + SetTouch ( NULL ); + } + else // touchable door + SetTouch( &CBaseDoor::DoorTouch ); + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); // this isn't finished + + // Fire the close target (if startopen is set, then "top" is closed) - netname is the close target + if ( pev->netname && !(pev->spawnflags & SF_DOOR_START_OPEN) ) + FireTargets( STRING(pev->netname), m_hActivator, this, USE_TOGGLE, 0 ); +} + +void CBaseDoor::Blocked( CBaseEntity *pOther ) +{ + edict_t *pentTarget = NULL; + CBaseDoor *pDoor = NULL; + + + // Hurt the blocker a little. + if ( pev->dmg ) + pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH ); + + // if a door has a negative wait, it would never come back if blocked, + // so let it just squash the object to death real fast + + if (m_flWait >= 0) + { + if (m_toggle_state == TS_GOING_DOWN) + { + DoorGoUp(); + } + else + { + DoorGoDown(); + } + } + + // Block all door pieces with the same targetname here. + if ( !FStringNull ( pev->targetname ) ) + { + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->targetname)); + + if ( VARS( pentTarget ) != pev ) + { + if (FNullEnt(pentTarget)) + break; + + if ( FClassnameIs ( pentTarget, "func_door" ) || FClassnameIs ( pentTarget, "func_door_rotating" ) ) + { + + pDoor = GetClassPtr( (CBaseDoor *) VARS(pentTarget) ); + + if ( pDoor->m_flWait >= 0) + { + if (pDoor->pev->velocity == pev->velocity && pDoor->pev->avelocity == pev->velocity) + { + // this is the most hacked, evil, bastardized thing I've ever seen. kjb + if ( FClassnameIs ( pentTarget, "func_door" ) ) + {// set origin to realign normal doors + pDoor->pev->origin = pev->origin; + pDoor->pev->velocity = g_vecZero;// stop! + } + else + {// set angles to realign rotating doors + pDoor->pev->angles = pev->angles; + pDoor->pev->avelocity = g_vecZero; + } + } + + if ( pDoor->m_toggle_state == TS_GOING_DOWN) + pDoor->DoorGoUp(); + else + pDoor->DoorGoDown(); + } + } + } + } + } +} + + +/*QUAKED FuncRotDoorSpawn (0 .5 .8) ? START_OPEN REVERSE +DOOR_DONT_LINK TOGGLE X_AXIS Y_AXIS +if two doors touch, they are assumed to be connected and operate as +a unit. + +TOGGLE causes the door to wait in both the start and end states for +a trigger event. + +START_OPEN causes the door to move to its destination when spawned, +and operate in reverse. It is used to temporarily or permanently +close off an area when triggered (not usefull for touch or +takedamage doors). + +You need to have an origin brush as part of this entity. The +center of that brush will be +the point around which it is rotated. It will rotate around the Z +axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"distance" is how many degrees the door will be rotated. +"speed" determines how fast the door moves; default value is 100. + +REVERSE will cause the door to rotate in the opposite direction. + +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote +button or trigger field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"dmg" damage to inflict when blocked (2 default) +"sounds" +0) no sound +1) stone +2) base +3) stone chain +4) screechy metal +*/ +class CRotDoor : public CBaseDoor +{ +public: + void Spawn( void ); + virtual void SetToggleState( int state ); +}; + +LINK_ENTITY_TO_CLASS( func_door_rotating, CRotDoor ); + + +void CRotDoor::Spawn( void ) +{ + Precache(); + // set the axis of rotation + CBaseToggle::AxisDir( pev ); + + // check for clockwise rotation + if ( FBitSet (pev->spawnflags, SF_DOOR_ROTATE_BACKWARDS) ) + pev->movedir = pev->movedir * -1; + + //m_flWait = 2; who the hell did this? (sjb) + m_vecAngle1 = pev->angles; + m_vecAngle2 = pev->angles + pev->movedir * m_flMoveDistance; + + ASSERTSZ(m_vecAngle1 != m_vecAngle2, "rotating door start/end positions are equal"); + + if ( FBitSet (pev->spawnflags, SF_DOOR_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + if (pev->speed == 0) + pev->speed = 100; + +// DOOR_START_OPEN is to allow an entity to be lighted in the closed position +// but spawn in the open position + if ( FBitSet (pev->spawnflags, SF_DOOR_START_OPEN) ) + { // swap pos1 and pos2, put door at pos2, invert movement direction + pev->angles = m_vecAngle2; + Vector vecSav = m_vecAngle1; + m_vecAngle2 = m_vecAngle1; + m_vecAngle1 = vecSav; + pev->movedir = pev->movedir * -1; + } + + m_toggle_state = TS_AT_BOTTOM; + + if ( FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) ) + { + SetTouch ( NULL ); + } + else // touchable button + SetTouch( &CBaseDoor::DoorTouch ); +} + + +void CRotDoor :: SetToggleState( int state ) +{ + if ( state == TS_AT_TOP ) + pev->angles = m_vecAngle2; + else + pev->angles = m_vecAngle1; + + UTIL_SetOrigin( pev, pev->origin ); +} + + +class CMomentaryDoor : public CBaseToggle +{ +public: + void Spawn( void ); + void Precache( void ); + + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + BYTE m_bMoveSnd; // sound a door makes while moving +}; + +LINK_ENTITY_TO_CLASS( momentary_door, CMomentaryDoor ); + +TYPEDESCRIPTION CMomentaryDoor::m_SaveData[] = +{ + DEFINE_FIELD( CMomentaryDoor, m_bMoveSnd, FIELD_CHARACTER ), +}; + +IMPLEMENT_SAVERESTORE( CMomentaryDoor, CBaseToggle ); + +void CMomentaryDoor::Spawn( void ) +{ + SetMovedir (pev); + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + if (pev->speed == 0) + pev->speed = 100; + if (pev->dmg == 0) + pev->dmg = 2; + + m_vecPosition1 = pev->origin; + // Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big + m_vecPosition2 = m_vecPosition1 + (pev->movedir * (fabs( pev->movedir.x * (pev->size.x-2) ) + fabs( pev->movedir.y * (pev->size.y-2) ) + fabs( pev->movedir.z * (pev->size.z-2) ) - m_flLip)); + ASSERTSZ(m_vecPosition1 != m_vecPosition2, "door start/end positions are equal"); + + if ( FBitSet (pev->spawnflags, SF_DOOR_START_OPEN) ) + { // swap pos1 and pos2, put door at pos2 + UTIL_SetOrigin(pev, m_vecPosition2); + m_vecPosition2 = m_vecPosition1; + m_vecPosition1 = pev->origin; + } + SetTouch( NULL ); + + Precache(); +} + +void CMomentaryDoor::Precache( void ) +{ + +// set the door's "in-motion" sound + switch (m_bMoveSnd) + { + case 0: + pev->noiseMoving = ALLOC_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("doors/doormove1.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove1.wav"); + break; + case 2: + PRECACHE_SOUND ("doors/doormove2.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove2.wav"); + break; + case 3: + PRECACHE_SOUND ("doors/doormove3.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove3.wav"); + break; + case 4: + PRECACHE_SOUND ("doors/doormove4.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove4.wav"); + break; + case 5: + PRECACHE_SOUND ("doors/doormove5.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove5.wav"); + break; + case 6: + PRECACHE_SOUND ("doors/doormove6.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove6.wav"); + break; + case 7: + PRECACHE_SOUND ("doors/doormove7.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove7.wav"); + break; + case 8: + PRECACHE_SOUND ("doors/doormove8.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove8.wav"); + break; + default: + pev->noiseMoving = ALLOC_STRING("common/null.wav"); + break; + } +} + +void CMomentaryDoor::KeyValue( KeyValueData *pkvd ) +{ + + if (FStrEq(pkvd->szKeyName, "movesnd")) + { + m_bMoveSnd = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "stopsnd")) + { +// m_bStopSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "healthvalue")) + { +// m_bHealthValue = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CMomentaryDoor::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( useType != USE_SET ) // Momentary buttons will pass down a float in here + return; + + if ( value > 1.0 ) + value = 1.0; + Vector move = m_vecPosition1 + (value * (m_vecPosition2 - m_vecPosition1)); + + Vector delta = move - pev->origin; + float speed = delta.Length() * 10; + + if ( speed != 0 ) + { + // This entity only thinks when it moves, so if it's thinking, it's in the process of moving + // play the sound when it starts moving + if ( pev->nextthink < pev->ltime || pev->nextthink == 0 ) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving), 1, ATTN_NORM); + + LinearMove( move, speed ); + } +} diff --git a/hlsdk/ricochet/dlls/doors.h b/hlsdk/ricochet/dlls/doors.h new file mode 100644 index 00000000..07447a05 --- /dev/null +++ b/hlsdk/ricochet/dlls/doors.h @@ -0,0 +1,33 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +#ifndef DOORS_H +#define DOORS_H + +// doors +#define SF_DOOR_ROTATE_Y 0 +#define SF_DOOR_START_OPEN 1 +#define SF_DOOR_ROTATE_BACKWARDS 2 +#define SF_DOOR_PASSABLE 8 +#define SF_DOOR_ONEWAY 16 +#define SF_DOOR_NO_AUTO_RETURN 32 +#define SF_DOOR_ROTATE_Z 64 +#define SF_DOOR_ROTATE_X 128 +#define SF_DOOR_USE_ONLY 256 // door must be opened by player's use button. +#define SF_DOOR_NOMONSTERS 512 // Monster can't open +#define SF_DOOR_SILENT 0x80000000 + + + +#endif //DOORS_H diff --git a/hlsdk/ricochet/dlls/effects.cpp b/hlsdk/ricochet/dlls/effects.cpp new file mode 100644 index 00000000..41b17a4c --- /dev/null +++ b/hlsdk/ricochet/dlls/effects.cpp @@ -0,0 +1,2267 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "customentity.h" +#include "effects.h" +#include "weapons.h" +#include "decals.h" +#include "func_break.h" +#include "shake.h" + +#define SF_GIBSHOOTER_REPEATABLE 1 // allows a gibshooter to be refired + +#define SF_FUNNEL_REVERSE 1 // funnel effect repels particles instead of attracting them. + + +// Lightning target, just alias landmark +LINK_ENTITY_TO_CLASS( info_target, CPointEntity ); + + +class CBubbling : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + void EXPORT FizzThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + static TYPEDESCRIPTION m_SaveData[]; + + int m_density; + int m_frequency; + int m_bubbleModel; + int m_state; +}; + +LINK_ENTITY_TO_CLASS( env_bubbles, CBubbling ); + +TYPEDESCRIPTION CBubbling::m_SaveData[] = +{ + DEFINE_FIELD( CBubbling, m_density, FIELD_INTEGER ), + DEFINE_FIELD( CBubbling, m_frequency, FIELD_INTEGER ), + DEFINE_FIELD( CBubbling, m_state, FIELD_INTEGER ), + // Let spawn restore this! + // DEFINE_FIELD( CBubbling, m_bubbleModel, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CBubbling, CBaseEntity ); + + +#define SF_BUBBLES_STARTOFF 0x0001 + +void CBubbling::Spawn( void ) +{ + Precache( ); + SET_MODEL( ENT(pev), STRING(pev->model) ); // Set size + + pev->solid = SOLID_NOT; // Remove model & collisions + pev->renderamt = 0; // The engine won't draw this model if this is set to 0 and blending is on + pev->rendermode = kRenderTransTexture; + int speed = pev->speed > 0 ? static_cast(pev->speed) : static_cast(-pev->speed); + + // HACKHACK!!! - Speed in rendercolor + pev->rendercolor.x = speed >> 8; + pev->rendercolor.y = speed & 255; + pev->rendercolor.z = (pev->speed < 0) ? 1 : 0; + + + if ( !(pev->spawnflags & SF_BUBBLES_STARTOFF) ) + { + SetThink( &CBubbling::FizzThink ); + pev->nextthink = gpGlobals->time + 2.0; + m_state = 1; + } + else + m_state = 0; +} + +void CBubbling::Precache( void ) +{ + m_bubbleModel = PRECACHE_MODEL("sprites/bubble.spr"); // Precache bubble sprite +} + + +void CBubbling::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( ShouldToggle( useType, m_state ) ) + m_state = !m_state; + + if ( m_state ) + { + SetThink( &CBubbling::FizzThink ); + pev->nextthink = gpGlobals->time + 0.1; + } + else + { + SetThink( NULL ); + pev->nextthink = 0; + } +} + + +void CBubbling::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "density")) + { + m_density = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "frequency")) + { + m_frequency = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "current")) + { + pev->speed = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +void CBubbling::FizzThink( void ) +{ + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, VecBModelOrigin(pev) ); + WRITE_BYTE( TE_FIZZ ); + WRITE_SHORT( (short)ENTINDEX( edict() ) ); + WRITE_SHORT( (short)m_bubbleModel ); + WRITE_BYTE( m_density ); + MESSAGE_END(); + + if ( m_frequency > 19 ) + pev->nextthink = gpGlobals->time + 0.5; + else + pev->nextthink = gpGlobals->time + 2.5 - (0.1 * m_frequency); +} + +// -------------------------------------------------- +// +// Beams +// +// -------------------------------------------------- + +LINK_ENTITY_TO_CLASS( beam, CBeam ); + +void CBeam::Spawn( void ) +{ + pev->solid = SOLID_NOT; // Remove model & collisions + Precache( ); +} + +void CBeam::Precache( void ) +{ + if ( pev->owner ) + SetStartEntity( ENTINDEX( pev->owner ) ); + if ( pev->aiment ) + SetEndEntity( ENTINDEX( pev->aiment ) ); +} + +void CBeam::SetStartEntity( int entityIndex ) +{ + pev->sequence = (entityIndex & 0x0FFF) | ((pev->sequence&0xF000)<<12); + pev->owner = g_engfuncs.pfnPEntityOfEntIndex( entityIndex ); +} + +void CBeam::SetEndEntity( int entityIndex ) +{ + pev->skin = (entityIndex & 0x0FFF) | ((pev->skin&0xF000)<<12); + pev->aiment = g_engfuncs.pfnPEntityOfEntIndex( entityIndex ); +} + + +// These don't take attachments into account +const Vector &CBeam::GetStartPos( void ) +{ + if ( GetType() == BEAM_ENTS ) + { + edict_t *pent = g_engfuncs.pfnPEntityOfEntIndex( GetStartEntity() ); + return pent->v.origin; + } + return pev->origin; +} + + +const Vector &CBeam::GetEndPos( void ) +{ + int type = GetType(); + if ( type == BEAM_POINTS || type == BEAM_HOSE ) + { + return pev->angles; + } + + edict_t *pent = g_engfuncs.pfnPEntityOfEntIndex( GetEndEntity() ); + if ( pent ) + return pent->v.origin; + return pev->angles; +} + + +CBeam *CBeam::BeamCreate( const char *pSpriteName, int width ) +{ + // Create a new entity with CBeam private data + CBeam *pBeam = GetClassPtr( (CBeam *)NULL ); + pBeam->pev->classname = MAKE_STRING("beam"); + + pBeam->BeamInit( pSpriteName, width ); + + return pBeam; +} + + +void CBeam::BeamInit( const char *pSpriteName, int width ) +{ + pev->flags |= FL_CUSTOMENTITY; + SetColor( 255, 255, 255 ); + SetBrightness( 255 ); + SetNoise( 0 ); + SetFrame( 0 ); + SetScrollRate( 0 ); + pev->model = MAKE_STRING( pSpriteName ); + SetTexture( PRECACHE_MODEL( (char *)pSpriteName ) ); + SetWidth( width ); + pev->skin = 0; + pev->sequence = 0; + pev->rendermode = 0; +} + + +void CBeam::PointsInit( const Vector &start, const Vector &end ) +{ + SetType( BEAM_POINTS ); + SetStartPos( start ); + SetEndPos( end ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + + +void CBeam::HoseInit( const Vector &start, const Vector &direction ) +{ + SetType( BEAM_HOSE ); + SetStartPos( start ); + SetEndPos( direction ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + + +void CBeam::PointEntInit( const Vector &start, int endIndex ) +{ + SetType( BEAM_ENTPOINT ); + SetStartPos( start ); + SetEndEntity( endIndex ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + +void CBeam::EntsInit( int startIndex, int endIndex ) +{ + SetType( BEAM_ENTS ); + SetStartEntity( startIndex ); + SetEndEntity( endIndex ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + + +void CBeam::RelinkBeam( void ) +{ + const Vector &startPos = GetStartPos(), &endPos = GetEndPos(); + + pev->mins.x = min( startPos.x, endPos.x ); + pev->mins.y = min( startPos.y, endPos.y ); + pev->mins.z = min( startPos.z, endPos.z ); + pev->maxs.x = max( startPos.x, endPos.x ); + pev->maxs.y = max( startPos.y, endPos.y ); + pev->maxs.z = max( startPos.z, endPos.z ); + pev->mins = pev->mins - pev->origin; + pev->maxs = pev->maxs - pev->origin; + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( pev, pev->origin ); +} + +#if 0 +void CBeam::SetObjectCollisionBox( void ) +{ + const Vector &startPos = GetStartPos(), &endPos = GetEndPos(); + + pev->absmin.x = min( startPos.x, endPos.x ); + pev->absmin.y = min( startPos.y, endPos.y ); + pev->absmin.z = min( startPos.z, endPos.z ); + pev->absmax.x = max( startPos.x, endPos.x ); + pev->absmax.y = max( startPos.y, endPos.y ); + pev->absmax.z = max( startPos.z, endPos.z ); +} +#endif + + +void CBeam::TriggerTouch( CBaseEntity *pOther ) +{ + if ( pOther->pev->flags & (FL_CLIENT | FL_MONSTER) ) + { + if ( pev->owner ) + { + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + pOwner->Use( pOther, this, USE_TOGGLE, 0 ); + } + ALERT( at_console, "Firing targets!!!\n" ); + } +} + + +CBaseEntity *CBeam::RandomTargetname( const char *szName ) +{ + int total = 0; + + CBaseEntity *pEntity = NULL; + CBaseEntity *pNewEntity = NULL; + while ((pNewEntity = UTIL_FindEntityByTargetname( pNewEntity, szName )) != NULL) + { + total++; + if (RANDOM_LONG(0,total-1) < 1) + pEntity = pNewEntity; + } + return pEntity; +} + + +void CBeam::DoSparks( const Vector &start, const Vector &end ) +{ + if ( pev->spawnflags & (SF_BEAM_SPARKSTART|SF_BEAM_SPARKEND) ) + { + if ( pev->spawnflags & SF_BEAM_SPARKSTART ) + { + UTIL_Sparks( start ); + } + if ( pev->spawnflags & SF_BEAM_SPARKEND ) + { + UTIL_Sparks( end ); + } + } +} + + +class CLightning : public CBeam +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void Activate( void ); + + void EXPORT StrikeThink( void ); + void EXPORT DamageThink( void ); + void RandomArea( void ); + void RandomPoint( Vector &vecSrc ); + void Zap( const Vector &vecSrc, const Vector &vecDest ); + void EXPORT StrikeUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + inline BOOL ServerSide( void ) + { + if ( m_life == 0 && !(pev->spawnflags & SF_BEAM_RING) ) + return TRUE; + return FALSE; + } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void BeamUpdateVars( void ); + + int m_active; + int m_iszStartEntity; + int m_iszEndEntity; + float m_life; + int m_boltWidth; + int m_noiseAmplitude; + int m_brightness; + int m_speed; + float m_restrike; + int m_spriteTexture; + int m_iszSpriteName; + int m_frameStart; + + float m_radius; +}; + +LINK_ENTITY_TO_CLASS( env_lightning, CLightning ); +LINK_ENTITY_TO_CLASS( env_beam, CLightning ); + +// UNDONE: Jay -- This is only a test +#if _DEBUG +class CTripBeam : public CLightning +{ + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS( trip_beam, CTripBeam ); + +void CTripBeam::Spawn( void ) +{ + CLightning::Spawn(); + SetTouch( &CBeam::TriggerTouch ); + pev->solid = SOLID_TRIGGER; + RelinkBeam(); +} +#endif + + + +TYPEDESCRIPTION CLightning::m_SaveData[] = +{ + DEFINE_FIELD( CLightning, m_active, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_iszStartEntity, FIELD_STRING ), + DEFINE_FIELD( CLightning, m_iszEndEntity, FIELD_STRING ), + DEFINE_FIELD( CLightning, m_life, FIELD_FLOAT ), + DEFINE_FIELD( CLightning, m_boltWidth, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_noiseAmplitude, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_brightness, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_speed, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_restrike, FIELD_FLOAT ), + DEFINE_FIELD( CLightning, m_spriteTexture, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_iszSpriteName, FIELD_STRING ), + DEFINE_FIELD( CLightning, m_frameStart, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_radius, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CLightning, CBeam ); + + +void CLightning::Spawn( void ) +{ + if ( FStringNull( m_iszSpriteName ) ) + { + SetThink( &CBaseEntity::SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; // Remove model & collisions + Precache( ); + + pev->dmgtime = gpGlobals->time; + + if ( ServerSide() ) + { + SetThink( NULL ); + if ( pev->dmg > 0 ) + { + SetThink( &CLightning::DamageThink ); + pev->nextthink = gpGlobals->time + 0.1; + } + if ( pev->targetname ) + { + if ( !(pev->spawnflags & SF_BEAM_STARTON) ) + { + pev->effects = EF_NODRAW; + m_active = 0; + pev->nextthink = 0; + } + else + m_active = 1; + + SetUse( &CLightning::ToggleUse ); + } + } + else + { + m_active = 0; + if ( !FStringNull(pev->targetname) ) + { + SetUse( &CLightning::StrikeUse ); + } + if ( FStringNull(pev->targetname) || FBitSet(pev->spawnflags, SF_BEAM_STARTON) ) + { + SetThink( &CLightning::StrikeThink ); + pev->nextthink = gpGlobals->time + 1.0; + } + } +} + +void CLightning::Precache( void ) +{ + m_spriteTexture = PRECACHE_MODEL( (char *)STRING(m_iszSpriteName) ); + CBeam::Precache(); +} + + +void CLightning::Activate( void ) +{ + if ( ServerSide() ) + BeamUpdateVars(); +} + + +void CLightning::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "LightningStart")) + { + m_iszStartEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "LightningEnd")) + { + m_iszEndEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "life")) + { + m_life = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "BoltWidth")) + { + m_boltWidth = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "NoiseAmplitude")) + { + m_noiseAmplitude = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "TextureScroll")) + { + m_speed = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "StrikeTime")) + { + m_restrike = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "texture")) + { + m_iszSpriteName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "framestart")) + { + m_frameStart = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "Radius")) + { + m_radius = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damage")) + { + pev->dmg = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBeam::KeyValue( pkvd ); +} + + +void CLightning::ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_active ) ) + return; + if ( m_active ) + { + m_active = 0; + pev->effects |= EF_NODRAW; + pev->nextthink = 0; + } + else + { + m_active = 1; + pev->effects &= ~EF_NODRAW; + DoSparks( GetStartPos(), GetEndPos() ); + if ( pev->dmg > 0 ) + { + pev->nextthink = gpGlobals->time; + pev->dmgtime = gpGlobals->time; + } + } +} + + +void CLightning::StrikeUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_active ) ) + return; + + if ( m_active ) + { + m_active = 0; + SetThink( NULL ); + } + else + { + SetThink( &CLightning::StrikeThink ); + pev->nextthink = gpGlobals->time + 0.1; + } + + if ( !FBitSet( pev->spawnflags, SF_BEAM_TOGGLE ) ) + SetUse( NULL ); +} + + +int IsPointEntity( CBaseEntity *pEnt ) +{ + if ( !pEnt->pev->modelindex ) + return 1; + if ( FClassnameIs( pEnt->pev, "info_target" ) || FClassnameIs( pEnt->pev, "info_landmark" ) || FClassnameIs( pEnt->pev, "path_corner" ) ) + return 1; + + return 0; +} + + +void CLightning::StrikeThink( void ) +{ + if ( m_life != 0 ) + { + if ( pev->spawnflags & SF_BEAM_RANDOM ) + pev->nextthink = gpGlobals->time + m_life + RANDOM_FLOAT( 0, m_restrike ); + else + pev->nextthink = gpGlobals->time + m_life + m_restrike; + } + m_active = 1; + + if (FStringNull(m_iszEndEntity)) + { + if (FStringNull(m_iszStartEntity)) + { + RandomArea( ); + } + else + { + CBaseEntity *pStart = RandomTargetname( STRING(m_iszStartEntity) ); + if (pStart != NULL) + RandomPoint( pStart->pev->origin ); + else + ALERT( at_console, "env_beam: unknown entity \"%s\"\n", STRING(m_iszStartEntity) ); + } + return; + } + + CBaseEntity *pStart = RandomTargetname( STRING(m_iszStartEntity) ); + CBaseEntity *pEnd = RandomTargetname( STRING(m_iszEndEntity) ); + + if ( pStart != NULL && pEnd != NULL ) + { + if ( IsPointEntity( pStart ) || IsPointEntity( pEnd ) ) + { + if ( pev->spawnflags & SF_BEAM_RING) + { + // don't work + return; + } + } + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + if ( IsPointEntity( pStart ) || IsPointEntity( pEnd ) ) + { + if ( !IsPointEntity( pEnd ) ) // One point entity must be in pEnd + { + CBaseEntity *pTemp; + pTemp = pStart; + pStart = pEnd; + pEnd = pTemp; + } + if ( !IsPointEntity( pStart ) ) // One sided + { + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( pStart->entindex() ); + WRITE_COORD( pEnd->pev->origin.x); + WRITE_COORD( pEnd->pev->origin.y); + WRITE_COORD( pEnd->pev->origin.z); + } + else + { + WRITE_BYTE( TE_BEAMPOINTS); + WRITE_COORD( pStart->pev->origin.x); + WRITE_COORD( pStart->pev->origin.y); + WRITE_COORD( pStart->pev->origin.z); + WRITE_COORD( pEnd->pev->origin.x); + WRITE_COORD( pEnd->pev->origin.y); + WRITE_COORD( pEnd->pev->origin.z); + } + + + } + else + { + if ( pev->spawnflags & SF_BEAM_RING) + WRITE_BYTE( TE_BEAMRING ); + else + WRITE_BYTE( TE_BEAMENTS ); + WRITE_SHORT( pStart->entindex() ); + WRITE_SHORT( pEnd->entindex() ); + } + + WRITE_SHORT( m_spriteTexture ); + WRITE_BYTE( m_frameStart ); // framestart + WRITE_BYTE( (int)pev->framerate); // framerate + WRITE_BYTE( (int)(m_life*10.0) ); // life + WRITE_BYTE( m_boltWidth ); // width + WRITE_BYTE( m_noiseAmplitude ); // noise + WRITE_BYTE( (int)pev->rendercolor.x ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.y ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.z ); // r, g, b + WRITE_BYTE( (int)pev->renderamt ); // brightness + WRITE_BYTE( m_speed ); // speed + MESSAGE_END(); + DoSparks( pStart->pev->origin, pEnd->pev->origin ); + if ( pev->dmg > 0 ) + { + TraceResult tr; + UTIL_TraceLine( pStart->pev->origin, pEnd->pev->origin, dont_ignore_monsters, NULL, &tr ); + BeamDamageInstant( &tr, pev->dmg ); + } + } +} + + +void CBeam::BeamDamage( TraceResult *ptr ) +{ + RelinkBeam(); + if ( ptr->flFraction != 1.0 && ptr->pHit != NULL ) + { + CBaseEntity *pHit = CBaseEntity::Instance(ptr->pHit); + if ( pHit ) + { + ClearMultiDamage(); + pHit->TraceAttack( pev, pev->dmg * (gpGlobals->time - pev->dmgtime), (ptr->vecEndPos - pev->origin).Normalize(), ptr, DMG_ENERGYBEAM ); + ApplyMultiDamage( pev, pev ); + if ( pev->spawnflags & SF_BEAM_DECALS ) + { + if ( pHit->IsBSPModel() ) + UTIL_DecalTrace( ptr, DECAL_BIGSHOT1 + RANDOM_LONG(0,4) ); + } + } + } + pev->dmgtime = gpGlobals->time; +} + + +void CLightning::DamageThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + TraceResult tr; + UTIL_TraceLine( GetStartPos(), GetEndPos(), dont_ignore_monsters, NULL, &tr ); + BeamDamage( &tr ); +} + + + +void CLightning::Zap( const Vector &vecSrc, const Vector &vecDest ) +{ +#if 1 + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS); + WRITE_COORD(vecSrc.x); + WRITE_COORD(vecSrc.y); + WRITE_COORD(vecSrc.z); + WRITE_COORD(vecDest.x); + WRITE_COORD(vecDest.y); + WRITE_COORD(vecDest.z); + WRITE_SHORT( m_spriteTexture ); + WRITE_BYTE( m_frameStart ); // framestart + WRITE_BYTE( (int)pev->framerate); // framerate + WRITE_BYTE( (int)(m_life*10.0) ); // life + WRITE_BYTE( m_boltWidth ); // width + WRITE_BYTE( m_noiseAmplitude ); // noise + WRITE_BYTE( (int)pev->rendercolor.x ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.y ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.z ); // r, g, b + WRITE_BYTE( (int)pev->renderamt ); // brightness + WRITE_BYTE( m_speed ); // speed + MESSAGE_END(); +#else + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE(TE_LIGHTNING); + WRITE_COORD(vecSrc.x); + WRITE_COORD(vecSrc.y); + WRITE_COORD(vecSrc.z); + WRITE_COORD(vecDest.x); + WRITE_COORD(vecDest.y); + WRITE_COORD(vecDest.z); + WRITE_BYTE(10); + WRITE_BYTE(50); + WRITE_BYTE(40); + WRITE_SHORT(m_spriteTexture); + MESSAGE_END(); +#endif + DoSparks( vecSrc, vecDest ); +} + +void CLightning::RandomArea( void ) +{ + int iLoops = 0; + + for (iLoops = 0; iLoops < 10; iLoops++) + { + Vector vecSrc = pev->origin; + + Vector vecDir1 = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + vecDir1 = vecDir1.Normalize(); + TraceResult tr1; + UTIL_TraceLine( vecSrc, vecSrc + vecDir1 * m_radius, ignore_monsters, ENT(pev), &tr1 ); + + if (tr1.flFraction == 1.0) + continue; + + Vector vecDir2; + do { + vecDir2 = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + } while (DotProduct(vecDir1, vecDir2 ) > 0); + vecDir2 = vecDir2.Normalize(); + TraceResult tr2; + UTIL_TraceLine( vecSrc, vecSrc + vecDir2 * m_radius, ignore_monsters, ENT(pev), &tr2 ); + + if (tr2.flFraction == 1.0) + continue; + + if ((tr1.vecEndPos - tr2.vecEndPos).Length() < m_radius * 0.1) + continue; + + UTIL_TraceLine( tr1.vecEndPos, tr2.vecEndPos, ignore_monsters, ENT(pev), &tr2 ); + + if (tr2.flFraction != 1.0) + continue; + + Zap( tr1.vecEndPos, tr2.vecEndPos ); + + break; + } +} + + +void CLightning::RandomPoint( Vector &vecSrc ) +{ + int iLoops = 0; + + for (iLoops = 0; iLoops < 10; iLoops++) + { + Vector vecDir1 = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + vecDir1 = vecDir1.Normalize(); + TraceResult tr1; + UTIL_TraceLine( vecSrc, vecSrc + vecDir1 * m_radius, ignore_monsters, ENT(pev), &tr1 ); + + if ((tr1.vecEndPos - vecSrc).Length() < m_radius * 0.1) + continue; + + if (tr1.flFraction == 1.0) + continue; + + Zap( vecSrc, tr1.vecEndPos ); + break; + } +} + + + +void CLightning::BeamUpdateVars( void ) +{ + int beamType; + int pointStart, pointEnd; + + edict_t *pStart = FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(m_iszStartEntity) ); + edict_t *pEnd = FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(m_iszEndEntity) ); + pointStart = IsPointEntity( CBaseEntity::Instance(pStart) ); + pointEnd = IsPointEntity( CBaseEntity::Instance(pEnd) ); + + pev->skin = 0; + pev->sequence = 0; + pev->rendermode = 0; + pev->flags |= FL_CUSTOMENTITY; + pev->model = m_iszSpriteName; + SetTexture( m_spriteTexture ); + + beamType = BEAM_ENTS; + if ( pointStart || pointEnd ) + { + if ( !pointStart ) // One point entity must be in pStart + { + edict_t *pTemp; + // Swap start & end + pTemp = pStart; + pStart = pEnd; + pEnd = pTemp; + int swap = pointStart; + pointStart = pointEnd; + pointEnd = swap; + } + if ( !pointEnd ) + beamType = BEAM_ENTPOINT; + else + beamType = BEAM_POINTS; + } + + SetType( beamType ); + if ( beamType == BEAM_POINTS || beamType == BEAM_ENTPOINT || beamType == BEAM_HOSE ) + { + SetStartPos( pStart->v.origin ); + if ( beamType == BEAM_POINTS || beamType == BEAM_HOSE ) + SetEndPos( pEnd->v.origin ); + else + SetEndEntity( ENTINDEX(pEnd) ); + } + else + { + SetStartEntity( ENTINDEX(pStart) ); + SetEndEntity( ENTINDEX(pEnd) ); + } + + RelinkBeam(); + + SetWidth( m_boltWidth ); + SetNoise( m_noiseAmplitude ); + SetFrame( m_frameStart ); + SetScrollRate( m_speed ); + if ( pev->spawnflags & SF_BEAM_SHADEIN ) + SetFlags( BEAM_FSHADEIN ); + else if ( pev->spawnflags & SF_BEAM_SHADEOUT ) + SetFlags( BEAM_FSHADEOUT ); +} + + +LINK_ENTITY_TO_CLASS( env_laser, CLaser ); + +TYPEDESCRIPTION CLaser::m_SaveData[] = +{ + DEFINE_FIELD( CLaser, m_pSprite, FIELD_CLASSPTR ), + DEFINE_FIELD( CLaser, m_iszSpriteName, FIELD_STRING ), + DEFINE_FIELD( CLaser, m_firePosition, FIELD_POSITION_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CLaser, CBeam ); + +void CLaser::Spawn( void ) +{ + if ( FStringNull( pev->model ) ) + { + SetThink( &CBaseEntity::SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; // Remove model & collisions + Precache( ); + + SetThink( &CLaser::StrikeThink ); + pev->flags |= FL_CUSTOMENTITY; + + PointsInit( pev->origin, pev->origin ); + + if ( !m_pSprite && m_iszSpriteName ) + m_pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteName), pev->origin, TRUE ); + else + m_pSprite = NULL; + + if ( m_pSprite ) + m_pSprite->SetTransparency( kRenderGlow, static_cast(pev->rendercolor.x), static_cast(pev->rendercolor.y), static_cast(pev->rendercolor.z), static_cast(pev->renderamt), static_cast(pev->renderfx) ); + + if ( pev->targetname && !(pev->spawnflags & SF_BEAM_STARTON) ) + TurnOff(); + else + TurnOn(); +} + +void CLaser::Precache( void ) +{ + pev->modelindex = PRECACHE_MODEL( (char *)STRING(pev->model) ); + if ( m_iszSpriteName ) + PRECACHE_MODEL( (char *)STRING(m_iszSpriteName) ); +} + + +void CLaser::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "LaserTarget")) + { + pev->message = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "width")) + { + SetWidth( atoi(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "NoiseAmplitude")) + { + SetNoise( atoi(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "TextureScroll")) + { + SetScrollRate( atoi(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "texture")) + { + pev->model = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "EndSprite")) + { + m_iszSpriteName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "framestart")) + { + pev->frame = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damage")) + { + pev->dmg = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBeam::KeyValue( pkvd ); +} + + +int CLaser::IsOn( void ) +{ + if (pev->effects & EF_NODRAW) + return 0; + return 1; +} + + +void CLaser::TurnOff( void ) +{ + pev->effects |= EF_NODRAW; + pev->nextthink = 0; + if ( m_pSprite ) + m_pSprite->TurnOff(); +} + + +void CLaser::TurnOn( void ) +{ + pev->effects &= ~EF_NODRAW; + if ( m_pSprite ) + m_pSprite->TurnOn(); + pev->dmgtime = gpGlobals->time; + pev->nextthink = gpGlobals->time; +} + + +void CLaser::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int active = IsOn(); + + if ( !ShouldToggle( useType, active ) ) + return; + if ( active ) + { + TurnOff(); + } + else + { + TurnOn(); + } +} + + +void CLaser::FireAtPoint( TraceResult &tr ) +{ + SetEndPos( tr.vecEndPos ); + if ( m_pSprite ) + UTIL_SetOrigin( m_pSprite->pev, tr.vecEndPos ); + + BeamDamage( &tr ); + DoSparks( GetStartPos(), tr.vecEndPos ); +} + +void CLaser::StrikeThink( void ) +{ + CBaseEntity *pEnd = RandomTargetname( STRING(pev->message) ); + + if ( pEnd ) + m_firePosition = pEnd->pev->origin; + + TraceResult tr; + + UTIL_TraceLine( pev->origin, m_firePosition, dont_ignore_monsters, NULL, &tr ); + FireAtPoint( tr ); + pev->nextthink = gpGlobals->time + 0.1; +} + + + +class CGlow : public CPointEntity +{ +public: + void Spawn( void ); + void Think( void ); + void Animate( float frames ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + float m_lastTime; + float m_maxFrame; +}; + +LINK_ENTITY_TO_CLASS( env_glow, CGlow ); + +TYPEDESCRIPTION CGlow::m_SaveData[] = +{ + DEFINE_FIELD( CGlow, m_lastTime, FIELD_TIME ), + DEFINE_FIELD( CGlow, m_maxFrame, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CGlow, CPointEntity ); + +void CGlow::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; + + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; + if ( m_maxFrame > 1.0 && pev->framerate != 0 ) + pev->nextthink = gpGlobals->time + 0.1; + + m_lastTime = gpGlobals->time; +} + + +void CGlow::Think( void ) +{ + Animate( pev->framerate * (gpGlobals->time - m_lastTime) ); + + pev->nextthink = gpGlobals->time + 0.1; + m_lastTime = gpGlobals->time; +} + + +void CGlow::Animate( float frames ) +{ + if ( m_maxFrame > 0 ) + pev->frame = fmod( pev->frame + frames, m_maxFrame ); +} + + +LINK_ENTITY_TO_CLASS( env_sprite, CSprite ); + +TYPEDESCRIPTION CSprite::m_SaveData[] = +{ + DEFINE_FIELD( CSprite, m_lastTime, FIELD_TIME ), + DEFINE_FIELD( CSprite, m_maxFrame, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CSprite, CPointEntity ); + +void CSprite::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; + + Precache(); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; + if ( pev->targetname && !(pev->spawnflags & SF_SPRITE_STARTON) ) + TurnOff(); + else + TurnOn(); + + // Worldcraft only sets y rotation, copy to Z + if ( pev->angles.y != 0 && pev->angles.z == 0 ) + { + pev->angles.z = pev->angles.y; + pev->angles.y = 0; + } +} + + +void CSprite::Precache( void ) +{ + PRECACHE_MODEL( (char *)STRING(pev->model) ); + + // Reset attachment after save/restore + if ( pev->aiment ) + SetAttachment( pev->aiment, pev->body ); + else + { + // Clear attachment + pev->skin = 0; + pev->body = 0; + } +} + + +void CSprite::SpriteInit( const char *pSpriteName, const Vector &origin ) +{ + pev->model = MAKE_STRING(pSpriteName); + pev->origin = origin; + Spawn(); +} + +CSprite *CSprite::SpriteCreate( const char *pSpriteName, const Vector &origin, BOOL animate ) +{ + CSprite *pSprite = GetClassPtr( (CSprite *)NULL ); + pSprite->SpriteInit( pSpriteName, origin ); + pSprite->pev->classname = MAKE_STRING("env_sprite"); + pSprite->pev->solid = SOLID_NOT; + pSprite->pev->movetype = MOVETYPE_NOCLIP; + if ( animate ) + pSprite->TurnOn(); + + return pSprite; +} + + +void CSprite::AnimateThink( void ) +{ + Animate( pev->framerate * (gpGlobals->time - m_lastTime) ); + + pev->nextthink = gpGlobals->time + 0.1; + m_lastTime = gpGlobals->time; +} + +void CSprite::AnimateUntilDead( void ) +{ + if ( gpGlobals->time > pev->dmgtime ) + UTIL_Remove(this); + else + { + AnimateThink(); + pev->nextthink = gpGlobals->time; + } +} + +void CSprite::Expand( float scaleSpeed, float fadeSpeed ) +{ + pev->speed = scaleSpeed; + pev->health = fadeSpeed; + SetThink( &CSprite::ExpandThink ); + + pev->nextthink = gpGlobals->time; + m_lastTime = gpGlobals->time; +} + + +void CSprite::ExpandThink( void ) +{ + float frametime = gpGlobals->time - m_lastTime; + pev->scale += pev->speed * frametime; + pev->renderamt -= pev->health * frametime; + if ( pev->renderamt <= 0 ) + { + pev->renderamt = 0; + UTIL_Remove( this ); + } + else + { + pev->nextthink = gpGlobals->time + 0.1; + m_lastTime = gpGlobals->time; + } +} + + +void CSprite::Animate( float frames ) +{ + pev->frame += frames; + if ( pev->frame > m_maxFrame ) + { + if ( pev->spawnflags & SF_SPRITE_ONCE ) + { + TurnOff(); + } + else + { + if ( m_maxFrame > 0 ) + pev->frame = fmod( pev->frame, m_maxFrame ); + } + } +} + + +void CSprite::TurnOff( void ) +{ + pev->effects = EF_NODRAW; + pev->nextthink = 0; +} + + +void CSprite::TurnOn( void ) +{ + pev->effects = 0; + if ( (pev->framerate && m_maxFrame > 1.0) || (pev->spawnflags & SF_SPRITE_ONCE) ) + { + SetThink( &CSprite::AnimateThink ); + pev->nextthink = gpGlobals->time; + m_lastTime = gpGlobals->time; + } + pev->frame = 0; +} + + +void CSprite::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int on = pev->effects != EF_NODRAW; + if ( ShouldToggle( useType, on ) ) + { + if ( on ) + { + TurnOff(); + } + else + { + TurnOn(); + } + } +} + + +class CGibShooter : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT ShootThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual CGib *CreateGib( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_iGibs; + int m_iGibCapacity; + int m_iGibMaterial; + int m_iGibModelIndex; + float m_flGibVelocity; + float m_flVariance; + float m_flGibLife; +}; + +TYPEDESCRIPTION CGibShooter::m_SaveData[] = +{ + DEFINE_FIELD( CGibShooter, m_iGibs, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_iGibCapacity, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_iGibMaterial, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_iGibModelIndex, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_flGibVelocity, FIELD_FLOAT ), + DEFINE_FIELD( CGibShooter, m_flVariance, FIELD_FLOAT ), + DEFINE_FIELD( CGibShooter, m_flGibLife, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CGibShooter, CBaseDelay ); +LINK_ENTITY_TO_CLASS( gibshooter, CGibShooter ); + + +void CGibShooter :: Precache ( void ) +{ + if ( g_Language == LANGUAGE_GERMAN ) + { + m_iGibModelIndex = PRECACHE_MODEL ("models/germanygibs.mdl"); + } + else + { + m_iGibModelIndex = PRECACHE_MODEL ("models/hgibs.mdl"); + } +} + + +void CGibShooter::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iGibs")) + { + m_iGibs = m_iGibCapacity = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flVelocity")) + { + m_flGibVelocity = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flVariance")) + { + m_flVariance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flGibLife")) + { + m_flGibLife = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + { + CBaseDelay::KeyValue( pkvd ); + } +} + +void CGibShooter::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink( &CGibShooter::ShootThink ); + pev->nextthink = gpGlobals->time; +} + +void CGibShooter::Spawn( void ) +{ + Precache(); + + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; + + if ( m_flDelay == 0 ) + { + m_flDelay = 0.1; + } + + if ( m_flGibLife == 0 ) + { + m_flGibLife = 25; + } + + SetMovedir ( pev ); + pev->body = MODEL_FRAMES( m_iGibModelIndex ); +} + + +CGib *CGibShooter :: CreateGib ( void ) +{ + if ( CVAR_GET_FLOAT("violence_hgibs") == 0 ) + return NULL; + + CGib *pGib = GetClassPtr( (CGib *)NULL ); + pGib->Spawn( "models/hgibs.mdl" ); + pGib->m_bloodColor = BLOOD_COLOR_RED; + + if ( pev->body <= 1 ) + { + ALERT ( at_aiconsole, "GibShooter Body is <= 1!\n" ); + } + + pGib->pev->body = RANDOM_LONG ( 1, pev->body - 1 );// avoid throwing random amounts of the 0th gib. (skull). + + return pGib; +} + + +void CGibShooter :: ShootThink ( void ) +{ + pev->nextthink = gpGlobals->time + m_flDelay; + + Vector vecShootDir; + + vecShootDir = pev->movedir; + + vecShootDir = vecShootDir + gpGlobals->v_right * RANDOM_FLOAT( -1, 1) * m_flVariance;; + vecShootDir = vecShootDir + gpGlobals->v_forward * RANDOM_FLOAT( -1, 1) * m_flVariance;; + vecShootDir = vecShootDir + gpGlobals->v_up * RANDOM_FLOAT( -1, 1) * m_flVariance;; + + vecShootDir = vecShootDir.Normalize(); + CGib *pGib = CreateGib(); + + if ( pGib ) + { + pGib->pev->origin = pev->origin; + pGib->pev->velocity = vecShootDir * m_flGibVelocity; + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + + float thinkTime = pGib->pev->nextthink - gpGlobals->time; + + pGib->m_lifeTime = (m_flGibLife * RANDOM_FLOAT( 0.95, 1.05 )); // +/- 5% + if ( pGib->m_lifeTime < thinkTime ) + { + pGib->pev->nextthink = gpGlobals->time + pGib->m_lifeTime; + pGib->m_lifeTime = 0; + } + + } + + if ( --m_iGibs <= 0 ) + { + if ( pev->spawnflags & SF_GIBSHOOTER_REPEATABLE ) + { + m_iGibs = m_iGibCapacity; + SetThink ( NULL ); + pev->nextthink = gpGlobals->time; + } + else + { + SetThink ( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time; + } + } +} + + +class CEnvShooter : public CGibShooter +{ + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + CGib *CreateGib( void ); +}; + +LINK_ENTITY_TO_CLASS( env_shooter, CEnvShooter ); + +void CEnvShooter :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "shootmodel")) + { + pev->model = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "shootsounds")) + { + int iNoise = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + switch( iNoise ) + { + case 0: + m_iGibMaterial = matGlass; + break; + case 1: + m_iGibMaterial = matWood; + break; + case 2: + m_iGibMaterial = matMetal; + break; + case 3: + m_iGibMaterial = matFlesh; + break; + case 4: + m_iGibMaterial = matRocks; + break; + + default: + case -1: + m_iGibMaterial = matNone; + break; + } + } + else + { + CGibShooter::KeyValue( pkvd ); + } +} + + +void CEnvShooter :: Precache ( void ) +{ + m_iGibModelIndex = PRECACHE_MODEL( (char *)STRING(pev->model) ); + CBreakable::MaterialSoundPrecache( (Materials)m_iGibMaterial ); +} + + +CGib *CEnvShooter :: CreateGib ( void ) +{ + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + pGib->Spawn( STRING(pev->model) ); + + int bodyPart = 0; + + if ( pev->body > 1 ) + bodyPart = RANDOM_LONG( 0, pev->body-1 ); + + pGib->pev->body = bodyPart; + pGib->m_bloodColor = DONT_BLEED; + pGib->m_material = m_iGibMaterial; + + pGib->pev->rendermode = pev->rendermode; + pGib->pev->renderamt = pev->renderamt; + pGib->pev->rendercolor = pev->rendercolor; + pGib->pev->renderfx = pev->renderfx; + pGib->pev->scale = pev->scale; + pGib->pev->skin = pev->skin; + + return pGib; +} + + + + +class CTestEffect : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + // void KeyValue( KeyValueData *pkvd ); + void EXPORT TestThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int m_iLoop; + int m_iBeam; + CBeam *m_pBeam[24]; + float m_flBeamTime[24]; + float m_flStartTime; +}; + + +LINK_ENTITY_TO_CLASS( test_effect, CTestEffect ); + +void CTestEffect::Spawn( void ) +{ + Precache( ); +} + +void CTestEffect::Precache( void ) +{ + PRECACHE_MODEL( "sprites/lgtning.spr" ); +} + +void CTestEffect::TestThink( void ) +{ + int i; + float t = (gpGlobals->time - m_flStartTime); + + if (m_iBeam < 24) + { + CBeam *pbeam = CBeam::BeamCreate( "sprites/lgtning.spr", 100 ); + + TraceResult tr; + + Vector vecSrc = pev->origin; + Vector vecDir = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + vecDir = vecDir.Normalize(); + UTIL_TraceLine( vecSrc, vecSrc + vecDir * 128, ignore_monsters, ENT(pev), &tr); + + pbeam->PointsInit( vecSrc, tr.vecEndPos ); + // pbeam->SetColor( 80, 100, 255 ); + pbeam->SetColor( 255, 180, 100 ); + pbeam->SetWidth( 100 ); + pbeam->SetScrollRate( 12 ); + + m_flBeamTime[m_iBeam] = gpGlobals->time; + m_pBeam[m_iBeam] = pbeam; + m_iBeam++; + +#if 0 + Vector vecMid = (vecSrc + tr.vecEndPos) * 0.5; + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE(TE_DLIGHT); + WRITE_COORD(vecMid.x); // X + WRITE_COORD(vecMid.y); // Y + WRITE_COORD(vecMid.z); // Z + WRITE_BYTE( 20 ); // radius * 0.1 + WRITE_BYTE( 255 ); // r + WRITE_BYTE( 180 ); // g + WRITE_BYTE( 100 ); // b + WRITE_BYTE( 20 ); // time * 10 + WRITE_BYTE( 0 ); // decay * 0.1 + MESSAGE_END( ); +#endif + } + + if (t < 3.0) + { + for (i = 0; i < m_iBeam; i++) + { + t = (gpGlobals->time - m_flBeamTime[i]) / ( 3 + m_flStartTime - m_flBeamTime[i]); + m_pBeam[i]->SetBrightness( static_cast(255 * t) ); + // m_pBeam[i]->SetScrollRate( 20 * t ); + } + pev->nextthink = gpGlobals->time + 0.1; + } + else + { + for (i = 0; i < m_iBeam; i++) + { + UTIL_Remove( m_pBeam[i] ); + } + m_flStartTime = gpGlobals->time; + m_iBeam = 0; + // pev->nextthink = gpGlobals->time; + SetThink( NULL ); + } +} + + +void CTestEffect::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink( &CTestEffect::TestThink ); + pev->nextthink = gpGlobals->time + 0.1; + m_flStartTime = gpGlobals->time; +} + + + +// Blood effects +class CBlood : public CPointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline int Color( void ) { return pev->impulse; } + inline float BloodAmount( void ) { return pev->dmg; } + + inline void SetColor( int color ) { pev->impulse = color; } + inline void SetBloodAmount( float amount ) { pev->dmg = amount; } + + Vector Direction( void ); + Vector BloodPosition( CBaseEntity *pActivator ); + +private: +}; + +LINK_ENTITY_TO_CLASS( env_blood, CBlood ); + + + +#define SF_BLOOD_RANDOM 0x0001 +#define SF_BLOOD_STREAM 0x0002 +#define SF_BLOOD_PLAYER 0x0004 +#define SF_BLOOD_DECAL 0x0008 + +void CBlood::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; + SetMovedir( pev ); +} + + +void CBlood::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "color")) + { + int color = atoi(pkvd->szValue); + switch( color ) + { + case 1: + SetColor( BLOOD_COLOR_YELLOW ); + break; + default: + SetColor( BLOOD_COLOR_RED ); + break; + } + + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "amount")) + { + SetBloodAmount( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +Vector CBlood::Direction( void ) +{ + if ( pev->spawnflags & SF_BLOOD_RANDOM ) + return UTIL_RandomBloodVector(); + + return pev->movedir; +} + + +Vector CBlood::BloodPosition( CBaseEntity *pActivator ) +{ + if ( pev->spawnflags & SF_BLOOD_PLAYER ) + { + edict_t *pPlayer; + + if ( pActivator && pActivator->IsPlayer() ) + { + pPlayer = pActivator->edict(); + } + else + pPlayer = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + if ( pPlayer ) + return (pPlayer->v.origin + pPlayer->v.view_ofs) + Vector( RANDOM_FLOAT(-10,10), RANDOM_FLOAT(-10,10), RANDOM_FLOAT(-10,10) ); + } + + return pev->origin; +} + + +void CBlood::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->spawnflags & SF_BLOOD_STREAM ) + UTIL_BloodStream( BloodPosition(pActivator), Direction(), (Color() == BLOOD_COLOR_RED) ? 70 : Color(), static_cast(BloodAmount()) ); + else + UTIL_BloodDrips( BloodPosition(pActivator), Direction(), Color(), static_cast(BloodAmount()) ); + + if ( pev->spawnflags & SF_BLOOD_DECAL ) + { + Vector forward = Direction(); + Vector start = BloodPosition( pActivator ); + TraceResult tr; + + UTIL_TraceLine( start, start + forward * BloodAmount() * 2, ignore_monsters, NULL, &tr ); + if ( tr.flFraction != 1.0 ) + UTIL_BloodDecalTrace( &tr, Color() ); + } +} + + + +// Screen shake +class CShake : public CPointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline float Amplitude( void ) { return pev->scale; } + inline float Frequency( void ) { return pev->dmg_save; } + inline float Duration( void ) { return pev->dmg_take; } + inline float Radius( void ) { return pev->dmg; } + + inline void SetAmplitude( float amplitude ) { pev->scale = amplitude; } + inline void SetFrequency( float frequency ) { pev->dmg_save = frequency; } + inline void SetDuration( float duration ) { pev->dmg_take = duration; } + inline void SetRadius( float radius ) { pev->dmg = radius; } +private: +}; + +LINK_ENTITY_TO_CLASS( env_shake, CShake ); + +// pev->scale is amplitude +// pev->dmg_save is frequency +// pev->dmg_take is duration +// pev->dmg is radius +// radius of 0 means all players +// NOTE: UTIL_ScreenShake() will only shake players who are on the ground + +#define SF_SHAKE_EVERYONE 0x0001 // Don't check radius +// UNDONE: These don't work yet +#define SF_SHAKE_DISRUPT 0x0002 // Disrupt controls +#define SF_SHAKE_INAIR 0x0004 // Shake players in air + +void CShake::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; + + if ( pev->spawnflags & SF_SHAKE_EVERYONE ) + pev->dmg = 0; +} + + +void CShake::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "amplitude")) + { + SetAmplitude( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "frequency")) + { + SetFrequency( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "duration")) + { + SetDuration( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "radius")) + { + SetRadius( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CShake::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + UTIL_ScreenShake( pev->origin, Amplitude(), Frequency(), Duration(), Radius() ); +} + + +class CFade : public CPointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline float Duration( void ) { return pev->dmg_take; } + inline float HoldTime( void ) { return pev->dmg_save; } + + inline void SetDuration( float duration ) { pev->dmg_take = duration; } + inline void SetHoldTime( float hold ) { pev->dmg_save = hold; } +private: +}; + +LINK_ENTITY_TO_CLASS( env_fade, CFade ); + +// pev->dmg_take is duration +// pev->dmg_save is hold duration +#define SF_FADE_IN 0x0001 // Fade in, not out +#define SF_FADE_MODULATE 0x0002 // Modulate, don't blend +#define SF_FADE_ONLYONE 0x0004 + +void CFade::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; +} + + +void CFade::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "duration")) + { + SetDuration( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "holdtime")) + { + SetHoldTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CFade::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int fadeFlags = 0; + + if ( !(pev->spawnflags & SF_FADE_IN) ) + fadeFlags |= FFADE_OUT; + + if ( pev->spawnflags & SF_FADE_MODULATE ) + fadeFlags |= FFADE_MODULATE; + + if ( pev->spawnflags & SF_FADE_ONLYONE ) + { + if ( pActivator->IsNetClient() ) + { + UTIL_ScreenFade( pActivator, pev->rendercolor, Duration(), HoldTime(), static_cast(pev->renderamt), fadeFlags ); + } + } + else + { + UTIL_ScreenFadeAll( pev->rendercolor, Duration(), HoldTime(), static_cast(pev->renderamt), fadeFlags ); + } + SUB_UseTargets( this, USE_TOGGLE, 0 ); +} + + +class CMessage : public CPointEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); +private: +}; + +LINK_ENTITY_TO_CLASS( env_message, CMessage ); + + +void CMessage::Spawn( void ) +{ + Precache(); + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + switch( pev->impulse ) + { + case 1: // Medium radius + pev->speed = ATTN_STATIC; + break; + + case 2: // Large radius + pev->speed = ATTN_NORM; + break; + + case 3: //EVERYWHERE + pev->speed = ATTN_NONE; + break; + + default: + case 0: // Small radius + pev->speed = ATTN_IDLE; + break; + } + pev->impulse = 0; + + // No volume, use normal + if ( pev->scale <= 0 ) + pev->scale = 1.0; +} + + +void CMessage::Precache( void ) +{ + if ( pev->noise ) + PRECACHE_SOUND( (char *)STRING(pev->noise) ); +} + +void CMessage::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "messagesound")) + { + pev->noise = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "messagevolume")) + { + pev->scale = atof(pkvd->szValue) * 0.1; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "messageattenuation")) + { + pev->impulse = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CMessage::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBaseEntity *pPlayer = NULL; + + if ( pev->spawnflags & SF_MESSAGE_ALL ) + UTIL_ShowMessageAll( STRING(pev->message) ); + else + { + if ( pActivator && pActivator->IsPlayer() ) + pPlayer = pActivator; + else + { + pPlayer = CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) ); + } + if ( pPlayer ) + UTIL_ShowMessage( STRING(pev->message), pPlayer ); + } + if ( pev->noise ) + { + EMIT_SOUND( edict(), CHAN_BODY, STRING(pev->noise), pev->scale, pev->speed ); + } + if ( pev->spawnflags & SF_MESSAGE_ONCE ) + UTIL_Remove( this ); + + SUB_UseTargets( this, USE_TOGGLE, 0 ); +} + + + +//========================================================= +// FunnelEffect +//========================================================= +class CEnvFunnel : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int m_iSprite; // Don't save, precache +}; + +void CEnvFunnel :: Precache ( void ) +{ + m_iSprite = PRECACHE_MODEL ( "sprites/flare6.spr" ); +} + +LINK_ENTITY_TO_CLASS( env_funnel, CEnvFunnel ); + +void CEnvFunnel::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_LARGEFUNNEL ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( m_iSprite ); + + if ( pev->spawnflags & SF_FUNNEL_REVERSE )// funnel flows in reverse? + { + WRITE_SHORT( 1 ); + } + else + { + WRITE_SHORT( 0 ); + } + + + MESSAGE_END(); + + SetThink( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time; +} + +void CEnvFunnel::Spawn( void ) +{ + Precache(); + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; +} + +//========================================================= +// Beverage Dispenser +// overloaded pev->frags, is now a flag for whether or not a can is stuck in the dispenser. +// overloaded pev->health, is now how many cans remain in the machine. +//========================================================= +class CEnvBeverage : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; + +void CEnvBeverage :: Precache ( void ) +{ + PRECACHE_MODEL( "models/can.mdl" ); + PRECACHE_SOUND( "weapons/g_bounce3.wav" ); +} + +LINK_ENTITY_TO_CLASS( env_beverage, CEnvBeverage ); + +void CEnvBeverage::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->frags != 0 || pev->health <= 0 ) + { + // no more cans while one is waiting in the dispenser, or if I'm out of cans. + return; + } + + CBaseEntity *pCan = CBaseEntity::Create( "item_sodacan", pev->origin, pev->angles, edict() ); + + if ( pev->skin == 6 ) + { + // random + pCan->pev->skin = RANDOM_LONG( 0, 5 ); + } + else + { + pCan->pev->skin = pev->skin; + } + + pev->frags = 1; + pev->health--; + + //SetThink (SUB_Remove); + //pev->nextthink = gpGlobals->time; +} + +void CEnvBeverage::Spawn( void ) +{ + Precache(); + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; + pev->frags = 0; + + if ( pev->health == 0 ) + { + pev->health = 10; + } +} + +//========================================================= +// Soda can +//========================================================= +class CItemSoda : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT CanThink ( void ); + void EXPORT CanTouch ( CBaseEntity *pOther ); +}; + +void CItemSoda :: Precache ( void ) +{ +} + +LINK_ENTITY_TO_CLASS( item_sodacan, CItemSoda ); + +void CItemSoda::Spawn( void ) +{ + Precache(); + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_TOSS; + + SET_MODEL ( ENT(pev), "models/can.mdl" ); + UTIL_SetSize ( pev, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ) ); + + SetThink (&CItemSoda::CanThink); + pev->nextthink = gpGlobals->time + 0.5; +} + +void CItemSoda::CanThink ( void ) +{ + EMIT_SOUND (ENT(pev), CHAN_WEAPON, "weapons/g_bounce3.wav", 1, ATTN_NORM ); + + pev->solid = SOLID_TRIGGER; + UTIL_SetSize ( pev, Vector ( -8, -8, 0 ), Vector ( 8, 8, 8 ) ); + SetThink ( NULL ); + SetTouch ( &CItemSoda::CanTouch ); +} + +void CItemSoda::CanTouch ( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + { + return; + } + + // spoit sound here + + pOther->TakeHealth( 1, DMG_GENERIC );// a bit of health. + + if ( !FNullEnt( pev->owner ) ) + { + // tell the machine the can was taken + pev->owner->v.frags = 0; + } + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = EF_NODRAW; + SetTouch ( NULL ); + SetThink ( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time; +} diff --git a/hlsdk/ricochet/dlls/effects.h b/hlsdk/ricochet/dlls/effects.h new file mode 100644 index 00000000..bcfc78ae --- /dev/null +++ b/hlsdk/ricochet/dlls/effects.h @@ -0,0 +1,209 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef EFFECTS_H +#define EFFECTS_H + +#define SF_BEAM_STARTON 0x0001 +#define SF_BEAM_TOGGLE 0x0002 +#define SF_BEAM_RANDOM 0x0004 +#define SF_BEAM_RING 0x0008 +#define SF_BEAM_SPARKSTART 0x0010 +#define SF_BEAM_SPARKEND 0x0020 +#define SF_BEAM_DECALS 0x0040 +#define SF_BEAM_SHADEIN 0x0080 +#define SF_BEAM_SHADEOUT 0x0100 +#define SF_BEAM_TEMPORARY 0x8000 + +#define SF_SPRITE_STARTON 0x0001 +#define SF_SPRITE_ONCE 0x0002 +#define SF_SPRITE_TEMPORARY 0x8000 + +class CSprite : public CPointEntity +{ +public: + void Spawn( void ); + void Precache( void ); + + int ObjectCaps( void ) + { + int flags = 0; + if ( pev->spawnflags & SF_SPRITE_TEMPORARY ) + flags = FCAP_DONT_SAVE; + return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | flags; + } + void EXPORT AnimateThink( void ); + void EXPORT ExpandThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Animate( float frames ); + void Expand( float scaleSpeed, float fadeSpeed ); + void SpriteInit( const char *pSpriteName, const Vector &origin ); + + inline void SetAttachment( edict_t *pEntity, int attachment ) + { + if ( pEntity ) + { + pev->skin = ENTINDEX(pEntity); + pev->body = attachment; + pev->aiment = pEntity; + pev->movetype = MOVETYPE_FOLLOW; + } + } + void TurnOff( void ); + void TurnOn( void ); + inline float Frames( void ) { return m_maxFrame; } + inline void SetTransparency( int rendermode, int r, int g, int b, int a, int fx ) + { + pev->rendermode = rendermode; + pev->rendercolor.x = r; + pev->rendercolor.y = g; + pev->rendercolor.z = b; + pev->renderamt = a; + pev->renderfx = fx; + } + inline void SetTexture( int spriteIndex ) { pev->modelindex = spriteIndex; } + inline void SetScale( float scale ) { pev->scale = scale; } + inline void SetColor( int r, int g, int b ) { pev->rendercolor.x = r; pev->rendercolor.y = g; pev->rendercolor.z = b; } + inline void SetBrightness( int brightness ) { pev->renderamt = brightness; } + + inline void AnimateAndDie( float framerate ) + { + SetThink(&CSprite::AnimateUntilDead); + pev->framerate = framerate; + pev->dmgtime = gpGlobals->time + (m_maxFrame / framerate); + pev->nextthink = gpGlobals->time; + } + + void EXPORT AnimateUntilDead( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + static CSprite *SpriteCreate( const char *pSpriteName, const Vector &origin, BOOL animate ); + +private: + + float m_lastTime; + float m_maxFrame; +}; + + +class CBeam : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + int ObjectCaps( void ) + { + int flags = 0; + if ( pev->spawnflags & SF_BEAM_TEMPORARY ) + flags = FCAP_DONT_SAVE; + return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | flags; + } + + void EXPORT TriggerTouch( CBaseEntity *pOther ); + + // These functions are here to show the way beams are encoded as entities. + // Encoding beams as entities simplifies their management in the client/server architecture + inline void SetType( int type ) { pev->rendermode = (pev->rendermode & 0xF0) | (type&0x0F); } + inline void SetFlags( int flags ) { pev->rendermode = (pev->rendermode & 0x0F) | (flags&0xF0); } + inline void SetStartPos( const Vector& pos ) { pev->origin = pos; } + inline void SetEndPos( const Vector& pos ) { pev->angles = pos; } + void SetStartEntity( int entityIndex ); + void SetEndEntity( int entityIndex ); + + inline void SetStartAttachment( int attachment ) { pev->sequence = (pev->sequence & 0x0FFF) | ((attachment&0xF)<<12); } + inline void SetEndAttachment( int attachment ) { pev->skin = (pev->skin & 0x0FFF) | ((attachment&0xF)<<12); } + + inline void SetTexture( int spriteIndex ) { pev->modelindex = spriteIndex; } + inline void SetWidth( int width ) { pev->scale = width; } + inline void SetNoise( int amplitude ) { pev->body = amplitude; } + inline void SetColor( int r, int g, int b ) { pev->rendercolor.x = r; pev->rendercolor.y = g; pev->rendercolor.z = b; } + inline void SetBrightness( int brightness ) { pev->renderamt = brightness; } + inline void SetFrame( float frame ) { pev->frame = frame; } + inline void SetScrollRate( int speed ) { pev->animtime = speed; } + + inline int GetType( void ) { return pev->rendermode & 0x0F; } + inline int GetFlags( void ) { return pev->rendermode & 0xF0; } + inline int GetStartEntity( void ) { return pev->sequence & 0xFFF; } + inline int GetEndEntity( void ) { return pev->skin & 0xFFF; } + + const Vector &GetStartPos( void ); + const Vector &GetEndPos( void ); + + Vector Center( void ) { return (GetStartPos() + GetEndPos()) * 0.5; }; // center point of beam + + inline int GetTexture( void ) { return pev->modelindex; } + inline int GetWidth( void ) { return static_cast(pev->scale); } + inline int GetNoise( void ) { return pev->body; } + // inline void GetColor( int r, int g, int b ) { pev->rendercolor.x = r; pev->rendercolor.y = g; pev->rendercolor.z = b; } + inline int GetBrightness( void ) { return static_cast(pev->renderamt); } + inline int GetFrame( void ) { return static_cast(pev->frame); } + inline int GetScrollRate( void ) { return static_cast(pev->animtime); } + + // Call after you change start/end positions + void RelinkBeam( void ); +// void SetObjectCollisionBox( void ); + + void DoSparks( const Vector &start, const Vector &end ); + CBaseEntity *RandomTargetname( const char *szName ); + void BeamDamage( TraceResult *ptr ); + // Init after BeamCreate() + void BeamInit( const char *pSpriteName, int width ); + void PointsInit( const Vector &start, const Vector &end ); + void PointEntInit( const Vector &start, int endIndex ); + void EntsInit( int startIndex, int endIndex ); + void HoseInit( const Vector &start, const Vector &direction ); + + static CBeam *BeamCreate( const char *pSpriteName, int width ); + + inline void LiveForTime( float time ) { SetThink(&CBaseEntity::SUB_Remove); pev->nextthink = gpGlobals->time + time; } + inline void BeamDamageInstant( TraceResult *ptr, float damage ) + { + pev->dmg = damage; + pev->dmgtime = gpGlobals->time - 1; + BeamDamage(ptr); + } +}; + + +#define SF_MESSAGE_ONCE 0x0001 // Fade in, not out +#define SF_MESSAGE_ALL 0x0002 // Send to all clients + + +class CLaser : public CBeam +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + void TurnOn( void ); + void TurnOff( void ); + int IsOn( void ); + + void FireAtPoint( TraceResult &point ); + + void EXPORT StrikeThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + CSprite *m_pSprite; + int m_iszSpriteName; + Vector m_firePosition; +}; + +#endif //EFFECTS_H diff --git a/hlsdk/ricochet/dlls/enginecallback.h b/hlsdk/ricochet/dlls/enginecallback.h new file mode 100644 index 00000000..d74ec5b5 --- /dev/null +++ b/hlsdk/ricochet/dlls/enginecallback.h @@ -0,0 +1,165 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef ENGINECALLBACK_H +#define ENGINECALLBACK_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +#include "event_flags.h" + +// Fix warning in MSVC8 +#undef SERVER_EXECUTE + +// Must be provided by user of this code +extern enginefuncs_t g_engfuncs; + +// The actual engine callbacks +#define GETPLAYERUSERID (*g_engfuncs.pfnGetPlayerUserId) +#define PRECACHE_MODEL (*g_engfuncs.pfnPrecacheModel) +#define PRECACHE_SOUND (*g_engfuncs.pfnPrecacheSound) +#define PRECACHE_GENERIC (*g_engfuncs.pfnPrecacheGeneric) +#define SET_MODEL (*g_engfuncs.pfnSetModel) +#define MODEL_INDEX (*g_engfuncs.pfnModelIndex) +#define MODEL_FRAMES (*g_engfuncs.pfnModelFrames) +#define SET_SIZE (*g_engfuncs.pfnSetSize) +#define CHANGE_LEVEL (*g_engfuncs.pfnChangeLevel) +#define GET_SPAWN_PARMS (*g_engfuncs.pfnGetSpawnParms) +#define SAVE_SPAWN_PARMS (*g_engfuncs.pfnSaveSpawnParms) +#define VEC_TO_YAW (*g_engfuncs.pfnVecToYaw) +#define VEC_TO_ANGLES (*g_engfuncs.pfnVecToAngles) +#define MOVE_TO_ORIGIN (*g_engfuncs.pfnMoveToOrigin) +#define oldCHANGE_YAW (*g_engfuncs.pfnChangeYaw) +#define CHANGE_PITCH (*g_engfuncs.pfnChangePitch) +#define MAKE_VECTORS (*g_engfuncs.pfnMakeVectors) +#define CREATE_ENTITY (*g_engfuncs.pfnCreateEntity) +#define REMOVE_ENTITY (*g_engfuncs.pfnRemoveEntity) +#define CREATE_NAMED_ENTITY (*g_engfuncs.pfnCreateNamedEntity) +#define MAKE_STATIC (*g_engfuncs.pfnMakeStatic) +#define ENT_IS_ON_FLOOR (*g_engfuncs.pfnEntIsOnFloor) +#define DROP_TO_FLOOR (*g_engfuncs.pfnDropToFloor) +#define WALK_MOVE (*g_engfuncs.pfnWalkMove) +#define SET_ORIGIN (*g_engfuncs.pfnSetOrigin) +#define EMIT_SOUND_DYN2 (*g_engfuncs.pfnEmitSound) +#define BUILD_SOUND_MSG (*g_engfuncs.pfnBuildSoundMsg) +#define TRACE_LINE (*g_engfuncs.pfnTraceLine) +#define TRACE_TOSS (*g_engfuncs.pfnTraceToss) +#define TRACE_MONSTER_HULL (*g_engfuncs.pfnTraceMonsterHull) +#define TRACE_HULL (*g_engfuncs.pfnTraceHull) +#define GET_AIM_VECTOR (*g_engfuncs.pfnGetAimVector) +#define SERVER_COMMAND (*g_engfuncs.pfnServerCommand) +#define SERVER_EXECUTE (*g_engfuncs.pfnServerExecute) +#define CLIENT_COMMAND (*g_engfuncs.pfnClientCommand) +#define PARTICLE_EFFECT (*g_engfuncs.pfnParticleEffect) +#define LIGHT_STYLE (*g_engfuncs.pfnLightStyle) +#define DECAL_INDEX (*g_engfuncs.pfnDecalIndex) +#define POINT_CONTENTS (*g_engfuncs.pfnPointContents) +#define CRC32_INIT (*g_engfuncs.pfnCRC32_Init) +#define CRC32_PROCESS_BUFFER (*g_engfuncs.pfnCRC32_ProcessBuffer) +#define CRC32_PROCESS_BYTE (*g_engfuncs.pfnCRC32_ProcessByte) +#define CRC32_FINAL (*g_engfuncs.pfnCRC32_Final) +#define RANDOM_LONG (*g_engfuncs.pfnRandomLong) +#define RANDOM_FLOAT (*g_engfuncs.pfnRandomFloat) +#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); +} +#define MESSAGE_END (*g_engfuncs.pfnMessageEnd) +#define WRITE_BYTE (*g_engfuncs.pfnWriteByte) +#define WRITE_CHAR (*g_engfuncs.pfnWriteChar) +#define WRITE_SHORT (*g_engfuncs.pfnWriteShort) +#define WRITE_LONG (*g_engfuncs.pfnWriteLong) +#define WRITE_ANGLE (*g_engfuncs.pfnWriteAngle) +#define WRITE_COORD (*g_engfuncs.pfnWriteCoord) +#define WRITE_STRING (*g_engfuncs.pfnWriteString) +#define WRITE_ENTITY (*g_engfuncs.pfnWriteEntity) +#define CVAR_REGISTER (*g_engfuncs.pfnCVarRegister) +#define CVAR_GET_FLOAT (*g_engfuncs.pfnCVarGetFloat) +#define CVAR_GET_STRING (*g_engfuncs.pfnCVarGetString) +#define CVAR_SET_FLOAT (*g_engfuncs.pfnCVarSetFloat) +#define CVAR_SET_STRING (*g_engfuncs.pfnCVarSetString) +#define CVAR_GET_POINTER (*g_engfuncs.pfnCVarGetPointer) +#define ALERT (*g_engfuncs.pfnAlertMessage) +#define ENGINE_FPRINTF (*g_engfuncs.pfnEngineFprintf) +#define ALLOC_PRIVATE (*g_engfuncs.pfnPvAllocEntPrivateData) +inline void *GET_PRIVATE( edict_t *pent ) +{ + if ( pent ) + return pent->pvPrivateData; + return NULL; +} + +#define FREE_PRIVATE (*g_engfuncs.pfnFreeEntPrivateData) +//#define STRING (*g_engfuncs.pfnSzFromIndex) +#define ALLOC_STRING (*g_engfuncs.pfnAllocString) +#define FIND_ENTITY_BY_STRING (*g_engfuncs.pfnFindEntityByString) +#define GETENTITYILLUM (*g_engfuncs.pfnGetEntityIllum) +#define FIND_ENTITY_IN_SPHERE (*g_engfuncs.pfnFindEntityInSphere) +#define FIND_CLIENT_IN_PVS (*g_engfuncs.pfnFindClientInPVS) +#define EMIT_AMBIENT_SOUND (*g_engfuncs.pfnEmitAmbientSound) +#define GET_MODEL_PTR (*g_engfuncs.pfnGetModelPtr) +#define REG_USER_MSG (*g_engfuncs.pfnRegUserMsg) +#define GET_BONE_POSITION (*g_engfuncs.pfnGetBonePosition) +#define FUNCTION_FROM_NAME (*g_engfuncs.pfnFunctionFromName) +#define NAME_FOR_FUNCTION (*g_engfuncs.pfnNameForFunction) +#define TRACE_TEXTURE (*g_engfuncs.pfnTraceTexture) +#define CLIENT_PRINTF (*g_engfuncs.pfnClientPrintf) +#define CMD_ARGS (*g_engfuncs.pfnCmd_Args) +#define CMD_ARGC (*g_engfuncs.pfnCmd_Argc) +#define CMD_ARGV (*g_engfuncs.pfnCmd_Argv) +#define GET_ATTACHMENT (*g_engfuncs.pfnGetAttachment) +#define SET_VIEW (*g_engfuncs.pfnSetView) +#define SET_CROSSHAIRANGLE (*g_engfuncs.pfnCrosshairAngle) +#define LOAD_FILE_FOR_ME (*g_engfuncs.pfnLoadFileForMe) +#define FREE_FILE (*g_engfuncs.pfnFreeFile) +#define COMPARE_FILE_TIME (*g_engfuncs.pfnCompareFileTime) +#define GET_GAME_DIR (*g_engfuncs.pfnGetGameDir) +#define IS_MAP_VALID (*g_engfuncs.pfnIsMapValid) +#define NUMBER_OF_ENTITIES (*g_engfuncs.pfnNumberOfEntities) +#define IS_DEDICATED_SERVER (*g_engfuncs.pfnIsDedicatedServer) + +#define PRECACHE_EVENT (*g_engfuncs.pfnPrecacheEvent) +#define PLAYBACK_EVENT_FULL (*g_engfuncs.pfnPlaybackEvent) + +#define ENGINE_SET_PVS (*g_engfuncs.pfnSetFatPVS) +#define ENGINE_SET_PAS (*g_engfuncs.pfnSetFatPAS) + +#define ENGINE_CHECK_VISIBILITY (*g_engfuncs.pfnCheckVisibility) + +#define DELTA_SET ( *g_engfuncs.pfnDeltaSetField ) +#define DELTA_UNSET ( *g_engfuncs.pfnDeltaUnsetField ) +#define DELTA_ADDENCODER ( *g_engfuncs.pfnDeltaAddEncoder ) +#define ENGINE_CURRENT_PLAYER ( *g_engfuncs.pfnGetCurrentPlayer ) + +#define ENGINE_CANSKIP ( *g_engfuncs.pfnCanSkipPlayer ) + +#define DELTA_FINDFIELD ( *g_engfuncs.pfnDeltaFindField ) +#define DELTA_SETBYINDEX ( *g_engfuncs.pfnDeltaSetFieldByIndex ) +#define DELTA_UNSETBYINDEX ( *g_engfuncs.pfnDeltaUnsetFieldByIndex ) + +#define ENGINE_GETPHYSINFO ( *g_engfuncs.pfnGetPhysicsInfoString ) + +#define ENGINE_SETGROUPMASK ( *g_engfuncs.pfnSetGroupMask ) + +#define ENGINE_INSTANCE_BASELINE ( *g_engfuncs.pfnCreateInstancedBaseline ) + +#define ENGINE_FORCE_UNMODIFIED ( *g_engfuncs.pfnForceUnmodified ) + +#define PLAYER_CNX_STATS ( *g_engfuncs.pfnGetPlayerStats ) + +#endif //ENGINECALLBACK_H diff --git a/hlsdk/ricochet/dlls/explode.cpp b/hlsdk/ricochet/dlls/explode.cpp new file mode 100644 index 00000000..4e5f1c15 --- /dev/null +++ b/hlsdk/ricochet/dlls/explode.cpp @@ -0,0 +1,273 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== explode.cpp ======================================================== + + Explosion-related code + +*/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "decals.h" +#include "explode.h" + +// Spark Shower +class CShower : public CBaseEntity +{ + void Spawn( void ); + void Think( void ); + void Touch( CBaseEntity *pOther ); + int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +}; + +LINK_ENTITY_TO_CLASS( spark_shower, CShower ); + +void CShower::Spawn( void ) +{ + pev->velocity = RANDOM_FLOAT( 200, 300 ) * pev->angles; + pev->velocity.x += RANDOM_FLOAT(-100.f,100.f); + pev->velocity.y += RANDOM_FLOAT(-100.f,100.f); + if ( pev->velocity.z >= 0 ) + pev->velocity.z += 200; + else + pev->velocity.z -= 200; + pev->movetype = MOVETYPE_BOUNCE; + pev->gravity = 0.5; + pev->nextthink = gpGlobals->time + 0.1; + pev->solid = SOLID_NOT; + SET_MODEL( edict(), "models/grenade.mdl"); // Need a model, just use the grenade, we don't draw it anyway + UTIL_SetSize(pev, g_vecZero, g_vecZero ); + pev->effects |= EF_NODRAW; + pev->speed = RANDOM_FLOAT( 0.5, 1.5 ); + + pev->angles = g_vecZero; +} + + +void CShower::Think( void ) +{ + UTIL_Sparks( pev->origin ); + + pev->speed -= 0.1; + if ( pev->speed > 0 ) + pev->nextthink = gpGlobals->time + 0.1; + else + UTIL_Remove( this ); + pev->flags &= ~FL_ONGROUND; +} + +void CShower::Touch( CBaseEntity *pOther ) +{ + if ( pev->flags & FL_ONGROUND ) + pev->velocity = pev->velocity * 0.1; + else + pev->velocity = pev->velocity * 0.6; + + if ( (pev->velocity.x*pev->velocity.x+pev->velocity.y*pev->velocity.y) < 10.0 ) + pev->speed = 0; +} + +class CEnvExplosion : public CBaseMonster +{ +public: + void Spawn( ); + void EXPORT Smoke ( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_iMagnitude;// how large is the fireball? how much damage? + int m_spriteScale; // what's the exact fireball sprite scale? +}; + +TYPEDESCRIPTION CEnvExplosion::m_SaveData[] = +{ + DEFINE_FIELD( CEnvExplosion, m_iMagnitude, FIELD_INTEGER ), + DEFINE_FIELD( CEnvExplosion, m_spriteScale, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CEnvExplosion, CBaseMonster ); +LINK_ENTITY_TO_CLASS( env_explosion, CEnvExplosion ); + +void CEnvExplosion::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "iMagnitude")) + { + m_iMagnitude = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CEnvExplosion::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; + + pev->movetype = MOVETYPE_NONE; + /* + if ( m_iMagnitude > 250 ) + { + m_iMagnitude = 250; + } + */ + + float flSpriteScale; + flSpriteScale = ( m_iMagnitude - 50) * 0.6; + + /* + if ( flSpriteScale > 50 ) + { + flSpriteScale = 50; + } + */ + if ( flSpriteScale < 10 ) + { + flSpriteScale = 10; + } + + m_spriteScale = (int)flSpriteScale; +} + +void CEnvExplosion::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + TraceResult tr; + + pev->model = iStringNull;//invisible + pev->solid = SOLID_NOT;// intangible + + 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); + + // Pull out of the wall a bit + if ( tr.flFraction != 1.0 ) + { + pev->origin = tr.vecEndPos + (tr.vecPlaneNormal * (m_iMagnitude - 24) * 0.6); + } + else + { + pev->origin = pev->origin; + } + + // draw decal + if (! ( pev->spawnflags & SF_ENVEXPLOSION_NODECAL)) + { + if ( RANDOM_FLOAT( 0 , 1 ) < 0.5 ) + { + UTIL_DecalTrace( &tr, DECAL_SCORCH1 ); + } + else + { + UTIL_DecalTrace( &tr, DECAL_SCORCH2 ); + } + } + + // draw fireball + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NOFIREBALL ) ) + { + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( (BYTE)m_spriteScale ); // scale * 10 + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + } + else + { + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( 0 ); // no sprite + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + } + + // do damage + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NODAMAGE ) ) + { + RadiusDamage ( pev, pev, m_iMagnitude, CLASS_NONE, DMG_BLAST ); + } + + SetThink( &CEnvExplosion::Smoke ); + pev->nextthink = gpGlobals->time + 0.3; + + // draw sparks + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NOSPARKS ) ) + { + int sparkCount = RANDOM_LONG(0,3); + + for ( int i = 0; i < sparkCount; i++ ) + { + Create( "spark_shower", pev->origin, tr.vecPlaneNormal, NULL ); + } + } +} + +void CEnvExplosion::Smoke( void ) +{ + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NOSMOKE ) ) + { + MESSAGE_BEGIN( MSG_PAS, 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)m_spriteScale ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + } + + if ( !(pev->spawnflags & SF_ENVEXPLOSION_REPEATABLE) ) + { + UTIL_Remove( this ); + } +} + + +// HACKHACK -- create one of these and fake a keyvalue to get the right explosion setup +void ExplosionCreate( const Vector ¢er, const Vector &angles, edict_t *pOwner, int magnitude, BOOL doDamage ) +{ + KeyValueData kvd; + char buf[128]; + + CBaseEntity *pExplosion = CBaseEntity::Create( "env_explosion", center, angles, pOwner ); + sprintf( buf, "%3d", magnitude ); + kvd.szKeyName = "iMagnitude"; + kvd.szValue = buf; + pExplosion->KeyValue( &kvd ); + if ( !doDamage ) + pExplosion->pev->spawnflags |= SF_ENVEXPLOSION_NODAMAGE; + + pExplosion->Spawn(); + pExplosion->Use( NULL, NULL, USE_TOGGLE, 0 ); +} diff --git a/hlsdk/ricochet/dlls/explode.h b/hlsdk/ricochet/dlls/explode.h new file mode 100644 index 00000000..202a581c --- /dev/null +++ b/hlsdk/ricochet/dlls/explode.h @@ -0,0 +1,32 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +#ifndef EXPLODE_H +#define EXPLODE_H + + +#define SF_ENVEXPLOSION_NODAMAGE ( 1 << 0 ) // when set, ENV_EXPLOSION will not actually inflict damage +#define SF_ENVEXPLOSION_REPEATABLE ( 1 << 1 ) // can this entity be refired? +#define SF_ENVEXPLOSION_NOFIREBALL ( 1 << 2 ) // don't draw the fireball +#define SF_ENVEXPLOSION_NOSMOKE ( 1 << 3 ) // don't draw the smoke +#define SF_ENVEXPLOSION_NODECAL ( 1 << 4 ) // don't make a scorch mark +#define SF_ENVEXPLOSION_NOSPARKS ( 1 << 5 ) // don't make a scorch mark + +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/hlsdk/ricochet/dlls/extdll.h b/hlsdk/ricochet/dlls/extdll.h new file mode 100644 index 00000000..1b5eaf4f --- /dev/null +++ b/hlsdk/ricochet/dlls/extdll.h @@ -0,0 +1,107 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef EXTDLL_H +#define EXTDLL_H + + +// +// Global header file for extension DLLs +// + +// Allow "DEBUG" in addition to default "_DEBUG" +#ifdef _DEBUG + #define DEBUG 1 +#endif + +// Silence certain warnings +#ifdef _MSC_VER + #pragma warning(disable : 4244) // int or float down-conversion + #pragma warning(disable : 4305) // int or float data truncation + #pragma warning(disable : 4201) // nameless struct/union + #pragma warning(disable : 4514) // unreferenced inline function removed + #pragma warning(disable : 4100) // unreferenced formal parameter + + #if _MSC_VER >= 1400 + #ifndef _CRT_SECURE_NO_DEPRECATE + #define _CRT_SECURE_NO_DEPRECATE + #endif + + #pragma warning(disable: 4996) // deprecated functions + #endif +#endif + +#ifdef _WIN32 +// Prevent tons of unused windows definitions +#define WIN32_LEAN_AND_MEAN +#define NOWINRES +#define NOSERVICE +#define NOMCX +#define NOIME +#include "windows.h" + +#else // _WIN32 +#define FALSE 0 +#define TRUE (!FALSE) + +typedef unsigned long ULONG; +typedef unsigned char BYTE; +typedef int BOOL; + +#define MAX_PATH PATH_MAX + +#include +#include +#include +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +#define itoa(a,b,c) sprintf(b, "%d", a) +#define _snprintf snprintf +#define _vsnprintf vsnprintf +#endif //_WIN32 + +// Misc C-runtime library headers +#include "stdio.h" +#include "stdlib.h" +#include "math.h" + +// Header file containing definition of globalvars_t and entvars_t +typedef int func_t; // +typedef int string_t; // from engine's pr_comp.h; +typedef float vec_t; // needed before including progdefs.h + +// Vector class +#include "vector.h" + +// Defining it as a (bogus) struct helps enforce type-checking +#define vec3_t Vector + +// Shared engine/DLL constants +#include "const.h" +#include "progdefs.h" +#include "edict.h" + +// Shared header describing protocol between engine and DLLs +#include "eiface.h" + +// Shared header between the client DLL and the game DLLs +#include "cdll_dll.h" + +#endif //EXTDLL_H diff --git a/hlsdk/ricochet/dlls/func_break.cpp b/hlsdk/ricochet/dlls/func_break.cpp new file mode 100644 index 00000000..c3179f7b --- /dev/null +++ b/hlsdk/ricochet/dlls/func_break.cpp @@ -0,0 +1,1005 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== bmodels.cpp ======================================================== + + spawn, think, and use functions for entities that use brush models + +*/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "func_break.h" +#include "decals.h" +#include "explode.h" + +extern DLL_GLOBAL Vector g_vecAttackDir; + +// =================== FUNC_Breakable ============================================== + +// Just add more items to the bottom of this array and they will automagically be supported +// This is done instead of just a classname in the FGD so we can control which entities can +// be spawned, and still remain fairly flexible +const char *CBreakable::pSpawnObjects[] = +{ + NULL, // 0 + "item_battery", // 1 + "item_healthkit", // 2 + "weapon_9mmhandgun",// 3 + "ammo_9mmclip", // 4 + "weapon_9mmAR", // 5 + "ammo_9mmAR", // 6 + "ammo_ARgrenades", // 7 + "weapon_shotgun", // 8 + "ammo_buckshot", // 9 + "weapon_crossbow", // 10 + "ammo_crossbow", // 11 + "weapon_357", // 12 + "ammo_357", // 13 + "weapon_rpg", // 14 + "ammo_rpgclip", // 15 + "ammo_gaussclip", // 16 + "weapon_handgrenade",// 17 + "weapon_tripmine", // 18 + "weapon_satchel", // 19 + "weapon_snark", // 20 + "weapon_hornetgun", // 21 +}; + +void CBreakable::KeyValue( KeyValueData* pkvd ) +{ + // UNDONE_WC: explicitly ignoring these fields, but they shouldn't be in the map file! + if (FStrEq(pkvd->szKeyName, "explosion")) + { + if (!stricmp(pkvd->szValue, "directed")) + m_Explosion = expDirected; + else if (!stricmp(pkvd->szValue, "random")) + m_Explosion = expRandom; + else + m_Explosion = expRandom; + + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "material")) + { + int i = atoi( pkvd->szValue); + + // 0:glass, 1:metal, 2:flesh, 3:wood + + if ((i < 0) || (i >= matLastMaterial)) + m_Material = matWood; + else + m_Material = (Materials)i; + + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "deadmodel")) + { + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "shards")) + { +// m_iShards = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "gibmodel") ) + { + m_iszGibModel = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spawnobject") ) + { + int object = atoi( pkvd->szValue ); + if ( object > 0 && object < static_cast(ARRAYSIZE(pSpawnObjects)) ) + m_iszSpawnObject = MAKE_STRING( pSpawnObjects[object] ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "explodemagnitude") ) + { + ExplosionSetMagnitude( atoi( pkvd->szValue ) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "lip") ) + pkvd->fHandled = TRUE; + else + CBaseDelay::KeyValue( pkvd ); +} + + +// +// func_breakable - bmodel that breaks into pieces after taking damage +// +LINK_ENTITY_TO_CLASS( func_breakable, CBreakable ); +TYPEDESCRIPTION CBreakable::m_SaveData[] = +{ + DEFINE_FIELD( CBreakable, m_Material, FIELD_INTEGER ), + DEFINE_FIELD( CBreakable, m_Explosion, FIELD_INTEGER ), + +// Don't need to save/restore these because we precache after restore +// DEFINE_FIELD( CBreakable, m_idShard, FIELD_INTEGER ), + + DEFINE_FIELD( CBreakable, m_angle, FIELD_FLOAT ), + DEFINE_FIELD( CBreakable, m_iszGibModel, FIELD_STRING ), + DEFINE_FIELD( CBreakable, m_iszSpawnObject, FIELD_STRING ), + + // Explosion magnitude is stored in pev->impulse +}; + +IMPLEMENT_SAVERESTORE( CBreakable, CBaseEntity ); + +void CBreakable::Spawn( void ) +{ + Precache( ); + + if ( FBitSet( pev->spawnflags, SF_BREAK_TRIGGER_ONLY ) ) + pev->takedamage = DAMAGE_NO; + else + pev->takedamage = DAMAGE_YES; + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + m_angle = pev->angles.y; + pev->angles.y = 0; + + + SET_MODEL(ENT(pev), STRING(pev->model) );//set size and link into world. + + SetTouch( &CBreakable::BreakTouch ); + if ( FBitSet( pev->spawnflags, SF_BREAK_TRIGGER_ONLY ) ) // Only break on trigger + SetTouch( NULL ); + + // Flag unbreakable glass as "worldbrush" so it will block ALL tracelines + if ( !IsBreakable() && pev->rendermode != kRenderNormal ) + pev->flags |= FL_WORLDBRUSH; +} + + +const char *CBreakable::pSoundsWood[] = +{ + "debris/wood1.wav", + "debris/wood2.wav", + "debris/wood3.wav", +}; + +const char *CBreakable::pSoundsFlesh[] = +{ + "debris/flesh1.wav", + "debris/flesh2.wav", + "debris/flesh3.wav", + "debris/flesh5.wav", + "debris/flesh6.wav", + "debris/flesh7.wav", +}; + +const char *CBreakable::pSoundsMetal[] = +{ + "debris/metal1.wav", + "debris/metal2.wav", + "debris/metal3.wav", +}; + +const char *CBreakable::pSoundsConcrete[] = +{ + "debris/concrete1.wav", + "debris/concrete2.wav", + "debris/concrete3.wav", +}; + + +const char *CBreakable::pSoundsGlass[] = +{ + "debris/glass1.wav", + "debris/glass2.wav", + "debris/glass3.wav", +}; + +const char **CBreakable::MaterialSoundList( Materials precacheMaterial, int &soundCount ) +{ + const char **pSoundList = NULL; + + switch ( precacheMaterial ) + { + case matWood: + pSoundList = pSoundsWood; + soundCount = ARRAYSIZE(pSoundsWood); + break; + case matFlesh: + pSoundList = pSoundsFlesh; + soundCount = ARRAYSIZE(pSoundsFlesh); + break; + case matComputer: + case matUnbreakableGlass: + case matGlass: + pSoundList = pSoundsGlass; + soundCount = ARRAYSIZE(pSoundsGlass); + break; + + case matMetal: + pSoundList = pSoundsMetal; + soundCount = ARRAYSIZE(pSoundsMetal); + break; + + case matCinderBlock: + case matRocks: + pSoundList = pSoundsConcrete; + soundCount = ARRAYSIZE(pSoundsConcrete); + break; + + + case matCeilingTile: + case matNone: + default: + soundCount = 0; + break; + } + + return pSoundList; +} + +void CBreakable::MaterialSoundPrecache( Materials precacheMaterial ) +{ + const char **pSoundList; + int i, soundCount = 0; + + pSoundList = MaterialSoundList( precacheMaterial, soundCount ); + + for ( i = 0; i < soundCount; i++ ) + { + PRECACHE_SOUND( (char *)pSoundList[i] ); + } +} + +void CBreakable::MaterialSoundRandom( edict_t *pEdict, Materials soundMaterial, float volume ) +{ + const char **pSoundList; + int soundCount = 0; + + pSoundList = MaterialSoundList( soundMaterial, soundCount ); + + if ( soundCount ) + EMIT_SOUND( pEdict, CHAN_BODY, pSoundList[ RANDOM_LONG(0,soundCount-1) ], volume, 1.0 ); +} + + +void CBreakable::Precache( void ) +{ + const char *pGibName = NULL; + + switch (m_Material) + { + case matWood: + pGibName = "models/woodgibs.mdl"; + + PRECACHE_SOUND("debris/bustcrate1.wav"); + PRECACHE_SOUND("debris/bustcrate2.wav"); + break; + case matFlesh: + pGibName = "models/fleshgibs.mdl"; + + PRECACHE_SOUND("debris/bustflesh1.wav"); + PRECACHE_SOUND("debris/bustflesh2.wav"); + break; + case matComputer: + PRECACHE_SOUND("buttons/spark5.wav"); + PRECACHE_SOUND("buttons/spark6.wav"); + pGibName = "models/computergibs.mdl"; + + PRECACHE_SOUND("debris/bustmetal1.wav"); + PRECACHE_SOUND("debris/bustmetal2.wav"); + break; + + case matUnbreakableGlass: + case matGlass: + pGibName = "models/glassgibs.mdl"; + + PRECACHE_SOUND("debris/bustglass1.wav"); + PRECACHE_SOUND("debris/bustglass2.wav"); + break; + case matMetal: + pGibName = "models/metalplategibs.mdl"; + + PRECACHE_SOUND("debris/bustmetal1.wav"); + PRECACHE_SOUND("debris/bustmetal2.wav"); + break; + case matCinderBlock: + pGibName = "models/cindergibs.mdl"; + + PRECACHE_SOUND("debris/bustconcrete1.wav"); + PRECACHE_SOUND("debris/bustconcrete2.wav"); + break; + case matRocks: + pGibName = "models/rockgibs.mdl"; + + PRECACHE_SOUND("debris/bustconcrete1.wav"); + PRECACHE_SOUND("debris/bustconcrete2.wav"); + break; + case matCeilingTile: + pGibName = "models/ceilinggibs.mdl"; + + PRECACHE_SOUND ("debris/bustceiling.wav"); + break; + default: + break; + } + MaterialSoundPrecache( m_Material ); + if ( m_iszGibModel ) + pGibName = STRING(m_iszGibModel); + + m_idShard = PRECACHE_MODEL( (char *)pGibName ); + + // Precache the spawn item's data + if ( m_iszSpawnObject ) + UTIL_PrecacheOther( (char *)STRING( m_iszSpawnObject ) ); +} + +// play shard sound when func_breakable takes damage. +// the more damage, the louder the shard sound. + + +void CBreakable::DamageSound( void ) +{ + int pitch; + float fvol; + char *rgpsz[6]; + int i = 0; + int material = m_Material; + +// if (RANDOM_LONG(0,1)) +// return; + + if (RANDOM_LONG(0,2)) + pitch = PITCH_NORM; + else + pitch = 95 + RANDOM_LONG(0,34); + + fvol = RANDOM_FLOAT(0.75, 1.0); + + if (material == matComputer && RANDOM_LONG(0,1)) + material = matMetal; + + switch (material) + { + case matComputer: + case matGlass: + case matUnbreakableGlass: + rgpsz[0] = "debris/glass1.wav"; + rgpsz[1] = "debris/glass2.wav"; + rgpsz[2] = "debris/glass3.wav"; + i = 3; + break; + + case matWood: + rgpsz[0] = "debris/wood1.wav"; + rgpsz[1] = "debris/wood2.wav"; + rgpsz[2] = "debris/wood3.wav"; + i = 3; + break; + + case matMetal: + rgpsz[0] = "debris/metal1.wav"; + rgpsz[1] = "debris/metal3.wav"; + rgpsz[2] = "debris/metal2.wav"; + i = 2; + break; + + case matFlesh: + rgpsz[0] = "debris/flesh1.wav"; + rgpsz[1] = "debris/flesh2.wav"; + rgpsz[2] = "debris/flesh3.wav"; + rgpsz[3] = "debris/flesh5.wav"; + rgpsz[4] = "debris/flesh6.wav"; + rgpsz[5] = "debris/flesh7.wav"; + i = 6; + break; + + case matRocks: + case matCinderBlock: + rgpsz[0] = "debris/concrete1.wav"; + rgpsz[1] = "debris/concrete2.wav"; + rgpsz[2] = "debris/concrete3.wav"; + i = 3; + break; + + case matCeilingTile: + // UNDONE: no ceiling tile shard sound yet + i = 0; + break; + } + + if (i) + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, rgpsz[RANDOM_LONG(0,i-1)], fvol, ATTN_NORM, 0, pitch); +} + +void CBreakable::BreakTouch( CBaseEntity *pOther ) +{ + float flDamage; + entvars_t* pevToucher = pOther->pev; + + // only players can break these right now + if ( !pOther->IsPlayer() || !IsBreakable() ) + { + return; + } + + if ( FBitSet ( pev->spawnflags, SF_BREAK_TOUCH ) ) + {// can be broken when run into + flDamage = pevToucher->velocity.Length() * 0.01; + + if (flDamage >= pev->health) + { + SetTouch( NULL ); + TakeDamage(pevToucher, pevToucher, flDamage, DMG_CRUSH); + + // do a little damage to player if we broke glass or computer + pOther->TakeDamage( pev, pev, flDamage/4, DMG_SLASH ); + } + } + + if ( FBitSet ( pev->spawnflags, SF_BREAK_PRESSURE ) && pevToucher->absmin.z >= pev->maxs.z - 2 ) + {// can be broken when stood upon + + // play creaking sound here. + DamageSound(); + + SetThink ( &CBreakable::Die ); + SetTouch( NULL ); + + if ( m_flDelay == 0 ) + {// !!!BUGBUG - why doesn't zero delay work? + m_flDelay = 0.1; + } + + pev->nextthink = pev->ltime + m_flDelay; + + } + +} + + +// +// Smash the our breakable object +// + +// Break when triggered +void CBreakable::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( IsBreakable() ) + { + pev->angles.y = m_angle; + UTIL_MakeVectors(pev->angles); + g_vecAttackDir = gpGlobals->v_forward; + + Die(); + } +} + + +void CBreakable::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + // random spark if this is a 'computer' object + if (RANDOM_LONG(0,1) ) + { + switch( m_Material ) + { + case matComputer: + { + UTIL_Sparks( ptr->vecEndPos ); + + float flVolume = RANDOM_FLOAT ( 0.7 , 1.0 );//random volume range + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark5.wav", flVolume, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark6.wav", flVolume, ATTN_NORM); break; + } + } + break; + + case matUnbreakableGlass: + UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT(0.5,1.5) ); + break; + + default: + break; + } + } + + CBaseDelay::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + +//========================================================= +// Special takedamage for func_breakable. Allows us to make +// exceptions that are breakable-specific +// bitsDamageType indicates the type of damage sustained ie: DMG_CRUSH +//========================================================= +int CBreakable :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + Vector vecTemp; + + // if Attacker == Inflictor, the attack was a melee or other instant-hit attack. + // (that is, no actual entity projectile was involved in the attack so use the shooter's origin). + if ( pevAttacker == pevInflictor ) + { + vecTemp = pevInflictor->origin - ( pev->absmin + ( pev->size * 0.5 ) ); + + // if a client hit the breakable with a crowbar, and breakable is crowbar-sensitive, break it now. + if ( FBitSet ( pevAttacker->flags, FL_CLIENT ) && + FBitSet ( pev->spawnflags, SF_BREAK_CROWBAR ) && (bitsDamageType & DMG_CLUB)) + flDamage = pev->health; + } + else + // an actual missile was involved. + { + vecTemp = pevInflictor->origin - ( pev->absmin + ( pev->size * 0.5 ) ); + } + + if (!IsBreakable()) + return 0; + + // Breakables take double damage from the crowbar + if ( bitsDamageType & DMG_CLUB ) + flDamage *= 2; + + // Boxes / glass / etc. don't take much poison damage, just the impact of the dart - consider that 10% + if ( bitsDamageType & DMG_POISON ) + flDamage *= 0.1; + +// this global is still used for glass and other non-monster killables, along with decals. + g_vecAttackDir = vecTemp.Normalize(); + +// do the damage + pev->health -= flDamage; + if (pev->health <= 0) + { + Killed( pevAttacker, GIB_NORMAL ); + Die(); + return 0; + } + + // Make a shard noise each time func breakable is hit. + // Don't play shard noise if cbreakable actually died. + + DamageSound(); + + return 1; +} + + +void CBreakable::Die( void ) +{ + Vector vecSpot;// shard origin + Vector vecVelocity;// shard velocity + char cFlag = 0; + int pitch; + float fvol; + + pitch = 95 + RANDOM_LONG(0,29); + + if (pitch > 97 && pitch < 103) + pitch = 100; + + // The more negative pev->health, the louder + // the sound should be. + + fvol = RANDOM_FLOAT(0.85, 1.0) + (abs(static_cast(pev->health)) / 100.0); + + if (fvol > 1.0) + fvol = 1.0; + + + switch (m_Material) + { + case matGlass: + switch ( RANDOM_LONG(0,1) ) + { + 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); + break; + } + cFlag = BREAK_GLASS; + break; + + case matWood: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustcrate1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustcrate2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_WOOD; + break; + + case matComputer: + case matMetal: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustmetal1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustmetal2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_METAL; + break; + + case matFlesh: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustflesh1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustflesh2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_FLESH; + break; + + case matRocks: + case matCinderBlock: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustconcrete1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustconcrete2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_CONCRETE; + break; + + case matCeilingTile: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustceiling.wav", fvol, ATTN_NORM, 0, pitch); + break; + + default: + break; + } + + + if (m_Explosion == expDirected) + vecVelocity = g_vecAttackDir * 200; + else + { + vecVelocity.x = 0; + vecVelocity.y = 0; + vecVelocity.z = 0; + } + + vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z ); + + // size + WRITE_COORD( pev->size.x); + WRITE_COORD( pev->size.y); + WRITE_COORD( pev->size.z); + + // velocity + WRITE_COORD( vecVelocity.x ); + WRITE_COORD( vecVelocity.y ); + WRITE_COORD( vecVelocity.z ); + + // randomization + WRITE_BYTE( 10 ); + + // Model + WRITE_SHORT( m_idShard ); //model id# + + // # of shards + WRITE_BYTE( 0 ); // let client decide + + // duration + WRITE_BYTE( 25 );// 2.5 seconds + + // flags + WRITE_BYTE( cFlag ); + MESSAGE_END(); + + float size = pev->size.x; + if ( size < pev->size.y ) + size = pev->size.y; + if ( size < pev->size.z ) + size = pev->size.z; + + // !!! HACK This should work! + // Build a box above the entity that looks like an 8 pixel high sheet + Vector mins = pev->absmin; + Vector maxs = pev->absmax; + mins.z = pev->absmax.z; + maxs.z += 8; + + // BUGBUG -- can only find 256 entities on a breakable -- should be enough + CBaseEntity *pList[256]; + int count = UTIL_EntitiesInBox( pList, 256, mins, maxs, FL_ONGROUND ); + if ( count ) + { + for ( int i = 0; i < count; i++ ) + { + ClearBits( pList[i]->pev->flags, FL_ONGROUND ); + pList[i]->pev->groundentity = NULL; + } + } + + // Don't fire something that could fire myself + pev->targetname = 0; + + pev->solid = SOLID_NOT; + // Fire targets on break + SUB_UseTargets( NULL, USE_TOGGLE, 0 ); + + SetThink( &CBaseEntity::SUB_Remove ); + pev->nextthink = pev->ltime + 0.1; + if ( m_iszSpawnObject ) + CBaseEntity::Create( (char *)STRING(m_iszSpawnObject), VecBModelOrigin(pev), pev->angles, edict() ); + + + if ( Explodable() ) + { + ExplosionCreate( Center(), pev->angles, edict(), ExplosionMagnitude(), TRUE ); + } +} + + + +BOOL CBreakable :: IsBreakable( void ) +{ + return m_Material != matUnbreakableGlass; +} + + +int CBreakable :: DamageDecal( int bitsDamageType ) +{ + if ( m_Material == matGlass ) + return DECAL_GLASSBREAK1 + RANDOM_LONG(0,2); + + if ( m_Material == matUnbreakableGlass ) + return DECAL_BPROOF1; + + return CBaseEntity::DamageDecal( bitsDamageType ); +} + + +class CPushable : public CBreakable +{ +public: + void Spawn ( void ); + void Precache( void ); + void Touch ( CBaseEntity *pOther ); + void Move( CBaseEntity *pMover, int push ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT StopSound( void ); +// virtual void SetActivator( CBaseEntity *pActivator ) { m_pPusher = pActivator; } + + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_CONTINUOUS_USE; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + inline float MaxSpeed( void ) { return m_maxSpeed; } + + // breakables use an overridden takedamage + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + + static TYPEDESCRIPTION m_SaveData[]; + + static char *m_soundNames[3]; + int m_lastSound; // no need to save/restore, just keeps the same sound from playing twice in a row + float m_maxSpeed; + float m_soundTime; +}; + +TYPEDESCRIPTION CPushable::m_SaveData[] = +{ + DEFINE_FIELD( CPushable, m_maxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CPushable, m_soundTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CPushable, CBreakable ); + +LINK_ENTITY_TO_CLASS( func_pushable, CPushable ); + +char *CPushable :: m_soundNames[3] = { "debris/pushbox1.wav", "debris/pushbox2.wav", "debris/pushbox3.wav" }; + + +void CPushable :: Spawn( void ) +{ + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + CBreakable::Spawn(); + else + Precache( ); + + pev->movetype = MOVETYPE_PUSHSTEP; + pev->solid = SOLID_BBOX; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + if ( pev->friction > 399 ) + pev->friction = 399; + + m_maxSpeed = 400 - pev->friction; + SetBits( pev->flags, FL_FLOAT ); + pev->friction = 0; + + pev->origin.z += 1; // Pick up off of the floor + UTIL_SetOrigin( pev, pev->origin ); + + // Multiply by area of the box's cross-section (assume 1000 units^3 standard volume) + pev->skin = static_cast(( pev->skin * (pev->maxs.x - pev->mins.x) * (pev->maxs.y - pev->mins.y) ) * 0.0005); + m_soundTime = 0; +} + + +void CPushable :: Precache( void ) +{ + for ( int i = 0; i < 3; i++ ) + PRECACHE_SOUND( m_soundNames[i] ); + + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + CBreakable::Precache( ); +} + + +void CPushable :: KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "size") ) + { + int bbox = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + switch( bbox ) + { + case 0: // Point + UTIL_SetSize(pev, Vector(-8, -8, -8), Vector(8, 8, 8)); + break; + + case 2: // Big Hull!?!? !!!BUGBUG Figure out what this hull really is + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN*2, VEC_DUCK_HULL_MAX*2); + break; + + case 3: // Player duck + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + break; + + default: + case 1: // Player + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + break; + } + + } + else if ( FStrEq(pkvd->szKeyName, "buoyancy") ) + { + pev->skin = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBreakable::KeyValue( pkvd ); +} + + +// Pull the func_pushable +void CPushable :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !pActivator || !pActivator->IsPlayer() ) + { + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + this->CBreakable::Use( pActivator, pCaller, useType, value ); + return; + } + + if ( pActivator->pev->velocity != g_vecZero ) + Move( pActivator, 0 ); +} + + +void CPushable :: Touch( CBaseEntity *pOther ) +{ + if ( FClassnameIs( pOther->pev, "worldspawn" ) ) + return; + + Move( pOther, 1 ); +} + + +void CPushable :: Move( CBaseEntity *pOther, int push ) +{ + entvars_t* pevToucher = pOther->pev; + int playerTouch = 0; + + // Is entity standing on this pushable ? + if ( FBitSet(pevToucher->flags,FL_ONGROUND) && pevToucher->groundentity && VARS(pevToucher->groundentity) == pev ) + { + // Only push if floating + if ( pev->waterlevel > 0 ) + pev->velocity.z += pevToucher->velocity.z * 0.1; + + return; + } + + + if ( pOther->IsPlayer() ) + { + if ( push && !(pevToucher->button & (IN_FORWARD|IN_USE)) ) // Don't push unless the player is pushing forward and NOT use (pull) + return; + playerTouch = 1; + } + + float factor; + + if ( playerTouch ) + { + if ( !(pevToucher->flags & FL_ONGROUND) ) // Don't push away from jumping/falling players unless in water + { + if ( pev->waterlevel < 1 ) + return; + else + factor = 0.1; + } + else + factor = 1; + } + else + factor = 0.25; + + pev->velocity.x += pevToucher->velocity.x * factor; + pev->velocity.y += pevToucher->velocity.y * factor; + + float length = sqrt( pev->velocity.x * pev->velocity.x + pev->velocity.y * pev->velocity.y ); + if ( push && (length > MaxSpeed()) ) + { + pev->velocity.x = (pev->velocity.x * MaxSpeed() / length ); + pev->velocity.y = (pev->velocity.y * MaxSpeed() / length ); + } + if ( playerTouch ) + { + pevToucher->velocity.x = pev->velocity.x; + pevToucher->velocity.y = pev->velocity.y; + if ( (gpGlobals->time - m_soundTime) > 0.7 ) + { + m_soundTime = gpGlobals->time; + if ( length > 0 && FBitSet(pev->flags,FL_ONGROUND) ) + { + m_lastSound = RANDOM_LONG(0,2); + EMIT_SOUND(ENT(pev), CHAN_WEAPON, m_soundNames[m_lastSound], 0.5, ATTN_NORM); + // SetThink( StopSound ); + // pev->nextthink = pev->ltime + 0.1; + } + else + STOP_SOUND( ENT(pev), CHAN_WEAPON, m_soundNames[m_lastSound] ); + } + } +} + +#if 0 +void CPushable::StopSound( void ) +{ + Vector dist = pev->oldorigin - pev->origin; + if ( dist.Length() <= 0 ) + STOP_SOUND( ENT(pev), CHAN_WEAPON, m_soundNames[m_lastSound] ); +} +#endif + +int CPushable::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + return CBreakable::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); + + return 1; +} + diff --git a/hlsdk/ricochet/dlls/func_break.h b/hlsdk/ricochet/dlls/func_break.h new file mode 100644 index 00000000..3c773b13 --- /dev/null +++ b/hlsdk/ricochet/dlls/func_break.h @@ -0,0 +1,74 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +#ifndef FUNC_BREAK_H +#define FUNC_BREAK_H + +typedef enum { expRandom, expDirected} Explosions; +typedef enum { matGlass = 0, matWood, matMetal, matFlesh, matCinderBlock, matCeilingTile, matComputer, matUnbreakableGlass, matRocks, matNone, matLastMaterial } Materials; + +#define NUM_SHARDS 6 // this many shards spawned when breakable objects break; + +class CBreakable : public CBaseDelay +{ +public: + // basic functions + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData* pkvd); + void EXPORT BreakTouch( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void DamageSound( void ); + + // 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 ); + + BOOL IsBreakable( void ); + BOOL SparkWhenHit( void ); + + int DamageDecal( int bitsDamageType ); + + void EXPORT Die( void ); + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + inline BOOL Explodable( void ) { return ExplosionMagnitude() > 0; } + inline int ExplosionMagnitude( void ) { return pev->impulse; } + inline void ExplosionSetMagnitude( int magnitude ) { pev->impulse = magnitude; } + + static void MaterialSoundPrecache( Materials precacheMaterial ); + static void MaterialSoundRandom( edict_t *pEdict, Materials soundMaterial, float volume ); + static const char **MaterialSoundList( Materials precacheMaterial, int &soundCount ); + + static const char *pSoundsWood[]; + static const char *pSoundsFlesh[]; + static const char *pSoundsGlass[]; + static const char *pSoundsMetal[]; + static const char *pSoundsConcrete[]; + static const char *pSpawnObjects[]; + + static TYPEDESCRIPTION m_SaveData[]; + + Materials m_Material; + Explosions m_Explosion; + int m_idShard; + float m_angle; + int m_iszGibModel; + int m_iszSpawnObject; +}; + +#endif // FUNC_BREAK_H diff --git a/hlsdk/ricochet/dlls/func_tank.cpp b/hlsdk/ricochet/dlls/func_tank.cpp new file mode 100644 index 00000000..0bf2b249 --- /dev/null +++ b/hlsdk/ricochet/dlls/func_tank.cpp @@ -0,0 +1,1035 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "effects.h" +#include "weapons.h" +#include "explode.h" + +#include "player.h" + + +#define SF_TANK_ACTIVE 0x0001 +#define SF_TANK_PLAYER 0x0002 +#define SF_TANK_HUMANS 0x0004 +#define SF_TANK_ALIENS 0x0008 +#define SF_TANK_LINEOFSIGHT 0x0010 +#define SF_TANK_CANCONTROL 0x0020 +#define SF_TANK_SOUNDON 0x8000 + +enum TANKBULLET +{ + TANK_BULLET_NONE = 0, + TANK_BULLET_9MM = 1, + TANK_BULLET_MP5 = 2, + TANK_BULLET_12MM = 3, +}; + +// Custom damage +// env_laser (duration is 0.5 rate of fire) +// rockets +// explosion? + +class CFuncTank : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Think( void ); + void TrackTarget( void ); + + virtual void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); + virtual Vector UpdateTargetPosition( CBaseEntity *pTarget ) + { + return pTarget->BodyTarget( pev->origin ); + } + + void StartRotSound( void ); + void StopRotSound( void ); + + // Bmodels don't go across transitions + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + inline BOOL IsActive( void ) { return (pev->spawnflags & SF_TANK_ACTIVE)?TRUE:FALSE; } + inline void TankActivate( void ) { pev->spawnflags |= SF_TANK_ACTIVE; pev->nextthink = pev->ltime + 0.1; m_fireLast = 0; } + inline void TankDeactivate( void ) { pev->spawnflags &= ~SF_TANK_ACTIVE; m_fireLast = 0; StopRotSound(); } + inline BOOL CanFire( void ) { return (gpGlobals->time - m_lastSightTime) < m_persist; } + BOOL InRange( float range ); + + // Acquire a target. pPlayer is a player in the PVS + edict_t *FindTarget( edict_t *pPlayer ); + + void TankTrace( const Vector &vecStart, const Vector &vecForward, const Vector &vecSpread, TraceResult &tr ); + + Vector BarrelPosition( void ) + { + Vector forward, right, up; + UTIL_MakeVectorsPrivate( pev->angles, forward, right, up ); + return pev->origin + (forward * m_barrelPos.x) + (right * m_barrelPos.y) + (up * m_barrelPos.z); + } + + void AdjustAnglesForBarrel( Vector &angles, float distance ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + BOOL OnControls( entvars_t *pevTest ); + BOOL StartControl( CBasePlayer* pController ); + void StopControl( void ); + void ControllerPostFrame( void ); + + +protected: + CBasePlayer* m_pController; + float m_flNextAttack; + Vector m_vecControllerUsePos; + + float m_yawCenter; // "Center" yaw + float m_yawRate; // Max turn rate to track targets + float m_yawRange; // Range of turning motion (one-sided: 30 is +/- 30 degress from center) + // Zero is full rotation + float m_yawTolerance; // Tolerance angle + + float m_pitchCenter; // "Center" pitch + float m_pitchRate; // Max turn rate on pitch + float m_pitchRange; // Range of pitch motion as above + float m_pitchTolerance; // Tolerance angle + + float m_fireLast; // Last time I fired + float m_fireRate; // How many rounds/second + float m_lastSightTime;// Last time I saw target + float m_persist; // Persistence of firing (how long do I shoot when I can't see) + float m_minRange; // Minimum range to aim/track + float m_maxRange; // Max range to aim/track + + Vector m_barrelPos; // Length of the freakin barrel + float m_spriteScale; // Scale of any sprites we shoot + int m_iszSpriteSmoke; + int m_iszSpriteFlash; + TANKBULLET m_bulletType; // Bullet type + int m_iBulletDamage; // 0 means use Bullet type's default damage + + Vector m_sightOrigin; // Last sight of target + int m_spread; // firing spread + int m_iszMaster; // Master entity (game_team_master or multisource) +}; + + +TYPEDESCRIPTION CFuncTank::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTank, m_yawCenter, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_yawRate, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_yawRange, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_yawTolerance, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_pitchCenter, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_pitchRate, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_pitchRange, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_pitchTolerance, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_fireLast, FIELD_TIME ), + DEFINE_FIELD( CFuncTank, m_fireRate, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_lastSightTime, FIELD_TIME ), + DEFINE_FIELD( CFuncTank, m_persist, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_minRange, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_maxRange, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_barrelPos, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTank, m_spriteScale, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_iszSpriteSmoke, FIELD_STRING ), + DEFINE_FIELD( CFuncTank, m_iszSpriteFlash, FIELD_STRING ), + DEFINE_FIELD( CFuncTank, m_bulletType, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTank, m_sightOrigin, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTank, m_spread, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTank, m_pController, FIELD_CLASSPTR ), + DEFINE_FIELD( CFuncTank, m_vecControllerUsePos, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTank, m_flNextAttack, FIELD_TIME ), + DEFINE_FIELD( CFuncTank, m_iBulletDamage, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTank, m_iszMaster, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTank, CBaseEntity ); + +static Vector gTankSpread[] = +{ + Vector( 0, 0, 0 ), // perfect + Vector( 0.025, 0.025, 0.025 ), // small cone + Vector( 0.05, 0.05, 0.05 ), // medium cone + Vector( 0.1, 0.1, 0.1 ), // large cone + Vector( 0.25, 0.25, 0.25 ), // extra-large cone +}; +#define MAX_FIRING_SPREADS ARRAYSIZE(gTankSpread) + + +void CFuncTank :: Spawn( void ) +{ + Precache(); + + pev->movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + pev->solid = SOLID_BSP; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + m_yawCenter = pev->angles.y; + m_pitchCenter = pev->angles.x; + + if ( IsActive() ) + pev->nextthink = pev->ltime + 1.0; + + m_sightOrigin = BarrelPosition(); // Point at the end of the barrel + + if ( m_fireRate <= 0 ) + m_fireRate = 1; + if ( m_spread > static_cast(MAX_FIRING_SPREADS) ) + m_spread = 0; + + pev->oldorigin = pev->origin; +} + + +void CFuncTank :: Precache( void ) +{ + if ( m_iszSpriteSmoke ) + PRECACHE_MODEL( (char *)STRING(m_iszSpriteSmoke) ); + if ( m_iszSpriteFlash ) + PRECACHE_MODEL( (char *)STRING(m_iszSpriteFlash) ); + + if ( pev->noise ) + PRECACHE_SOUND( (char *)STRING(pev->noise) ); +} + + +void CFuncTank :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "yawrate")) + { + m_yawRate = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "yawrange")) + { + m_yawRange = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "yawtolerance")) + { + m_yawTolerance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitchrange")) + { + m_pitchRange = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitchrate")) + { + m_pitchRate = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitchtolerance")) + { + m_pitchTolerance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "firerate")) + { + m_fireRate = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "barrel")) + { + m_barrelPos.x = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "barrely")) + { + m_barrelPos.y = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "barrelz")) + { + m_barrelPos.z = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spritescale")) + { + m_spriteScale = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spritesmoke")) + { + m_iszSpriteSmoke = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spriteflash")) + { + m_iszSpriteFlash = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "rotatesound")) + { + pev->noise = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "persistence")) + { + m_persist = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "bullet")) + { + m_bulletType = (TANKBULLET)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "bullet_damage" )) + { + m_iBulletDamage = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "firespread")) + { + m_spread = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "minRange")) + { + m_minRange = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "maxRange")) + { + m_maxRange = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "master")) + { + m_iszMaster = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +////////////// START NEW STUFF ////////////// + +//================================================================================== +// TANK CONTROLLING +BOOL CFuncTank :: OnControls( entvars_t *pevTest ) +{ + if ( !(pev->spawnflags & SF_TANK_CANCONTROL) ) + return FALSE; + + Vector offset = pevTest->origin - pev->origin; + + if ( (m_vecControllerUsePos - pevTest->origin).Length() < 30 ) + return TRUE; + + return FALSE; +} + +BOOL CFuncTank :: StartControl( CBasePlayer *pController ) +{ + if ( m_pController != NULL ) + return FALSE; + + // Team only or disabled? + if ( m_iszMaster ) + { + if ( !UTIL_IsMasterTriggered( m_iszMaster, pController ) ) + return FALSE; + } + + ALERT( at_console, "using TANK!\n"); + + // Holster player's weapon + m_pController = pController; + if ( m_pController->m_pActiveItem ) + { + m_pController->m_pActiveItem->Holster(); + m_pController->pev->weaponmodel = 0; + } + + m_pController->m_iHideHUD |= HIDEHUD_WEAPONS; + m_vecControllerUsePos = m_pController->pev->origin; + + pev->nextthink = pev->ltime + 0.1; + + return TRUE; +} + +void CFuncTank :: StopControl() +{ + // TODO: bring back the controllers current weapon + if ( !m_pController ) + return; + + if ( m_pController->m_pActiveItem ) + m_pController->m_pActiveItem->Deploy(); + + ALERT( at_console, "stopped using TANK\n"); + + m_pController->m_iHideHUD &= ~HIDEHUD_WEAPONS; + + pev->nextthink = 0; + m_pController = NULL; + + if ( IsActive() ) + pev->nextthink = pev->ltime + 1.0; +} + +// Called each frame by the player's ItemPostFrame +void CFuncTank :: ControllerPostFrame( void ) +{ + ASSERT(m_pController != NULL); + + if ( gpGlobals->time < m_flNextAttack ) + return; + + if ( m_pController->pev->button & IN_ATTACK ) + { + Vector vecForward; + UTIL_MakeVectorsPrivate( pev->angles, vecForward, NULL, NULL ); + + m_fireLast = gpGlobals->time - (1/m_fireRate) - 0.01; // to make sure the gun doesn't fire too many bullets + + Fire( BarrelPosition(), vecForward, m_pController->pev ); + + // HACKHACK -- make some noise (that the AI can hear) + if ( m_pController && m_pController->IsPlayer() ) + ((CBasePlayer *)m_pController)->m_iWeaponVolume = LOUD_GUN_VOLUME; + + m_flNextAttack = gpGlobals->time + (1/m_fireRate); + } +} +////////////// END NEW STUFF ////////////// + + +void CFuncTank :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->spawnflags & SF_TANK_CANCONTROL ) + { // player controlled turret + + if ( pActivator->Classify() != CLASS_PLAYER ) + return; + + if ( value == 2 && useType == USE_SET ) + { + ControllerPostFrame(); + } + else if ( !m_pController && useType != USE_OFF ) + { + ((CBasePlayer*)pActivator)->m_pTank = this; + StartControl( (CBasePlayer*)pActivator ); + } + else + { + StopControl(); + } + } + else + { + if ( !ShouldToggle( useType, IsActive() ) ) + return; + + if ( IsActive() ) + TankDeactivate(); + else + TankActivate(); + } +} + + +edict_t *CFuncTank :: FindTarget( edict_t *pPlayer ) +{ + return pPlayer; +} + + + +BOOL CFuncTank :: InRange( float range ) +{ + if ( range < m_minRange ) + return FALSE; + if ( m_maxRange > 0 && range > m_maxRange ) + return FALSE; + + return TRUE; +} + + +void CFuncTank :: Think( void ) +{ + pev->avelocity = g_vecZero; + TrackTarget(); + + if ( fabs(pev->avelocity.x) > 1 || fabs(pev->avelocity.y) > 1 ) + StartRotSound(); + else + StopRotSound(); +} + +void CFuncTank::TrackTarget( void ) +{ + TraceResult tr; + edict_t *pPlayer = FIND_CLIENT_IN_PVS( edict() ); + BOOL updateTime = FALSE, lineOfSight; + Vector angles, direction, targetPosition, barrelEnd; + edict_t *pTarget = NULL; + + // Get a position to aim for + if (m_pController) + { + // Tanks attempt to mirror the player's angles + angles = m_pController->pev->v_angle; + angles[0] = 0 - angles[0]; + pev->nextthink = pev->ltime + 0.05; + } + else + { + if ( IsActive() ) + pev->nextthink = pev->ltime + 0.1; + else + return; + + if ( FNullEnt( pPlayer ) ) + { + if ( IsActive() ) + pev->nextthink = pev->ltime + 2; // Wait 2 secs + return; + } + pTarget = FindTarget( pPlayer ); + if ( !pTarget ) + return; + + // Calculate angle needed to aim at target + barrelEnd = BarrelPosition(); + targetPosition = pTarget->v.origin + pTarget->v.view_ofs; + float range = (targetPosition - barrelEnd).Length(); + + if ( !InRange( range ) ) + return; + + UTIL_TraceLine( barrelEnd, targetPosition, dont_ignore_monsters, edict(), &tr ); + + lineOfSight = FALSE; + // No line of sight, don't track + if ( tr.flFraction == 1.0 || tr.pHit == pTarget ) + { + lineOfSight = TRUE; + + CBaseEntity *pInstance = CBaseEntity::Instance(pTarget); + if ( InRange( range ) && pInstance && pInstance->IsAlive() ) + { + updateTime = TRUE; + m_sightOrigin = UpdateTargetPosition( pInstance ); + } + } + + // Track sight origin + +// !!! I'm not sure what i changed + direction = m_sightOrigin - pev->origin; +// direction = m_sightOrigin - barrelEnd; + angles = UTIL_VecToAngles( direction ); + + // Calculate the additional rotation to point the end of the barrel at the target (not the gun's center) + AdjustAnglesForBarrel( angles, direction.Length() ); + } + + angles.x = -angles.x; + + // Force the angles to be relative to the center position + angles.y = m_yawCenter + UTIL_AngleDistance( angles.y, m_yawCenter ); + angles.x = m_pitchCenter + UTIL_AngleDistance( angles.x, m_pitchCenter ); + + // Limit against range in y + if ( angles.y > m_yawCenter + m_yawRange ) + { + angles.y = m_yawCenter + m_yawRange; + updateTime = FALSE; // Don't update if you saw the player, but out of range + } + else if ( angles.y < (m_yawCenter - m_yawRange) ) + { + angles.y = (m_yawCenter - m_yawRange); + updateTime = FALSE; // Don't update if you saw the player, but out of range + } + + if ( updateTime ) + m_lastSightTime = gpGlobals->time; + + // Move toward target at rate or less + float distY = UTIL_AngleDistance( angles.y, pev->angles.y ); + pev->avelocity.y = distY * 10; + if ( pev->avelocity.y > m_yawRate ) + pev->avelocity.y = m_yawRate; + else if ( pev->avelocity.y < -m_yawRate ) + pev->avelocity.y = -m_yawRate; + + // Limit against range in x + if ( angles.x > m_pitchCenter + m_pitchRange ) + angles.x = m_pitchCenter + m_pitchRange; + else if ( angles.x < m_pitchCenter - m_pitchRange ) + angles.x = m_pitchCenter - m_pitchRange; + + // Move toward target at rate or less + float distX = UTIL_AngleDistance( angles.x, pev->angles.x ); + pev->avelocity.x = distX * 10; + + if ( pev->avelocity.x > m_pitchRate ) + pev->avelocity.x = m_pitchRate; + else if ( pev->avelocity.x < -m_pitchRate ) + pev->avelocity.x = -m_pitchRate; + + if ( m_pController ) + return; + + if ( CanFire() && ( (fabs(distX) < m_pitchTolerance && fabs(distY) < m_yawTolerance) || (pev->spawnflags & SF_TANK_LINEOFSIGHT) ) ) + { + BOOL fire = FALSE; + Vector forward; + UTIL_MakeVectorsPrivate( pev->angles, forward, NULL, NULL ); + + if ( pev->spawnflags & SF_TANK_LINEOFSIGHT ) + { + float length = direction.Length(); + UTIL_TraceLine( barrelEnd, barrelEnd + forward * length, dont_ignore_monsters, edict(), &tr ); + if ( tr.pHit == pTarget ) + fire = TRUE; + } + else + fire = TRUE; + + if ( fire ) + { + Fire( BarrelPosition(), forward, pev ); + } + else + m_fireLast = 0; + } + else + m_fireLast = 0; +} + + +// If barrel is offset, add in additional rotation +void CFuncTank::AdjustAnglesForBarrel( Vector &angles, float distance ) +{ + float r2, d2; + + + if ( m_barrelPos.y != 0 || m_barrelPos.z != 0 ) + { + distance -= m_barrelPos.z; + d2 = distance * distance; + if ( m_barrelPos.y ) + { + r2 = m_barrelPos.y * m_barrelPos.y; + angles.y += (180.0 / M_PI) * atan2( m_barrelPos.y, sqrt( d2 - r2 ) ); + } + if ( m_barrelPos.z ) + { + r2 = m_barrelPos.z * m_barrelPos.z; + angles.x += (180.0 / M_PI) * atan2( -m_barrelPos.z, sqrt( d2 - r2 ) ); + } + } +} + + +// Fire targets and spawn sprites +void CFuncTank::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ + if ( m_fireLast != 0 ) + { + if ( m_iszSpriteSmoke ) + { + CSprite *pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteSmoke), barrelEnd, TRUE ); + pSprite->AnimateAndDie( RANDOM_FLOAT( 15.0, 20.0 ) ); + pSprite->SetTransparency( kRenderTransAlpha, static_cast(pev->rendercolor.x), static_cast(pev->rendercolor.y), static_cast(pev->rendercolor.z), 255, kRenderFxNone ); + pSprite->pev->velocity.z = RANDOM_FLOAT(40, 80); + pSprite->SetScale( m_spriteScale ); + } + if ( m_iszSpriteFlash ) + { + CSprite *pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteFlash), barrelEnd, TRUE ); + pSprite->AnimateAndDie( 60 ); + pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); + pSprite->SetScale( m_spriteScale ); + } + SUB_UseTargets( this, USE_TOGGLE, 0 ); + } + m_fireLast = gpGlobals->time; +} + + +void CFuncTank::TankTrace( const Vector &vecStart, const Vector &vecForward, const Vector &vecSpread, TraceResult &tr ) +{ + // get circular gaussian spread + float x, y, z; + 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); + Vector vecDir = vecForward + + x * vecSpread.x * gpGlobals->v_right + + y * vecSpread.y * gpGlobals->v_up; + Vector vecEnd; + + vecEnd = vecStart + vecDir * 4096; + UTIL_TraceLine( vecStart, vecEnd, dont_ignore_monsters, edict(), &tr ); +} + + +void CFuncTank::StartRotSound( void ) +{ + if ( !pev->noise || (pev->spawnflags & SF_TANK_SOUNDON) ) + return; + pev->spawnflags |= SF_TANK_SOUNDON; + EMIT_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noise), 0.85, ATTN_NORM); +} + + +void CFuncTank::StopRotSound( void ) +{ + if ( pev->spawnflags & SF_TANK_SOUNDON ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noise) ); + pev->spawnflags &= ~SF_TANK_SOUNDON; +} + +class CFuncTankGun : public CFuncTank +{ +public: + void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); +}; +LINK_ENTITY_TO_CLASS( func_tank, CFuncTankGun ); + +void CFuncTankGun::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ + int i; + + if ( m_fireLast != 0 ) + { + // FireBullets needs gpGlobals->v_up, etc. + UTIL_MakeAimVectors(pev->angles); + + int bulletCount = static_cast((gpGlobals->time - m_fireLast) * m_fireRate); + if ( bulletCount > 0 ) + { + for ( i = 0; i < bulletCount; i++ ) + { + switch( m_bulletType ) + { + case TANK_BULLET_9MM: + FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], 4096, BULLET_MONSTER_9MM, 1, m_iBulletDamage, pevAttacker ); + break; + + case TANK_BULLET_MP5: + FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], 4096, BULLET_MONSTER_MP5, 1, m_iBulletDamage, pevAttacker ); + break; + + case TANK_BULLET_12MM: + FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], 4096, BULLET_MONSTER_12MM, 1, m_iBulletDamage, pevAttacker ); + break; + + default: + case TANK_BULLET_NONE: + break; + } + } + CFuncTank::Fire( barrelEnd, forward, pevAttacker ); + } + } + else + CFuncTank::Fire( barrelEnd, forward, pevAttacker ); +} + + + +class CFuncTankLaser : public CFuncTank +{ +public: + void Activate( void ); + void KeyValue( KeyValueData *pkvd ); + void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); + void Think( void ); + CLaser *GetLaser( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + CLaser *m_pLaser; + float m_laserTime; +}; +LINK_ENTITY_TO_CLASS( func_tanklaser, CFuncTankLaser ); + +TYPEDESCRIPTION CFuncTankLaser::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTankLaser, m_pLaser, FIELD_CLASSPTR ), + DEFINE_FIELD( CFuncTankLaser, m_laserTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTankLaser, CFuncTank ); + +void CFuncTankLaser::Activate( void ) +{ + if ( !GetLaser() ) + { + UTIL_Remove(this); + ALERT( at_error, "Laser tank with no env_laser!\n" ); + } + else + { + m_pLaser->TurnOff(); + } +} + + +void CFuncTankLaser::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "laserentity")) + { + pev->message = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CFuncTank::KeyValue( pkvd ); +} + + +CLaser *CFuncTankLaser::GetLaser( void ) +{ + if ( m_pLaser ) + return m_pLaser; + + edict_t *pentLaser; + + pentLaser = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->message) ); + while ( !FNullEnt( pentLaser ) ) + { + // Found the landmark + if ( FClassnameIs( pentLaser, "env_laser" ) ) + { + m_pLaser = (CLaser *)CBaseEntity::Instance(pentLaser); + break; + } + else + pentLaser = FIND_ENTITY_BY_TARGETNAME( pentLaser, STRING(pev->message) ); + } + + return m_pLaser; +} + + +void CFuncTankLaser::Think( void ) +{ + if ( m_pLaser && (gpGlobals->time > m_laserTime) ) + m_pLaser->TurnOff(); + + CFuncTank::Think(); +} + + +void CFuncTankLaser::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ + int i; + TraceResult tr; + + if ( m_fireLast != 0 && GetLaser() ) + { + // TankTrace needs gpGlobals->v_up, etc. + UTIL_MakeAimVectors(pev->angles); + + int bulletCount = static_cast((gpGlobals->time - m_fireLast) * m_fireRate); + if ( bulletCount ) + { + for ( i = 0; i < bulletCount; i++ ) + { + m_pLaser->pev->origin = barrelEnd; + TankTrace( barrelEnd, forward, gTankSpread[m_spread], tr ); + + m_laserTime = gpGlobals->time; + m_pLaser->TurnOn(); + m_pLaser->pev->dmgtime = gpGlobals->time - 1.0; + m_pLaser->FireAtPoint( tr ); + m_pLaser->pev->nextthink = 0; + } + CFuncTank::Fire( barrelEnd, forward, pev ); + } + } + else + { + CFuncTank::Fire( barrelEnd, forward, pev ); + } +} + +class CFuncTankRocket : public CFuncTank +{ +public: + void Precache( void ); + void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); +}; +LINK_ENTITY_TO_CLASS( func_tankrocket, CFuncTankRocket ); + +void CFuncTankRocket::Precache( void ) +{ + UTIL_PrecacheOther( "rpg_rocket" ); + CFuncTank::Precache(); +} + + + +void CFuncTankRocket::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ + int i; + + if ( m_fireLast != 0 ) + { + int bulletCount = static_cast((gpGlobals->time - m_fireLast) * m_fireRate); + if ( bulletCount > 0 ) + { + for ( i = 0; i < bulletCount; i++ ) + { + CBaseEntity::Create( "rpg_rocket", barrelEnd, pev->angles, edict() ); + } + CFuncTank::Fire( barrelEnd, forward, pev ); + } + } + else + CFuncTank::Fire( barrelEnd, forward, pev ); +} + + +class CFuncTankMortar : public CFuncTank +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); +}; +LINK_ENTITY_TO_CLASS( func_tankmortar, CFuncTankMortar ); + + +void CFuncTankMortar::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "iMagnitude")) + { + pev->impulse = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CFuncTank::KeyValue( pkvd ); +} + + +void CFuncTankMortar::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ + if ( m_fireLast != 0 ) + { + int bulletCount = static_cast((gpGlobals->time - m_fireLast) * m_fireRate); + // Only create 1 explosion + if ( bulletCount > 0 ) + { + TraceResult tr; + + // TankTrace needs gpGlobals->v_up, etc. + UTIL_MakeAimVectors(pev->angles); + + TankTrace( barrelEnd, forward, gTankSpread[m_spread], tr ); + + ExplosionCreate( tr.vecEndPos, pev->angles, edict(), pev->impulse, TRUE ); + + CFuncTank::Fire( barrelEnd, forward, pev ); + } + } + else + CFuncTank::Fire( barrelEnd, forward, pev ); +} + + + +//============================================================================ +// FUNC TANK CONTROLS +//============================================================================ +class CFuncTankControls : public CBaseEntity +{ +public: + virtual int ObjectCaps( void ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Think( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + CFuncTank *m_pTank; +}; +LINK_ENTITY_TO_CLASS( func_tankcontrols, CFuncTankControls ); + +TYPEDESCRIPTION CFuncTankControls::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTankControls, m_pTank, FIELD_CLASSPTR ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTankControls, CBaseEntity ); + +int CFuncTankControls :: ObjectCaps( void ) +{ + return (CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_IMPULSE_USE; +} + + +void CFuncTankControls :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ // pass the Use command onto the controls + if ( m_pTank ) + m_pTank->Use( pActivator, pCaller, useType, value ); + + ASSERT( m_pTank != NULL ); // if this fails, most likely means save/restore hasn't worked properly +} + + +void CFuncTankControls :: Think( void ) +{ + edict_t *pTarget = NULL; + + do + { + pTarget = FIND_ENTITY_BY_TARGETNAME( pTarget, STRING(pev->target) ); + } while ( !FNullEnt(pTarget) && strncmp( STRING(pTarget->v.classname), "func_tank", 9 ) ); + + if ( FNullEnt( pTarget ) ) + { + ALERT( at_console, "No tank %s\n", STRING(pev->target) ); + return; + } + + m_pTank = (CFuncTank*)Instance(pTarget); +} + +void CFuncTankControls::Spawn( void ) +{ + pev->solid = SOLID_TRIGGER; + pev->movetype = MOVETYPE_NONE; + pev->effects |= EF_NODRAW; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( pev, pev->origin ); + + pev->nextthink = gpGlobals->time + 0.3; // After all the func_tank's have spawned + + CBaseEntity::Spawn(); +} diff --git a/hlsdk/ricochet/dlls/game.cpp b/hlsdk/ricochet/dlls/game.cpp new file mode 100644 index 00000000..69f585d2 --- /dev/null +++ b/hlsdk/ricochet/dlls/game.cpp @@ -0,0 +1,899 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "eiface.h" +#include "util.h" +#include "game.h" + + +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 allow_spectators = { "allow_spectators", "1.0", FCVAR_SERVER }; // 0 prevents players from being spectators + +// discwar +cvar_t rc_rounds = {"rc_rounds", "3", FCVAR_SERVER | FCVAR_UNLOGGED }; +cvar_t rc_playersperteam = {"rc_playersperteam", "1", FCVAR_SERVER | FCVAR_UNLOGGED }; +cvar_t rc_arena = {"rc_arena", "1", FCVAR_SERVER | FCVAR_UNLOGGED }; + +// multiplayer server rules +cvar_t teamplay = {"mp_teamplay","0", FCVAR_SERVER }; +cvar_t fraglimit = {"mp_fraglimit","0", 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","male", FCVAR_SERVER }; +cvar_t teamoverride = {"mp_teamoverride","1" }; +cvar_t defaultteam = {"mp_defaultteam","0" }; +cvar_t allowmonsters={"mp_allowmonsters","0", FCVAR_SERVER }; + +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 + +// Register your console variables here +// This gets called one time when the game is initialied +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 (&fraglimit); + CVAR_REGISTER (&timelimit); + + CVAR_REGISTER (&fragsleft); + CVAR_REGISTER (&timeleft); + + CVAR_REGISTER( &allow_spectators ); + + // Discwar + CVAR_REGISTER (&rc_rounds); + CVAR_REGISTER (&rc_playersperteam); + CVAR_REGISTER (&rc_arena); + + 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); + +// 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" ); +} + +void GameDLLShutdown() +{ +} diff --git a/hlsdk/ricochet/dlls/game.h b/hlsdk/ricochet/dlls/game.h new file mode 100644 index 00000000..c8e80391 --- /dev/null +++ b/hlsdk/ricochet/dlls/game.h @@ -0,0 +1,44 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef GAME_H +#define GAME_H + +extern void GameDLLInit( void ); +extern void GameDLLShutdown( void ); + +extern cvar_t displaysoundlist; + +// multiplayer server rules +extern cvar_t fraglimit; +extern cvar_t timelimit; +extern cvar_t friendlyfir; +extern cvar_t falldamage; +extern cvar_t weaponstay; +extern cvar_t forcerespaw; +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 allow_spectators; + +// 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/hlsdk/ricochet/dlls/gamerules.cpp b/hlsdk/ricochet/dlls/gamerules.cpp new file mode 100644 index 00000000..489e8f4e --- /dev/null +++ b/hlsdk/ricochet/dlls/gamerules.cpp @@ -0,0 +1,328 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// 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 "skill.h" + +extern edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ); + +DLL_GLOBAL CGameRules* g_pGameRules = NULL; +extern DLL_GLOBAL BOOL g_fGameOver; +extern int gmsgDeathMsg; // client dll messages +extern int gmsgScoreInfo; +extern int gmsgMOTD; + +//========================================================= +//========================================================= +BOOL CGameRules::CanHaveAmmo( CBasePlayer *pPlayer, const char *pszAmmoName, int iMaxCarry ) +{ + int iAmmoIndex; + + if ( pszAmmoName ) + { + iAmmoIndex = pPlayer->GetAmmoIndex( pszAmmoName ); + + if ( iAmmoIndex > -1 ) + { + if ( pPlayer->AmmoInventory( iAmmoIndex ) < iMaxCarry ) + { + // player has room for more of this type of ammo + return TRUE; + } + } + } + + return FALSE; +} + +//========================================================= +//========================================================= +edict_t *CGameRules :: GetPlayerSpawnSpot( CBasePlayer *pPlayer ) +{ + edict_t *pentSpawnSpot = EntSelectSpawnPoint( pPlayer ); + + pPlayer->pev->origin = VARS(pentSpawnSpot)->origin + Vector(0,0,1); + pPlayer->pev->v_angle = g_vecZero; + pPlayer->pev->velocity = g_vecZero; + pPlayer->pev->angles = VARS(pentSpawnSpot)->angles; + pPlayer->pev->punchangle = g_vecZero; + pPlayer->pev->fixangle = TRUE; + + return pentSpawnSpot; +} + +//========================================================= +//========================================================= +BOOL CGameRules::CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ + // only living players can have items + if ( pPlayer->pev->deadflag != DEAD_NO ) + return FALSE; + + if ( pWeapon->pszAmmo1() ) + { + if ( !CanHaveAmmo( pPlayer, pWeapon->pszAmmo1(), pWeapon->iMaxAmmo1() ) ) + { + // we can't carry anymore ammo for this gun. We can only + // have the gun if we aren't already carrying one of this type + if ( pPlayer->HasPlayerItem( pWeapon ) ) + { + return FALSE; + } + } + } + else + { + // weapon doesn't use ammo, don't take another if you already have it. + if ( pPlayer->HasPlayerItem( pWeapon ) ) + { + return FALSE; + } + } + + // note: will fall through to here if GetItemInfo doesn't fill the struct! + return TRUE; +} + +//========================================================= +// load the SkillData struct with the proper values based on the skill level. +//========================================================= +void CGameRules::RefreshSkillData ( void ) +{ + int iSkill; + + iSkill = (int)CVAR_GET_FLOAT("skill"); + + 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" ); +} + +//========================================================= +// instantiate the proper game rules object +//========================================================= + +CGameRules *InstallGameRules( void ) +{ + SERVER_COMMAND( "exec game.cfg\n" ); + SERVER_EXECUTE( ); + + if ( !gpGlobals->deathmatch ) + { + // generic half-life + return new CHalfLifeRules; + } + else + { + // Discwar's always in teamplay mode. + //CVAR_SET_FLOAT( "mp_teamplay", 1 ); + //return new CHalfLifeTeamplay; + return new CHalfLifeMultiplay; + } +} + + + diff --git a/hlsdk/ricochet/dlls/gamerules.h b/hlsdk/ricochet/dlls/gamerules.h new file mode 100644 index 00000000..5522d238 --- /dev/null +++ b/hlsdk/ricochet/dlls/gamerules.h @@ -0,0 +1,360 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// GameRules +//========================================================= + +//#include "weapons.h" +//#include "items.h" +class CBasePlayerItem; +class CBasePlayer; +class CItem; +class CBasePlayerAmmo; + +// weapon respawning return codes +enum +{ + GR_NONE = 0, + + GR_WEAPON_RESPAWN_YES, + GR_WEAPON_RESPAWN_NO, + + GR_AMMO_RESPAWN_YES, + GR_AMMO_RESPAWN_NO, + + GR_ITEM_RESPAWN_YES, + GR_ITEM_RESPAWN_NO, + + GR_PLR_DROP_GUN_ALL, + GR_PLR_DROP_GUN_ACTIVE, + GR_PLR_DROP_GUN_NO, + + GR_PLR_DROP_AMMO_ALL, + GR_PLR_DROP_AMMO_ACTIVE, + GR_PLR_DROP_AMMO_NO, +}; + +// Player relationship return codes +enum +{ + GR_NOTTEAMMATE = 0, + GR_TEAMMATE, + GR_ENEMY, + GR_ALLY, + GR_NEUTRAL, +}; + +class CGameRules +{ +public: + virtual void RefreshSkillData( void );// fill skill data struct with proper values + virtual void Think( void ) = 0;// GR_Think - runs every server frame, should handle any timer tasks, periodic events, etc. + virtual BOOL IsAllowedToSpawn( CBaseEntity *pEntity ) = 0; // Can this item spawn (eg monsters don't spawn in deathmatch). + + virtual BOOL FAllowFlashlight( void ) = 0;// Are players allowed to switch on their flashlight? + virtual BOOL FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) = 0;// should the player switch to this weapon? + virtual BOOL GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) = 0;// I can't use this weapon anymore, get me the next best one. + +// Functions to verify the single/multiplayer status of a game + virtual BOOL IsMultiplayer( void ) = 0;// is this a multiplayer game? (either coop or deathmatch) + 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 "Ricochet"; } // 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) + virtual void InitHUD( CBasePlayer *pl ) = 0; // the client dll is ready for updating + virtual void ClientDisconnected( edict_t *pClient ) = 0;// a client just disconnected from the server + virtual void UpdateGameMode( CBasePlayer *pPlayer ) {} // the client needs to be informed of the current game mode + +// Client damage rules + 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; } + +// Client spawn/respawn control + virtual void PlayerSpawn( CBasePlayer *pPlayer ) = 0;// called by CBasePlayer::Spawn just before releasing player into the game + virtual void PlayerThink( CBasePlayer *pPlayer ) = 0; // called by CBasePlayer::PreThink every frame, before physics are run and after keys are accepted + virtual BOOL FPlayerCanRespawn( CBasePlayer *pPlayer ) = 0;// is this player allowed to respawn now? + virtual float FlPlayerSpawnTime( CBasePlayer *pPlayer ) = 0;// When in the future will this player be able to spawn? + virtual edict_t *GetPlayerSpawnSpot( CBasePlayer *pPlayer );// Place this player on their spawnspot and face them the proper direction. + + virtual BOOL AllowAutoTargetCrosshair( void ) { return TRUE; }; + virtual BOOL ClientCommand( CBasePlayer *pPlayer, const char *pcmd ) { return FALSE; }; // handles the user commands; returns TRUE if command handled properly + virtual void ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ) {} // the player has changed userinfo; can change it now + +// 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 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? + virtual void PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) = 0;// Called each time a player picks up a weapon from the ground + +// Weapon spawn/respawn control + virtual int WeaponShouldRespawn( CBasePlayerItem *pWeapon ) = 0;// should this weapon respawn? + virtual float FlWeaponRespawnTime( CBasePlayerItem *pWeapon ) = 0;// when may this weapon respawn? + virtual float FlWeaponTryRespawn( CBasePlayerItem *pWeapon ) = 0; // can i respawn now, and if not, when should i try again? + virtual Vector VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ) = 0;// where in the world should this weapon respawn? + +// 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) + +// Item spawn/respawn control + virtual int ItemShouldRespawn( CItem *pItem ) = 0;// Should this item respawn? + virtual float FlItemRespawnTime( CItem *pItem ) = 0;// when may this item respawn? + virtual Vector VecItemRespawnSpot( CItem *pItem ) = 0;// where in the world should this item respawn? + +// Ammo retrieval + virtual BOOL CanHaveAmmo( CBasePlayer *pPlayer, const char *pszAmmoName, int iMaxCarry );// can this player take more of this ammo? + virtual void PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ) = 0;// called each time a player picks up some ammo in the world + +// Ammo spawn/respawn control + virtual int AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ) = 0;// should this ammo item respawn? + virtual float FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ) = 0;// when should this ammo item respawn? + virtual Vector VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ) = 0;// where in the world should this ammo item respawn? + // by default, everything spawns + +// Healthcharger respawn control + virtual float FlHealthChargerRechargeTime( void ) = 0;// how long until a depleted HealthCharger recharges itself? + virtual float FlHEVChargerRechargeTime( void ) { return 0; }// how long until a depleted HealthCharger recharges itself? + +// What happens to a dead player's weapons + virtual int DeadPlayerWeapons( CBasePlayer *pPlayer ) = 0;// what do I do with a player's weapons when he's killed? + +// What happens to a dead player's ammo + virtual int DeadPlayerAmmo( CBasePlayer *pPlayer ) = 0;// Do I drop ammo when the player dies? How much? + +// Teamplay stuff + virtual const char *GetTeamID( CBaseEntity *pEntity ) = 0;// what team is this entity on? + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) = 0;// What is the player's relationship with this entity? + virtual int GetTeamIndex( const char *pTeamName ) { return -1; } + virtual const char *GetIndexedTeamName( int teamIndex ) { return ""; } + virtual BOOL IsValidTeam( const char *pTeamName ) { return TRUE; } + virtual void ChangePlayerTeam( CBasePlayer *pPlayer, const char *pTeamName, BOOL bKill, BOOL bGib ) {} + virtual const char *SetDefaultPlayerTeam( CBasePlayer *pPlayer ) { return ""; } + +// Sounds + virtual BOOL PlayTextureSounds( void ) { return TRUE; } + virtual BOOL PlayFootstepSounds( CBasePlayer *pl, float fvol ) { return TRUE; } + +// Monsters + virtual BOOL FAllowMonsters( void ) = 0;//are monsters allowed + + // Immediately end a multiplayer game + virtual void EndMultiplayerGame( void ) {} +}; + +extern CGameRules *InstallGameRules( void ); + + +//========================================================= +// CHalfLifeRules - rules for the single player Half-Life +// game. +//========================================================= +class CHalfLifeRules : public CGameRules +{ +public: + CHalfLifeRules ( void ); + +// GR_Think + virtual void Think( void ); + virtual BOOL IsAllowedToSpawn( CBaseEntity *pEntity ); + virtual BOOL FAllowFlashlight( void ) { return TRUE; }; + + virtual BOOL FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + virtual BOOL GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ); + +// Functions to verify the single/multiplayer status of a game + virtual BOOL IsMultiplayer( void ); + virtual BOOL IsDeathmatch( void ); + virtual BOOL IsCoOp( void ); + +// Client connection/disconnection + virtual BOOL ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); + virtual void InitHUD( CBasePlayer *pl ); // the client dll is ready for updating + virtual void ClientDisconnected( edict_t *pClient ); + +// Client damage rules + virtual float FlPlayerFallDamage( CBasePlayer *pPlayer ); + +// Client spawn/respawn control + virtual void PlayerSpawn( CBasePlayer *pPlayer ); + virtual void PlayerThink( CBasePlayer *pPlayer ); + virtual BOOL FPlayerCanRespawn( CBasePlayer *pPlayer ); + virtual float FlPlayerSpawnTime( CBasePlayer *pPlayer ); + + virtual BOOL AllowAutoTargetCrosshair( void ); + +// 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 ); + +// Weapon retrieval + virtual void PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + +// Weapon spawn/respawn control + virtual int WeaponShouldRespawn( CBasePlayerItem *pWeapon ); + virtual float FlWeaponRespawnTime( CBasePlayerItem *pWeapon ); + virtual float FlWeaponTryRespawn( CBasePlayerItem *pWeapon ); + virtual Vector VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ); + +// Item retrieval + virtual BOOL CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ); + virtual void PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ); + +// Item spawn/respawn control + virtual int ItemShouldRespawn( CItem *pItem ); + virtual float FlItemRespawnTime( CItem *pItem ); + virtual Vector VecItemRespawnSpot( CItem *pItem ); + +// Ammo retrieval + virtual void PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ); + +// Ammo spawn/respawn control + virtual int AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ); + virtual float FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ); + virtual Vector VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ); + +// Healthcharger respawn control + virtual float FlHealthChargerRechargeTime( void ); + +// What happens to a dead player's weapons + virtual int DeadPlayerWeapons( CBasePlayer *pPlayer ); + +// What happens to a dead player's ammo + virtual int DeadPlayerAmmo( CBasePlayer *pPlayer ); + +// Monsters + virtual BOOL FAllowMonsters( void ); + +// Teamplay stuff + virtual const char *GetTeamID( CBaseEntity *pEntity ) {return "";}; + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); +}; + +//========================================================= +// CHalfLifeMultiplay - rules for the basic half life multiplayer +// competition +//========================================================= +class CHalfLifeMultiplay : public CGameRules +{ +public: + CHalfLifeMultiplay(); + +// GR_Think + virtual void Think( void ); + virtual void RefreshSkillData( void ); + virtual BOOL IsAllowedToSpawn( CBaseEntity *pEntity ); + virtual BOOL FAllowFlashlight( void ); + + virtual BOOL FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + virtual BOOL GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ); + +// Functions to verify the single/multiplayer status of a game + virtual BOOL IsMultiplayer( void ); + virtual BOOL IsDeathmatch( void ); + virtual BOOL IsCoOp( void ); + +// Client connection/disconnection + // If ClientConnected returns FALSE, the connection is rejected and the user is provided the reason specified in + // svRejectReason + // Only the client's name and remote address are provided to the dll for verification. + virtual BOOL ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); + virtual void InitHUD( CBasePlayer *pl ); // the client dll is ready for updating + virtual void ClientDisconnected( edict_t *pClient ); + virtual void UpdateGameMode( CBasePlayer *pPlayer ); // the client needs to be informed of the current game mode + +// Client damage rules + virtual float FlPlayerFallDamage( CBasePlayer *pPlayer ); + virtual BOOL FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ); + +// Client spawn/respawn control + virtual void PlayerSpawn( CBasePlayer *pPlayer ); + virtual void PlayerThink( CBasePlayer *pPlayer ); + virtual BOOL FPlayerCanRespawn( CBasePlayer *pPlayer ); + virtual float FlPlayerSpawnTime( CBasePlayer *pPlayer ); + virtual edict_t *GetPlayerSpawnSpot( CBasePlayer *pPlayer ); + + virtual BOOL AllowAutoTargetCrosshair( void ); + 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 ); + +// Weapon retrieval + virtual void PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + virtual BOOL CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon );// The player is touching an CBasePlayerItem, do I give it to him? + +// Weapon spawn/respawn control + virtual int WeaponShouldRespawn( CBasePlayerItem *pWeapon ); + virtual float FlWeaponRespawnTime( CBasePlayerItem *pWeapon ); + virtual float FlWeaponTryRespawn( CBasePlayerItem *pWeapon ); + virtual Vector VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ); + +// Item retrieval + virtual BOOL CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ); + virtual void PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ); + +// Item spawn/respawn control + virtual int ItemShouldRespawn( CItem *pItem ); + virtual float FlItemRespawnTime( CItem *pItem ); + virtual Vector VecItemRespawnSpot( CItem *pItem ); + +// Ammo retrieval + virtual void PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ); + +// Ammo spawn/respawn control + virtual int AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ); + virtual float FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ); + virtual Vector VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ); + +// Healthcharger respawn control + virtual float FlHealthChargerRechargeTime( void ); + virtual float FlHEVChargerRechargeTime( void ); + +// What happens to a dead player's weapons + virtual int DeadPlayerWeapons( CBasePlayer *pPlayer ); + +// What happens to a dead player's ammo + virtual int DeadPlayerAmmo( CBasePlayer *pPlayer ); + +// Teamplay stuff + virtual const char *GetTeamID( CBaseEntity *pEntity ) {return "";} + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); + + virtual BOOL PlayTextureSounds( void ) { return FALSE; } + virtual BOOL PlayFootstepSounds( CBasePlayer *pl, float fvol ); + +// Monsters + virtual BOOL FAllowMonsters( void ); + + // Immediately end a multiplayer game + virtual void EndMultiplayerGame( void ) { GoToIntermission(); } + +protected: + virtual void ChangeLevel( void ); + virtual void GoToIntermission( void ); + float m_flIntermissionEndTime; + BOOL m_iEndIntermissionButtonHit; + void SendMOTDToClient( edict_t *client ); +}; + +extern DLL_GLOBAL CGameRules* g_pGameRules; diff --git a/hlsdk/ricochet/dlls/ggrenade.cpp b/hlsdk/ricochet/dlls/ggrenade.cpp new file mode 100644 index 00000000..1805eb0d --- /dev/null +++ b/hlsdk/ricochet/dlls/ggrenade.cpp @@ -0,0 +1,488 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== generic grenade.cpp ======================================================== + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "soundent.h" +#include "decals.h" + + +//===================grenade + + +LINK_ENTITY_TO_CLASS( grenade, CGrenade ); + +// Grenades flagged with this will be triggered when the owner calls detonateSatchelCharges +#define SF_DETONATE 0x0001 + +// +// Grenade Explode +// +void CGrenade::Explode( Vector vecSrc, Vector vecAim ) +{ + TraceResult tr; + UTIL_TraceLine ( pev->origin, pev->origin + Vector ( 0, 0, -32 ), ignore_monsters, ENT(pev), & tr); + + Explode( &tr, DMG_BLAST ); +} + +// UNDONE: temporary scorching for PreAlpha - find a less sleazy permenant solution. +void CGrenade::Explode( TraceResult *pTrace, int bitsDamageType ) +{ + float flRndSound;// sound randomizer + + pev->model = iStringNull;//invisible + pev->solid = SOLID_NOT;// intangible + + pev->takedamage = DAMAGE_NO; + + // Pull out of the wall a bit + if ( pTrace->flFraction != 1.0 ) + { + pev->origin = pTrace->vecEndPos + (pTrace->vecPlaneNormal * (pev->dmg - 24) * 0.6); + } + + int iContents = UTIL_PointContents ( pev->origin ); + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION ); // This makes a dynamic light and the explosion sprites/sound + WRITE_COORD( pev->origin.x ); // Send to PAS because of the sound + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + if (iContents != CONTENTS_WATER) + { + WRITE_SHORT( g_sModelIndexFireball ); + } + else + { + WRITE_SHORT( g_sModelIndexWExplosion ); + } + WRITE_BYTE( static_cast((pev->dmg - 50) * .60) ); // scale * 10 + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + + 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, bitsDamageType ); + + if ( RANDOM_FLOAT( 0 , 1 ) < 0.5 ) + { + UTIL_DecalTrace( pTrace, DECAL_SCORCH1 ); + } + else + { + UTIL_DecalTrace( pTrace, DECAL_SCORCH2 ); + } + + flRndSound = RANDOM_FLOAT( 0 , 1 ); + + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris1.wav", 0.55, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris2.wav", 0.55, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris3.wav", 0.55, ATTN_NORM); break; + } + + pev->effects |= EF_NODRAW; + SetThink( &CGrenade::Smoke ); + pev->velocity = g_vecZero; + pev->nextthink = gpGlobals->time + 0.3; + + if (iContents != CONTENTS_WATER) + { + int sparkCount = RANDOM_LONG(0,3); + for ( int i = 0; i < sparkCount; i++ ) + Create( "spark_shower", pev->origin, pTrace->vecPlaneNormal, NULL ); + } +} + + +void CGrenade::Smoke( void ) +{ + if (UTIL_PointContents ( pev->origin ) == CONTENTS_WATER) + { + UTIL_Bubbles( pev->origin - Vector( 64, 64, 64 ), pev->origin + Vector( 64, 64, 64 ), 100 ); + } + else + { + 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( static_cast((pev->dmg - 50) * 0.80) ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + } + UTIL_Remove( this ); +} + +void CGrenade::Killed( entvars_t *pevAttacker, int iGib ) +{ + Detonate( ); +} + + +// Timed grenade, this think is called when time runs out. +void CGrenade::DetonateUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink( &CGrenade::Detonate ); + pev->nextthink = gpGlobals->time; +} + +void CGrenade::PreDetonate( void ) +{ + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin, 400, 0.3 ); + + SetThink( &CGrenade::Detonate ); + pev->nextthink = gpGlobals->time + 1; +} + + +void CGrenade::Detonate( void ) +{ + 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); + + Explode( &tr, DMG_BLAST ); +} + + +// +// Contact grenade, explode when it touches something +// +void CGrenade::ExplodeTouch( 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 ); + + Explode( &tr, DMG_BLAST ); +} + + +void CGrenade::DangerSoundThink( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin + pev->velocity * 0.5, static_cast(pev->velocity.Length( )), 0.2 ); + pev->nextthink = gpGlobals->time + 0.2; + + if (pev->waterlevel != 0) + { + pev->velocity = pev->velocity * 0.5; + } +} + + +void CGrenade::BounceTouch( CBaseEntity *pOther ) +{ + // don't hit the guy that launched this grenade + if ( pOther->edict() == pev->owner ) + return; + + // only do damage if we're moving fairly fast + if (m_flNextAttack < gpGlobals->time && pev->velocity.Length() > 100) + { + entvars_t *pevOwner = VARS( pev->owner ); + if (pevOwner) + { + TraceResult tr = UTIL_GetGlobalTrace( ); + ClearMultiDamage( ); + pOther->TraceAttack(pevOwner, 1, gpGlobals->v_forward, &tr, DMG_CLUB ); + ApplyMultiDamage( pev, pevOwner); + } + m_flNextAttack = gpGlobals->time + 1.0; // debounce + } + + Vector vecTestVelocity; + // pev->avelocity = Vector (300, 300, 300); + + // this is my heuristic for modulating the grenade velocity because grenades dropped purely vertical + // or thrown very far tend to slow down too quickly for me to always catch just by testing velocity. + // trimming the Z velocity a bit seems to help quite a bit. + vecTestVelocity = pev->velocity; + vecTestVelocity.z *= 0.45; + + if ( !m_fRegisteredSound && vecTestVelocity.Length() <= 60 ) + { + //ALERT( at_console, "Grenade Registered!: %f\n", vecTestVelocity.Length() ); + + // grenade is moving really slow. It's probably very close to where it will ultimately stop moving. + // go ahead and emit the danger sound. + + // register a radius louder than the explosion, so we make sure everyone gets out of the way + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin, static_cast(pev->dmg / 0.4), 0.3 ); + m_fRegisteredSound = TRUE; + } + + if (pev->flags & FL_ONGROUND) + { + // add a bit of static friction + pev->velocity = pev->velocity * 0.8; + + pev->sequence = RANDOM_LONG( 1, 1 ); + } + else + { + // play bounce sound + BounceSound(); + } + pev->framerate = pev->velocity.Length() / 200.0; + if (pev->framerate > 1.0) + pev->framerate = 1; + else if (pev->framerate < 0.5) + pev->framerate = 0; + +} + + + +void CGrenade::SlideTouch( CBaseEntity *pOther ) +{ + // don't hit the guy that launched this grenade + if ( pOther->edict() == pev->owner ) + return; + + // pev->avelocity = Vector (300, 300, 300); + + if (pev->flags & FL_ONGROUND) + { + // add a bit of static friction + pev->velocity = pev->velocity * 0.95; + + if (pev->velocity.x != 0 || pev->velocity.y != 0) + { + // maintain sliding sound + } + } + else + { + BounceSound(); + } +} + +void CGrenade :: BounceSound( void ) +{ + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/grenade_hit1.wav", 0.25, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/grenade_hit2.wav", 0.25, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/grenade_hit3.wav", 0.25, ATTN_NORM); break; + } +} + +void CGrenade :: TumbleThink( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + StudioFrameAdvance( ); + 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( &CGrenade::Detonate ); + } + if (pev->waterlevel != 0) + { + pev->velocity = pev->velocity * 0.5; + pev->framerate = 0.2; + } +} + + +void CGrenade:: Spawn( void ) +{ + pev->movetype = MOVETYPE_BOUNCE; + pev->classname = MAKE_STRING( "grenade" ); + + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/grenade.mdl"); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + + pev->dmg = 100; + m_fRegisteredSound = FALSE; +} + + +CGrenade *CGrenade::ShootContact( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ) +{ + CGrenade *pGrenade = GetClassPtr( (CGrenade *)NULL ); + pGrenade->Spawn(); + // contact grenades arc lower + pGrenade->pev->gravity = 0.5;// lower gravity since grenade is aerodynamic and engine doesn't know it. + UTIL_SetOrigin( pGrenade->pev, vecStart ); + pGrenade->pev->velocity = vecVelocity; + pGrenade->pev->angles = UTIL_VecToAngles (pGrenade->pev->velocity); + pGrenade->pev->owner = ENT(pevOwner); + + // make monsters afaid of it while in the air + pGrenade->SetThink( &CGrenade::DangerSoundThink ); + pGrenade->pev->nextthink = gpGlobals->time; + + // Tumble in air + pGrenade->pev->avelocity.x = RANDOM_FLOAT ( -100, -500 ); + + // Explode on contact + pGrenade->SetTouch( &CGrenade::ExplodeTouch ); + + pGrenade->pev->dmg = gSkillData.plrDmgM203Grenade; + + return pGrenade; +} + + +CGrenade * CGrenade:: ShootTimed( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time ) +{ + CGrenade *pGrenade = GetClassPtr( (CGrenade *)NULL ); + pGrenade->Spawn(); + UTIL_SetOrigin( pGrenade->pev, vecStart ); + pGrenade->pev->velocity = vecVelocity; + pGrenade->pev->angles = UTIL_VecToAngles(pGrenade->pev->velocity); + pGrenade->pev->owner = ENT(pevOwner); + + pGrenade->SetTouch( &CGrenade::BounceTouch ); // Bounce if touched + + // 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(). + + pGrenade->pev->dmgtime = gpGlobals->time + time; + pGrenade->SetThink( &CGrenade::TumbleThink ); + pGrenade->pev->nextthink = gpGlobals->time + 0.1; + if (time < 0.1) + { + pGrenade->pev->nextthink = gpGlobals->time; + pGrenade->pev->velocity = Vector( 0, 0, 0 ); + } + + pGrenade->pev->sequence = RANDOM_LONG( 3, 6 ); + pGrenade->pev->framerate = 1.0; + + // Tumble through the air + // pGrenade->pev->avelocity.x = -400; + + pGrenade->pev->gravity = 0.5; + pGrenade->pev->friction = 0.8; + + SET_MODEL(ENT(pGrenade->pev), "models/w_grenade.mdl"); + pGrenade->pev->dmg = 100; + + return pGrenade; +} + + +CGrenade * CGrenade :: ShootSatchelCharge( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ) +{ + CGrenade *pGrenade = GetClassPtr( (CGrenade *)NULL ); + pGrenade->pev->movetype = MOVETYPE_BOUNCE; + pGrenade->pev->classname = MAKE_STRING( "grenade" ); + + pGrenade->pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pGrenade->pev), "models/grenade.mdl"); // Change this to satchel charge model + + UTIL_SetSize(pGrenade->pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + + pGrenade->pev->dmg = 200; + UTIL_SetOrigin( pGrenade->pev, vecStart ); + pGrenade->pev->velocity = vecVelocity; + pGrenade->pev->angles = g_vecZero; + pGrenade->pev->owner = ENT(pevOwner); + + // Detonate in "time" seconds + pGrenade->SetThink( &CBaseEntity::SUB_DoNothing ); + pGrenade->SetUse( &CGrenade::DetonateUse ); + pGrenade->SetTouch( &CGrenade::SlideTouch ); + pGrenade->pev->spawnflags = SF_DETONATE; + + pGrenade->pev->friction = 0.9; + + return pGrenade; +} + + + +void CGrenade :: UseSatchelCharges( entvars_t *pevOwner, SATCHELCODE code ) +{ + edict_t *pentFind; + edict_t *pentOwner; + + if ( !pevOwner ) + return; + + CBaseEntity *pOwner = CBaseEntity::Instance( pevOwner ); + + pentOwner = pOwner->edict(); + + pentFind = FIND_ENTITY_BY_CLASSNAME( NULL, "grenade" ); + while ( !FNullEnt( pentFind ) ) + { + CBaseEntity *pEnt = Instance( pentFind ); + if ( pEnt ) + { + if ( FBitSet( pEnt->pev->spawnflags, SF_DETONATE ) && pEnt->pev->owner == pentOwner ) + { + if ( code == SATCHEL_DETONATE ) + pEnt->Use( pOwner, pOwner, USE_ON, 0 ); + else // SATCHEL_RELEASE + pEnt->pev->owner = NULL; + } + } + pentFind = FIND_ENTITY_BY_CLASSNAME( pentFind, "grenade" ); + } +} + +//======================end grenade + diff --git a/hlsdk/ricochet/dlls/globals.cpp b/hlsdk/ricochet/dlls/globals.cpp new file mode 100644 index 00000000..9f6125cc --- /dev/null +++ b/hlsdk/ricochet/dlls/globals.cpp @@ -0,0 +1,39 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +/* + +===== globals.cpp ======================================================== + + DLL-wide global variable definitions. + They're all defined here, for convenient centralization. + Source files that need them should "extern ..." declare each + variable, to better document what globals they care about. + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "soundent.h" + +DLL_GLOBAL ULONG g_ulFrameCount; +DLL_GLOBAL ULONG g_ulModelIndexEyes; +DLL_GLOBAL ULONG g_ulModelIndexPlayer; +DLL_GLOBAL Vector g_vecAttackDir; +DLL_GLOBAL int g_iSkillLevel; +DLL_GLOBAL int gDisplayTitle; +DLL_GLOBAL BOOL g_fGameOver; +DLL_GLOBAL const Vector g_vecZero = Vector(0,0,0); +DLL_GLOBAL int g_Language; diff --git a/hlsdk/ricochet/dlls/h_ai.cpp b/hlsdk/ricochet/dlls/h_ai.cpp new file mode 100644 index 00000000..7eabdf83 --- /dev/null +++ b/hlsdk/ricochet/dlls/h_ai.cpp @@ -0,0 +1,198 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + + h_ai.cpp - halflife specific ai code + +*/ + + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" + +#define NUM_LATERAL_CHECKS 13 // how many checks are made on each side of a monster looking for lateral cover +#define NUM_LATERAL_LOS_CHECKS 6 // how many checks are made on each side of a monster looking for lateral cover + +//float flRandom = RANDOM_FLOAT(0,1); + +DLL_GLOBAL BOOL g_fDrawLines = FALSE; + +//========================================================= +// +// AI UTILITY FUNCTIONS +// +// !!!UNDONE - move CBaseMonster functions to monsters.cpp +//========================================================= + +//========================================================= +// FBoxVisible - a more accurate ( and slower ) version +// of FVisible. +// +// !!!UNDONE - make this CBaseMonster? +//========================================================= +BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget, Vector &vecTargetOrigin, float flSize ) +{ + // don't look through water + if ((pevLooker->waterlevel != 3 && pevTarget->waterlevel == 3) + || (pevLooker->waterlevel == 3 && pevTarget->waterlevel == 0)) + return FALSE; + + TraceResult tr; + Vector vecLookerOrigin = pevLooker->origin + pevLooker->view_ofs;//look through the monster's 'eyes' + for (int i = 0; i < 5; i++) + { + Vector vecTarget = pevTarget->origin; + vecTarget.x += RANDOM_FLOAT( pevTarget->mins.x + flSize, pevTarget->maxs.x - flSize); + vecTarget.y += RANDOM_FLOAT( pevTarget->mins.y + flSize, pevTarget->maxs.y - flSize); + vecTarget.z += RANDOM_FLOAT( pevTarget->mins.z + flSize, pevTarget->maxs.z - flSize); + + UTIL_TraceLine(vecLookerOrigin, vecTarget, ignore_monsters, ignore_glass, ENT(pevLooker)/*pentIgnore*/, &tr); + + if (tr.flFraction == 1.0) + { + vecTargetOrigin = vecTarget; + return TRUE;// line of sight is valid. + } + } + return FALSE;// Line of sight is not established +} + +// +// VecCheckToss - returns the velocity at which an object should be lobbed from vecspot1 to land near vecspot2. +// returns g_vecZero if toss is not feasible. +// +Vector VecCheckToss ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flGravityAdj ) +{ + TraceResult tr; + Vector vecMidPoint;// halfway point between Spot1 and Spot2 + Vector vecApex;// highest point + Vector vecScale; + Vector vecGrenadeVel; + Vector vecTemp; + float flGravity = CVAR_GET_FLOAT( "sv_gravity" ) * flGravityAdj; + + if (vecSpot2.z - vecSpot1.z > 500) + { + // to high, fail + return g_vecZero; + } + + UTIL_MakeVectors (pev->angles); + + // toss a little bit to the left or right, not right down on the enemy's bean (head). + vecSpot2 = vecSpot2 + gpGlobals->v_right * ( RANDOM_FLOAT(-8,8) + RANDOM_FLOAT(-16,16) ); + vecSpot2 = vecSpot2 + gpGlobals->v_forward * ( RANDOM_FLOAT(-8,8) + RANDOM_FLOAT(-16,16) ); + + // calculate the midpoint and apex of the 'triangle' + // UNDONE: normalize any Z position differences between spot1 and spot2 so that triangle is always RIGHT + + // How much time does it take to get there? + + // get a rough idea of how high it can be thrown + vecMidPoint = vecSpot1 + (vecSpot2 - vecSpot1) * 0.5; + UTIL_TraceLine(vecMidPoint, vecMidPoint + Vector(0,0,500), ignore_monsters, ENT(pev), &tr); + vecMidPoint = tr.vecEndPos; + // (subtract 15 so the grenade doesn't hit the ceiling) + vecMidPoint.z -= 15; + + if (vecMidPoint.z < vecSpot1.z || vecMidPoint.z < vecSpot2.z) + { + // to not enough space, fail + return g_vecZero; + } + + // How high should the grenade travel to reach the apex + float distance1 = (vecMidPoint.z - vecSpot1.z); + float distance2 = (vecMidPoint.z - vecSpot2.z); + + // How long will it take for the grenade to travel this distance + float time1 = sqrt( distance1 / (0.5 * flGravity) ); + float time2 = sqrt( distance2 / (0.5 * flGravity) ); + + if (time1 < 0.1) + { + // too close + return g_vecZero; + } + + // how hard to throw sideways to get there in time. + vecGrenadeVel = (vecSpot2 - vecSpot1) / (time1 + time2); + // how hard upwards to reach the apex at the right time. + vecGrenadeVel.z = flGravity * time1; + + // find the apex + vecApex = vecSpot1 + vecGrenadeVel * time1; + vecApex.z = vecMidPoint.z; + + UTIL_TraceLine(vecSpot1, vecApex, dont_ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + // UNDONE: either ignore monsters or change it to not care if we hit our enemy + UTIL_TraceLine(vecSpot2, vecApex, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + return vecGrenadeVel; +} + + +// +// VecCheckThrow - returns the velocity vector at which an object should be thrown from vecspot1 to hit vecspot2. +// returns g_vecZero if throw is not feasible. +// +Vector VecCheckThrow ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flSpeed, float flGravityAdj ) +{ + float flGravity = CVAR_GET_FLOAT( "sv_gravity" ) * flGravityAdj; + + Vector vecGrenadeVel = (vecSpot2 - vecSpot1); + + // throw at a constant time + float time = vecGrenadeVel.Length( ) / flSpeed; + vecGrenadeVel = vecGrenadeVel * (1.0 / time); + + // adjust upward toss to compensate for gravity loss + vecGrenadeVel.z += flGravity * time * 0.5; + + Vector vecApex = vecSpot1 + (vecSpot2 - vecSpot1) * 0.5; + vecApex.z += 0.5 * flGravity * (time * 0.5) * (time * 0.5); + + TraceResult tr; + UTIL_TraceLine(vecSpot1, vecApex, dont_ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + UTIL_TraceLine(vecSpot2, vecApex, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + return vecGrenadeVel; +} + + diff --git a/hlsdk/ricochet/dlls/h_battery.cpp b/hlsdk/ricochet/dlls/h_battery.cpp new file mode 100644 index 00000000..6f2e07ad --- /dev/null +++ b/hlsdk/ricochet/dlls/h_battery.cpp @@ -0,0 +1,200 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== h_battery.cpp ======================================================== + + battery-related code + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "skill.h" +#include "gamerules.h" + +class CRecharge : public CBaseToggle +{ +public: + void Spawn( ); + void Precache( void ); + void EXPORT Off(void); + void EXPORT Recharge(void); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return (CBaseToggle :: ObjectCaps() | FCAP_CONTINUOUS_USE) & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flNextCharge; + int m_iReactivate ; // DeathMatch Delay until reactvated + int m_iJuice; + int m_iOn; // 0 = off, 1 = startup, 2 = going + float m_flSoundTime; +}; + +TYPEDESCRIPTION CRecharge::m_SaveData[] = +{ + DEFINE_FIELD( CRecharge, m_flNextCharge, FIELD_TIME ), + DEFINE_FIELD( CRecharge, m_iReactivate, FIELD_INTEGER), + DEFINE_FIELD( CRecharge, m_iJuice, FIELD_INTEGER), + DEFINE_FIELD( CRecharge, m_iOn, FIELD_INTEGER), + DEFINE_FIELD( CRecharge, m_flSoundTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CRecharge, CBaseEntity ); + +LINK_ENTITY_TO_CLASS(func_recharge, CRecharge); + + +void CRecharge::KeyValue( KeyValueData *pkvd ) +{ + 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 if (FStrEq(pkvd->szKeyName, "dmdelay")) + { + m_iReactivate = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CRecharge::Spawn() +{ + Precache( ); + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); // set size and link into world + UTIL_SetSize(pev, pev->mins, pev->maxs); + SET_MODEL(ENT(pev), STRING(pev->model) ); + m_iJuice = static_cast(gSkillData.suitchargerCapacity); + pev->frame = 0; +} + +void CRecharge::Precache() +{ + PRECACHE_SOUND("items/suitcharge1.wav"); + PRECACHE_SOUND("items/suitchargeno1.wav"); + PRECACHE_SOUND("items/suitchargeok1.wav"); +} + + +void CRecharge::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // if it's not a player, ignore + if (!FClassnameIs(pActivator->pev, "player")) + return; + + // if there is no juice left, turn it off + if (m_iJuice <= 0) + { + pev->frame = 1; + Off(); + } + + // if the player doesn't have the suit, or there is no juice left, make the deny noise + if ((m_iJuice <= 0) || (!(pActivator->pev->weapons & (1<time) + { + m_flSoundTime = gpGlobals->time + 0.62; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/suitchargeno1.wav", 0.85, ATTN_NORM ); + } + return; + } + + pev->nextthink = pev->ltime + 0.25; + SetThink(&CRecharge::Off); + + // Time to recharge yet? + + if (m_flNextCharge >= gpGlobals->time) + return; + + // Make sure that we have a caller + if (!pActivator) + return; + + m_hActivator = pActivator; + + //only recharge the player + + if (!m_hActivator->IsPlayer() ) + return; + + // Play the on sound or the looping charging sound + if (!m_iOn) + { + m_iOn++; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/suitchargeok1.wav", 0.85, ATTN_NORM ); + m_flSoundTime = 0.56 + gpGlobals->time; + } + if ((m_iOn == 1) && (m_flSoundTime <= gpGlobals->time)) + { + m_iOn++; + EMIT_SOUND(ENT(pev), CHAN_STATIC, "items/suitcharge1.wav", 0.85, ATTN_NORM ); + } + + + // charge the player + if (m_hActivator->pev->armorvalue < 100) + { + m_iJuice--; + m_hActivator->pev->armorvalue += 1; + + if (m_hActivator->pev->armorvalue > 100) + m_hActivator->pev->armorvalue = 100; + } + + // govern the rate of charge + m_flNextCharge = gpGlobals->time + 0.1; +} + +void CRecharge::Recharge(void) +{ + m_iJuice = static_cast(gSkillData.suitchargerCapacity); + pev->frame = 0; + SetThink( &CBaseEntity::SUB_DoNothing ); +} + +void CRecharge::Off(void) +{ + // Stop looping sound. + if (m_iOn > 1) + STOP_SOUND( ENT(pev), CHAN_STATIC, "items/suitcharge1.wav" ); + + m_iOn = 0; + + if ((!m_iJuice) && ( ( m_iReactivate = static_cast(g_pGameRules->FlHEVChargerRechargeTime()) ) > 0) ) + { + pev->nextthink = pev->ltime + m_iReactivate; + SetThink(&CRecharge::Recharge); + } + else + SetThink( &CBaseEntity::SUB_DoNothing ); +} diff --git a/hlsdk/ricochet/dlls/h_cycler.cpp b/hlsdk/ricochet/dlls/h_cycler.cpp new file mode 100644 index 00000000..47a5e54c --- /dev/null +++ b/hlsdk/ricochet/dlls/h_cycler.cpp @@ -0,0 +1,471 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== h_cycler.cpp ======================================================== + + The Halflife Cycler Monsters + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "animation.h" +#include "weapons.h" +#include "player.h" + + +#define TEMP_FOR_SCREEN_SHOTS +#ifdef TEMP_FOR_SCREEN_SHOTS //=================================================== + +class CCycler : public CBaseMonster +{ +public: + void GenericCyclerSpawn(char *szModel, Vector vecMin, Vector vecMax); + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() | FCAP_IMPULSE_USE); } + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void Spawn( void ); + void Think( void ); + //void Pain( float flDamage ); + void Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + // Don't treat as a live target + virtual BOOL IsAlive( void ) { return FALSE; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_animate; +}; + +TYPEDESCRIPTION CCycler::m_SaveData[] = +{ + DEFINE_FIELD( CCycler, m_animate, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CCycler, CBaseMonster ); + + +// +// we should get rid of all the other cyclers and replace them with this. +// +class CGenericCycler : public CCycler +{ +public: + void Spawn( void ) { GenericCyclerSpawn( (char *)STRING(pev->model), Vector(-16, -16, 0), Vector(16, 16, 72) ); } +}; +LINK_ENTITY_TO_CLASS( cycler, CGenericCycler ); + + + +// Probe droid imported for tech demo compatibility +// +// PROBE DROID +// +class CCyclerProbe : public CCycler +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS( cycler_prdroid, CCyclerProbe ); +void CCyclerProbe :: Spawn( void ) +{ + pev->origin = pev->origin + Vector ( 0, 0, 16 ); + GenericCyclerSpawn( "models/prdroid.mdl", Vector(-16,-16,-16), Vector(16,16,16)); +} + + + +// Cycler member functions + +void CCycler :: GenericCyclerSpawn(char *szModel, Vector vecMin, Vector vecMax) +{ + if (!szModel || !*szModel) + { + ALERT(at_error, "cycler at %.0f %.0f %0.f missing modelname", pev->origin.x, pev->origin.y, pev->origin.z ); + REMOVE_ENTITY(ENT(pev)); + return; + } + + pev->classname = MAKE_STRING("cycler"); + PRECACHE_MODEL( szModel ); + SET_MODEL(ENT(pev), szModel); + + CCycler::Spawn( ); + + UTIL_SetSize(pev, vecMin, vecMax); +} + + +void CCycler :: Spawn( ) +{ + InitBoneControllers(); + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_NONE; + pev->takedamage = DAMAGE_YES; + pev->effects = 0; + pev->health = 80000;// no cycler should die + pev->yaw_speed = 5; + pev->ideal_yaw = pev->angles.y; + ChangeYaw( 360 ); + + m_flFrameRate = 75; + m_flGroundSpeed = 0; + + pev->nextthink += 1.0; + + ResetSequenceInfo( ); + + if (pev->sequence != 0 || pev->frame != 0) + { + m_animate = 0; + pev->framerate = 0; + } + else + { + m_animate = 1; + } +} + + + + +// +// cycler think +// +void CCycler :: Think( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + if (m_animate) + { + StudioFrameAdvance ( ); + } + if (m_fSequenceFinished && !m_fSequenceLoops) + { + // ResetSequenceInfo(); + // hack to avoid reloading model every frame + pev->animtime = gpGlobals->time; + pev->framerate = 1.0; + m_fSequenceFinished = FALSE; + m_flLastEventCheck = gpGlobals->time; + pev->frame = 0; + if (!m_animate) + pev->framerate = 0.0; // FIX: don't reset framerate + } +} + +// +// CyclerUse - starts a rotation trend +// +void CCycler :: Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_animate = !m_animate; + if (m_animate) + pev->framerate = 1.0; + else + pev->framerate = 0.0; +} + +// +// CyclerPain , changes sequences when shot +// +//void CCycler :: Pain( float flDamage ) +int CCycler :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if (m_animate) + { + pev->sequence++; + + ResetSequenceInfo( ); + + if (m_flFrameRate == 0.0) + { + pev->sequence = 0; + ResetSequenceInfo( ); + } + pev->frame = 0; + } + else + { + pev->framerate = 1.0; + StudioFrameAdvance ( 0.1 ); + pev->framerate = 0; + ALERT( at_console, "sequence: %d, frame %.0f\n", pev->sequence, pev->frame ); + } + + return 0; +} + +#endif + + +class CCyclerSprite : public CBaseEntity +{ +public: + void Spawn( void ); + void Think( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() | FCAP_DONT_SAVE | FCAP_IMPULSE_USE); } + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void Animate( float frames ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + inline int ShouldAnimate( void ) { return m_animate && m_maxFrame > 1.0; } + int m_animate; + float m_lastTime; + float m_maxFrame; +}; + +LINK_ENTITY_TO_CLASS( cycler_sprite, CCyclerSprite ); + +TYPEDESCRIPTION CCyclerSprite::m_SaveData[] = +{ + DEFINE_FIELD( CCyclerSprite, m_animate, FIELD_INTEGER ), + DEFINE_FIELD( CCyclerSprite, m_lastTime, FIELD_TIME ), + DEFINE_FIELD( CCyclerSprite, m_maxFrame, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CCyclerSprite, CBaseEntity ); + + +void CCyclerSprite::Spawn( void ) +{ + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_NONE; + pev->takedamage = DAMAGE_YES; + pev->effects = 0; + + pev->frame = 0; + pev->nextthink = gpGlobals->time + 0.1; + m_animate = 1; + m_lastTime = gpGlobals->time; + + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; +} + + +void CCyclerSprite::Think( void ) +{ + if ( ShouldAnimate() ) + Animate( pev->framerate * (gpGlobals->time - m_lastTime) ); + + pev->nextthink = gpGlobals->time + 0.1; + m_lastTime = gpGlobals->time; +} + + +void CCyclerSprite::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_animate = !m_animate; + ALERT( at_console, "Sprite: %s\n", STRING(pev->model) ); +} + + +int CCyclerSprite::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( m_maxFrame > 1.0 ) + { + Animate( 1.0 ); + } + return 1; +} + +void CCyclerSprite::Animate( float frames ) +{ + pev->frame += frames; + if ( m_maxFrame > 0 ) + pev->frame = fmod( pev->frame, m_maxFrame ); +} + + + + + + + +class CWeaponCycler : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + int iItemSlot( void ) { return 1; } + int GetItemInfo(ItemInfo *p) {return 0; } + + void PrimaryAttack( void ); + void SecondaryAttack( void ); + BOOL Deploy( void ); + void Holster( int skiplocal = 0 ); + int m_iszModel; + int m_iModel; +}; +LINK_ENTITY_TO_CLASS( cycler_weapon, CWeaponCycler ); + + +void CWeaponCycler::Spawn( ) +{ + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_NONE; + + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL( ENT(pev), STRING(pev->model) ); + m_iszModel = pev->model; + m_iModel = pev->modelindex; + + UTIL_SetOrigin( pev, pev->origin ); + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 16)); + SetTouch( &CBasePlayerItem::DefaultTouch ); +} + + + +BOOL CWeaponCycler::Deploy( ) +{ + m_pPlayer->pev->viewmodel = m_iszModel; + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.0; + SendWeaponAnim( 0 ); + m_iClip = 0; + return TRUE; +} + + +void CWeaponCycler::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; +} + + +void CWeaponCycler::PrimaryAttack() +{ + + SendWeaponAnim( pev->sequence ); + + m_flNextPrimaryAttack = gpGlobals->time + 0.3; +} + + +void CWeaponCycler::SecondaryAttack( void ) +{ + float flFrameRate, flGroundSpeed; + + pev->sequence = (pev->sequence + 1) % 8; + + pev->modelindex = m_iModel; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + GetSequenceInfo( pmodel, pev, &flFrameRate, &flGroundSpeed ); + pev->modelindex = 0; + + if (flFrameRate == 0.0) + { + pev->sequence = 0; + } + + SendWeaponAnim( pev->sequence ); + + m_flNextSecondaryAttack = gpGlobals->time + 0.3; +} + + + +// Flaming Wreakage +class CWreckage : public CBaseMonster +{ + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + void Think( void ); + + int m_flStartTime; +}; +TYPEDESCRIPTION CWreckage::m_SaveData[] = +{ + DEFINE_FIELD( CWreckage, m_flStartTime, FIELD_TIME ), +}; +IMPLEMENT_SAVERESTORE( CWreckage, CBaseMonster ); + + +LINK_ENTITY_TO_CLASS( cycler_wreckage, CWreckage ); + +void CWreckage::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->takedamage = 0; + pev->effects = 0; + + pev->frame = 0; + pev->nextthink = gpGlobals->time + 0.1; + + if (pev->model) + { + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL( ENT(pev), STRING(pev->model) ); + } + // pev->scale = 5.0; + + m_flStartTime = static_cast(gpGlobals->time); +} + +void CWreckage::Precache( ) +{ + if ( pev->model ) + PRECACHE_MODEL( (char *)STRING(pev->model) ); +} + +void CWreckage::Think( void ) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.2; + + if (pev->dmgtime) + { + if (pev->dmgtime < gpGlobals->time) + { + UTIL_Remove( this ); + return; + } + else if (RANDOM_FLOAT( 0, pev->dmgtime - m_flStartTime ) > pev->dmgtime - gpGlobals->time) + { + return; + } + } + + Vector VecSrc; + + VecSrc.x = RANDOM_FLOAT( pev->absmin.x, pev->absmax.x ); + VecSrc.y = RANDOM_FLOAT( pev->absmin.y, pev->absmax.y ); + VecSrc.z = RANDOM_FLOAT( pev->absmin.z, pev->absmax.z ); + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, VecSrc ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( VecSrc.x ); + WRITE_COORD( VecSrc.y ); + WRITE_COORD( VecSrc.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( RANDOM_LONG(0,49) + 50 ); // scale * 10 + WRITE_BYTE( RANDOM_LONG(0, 3) + 8 ); // framerate + MESSAGE_END(); +} diff --git a/hlsdk/ricochet/dlls/h_export.cpp b/hlsdk/ricochet/dlls/h_export.cpp new file mode 100644 index 00000000..99970644 --- /dev/null +++ b/hlsdk/ricochet/dlls/h_export.cpp @@ -0,0 +1,68 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== h_export.cpp ======================================================== + + Entity classes exported by Halflife. + +*/ + +#include "extdll.h" +#include "util.h" + +#include "cbase.h" + +// Holds engine functionality callbacks +enginefuncs_t g_engfuncs; +globalvars_t *gpGlobals; + +#ifdef _WIN32 + +// Required DLL entry point +BOOL WINAPI DllMain( + HINSTANCE hinstDLL, + DWORD fdwReason, + LPVOID lpvReserved) +{ + if (fdwReason == DLL_PROCESS_ATTACH) + { + } + else if (fdwReason == DLL_PROCESS_DETACH) + { + } + return TRUE; +} + + +void DLLEXPORT GiveFnptrsToDll( enginefuncs_t* pengfuncsFromEngine, globalvars_t *pGlobals ) +{ + memcpy(&g_engfuncs, pengfuncsFromEngine, sizeof(enginefuncs_t)); + gpGlobals = pGlobals; +} + +#else + +extern "C" { + +void GiveFnptrsToDll( enginefuncs_t* pengfuncsFromEngine, globalvars_t *pGlobals ) +{ + memcpy(&g_engfuncs, pengfuncsFromEngine, sizeof(enginefuncs_t)); + gpGlobals = pGlobals; +} + +} + +#endif diff --git a/hlsdk/ricochet/dlls/healthkit.cpp b/hlsdk/ricochet/dlls/healthkit.cpp new file mode 100644 index 00000000..7091e9c8 --- /dev/null +++ b/hlsdk/ricochet/dlls/healthkit.cpp @@ -0,0 +1,259 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "items.h" +#include "gamerules.h" + +extern int gmsgItemPickup; + +class CHealthKit : public CItem +{ + void Spawn( void ); + void Precache( void ); + BOOL MyTouch( CBasePlayer *pPlayer ); + +/* + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; +*/ + +}; + + +LINK_ENTITY_TO_CLASS( item_healthkit, CHealthKit ); + +/* +TYPEDESCRIPTION CHealthKit::m_SaveData[] = +{ + +}; + + +IMPLEMENT_SAVERESTORE( CHealthKit, CItem); +*/ + +void CHealthKit :: Spawn( void ) +{ + Precache( ); + SET_MODEL(ENT(pev), "models/w_medkit.mdl"); + + CItem::Spawn(); +} + +void CHealthKit::Precache( void ) +{ + PRECACHE_MODEL("models/w_medkit.mdl"); + PRECACHE_SOUND("items/smallmedkit1.wav"); +} + +BOOL CHealthKit::MyTouch( CBasePlayer *pPlayer ) +{ + if ( pPlayer->TakeHealth( gSkillData.healthkitCapacity, DMG_GENERIC ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgItemPickup, NULL, pPlayer->pev ); + WRITE_STRING( STRING(pev->classname) ); + MESSAGE_END(); + + EMIT_SOUND(ENT(pPlayer->pev), CHAN_ITEM, "items/smallmedkit1.wav", 1, ATTN_NORM); + + if ( g_pGameRules->ItemShouldRespawn( this ) ) + { + Respawn(); + } + else + { + UTIL_Remove(this); + } + + return TRUE; + } + + return FALSE; +} + + + +//------------------------------------------------------------- +// Wall mounted health kit +//------------------------------------------------------------- +class CWallHealth : public CBaseToggle +{ +public: + void Spawn( ); + void Precache( void ); + void EXPORT Off(void); + void EXPORT Recharge(void); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return (CBaseToggle :: ObjectCaps() | FCAP_CONTINUOUS_USE) & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flNextCharge; + int m_iReactivate ; // DeathMatch Delay until reactvated + int m_iJuice; + int m_iOn; // 0 = off, 1 = startup, 2 = going + float m_flSoundTime; +}; + +TYPEDESCRIPTION CWallHealth::m_SaveData[] = +{ + DEFINE_FIELD( CWallHealth, m_flNextCharge, FIELD_TIME), + DEFINE_FIELD( CWallHealth, m_iReactivate, FIELD_INTEGER), + DEFINE_FIELD( CWallHealth, m_iJuice, FIELD_INTEGER), + DEFINE_FIELD( CWallHealth, m_iOn, FIELD_INTEGER), + DEFINE_FIELD( CWallHealth, m_flSoundTime, FIELD_TIME), +}; + +IMPLEMENT_SAVERESTORE( CWallHealth, CBaseEntity ); + +LINK_ENTITY_TO_CLASS(func_healthcharger, CWallHealth); + + +void CWallHealth::KeyValue( KeyValueData *pkvd ) +{ + 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 if (FStrEq(pkvd->szKeyName, "dmdelay")) + { + m_iReactivate = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CWallHealth::Spawn() +{ + Precache( ); + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); // set size and link into world + UTIL_SetSize(pev, pev->mins, pev->maxs); + SET_MODEL(ENT(pev), STRING(pev->model) ); + m_iJuice = static_cast(gSkillData.healthchargerCapacity); + pev->frame = 0; + +} + +void CWallHealth::Precache() +{ + PRECACHE_SOUND("items/medshot4.wav"); + PRECACHE_SOUND("items/medshotno1.wav"); + PRECACHE_SOUND("items/medcharge4.wav"); +} + + +void CWallHealth::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Make sure that we have a caller + if (!pActivator) + return; + // if it's not a player, ignore + if ( !pActivator->IsPlayer() ) + return; + + // if there is no juice left, turn it off + if (m_iJuice <= 0) + { + pev->frame = 1; + Off(); + } + + // if the player doesn't have the suit, or there is no juice left, make the deny noise + if ((m_iJuice <= 0) || (!(pActivator->pev->weapons & (1<time) + { + m_flSoundTime = gpGlobals->time + 0.62; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/medshotno1.wav", 1.0, ATTN_NORM ); + } + return; + } + + pev->nextthink = pev->ltime + 0.25; + SetThink(&CWallHealth::Off); + + // Time to recharge yet? + + if (m_flNextCharge >= gpGlobals->time) + return; + + // Play the on sound or the looping charging sound + if (!m_iOn) + { + m_iOn++; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/medshot4.wav", 1.0, ATTN_NORM ); + m_flSoundTime = 0.56 + gpGlobals->time; + } + if ((m_iOn == 1) && (m_flSoundTime <= gpGlobals->time)) + { + m_iOn++; + EMIT_SOUND(ENT(pev), CHAN_STATIC, "items/medcharge4.wav", 1.0, ATTN_NORM ); + } + + + // charge the player + if ( pActivator->TakeHealth( 1, DMG_GENERIC ) ) + { + m_iJuice--; + } + + // govern the rate of charge + m_flNextCharge = gpGlobals->time + 0.1; +} + +void CWallHealth::Recharge(void) +{ + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/medshot4.wav", 1.0, ATTN_NORM ); + m_iJuice = static_cast(gSkillData.healthchargerCapacity); + pev->frame = 0; + SetThink( &CBaseEntity::SUB_DoNothing ); +} + +void CWallHealth::Off(void) +{ + // Stop looping sound. + if (m_iOn > 1) + STOP_SOUND( ENT(pev), CHAN_STATIC, "items/medcharge4.wav" ); + + m_iOn = 0; + + if ((!m_iJuice) && ( ( m_iReactivate = static_cast(g_pGameRules->FlHealthChargerRechargeTime()) ) > 0) ) + { + pev->nextthink = pev->ltime + m_iReactivate; + SetThink(&CWallHealth::Recharge); + } + else + SetThink( &CBaseEntity::SUB_DoNothing ); +} diff --git a/hlsdk/ricochet/dlls/items.cpp b/hlsdk/ricochet/dlls/items.cpp new file mode 100644 index 00000000..ca168881 --- /dev/null +++ b/hlsdk/ricochet/dlls/items.cpp @@ -0,0 +1,337 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== items.cpp ======================================================== + + functions governing the selection/use of weapons for players + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "player.h" +#include "skill.h" +#include "items.h" +#include "gamerules.h" + +extern int gmsgItemPickup; + +class CWorldItem : public CBaseEntity +{ +public: + void KeyValue(KeyValueData *pkvd ); + void Spawn( void ); + int m_iType; +}; + +LINK_ENTITY_TO_CLASS(world_items, CWorldItem); + +void CWorldItem::KeyValue(KeyValueData *pkvd) +{ + if (FStrEq(pkvd->szKeyName, "type")) + { + m_iType = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CWorldItem::Spawn( void ) +{ + CBaseEntity *pEntity = NULL; + + 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) + { + ALERT( at_console, "unable to create world_item %d\n", m_iType ); + } + else + { + pEntity->pev->target = pev->target; + pEntity->pev->targetname = pev->targetname; + pEntity->pev->spawnflags = pev->spawnflags; + } + + REMOVE_ENTITY(edict()); +} + + +void CItem::Spawn( void ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + UTIL_SetOrigin( pev, pev->origin ); + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 16)); + SetTouch(&CItem::ItemTouch); + + if (DROP_TO_FLOOR(ENT(pev)) == 0) + { + ALERT(at_error, "Item %s fell out of level at %f,%f,%f", STRING( pev->classname ), pev->origin.x, pev->origin.y, pev->origin.z); + UTIL_Remove( this ); + return; + } +} + +extern int gEvilImpulse101; + +void CItem::ItemTouch( CBaseEntity *pOther ) +{ + // if it's not a player, ignore + if ( !pOther->IsPlayer() ) + { + return; + } + + CBasePlayer *pPlayer = (CBasePlayer *)pOther; + + // ok, a player is touching this item, but can he have it? + if ( !g_pGameRules->CanHaveItem( pPlayer, this ) ) + { + // no? Ignore the touch. + return; + } + + if (MyTouch( pPlayer )) + { + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); + SetTouch( NULL ); + + // player grabbed the item. + g_pGameRules->PlayerGotItem( pPlayer, this ); + if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_YES ) + { + Respawn(); + } + else + { + UTIL_Remove( this ); + } + } + else if (gEvilImpulse101) + { + UTIL_Remove( this ); + } +} + +CBaseEntity* CItem::Respawn( void ) +{ + SetTouch( NULL ); + pev->effects |= EF_NODRAW; + + UTIL_SetOrigin( pev, g_pGameRules->VecItemRespawnSpot( this ) );// blip to whereever you should respawn. + + SetThink ( &CItem::Materialize ); + pev->nextthink = g_pGameRules->FlItemRespawnTime( this ); + return this; +} + +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; + } + + SetTouch( &CItem::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->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/hlsdk/ricochet/dlls/items.h b/hlsdk/ricochet/dlls/items.h new file mode 100644 index 00000000..3fbb370a --- /dev/null +++ b/hlsdk/ricochet/dlls/items.h @@ -0,0 +1,29 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +#ifndef ITEMS_H +#define ITEMS_H + + +class CItem : public CBaseEntity +{ +public: + void Spawn( void ); + CBaseEntity* Respawn( void ); + void EXPORT ItemTouch( CBaseEntity *pOther ); + void EXPORT Materialize( void ); + virtual BOOL MyTouch( CBasePlayer *pPlayer ) { return FALSE; }; +}; + +#endif // ITEMS_H diff --git a/hlsdk/ricochet/dlls/lights.cpp b/hlsdk/ricochet/dlls/lights.cpp new file mode 100644 index 00000000..b9488b5e --- /dev/null +++ b/hlsdk/ricochet/dlls/lights.cpp @@ -0,0 +1,199 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== lights.cpp ======================================================== + + spawn and think functions for editor-placed lights + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" + + + +class CLight : public CPointEntity +{ +public: + virtual void KeyValue( KeyValueData* pkvd ); + virtual void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_iStyle; + int m_iszPattern; +}; +LINK_ENTITY_TO_CLASS( light, CLight ); + +TYPEDESCRIPTION CLight::m_SaveData[] = +{ + DEFINE_FIELD( CLight, m_iStyle, FIELD_INTEGER ), + DEFINE_FIELD( CLight, m_iszPattern, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CLight, CPointEntity ); + + +// +// Cache user-entity-field values until spawn is called. +// +void CLight :: KeyValue( KeyValueData* pkvd) +{ + if (FStrEq(pkvd->szKeyName, "style")) + { + m_iStyle = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitch")) + { + pev->angles.x = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pattern")) + { + m_iszPattern = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + { + CPointEntity::KeyValue( pkvd ); + } +} + +/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) LIGHT_START_OFF +Non-displayed light. +Default light value is 300 +Default style is 0 +If targeted, it will toggle between on or off. +*/ + +void CLight :: Spawn( void ) +{ + if (FStringNull(pev->targetname)) + { // inert light + REMOVE_ENTITY(ENT(pev)); + return; + } + + if (m_iStyle >= 32) + { +// CHANGE_METHOD(ENT(pev), em_use, light_use); + if (FBitSet(pev->spawnflags, SF_LIGHT_START_OFF)) + LIGHT_STYLE(m_iStyle, "a"); + else if (m_iszPattern) + LIGHT_STYLE(m_iStyle, (char *)STRING( m_iszPattern )); + else + LIGHT_STYLE(m_iStyle, "m"); + } +} + + +void CLight :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (m_iStyle >= 32) + { + if ( !ShouldToggle( useType, !FBitSet(pev->spawnflags, SF_LIGHT_START_OFF) ) ) + return; + + if (FBitSet(pev->spawnflags, SF_LIGHT_START_OFF)) + { + if (m_iszPattern) + LIGHT_STYLE(m_iStyle, (char *)STRING( m_iszPattern )); + else + LIGHT_STYLE(m_iStyle, "m"); + ClearBits(pev->spawnflags, SF_LIGHT_START_OFF); + } + else + { + LIGHT_STYLE(m_iStyle, "a"); + SetBits(pev->spawnflags, SF_LIGHT_START_OFF); + } + } +} + +// +// shut up spawn functions for new spotlights +// +LINK_ENTITY_TO_CLASS( light_spot, CLight ); + + +class CEnvLight : public CLight +{ +public: + void KeyValue( KeyValueData* pkvd ); + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( light_environment, CEnvLight ); + +void CEnvLight::KeyValue( KeyValueData* pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "_light")) + { + int r, g, b, v, j; + char szColor[64]; + j = sscanf( pkvd->szValue, "%d %d %d %d\n", &r, &g, &b, &v ); + if (j == 1) + { + g = b = r; + } + else if (j == 4) + { + r = r * (v / 255); + g = g * (v / 255); + b = b * (v / 255); + } + + // simulate qrad direct, ambient,and gamma adjustments, as well as engine scaling + r = static_cast(powf( r / 114.0f, 0.6f ) * 264); + g = static_cast(powf( g / 114.0f, 0.6f ) * 264); + b = static_cast(powf( b / 114.0f, 0.6f ) * 264); + + pkvd->fHandled = TRUE; + sprintf( szColor, "%d", r ); + CVAR_SET_STRING( "sv_skycolor_r", szColor ); + sprintf( szColor, "%d", g ); + CVAR_SET_STRING( "sv_skycolor_g", szColor ); + sprintf( szColor, "%d", b ); + CVAR_SET_STRING( "sv_skycolor_b", szColor ); + } + else + { + CLight::KeyValue( pkvd ); + } +} + + +void CEnvLight :: Spawn( void ) +{ + char szVector[64]; + UTIL_MakeAimVectors( pev->angles ); + + sprintf( szVector, "%f", gpGlobals->v_forward.x ); + CVAR_SET_STRING( "sv_skyvec_x", szVector ); + sprintf( szVector, "%f", gpGlobals->v_forward.y ); + CVAR_SET_STRING( "sv_skyvec_y", szVector ); + sprintf( szVector, "%f", gpGlobals->v_forward.z ); + CVAR_SET_STRING( "sv_skyvec_z", szVector ); + + CLight::Spawn( ); +} diff --git a/hlsdk/ricochet/dlls/maprules.cpp b/hlsdk/ricochet/dlls/maprules.cpp new file mode 100644 index 00000000..a07562df --- /dev/null +++ b/hlsdk/ricochet/dlls/maprules.cpp @@ -0,0 +1,918 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +// ------------------------------------------- +// +// maprules.cpp +// +// This module contains entities for implementing/changing game +// rules dynamically within each map (.BSP) +// +// ------------------------------------------- + +#include "extdll.h" +#include "eiface.h" +#include "util.h" +#include "gamerules.h" +#include "maprules.h" +#include "cbase.h" +#include "player.h" + +class CRuleEntity : public CBaseEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void SetMaster( int iszMaster ) { m_iszMaster = iszMaster; } + +protected: + BOOL CanFireForActivator( CBaseEntity *pActivator ); + +private: + string_t m_iszMaster; +}; + +TYPEDESCRIPTION CRuleEntity::m_SaveData[] = +{ + DEFINE_FIELD( CRuleEntity, m_iszMaster, FIELD_STRING), +}; + +IMPLEMENT_SAVERESTORE( CRuleEntity, CBaseEntity ); + + +void CRuleEntity::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = EF_NODRAW; +} + + +void CRuleEntity::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "master")) + { + SetMaster( ALLOC_STRING(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +BOOL CRuleEntity::CanFireForActivator( CBaseEntity *pActivator ) +{ + if ( m_iszMaster ) + { + if ( UTIL_IsMasterTriggered( m_iszMaster, pActivator ) ) + return TRUE; + else + return FALSE; + } + + return TRUE; +} + +// +// CRulePointEntity -- base class for all rule "point" entities (not brushes) +// +class CRulePointEntity : public CRuleEntity +{ +public: + void Spawn( void ); +}; + +void CRulePointEntity::Spawn( void ) +{ + CRuleEntity::Spawn(); + pev->frame = 0; + pev->model = 0; +} + +// +// CRuleBrushEntity -- base class for all rule "brush" entities (not brushes) +// Default behavior is to set up like a trigger, invisible, but keep the model for volume testing +// +class CRuleBrushEntity : public CRuleEntity +{ +public: + void Spawn( void ); + +private: +}; + +void CRuleBrushEntity::Spawn( void ) +{ + SET_MODEL( edict(), STRING(pev->model) ); + CRuleEntity::Spawn(); +} + + +// CGameScore / game_score -- award points to player / team +// Points +/- total +// Flag: Allow negative scores SF_SCORE_NEGATIVE +// Flag: Award points to team in teamplay SF_SCORE_TEAM + +#define SF_SCORE_NEGATIVE 0x0001 +#define SF_SCORE_TEAM 0x0002 + +class CGameScore : public CRulePointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline int Points( void ) { return static_cast(pev->frags); } + inline BOOL AllowNegativeScore( void ) { return pev->spawnflags & SF_SCORE_NEGATIVE; } + inline BOOL AwardToTeam( void ) { return pev->spawnflags & SF_SCORE_TEAM; } + + inline void SetPoints( int points ) { pev->frags = points; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_score, CGameScore ); + + +void CGameScore::Spawn( void ) +{ + CRulePointEntity::Spawn(); +} + + +void CGameScore::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "points")) + { + SetPoints( atoi(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CRulePointEntity::KeyValue( pkvd ); +} + + + +void CGameScore::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + // Only players can use this + if ( pActivator->IsPlayer() ) + { + if ( AwardToTeam() ) + { + pActivator->AddPointsToTeam( Points(), AllowNegativeScore() ); + } + else + { + pActivator->AddPoints( Points(), AllowNegativeScore() ); + } + } +} + + +// CGameEnd / game_end -- Ends the game in MP + +class CGameEnd : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +private: +}; + +LINK_ENTITY_TO_CLASS( game_end, CGameEnd ); + + +void CGameEnd::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + g_pGameRules->EndMultiplayerGame(); +} + + +// +// CGameText / game_text -- NON-Localized HUD Message (use env_message to display a titles.txt message) +// Flag: All players SF_ENVTEXT_ALLPLAYERS +// + + +#define SF_ENVTEXT_ALLPLAYERS 0x0001 + + +class CGameText : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + inline BOOL MessageToAll( void ) { return (pev->spawnflags & SF_ENVTEXT_ALLPLAYERS); } + inline void MessageSet( const char *pMessage ) { pev->message = ALLOC_STRING(pMessage); } + inline const char *MessageGet( void ) { return STRING(pev->message); } + +private: + + hudtextparms_t m_textParms; +}; + +LINK_ENTITY_TO_CLASS( game_text, CGameText ); + +// Save parms as a block. Will break save/restore if the structure changes, but this entity didn't ship with Half-Life, so +// it can't impact saved Half-Life games. +TYPEDESCRIPTION CGameText::m_SaveData[] = +{ + DEFINE_ARRAY( CGameText, m_textParms, FIELD_CHARACTER, sizeof(hudtextparms_t) ), +}; + +IMPLEMENT_SAVERESTORE( CGameText, CRulePointEntity ); + + +void CGameText::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "channel")) + { + m_textParms.channel = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "x")) + { + m_textParms.x = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "y")) + { + m_textParms.y = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "effect")) + { + m_textParms.effect = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "color")) + { + int color[4]; + UTIL_StringToIntArray( color, 4, pkvd->szValue ); + m_textParms.r1 = color[0]; + m_textParms.g1 = color[1]; + m_textParms.b1 = color[2]; + m_textParms.a1 = color[3]; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "color2")) + { + int color[4]; + UTIL_StringToIntArray( color, 4, pkvd->szValue ); + m_textParms.r2 = color[0]; + m_textParms.g2 = color[1]; + m_textParms.b2 = color[2]; + m_textParms.a2 = color[3]; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "fadein")) + { + m_textParms.fadeinTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "fadeout")) + { + m_textParms.fadeoutTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "holdtime")) + { + m_textParms.holdTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "fxtime")) + { + m_textParms.fxTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CRulePointEntity::KeyValue( pkvd ); +} + + +void CGameText::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( MessageToAll() ) + { + UTIL_HudMessageAll( m_textParms, MessageGet() ); + } + else + { + if ( pActivator->IsNetClient() ) + { + UTIL_HudMessage( pActivator, m_textParms, MessageGet() ); + } + } +} + + +// +// 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 +// +// team index (pulled from server team list "mp_teamlist" +// Flag: Remove on Fire +// Flag: Any team until set? -- Any team can use this until the team is set (otherwise no teams can use it) +// + +#define SF_TEAMMASTER_FIREONCE 0x0001 +#define SF_TEAMMASTER_ANYTEAM 0x0002 + +class CGameTeamMaster : public CRulePointEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int ObjectCaps( void ) { return CRulePointEntity:: ObjectCaps() | FCAP_MASTER; } + + BOOL IsTriggered( CBaseEntity *pActivator ); + const char *TeamID( void ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_TEAMMASTER_FIREONCE) ? TRUE : FALSE; } + inline BOOL AnyTeam( void ) { return (pev->spawnflags & SF_TEAMMASTER_ANYTEAM) ? TRUE : FALSE; } + +private: + BOOL TeamMatch( CBaseEntity *pActivator ); + + int m_teamIndex; + USE_TYPE triggerType; +}; + +LINK_ENTITY_TO_CLASS( game_team_master, CGameTeamMaster ); + +void CGameTeamMaster::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "teamindex")) + { + m_teamIndex = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "triggerstate")) + { + int type = atoi( pkvd->szValue ); + switch( type ) + { + case 0: + triggerType = USE_OFF; + break; + case 2: + triggerType = USE_TOGGLE; + break; + default: + triggerType = USE_ON; + break; + } + pkvd->fHandled = TRUE; + } + else + CRulePointEntity::KeyValue( pkvd ); +} + + +void CGameTeamMaster::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( useType == USE_SET ) + { + if ( value < 0 ) + { + m_teamIndex = -1; + } + else + { + m_teamIndex = g_pGameRules->GetTeamIndex( pActivator->TeamID() ); + } + return; + } + + if ( TeamMatch( pActivator ) ) + { + SUB_UseTargets( pActivator, triggerType, value ); + if ( RemoveOnFire() ) + UTIL_Remove( this ); + } +} + + +BOOL CGameTeamMaster::IsTriggered( CBaseEntity *pActivator ) +{ + return TeamMatch( pActivator ); +} + + +const char *CGameTeamMaster::TeamID( void ) +{ + if ( m_teamIndex < 0 ) // Currently set to "no team" + return ""; + + return g_pGameRules->GetIndexedTeamName( m_teamIndex ); // UNDONE: Fill this in with the team from the "teamlist" +} + + +BOOL CGameTeamMaster::TeamMatch( CBaseEntity *pActivator ) +{ + if ( m_teamIndex < 0 && AnyTeam() ) + return TRUE; + + if ( !pActivator ) + return FALSE; + + return UTIL_TeamsMatch( pActivator->TeamID(), TeamID() ); +} + + +// +// CGameTeamSet / game_team_set -- Changes the team of the entity it targets to the activator's team +// Flag: Fire once +// Flag: Clear team -- Sets the team to "NONE" instead of activator + +#define SF_TEAMSET_FIREONCE 0x0001 +#define SF_TEAMSET_CLEARTEAM 0x0002 + +class CGameTeamSet : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_TEAMSET_FIREONCE) ? TRUE : FALSE; } + inline BOOL ShouldClearTeam( void ) { return (pev->spawnflags & SF_TEAMSET_CLEARTEAM) ? TRUE : FALSE; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_team_set, CGameTeamSet ); + + +void CGameTeamSet::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( ShouldClearTeam() ) + { + SUB_UseTargets( pActivator, USE_SET, -1 ); + } + else + { + SUB_UseTargets( pActivator, USE_SET, 0 ); + } + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + +// +// CGamePlayerZone / game_player_zone -- players in the zone fire my target when I'm fired +// +// Needs master? +class CGamePlayerZone : public CRuleBrushEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + string_t m_iszInTarget; + string_t m_iszOutTarget; + string_t m_iszInCount; + string_t m_iszOutCount; +}; + +LINK_ENTITY_TO_CLASS( game_zone_player, CGamePlayerZone ); +TYPEDESCRIPTION CGamePlayerZone::m_SaveData[] = +{ + DEFINE_FIELD( CGamePlayerZone, m_iszInTarget, FIELD_STRING ), + DEFINE_FIELD( CGamePlayerZone, m_iszOutTarget, FIELD_STRING ), + DEFINE_FIELD( CGamePlayerZone, m_iszInCount, FIELD_STRING ), + DEFINE_FIELD( CGamePlayerZone, m_iszOutCount, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CGamePlayerZone, CRuleBrushEntity ); + +void CGamePlayerZone::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "intarget")) + { + m_iszInTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "outtarget")) + { + m_iszOutTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "incount")) + { + m_iszInCount = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "outcount")) + { + m_iszOutCount = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CRuleBrushEntity::KeyValue( pkvd ); +} + +void CGamePlayerZone::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int playersInCount = 0; + int playersOutCount = 0; + + if ( !CanFireForActivator( pActivator ) ) + return; + + CBaseEntity *pPlayer = NULL; + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + { + TraceResult trace; + int hullNumber; + + hullNumber = human_hull; + if ( pPlayer->pev->flags & FL_DUCKING ) + { + hullNumber = head_hull; + } + + UTIL_TraceModel( pPlayer->pev->origin, pPlayer->pev->origin, hullNumber, edict(), &trace ); + + if ( trace.fStartSolid ) + { + playersInCount++; + if ( m_iszInTarget ) + { + FireTargets( STRING(m_iszInTarget), pPlayer, pActivator, useType, value ); + } + } + else + { + playersOutCount++; + if ( m_iszOutTarget ) + { + FireTargets( STRING(m_iszOutTarget), pPlayer, pActivator, useType, value ); + } + } + } + } + + if ( m_iszInCount ) + { + FireTargets( STRING(m_iszInCount), pActivator, this, USE_SET, playersInCount ); + } + + if ( m_iszOutCount ) + { + FireTargets( STRING(m_iszOutCount), pActivator, this, USE_SET, playersOutCount ); + } +} + + + +// +// CGamePlayerHurt / game_player_hurt -- Damages the player who fires it +// Flag: Fire once + +#define SF_PKILL_FIREONCE 0x0001 +class CGamePlayerHurt : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_PKILL_FIREONCE) ? TRUE : FALSE; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_player_hurt, CGamePlayerHurt ); + + +void CGamePlayerHurt::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( pActivator->IsPlayer() ) + { + if ( pev->dmg < 0 ) + pActivator->TakeHealth( -pev->dmg, DMG_GENERIC ); + else + pActivator->TakeDamage( pev, pev, pev->dmg, DMG_GENERIC ); + } + + SUB_UseTargets( pActivator, useType, value ); + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + + +// +// CGameCounter / game_counter -- Counts events and fires target +// Flag: Fire once +// Flag: Reset on Fire + +#define SF_GAMECOUNT_FIREONCE 0x0001 +#define SF_GAMECOUNT_RESET 0x0002 + +class CGameCounter : public CRulePointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_GAMECOUNT_FIREONCE) ? TRUE : FALSE; } + inline BOOL ResetOnFire( void ) { return (pev->spawnflags & SF_GAMECOUNT_RESET) ? TRUE : FALSE; } + + inline void CountUp( void ) { pev->frags++; } + inline void CountDown( void ) { pev->frags--; } + inline void ResetCount( void ) { pev->frags = pev->dmg; } + inline int CountValue( void ) { return static_cast(pev->frags); } + inline int LimitValue( void ) { return static_cast(pev->health); } + + inline BOOL HitLimit( void ) { return CountValue() == LimitValue(); } + +private: + + inline void SetCountValue( int value ) { pev->frags = value; } + inline void SetInitialValue( int value ) { pev->dmg = value; } +}; + +LINK_ENTITY_TO_CLASS( game_counter, CGameCounter ); + +void CGameCounter::Spawn( void ) +{ + // Save off the initial count + SetInitialValue( CountValue() ); + CRulePointEntity::Spawn(); +} + + +void CGameCounter::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + switch( useType ) + { + case USE_ON: + case USE_TOGGLE: + CountUp(); + break; + + case USE_OFF: + CountDown(); + break; + + case USE_SET: + SetCountValue( (int)value ); + break; + } + + if ( HitLimit() ) + { + SUB_UseTargets( pActivator, USE_TOGGLE, 0 ); + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } + + if ( ResetOnFire() ) + { + ResetCount(); + } + } +} + + + +// +// CGameCounterSet / game_counter_set -- Sets the counter's value +// Flag: Fire once + +#define SF_GAMECOUNTSET_FIREONCE 0x0001 + +class CGameCounterSet : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_GAMECOUNTSET_FIREONCE) ? TRUE : FALSE; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_counter_set, CGameCounterSet ); + + +void CGameCounterSet::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + SUB_UseTargets( pActivator, USE_SET, pev->frags ); + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + +// +// CGamePlayerEquip / game_playerequip -- Sets the default player equipment +// Flag: USE Only + +#define SF_PLAYEREQUIP_USEONLY 0x0001 +#define MAX_EQUIP 32 + +class CGamePlayerEquip : public CRulePointEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Touch( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + inline BOOL UseOnly( void ) { return (pev->spawnflags & SF_PLAYEREQUIP_USEONLY) ? TRUE : FALSE; } + +private: + + void EquipPlayer( CBaseEntity *pPlayer ); + + string_t m_weaponNames[MAX_EQUIP]; + int m_weaponCount[MAX_EQUIP]; +}; + +LINK_ENTITY_TO_CLASS( game_player_equip, CGamePlayerEquip ); + + +void CGamePlayerEquip::KeyValue( KeyValueData *pkvd ) +{ + CRulePointEntity::KeyValue( pkvd ); + + if ( !pkvd->fHandled ) + { + for ( int i = 0; i < MAX_EQUIP; i++ ) + { + if ( !m_weaponNames[i] ) + { + char tmp[128]; + + UTIL_StripToken( pkvd->szKeyName, tmp ); + + m_weaponNames[i] = ALLOC_STRING(tmp); + m_weaponCount[i] = atoi(pkvd->szValue); + m_weaponCount[i] = max(1,m_weaponCount[i]); + pkvd->fHandled = TRUE; + break; + } + } + } +} + + +void CGamePlayerEquip::Touch( CBaseEntity *pOther ) +{ + if ( !CanFireForActivator( pOther ) ) + return; + + if ( UseOnly() ) + return; + + EquipPlayer( pOther ); +} + +void CGamePlayerEquip::EquipPlayer( CBaseEntity *pEntity ) +{ + CBasePlayer *pPlayer = NULL; + + if ( pEntity->IsPlayer() ) + { + pPlayer = (CBasePlayer *)pEntity; + } + + if ( !pPlayer ) + return; + + for ( int i = 0; i < MAX_EQUIP; i++ ) + { + if ( !m_weaponNames[i] ) + break; + for ( int j = 0; j < m_weaponCount[i]; j++ ) + { + pPlayer->GiveNamedItem( STRING(m_weaponNames[i]) ); + } + } +} + + +void CGamePlayerEquip::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + EquipPlayer( pActivator ); +} + + +// +// CGamePlayerTeam / game_player_team -- Changes the team of the player who fired it +// Flag: Fire once +// Flag: Kill Player +// Flag: Gib Player + +#define SF_PTEAM_FIREONCE 0x0001 +#define SF_PTEAM_KILL 0x0002 +#define SF_PTEAM_GIB 0x0004 + +class CGamePlayerTeam : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +private: + + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_PTEAM_FIREONCE) ? TRUE : FALSE; } + inline BOOL ShouldKillPlayer( void ) { return (pev->spawnflags & SF_PTEAM_KILL) ? TRUE : FALSE; } + inline BOOL ShouldGibPlayer( void ) { return (pev->spawnflags & SF_PTEAM_GIB) ? TRUE : FALSE; } + + const char *TargetTeamName( const char *pszTargetName ); +}; + +LINK_ENTITY_TO_CLASS( game_player_team, CGamePlayerTeam ); + + +const char *CGamePlayerTeam::TargetTeamName( const char *pszTargetName ) +{ + CBaseEntity *pTeamEntity = NULL; + + while ((pTeamEntity = UTIL_FindEntityByTargetname( pTeamEntity, pszTargetName )) != NULL) + { + if ( FClassnameIs( pTeamEntity->pev, "game_team_master" ) ) + return pTeamEntity->TeamID(); + } + + return NULL; +} + + +void CGamePlayerTeam::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( pActivator->IsPlayer() ) + { + const char *pszTargetTeam = TargetTeamName( STRING(pev->target) ); + if ( pszTargetTeam ) + { + CBasePlayer *pPlayer = (CBasePlayer *)pActivator; + g_pGameRules->ChangePlayerTeam( pPlayer, pszTargetTeam, ShouldKillPlayer(), ShouldGibPlayer() ); + } + } + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + diff --git a/hlsdk/ricochet/dlls/maprules.h b/hlsdk/ricochet/dlls/maprules.h new file mode 100644 index 00000000..9c50dcc3 --- /dev/null +++ b/hlsdk/ricochet/dlls/maprules.h @@ -0,0 +1,22 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ + +#ifndef MAPRULES_H +#define MAPRULES_H + + + +#endif // MAPRULES_H + diff --git a/hlsdk/ricochet/dlls/monsterevent.h b/hlsdk/ricochet/dlls/monsterevent.h new file mode 100644 index 00000000..27bcb05d --- /dev/null +++ b/hlsdk/ricochet/dlls/monsterevent.h @@ -0,0 +1,34 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +#ifndef MONSTEREVENT_H +#define MONSTEREVENT_H + +typedef struct +{ + int event; + char *options; +} MonsterEvent_t; + +#define EVENT_SPECIFIC 0 +#define EVENT_SCRIPTED 1000 +#define EVENT_SHARED 2000 +#define EVENT_CLIENT 5000 + +#define MONSTER_EVENT_BODYDROP_LIGHT 2001 +#define MONSTER_EVENT_BODYDROP_HEAVY 2002 + +#define MONSTER_EVENT_SWISHSOUND 2010 + +#endif // MONSTEREVENT_H diff --git a/hlsdk/ricochet/dlls/monsters.h b/hlsdk/ricochet/dlls/monsters.h new file mode 100644 index 00000000..f46ae525 --- /dev/null +++ b/hlsdk/ricochet/dlls/monsters.h @@ -0,0 +1,88 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +#ifndef MONSTERS_H +#include "skill.h" +#define MONSTERS_H + +/* + +===== monsters.h ======================================================== + + Header file for monster-related utility code + +*/ + +// Hit Group standards +#define HITGROUP_GENERIC 0 +#define HITGROUP_HEAD 1 +#define HITGROUP_CHEST 2 +#define HITGROUP_STOMACH 3 +#define HITGROUP_LEFTARM 4 +#define HITGROUP_RIGHTARM 5 +#define HITGROUP_LEFTLEG 6 +#define HITGROUP_RIGHTLEG 7 + + +// 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 ); + +Vector VecCheckToss ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flGravityAdj = 1.0 ); +Vector VecCheckThrow ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flSpeed, float flGravityAdj = 1.0 ); +extern DLL_GLOBAL Vector g_vecAttackDir; +extern DLL_GLOBAL CONSTANT float g_flMeleeRange; +extern DLL_GLOBAL CONSTANT float g_flMediumRange; +extern DLL_GLOBAL CONSTANT float g_flLongRange; +extern void EjectBrass (const Vector &vecOrigin, const Vector &vecVelocity, float rotation, int model, int soundtype ); +extern void ExplodeModel( const Vector &vecOrigin, float speed, int model, int count ); + +BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget ); +BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget, Vector &vecTargetOrigin, float flSize = 0.0 ); + +// monster to monster relationship types +#define R_AL -2 // (ALLY) pals. Good alternative to R_NO when applicable. +#define R_FR -1// (FEAR)will run +#define R_NO 0// (NO RELATIONSHIP) disregard +#define R_DL 1// (DISLIKE) will attack +#define R_HT 2// (HATE)will attack this character instead of any visible DISLIKEd characters +#define R_NM 3// (NEMESIS) A monster Will ALWAYS attack its nemsis, no matter what + + +#define bits_MEMORY_KILLED ( 1 << 7 )// HACKHACK -- remember that I've already called my Killed() + +// +// A gib is a chunk of a body, or a piece of wood/metal/rocks/etc. +// +class CGib : public CBaseEntity +{ +public: + void Spawn( const char *szGibModel ); + void EXPORT BounceGibTouch ( CBaseEntity *pOther ); + void EXPORT StickyGibTouch ( CBaseEntity *pOther ); + void EXPORT WaitTillLand( void ); + void LimitVelocity( void ); + + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DONT_SAVE; } + static void SpawnHeadGib( entvars_t *pevVictim ); + static void SpawnRandomGibs( entvars_t *pevVictim, int cGibs, int human ); + static void SpawnStickyGibs( entvars_t *pevVictim, Vector vecOrigin, int cGibs ); + + int m_bloodColor; + int m_cBloodDecals; + int m_material; + float m_lifeTime; +}; + + +#endif //MONSTERS_H diff --git a/hlsdk/ricochet/dlls/mortar.cpp b/hlsdk/ricochet/dlls/mortar.cpp new file mode 100644 index 00000000..18b221bd --- /dev/null +++ b/hlsdk/ricochet/dlls/mortar.cpp @@ -0,0 +1,323 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== mortar.cpp ======================================================== + + the "LaBuznik" mortar device + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "weapons.h" +#include "decals.h" +#include "soundent.h" + +class CFuncMortarField : public CBaseToggle +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + // Bmodels don't go across transitions + virtual int ObjectCaps( void ) { return CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + void EXPORT FieldUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int m_iszXController; + int m_iszYController; + float m_flSpread; + float m_flDelay; + int m_iCount; + int m_fControl; +}; + +LINK_ENTITY_TO_CLASS( func_mortar_field, CFuncMortarField ); + +TYPEDESCRIPTION CFuncMortarField::m_SaveData[] = +{ + DEFINE_FIELD( CFuncMortarField, m_iszXController, FIELD_STRING ), + DEFINE_FIELD( CFuncMortarField, m_iszYController, FIELD_STRING ), + DEFINE_FIELD( CFuncMortarField, m_flSpread, FIELD_FLOAT ), + DEFINE_FIELD( CFuncMortarField, m_flDelay, FIELD_FLOAT ), + DEFINE_FIELD( CFuncMortarField, m_iCount, FIELD_INTEGER ), + DEFINE_FIELD( CFuncMortarField, m_fControl, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CFuncMortarField, CBaseToggle ); + + +void CFuncMortarField :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszXController")) + { + m_iszXController = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszYController")) + { + m_iszYController = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flSpread")) + { + m_flSpread = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fControl")) + { + m_fControl = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iCount")) + { + m_iCount = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } +} + + +// Drop bombs from above +void CFuncMortarField :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->movetype = MOVETYPE_NONE; + SetBits( pev->effects, EF_NODRAW ); + SetUse( &CFuncMortarField::FieldUse ); + Precache(); +} + + +void CFuncMortarField :: Precache( void ) +{ + PRECACHE_SOUND ("weapons/mortar.wav"); + PRECACHE_SOUND ("weapons/mortarhit.wav"); + PRECACHE_MODEL( "sprites/lgtning.spr" ); +} + + +// If connected to a table, then use the table controllers, else hit where the trigger is. +void CFuncMortarField :: FieldUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + Vector vecStart; + + vecStart.x = RANDOM_FLOAT( pev->mins.x, pev->maxs.x ); + vecStart.y = RANDOM_FLOAT( pev->mins.y, pev->maxs.y ); + vecStart.z = pev->maxs.z; + + switch( m_fControl ) + { + case 0: // random + break; + case 1: // Trigger Activator + if (pActivator != NULL) + { + vecStart.x = pActivator->pev->origin.x; + vecStart.y = pActivator->pev->origin.y; + } + break; + case 2: // table + { + CBaseEntity *pController; + + if (!FStringNull(m_iszXController)) + { + pController = UTIL_FindEntityByTargetname( NULL, STRING(m_iszXController)); + if (pController != NULL) + { + vecStart.x = pev->mins.x + pController->pev->ideal_yaw * (pev->size.x); + } + } + if (!FStringNull(m_iszYController)) + { + pController = UTIL_FindEntityByTargetname( NULL, STRING(m_iszYController)); + if (pController != NULL) + { + vecStart.y = pev->mins.y + pController->pev->ideal_yaw * (pev->size.y); + } + } + } + break; + } + + int pitch = RANDOM_LONG(95,124); + + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "weapons/mortar.wav", 1.0, ATTN_NONE, 0, pitch); + + float t = 2.5; + for (int i = 0; i < m_iCount; i++) + { + Vector vecSpot = vecStart; + vecSpot.x += RANDOM_FLOAT( -m_flSpread, m_flSpread ); + vecSpot.y += RANDOM_FLOAT( -m_flSpread, m_flSpread ); + + TraceResult tr; + UTIL_TraceLine( vecSpot, vecSpot + Vector( 0, 0, -1 ) * 4096, ignore_monsters, ENT(pev), &tr ); + + edict_t *pentOwner = NULL; + if (pActivator) pentOwner = pActivator->edict(); + + CBaseEntity *pMortar = Create("monster_mortar", tr.vecEndPos, Vector( 0, 0, 0 ), pentOwner ); + pMortar->pev->nextthink = gpGlobals->time + t; + t += RANDOM_FLOAT( 0.2, 0.5 ); + + if (i == 0) + CSoundEnt::InsertSound ( bits_SOUND_DANGER, tr.vecEndPos, 400, 0.3 ); + } +} + + +class CMortar : public CGrenade +{ +public: + void Spawn( void ); + void Precache( void ); + + void EXPORT MortarExplode( void ); + + int m_spriteTexture; +}; + +LINK_ENTITY_TO_CLASS( monster_mortar, CMortar ); + +void CMortar::Spawn( ) +{ + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT; + + pev->dmg = 200; + + SetThink( &CMortar::MortarExplode ); + pev->nextthink = 0; + + Precache( ); + + +} + + +void CMortar::Precache( ) +{ + m_spriteTexture = PRECACHE_MODEL( "sprites/lgtning.spr" ); +} + +void CMortar::MortarExplode( void ) +{ +#if 1 + // mortar beam + 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(pev->origin.x); + WRITE_COORD(pev->origin.y); + WRITE_COORD(pev->origin.z + 1024); + WRITE_SHORT(m_spriteTexture ); + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 1 ); // life + WRITE_BYTE( 40 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 160 ); // r, g, b + WRITE_BYTE( 100 ); // r, g, b + WRITE_BYTE( 128 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); +#endif + +#if 0 + // blast circle + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMTORUS); + WRITE_COORD(pev->origin.x); + WRITE_COORD(pev->origin.y); + WRITE_COORD(pev->origin.z + 32); + WRITE_COORD(pev->origin.x); + WRITE_COORD(pev->origin.y); + WRITE_COORD(pev->origin.z + 32 + pev->dmg * 2 / .2); // reach damage radius over .3 seconds + WRITE_SHORT(m_spriteTexture ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 2 ); // life + WRITE_BYTE( 12 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 160 ); // r, g, b + WRITE_BYTE( 100 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); +#endif + + TraceResult tr; + UTIL_TraceLine( pev->origin + Vector( 0, 0, 1024 ), pev->origin - Vector( 0, 0, 1024 ), dont_ignore_monsters, ENT(pev), &tr ); + + Explode( &tr, DMG_BLAST | DMG_MORTAR ); + UTIL_ScreenShake( tr.vecEndPos, 25.0, 150.0, 1.0, 750 ); + +#if 0 + int pitch = RANDOM_LONG(95,124); + 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 ); + + /* + if ( RANDOM_FLOAT ( 0 , 1 ) < 0.5 ) + { + UTIL_DecalTrace( pTrace, DECAL_SCORCH1 ); + } + else + { + UTIL_DecalTrace( pTrace, DECAL_SCORCH2 ); + } + */ + + SetThink( SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; +#endif + +} + + +#if 0 +void CMortar::ShootTimed( EVARS *pevOwner, Vector vecStart, float time ) +{ + CMortar *pMortar = GetClassPtr( (CMortar *)NULL ); + pMortar->Spawn(); + + TraceResult tr; + UTIL_TraceLine( vecStart, vecStart + Vector( 0, 0, -1 ) * 4096, ignore_monsters, ENT(pMortar->pev), &tr ); + + pMortar->pev->nextthink = gpGlobals->time + time; + + UTIL_SetOrigin( pMortar->pev, tr.vecEndPos ); +} +#endif diff --git a/hlsdk/ricochet/dlls/mp.def b/hlsdk/ricochet/dlls/mp.def new file mode 100644 index 00000000..5ae6f18f --- /dev/null +++ b/hlsdk/ricochet/dlls/mp.def @@ -0,0 +1,5 @@ +LIBRARY mp +EXPORTS + GiveFnptrsToDll @1 +SECTIONS + .data READ WRITE diff --git a/hlsdk/ricochet/dlls/mpstubb.cpp b/hlsdk/ricochet/dlls/mpstubb.cpp new file mode 100644 index 00000000..6c7a34aa --- /dev/null +++ b/hlsdk/ricochet/dlls/mpstubb.cpp @@ -0,0 +1,264 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "soundent.h" +#include "nodes.h" +#include "talkmonster.h" + + +float CTalkMonster::g_talkWaitTime = 0; // time delay until it's ok to speak: used so that two NPCs don't talk at once + +/*********************************************************/ + + +CGraph WorldGraph; +void CGraph :: InitGraph( void ) { } +int CGraph :: FLoadGraph ( char *szMapName ) { return FALSE; } +int CGraph :: AllocNodes ( void ) { return FALSE; } +int CGraph :: CheckNODFile ( char *szMapName ) { return FALSE; } +int CGraph :: FSetGraphPointers ( void ) { return 0; } +void CGraph :: ShowNodeConnections ( int iNode ) { } +int CGraph :: FindNearestNode ( const Vector &vecOrigin, int afNodeTypes ) { return 0; } + + +/*********************************************************/ + + +void CBaseMonster :: ReportAIState( void ) { } +float CBaseMonster :: ChangeYaw ( int speed ) { return 0; } +void CBaseMonster :: MakeIdealYaw( Vector vecTarget ) { } + + +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( &CBaseMonster::CorpseFallThink ); + pev->nextthink = gpGlobals->time + 0.5; +} + + +BOOL CBaseMonster :: ShouldFadeOnDeath( void ) +{ + return FALSE; +} + +BOOL CBaseMonster :: FCheckAITrigger ( void ) +{ + return FALSE; +} + +void CBaseMonster :: KeyValue( KeyValueData *pkvd ) +{ + CBaseToggle::KeyValue( pkvd ); +} + +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() ]; +} + + +//========================================================= +// 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 + + 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]; + if ( pSightEnt != this && 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 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 ); +} + + +//========================================================= +// 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 = static_cast(( 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 = static_cast(( pNextEnt->pev->origin - pev->origin ).Length()); + + if ( iDist <= iNearest ) + { + iNearest = iDist; + iBestRelationship = IRelationship ( pNextEnt ); + pReturn = pNextEnt; + } + } + } + + pNextEnt = pNextEnt->m_pLink; + } + + return pReturn; +} diff --git a/hlsdk/ricochet/dlls/msvc6/mp.dsp b/hlsdk/ricochet/dlls/msvc6/mp.dsp new file mode 100644 index 00000000..85ed88a7 --- /dev/null +++ b/hlsdk/ricochet/dlls/msvc6/mp.dsp @@ -0,0 +1,518 @@ +# Microsoft Developer Studio Project File - Name="mp" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=mp - 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 "mp.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 "mp.mak" CFG="mp - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mp - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mp - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mp - Win32 Profile" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "mp - 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 ".\Releasemp" +# PROP Intermediate_Dir ".\Releasemp" +# PROP Ignore_Export_Lib 0 +# 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 /GX /Zi /O2 /I "..\..\dlls" /I "..\..\..\engine" /I "..\..\..\common" /I "..\..\..\game_shared" /I "..\..\pm_shared" /I "..\..\" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "VALVE_DLL" /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:"..\mp.def" +# SUBTRACT LINK32 /profile + +!ELSEIF "$(CFG)" == "mp - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\mp___Win" +# PROP BASE Intermediate_Dir ".\mp___Win" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir ".\debugmp" +# PROP Intermediate_Dir ".\debugmp" +# 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 /GX /ZI /Od /I "..\..\dlls" /I "..\..\..\engine" /I "..\..\..\common" /I "..\..\..\game_shared" /I "..\..\pm_shared" /I "..\..\" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "VALVE_DLL" /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:"..\mp.def" /implib:".\Debug\mp.lib" +# SUBTRACT LINK32 /profile + +!ELSEIF "$(CFG)" == "mp - Win32 Profile" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\mp___Win" +# PROP BASE Intermediate_Dir ".\mp___Win" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir ".\Profilemp" +# PROP Intermediate_Dir ".\Profilemp" +# 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 /GX /Zi /O2 /I "..\..\dlls" /I "..\..\..\engine" /I "..\..\..\game_shared" /I "..\..\..\common" /I "..\..\pm_shared" /I "..\..\" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "VALVE_DLL" /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 /nologo /subsystem:windows /dll /debug /machine:I386 /def:"..\mp.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:"..\mp.def" + +!ENDIF + +# Begin Target + +# Name "mp - Win32 Release" +# Name "mp - Win32 Debug" +# Name "mp - Win32 Profile" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=..\airtank.cpp +# End Source File +# 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=..\disc_arena.cpp +# End Source File +# Begin Source File + +SOURCE=..\disc_powerups.cpp +# End Source File +# Begin Source File + +SOURCE=..\wpn_shared\disc_weapon_disc.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=..\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_battery.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=..\healthkit.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=..\mortar.cpp +# End Source File +# Begin Source File + +SOURCE=..\mpstubb.cpp +# End Source File +# Begin Source File + +SOURCE=..\multiplay_gamerules.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=..\singleplay_gamerules.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=..\teamplay_gamerules.cpp +# End Source File +# Begin Source File + +SOURCE=..\triggers.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 +# Begin Source File + +SOURCE=..\xen.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=..\disc_arena.h +# End Source File +# Begin Source File + +SOURCE=..\discwar.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=..\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=..\hornet.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=..\saverestore.h +# End Source File +# Begin Source File + +SOURCE=..\schedule.h +# End Source File +# Begin Source File + +SOURCE=..\scriptevent.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=..\talkmonster.h +# End Source File +# Begin Source File + +SOURCE=..\teamplay_gamerules.h +# End Source File +# Begin Source File + +SOURCE=..\trains.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/hlsdk/ricochet/dlls/msvc7/mp.vcproj b/hlsdk/ricochet/dlls/msvc7/mp.vcproj new file mode 100644 index 00000000..eca50f45 --- /dev/null +++ b/hlsdk/ricochet/dlls/msvc7/mp.vcproj @@ -0,0 +1,1825 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hlsdk/ricochet/dlls/msvc8/mp.vcproj b/hlsdk/ricochet/dlls/msvc8/mp.vcproj new file mode 100644 index 00000000..2c09d596 --- /dev/null +++ b/hlsdk/ricochet/dlls/msvc8/mp.vcproj @@ -0,0 +1,2325 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hlsdk/ricochet/dlls/multiplay_gamerules.cpp b/hlsdk/ricochet/dlls/multiplay_gamerules.cpp new file mode 100644 index 00000000..1c8d9394 --- /dev/null +++ b/hlsdk/ricochet/dlls/multiplay_gamerules.cpp @@ -0,0 +1,1622 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// teamplay_gamerules.cpp +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "game.h" +#include "items.h" +#include "monsters.h" +#include "discwar.h" +#include "voice_gamemgr.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 gmsgSpectator; +extern int gmsgReward; + +extern int g_iMapTurnedOffArena; + +#define ITEM_RESPAWN_TIME 30 +#define WEAPON_RESPAWN_TIME 20 +#define AMMO_RESPAWN_TIME 20 + +CVoiceGameMgr g_VoiceGameMgr; + +int InArenaMode( void ); + +class CMultiplayGameMgrHelper : public IVoiceGameMgrHelper +{ +public: + virtual bool CanPlayerHearPlayer(CBasePlayer *pListener, CBasePlayer *pTalker) + { + //FFA + if ( InArenaMode() == FALSE ) + return true; + + if ( pListener->m_pCurrentArena == NULL && pTalker->m_pCurrentArena == NULL ) + return true; //Both spectating + + if ( pListener->m_pCurrentArena == pTalker->m_pCurrentArena ) + return true; //Same arena + + return false; + } +}; +static CMultiplayGameMgrHelper g_GameMgrHelper; + +//********************************************************* +// Rules for the half-life multiplayer game. +//********************************************************* + +CHalfLifeMultiplay :: CHalfLifeMultiplay() +{ + g_VoiceGameMgr.Init(&g_GameMgrHelper, gpGlobals->maxClients); + + RefreshSkillData(); + m_flIntermissionEndTime = 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; + + return CGameRules::ClientCommand(pPlayer, pcmd); +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay::RefreshSkillData( void ) +{ +// load all default values + CGameRules::RefreshSkillData(); + +// override some values for multiplay. + + // suitcharger + gSkillData.suitchargerCapacity = 30; + + // Crowbar whack + gSkillData.plrDmgCrowbar = 25; + + // Glock Round + gSkillData.plrDmg9MM = 12; + + // 357 Round + gSkillData.plrDmg357 = 40; + + // MP5 Round + gSkillData.plrDmgMP5 = 12; + + // M203 grenade + gSkillData.plrDmgM203Grenade = 100; + + // Shotgun buckshot + gSkillData.plrDmgBuckshot = 20;// fewer pellets in deathmatch + + // Crossbow + gSkillData.plrDmgCrossbowClient = 20; + + // RPG + gSkillData.plrDmgRPG = 120; + + // Egon + gSkillData.plrDmgEgonWide = 20; + gSkillData.plrDmgEgonNarrow = 10; + + // Hand Grendade + gSkillData.plrDmgHandGrenade = 100; + + // Satchel Charge + gSkillData.plrDmgSatchel = 120; + + // Tripmine + gSkillData.plrDmgTripmine = 150; + + // hornet + gSkillData.plrDmgHornet = 10; +} + +// longest the intermission can last, in seconds +#define MAX_INTERMISSION_TIME 120 + +extern cvar_t timeleft, fragsleft; +//========================================================= +//========================================================= +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 + { + if ( m_flIntermissionEndTime < gpGlobals->time ) + { + if ( m_iEndIntermissionButtonHit // check that someone has pressed a key, or the max intermission time is over + || ((m_flIntermissionEndTime + 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 = static_cast(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; +} + + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsMultiplayer( void ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsDeathmatch( void ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsCoOp( void ) +{ + return static_cast(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; + } + + if ( pWeapon->iWeight() > pPlayer->m_pActiveItem->iWeight() ) + { + 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 ) + { + 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); + + // Increase their speed to the Discwar speed + MESSAGE_BEGIN( MSG_ONE, SVC_STUFFTEXT, NULL, pEntity ); + WRITE_STRING( UTIL_VarArgs("cl_forwardspeed %d\ncl_backspeed %d\ncl_sidespeed %d\n", 320, 320, 320) ); + MESSAGE_END(); + + + return TRUE; +} + +extern int gmsgSayText; +extern int gmsgGameMode; + +void CHalfLifeMultiplay :: UpdateGameMode( CBasePlayer *pPlayer ) +{ + MESSAGE_BEGIN( MSG_ONE, gmsgGameMode, NULL, pPlayer->edict() ); + WRITE_BYTE( InArenaMode() ); // game mode none + MESSAGE_END(); +} + +void CHalfLifeMultiplay :: InitHUD( CBasePlayer *pl ) +{ + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" entered the game\n", + STRING( pl->pev->netname ), + GETPLAYERUSERID( pl->edict() ), + GETPLAYERAUTHID( 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(); + + 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 ) + { + ALERT( at_console, "Sending %s data to %s\n", STRING(plr->pev->netname), STRING( pl->pev->netname ) ); + + MESSAGE_BEGIN( MSG_ONE, gmsgScoreInfo, NULL, pl->edict() ); + WRITE_BYTE( i ); // client number + WRITE_SHORT( static_cast(plr->pev->frags) ); + WRITE_SHORT( plr->m_iDeaths ); + WRITE_SHORT( plr->pev->playerclass ); + WRITE_SHORT( plr->pev->team ); + MESSAGE_END(); + + // Send their spectator state + MESSAGE_BEGIN( MSG_ONE, gmsgSpectator, NULL, pl->edict() ); + WRITE_BYTE( i ); + WRITE_BYTE( (plr->pev->iuser1 != 0) ); + 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 ); + + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" disconnected\n", + STRING( pPlayer->pev->netname ), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + GETPLAYERUSERID( pPlayer->edict() ) ); + + pPlayer->RemoveAllItems( TRUE );// 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 ) +{ + int iFallDamage = (int)CVAR_GET_FLOAT("mp_falldamage"); + + switch ( iFallDamage ) + { + case 1://progressive + pPlayer->m_flFallVelocity -= PLAYER_MAX_SAFE_FALL_SPEED; + return pPlayer->m_flFallVelocity * DAMAGE_FOR_FALL_SPEED; + break; + default: + case 0:// fixed + return 10; + break; + } +} + +//========================================================= +//========================================================= +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; + + pPlayer->pev->weapons |= (1<Touch( pPlayer ); + addDefault = FALSE; + } + + if ( addDefault ) + { + pPlayer->GiveNamedItem( "weapon_disc" ); + pPlayer->GiveAmmo( MAX_DISCS, "disc", MAX_DISCS ); + } +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay :: FPlayerCanRespawn( CBasePlayer *pPlayer ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +float CHalfLifeMultiplay :: FlPlayerSpawnTime( CBasePlayer *pPlayer ) +{ + return gpGlobals->time;//now! +} + +BOOL CHalfLifeMultiplay :: AllowAutoTargetCrosshair( void ) +{ + return ( CVAR_GET_FLOAT( "mp_autocrosshair" ) != 0 ); +} + +//========================================================= +// 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 ); + + 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; + + CBaseEntity *ep = CBaseEntity::Instance( pKiller ); + if ( ep && ep->Classify() == CLASS_PLAYER ) + { + CBasePlayer *PK = (CBasePlayer*)ep; + + // 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 ); + + 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 ( pVictim->pev == pKiller ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" committed suicide with \"%s\"\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); + + pKiller->frags -= 1; + } + else if ( pKiller->flags & FL_CLIENT ) + { + if ( !strcmp( killer_weapon_name, "disc" ) ) + { + int iTele = 0; + if ( (pVictim->m_flLastDiscHitTeleport != 0) && (gpGlobals->time < pVictim->m_flLastDiscHitTeleport + MAX_SCORE_TIME_AFTER_HIT) ) + iTele = REWARD_TELEPORT; + + // Decapitated? + if ( pVictim->m_LastHitGroup == HITGROUP_HEAD ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" killed \"%s<%i><%s><%i>\" with \"%s\" (decapitated)\n", + STRING( pKiller->netname ), + GETPLAYERUSERID( ENT(pKiller) ), + GETPLAYERAUTHID( ENT(pKiller) ), + GETPLAYERUSERID( ENT(pKiller) ), + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); + + // Tell the client to display the death message + MESSAGE_BEGIN( MSG_ALL, gmsgDeathMsg ); + WRITE_BYTE( killer_index ); // the killer + WRITE_BYTE( ENTINDEX(pVictim->edict()) ); // the victim + WRITE_STRING( "decapitate" ); // what they were killed by (should this be a string?) + MESSAGE_END(); + + // Bonus point for teleport hit + if ( iTele ) + pKiller->frags++; + + pKiller->frags += 1; + + // Bring up the reward window on the killer's screen + MESSAGE_BEGIN( MSG_ONE, gmsgReward, NULL, Killer->edict() ); + WRITE_SHORT( REWARD_DECAPITATE | iTele ); + MESSAGE_END(); + + return; + } + + // Otherwise, calculate number of disc bounces + if ( pVictim->m_flLastDiscBounces == 0 ) + { + // Bring up the reward window on the killer's screen + MESSAGE_BEGIN( MSG_ONE, gmsgReward, NULL, Killer->edict() ); + WRITE_SHORT( REWARD_BOUNCE_NONE | iTele ); + MESSAGE_END(); + } + else if ( pVictim->m_flLastDiscBounces == 1 ) + { + // Bring up the reward window on the killer's screen + MESSAGE_BEGIN( MSG_ONE, gmsgReward, NULL, Killer->edict() ); + WRITE_SHORT( REWARD_BOUNCE_ONE | iTele ); + MESSAGE_END(); + } + else + { + if ( pVictim->m_flLastDiscBounces == 2 ) + { + // Bring up the reward window on the killer's screen + MESSAGE_BEGIN( MSG_ONE, gmsgReward, NULL, Killer->edict() ); + WRITE_SHORT( REWARD_BOUNCE_TWO | iTele ); + MESSAGE_END(); + } + else + { + // Bring up the reward window on the killer's screen + MESSAGE_BEGIN( MSG_ONE, gmsgReward, NULL, Killer->edict() ); + WRITE_SHORT( REWARD_BOUNCE_THREE | iTele ); + MESSAGE_END(); + + // Cap the number of frags a killer can get to 4 + pVictim->m_flLastDiscBounces = 3; + } + } + + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" killed \"%s<%i><%s><%i>\" with \"%s\" (bounces \"%d\")\n", + STRING( pKiller->netname ), + GETPLAYERUSERID( ENT(pKiller) ), + GETPLAYERAUTHID( ENT(pKiller) ), + GETPLAYERUSERID( ENT(pKiller) ), + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name, + pVictim->m_flLastDiscBounces ); + + char sz[1024]; + sprintf( sz, "%dbounce", (int)(pVictim->m_flLastDiscBounces) ); + + // Tell the client to display the death message + MESSAGE_BEGIN( MSG_ALL, gmsgDeathMsg ); + WRITE_BYTE( killer_index ); // the killer + WRITE_BYTE( ENTINDEX(pVictim->edict()) ); // the victim + WRITE_STRING( sz ); // what they were killed by (should this be a string?) + MESSAGE_END(); + + pKiller->frags += (1 + pVictim->m_flLastDiscBounces); + + // Bonus point for teleport hit + if ( iTele ) + pKiller->frags++; + } + else + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" killed \"%s<%i><%s><%i>\" with \"%s\"\n", + STRING( pKiller->netname ), + GETPLAYERUSERID( ENT(pKiller) ), + GETPLAYERAUTHID( ENT(pKiller) ), + GETPLAYERUSERID( ENT(pKiller) ), + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); + + // Tell the client to display the death message + MESSAGE_BEGIN( MSG_ALL, gmsgDeathMsg ); + WRITE_BYTE( killer_index ); // the killer + WRITE_BYTE( ENTINDEX(pVictim->edict()) ); // the victim + WRITE_STRING( killer_weapon_name ); // what they were killed by (should this be a string?) + MESSAGE_END(); + + pKiller->frags += 1; + } + } + else + { + pKiller->frags -= 1; + + // Fell to their death + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" committed suicide with \"world\"\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); + + // Tell the client to display the death message + MESSAGE_BEGIN( MSG_ALL, gmsgDeathMsg ); + WRITE_BYTE( killer_index ); // the killer + WRITE_BYTE( ENTINDEX(pVictim->edict()) ); // the victim + WRITE_STRING( "falling" ); // what they were killed by (should this be a string?) + MESSAGE_END(); + } + + return; +} + +//========================================================= +// 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 ( CVAR_GET_FLOAT("mp_weaponstay") > 0 ) + { + // make sure it's only certain weapons + if ( !(pWeapon->iFlags() & ITEM_FLAG_LIMITINWORLD) ) + { + return gpGlobals->time + 0; // weapon respawns almost instantly + } + } + + 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 ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +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; + + 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_ACTIVE; +} + +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 CVAR_GET_FLOAT( "mp_flashlight" ) != 0; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay :: FAllowMonsters( void ) +{ + return ( CVAR_GET_FLOAT( "mp_allowmonsters" ) != 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(); + + m_flIntermissionEndTime = gpGlobals->time + INTERMISSION_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 (MAX_MOTD_CHUNK * 4) + +void CHalfLifeMultiplay :: SendMOTDToClient( edict_t *client ) +{ + // read from the MOTD.txt file + int length, char_count = 0; + char *pFileList; + char *aFileList = pFileList = (char*)LOAD_FILE_FOR_ME( "motd.txt", &length ); + + // 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]; + + 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(); + } + + FREE_FILE( aFileList ); +} + + diff --git a/hlsdk/ricochet/dlls/nodes.h b/hlsdk/ricochet/dlls/nodes.h new file mode 100644 index 00000000..f610bbc4 --- /dev/null +++ b/hlsdk/ricochet/dlls/nodes.h @@ -0,0 +1,54 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// nodes.h +//========================================================= + +#ifndef NODES_H +#define NODES_H + +#define bits_NODE_GROUP_REALM 1 + +class CLink +{ +public: + entvars_t *m_pLinkEnt;// the entity that blocks this connection (doors, etc) +}; + + +class CGraph +{ +public: + BOOL m_fGraphPresent;// is the graph in memory? + BOOL m_fGraphPointersSet;// are the entity pointers for the graph all set? + + int m_cLinks;// total number of links + CLink *m_pLinkPool;// big list of all node connections + + void InitGraph( void ); + int AllocNodes ( void ); + + int CheckNODFile(char *szMapName); + int FLoadGraph(char *szMapName); + int FSetGraphPointers(void); + void ShowNodeConnections ( int iNode ); + int FindNearestNode ( const Vector &vecOrigin, CBaseEntity *pEntity ); + int FindNearestNode ( const Vector &vecOrigin, int afNodeTypes ); + +}; + +extern CGraph WorldGraph; + +#endif // NODES_H diff --git a/hlsdk/ricochet/dlls/observer.cpp b/hlsdk/ricochet/dlls/observer.cpp new file mode 100644 index 00000000..1cb25583 --- /dev/null +++ b/hlsdk/ricochet/dlls/observer.cpp @@ -0,0 +1,326 @@ +//=========== (C) Copyright 1999 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: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= +// observer.cpp +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.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 != 0 ) + { + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + } + + // clear out the suit message cache so we don't keep chattering + SetSuitUpdate(NULL, FALSE, 0); + + // 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 + m_iHideHUD = (HIDEHUD_HEALTH | HIDEHUD_WEAPONS); + m_afPhysicsFlags |= PFLAG_OBSERVER; + pev->effects = EF_NODRAW; + pev->view_ofs = g_vecZero; + 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 + MESSAGE_BEGIN( MSG_ALL, gmsgTeamInfo ); + WRITE_BYTE( ENTINDEX(edict()) ); + WRITE_STRING( "" ); + MESSAGE_END(); + + // Remove all the player's stuff + RemoveAllItems( FALSE ); + + // Move them to the new position + UTIL_SetOrigin( pev, vecPosition ); + + // Find a player to watch + m_flNextObserverInput = 0; + Observer_SetMode(OBS_ROAMING); + + // Tell all clients this player is now a spectator + MESSAGE_BEGIN( MSG_ALL, gmsgSpectator ); + WRITE_BYTE( ENTINDEX( edict() ) ); + WRITE_BYTE( 1 ); + MESSAGE_END(); + + pev->angles = pev->v_angle = vecViewAngle; + pev->fixangle = TRUE; +} + +// 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_fWeapon = FALSE; // force weapon send + m_iHideHUD = 0; +} + +// 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() ); + pev->groupinfo = m_hObserverTarget->pev->groupinfo; + + // Move to the target + if ( pev->iuser1 != OBS_LOCKEDVIEW ) + 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" ); + } +} + +void CBasePlayer::ObserverInput_ChangeMode() +{ + if ( pev->iuser1 == OBS_ROAMING ) + Observer_SetMode( OBS_CHASE_FREE ); + else if ( pev->iuser1 == OBS_CHASE_FREE ) + Observer_SetMode( OBS_LOCKEDVIEW ); + else + Observer_SetMode( OBS_ROAMING ); +} + +void CBasePlayer::ObserverInput_NextPlayer() +{ + if ( pev->iuser1 != OBS_ROAMING ) + Observer_FindNextPlayer( false ); +} + +void CBasePlayer::ObserverInput_PrevPlayer() +{ + if ( pev->iuser1 != OBS_ROAMING ) + Observer_FindNextPlayer( true ); +} + +// Handle buttons in observer mode +void CBasePlayer::Observer_HandleButtons() +{ + + if ( m_flChangeAngles != -1 && m_flChangeAngles <= gpGlobals->time ) + { + if ( pev->iuser1 == OBS_LOCKEDVIEW ) + { + pev->angles = m_vecHitVelocity; + pev->fixangle = TRUE; + } + + m_flChangeAngles = -1; + } + + return; + + // 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_FREE ); + else if ( pev->iuser1 == OBS_CHASE_FREE ) + Observer_SetMode( OBS_LOCKEDVIEW ); + 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; + pev->maxspeed = 320; + + ClientPrint( pev, HUD_PRINTCENTER, "#Spec_Mode3" ); + return; + } + + if ( iMode == OBS_LOCKEDVIEW ) + { + // Find the spectator spawn position + CBaseEntity *pSpot = UTIL_FindEntityByClassname( NULL, "info_player_spectator"); + + if ( pSpot ) + { + // Move them to the new position + UTIL_SetOrigin( pev, pSpot->pev->origin ); + + pev->iuser1 = OBS_LOCKEDVIEW; + + m_flChangeAngles = gpGlobals->time + 0.1; + m_vecHitVelocity = pSpot->pev->v_angle; + pev->iuser2 = 0; + pev->maxspeed = 1; + } + + 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 == 0 ) + 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 = 1; + } + else + { + ClientPrint( pev, HUD_PRINTCENTER, "#Spec_NoTarget" ); + Observer_SetMode(OBS_ROAMING); + } + + return; + } +} + diff --git a/hlsdk/ricochet/dlls/pathcorner.cpp b/hlsdk/ricochet/dlls/pathcorner.cpp new file mode 100644 index 00000000..173b9608 --- /dev/null +++ b/hlsdk/ricochet/dlls/pathcorner.cpp @@ -0,0 +1,428 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// +// ========================== PATH_CORNER =========================== +// + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "trains.h" +#include "saverestore.h" + +class CPathCorner : public CPointEntity +{ +public: + void Spawn( ); + void KeyValue( KeyValueData* pkvd ); + float GetDelay( void ) { return m_flWait; } +// void Touch( CBaseEntity *pOther ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + float m_flWait; +}; + +LINK_ENTITY_TO_CLASS( path_corner, CPathCorner ); + +// Global Savedata for Delay +TYPEDESCRIPTION CPathCorner::m_SaveData[] = +{ + DEFINE_FIELD( CPathCorner, m_flWait, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CPathCorner, CPointEntity ); + +// +// Cache user-entity-field values until spawn is called. +// +void CPathCorner :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CPathCorner :: Spawn( ) +{ + ASSERTSZ(!FStringNull(pev->targetname), "path_corner without a targetname"); +} + +#if 0 +void CPathCorner :: Touch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + + if ( FBitSet ( pevToucher->flags, FL_MONSTER ) ) + {// monsters don't navigate path corners based on touch anymore + return; + } + + // If OTHER isn't explicitly looking for this path_corner, bail out + if ( pOther->m_pGoalEnt != this ) + { + return; + } + + // If OTHER has an enemy, this touch is incidental, ignore + if ( !FNullEnt(pevToucher->enemy) ) + { + return; // fighting, not following a path + } + + // UNDONE: support non-zero flWait + /* + if (m_flWait != 0) + ALERT(at_warning, "Non-zero path-cornder waits NYI"); + */ + + // Find the next "stop" on the path, make it the goal of the "toucher". + if (FStringNull(pev->target)) + { + ALERT(at_warning, "PathCornerTouch: no next stop specified"); + } + + pOther->m_pGoalEnt = CBaseEntity::Instance( FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(pev->target) ) ); + + // If "next spot" was not found (does not exist - level design error) + if ( !pOther->m_pGoalEnt ) + { + ALERT(at_console, "PathCornerTouch--%s couldn't find next stop in path: %s", STRING(pev->classname), STRING(pev->target)); + return; + } + + // Turn towards the next stop in the path. + pevToucher->ideal_yaw = UTIL_VecToYaw ( pOther->m_pGoalEnt->pev->origin - pevToucher->origin ); +} +#endif + + + +TYPEDESCRIPTION CPathTrack::m_SaveData[] = +{ + DEFINE_FIELD( CPathTrack, m_length, FIELD_FLOAT ), + DEFINE_FIELD( CPathTrack, m_pnext, FIELD_CLASSPTR ), + DEFINE_FIELD( CPathTrack, m_paltpath, FIELD_CLASSPTR ), + DEFINE_FIELD( CPathTrack, m_pprevious, FIELD_CLASSPTR ), + DEFINE_FIELD( CPathTrack, m_altName, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CPathTrack, CBaseEntity ); +LINK_ENTITY_TO_CLASS( path_track, CPathTrack ); + +// +// Cache user-entity-field values until spawn is called. +// +void CPathTrack :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "altpath")) + { + m_altName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CPathTrack :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int on; + + // Use toggles between two paths + if ( m_paltpath ) + { + on = !FBitSet( pev->spawnflags, SF_PATH_ALTERNATE ); + if ( ShouldToggle( useType, on ) ) + { + if ( on ) + SetBits( pev->spawnflags, SF_PATH_ALTERNATE ); + else + ClearBits( pev->spawnflags, SF_PATH_ALTERNATE ); + } + } + else // Use toggles between enabled/disabled + { + on = !FBitSet( pev->spawnflags, SF_PATH_DISABLED ); + + if ( ShouldToggle( useType, on ) ) + { + if ( on ) + SetBits( pev->spawnflags, SF_PATH_DISABLED ); + else + ClearBits( pev->spawnflags, SF_PATH_DISABLED ); + } + } +} + + +void CPathTrack :: Link( void ) +{ + edict_t *pentTarget; + + if ( !FStringNull(pev->target) ) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) ); + if ( !FNullEnt(pentTarget) ) + { + m_pnext = CPathTrack::Instance( pentTarget ); + + if ( m_pnext ) // If no next pointer, this is the end of a path + { + m_pnext->SetPrevious( this ); + } + } + else + ALERT( at_console, "Dead end link %s\n", STRING(pev->target) ); + } + + // Find "alternate" path + if ( m_altName ) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_altName) ); + if ( !FNullEnt(pentTarget) ) + { + m_paltpath = CPathTrack::Instance( pentTarget ); + + if ( m_paltpath ) // If no next pointer, this is the end of a path + { + m_paltpath->SetPrevious( this ); + } + } + } +} + + +void CPathTrack :: Spawn( void ) +{ + pev->solid = SOLID_TRIGGER; + UTIL_SetSize(pev, Vector(-8, -8, -8), Vector(8, 8, 8)); + + m_pnext = NULL; + m_pprevious = NULL; +// DEBUGGING CODE +#if PATH_SPARKLE_DEBUG + SetThink( Sparkle ); + pev->nextthink = gpGlobals->time + 0.5; +#endif +} + + +void CPathTrack::Activate( void ) +{ + if ( !FStringNull( pev->targetname ) ) // Link to next, and back-link + Link(); +} + +CPathTrack *CPathTrack :: ValidPath( CPathTrack *ppath, int testFlag ) +{ + if ( !ppath ) + return NULL; + + if ( testFlag && FBitSet( ppath->pev->spawnflags, SF_PATH_DISABLED ) ) + return NULL; + + return ppath; +} + + +void CPathTrack :: Project( CPathTrack *pstart, CPathTrack *pend, Vector *origin, float dist ) +{ + if ( pstart && pend ) + { + Vector dir = (pend->pev->origin - pstart->pev->origin); + dir = dir.Normalize(); + *origin = pend->pev->origin + dir * dist; + } +} + +CPathTrack *CPathTrack::GetNext( void ) +{ + if ( m_paltpath && FBitSet( pev->spawnflags, SF_PATH_ALTERNATE ) && !FBitSet( pev->spawnflags, SF_PATH_ALTREVERSE ) ) + return m_paltpath; + + return m_pnext; +} + + + +CPathTrack *CPathTrack::GetPrevious( void ) +{ + if ( m_paltpath && FBitSet( pev->spawnflags, SF_PATH_ALTERNATE ) && FBitSet( pev->spawnflags, SF_PATH_ALTREVERSE ) ) + return m_paltpath; + + return m_pprevious; +} + + + +void CPathTrack::SetPrevious( CPathTrack *pprev ) +{ + // Only set previous if this isn't my alternate path + if ( pprev && !FStrEq( STRING(pprev->pev->targetname), STRING(m_altName) ) ) + m_pprevious = pprev; +} + + +// Assumes this is ALWAYS enabled +CPathTrack *CPathTrack :: LookAhead( Vector *origin, float dist, int move ) +{ + CPathTrack *pcurrent; + float originalDist = dist; + + pcurrent = this; + Vector currentPos = *origin; + + if ( dist < 0 ) // Travelling backwards through path + { + dist = -dist; + while ( dist > 0 ) + { + Vector dir = pcurrent->pev->origin - currentPos; + float length = dir.Length(); + if ( !length ) + { + if ( !ValidPath(pcurrent->GetPrevious(), move) ) // If there is no previous node, or it's disabled, return now. + { + if ( !move ) + Project( pcurrent->GetNext(), pcurrent, origin, dist ); + return NULL; + } + pcurrent = pcurrent->GetPrevious(); + } + else if ( length > dist ) // enough left in this path to move + { + *origin = currentPos + (dir * (dist / length)); + return pcurrent; + } + else + { + dist -= length; + currentPos = pcurrent->pev->origin; + *origin = currentPos; + if ( !ValidPath(pcurrent->GetPrevious(), move) ) // If there is no previous node, or it's disabled, return now. + return NULL; + + pcurrent = pcurrent->GetPrevious(); + } + } + *origin = currentPos; + return pcurrent; + } + else + { + while ( dist > 0 ) + { + if ( !ValidPath(pcurrent->GetNext(), move) ) // If there is no next node, or it's disabled, return now. + { + if ( !move ) + Project( pcurrent->GetPrevious(), pcurrent, origin, dist ); + return NULL; + } + Vector dir = pcurrent->GetNext()->pev->origin - currentPos; + float length = dir.Length(); + if ( !length && !ValidPath( pcurrent->GetNext()->GetNext(), move ) ) + { + if ( dist == originalDist ) // HACK -- up against a dead end + return NULL; + return pcurrent; + } + if ( length > dist ) // enough left in this path to move + { + *origin = currentPos + (dir * (dist / length)); + return pcurrent; + } + else + { + dist -= length; + currentPos = pcurrent->GetNext()->pev->origin; + pcurrent = pcurrent->GetNext(); + *origin = currentPos; + } + } + *origin = currentPos; + } + + return pcurrent; +} + + +// Assumes this is ALWAYS enabled +CPathTrack *CPathTrack :: Nearest( Vector origin ) +{ + int deadCount; + float minDist, dist; + Vector delta; + CPathTrack *ppath, *pnearest; + + + delta = origin - pev->origin; + delta.z = 0; + minDist = delta.Length(); + pnearest = this; + ppath = GetNext(); + + // Hey, I could use the old 2 racing pointers solution to this, but I'm lazy :) + deadCount = 0; + while ( ppath && ppath != this ) + { + deadCount++; + if ( deadCount > 9999 ) + { + ALERT( at_error, "Bad sequence of path_tracks from %s", STRING(pev->targetname) ); + return NULL; + } + delta = origin - ppath->pev->origin; + delta.z = 0; + dist = delta.Length(); + if ( dist < minDist ) + { + minDist = dist; + pnearest = ppath; + } + ppath = ppath->GetNext(); + } + return pnearest; +} + + +CPathTrack *CPathTrack::Instance( edict_t *pent ) +{ + if ( FClassnameIs( pent, "path_track" ) ) + return (CPathTrack *)GET_PRIVATE(pent); + return NULL; +} + + + // DEBUGGING CODE +#if PATH_SPARKLE_DEBUG +void CPathTrack :: Sparkle( void ) +{ + + pev->nextthink = gpGlobals->time + 0.2; + if ( FBitSet( pev->spawnflags, SF_PATH_DISABLED ) ) + UTIL_ParticleEffect(pev->origin, Vector(0,0,100), 210, 10); + else + UTIL_ParticleEffect(pev->origin, Vector(0,0,100), 84, 10); +} +#endif + diff --git a/hlsdk/ricochet/dlls/plane.cpp b/hlsdk/ricochet/dlls/plane.cpp new file mode 100644 index 00000000..77e22737 --- /dev/null +++ b/hlsdk/ricochet/dlls/plane.cpp @@ -0,0 +1,60 @@ +/*** +* +* Copyright (c) 1999, 2000 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 "plane.h" + +//========================================================= +// Plane +//========================================================= +CPlane :: CPlane ( void ) +{ + m_fInitialized = FALSE; +} + +//========================================================= +// InitializePlane - Takes a normal for the plane and a +// point on the plane and +//========================================================= +void CPlane :: InitializePlane ( const Vector &vecNormal, const Vector &vecPoint ) +{ + m_vecNormal = vecNormal; + m_flDist = DotProduct ( m_vecNormal, vecPoint ); + m_fInitialized = TRUE; +} + + +//========================================================= +// PointInFront - determines whether the given vector is +// in front of the plane. +//========================================================= +BOOL CPlane :: PointInFront ( const Vector &vecPoint ) +{ + float flFace; + + if ( !m_fInitialized ) + { + return FALSE; + } + + flFace = DotProduct ( m_vecNormal, vecPoint ) - m_flDist; + + if ( flFace >= 0 ) + { + return TRUE; + } + + return FALSE; +} + diff --git a/hlsdk/ricochet/dlls/plane.h b/hlsdk/ricochet/dlls/plane.h new file mode 100644 index 00000000..5b64e84d --- /dev/null +++ b/hlsdk/ricochet/dlls/plane.h @@ -0,0 +1,43 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +#ifndef PLANE_H +#define PLANE_H + +//========================================================= +// Plane +//========================================================= +class CPlane +{ +public: + CPlane ( void ); + + //========================================================= + // InitializePlane - Takes a normal for the plane and a + // point on the plane and + //========================================================= + void InitializePlane ( const Vector &vecNormal, const Vector &vecPoint ); + + //========================================================= + // PointInFront - determines whether the given vector is + // in front of the plane. + //========================================================= + BOOL PointInFront ( const Vector &vecPoint ); + + Vector m_vecNormal; + float m_flDist; + BOOL m_fInitialized; +}; + +#endif // PLANE_H diff --git a/hlsdk/ricochet/dlls/plats.cpp b/hlsdk/ricochet/dlls/plats.cpp new file mode 100644 index 00000000..5b727f79 --- /dev/null +++ b/hlsdk/ricochet/dlls/plats.cpp @@ -0,0 +1,2285 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== plats.cpp ======================================================== + + spawn, think, and touch functions for trains, etc + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "trains.h" +#include "saverestore.h" + +static void PlatSpawnInsideTrigger(entvars_t* pevPlatform); + +#define SF_PLAT_TOGGLE 0x0001 + +class CBasePlatTrain : public CBaseToggle +{ +public: + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + void KeyValue( KeyValueData* pkvd); + void Precache( void ); + + // This is done to fix spawn flag collisions between this class and a derived class + virtual BOOL IsTogglePlat( void ) { return (pev->spawnflags & SF_PLAT_TOGGLE) ? TRUE : FALSE; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + BYTE m_bMoveSnd; // sound a plat makes while moving + BYTE m_bStopSnd; // sound a plat makes when it stops + float m_volume; // Sound volume +}; + +TYPEDESCRIPTION CBasePlatTrain::m_SaveData[] = +{ + DEFINE_FIELD( CBasePlatTrain, m_bMoveSnd, FIELD_CHARACTER ), + DEFINE_FIELD( CBasePlatTrain, m_bStopSnd, FIELD_CHARACTER ), + DEFINE_FIELD( CBasePlatTrain, m_volume, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CBasePlatTrain, CBaseToggle ); + +void CBasePlatTrain :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "lip")) + { + m_flLip = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "height")) + { + m_flHeight = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "rotation")) + { + m_vecFinalAngle.x = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "movesnd")) + { + m_bMoveSnd = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "stopsnd")) + { + m_bStopSnd = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "volume")) + { + m_volume = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +#define noiseMoving noise +#define noiseArrived noise1 + +void CBasePlatTrain::Precache( void ) +{ +// set the plat's "in-motion" sound + switch (m_bMoveSnd) + { + case 0: + pev->noiseMoving = MAKE_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("plats/bigmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/bigmove1.wav"); + break; + case 2: + PRECACHE_SOUND ("plats/bigmove2.wav"); + pev->noiseMoving = MAKE_STRING("plats/bigmove2.wav"); + break; + case 3: + PRECACHE_SOUND ("plats/elevmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/elevmove1.wav"); + break; + case 4: + PRECACHE_SOUND ("plats/elevmove2.wav"); + pev->noiseMoving = MAKE_STRING("plats/elevmove2.wav"); + break; + case 5: + PRECACHE_SOUND ("plats/elevmove3.wav"); + pev->noiseMoving = MAKE_STRING("plats/elevmove3.wav"); + break; + case 6: + PRECACHE_SOUND ("plats/freightmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/freightmove1.wav"); + break; + case 7: + PRECACHE_SOUND ("plats/freightmove2.wav"); + pev->noiseMoving = MAKE_STRING("plats/freightmove2.wav"); + break; + case 8: + PRECACHE_SOUND ("plats/heavymove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/heavymove1.wav"); + break; + case 9: + PRECACHE_SOUND ("plats/rackmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/rackmove1.wav"); + break; + case 10: + PRECACHE_SOUND ("plats/railmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/railmove1.wav"); + break; + case 11: + PRECACHE_SOUND ("plats/squeekmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/squeekmove1.wav"); + break; + case 12: + PRECACHE_SOUND ("plats/talkmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/talkmove1.wav"); + break; + case 13: + PRECACHE_SOUND ("plats/talkmove2.wav"); + pev->noiseMoving = MAKE_STRING("plats/talkmove2.wav"); + break; + default: + pev->noiseMoving = MAKE_STRING("common/null.wav"); + break; + } + +// set the plat's 'reached destination' stop sound + switch (m_bStopSnd) + { + case 0: + pev->noiseArrived = MAKE_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("plats/bigstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/bigstop1.wav"); + break; + case 2: + PRECACHE_SOUND ("plats/bigstop2.wav"); + pev->noiseArrived = MAKE_STRING("plats/bigstop2.wav"); + break; + case 3: + PRECACHE_SOUND ("plats/freightstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/freightstop1.wav"); + break; + case 4: + PRECACHE_SOUND ("plats/heavystop2.wav"); + pev->noiseArrived = MAKE_STRING("plats/heavystop2.wav"); + break; + case 5: + PRECACHE_SOUND ("plats/rackstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/rackstop1.wav"); + break; + case 6: + PRECACHE_SOUND ("plats/railstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/railstop1.wav"); + break; + case 7: + PRECACHE_SOUND ("plats/squeekstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/squeekstop1.wav"); + break; + case 8: + PRECACHE_SOUND ("plats/talkstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/talkstop1.wav"); + break; + + default: + pev->noiseArrived = MAKE_STRING("common/null.wav"); + break; + } +} + +// +//====================== PLAT code ==================================================== +// + + +#define noiseMovement noise +#define noiseStopMoving noise1 + +class CFuncPlat : public CBasePlatTrain +{ +public: + void Spawn( void ); + void Precache( void ); + void Setup( void ); + + virtual void Blocked( CBaseEntity *pOther ); + + + void EXPORT PlatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + void EXPORT CallGoDown( void ) { GoDown(); } + void EXPORT CallHitTop( void ) { HitTop(); } + void EXPORT CallHitBottom( void ) { HitBottom(); } + + virtual void GoUp( void ); + virtual void GoDown( void ); + virtual void HitTop( void ); + virtual void HitBottom( void ); +}; +LINK_ENTITY_TO_CLASS( func_plat, CFuncPlat ); + + +// UNDONE: Need to save this!!! It needs class & linkage +class CPlatTrigger : public CBaseEntity +{ +public: + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DONT_SAVE; } + void SpawnInsideTrigger( CFuncPlat *pPlatform ); + void Touch( CBaseEntity *pOther ); + CFuncPlat *m_pPlatform; +}; + + + +/*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER +speed default 150 + +Plats are always drawn in the extended position, so they will light correctly. + +If the plat is the target of another trigger or button, it will start out disabled in +the extended position until it is trigger, when it will lower and become a normal plat. + +If the "height" key is set, that will determine the amount the plat moves, instead of +being implicitly determined by the model's height. + +Set "sounds" to one of the following: +1) base fast +2) chain slow +*/ + +void CFuncPlat :: Setup( void ) +{ + //pev->noiseMovement = MAKE_STRING("plats/platmove1.wav"); + //pev->noiseStopMoving = MAKE_STRING("plats/platstop1.wav"); + + if (m_flTLength == 0) + m_flTLength = 80; + if (m_flTWidth == 0) + m_flTWidth = 10; + + pev->angles = g_vecZero; + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); // set size and link into world + UTIL_SetSize(pev, pev->mins, pev->maxs); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + // vecPosition1 is the top position, vecPosition2 is the bottom + m_vecPosition1 = pev->origin; + m_vecPosition2 = pev->origin; + if (m_flHeight != 0) + m_vecPosition2.z = pev->origin.z - m_flHeight; + else + m_vecPosition2.z = pev->origin.z - pev->size.z + 8; + if (pev->speed == 0) + pev->speed = 150; + + if ( m_volume == 0 ) + m_volume = 0.85; +} + + +void CFuncPlat :: Precache( ) +{ + CBasePlatTrain::Precache(); + //PRECACHE_SOUND("plats/platmove1.wav"); + //PRECACHE_SOUND("plats/platstop1.wav"); + if ( !IsTogglePlat() ) + PlatSpawnInsideTrigger( pev ); // the "start moving" trigger +} + + +void CFuncPlat :: Spawn( ) +{ + Setup(); + + Precache(); + + // If this platform is the target of some button, it starts at the TOP position, + // and is brought down by that button. Otherwise, it starts at BOTTOM. + if ( !FStringNull(pev->targetname) ) + { + UTIL_SetOrigin (pev, m_vecPosition1); + m_toggle_state = TS_AT_TOP; + SetUse( &CFuncPlat::PlatUse ); + } + else + { + UTIL_SetOrigin (pev, m_vecPosition2); + m_toggle_state = TS_AT_BOTTOM; + } +} + + + +static void PlatSpawnInsideTrigger(entvars_t* pevPlatform) +{ + GetClassPtr( (CPlatTrigger *)NULL)->SpawnInsideTrigger( GetClassPtr( (CFuncPlat *)pevPlatform ) ); +} + + +// +// Create a trigger entity for a platform. +// +void CPlatTrigger :: SpawnInsideTrigger( CFuncPlat *pPlatform ) +{ + m_pPlatform = pPlatform; + // Create trigger entity, "point" it at the owning platform, give it a touch method + pev->solid = SOLID_TRIGGER; + pev->movetype = MOVETYPE_NONE; + pev->origin = pPlatform->pev->origin; + + // Establish the trigger field's size + Vector vecTMin = m_pPlatform->pev->mins + Vector ( 25 , 25 , 0 ); + Vector vecTMax = m_pPlatform->pev->maxs + Vector ( 25 , 25 , 8 ); + vecTMin.z = vecTMax.z - ( m_pPlatform->m_vecPosition1.z - m_pPlatform->m_vecPosition2.z + 8 ); + if (m_pPlatform->pev->size.x <= 50) + { + vecTMin.x = (m_pPlatform->pev->mins.x + m_pPlatform->pev->maxs.x) / 2; + vecTMax.x = vecTMin.x + 1; + } + if (m_pPlatform->pev->size.y <= 50) + { + vecTMin.y = (m_pPlatform->pev->mins.y + m_pPlatform->pev->maxs.y) / 2; + vecTMax.y = vecTMin.y + 1; + } + UTIL_SetSize ( pev, vecTMin, vecTMax ); +} + + +// +// When the platform's trigger field is touched, the platform ??? +// +void CPlatTrigger :: Touch( CBaseEntity *pOther ) +{ + // Ignore touches by non-players + entvars_t* pevToucher = pOther->pev; + if ( !FClassnameIs (pevToucher, "player") ) + return; + + // Ignore touches by corpses + if (!pOther->IsAlive()) + return; + + // Make linked platform go up/down. + if (m_pPlatform->m_toggle_state == TS_AT_BOTTOM) + m_pPlatform->GoUp(); + else if (m_pPlatform->m_toggle_state == TS_AT_TOP) + m_pPlatform->pev->nextthink = m_pPlatform->pev->ltime + 1;// delay going down +} + + +// +// Used by SUB_UseTargets, when a platform is the target of a button. +// Start bringing platform down. +// +void CFuncPlat :: PlatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( IsTogglePlat() ) + { + // Top is off, bottom is on + BOOL on = (m_toggle_state == TS_AT_BOTTOM) ? TRUE : FALSE; + + if ( !ShouldToggle( useType, on ) ) + return; + + if (m_toggle_state == TS_AT_TOP) + GoDown(); + else if ( m_toggle_state == TS_AT_BOTTOM ) + GoUp(); + } + else + { + SetUse( NULL ); + + if (m_toggle_state == TS_AT_TOP) + GoDown(); + } +} + + +// +// Platform is at top, now starts moving down. +// +void CFuncPlat :: GoDown( void ) +{ + if(pev->noiseMovement) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_AT_TOP || m_toggle_state == TS_GOING_UP); + m_toggle_state = TS_GOING_DOWN; + SetMoveDone(&CFuncPlat::CallHitBottom); + LinearMove(m_vecPosition2, pev->speed); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncPlat :: HitBottom( void ) +{ + if(pev->noiseMovement) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement)); + + if (pev->noiseStopMoving) + EMIT_SOUND(ENT(pev), CHAN_WEAPON, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_AT_BOTTOM; +} + + +// +// Platform is at bottom, now starts moving up +// +void CFuncPlat :: GoUp( void ) +{ + if (pev->noiseMovement) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_AT_BOTTOM || m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_GOING_UP; + SetMoveDone(&CFuncPlat::CallHitTop); + LinearMove(m_vecPosition1, pev->speed); +} + + +// +// Platform has hit top. Pauses, then starts back down again. +// +void CFuncPlat :: HitTop( void ) +{ + if(pev->noiseMovement) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement)); + + if (pev->noiseStopMoving) + EMIT_SOUND(ENT(pev), CHAN_WEAPON, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_GOING_UP); + m_toggle_state = TS_AT_TOP; + + if ( !IsTogglePlat() ) + { + // After a delay, the platform will automatically start going down again. + SetThink( &CFuncPlat::CallGoDown ); + pev->nextthink = pev->ltime + 3; + } +} + + +void CFuncPlat :: Blocked( CBaseEntity *pOther ) +{ + ALERT( at_aiconsole, "%s Blocked by %s\n", STRING(pev->classname), STRING(pOther->pev->classname) ); + // Hurt the blocker a little + pOther->TakeDamage(pev, pev, 1, DMG_CRUSH); + + if(pev->noiseMovement) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement)); + + // Send the platform back where it came from + ASSERT(m_toggle_state == TS_GOING_UP || m_toggle_state == TS_GOING_DOWN); + if (m_toggle_state == TS_GOING_UP) + GoDown(); + else if (m_toggle_state == TS_GOING_DOWN) + GoUp (); +} + + +class CFuncPlatRot : public CFuncPlat +{ +public: + void Spawn( void ); + void SetupRotation( void ); + + virtual void GoUp( void ); + virtual void GoDown( void ); + virtual void HitTop( void ); + virtual void HitBottom( void ); + + void RotMove( Vector &destAngle, float time ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + Vector m_end, m_start; +}; +LINK_ENTITY_TO_CLASS( func_platrot, CFuncPlatRot ); +TYPEDESCRIPTION CFuncPlatRot::m_SaveData[] = +{ + DEFINE_FIELD( CFuncPlatRot, m_end, FIELD_VECTOR ), + DEFINE_FIELD( CFuncPlatRot, m_start, FIELD_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CFuncPlatRot, CFuncPlat ); + + +void CFuncPlatRot :: SetupRotation( void ) +{ + if ( m_vecFinalAngle.x != 0 ) // This plat rotates too! + { + CBaseToggle :: AxisDir( pev ); + m_start = pev->angles; + m_end = pev->angles + pev->movedir * m_vecFinalAngle.x; + } + else + { + m_start = g_vecZero; + m_end = g_vecZero; + } + if ( !FStringNull(pev->targetname) ) // Start at top + { + pev->angles = m_end; + } +} + + +void CFuncPlatRot :: Spawn( void ) +{ + CFuncPlat :: Spawn(); + SetupRotation(); +} + +void CFuncPlatRot :: GoDown( void ) +{ + CFuncPlat :: GoDown(); + RotMove( m_start, pev->nextthink - pev->ltime ); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncPlatRot :: HitBottom( void ) +{ + CFuncPlat :: HitBottom(); + pev->avelocity = g_vecZero; + pev->angles = m_start; +} + + +// +// Platform is at bottom, now starts moving up +// +void CFuncPlatRot :: GoUp( void ) +{ + CFuncPlat :: GoUp(); + RotMove( m_end, pev->nextthink - pev->ltime ); +} + + +// +// Platform has hit top. Pauses, then starts back down again. +// +void CFuncPlatRot :: HitTop( void ) +{ + CFuncPlat :: HitTop(); + pev->avelocity = g_vecZero; + pev->angles = m_end; +} + + +void CFuncPlatRot :: RotMove( Vector &destAngle, float time ) +{ + // set destdelta to the vector needed to move + Vector vecDestDelta = destAngle - pev->angles; + + // Travel time is so short, we're practically there already; so make it so. + if ( time >= 0.1) + pev->avelocity = vecDestDelta / time; + else + { + pev->avelocity = vecDestDelta; + pev->nextthink = pev->ltime + 1; + } +} + + +// +//====================== TRAIN code ================================================== +// + +class CFuncTrain : public CBasePlatTrain +{ +public: + void Spawn( void ); + void Precache( void ); + void Activate( void ); + void OverrideReset( void ); + + void Blocked( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + + void EXPORT Wait( void ); + void EXPORT Next( void ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + entvars_t *m_pevCurrentTarget; + int m_sounds; + BOOL m_activated; +}; + +LINK_ENTITY_TO_CLASS( func_train, CFuncTrain ); +TYPEDESCRIPTION CFuncTrain::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTrain, m_sounds, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrain, m_pevCurrentTarget, FIELD_EVARS ), + DEFINE_FIELD( CFuncTrain, m_activated, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTrain, CBasePlatTrain ); + + +void CFuncTrain :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBasePlatTrain::KeyValue( pkvd ); +} + + +void CFuncTrain :: Blocked( CBaseEntity *pOther ) + +{ + if ( gpGlobals->time < m_flActivateFinished) + return; + + m_flActivateFinished = gpGlobals->time + 0.5; + + pOther->TakeDamage(pev, pev, pev->dmg, DMG_CRUSH); +} + + +void CFuncTrain :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->spawnflags & SF_TRAIN_WAIT_RETRIGGER ) + { + // Move toward my target + pev->spawnflags &= ~SF_TRAIN_WAIT_RETRIGGER; + Next(); + } + else + { + pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER; + // Pop back to last target if it's available + if ( pev->enemy ) + pev->target = pev->enemy->v.targetname; + pev->nextthink = 0; + pev->velocity = g_vecZero; + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + } +} + + +void CFuncTrain :: Wait( void ) +{ + // Fire the pass target if there is one + if ( m_pevCurrentTarget->message ) + { + FireTargets( STRING(m_pevCurrentTarget->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( m_pevCurrentTarget->spawnflags, SF_CORNER_FIREONCE ) ) + m_pevCurrentTarget->message = 0; + } + + // need pointer to LAST target. + if ( FBitSet (m_pevCurrentTarget->spawnflags , SF_TRAIN_WAIT_RETRIGGER ) || ( pev->spawnflags & SF_TRAIN_WAIT_RETRIGGER ) ) + { + pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER; + // clear the sound channel. + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + pev->nextthink = 0; + return; + } + + // ALERT ( at_console, "%f\n", m_flWait ); + + if (m_flWait != 0) + {// -1 wait will wait forever! + pev->nextthink = pev->ltime + m_flWait; + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + SetThink( &CFuncTrain::Next ); + } + else + { + Next();// do it RIGHT now! + } +} + + +// +// Train next - path corner needs to change to next target +// +void CFuncTrain :: Next( void ) +{ + CBaseEntity *pTarg; + + + // now find our next target + pTarg = GetNextTarget(); + + if ( !pTarg ) + { + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + // Play stop sound + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + return; + } + + // Save last target in case we need to find it again + pev->message = pev->target; + + pev->target = pTarg->pev->target; + m_flWait = pTarg->GetDelay(); + + if ( m_pevCurrentTarget && m_pevCurrentTarget->speed != 0 ) + {// don't copy speed from target if it is 0 (uninitialized) + pev->speed = m_pevCurrentTarget->speed; + ALERT( at_aiconsole, "Train %s speed to %4.2f\n", STRING(pev->targetname), pev->speed ); + } + m_pevCurrentTarget = pTarg->pev;// keep track of this since path corners change our target for us. + + pev->enemy = pTarg->edict();//hack + + if(FBitSet(m_pevCurrentTarget->spawnflags, SF_CORNER_TELEPORT)) + { + // Path corner has indicated a teleport to the next corner. + SetBits(pev->effects, EF_NOINTERP); + UTIL_SetOrigin(pev, pTarg->pev->origin - (pev->mins + pev->maxs)* 0.5); + Wait(); // Get on with doing the next path corner. + } + else + { + // Normal linear move. + + // CHANGED this from CHAN_VOICE to CHAN_STATIC around OEM beta time because trains should + // use CHAN_STATIC for their movement sounds to prevent sound field problems. + // this is not a hack or temporary fix, this is how things should be. (sjb). + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + if ( pev->noiseMovement ) + EMIT_SOUND (ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement), m_volume, ATTN_NORM); + ClearBits(pev->effects, EF_NOINTERP); + SetMoveDone( &CFuncTrain::Wait ); + LinearMove (pTarg->pev->origin - (pev->mins + pev->maxs)* 0.5, pev->speed); + } +} + + +void CFuncTrain :: Activate( void ) +{ + // Not yet active, so teleport to first target + if ( !m_activated ) + { + m_activated = TRUE; + entvars_t *pevTarg = VARS( FIND_ENTITY_BY_TARGETNAME (NULL, STRING(pev->target) ) ); + + pev->target = pevTarg->target; + m_pevCurrentTarget = pevTarg;// keep track of this since path corners change our target for us. + + UTIL_SetOrigin (pev, pevTarg->origin - (pev->mins + pev->maxs) * 0.5 ); + + if ( FStringNull(pev->targetname) ) + { // not triggered, so start immediately + pev->nextthink = pev->ltime + 0.1; + SetThink( &CFuncTrain::Next ); + } + else + pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER; + } +} + +/*QUAKED func_train (0 .5 .8) ? +Trains are moving platforms that players can ride. +The targets origin specifies the min point of the train at each corner. +The train spawns at the first target it is pointing at. +If the train is the target of a button or trigger, it will not begin moving until activated. +speed default 100 +dmg default 2 +sounds +1) ratchet metal +*/ + +void CFuncTrain :: Spawn( void ) +{ + Precache(); + if (pev->speed == 0) + pev->speed = 100; + + if ( FStringNull(pev->target) ) + ALERT(at_console, "FuncTrain with no target"); + + if (pev->dmg == 0) + pev->dmg = 2; + + pev->movetype = MOVETYPE_PUSH; + + if ( FBitSet (pev->spawnflags, SF_TRACKTRAIN_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + + SET_MODEL( ENT(pev), STRING(pev->model) ); + UTIL_SetSize (pev, pev->mins, pev->maxs); + UTIL_SetOrigin(pev, pev->origin); + + m_activated = FALSE; + + if ( m_volume == 0 ) + m_volume = 0.85; +} + + +void CFuncTrain::Precache( void ) +{ + CBasePlatTrain::Precache(); + +#if 0 // obsolete + // otherwise use preset sound + switch (m_sounds) + { + case 0: + pev->noise = 0; + pev->noise1 = 0; + break; + + case 1: + PRECACHE_SOUND ("plats/train2.wav"); + PRECACHE_SOUND ("plats/train1.wav"); + pev->noise = MAKE_STRING("plats/train2.wav"); + pev->noise1 = MAKE_STRING("plats/train1.wav"); + break; + + case 2: + PRECACHE_SOUND ("plats/platmove1.wav"); + PRECACHE_SOUND ("plats/platstop1.wav"); + pev->noise = MAKE_STRING("plats/platstop1.wav"); + pev->noise1 = MAKE_STRING("plats/platmove1.wav"); + break; + } +#endif +} + + +void CFuncTrain::OverrideReset( void ) +{ + CBaseEntity *pTarg; + + // Are we moving? + if ( pev->velocity != g_vecZero && pev->nextthink != 0 ) + { + pev->target = pev->message; + // now find our next target + pTarg = GetNextTarget(); + if ( !pTarg ) + { + pev->nextthink = 0; + pev->velocity = g_vecZero; + } + else // Keep moving for 0.1 secs, then find path_corner again and restart + { + SetThink( &CFuncTrain::Next ); + pev->nextthink = pev->ltime + 0.1; + } + } +} + + + + +// --------------------------------------------------------------------- +// +// Track Train +// +// --------------------------------------------------------------------- + +TYPEDESCRIPTION CFuncTrackTrain::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTrackTrain, m_ppath, FIELD_CLASSPTR ), + DEFINE_FIELD( CFuncTrackTrain, m_length, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_height, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_speed, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_dir, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_startSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_controlMins, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTrackTrain, m_controlMaxs, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTrackTrain, m_sounds, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrackTrain, m_flVolume, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_flBank, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_oldSpeed, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTrackTrain, CBaseEntity ); +LINK_ENTITY_TO_CLASS( func_tracktrain, CFuncTrackTrain ); + +void CFuncTrackTrain :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "wheels")) + { + m_length = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "height")) + { + m_height = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "startspeed")) + { + m_startSpeed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "volume")) + { + m_flVolume = (float) (atoi(pkvd->szValue)); + m_flVolume *= 0.1; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "bank")) + { + m_flBank = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +void CFuncTrackTrain :: NextThink( float thinkTime, BOOL alwaysThink ) +{ + if ( alwaysThink ) + pev->flags |= FL_ALWAYSTHINK; + else + pev->flags &= ~FL_ALWAYSTHINK; + + pev->nextthink = thinkTime; +} + + +void CFuncTrackTrain :: Blocked( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + // Blocker is on-ground on the train + if ( FBitSet( pevOther->flags, FL_ONGROUND ) && VARS(pevOther->groundentity) == pev ) + { + float deltaSpeed = fabs(pev->speed); + if ( deltaSpeed > 50 ) + deltaSpeed = 50; + if ( !pevOther->velocity.z ) + pevOther->velocity.z += deltaSpeed; + return; + } + else + pevOther->velocity = (pevOther->origin - pev->origin ).Normalize() * pev->dmg; + + ALERT( at_aiconsole, "TRAIN(%s): Blocked by %s (dmg:%.2f)\n", STRING(pev->targetname), STRING(pOther->pev->classname), pev->dmg ); + if ( pev->dmg <= 0 ) + return; + // we can't hurt this thing, so we're not concerned with it + pOther->TakeDamage(pev, pev, pev->dmg, DMG_CRUSH); +} + + +void CFuncTrackTrain :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( useType != USE_SET ) + { + if ( !ShouldToggle( useType, (pev->speed != 0) ) ) + return; + + if ( pev->speed == 0 ) + { + pev->speed = m_speed * m_dir; + + Next(); + } + else + { + pev->speed = 0; + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + StopSound(); + SetThink( NULL ); + } + } + else + { + float delta = value; + + delta = ((int)(pev->speed * 4) / (int)m_speed)*0.25 + 0.25 * delta; + if ( delta > 1 ) + delta = 1; + else if ( delta < -1 ) + delta = -1; + if ( pev->spawnflags & SF_TRACKTRAIN_FORWARDONLY ) + { + if ( delta < 0 ) + delta = 0; + } + pev->speed = m_speed * delta; + Next(); + ALERT( at_aiconsole, "TRAIN(%s), speed to %.2f\n", STRING(pev->targetname), pev->speed ); + } +} + + +static float Fix( float angle ) +{ + while ( angle < 0 ) + angle += 360; + while ( angle > 360 ) + angle -= 360; + + return angle; +} + + +static void FixupAngles( Vector &v ) +{ + v.x = Fix( v.x ); + v.y = Fix( v.y ); + v.z = Fix( v.z ); +} + +#define TRAIN_STARTPITCH 60 +#define TRAIN_MAXPITCH 200 +#define TRAIN_MAXSPEED 1000 // approx max speed for sound pitch calculation + +void CFuncTrackTrain :: StopSound( void ) +{ + // if sound playing, stop it + if (m_soundPlaying && pev->noise) + { + unsigned short us_encode; + unsigned short us_sound = ( ( unsigned short )( m_sounds ) & 0x0007 ) << 12; + + us_encode = us_sound; + + PLAYBACK_EVENT_FULL( FEV_RELIABLE | FEV_UPDATE, edict(), m_usAdjustPitch, 0.0, + (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, us_encode, 0, 1, 0 ); + + /* + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noise)); + */ + EMIT_SOUND_DYN(ENT(pev), CHAN_ITEM, "plats/ttrain_brake1.wav", m_flVolume, ATTN_NORM, 0, 100); + } + + m_soundPlaying = 0; +} + +// update pitch based on speed, start sound if not playing +// NOTE: when train goes through transition, m_soundPlaying should go to 0, +// which will cause the looped sound to restart. + +void CFuncTrackTrain :: UpdateSound( void ) +{ + float flpitch; + + if (!pev->noise) + return; + + flpitch = TRAIN_STARTPITCH + (abs(static_cast(pev->speed)) * (TRAIN_MAXPITCH - TRAIN_STARTPITCH) / TRAIN_MAXSPEED); + + if (!m_soundPlaying) + { + // play startup sound for train + EMIT_SOUND_DYN(ENT(pev), CHAN_ITEM, "plats/ttrain_start1.wav", m_flVolume, ATTN_NORM, 0, 100); + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noise), m_flVolume, ATTN_NORM, 0, (int) flpitch); + m_soundPlaying = 1; + } + else + { +/* + // update pitch + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noise), m_flVolume, ATTN_NORM, SND_CHANGE_PITCH, (int) flpitch); +*/ + // volume 0.0 - 1.0 - 6 bits + // m_sounds 3 bits + // flpitch = 6 bits + // 15 bits total + + unsigned short us_encode; + unsigned short us_sound = ( ( unsigned short )( m_sounds ) & 0x0007 ) << 12; + unsigned short us_pitch = ( ( unsigned short )( flpitch / 10.0 ) & 0x003f ) << 6; + unsigned short us_volume = ( ( unsigned short )( m_flVolume * 40.0 ) & 0x003f ); + + us_encode = us_sound | us_pitch | us_volume; + + PLAYBACK_EVENT_FULL( FEV_RELIABLE | FEV_UPDATE, edict(), m_usAdjustPitch, 0.0, + (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, us_encode, 0, 0, 0 ); + } +} + + +void CFuncTrackTrain :: Next( void ) +{ + float time = 0.5; + + if ( !pev->speed ) + { + ALERT( at_aiconsole, "TRAIN(%s): Speed is 0\n", STRING(pev->targetname) ); + StopSound(); + return; + } + +// if ( !m_ppath ) +// m_ppath = CPathTrack::Instance(FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) )); + if ( !m_ppath ) + { + ALERT( at_aiconsole, "TRAIN(%s): Lost path\n", STRING(pev->targetname) ); + StopSound(); + return; + } + + UpdateSound(); + + Vector nextPos = pev->origin; + + nextPos.z -= m_height; + CPathTrack *pnext = m_ppath->LookAhead( &nextPos, pev->speed * 0.1, 1 ); + nextPos.z += m_height; + + pev->velocity = (nextPos - pev->origin) * 10; + Vector nextFront = pev->origin; + + nextFront.z -= m_height; + if ( m_length > 0 ) + m_ppath->LookAhead( &nextFront, m_length, 0 ); + else + m_ppath->LookAhead( &nextFront, 100, 0 ); + nextFront.z += m_height; + + Vector delta = nextFront - pev->origin; + Vector angles = UTIL_VecToAngles( delta ); + // The train actually points west + angles.y += 180; + + // !!! All of this crap has to be done to make the angles not wrap around, revisit this. + FixupAngles( angles ); + FixupAngles( pev->angles ); + + if ( !pnext || (delta.x == 0 && delta.y == 0) ) + angles = pev->angles; + + float vy, vx; + if ( !(pev->spawnflags & SF_TRACKTRAIN_NOPITCH) ) + vx = UTIL_AngleDistance( angles.x, pev->angles.x ); + else + vx = 0; + vy = UTIL_AngleDistance( angles.y, pev->angles.y ); + + pev->avelocity.y = vy * 10; + pev->avelocity.x = vx * 10; + + if ( m_flBank != 0 ) + { + if ( pev->avelocity.y < -5 ) + pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( -m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z); + else if ( pev->avelocity.y > 5 ) + pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z); + else + pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( 0, pev->angles.z, m_flBank*4 ), pev->angles.z) * 4; + } + + if ( pnext ) + { + if ( pnext != m_ppath ) + { + CPathTrack *pFire; + if ( pev->speed >= 0 ) + pFire = pnext; + else + pFire = m_ppath; + + m_ppath = pnext; + // Fire the pass target if there is one + if ( pFire->pev->message ) + { + FireTargets( STRING(pFire->pev->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( pFire->pev->spawnflags, SF_PATH_FIREONCE ) ) + pFire->pev->message = 0; + } + + if ( pFire->pev->spawnflags & SF_PATH_DISABLE_TRAIN ) + pev->spawnflags |= SF_TRACKTRAIN_NOCONTROL; + + // Don't override speed if under user control + if ( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL ) + { + if ( pFire->pev->speed != 0 ) + {// don't copy speed from target if it is 0 (uninitialized) + pev->speed = pFire->pev->speed; + ALERT( at_aiconsole, "TrackTrain %s speed to %4.2f\n", STRING(pev->targetname), pev->speed ); + } + } + + } + SetThink( &CFuncTrackTrain::Next ); + NextThink( pev->ltime + time, TRUE ); + } + else // end of path, stop + { + StopSound(); + pev->velocity = (nextPos - pev->origin); + pev->avelocity = g_vecZero; + float distance = pev->velocity.Length(); + m_oldSpeed = pev->speed; + + + pev->speed = 0; + + // Move to the dead end + + // Are we there yet? + if ( distance > 0 ) + { + // no, how long to get there? + time = distance / m_oldSpeed; + pev->velocity = pev->velocity * (m_oldSpeed / distance); + SetThink( &CFuncTrackTrain::DeadEnd ); + NextThink( pev->ltime + time, FALSE ); + } + else + { + DeadEnd(); + } + } +} + + +void CFuncTrackTrain::DeadEnd( void ) +{ + // Fire the dead-end target if there is one + CPathTrack *pTrack, *pNext; + + pTrack = m_ppath; + + ALERT( at_aiconsole, "TRAIN(%s): Dead end ", STRING(pev->targetname) ); + // Find the dead end path node + // HACKHACK -- This is bugly, but the train can actually stop moving at a different node depending on it's speed + // so we have to traverse the list to it's end. + if ( pTrack ) + { + if ( m_oldSpeed < 0 ) + { + do + { + pNext = pTrack->ValidPath( pTrack->GetPrevious(), TRUE ); + if ( pNext ) + pTrack = pNext; + } while ( pNext ); + } + else + { + do + { + pNext = pTrack->ValidPath( pTrack->GetNext(), TRUE ); + if ( pNext ) + pTrack = pNext; + } while ( pNext ); + } + } + + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + if ( pTrack ) + { + ALERT( at_aiconsole, "at %s\n", STRING(pTrack->pev->targetname) ); + if ( pTrack->pev->netname ) + FireTargets( STRING(pTrack->pev->netname), this, this, USE_TOGGLE, 0 ); + } + else + ALERT( at_aiconsole, "\n" ); +} + + +void CFuncTrackTrain :: SetControls( entvars_t *pevControls ) +{ + Vector offset = pevControls->origin - pev->oldorigin; + + m_controlMins = pevControls->mins + offset; + m_controlMaxs = pevControls->maxs + offset; +} + + +BOOL CFuncTrackTrain :: OnControls( entvars_t *pevTest ) +{ + Vector offset = pevTest->origin - pev->origin; + + if ( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL ) + return FALSE; + + // Transform offset into local coordinates + UTIL_MakeVectors( pev->angles ); + Vector local; + local.x = DotProduct( offset, gpGlobals->v_forward ); + local.y = -DotProduct( offset, gpGlobals->v_right ); + local.z = DotProduct( offset, gpGlobals->v_up ); + + if ( local.x >= m_controlMins.x && local.y >= m_controlMins.y && local.z >= m_controlMins.z && + local.x <= m_controlMaxs.x && local.y <= m_controlMaxs.y && local.z <= m_controlMaxs.z ) + return TRUE; + + return FALSE; +} + + +void CFuncTrackTrain :: Find( void ) +{ + m_ppath = CPathTrack::Instance(FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) )); + if ( !m_ppath ) + return; + + entvars_t *pevTarget = m_ppath->pev; + if ( !FClassnameIs( pevTarget, "path_track" ) ) + { + ALERT( at_error, "func_track_train must be on a path of path_track\n" ); + m_ppath = NULL; + return; + } + + Vector nextPos = pevTarget->origin; + nextPos.z += m_height; + + Vector look = nextPos; + look.z -= m_height; + m_ppath->LookAhead( &look, m_length, 0 ); + look.z += m_height; + + pev->angles = UTIL_VecToAngles( look - nextPos ); + // The train actually points west + pev->angles.y += 180; + + if ( pev->spawnflags & SF_TRACKTRAIN_NOPITCH ) + pev->angles.x = 0; + UTIL_SetOrigin( pev, nextPos ); + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncTrackTrain::Next ); + pev->speed = m_startSpeed; + + UpdateSound(); +} + + +void CFuncTrackTrain :: NearestPath( void ) +{ + CBaseEntity *pTrack = NULL; + CBaseEntity *pNearest = NULL; + float dist, closest; + + closest = 1024; + + while ((pTrack = UTIL_FindEntityInSphere( pTrack, pev->origin, 1024 )) != NULL) + { + // filter out non-tracks + if ( !(pTrack->pev->flags & (FL_CLIENT|FL_MONSTER)) && FClassnameIs( pTrack->pev, "path_track" ) ) + { + dist = (pev->origin - pTrack->pev->origin).Length(); + if ( dist < closest ) + { + closest = dist; + pNearest = pTrack; + } + } + } + + if ( !pNearest ) + { + ALERT( at_console, "Can't find a nearby track !!!\n" ); + SetThink(NULL); + return; + } + + ALERT( at_aiconsole, "TRAIN: %s, Nearest track is %s\n", STRING(pev->targetname), STRING(pNearest->pev->targetname) ); + // If I'm closer to the next path_track on this path, then it's my real path + pTrack = ((CPathTrack *)pNearest)->GetNext(); + if ( pTrack ) + { + if ( (pev->origin - pTrack->pev->origin).Length() < (pev->origin - pNearest->pev->origin).Length() ) + pNearest = pTrack; + } + + m_ppath = (CPathTrack *)pNearest; + + if ( pev->speed != 0 ) + { + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncTrackTrain::Next ); + } +} + + +void CFuncTrackTrain::OverrideReset( void ) +{ + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncTrackTrain::NearestPath ); +} + + +CFuncTrackTrain *CFuncTrackTrain::Instance( edict_t *pent ) +{ + if ( FClassnameIs( pent, "func_tracktrain" ) ) + return (CFuncTrackTrain *)GET_PRIVATE(pent); + return NULL; +} + +/*QUAKED func_train (0 .5 .8) ? +Trains are moving platforms that players can ride. +The targets origin specifies the min point of the train at each corner. +The train spawns at the first target it is pointing at. +If the train is the target of a button or trigger, it will not begin moving until activated. +speed default 100 +dmg default 2 +sounds +1) ratchet metal +*/ + +void CFuncTrackTrain :: Spawn( void ) +{ + if ( pev->speed == 0 ) + m_speed = 100; + else + m_speed = pev->speed; + + pev->speed = 0; + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + pev->impulse = static_cast(m_speed); + + m_dir = 1; + + if ( FStringNull(pev->target) ) + ALERT( at_console, "FuncTrain with no target" ); + + if ( pev->spawnflags & SF_TRACKTRAIN_PASSABLE ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + SET_MODEL( ENT(pev), STRING(pev->model) ); + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( pev, pev->origin ); + + // Cache off placed origin for train controls + pev->oldorigin = pev->origin; + + m_controlMins = pev->mins; + m_controlMaxs = pev->maxs; + m_controlMaxs.z += 72; +// start trains on the next frame, to make sure their targets have had +// a chance to spawn/activate + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncTrackTrain::Find ); + Precache(); +} + +void CFuncTrackTrain :: Precache( void ) +{ + if (m_flVolume == 0.0) + m_flVolume = 1.0; + + switch (m_sounds) + { + default: + // no sound + pev->noise = 0; + break; + case 1: PRECACHE_SOUND("plats/ttrain1.wav"); pev->noise = MAKE_STRING("plats/ttrain1.wav");break; + case 2: PRECACHE_SOUND("plats/ttrain2.wav"); pev->noise = MAKE_STRING("plats/ttrain2.wav");break; + case 3: PRECACHE_SOUND("plats/ttrain3.wav"); pev->noise = MAKE_STRING("plats/ttrain3.wav");break; + case 4: PRECACHE_SOUND("plats/ttrain4.wav"); pev->noise = MAKE_STRING("plats/ttrain4.wav");break; + case 5: PRECACHE_SOUND("plats/ttrain6.wav"); pev->noise = MAKE_STRING("plats/ttrain6.wav");break; + case 6: PRECACHE_SOUND("plats/ttrain7.wav"); pev->noise = MAKE_STRING("plats/ttrain7.wav");break; + } + + PRECACHE_SOUND("plats/ttrain_brake1.wav"); + PRECACHE_SOUND("plats/ttrain_start1.wav"); + + m_usAdjustPitch = PRECACHE_EVENT( 1, "events/train.sc" ); +} + +// This class defines the volume of space that the player must stand in to control the train +class CFuncTrainControls : public CBaseEntity +{ +public: + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + void Spawn( void ); + void EXPORT Find( void ); +}; +LINK_ENTITY_TO_CLASS( func_traincontrols, CFuncTrainControls ); + + +void CFuncTrainControls :: Find( void ) +{ + edict_t *pTarget = NULL; + + do + { + pTarget = FIND_ENTITY_BY_TARGETNAME( pTarget, STRING(pev->target) ); + } while ( !FNullEnt(pTarget) && !FClassnameIs(pTarget, "func_tracktrain") ); + + if ( FNullEnt( pTarget ) ) + { + ALERT( at_console, "No train %s\n", STRING(pev->target) ); + return; + } + + CFuncTrackTrain *ptrain = CFuncTrackTrain::Instance(pTarget); + ptrain->SetControls( pev ); + UTIL_Remove( this ); +} + + +void CFuncTrainControls :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( pev, pev->origin ); + + SetThink( &CFuncTrainControls::Find ); + pev->nextthink = gpGlobals->time; +} + + + +// ---------------------------------------------------------------------------- +// +// Track changer / Train elevator +// +// ---------------------------------------------------------------------------- + +#define SF_TRACK_ACTIVATETRAIN 0x00000001 +#define SF_TRACK_RELINK 0x00000002 +#define SF_TRACK_ROTMOVE 0x00000004 +#define SF_TRACK_STARTBOTTOM 0x00000008 +#define SF_TRACK_DONT_MOVE 0x00000010 + +// +// This entity is a rotating/moving platform that will carry a train to a new track. +// It must be larger in X-Y planar area than the train, since it must contain the +// train within these dimensions in order to operate when the train is near it. +// + +typedef enum { TRAIN_SAFE, TRAIN_BLOCKING, TRAIN_FOLLOWING } TRAIN_CODE; + +class CFuncTrackChange : public CFuncPlatRot +{ +public: + void Spawn( void ); + void Precache( void ); + +// virtual void Blocked( void ); + virtual void EXPORT GoUp( void ); + virtual void EXPORT GoDown( void ); + + void KeyValue( KeyValueData* pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Find( void ); + TRAIN_CODE EvaluateTrain( CPathTrack *pcurrent ); + void UpdateTrain( Vector &dest ); + virtual void HitBottom( void ); + virtual void HitTop( void ); + void Touch( CBaseEntity *pOther ); + virtual void UpdateAutoTargets( int toggleState ); + virtual BOOL IsTogglePlat( void ) { return TRUE; } + + void DisableUse( void ) { m_use = 0; } + void EnableUse( void ) { m_use = 1; } + int UseEnabled( void ) { return m_use; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + virtual void OverrideReset( void ); + + + CPathTrack *m_trackTop; + CPathTrack *m_trackBottom; + + CFuncTrackTrain *m_train; + + int m_trackTopName; + int m_trackBottomName; + int m_trainName; + TRAIN_CODE m_code; + int m_targetState; + int m_use; +}; +LINK_ENTITY_TO_CLASS( func_trackchange, CFuncTrackChange ); + +TYPEDESCRIPTION CFuncTrackChange::m_SaveData[] = +{ + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackTop, FIELD_CLASSPTR ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackBottom, FIELD_CLASSPTR ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_train, FIELD_CLASSPTR ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackTopName, FIELD_STRING ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackBottomName, FIELD_STRING ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trainName, FIELD_STRING ), + DEFINE_FIELD( CFuncTrackChange, m_code, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrackChange, m_targetState, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrackChange, m_use, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTrackChange, CFuncPlatRot ); + +void CFuncTrackChange :: Spawn( void ) +{ + Setup(); + if ( FBitSet( pev->spawnflags, SF_TRACK_DONT_MOVE ) ) + m_vecPosition2.z = pev->origin.z; + + SetupRotation(); + + if ( FBitSet( pev->spawnflags, SF_TRACK_STARTBOTTOM ) ) + { + UTIL_SetOrigin (pev, m_vecPosition2); + m_toggle_state = TS_AT_BOTTOM; + pev->angles = m_start; + m_targetState = TS_AT_TOP; + } + else + { + UTIL_SetOrigin (pev, m_vecPosition1); + m_toggle_state = TS_AT_TOP; + pev->angles = m_end; + m_targetState = TS_AT_BOTTOM; + } + + EnableUse(); + pev->nextthink = pev->ltime + 2.0; + SetThink( &CFuncTrackChange::Find ); + Precache(); +} + +void CFuncTrackChange :: Precache( void ) +{ + // Can't trigger sound + PRECACHE_SOUND( "buttons/button11.wav" ); + + CFuncPlatRot::Precache(); +} + + +// UNDONE: Filter touches before re-evaluating the train. +void CFuncTrackChange :: Touch( CBaseEntity *pOther ) +{ +#if 0 + TRAIN_CODE code; + entvars_t *pevToucher = pOther->pev; +#endif +} + + + +void CFuncTrackChange :: KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "train") ) + { + m_trainName = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "toptrack") ) + { + m_trackTopName = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "bottomtrack") ) + { + m_trackBottomName = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + { + CFuncPlatRot::KeyValue( pkvd ); // Pass up to base class + } +} + + +void CFuncTrackChange::OverrideReset( void ) +{ + pev->nextthink = pev->ltime + 1.0; + SetThink( &CFuncTrackChange::Find ); +} + +void CFuncTrackChange :: Find( void ) +{ + // Find track entities + edict_t *target; + + target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trackTopName) ); + if ( !FNullEnt(target) ) + { + m_trackTop = CPathTrack::Instance( target ); + target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trackBottomName) ); + if ( !FNullEnt(target) ) + { + m_trackBottom = CPathTrack::Instance( target ); + target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trainName) ); + if ( !FNullEnt(target) ) + { + m_train = CFuncTrackTrain::Instance( FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trainName) ) ); + if ( !m_train ) + { + ALERT( at_error, "Can't find train for track change! %s\n", STRING(m_trainName) ); + return; + } + Vector center = (pev->absmin + pev->absmax) * 0.5; + m_trackBottom = m_trackBottom->Nearest( center ); + m_trackTop = m_trackTop->Nearest( center ); + UpdateAutoTargets( m_toggle_state ); + SetThink( NULL ); + return; + } + else + { + ALERT( at_error, "Can't find train for track change! %s\n", STRING(m_trainName) ); + target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trainName) ); + } + } + else + ALERT( at_error, "Can't find bottom track for track change! %s\n", STRING(m_trackBottomName) ); + } + else + ALERT( at_error, "Can't find top track for track change! %s\n", STRING(m_trackTopName) ); +} + + + +TRAIN_CODE CFuncTrackChange :: EvaluateTrain( CPathTrack *pcurrent ) +{ + // Go ahead and work, we don't have anything to switch, so just be an elevator + if ( !pcurrent || !m_train ) + return TRAIN_SAFE; + + if ( m_train->m_ppath == pcurrent || (pcurrent->m_pprevious && m_train->m_ppath == pcurrent->m_pprevious) || + (pcurrent->m_pnext && m_train->m_ppath == pcurrent->m_pnext) ) + { + if ( m_train->pev->speed != 0 ) + return TRAIN_BLOCKING; + + Vector dist = pev->origin - m_train->pev->origin; + float length = dist.Length2D(); + if ( length < m_train->m_length ) // Empirically determined close distance + return TRAIN_FOLLOWING; + else if ( length > (150 + m_train->m_length) ) + return TRAIN_SAFE; + + return TRAIN_BLOCKING; + } + + return TRAIN_SAFE; +} + + +void CFuncTrackChange :: UpdateTrain( Vector &dest ) +{ + float time = (pev->nextthink - pev->ltime); + + m_train->pev->velocity = pev->velocity; + m_train->pev->avelocity = pev->avelocity; + m_train->NextThink( m_train->pev->ltime + time, FALSE ); + + // Attempt at getting the train to rotate properly around the origin of the trackchange + if ( time <= 0 ) + return; + + Vector offset = m_train->pev->origin - pev->origin; + Vector delta = dest - pev->angles; + // Transform offset into local coordinates + UTIL_MakeInvVectors( delta, gpGlobals ); + Vector local; + local.x = DotProduct( offset, gpGlobals->v_forward ); + local.y = DotProduct( offset, gpGlobals->v_right ); + local.z = DotProduct( offset, gpGlobals->v_up ); + + local = local - offset; + m_train->pev->velocity = pev->velocity + (local * (1.0/time)); +} + +void CFuncTrackChange :: GoDown( void ) +{ + if ( m_code == TRAIN_BLOCKING ) + return; + + // HitBottom may get called during CFuncPlat::GoDown(), so set up for that + // before you call GoDown() + + UpdateAutoTargets( TS_GOING_DOWN ); + // If ROTMOVE, move & rotate + if ( FBitSet( pev->spawnflags, SF_TRACK_DONT_MOVE ) ) + { + SetMoveDone( &CFuncPlat::CallHitBottom ); + m_toggle_state = TS_GOING_DOWN; + AngularMove( m_start, pev->speed ); + } + else + { + CFuncPlat :: GoDown(); + SetMoveDone( &CFuncPlat::CallHitBottom ); + RotMove( m_start, pev->nextthink - pev->ltime ); + } + // Otherwise, rotate first, move second + + // If the train is moving with the platform, update it + if ( m_code == TRAIN_FOLLOWING ) + { + UpdateTrain( m_start ); + m_train->m_ppath = NULL; + } +} + + +// +// Platform is at bottom, now starts moving up +// +void CFuncTrackChange :: GoUp( void ) +{ + if ( m_code == TRAIN_BLOCKING ) + return; + + // HitTop may get called during CFuncPlat::GoUp(), so set up for that + // before you call GoUp(); + + UpdateAutoTargets( TS_GOING_UP ); + if ( FBitSet( pev->spawnflags, SF_TRACK_DONT_MOVE ) ) + { + m_toggle_state = TS_GOING_UP; + SetMoveDone( &CFuncPlat::CallHitTop ); + AngularMove( m_end, pev->speed ); + } + else + { + // If ROTMOVE, move & rotate + CFuncPlat :: GoUp(); + SetMoveDone( &CFuncPlat::CallHitTop ); + RotMove( m_end, pev->nextthink - pev->ltime ); + } + + // Otherwise, move first, rotate second + + // If the train is moving with the platform, update it + if ( m_code == TRAIN_FOLLOWING ) + { + UpdateTrain( m_end ); + m_train->m_ppath = NULL; + } +} + + +// Normal track change +void CFuncTrackChange :: UpdateAutoTargets( int toggleState ) +{ + if ( !m_trackTop || !m_trackBottom ) + return; + + if ( toggleState == TS_AT_TOP ) + ClearBits( m_trackTop->pev->spawnflags, SF_PATH_DISABLED ); + else + SetBits( m_trackTop->pev->spawnflags, SF_PATH_DISABLED ); + + if ( toggleState == TS_AT_BOTTOM ) + ClearBits( m_trackBottom->pev->spawnflags, SF_PATH_DISABLED ); + else + SetBits( m_trackBottom->pev->spawnflags, SF_PATH_DISABLED ); +} + + +void CFuncTrackChange :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( m_toggle_state != TS_AT_TOP && m_toggle_state != TS_AT_BOTTOM ) + return; + + // If train is in "safe" area, but not on the elevator, play alarm sound + if ( m_toggle_state == TS_AT_TOP ) + m_code = EvaluateTrain( m_trackTop ); + else if ( m_toggle_state == TS_AT_BOTTOM ) + m_code = EvaluateTrain( m_trackBottom ); + else + m_code = TRAIN_BLOCKING; + if ( m_code == TRAIN_BLOCKING ) + { + // Play alarm and return + EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/button11.wav", 1, ATTN_NORM); + return; + } + + // Otherwise, it's safe to move + // If at top, go down + // at bottom, go up + + DisableUse(); + if (m_toggle_state == TS_AT_TOP) + GoDown(); + else + GoUp(); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncTrackChange :: HitBottom( void ) +{ + CFuncPlatRot :: HitBottom(); + if ( m_code == TRAIN_FOLLOWING ) + { +// UpdateTrain(); + m_train->SetTrack( m_trackBottom ); + } + SetThink( NULL ); + pev->nextthink = -1; + + UpdateAutoTargets( m_toggle_state ); + + EnableUse(); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncTrackChange :: HitTop( void ) +{ + CFuncPlatRot :: HitTop(); + if ( m_code == TRAIN_FOLLOWING ) + { +// UpdateTrain(); + m_train->SetTrack( m_trackTop ); + } + + // Don't let the plat go back down + SetThink( NULL ); + pev->nextthink = -1; + UpdateAutoTargets( m_toggle_state ); + EnableUse(); +} + + + +class CFuncTrackAuto : public CFuncTrackChange +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual void UpdateAutoTargets( int toggleState ); +}; + +LINK_ENTITY_TO_CLASS( func_trackautochange, CFuncTrackAuto ); + +// Auto track change +void CFuncTrackAuto :: UpdateAutoTargets( int toggleState ) +{ + CPathTrack *pTarget, *pNextTarget; + + if ( !m_trackTop || !m_trackBottom ) + return; + + if ( m_targetState == TS_AT_TOP ) + { + pTarget = m_trackTop->GetNext(); + pNextTarget = m_trackBottom->GetNext(); + } + else + { + pTarget = m_trackBottom->GetNext(); + pNextTarget = m_trackTop->GetNext(); + } + if ( pTarget ) + { + ClearBits( pTarget->pev->spawnflags, SF_PATH_DISABLED ); + if ( m_code == TRAIN_FOLLOWING && m_train && m_train->pev->speed == 0 ) + m_train->Use( this, this, USE_ON, 0 ); + } + + if ( pNextTarget ) + SetBits( pNextTarget->pev->spawnflags, SF_PATH_DISABLED ); + +} + + +void CFuncTrackAuto :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CPathTrack *pTarget; + + if ( !UseEnabled() ) + return; + + if ( m_toggle_state == TS_AT_TOP ) + pTarget = m_trackTop; + else if ( m_toggle_state == TS_AT_BOTTOM ) + pTarget = m_trackBottom; + else + pTarget = NULL; + + if ( FClassnameIs( pActivator->pev, "func_tracktrain" ) ) + { + m_code = EvaluateTrain( pTarget ); + // Safe to fire? + if ( m_code == TRAIN_FOLLOWING && m_toggle_state != m_targetState ) + { + DisableUse(); + if (m_toggle_state == TS_AT_TOP) + GoDown(); + else + GoUp(); + } + } + else + { + if ( pTarget ) + pTarget = pTarget->GetNext(); + if ( pTarget && m_train->m_ppath != pTarget && ShouldToggle( useType, m_targetState ) ) + { + if ( m_targetState == TS_AT_TOP ) + m_targetState = TS_AT_BOTTOM; + else + m_targetState = TS_AT_TOP; + } + + UpdateAutoTargets( m_targetState ); + } +} + + +// ---------------------------------------------------------- +// +// +// pev->speed is the travel speed +// pev->health is current health +// pev->max_health is the amount to reset to each time it starts + +#define FGUNTARGET_START_ON 0x0001 + +class CGunTarget : public CBaseMonster +{ +public: + void Spawn( void ); + void Activate( void ); + void EXPORT Next( void ); + void EXPORT Start( void ); + void EXPORT Wait( void ); + void Stop( void ); + + int BloodColor( void ) { return DONT_BLEED; } + int Classify( void ) { return CLASS_MACHINE; } + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + Vector BodyTarget( const Vector &posSrc ) { return pev->origin; } + + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + BOOL m_on; +}; + + +LINK_ENTITY_TO_CLASS( func_guntarget, CGunTarget ); + +TYPEDESCRIPTION CGunTarget::m_SaveData[] = +{ + DEFINE_FIELD( CGunTarget, m_on, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CGunTarget, CBaseMonster ); + + +void CGunTarget::Spawn( void ) +{ + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + if ( pev->speed == 0 ) + pev->speed = 100; + + // Don't take damage until "on" + pev->takedamage = DAMAGE_NO; + pev->flags |= FL_MONSTER; + + m_on = FALSE; + pev->max_health = pev->health; + + if ( pev->spawnflags & FGUNTARGET_START_ON ) + { + SetThink( &CGunTarget::Start ); + pev->nextthink = pev->ltime + 0.3; + } +} + + +void CGunTarget::Activate( void ) +{ + CBaseEntity *pTarg; + + // now find our next target + pTarg = GetNextTarget(); + if ( pTarg ) + { + m_hTargetEnt = pTarg; + UTIL_SetOrigin( pev, pTarg->pev->origin - (pev->mins + pev->maxs) * 0.5 ); + } +} + + +void CGunTarget::Start( void ) +{ + Use( this, this, USE_ON, 0 ); +} + + +void CGunTarget::Next( void ) +{ + SetThink( NULL ); + + m_hTargetEnt = GetNextTarget(); + CBaseEntity *pTarget = m_hTargetEnt; + + if ( !pTarget ) + { + Stop(); + return; + } + SetMoveDone( &CGunTarget::Wait ); + LinearMove( pTarget->pev->origin - (pev->mins + pev->maxs) * 0.5, pev->speed ); +} + + +void CGunTarget::Wait( void ) +{ + CBaseEntity *pTarget = m_hTargetEnt; + + if ( !pTarget ) + { + Stop(); + return; + } + + // Fire the pass target if there is one + if ( pTarget->pev->message ) + { + FireTargets( STRING(pTarget->pev->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( pTarget->pev->spawnflags, SF_CORNER_FIREONCE ) ) + pTarget->pev->message = 0; + } + + m_flWait = pTarget->GetDelay(); + + pev->target = pTarget->pev->target; + SetThink( &CGunTarget::Next ); + if (m_flWait != 0) + {// -1 wait will wait forever! + pev->nextthink = pev->ltime + m_flWait; + } + else + { + Next();// do it RIGHT now! + } +} + + +void CGunTarget::Stop( void ) +{ + pev->velocity = g_vecZero; + pev->nextthink = 0; + pev->takedamage = DAMAGE_NO; +} + + +int CGunTarget::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( pev->health > 0 ) + { + pev->health -= flDamage; + if ( pev->health <= 0 ) + { + pev->health = 0; + Stop(); + if ( pev->message ) + FireTargets( STRING(pev->message), this, this, USE_TOGGLE, 0 ); + } + } + return 0; +} + + +void CGunTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_on ) ) + return; + + if ( m_on ) + { + Stop(); + } + else + { + pev->takedamage = DAMAGE_AIM; + m_hTargetEnt = GetNextTarget(); + if ( m_hTargetEnt == 0 ) + return; + pev->health = pev->max_health; + Next(); + } +} + + + diff --git a/hlsdk/ricochet/dlls/player.cpp b/hlsdk/ricochet/dlls/player.cpp new file mode 100644 index 00000000..9121abfa --- /dev/null +++ b/hlsdk/ricochet/dlls/player.cpp @@ -0,0 +1,4894 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== player.cpp ======================================================== + + functions dealing with the player + +*/ + +#include "extdll.h" +#include "util.h" + +#include "cbase.h" +#include "player.h" +#include "trains.h" +#include "nodes.h" +#include "weapons.h" +#include "soundent.h" +#include "monsters.h" +#include "../engine/shake.h" +#include "decals.h" +#include "gamerules.h" +#include "animation.h" +#include "discwar.h" +#include "disc_objects.h" +#include "disc_arena.h" + +// #define DUCKFIX + +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 = FALSE; + +extern void CopyToBodyQue(entvars_t* pev); +extern void respawn(entvars_t *pev, BOOL fCopyCorpse); +extern Vector VecBModelOrigin(entvars_t *pevBModel ); +extern edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ); +int MapTextureTypeStepType(char chTextureType); + +entvars_t *g_pevLastInflictor; // Set in combat.cpp. Used to pass the damage inflictor for death messages. + // Better solution: Add as parameter to all Killed() functions. + +// the world node graph +extern CGraph WorldGraph; + +#define PLAYER_WALLJUMP_SPEED 300 // how fast we can spring off walls +#define PLAYER_LONGJUMP_SPEED 350 // how fast we longjump + +#define TRAIN_ACTIVE 0x80 +#define TRAIN_NEW 0xc0 +#define TRAIN_OFF 0x00 +#define TRAIN_NEUTRAL 0x01 +#define TRAIN_SLOW 0x02 +#define TRAIN_MEDIUM 0x03 +#define TRAIN_FAST 0x04 +#define TRAIN_BACK 0x05 + +#define FLASH_DRAIN_TIME 1.2 //100 units/3 minutes +#define FLASH_CHARGE_TIME 0.2 // 100 units/20 seconds (seconds per unit) + + +//#define PLAYER_MAX_SAFE_FALL_DIST 20// falling any farther than this many feet will inflict damage +//#define PLAYER_FATAL_FALL_DIST 60// 100% damage inflicted if player falls this many feet +//#define DAMAGE_PER_UNIT_FALLEN (float)( 100 ) / ( ( PLAYER_FATAL_FALL_DIST - PLAYER_MAX_SAFE_FALL_DIST ) * 12 ) +//#define MAX_SAFE_FALL_UNITS ( PLAYER_MAX_SAFE_FALL_DIST * 12 ) + +// Global Savedata for player +TYPEDESCRIPTION CBasePlayer::m_playerSaveData[] = +{ + DEFINE_FIELD( CBasePlayer, m_flFlashLightTime, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_iFlashBattery, FIELD_INTEGER ), + + DEFINE_FIELD( CBasePlayer, m_afButtonLast, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_afButtonPressed, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_afButtonReleased, FIELD_INTEGER ), + + DEFINE_ARRAY( CBasePlayer, m_rgItems, FIELD_INTEGER, MAX_ITEMS ), + DEFINE_FIELD( CBasePlayer, m_afPhysicsFlags, FIELD_INTEGER ), + + DEFINE_FIELD( CBasePlayer, m_flTimeStepSound, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flTimeWeaponIdle, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flSwimTime, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flDuckTime, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flWallJumpTime, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_flSuitUpdate, FIELD_TIME ), + DEFINE_ARRAY( CBasePlayer, m_rgSuitPlayList, FIELD_INTEGER, CSUITPLAYLIST ), + DEFINE_FIELD( CBasePlayer, m_iSuitPlayNext, FIELD_INTEGER ), + DEFINE_ARRAY( CBasePlayer, m_rgiSuitNoRepeat, FIELD_INTEGER, CSUITNOREPEAT ), + DEFINE_ARRAY( CBasePlayer, m_rgflSuitNoRepeatTime, FIELD_TIME, CSUITNOREPEAT ), + DEFINE_FIELD( CBasePlayer, m_lastDamageAmount, FIELD_INTEGER ), + + DEFINE_ARRAY( CBasePlayer, m_rgpPlayerItems, FIELD_CLASSPTR, MAX_ITEM_TYPES ), + DEFINE_FIELD( CBasePlayer, m_pActiveItem, FIELD_CLASSPTR ), + DEFINE_FIELD( CBasePlayer, m_pLastItem, FIELD_CLASSPTR ), + + DEFINE_ARRAY( CBasePlayer, m_rgAmmo, FIELD_INTEGER, MAX_AMMO_SLOTS ), + DEFINE_FIELD( CBasePlayer, m_idrowndmg, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_idrownrestored, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_tSneaking, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_iTrain, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_bitsHUDDamage, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_flFallVelocity, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayer, m_iTargetVolume, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iWeaponVolume, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iExtraSoundTypes, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iWeaponFlash, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_fLongJump, FIELD_BOOLEAN ), + DEFINE_FIELD( CBasePlayer, m_fInitHUD, FIELD_BOOLEAN ), + DEFINE_FIELD( CBasePlayer, m_tbdPrev, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_pTank, FIELD_EHANDLE ), + DEFINE_FIELD( CBasePlayer, m_iHideHUD, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iFOV, FIELD_INTEGER ), + + //DEFINE_FIELD( CBasePlayer, m_fDeadTime, FIELD_FLOAT ), // only used in multiplayer games + //DEFINE_FIELD( CBasePlayer, m_fGameHUDInitialized, FIELD_INTEGER ), // only used in multiplayer games + //DEFINE_FIELD( CBasePlayer, m_flStopExtraSoundTime, FIELD_TIME ), + //DEFINE_FIELD( CBasePlayer, m_fKnownItem, FIELD_INTEGER ), // reset to zero on load + //DEFINE_FIELD( CBasePlayer, m_iPlayerSound, FIELD_INTEGER ), // Don't restore, set in Precache() + //DEFINE_FIELD( CBasePlayer, m_pentSndLast, FIELD_EDICT ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_flSndRoomtype, FIELD_FLOAT ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_flSndRange, FIELD_FLOAT ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_fNewAmmo, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_flgeigerRange, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( CBasePlayer, m_flgeigerDelay, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( CBasePlayer, m_igeigerRangePrev, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( CBasePlayer, m_iStepLeft, FIELD_INTEGER ), // Don't need to restore + //DEFINE_ARRAY( CBasePlayer, m_szTextureName, FIELD_CHARACTER, CBTEXTURENAMEMAX ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_chTextureType, FIELD_CHARACTER ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_fNoPlayerSound, FIELD_BOOLEAN ), // Don't need to restore, debug + //DEFINE_FIELD( CBasePlayer, m_iUpdateTime, FIELD_INTEGER ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_iClientHealth, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_iClientBattery, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_iClientHideHUD, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_fWeapon, FIELD_BOOLEAN ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_nCustomSprayFrames, FIELD_INTEGER ), // Don't restore, depends on server message after spawning and only matters in multiplayer + //DEFINE_FIELD( CBasePlayer, m_vecAutoAim, FIELD_VECTOR ), // Don't save/restore - this is recomputed + //DEFINE_ARRAY( CBasePlayer, m_rgAmmoLast, FIELD_INTEGER, MAX_AMMO_SLOTS ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_fOnTarget, FIELD_BOOLEAN ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_nCustomSprayFrames, FIELD_INTEGER ), // Don't need to restore + +}; + + +int giPrecacheGrunt = 0; +int gmsgShake = 0; +int gmsgFade = 0; +int gmsgSelAmmo = 0; +int gmsgFlashlight = 0; +int gmsgFlashBattery = 0; +int gmsgResetHUD = 0; +int gmsgInitHUD = 0; +int gmsgShowGameTitle = 0; +int gmsgCurWeapon = 0; +int gmsgHealth = 0; +int gmsgDamage = 0; +int gmsgBattery = 0; +int gmsgTrain = 0; +int gmsgLogo = 0; +int gmsgWeaponList = 0; +int gmsgAmmoX = 0; +int gmsgHudText = 0; +int gmsgDeathMsg = 0; +int gmsgScoreInfo = 0; +int gmsgTeamInfo = 0; +int gmsgTeamScore = 0; +int gmsgGameMode = 0; +int gmsgMOTD = 0; +int gmsgAmmoPickup = 0; +int gmsgWeapPickup = 0; +int gmsgItemPickup = 0; +int gmsgHideWeapon = 0; +int gmsgSetCurWeap = 0; +int gmsgSayText = 0; +int gmsgTextMsg = 0; +int gmsgSetFOV = 0; +int gmsgShowMenu = 0; +int gmsgGeigerRange = 0; +int gmsgSpectator = 0; +int gmsgStartRnd = 0; +int gmsgEndRnd = 0; +int gmsgPowerup = 0; +int gmsgReward = 0; +int gmsgFrozen = 0; + +void LinkUserMessages( void ) +{ + // Already taken care of? + if ( gmsgSelAmmo ) + { + return; + } + + gmsgSelAmmo = REG_USER_MSG("SelAmmo", sizeof(SelAmmo)); + gmsgCurWeapon = REG_USER_MSG("CurWeapon", 3); + gmsgGeigerRange = REG_USER_MSG("Geiger", 1); + gmsgFlashlight = REG_USER_MSG("Flashlight", 2); + gmsgFlashBattery = REG_USER_MSG("FlashBat", 1); + gmsgHealth = REG_USER_MSG( "Health", 1 ); + gmsgDamage = REG_USER_MSG( "Damage", 12 ); + gmsgBattery = REG_USER_MSG( "Battery", 2); + gmsgTrain = REG_USER_MSG( "Train", 1); + gmsgHudText = REG_USER_MSG( "HudText", -1 ); + gmsgSayText = REG_USER_MSG( "SayText", -1 ); + gmsgTextMsg = REG_USER_MSG( "TextMsg", -1 ); + gmsgWeaponList = REG_USER_MSG("WeaponList", -1); + gmsgResetHUD = REG_USER_MSG("ResetHUD", 1); // called every respawn + gmsgInitHUD = REG_USER_MSG("InitHUD", 0 ); // called every time a new player joins the server + gmsgShowGameTitle = REG_USER_MSG("GameTitle", 1); + gmsgDeathMsg = REG_USER_MSG( "DeathMsg", -1 ); + gmsgScoreInfo = REG_USER_MSG( "ScoreInfo", 9 ); + gmsgTeamInfo = REG_USER_MSG( "TeamInfo", -1 ); // sets the name of a player's team + gmsgTeamScore = REG_USER_MSG( "TeamScore", -1 ); // sets the score of a team on the scoreboard + gmsgGameMode = REG_USER_MSG( "GameMode", 1 ); + gmsgMOTD = REG_USER_MSG( "MOTD", -1 ); + gmsgAmmoPickup = REG_USER_MSG( "AmmoPickup", 2 ); + gmsgWeapPickup = REG_USER_MSG( "WeapPickup", 1 ); + gmsgItemPickup = REG_USER_MSG( "ItemPickup", -1 ); + gmsgHideWeapon = REG_USER_MSG( "HideWeapon", 1 ); + gmsgSetFOV = REG_USER_MSG( "SetFOV", 1 ); + gmsgShowMenu = REG_USER_MSG( "ShowMenu", -1 ); + gmsgShake = REG_USER_MSG("ScreenShake", sizeof(ScreenShake)); + gmsgFade = REG_USER_MSG("ScreenFade", sizeof(ScreenFade)); + gmsgAmmoX = REG_USER_MSG("AmmoX", 2); + + gmsgSpectator = REG_USER_MSG( "Spectator", 2 ); + + // Discwar + gmsgStartRnd = REG_USER_MSG( "StartRnd", -1 ); + gmsgEndRnd = REG_USER_MSG( "EndRnd", -1 ); + gmsgPowerup = REG_USER_MSG( "Powerup", 1 ); + gmsgReward = REG_USER_MSG( "Reward", 2 ); + gmsgFrozen = REG_USER_MSG( "Frozen", 1 ); +} + +LINK_ENTITY_TO_CLASS( player, CBasePlayer ); + + + +void CBasePlayer :: Pain( void ) +{ + float flRndSound;//sound randomizer + + flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.33 ) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain5.wav", 1, ATTN_NORM); + else if ( flRndSound <= 0.66 ) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain6.wav", 1, ATTN_NORM); + else + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain7.wav", 1, ATTN_NORM); +} + +/* + * + */ +Vector VecVelocityForDamage(float flDamage) +{ + Vector vec(RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + + if (flDamage > -50) + vec = vec * 0.7; + else if (flDamage > -200) + vec = vec * 2; + else + vec = vec * 10; + + return vec; +} + +#if 0 /* +static void ThrowGib(entvars_t *pev, char *szGibModel, float flDamage) +{ + edict_t *pentNew = CREATE_ENTITY(); + entvars_t *pevNew = VARS(pentNew); + + pevNew->origin = pev->origin; + SET_MODEL(ENT(pevNew), szGibModel); + UTIL_SetSize(pevNew, g_vecZero, g_vecZero); + + pevNew->velocity = VecVelocityForDamage(flDamage); + pevNew->movetype = MOVETYPE_BOUNCE; + pevNew->solid = SOLID_NOT; + pevNew->avelocity.x = RANDOM_FLOAT(0,600); + pevNew->avelocity.y = RANDOM_FLOAT(0,600); + pevNew->avelocity.z = RANDOM_FLOAT(0,600); + CHANGE_METHOD(ENT(pevNew), em_think, SUB_Remove); + pevNew->ltime = gpGlobals->time; + pevNew->nextthink = gpGlobals->time + RANDOM_FLOAT(10,20); + pevNew->frame = 0; + pevNew->flags = 0; +} + + +static void ThrowHead(entvars_t *pev, char *szGibModel, floatflDamage) +{ + SET_MODEL(ENT(pev), szGibModel); + pev->frame = 0; + pev->nextthink = -1; + pev->movetype = MOVETYPE_BOUNCE; + pev->takedamage = DAMAGE_NO; + pev->solid = SOLID_NOT; + pev->view_ofs = Vector(0,0,8); + UTIL_SetSize(pev, Vector(-16,-16,0), Vector(16,16,56)); + pev->velocity = VecVelocityForDamage(flDamage); + pev->avelocity = RANDOM_FLOAT(-1,1) * Vector(0,600,0); + pev->origin.z -= 24; + ClearBits(pev->flags, FL_ONGROUND); +} + + +*/ +#endif + +int TrainSpeed(int iSpeed, int iMax) +{ + float fSpeed, fMax; + int iRet = 0; + + fMax = (float)iMax; + fSpeed = iSpeed; + + fSpeed = fSpeed/fMax; + + if (iSpeed < 0) + iRet = TRAIN_BACK; + else if (iSpeed == 0) + iRet = TRAIN_NEUTRAL; + else if (fSpeed < 0.33) + iRet = TRAIN_SLOW; + else if (fSpeed < 0.66) + iRet = TRAIN_MEDIUM; + else + iRet = TRAIN_FAST; + + return iRet; +} + +void CBasePlayer :: DeathSound( void ) +{ +} + +// override takehealth +// bitsDamageType indicates type of damage healed. + +int CBasePlayer :: TakeHealth( float flHealth, int bitsDamageType ) +{ + return CBaseMonster :: TakeHealth (flHealth, bitsDamageType); + +} + +Vector CBasePlayer :: GetGunPosition( ) +{ +// UTIL_MakeVectors(pev->v_angle); +// m_HackedGunPos = pev->view_ofs; + Vector origin; + + origin = pev->origin + pev->view_ofs; + return origin; +} + +//========================================================= +// TraceAttack +//========================================================= +void CBasePlayer :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if ( pev->takedamage ) + { + m_LastHitGroup = ptr->iHitgroup; + + switch ( ptr->iHitgroup ) + { + case HITGROUP_GENERIC: + break; + case HITGROUP_HEAD: + flDamage *= gSkillData.plrHead; + break; + case HITGROUP_CHEST: + flDamage *= gSkillData.plrChest; + break; + case HITGROUP_STOMACH: + flDamage *= gSkillData.plrStomach; + break; + case HITGROUP_LEFTARM: + case HITGROUP_RIGHTARM: + flDamage *= gSkillData.plrArm; + break; + case HITGROUP_LEFTLEG: + case HITGROUP_RIGHTLEG: + flDamage *= gSkillData.plrLeg; + break; + default: + break; + } + + SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage);// a little surface blood. + TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + } +} + +/* + Take some damage. + NOTE: each call to TakeDamage with bitsDamageType set to a time-based damage + type will cause the damage time countdown to be reset. Thus the ongoing effects of poison, radiation + etc are implemented with subsequent calls to TakeDamage using DMG_GENERIC. +*/ + +#define ARMOR_RATIO 0.2 // Armor Takes 80% of the damage +#define ARMOR_BONUS 0.5 // Each Point of Armor is work 1/x points of health + +int CBasePlayer :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // Abort if its the world doing damage + //if ( ENTINDEX( ENT(pevInflictor) ) == 0 ) + //return 0; + + if (pev->takedamage == DAMAGE_NO) + return 0; + + // Discwar instantly kills everyone when they take damage + pev->health = -1; + g_pevLastInflictor = pevInflictor; + + if ( bitsDamageType & DMG_ALWAYSGIB ) + { + Killed( pevAttacker, GIB_ALWAYS ); + } + else if ( bitsDamageType & DMG_NEVERGIB ) + { + Killed( pevAttacker, GIB_NEVER ); + } + else + { + Killed( pevAttacker, GIB_NORMAL ); + } + + g_pevLastInflictor = NULL; + return 1; +} + +void CBasePlayer::RemoveAllItems( BOOL removeSuit ) +{ + if (m_pActiveItem) + { + ResetAutoaim( ); + m_pActiveItem->Holster( ); + m_pActiveItem = NULL; + } + + m_pLastItem = NULL; + + int i; + CBasePlayerItem *pPendingItem; + for (i = 0; i < MAX_ITEM_TYPES; i++) + { + m_pActiveItem = m_rgpPlayerItems[i]; + while (m_pActiveItem) + { + pPendingItem = m_pActiveItem->m_pNext; + m_pActiveItem->Drop( ); + m_pActiveItem = pPendingItem; + } + m_rgpPlayerItems[i] = NULL; + } + m_pActiveItem = NULL; + + pev->viewmodel = 0; + pev->weaponmodel = 0; + + if ( removeSuit ) + pev->weapons = 0; + else + pev->weapons &= ~WEAPON_ALLWEAPONS; + + for ( i = 0; i < MAX_AMMO_SLOTS;i++) + m_rgAmmo[i] = 0; + + UpdateClientData(); + // send Selected Weapon Message to our client + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pev ); + WRITE_BYTE(0); + WRITE_BYTE(0); + WRITE_BYTE(0); + MESSAGE_END(); +} + +/* + * GLOBALS ASSUMED SET: g_ulModelIndexPlayer + * + * ENTITY_METHOD(PlayerDie) + */ +void CBasePlayer::Killed( entvars_t *pevAttacker, int iGib ) +{ + CSound *pSound; + + // Discwars scoring system + if ( (m_flLastDiscHit != 0) && (gpGlobals->time < m_flLastDiscHit + MAX_SCORE_TIME_AFTER_HIT) ) + { + pevAttacker = ((CBaseEntity*)m_hLastPlayerToHitMe)->pev; + g_pevLastInflictor = ((CBaseEntity*)m_hLastPlayerToHitMe)->pev; + } + + g_pGameRules->PlayerKilled( this, pevAttacker, g_pevLastInflictor ); + + if ( m_pTank != 0 ) + { + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + } + + // this client isn't going to be thinking for a while, so reset the sound until they respawn + pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( edict() ) ); + { + if ( pSound ) + { + pSound->Reset(); + } + } + + SetAnimation( PLAYER_DIE ); + + m_iRespawnFrames = 0; + + pev->modelindex = g_ulModelIndexPlayer; // don't use eyes + + pev->takedamage = DAMAGE_NO; + pev->deadflag = DEAD_DYING; + pev->movetype = MOVETYPE_TOSS; + ClearBits(pev->flags, FL_ONGROUND); + if (pev->velocity.z < 10) + pev->velocity.z += RANDOM_FLOAT(0,300); + + // clear out the suit message cache so we don't keep chattering + SetSuitUpdate(NULL, FALSE, 0); + + // send "health" update message to zero + m_iClientHealth = 0; + MESSAGE_BEGIN( MSG_ONE, gmsgHealth, NULL, pev ); + WRITE_BYTE( m_iClientHealth ); + MESSAGE_END(); + + // Tell Ammo Hud that the player is dead + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pev ); + WRITE_BYTE(0); + WRITE_BYTE(0XFF); + WRITE_BYTE(0xFF); + MESSAGE_END(); + + // reset FOV + m_iFOV = m_iClientFOV = 0; + + MESSAGE_BEGIN( MSG_ONE, gmsgSetFOV, NULL, pev ); + 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; + GibMonster(); // This clears pev->model + pev->effects |= EF_NODRAW; + + // Tell the arena that this player's died + if (m_pCurrentArena) + m_pCurrentArena->PlayerKilled( this ); + + return; + } + + // Tell the arena that this player's died + if (m_pCurrentArena) + m_pCurrentArena->PlayerKilled( this ); + + DeathSound(); + + pev->angles.x = 0; + pev->angles.z = 0; + + SetThink(&CBasePlayer::PlayerDeathThink); + pev->nextthink = gpGlobals->time + 0.1; + + // Tell all this player's discs to remove themselves after the 3rd bounce + edict_t *pFind = FIND_ENTITY_BY_CLASSNAME( NULL, "disc" ); + while ( !FNullEnt( pFind ) ) + { + CBaseEntity *pEnt = CBaseEntity::Instance( pFind ); + CDisc *pDisc = (CDisc *)pEnt; + + if ( pDisc ) + { + if ( ((CBaseEntity*)pDisc->m_hOwner) == this ) + pDisc->m_bRemoveSelf = true; + } + + pFind = FIND_ENTITY_BY_CLASSNAME( pFind, "disc" ); + } +} + +// Hit by a decapitator disc +void CBasePlayer::Decapitate( entvars_t *pevKiller ) +{ + // If the player is frozen, shatter instead of decapitating + if ( m_iFrozen ) + { + Shatter( pevKiller ); + return; + } + + if ( IsAlive() ) + { + m_LastHitGroup = HITGROUP_HEAD; + m_hLastPlayerToHitMe = CBaseEntity::Instance( pevKiller ); + m_flLastDiscHit = gpGlobals->time; + TakeDamage( pev, pevKiller, 500, DMG_NEVERGIB ); + + EMIT_SOUND(ENT(pev), CHAN_AUTO, "decap.wav", 1, ATTN_NORM); + + // Remove the Head + SetBodygroup( 1, 2 ); + + // Spawn a head + CGib *pGib = GetClassPtr( (CGib *)NULL ); + pGib->Spawn( "models/head.mdl" );// throw one head + pGib->pev->solid = SOLID_NOT; + pGib->pev->groupinfo = pev->groupinfo; + pGib->pev->origin = pev->origin + pev->view_ofs; + pGib->pev->velocity = Vector (RANDOM_FLOAT(-10,10), RANDOM_FLOAT(-10,10), 300); + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + pGib->m_bloodColor = BloodColor(); + } +} + +// Hit by a frozen decapitator disc +void CBasePlayer::Shatter( entvars_t *pevKiller ) +{ + if ( IsAlive() ) + { + m_LastHitGroup = HITGROUP_HEAD; + m_hLastPlayerToHitMe = CBaseEntity::Instance( pevKiller ); + m_flLastDiscHit = gpGlobals->time; + TakeDamage( pev, pevKiller, 500, DMG_NEVERGIB ); + + EMIT_SOUND(ENT(pev), CHAN_AUTO, "shatter.wav", 1, ATTN_NORM); + + // make myself invisible + pev->effects |= EF_NODRAW; + pev->solid = SOLID_NOT; + UTIL_SetOrigin( pev, pev->origin ); + + // Spawn a head + CGib::SpawnHeadGib( pev ); + CGib::SpawnRandomGibs( pev, 6, 1 ); + } +} + +#define WALK_SPEED 100 + +// Set the activity based on an event or current state +void CBasePlayer::SetAnimation( PLAYER_ANIM playerAnim ) +{ + int animDesired; + float speed; + + speed = pev->velocity.Length2D(); + + if (pev->flags & FL_FROZEN) + { + speed = 0; + playerAnim = PLAYER_IDLE; + } + + switch (playerAnim) + { + case PLAYER_JUMP: + m_IdealActivity = ACT_HOP; + break; + + case PLAYER_SUPERJUMP: + m_IdealActivity = ACT_LEAP; + break; + + case PLAYER_DIE: + m_IdealActivity = GetDeathActivity(); + break; + + case PLAYER_ATTACK1: + m_IdealActivity = ACT_BASE_THROW; + break; + + case PLAYER_FALL: + m_IdealActivity = ACT_FALL; + break; + + case PLAYER_IDLE: + case PLAYER_WALK: + if ( !FBitSet( pev->flags, FL_ONGROUND ) && (m_Activity == ACT_HOP) ) // Still jumping + { + m_IdealActivity = m_Activity; + } + else + { + m_IdealActivity = ACT_BASE_WALK; + } + break; + } + + Vector vecNormVel; + float flDot, flSideDot, flVelDot; + bool bInReverse; + int iFrame; + + // Decide which sequence to play based upon the activity + switch (m_IdealActivity) + { + case ACT_DIEFORWARD: + case ACT_FALL: + default: + if ( m_Activity == m_IdealActivity) + return; + m_Activity = ACT_FALL; + + animDesired = GetFallAnimation(); + // Already using the desired animation? + if (pev->sequence == animDesired) + return; + + pev->gaitsequence = 0; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + + case ACT_LEAP: + UTIL_MakeVectors( pev->angles ); + vecNormVel = pev->velocity.Normalize(); + flDot = DotProduct( vecNormVel, gpGlobals->v_forward ); + if ( flDot < -0.6 ) + { + // Use non-blended backflip + animDesired = LookupSequence( "backflip" ); + m_Activity = m_IdealActivity; + pev->gaitsequence = 0; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + } + else + { + // Use blended longjump + animDesired = LookupSequence( "longjump" ); + m_Activity = ACT_LEAP; + pev->gaitsequence = animDesired; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + } + break; + + case ACT_DIE_HEADSHOT: + animDesired = LookupSequence( "die_simple" ); + m_Activity = m_IdealActivity; + + pev->gaitsequence = 0; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + + case ACT_HOP: + iFrame = static_cast(pev->frame / 18); + if ( iFrame >= 2 && iFrame <= 11 ) + animDesired = LookupSequence( "jump" ); + else + animDesired = LookupSequence( "jumpl" ); + // Already using the desired animation? + if (pev->sequence == animDesired) + return; + + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + + case ACT_BASE_THROW: + // No throw animation during backflip + if ( pev->sequence == LookupSequence( "backflip" ) ) + return; + + // If we're in the air, we need to use the blended longjump throw + if ( pev->sequence == LookupSequence( "longjump" ) ) + { + // Use blended longjump + animDesired = LookupSequence( "longjump_throw" ); + m_Activity = ACT_FLINCH_CLOCKWISE; + pev->gaitsequence = animDesired; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + } + + animDesired = GetThrowAnim(); + m_Activity = m_IdealActivity; + m_flThrowTime = gpGlobals->time; + break; + + case ACT_BASE_WALK: + UTIL_MakeVectors( pev->angles ); + bInReverse = ( pev->sequence == LookupSequence("base_reverse") ); + vecNormVel = pev->velocity.Normalize(); + flDot = DotProduct( vecNormVel, gpGlobals->v_forward ); + flSideDot = DotProduct( vecNormVel, gpGlobals->v_right ); + flVelDot = DotProduct( m_vecOldVelocity, vecNormVel ); + if ( ( m_flBackupTime < gpGlobals->time ) && (m_Activity != ACT_BASE_THROW) || m_fSequenceFinished ) + { + //UTIL_MakeVectors( pev->angles ); + //ALERT(at_console, "%f\n", flDot ); + //ALERT(at_console, "%f\n", flSideDot ); + //ALERT(at_console, "%f\n", flVelDot ); + //ALERT(at_console, "%f %f\n", flVelDot, flDot ); + + if ( speed == 0 ) + { + animDesired = LookupSequence( "base_stand" ); + } + /* + else if ( ( pev->sequence == LookupSequence("base_backup") ) && ( flSideDot < -0.3 || flSideDot > 0.3 ) && (flDot < -0.3) ) + { + // Detect running backwards -> strafe transition + animDesired = LookupSequence( "base_reverse" ); + m_flTransitionTime = gpGlobals->time + 0.5; + ALERT(at_console, "HERE.. "); + } + */ + else if ( flDot < -0.6 ) // && m_flTransitionTime < gpGlobals->time ) + { + animDesired = LookupSequence( "base_backup" ); + } + else if ( ( flVelDot <= 0 ) && ( flDot <= 0.6 ) ) + { + animDesired = LookupSequence( "base_reverse" ); + m_flBackupTime = gpGlobals->time + 0.7; + pev->effects |= EF_NOINTERP; + } + else + { + if ( speed > WALK_SPEED ) + animDesired = LookupSequence( "base_run" ); + else + animDesired = LookupSequence( "base_walk" ); + } + + if (animDesired == -1) + { + animDesired = 0; + } + m_Activity = ACT_BASE_WALK; + } + else if ( bInReverse ) + { + // Don't play the backup run if we're still in the backup run + if ( DotProduct( m_vecOldVelocity, vecNormVel ) < 0 ) + { + m_flBackupTime = 0; + if ( speed > WALK_SPEED ) + animDesired = LookupSequence( "base_run" ); + else + animDesired = LookupSequence( "base_walk" ); + pev->effects |= EF_NOINTERP; + } + else + { + animDesired = pev->sequence; + } + } + else + { + animDesired = pev->sequence; + } + break; + } + + // Set gait animation + if ( m_flBackupTime > gpGlobals->time ) + { + pev->gaitsequence = LookupSequence( "base_backup" ); + } + else + { + if ( speed > WALK_SPEED ) + { + pev->gaitsequence = LookupSequence( "base_run" ); + } + else if (speed > 0) + { + pev->gaitsequence = LookupSequence( "base_walk" ); + } + } + + // Idle? + if (speed <= 0) + { + pev->gaitsequence = LookupSequence( "base_stand" ); + } + + // Already using the desired animation? + if (pev->sequence == animDesired) + return; + + // Reset to first frame of desired animation + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); +} + +int CBasePlayer::GetThrowAnim( void ) +{ + int throwAnim; + + if ( pev->velocity.Length2D() == 0 ) + throwAnim = LookupSequence( "base_stand_throw" ); + /* + // Choose throw anim based upon powerups. + // Multiple Powerups can be had, in which case the one considered more dangerous has the throw animation. + else if ( m_iPowerups & POW_TRIPLE ) + throwAnim = LookupSequence( "triple_throw" ); + else if ( m_iPowerups & POW_FAST ) + throwAnim = LookupSequence( "kill_throw" ); + else if ( m_iPowerups & POW_HARD ) + throwAnim = LookupSequence( "hard_throw" ); + else if ( m_iPowerups & POW_FREEZE ) + throwAnim = LookupSequence( "freeze_throw" ); + */ + else + throwAnim = LookupSequence( "base_throw" ); + + return throwAnim; +} + +int CBasePlayer::GetHoldAnim( void ) +{ + int holdAnim; + + // Choose hold anim based upon powerups. + // Multiple Powerups can be had, in which case the one considered more dangerous has the animation. + if ( m_iPowerups & POW_TRIPLE ) + holdAnim = LookupSequence( "triple_ready" ); + else if ( m_iPowerups & POW_FAST ) + holdAnim = LookupSequence( "kill_ready" ); + else if ( m_iPowerups & POW_HARD ) + holdAnim = LookupSequence( "hard_ready" ); + else if ( m_iPowerups & POW_FREEZE ) + holdAnim = LookupSequence( "freeze_ready" ); + else + holdAnim = LookupSequence( "base_ready" ); + + return holdAnim; +} + +int CBasePlayer::GetFallAnimation( void ) +{ + Vector vecNormVel = pev->velocity.Normalize(); + int fallAnim; + + UTIL_MakeVectors( pev->angles ); + float flDot = DotProduct( vecNormVel, gpGlobals->v_forward ); + float flSideDot = DotProduct( vecNormVel, gpGlobals->v_right ); + // Choose a falling animation based upon the velocity vector + if ( flDot < -0.6 ) + fallAnim = LookupSequence( "fall_b" ); + else if ( flSideDot < -0.6 ) + fallAnim = LookupSequence( "fall_r" ); + else if ( flSideDot > 0.6 ) + fallAnim = LookupSequence( "fall_l" ); + else + fallAnim = LookupSequence( "fall_f" ); + + return fallAnim; +} + +/* +=========== +WaterMove +============ +*/ +#define AIRTIME 12 // lung full of air lasts this many seconds + +void CBasePlayer::WaterMove() +{ + int air; + + if (pev->movetype == MOVETYPE_NOCLIP) + return; + + if (pev->health < 0) + return; + + // waterlevel 0 - not in water + // waterlevel 1 - feet in water + // waterlevel 2 - waist in water + // waterlevel 3 - head in water + + if (pev->waterlevel != 3) + { + // not underwater + + // play 'up for air' sound + if (pev->air_finished < gpGlobals->time) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_wade1.wav", 1, ATTN_NORM); + else if (pev->air_finished < gpGlobals->time + 9) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_wade2.wav", 1, ATTN_NORM); + + pev->air_finished = gpGlobals->time + AIRTIME; + pev->dmg = 2; + + // if we took drowning damage, give it back slowly + if (m_idrowndmg > m_idrownrestored) + { + // set drowning damage bit. hack - dmg_drownrecover actually + // makes the time based damage code 'give back' health over time. + // make sure counter is cleared so we start count correctly. + + // NOTE: this actually causes the count to continue restarting + // until all drowning damage is healed. + + m_bitsDamageType |= DMG_DROWNRECOVER; + m_bitsDamageType &= ~DMG_DROWN; + m_rgbTimeBasedDamage[itbd_DrownRecover] = 0; + } + + } + else + { // fully under water + // stop restoring damage while underwater + m_bitsDamageType &= ~DMG_DROWNRECOVER; + m_rgbTimeBasedDamage[itbd_DrownRecover] = 0; + + if (pev->air_finished < gpGlobals->time) // drown! + { + if (pev->pain_finished < gpGlobals->time) + { + // take drowning damage + pev->dmg += 1; + if (pev->dmg > 5) + pev->dmg = 5; + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), pev->dmg, DMG_DROWN); + pev->pain_finished = gpGlobals->time + 1; + + // track drowning damage, give it back when + // player finally takes a breath + + m_idrowndmg += static_cast(pev->dmg); + } + } + else + { + m_bitsDamageType &= ~DMG_DROWN; + } + } + + if (!pev->waterlevel) + { + if (FBitSet(pev->flags, FL_INWATER)) + { +#if 0 + // play leave water sound + switch (RANDOM_LONG(0,3)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM); break; + } +#endif + + ClearBits(pev->flags, FL_INWATER); + } + return; + } + + // make bubbles + + air = (int)(pev->air_finished - gpGlobals->time); + if (!RANDOM_LONG(0,0x1f) && RANDOM_LONG(0,AIRTIME-1) >= air) + { + switch (RANDOM_LONG(0,3)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim1.wav", 0.8, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim2.wav", 0.8, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim3.wav", 0.8, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim4.wav", 0.8, ATTN_NORM); break; + } + } + + if (pev->watertype == CONTENT_LAVA) // do damage + { + if (pev->dmgtime < gpGlobals->time) + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), 10 * pev->waterlevel, DMG_BURN); + } + else if (pev->watertype == CONTENT_SLIME) // do damage + { + pev->dmgtime = gpGlobals->time + 1; + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), 4 * pev->waterlevel, DMG_ACID); + } + + if (!FBitSet(pev->flags, FL_INWATER)) + { +#if 0 + // player enter water sound + if (pev->watertype == CONTENT_WATER) + { + switch (RANDOM_LONG(0,3)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM); break; + } + } +#endif + + SetBits(pev->flags, FL_INWATER); + pev->dmgtime = 0; + } + + if (!FBitSet(pev->flags, FL_WATERJUMP)) + pev->velocity = pev->velocity - 0.8 * pev->waterlevel * gpGlobals->frametime * pev->velocity; +} + + +// TRUE if the player is attached to a ladder +BOOL CBasePlayer::IsOnLadder( void ) +{ + return (pev->movetype == MOVETYPE_FLY); +} + +void CBasePlayer::PlayerDeathThink(void) +{ + float flForward; + + if (FBitSet(pev->flags, FL_ONGROUND)) + { + flForward = pev->velocity.Length() - 20; + if (flForward <= 0) + pev->velocity = g_vecZero; + else + pev->velocity = flForward * pev->velocity.Normalize(); + } + + if (pev->modelindex && (!m_fSequenceFinished) && (pev->deadflag == DEAD_DYING)) + { + StudioFrameAdvance( ); + + m_iRespawnFrames++; // Note, these aren't necessarily real "frames", so behavior is dependent on # of client movement commands + if ( m_iRespawnFrames < 120 ) // Animations should be no longer than this + return; + } + + if (pev->deadflag == DEAD_DYING) + pev->deadflag = DEAD_DEAD; + + StopAnimation(); + + pev->effects |= EF_NOINTERP; + pev->framerate = 0.0; + + if (pev->deadflag == DEAD_DEAD) + { + if ( g_pGameRules->FPlayerCanRespawn( this ) ) + { + m_fDeadTime = gpGlobals->time; + pev->deadflag = DEAD_RESPAWNABLE; + } + + return; + } + +// if the player has been dead for one second longer than allowed by forcerespawn, +// forcerespawn isn't on. Send the player off to an intermission camera until they +// choose to respawn. + if ( g_pGameRules->IsMultiplayer() && ( gpGlobals->time > (m_fDeadTime + 6) ) && !(m_afPhysicsFlags & PFLAG_OBSERVER) ) + { + // go to dead camera. + StartDeathCam(); + } + + // wait three seconds to respawn + if ( gpGlobals->time <= ( m_fDeadTime + 3 ) ) + return; + + pev->button = 0; + m_iRespawnFrames = 0; + + //ALERT(at_console, "Respawn\n"); + respawn(pev, !(m_afPhysicsFlags & PFLAG_OBSERVER) );// don't copy a corpse if we're in deathcam. + pev->nextthink = -1; + + // Tell the arena that this player's died + if (m_pCurrentArena) + m_pCurrentArena->PlayerRespawned( this ); +} + +//========================================================= +// StartDeathCam - find an intermission spot and send the +// player off into observer mode +//========================================================= +void CBasePlayer::StartDeathCam( void ) +{ + edict_t *pSpot, *pNewSpot; + int iRand; + + if ( pev->view_ofs == g_vecZero ) + { + // don't accept subsequent attempts to StartDeathCam() + return; + } + + pSpot = FIND_ENTITY_BY_CLASSNAME( NULL, "info_intermission"); + + if ( !FNullEnt( pSpot ) ) + { + // at least one intermission spot in the world. + iRand = RANDOM_LONG( 0, 3 ); + + while ( iRand > 0 ) + { + pNewSpot = FIND_ENTITY_BY_CLASSNAME( pSpot, "info_intermission"); + + if ( pNewSpot ) + { + pSpot = pNewSpot; + } + + iRand--; + } + + CopyToBodyQue( pev ); + StartObserver( pSpot->v.origin, pSpot->v.v_angle ); + } + else + { + // no intermission spot. Push them up in the air, looking down at their corpse + TraceResult tr; + CopyToBodyQue( pev ); + UTIL_TraceLine( pev->origin, pev->origin + Vector( 0, 0, 128 ), ignore_monsters, edict(), &tr ); + StartObserver( tr.vecEndPos, UTIL_VecToAngles( tr.vecEndPos - pev->origin ) ); + return; + } +} + +// +// PlayerUse - handles USE keypress +// +#define PLAYER_SEARCH_RADIUS (float)64 + +void CBasePlayer::PlayerUse ( void ) +{ + // Was use pressed or released? + if ( ! ((pev->button | m_afButtonPressed | m_afButtonReleased) & IN_USE) ) + return; + + // Hit Use on a train? + if ( m_afButtonPressed & IN_USE ) + { + if ( m_pTank != 0 ) + { + // Stop controlling the tank + // TODO: Send HUD Update + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + return; + } + else + { + if ( m_afPhysicsFlags & PFLAG_ONTRAIN ) + { + m_afPhysicsFlags &= ~PFLAG_ONTRAIN; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + else + { // Start controlling the train! + CBaseEntity *pTrain = CBaseEntity::Instance( pev->groundentity ); + + if ( pTrain && !(pev->button & IN_JUMP) && FBitSet(pev->flags, FL_ONGROUND) && (pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) && pTrain->OnControls(pev) ) + { + m_afPhysicsFlags |= PFLAG_ONTRAIN; + m_iTrain = TrainSpeed(static_cast(pTrain->pev->speed), pTrain->pev->impulse); + m_iTrain |= TRAIN_NEW; + EMIT_SOUND( ENT(pev), CHAN_ITEM, "plats/train_use1.wav", 0.8, ATTN_NORM); + return; + } + } + } + } + + CBaseEntity *pObject = NULL; + CBaseEntity *pClosest = NULL; + Vector vecLOS; + float flMaxDot = VIEW_FIELD_NARROW; + float flDot; + + UTIL_MakeVectors ( pev->v_angle );// so we know which way we are facing + + while ((pObject = UTIL_FindEntityInSphere( pObject, pev->origin, PLAYER_SEARCH_RADIUS )) != NULL) + { + + if (pObject->ObjectCaps() & (FCAP_IMPULSE_USE | FCAP_CONTINUOUS_USE | FCAP_ONOFF_USE)) + { + // !!!PERFORMANCE- should this check be done on a per case basis AFTER we've determined that + // this object is actually usable? This dot is being done for every object within PLAYER_SEARCH_RADIUS + // when player hits the use key. How many objects can be in that area, anyway? (sjb) + vecLOS = (VecBModelOrigin( pObject->pev ) - (pev->origin + pev->view_ofs)); + + // This essentially moves the origin of the target to the corner nearest the player to test to see + // if it's "hull" is in the view cone + vecLOS = UTIL_ClampVectorToBox( vecLOS, pObject->pev->size * 0.5 ); + + flDot = DotProduct (vecLOS , gpGlobals->v_forward); + if (flDot > flMaxDot ) + {// only if the item is in front of the user + pClosest = pObject; + flMaxDot = flDot; +// ALERT( at_console, "%s : %f\n", STRING( pObject->pev->classname ), flDot ); + } +// ALERT( at_console, "%s : %f\n", STRING( pObject->pev->classname ), flDot ); + } + } + pObject = pClosest; + + // Found an object + if (pObject ) + { + //!!!UNDONE: traceline here to prevent USEing buttons through walls + int caps = pObject->ObjectCaps(); + + if ( m_afButtonPressed & IN_USE ) + EMIT_SOUND( ENT(pev), CHAN_ITEM, "common/wpn_select.wav", 0.4, ATTN_NORM); + + if ( ( (pev->button & IN_USE) && (caps & FCAP_CONTINUOUS_USE) ) || + ( (m_afButtonPressed & IN_USE) && (caps & (FCAP_IMPULSE_USE|FCAP_ONOFF_USE)) ) ) + { + if ( caps & FCAP_CONTINUOUS_USE ) + m_afPhysicsFlags |= PFLAG_USING; + + pObject->Use( this, this, USE_SET, 1 ); + } + // UNDONE: Send different USE codes for ON/OFF. Cache last ONOFF_USE object to send 'off' if you turn away + else if ( (m_afButtonReleased & IN_USE) && (pObject->ObjectCaps() & FCAP_ONOFF_USE) ) // BUGBUG This is an "off" use + { + pObject->Use( this, this, USE_SET, 0 ); + } + } + else + { + if ( m_afButtonPressed & IN_USE ) + EMIT_SOUND( ENT(pev), CHAN_ITEM, "common/wpn_denyselect.wav", 0.4, ATTN_NORM); + } +} + + + +void CBasePlayer::Jump() +{ + Vector vecWallCheckDir;// direction we're tracing a line to find a wall when walljumping + Vector vecAdjustedVelocity; + Vector vecSpot; + TraceResult tr; + + if (FBitSet(pev->flags, FL_WATERJUMP)) + return; + + if (pev->waterlevel >= 2) + { + return; + } + + // jump velocity is sqrt( height * gravity * 2) + + // If this isn't the first frame pressing the jump button, break out. + if ( !FBitSet( m_afButtonPressed, IN_JUMP ) ) + return; // don't pogo stick + + if ( !(pev->flags & FL_ONGROUND) || !pev->groundentity ) + { + return; + } + +// many features in this function use v_forward, so makevectors now. + UTIL_MakeVectors (pev->angles); + + SetAnimation( PLAYER_JUMP ); + + // If you're standing on a conveyor, add it's velocity to yours (for momentum) + entvars_t *pevGround = VARS(pev->groundentity); + if ( pevGround && (pevGround->flags & FL_CONVEYOR) ) + { + pev->velocity = pev->velocity + pev->basevelocity; + } +} + + + +// This is a glorious hack to find free space when you've crouched into some solid space +// Our crouching collisions do not work correctly for some reason and this is easier +// than fixing the problem :( +void FixPlayerCrouchStuck( edict_t *pPlayer ) +{ + TraceResult trace; + + // Move up as many as 18 pixels if the player is stuck. + for ( int i = 0; i < 18; i++ ) + { + UTIL_TraceHull( pPlayer->v.origin, pPlayer->v.origin, dont_ignore_monsters, head_hull, pPlayer, &trace ); + if ( trace.fStartSolid ) + pPlayer->v.origin.z ++; + else + break; + } +} + +void CBasePlayer::Duck( ) +{ + return; +} + +// +// ID's player as such. +// +int CBasePlayer::Classify ( void ) +{ + return CLASS_PLAYER; +} + + +void CBasePlayer::AddPoints( int score, BOOL bAllowNegativeScore ) +{ + // Positive score always adds + if ( score < 0 ) + { + if ( !bAllowNegativeScore ) + { + if ( pev->frags < 0 ) // Can't go more negative + return; + + if ( -score > pev->frags ) // Will this go negative? + { + score = static_cast(-pev->frags); // Sum will be 0 + } + } + } + + pev->frags += score; +} + + +void CBasePlayer::AddPointsToTeam( int score, BOOL bAllowNegativeScore ) +{ + int index = entindex(); + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + if ( pPlayer && i != index ) + { + if ( g_pGameRules->PlayerRelationship( this, pPlayer ) == GR_TEAMMATE ) + { + pPlayer->AddPoints( score, bAllowNegativeScore ); + } + } + } +} + +#if 0 +void CBasePlayer::CheckWeapon(void) +{ + // play a weapon idle anim if it's time! + if ( gpGlobals->time > m_flTimeWeaponIdle ) + { + WeaponIdle ( ); + } +} +#endif + + +// play a footstep if it's time - this will eventually be frame-based. not time based. + +#define STEP_CONCRETE 0 // default step sound +#define STEP_METAL 1 // metal floor +#define STEP_DIRT 2 // dirt, sand, rock +#define STEP_VENT 3 // ventillation duct +#define STEP_GRATE 4 // metal grating +#define STEP_TILE 5 // floor tiles +#define STEP_SLOSH 6 // shallow liquid puddle +#define STEP_WADE 7 // wading in liquid +#define STEP_LADDER 8 // climbing ladder + +// Play correct step sound for material we're on or in + +void CBasePlayer :: PlayStepSound(int step, float fvol) +{ + static int iSkipStep = 0; + + if ( !g_pGameRules->PlayFootstepSounds( this, fvol ) ) + return; + + // irand - 0,1 for right foot, 2,3 for left foot + // used to alternate left and right foot + int irand = RANDOM_LONG(0,1) + (m_iStepLeft * 2); + + m_iStepLeft = !m_iStepLeft; + + switch (step) + { + default: + case STEP_CONCRETE: + switch (irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_METAL: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_metal1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_metal3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_metal2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_metal4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_DIRT: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_dirt1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_dirt3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_dirt2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_dirt4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_VENT: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_duct1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_duct3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_duct2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_duct4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_GRATE: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_grate1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_grate3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_grate2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_grate4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_TILE: + if (!RANDOM_LONG(0,4)) + irand = 4; + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile4.wav", fvol, ATTN_NORM); break; + case 4: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile5.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_SLOSH: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_slosh1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_slosh3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_slosh2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_slosh4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_WADE: + if ( iSkipStep == 0 ) + { + iSkipStep++; + break; + } + + if ( iSkipStep++ == 3 ) + { + iSkipStep = 0; + } + + switch (irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_wade1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_wade2.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_wade3.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_wade4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_LADDER: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_ladder1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_ladder3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_ladder2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_ladder4.wav", fvol, ATTN_NORM); break; + } + break; + } +} + +// Simple mapping from texture type character to step type + +int MapTextureTypeStepType(char chTextureType) +{ +switch (chTextureType) + { + default: + case CHAR_TEX_CONCRETE: return STEP_CONCRETE; + case CHAR_TEX_METAL: return STEP_METAL; + case CHAR_TEX_DIRT: return STEP_DIRT; + case CHAR_TEX_VENT: return STEP_VENT; + case CHAR_TEX_GRATE: return STEP_GRATE; + case CHAR_TEX_TILE: return STEP_TILE; + case CHAR_TEX_SLOSH: return STEP_SLOSH; + } +} + +// Play left or right footstep based on material player is on or in + +void CBasePlayer :: UpdateStepSound( void ) +{ + int fWalking; + float fvol; + char szbuffer[64]; + const char *pTextureName; + Vector start, end; + float rgfl1[3]; + float rgfl2[3]; + Vector knee; + Vector feet; + Vector center; + float height; + float speed; + float velrun; + float velwalk; + float flduck; + int fLadder; + int step; + + if (gpGlobals->time <= m_flTimeStepSound) + return; + + if (pev->flags & FL_FROZEN) + return; + + if (m_flTouchedByJumpPad > gpGlobals->time) + return; + + speed = pev->velocity.Length(); + + // determine if we are on a ladder + fLadder = IsOnLadder(); + + // UNDONE: need defined numbers for run, walk, crouch, crouch run velocities!!!! + if (FBitSet(pev->flags, FL_DUCKING) || fLadder) + { + velwalk = 60; // These constants should be based on cl_movespeedkey * cl_forwardspeed somehow + velrun = 80; // UNDONE: Move walking to server + flduck = 0.1; + } + else + { + velwalk = 120; + velrun = 210; + flduck = 0.0; + } + + // ALERT (at_console, "vel: %f\n", vecVel.Length()); + + // if we're on a ladder or on the ground, and we're moving fast enough, + // play step sound. Also, if m_flTimeStepSound is zero, get the new + // sound right away - we just started moving in new level. + + if ((fLadder || FBitSet (pev->flags, FL_ONGROUND)) && pev->velocity != g_vecZero + && (speed >= velwalk || !m_flTimeStepSound)) + { + SetAnimation( PLAYER_WALK ); + + fWalking = speed < velrun; + + center = knee = feet = (pev->absmin + pev->absmax) * 0.5; + height = pev->absmax.z - pev->absmin.z; + + knee.z = pev->absmin.z + height * 0.2; + feet.z = pev->absmin.z; + + // find out what we're stepping in or on... + if (fLadder) + { + step = STEP_LADDER; + fvol = 0.35; + m_flTimeStepSound = gpGlobals->time + 0.35; + } + else if ( UTIL_PointContents ( knee ) == CONTENTS_WATER ) + { + step = STEP_WADE; + fvol = 0.65; + m_flTimeStepSound = gpGlobals->time + 0.6; + } + else if (UTIL_PointContents ( feet ) == CONTENTS_WATER ) + { + step = STEP_SLOSH; + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + } + else + { + // find texture under player, if different from current texture, + // get material type + + start = end = center; // center point of player BB + start.z = end.z = pev->absmin.z; // copy zmin + start.z += 4.0; // extend start up + end.z -= 24.0; // extend end down + + start.CopyToArray(rgfl1); + end.CopyToArray(rgfl2); + + pTextureName = TRACE_TEXTURE( ENT( pev->groundentity), rgfl1, rgfl2 ); + if ( pTextureName ) + { + // strip leading '-0' or '{' or '!' + if (*pTextureName == '-') + pTextureName += 2; + if (*pTextureName == '{' || *pTextureName == '!') + pTextureName++; + + if (_strnicmp(pTextureName, m_szTextureName, CBTEXTURENAMEMAX-1)) + { + // current texture is different from texture player is on... + // set current texture + strcpy(szbuffer, pTextureName); + szbuffer[CBTEXTURENAMEMAX - 1] = 0; + strcpy(m_szTextureName, szbuffer); + + // ALERT ( at_aiconsole, "texture: %s\n", m_szTextureName ); + + // get texture type + m_chTextureType = TEXTURETYPE_Find(m_szTextureName); + } + } + + step = MapTextureTypeStepType(m_chTextureType); + + switch (m_chTextureType) + { + default: + case CHAR_TEX_CONCRETE: + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_METAL: + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_DIRT: + fvol = fWalking ? 0.25 : 0.55; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_VENT: + fvol = fWalking ? 0.4 : 0.7; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_GRATE: + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_TILE: + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_SLOSH: + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + } + } + + m_flTimeStepSound += flduck; // slower step time if ducking + + // play the sound + + // 35% volume if ducking + if ( pev->flags & FL_DUCKING ) + fvol *= 0.35; + } +} + + +#define CLIMB_SHAKE_FREQUENCY 22 // how many frames in between screen shakes when climbing +#define MAX_CLIMB_SPEED 200 // fastest vertical climbing speed possible +#define CLIMB_SPEED_DEC 15 // climbing deceleration rate +#define CLIMB_PUNCH_X -7 // how far to 'punch' client X axis when climbing +#define CLIMB_PUNCH_Z 7 // how far to 'punch' client Z axis when climbing + +void CBasePlayer::PreThink(void) +{ + int buttonsChanged = (m_afButtonLast ^ pev->button); // These buttons have changed this frame + + // Fade away the renderfx + if ( pev->renderamt ) + { + if ( !m_iFrozen ) + pev->renderamt -= 25; + + if (pev->renderamt <= 0 || (m_flFreezeTime < gpGlobals->time && m_iFrozen )) + ClearFreezeAndRender(); + } + + if ( m_flSendArenaStatus != -1 && m_flSendArenaStatus <= gpGlobals->time ) + { + g_pGameRules->UpdateGameMode( this ); + m_flSendArenaStatus = -1; + } + + // Hack to pass down the weapon information to the client after they've connected. + // Needed for listen servers because they lose msgs while bringing up the svr. + if ( m_flKnownItemTime && m_flKnownItemTime < gpGlobals->time ) + { + CLIENT_COMMAND( edict(), "r_drawviewmodel 0\n" ); + m_fKnownItem = FALSE; + m_flKnownItemTime = 0; + } + + // Debounced button codes for pressed/released + // UNDONE: Do we need auto-repeat? + m_afButtonPressed = buttonsChanged & pev->button; // The changed ones still down are "pressed" + m_afButtonReleased = buttonsChanged & (~pev->button); // The ones not down are "released" + + g_pGameRules->PlayerThink( this ); + + if ( g_fGameOver ) + return; // intermission or finale + + UTIL_MakeVectors(pev->v_angle); // is this still used? + + ItemPreFrame( ); + WaterMove(); + + if ( g_pGameRules && g_pGameRules->FAllowFlashlight() ) + m_iHideHUD &= ~HIDEHUD_FLASHLIGHT; + else + m_iHideHUD |= HIDEHUD_FLASHLIGHT; + + + // JOHN: checks if new client data (for HUD and view control) needs to be sent to the client + UpdateClientData(); + + CheckTimeBasedDamage(); + + // Observer Button Handling + if ( IsObserver() ) + { + Observer_HandleButtons(); + pev->impulse = 0; + return; + } + + CheckSuitUpdate(); + + if (pev->deadflag >= DEAD_DYING) + { + PlayerDeathThink(); + return; + } + + // So the correct flags get sent to client asap. + // + if ( m_afPhysicsFlags & PFLAG_ONTRAIN ) + pev->flags |= FL_ONTRAIN; + else + pev->flags &= ~FL_ONTRAIN; + + // Train speed control + if ( m_afPhysicsFlags & PFLAG_ONTRAIN ) + { + CBaseEntity *pTrain = CBaseEntity::Instance( pev->groundentity ); + float vel; + + if ( !pTrain ) + { + TraceResult trainTrace; + // Maybe this is on the other side of a level transition + UTIL_TraceLine( pev->origin, pev->origin + Vector(0,0,-38), ignore_monsters, ENT(pev), &trainTrace ); + + // HACKHACK - Just look for the func_tracktrain classname + if ( trainTrace.flFraction != 1.0 && trainTrace.pHit ) + pTrain = CBaseEntity::Instance( trainTrace.pHit ); + + + if ( !pTrain || !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) || !pTrain->OnControls(pev) ) + { + //ALERT( at_error, "In train mode with no train!\n" ); + m_afPhysicsFlags &= ~PFLAG_ONTRAIN; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + } + else if ( !FBitSet( pev->flags, FL_ONGROUND ) || FBitSet( pTrain->pev->spawnflags, SF_TRACKTRAIN_NOCONTROL ) || (pev->button & (IN_MOVELEFT|IN_MOVERIGHT) ) ) + { + // Turn off the train if you jump, strafe, or the train controls go dead + m_afPhysicsFlags &= ~PFLAG_ONTRAIN; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + + pev->velocity = g_vecZero; + vel = 0; + if ( m_afButtonPressed & IN_FORWARD ) + { + vel = 1; + pTrain->Use( this, this, USE_SET, (float)vel ); + } + else if ( m_afButtonPressed & IN_BACK ) + { + vel = -1; + pTrain->Use( this, this, USE_SET, (float)vel ); + } + + if (vel) + { + m_iTrain = TrainSpeed(static_cast(pTrain->pev->speed), pTrain->pev->impulse); + m_iTrain |= TRAIN_ACTIVE|TRAIN_NEW; + } + + } else if (m_iTrain & TRAIN_ACTIVE) + m_iTrain = TRAIN_NEW; // turn off train + + // DISCWAR: No Jumping or Ducking + /* + if (pev->button & IN_JUMP) + { + // If on a ladder, jump off the ladder + // else Jump + Jump(); + } + // If trying to duck, already ducked, or in the process of ducking + if ((pev->button & IN_DUCK) || FBitSet(pev->flags,FL_DUCKING) || (m_afPhysicsFlags & PFLAG_DUCKING) ) + Duck(); + */ + + // DISCWAR: Impart the velocity from a disc hit onto the player + if ( m_vecHitVelocity != g_vecZero ) + { + pev->velocity = m_vecHitVelocity; + m_vecHitVelocity = g_vecZero; + } + + // play a footstep if it's time - this will eventually be frame-based. not time based. + + UpdateStepSound(); + + if ( !FBitSet ( pev->flags, FL_ONGROUND ) ) + { + m_flFallVelocity = -pev->velocity.z; + } + + // StudioFrameAdvance( );//!!!HACKHACK!!! Can't be hit by traceline when not animating? + + // Clear out ladder pointer + m_hEnemy = NULL; + + if ( m_afPhysicsFlags & PFLAG_ONBARNACLE ) + { + pev->velocity = g_vecZero; + } +} +/* Time based Damage works as follows: + 1) There are several types of timebased damage: + + #define DMG_PARALYZE (1 << 14) // slows affected creature down + #define DMG_NERVEGAS (1 << 15) // nerve toxins, very bad + #define DMG_POISON (1 << 16) // blood poisioning + #define DMG_RADIATION (1 << 17) // radiation exposure + #define DMG_DROWNRECOVER (1 << 18) // drown recovery + #define DMG_ACID (1 << 19) // toxic chemicals or acid burns + #define DMG_SLOWBURN (1 << 20) // in an oven + #define DMG_SLOWFREEZE (1 << 21) // in a subzero freezer + + 2) A new hit inflicting tbd restarts the tbd counter - each monster has an 8bit counter, + per damage type. The counter is decremented every second, so the maximum time + an effect will last is 255/60 = 4.25 minutes. Of course, staying within the radius + of a damaging effect like fire, nervegas, radiation will continually reset the counter to max. + + 3) Every second that a tbd counter is running, the player takes damage. The damage + is determined by the type of tdb. + Paralyze - 1/2 movement rate, 30 second duration. + Nervegas - 5 points per second, 16 second duration = 80 points max dose. + Poison - 2 points per second, 25 second duration = 50 points max dose. + Radiation - 1 point per second, 50 second duration = 50 points max dose. + Drown - 5 points per second, 2 second duration. + Acid/Chemical - 5 points per second, 10 second duration = 50 points max. + Burn - 10 points per second, 2 second duration. + Freeze - 3 points per second, 10 second duration = 30 points max. + + 4) Certain actions or countermeasures counteract the damaging effects of tbds: + + Armor/Heater/Cooler - Chemical(acid),burn, freeze all do damage to armor power, then to body + - recharged by suit recharger + Air In Lungs - drowning damage is done to air in lungs first, then to body + - recharged by poking head out of water + - 10 seconds if swiming fast + Air In SCUBA - drowning damage is done to air in tanks first, then to body + - 2 minutes in tanks. Need new tank once empty. + Radiation Syringe - Each syringe full provides protection vs one radiation dosage + Antitoxin Syringe - Each syringe full provides protection vs one poisoning (nervegas or poison). + Health kit - Immediate stop to acid/chemical, fire or freeze damage. + Radiation Shower - Immediate stop to radiation damage, acid/chemical or fire damage. + + +*/ + +// If player is taking time based damage, continue doing damage to player - +// this simulates the effect of being poisoned, gassed, dosed with radiation etc - +// anything that continues to do damage even after the initial contact stops. +// Update all time based damage counters, and shut off any that are done. + +// The m_bitsDamageType bit MUST be set if any damage is to be taken. +// This routine will detect the initial on value of the m_bitsDamageType +// and init the appropriate counter. Only processes damage every second. + +//#define PARALYZE_DURATION 30 // number of 2 second intervals to take damage +//#define PARALYZE_DAMAGE 0.0 // damage to take each 2 second interval + +//#define NERVEGAS_DURATION 16 +//#define NERVEGAS_DAMAGE 5.0 + +//#define POISON_DURATION 25 +//#define POISON_DAMAGE 2.0 + +//#define RADIATION_DURATION 50 +//#define RADIATION_DAMAGE 1.0 + +//#define ACID_DURATION 10 +//#define ACID_DAMAGE 5.0 + +//#define SLOWBURN_DURATION 2 +//#define SLOWBURN_DAMAGE 1.0 + +//#define SLOWFREEZE_DURATION 1.0 +//#define SLOWFREEZE_DAMAGE 3.0 + +/* */ + + +void CBasePlayer::CheckTimeBasedDamage() +{ + int i; + BYTE bDuration = 0; + + if (!(m_bitsDamageType & DMG_TIMEBASED)) + return; + + // only check for time based damage approx. every 2 seconds + if (abs(static_cast(gpGlobals->time - m_tbdPrev)) < 2.0) + return; + + m_tbdPrev = gpGlobals->time; + + for (i = 0; i < CDMG_TIMEBASED; i++) + { + // make sure bit is set for damage type + if (m_bitsDamageType & (DMG_PARALYZE << i)) + { + switch (i) + { + case itbd_Paralyze: + // UNDONE - flag movement as half-speed + bDuration = PARALYZE_DURATION; + break; + case itbd_NerveGas: +// TakeDamage(pev, pev, NERVEGAS_DAMAGE, DMG_GENERIC); + bDuration = NERVEGAS_DURATION; + break; + case itbd_Poison: + TakeDamage(pev, pev, POISON_DAMAGE, DMG_GENERIC); + bDuration = POISON_DURATION; + break; + case itbd_Radiation: +// TakeDamage(pev, pev, RADIATION_DAMAGE, DMG_GENERIC); + bDuration = RADIATION_DURATION; + break; + case itbd_DrownRecover: + // NOTE: this hack is actually used to RESTORE health + // after the player has been drowning and finally takes a breath + if (m_idrowndmg > m_idrownrestored) + { + int idif = min(m_idrowndmg - m_idrownrestored, 10); + + TakeHealth(idif, DMG_GENERIC); + m_idrownrestored += idif; + } + bDuration = 4; // get up to 5*10 = 50 points back + break; + case itbd_Acid: +// TakeDamage(pev, pev, ACID_DAMAGE, DMG_GENERIC); + bDuration = ACID_DURATION; + break; + case itbd_SlowBurn: +// TakeDamage(pev, pev, SLOWBURN_DAMAGE, DMG_GENERIC); + bDuration = SLOWBURN_DURATION; + break; + case itbd_SlowFreeze: +// TakeDamage(pev, pev, SLOWFREEZE_DAMAGE, DMG_GENERIC); + bDuration = SLOWFREEZE_DURATION; + break; + default: + bDuration = 0; + } + + if (m_rgbTimeBasedDamage[i]) + { + // use up an antitoxin on poison or nervegas after a few seconds of damage + if (((i == itbd_NerveGas) && (m_rgbTimeBasedDamage[i] < NERVEGAS_DURATION)) || + ((i == itbd_Poison) && (m_rgbTimeBasedDamage[i] < POISON_DURATION))) + { + if (m_rgItems[ITEM_ANTIDOTE]) + { + m_rgbTimeBasedDamage[i] = 0; + m_rgItems[ITEM_ANTIDOTE]--; + SetSuitUpdate("!HEV_HEAL4", FALSE, SUIT_REPEAT_OK); + } + } + + + // decrement damage duration, detect when done. + if (!m_rgbTimeBasedDamage[i] || --m_rgbTimeBasedDamage[i] == 0) + { + m_rgbTimeBasedDamage[i] = 0; + // if we're done, clear damage bits + m_bitsDamageType &= ~(DMG_PARALYZE << i); + } + } + else + // first time taking this damage type - init damage duration + m_rgbTimeBasedDamage[i] = bDuration; + } + } +} + +/* +THE POWER SUIT + +The Suit provides 3 main functions: Protection, Notification and Augmentation. +Some functions are automatic, some require power. +The player gets the suit shortly after getting off the train in C1A0 and it stays +with him for the entire game. + +Protection + + Heat/Cold + When the player enters a hot/cold area, the heating/cooling indicator on the suit + will come on and the battery will drain while the player stays in the area. + After the battery is dead, the player starts to take damage. + This feature is built into the suit and is automatically engaged. + Radiation Syringe + This will cause the player to be immune from the effects of radiation for N seconds. Single use item. + Anti-Toxin Syringe + This will cure the player from being poisoned. Single use item. + Health + Small (1st aid kits, food, etc.) + Large (boxes on walls) + Armor + The armor works using energy to create a protective field that deflects a + percentage of damage projectile and explosive attacks. After the armor has been deployed, + it will attempt to recharge itself to full capacity with the energy reserves from the battery. + It takes the armor N seconds to fully charge. + +Notification (via the HUD) + +x Health +x Ammo +x Automatic Health Care + Notifies the player when automatic healing has been engaged. +x Geiger counter + Classic Geiger counter sound and status bar at top of HUD + alerts player to dangerous levels of radiation. This is not visible when radiation levels are normal. +x Poison + Armor + Displays the current level of armor. + +Augmentation + + Reanimation (w/adrenaline) + Causes the player to come back to life after he has been dead for 3 seconds. + Will not work if player was gibbed. Single use. + Long Jump + Used by hitting the ??? key(s). Caused the player to further than normal. + SCUBA + Used automatically after picked up and after player enters the water. + Works for N seconds. Single use. + +Things powered by the battery + + Armor + Uses N watts for every M units of damage. + Heat/Cool + Uses N watts for every second in hot/cold area. + Long Jump + Uses N watts for every jump. + Alien Cloak + Uses N watts for each use. Each use lasts M seconds. + Alien Shield + Augments armor. Reduces Armor drain by one half + +*/ + +// if in range of radiation source, ping geiger counter +#define GEIGERDELAY 0.25 + +void CBasePlayer :: UpdateGeigerCounter( void ) +{ + BYTE range; + + // delay per update ie: don't flood net with these msgs + if (gpGlobals->time < m_flgeigerDelay) + return; + + m_flgeigerDelay = gpGlobals->time + GEIGERDELAY; + + // send range to radition source to client + + range = (BYTE) (m_flgeigerRange / 4); + + if (range != m_igeigerRangePrev) + { + m_igeigerRangePrev = range; + + MESSAGE_BEGIN( MSG_ONE, gmsgGeigerRange, NULL, pev ); + WRITE_BYTE( range ); + MESSAGE_END(); + } + + // reset counter and semaphore + if (!RANDOM_LONG(0,3)) + m_flgeigerRange = 1000; + +} + +/* +================ +CheckSuitUpdate + +Play suit update if it's time +================ +*/ + +#define SUITUPDATETIME 3.5 +#define SUITFIRSTUPDATETIME 0.1 + +void CBasePlayer::CheckSuitUpdate() +{ + int i; + int isentence = 0; + int isearch = m_iSuitPlayNext; + + // Ignore suit updates if no suit + if ( !(pev->weapons & (1<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 + +Check for turning off powerups + +GLOBALS ASSUMED SET: g_ulModelIndexPlayer +================ +*/ + static void +CheckPowerups(entvars_t *pev) +{ + if (pev->health <= 0) + return; + + pev->modelindex = g_ulModelIndexPlayer; // don't use eyes +} + + +//========================================================= +// UpdatePlayerSound - updates the position of the player's +// reserved sound slot in the sound list. +//========================================================= +void CBasePlayer :: UpdatePlayerSound ( void ) +{ + int iBodyVolume; + int iVolume; + CSound *pSound; + + pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt :: ClientSoundIndex( edict() ) ); + + if ( !pSound ) + { + ALERT ( at_console, "Client lost reserved sound!\n" ); + return; + } + + pSound->m_iType = bits_SOUND_NONE; + + // now calculate the best target volume for the sound. If the player's weapon + // is louder than his body/movement, use the weapon volume, else, use the body volume. + + if ( FBitSet ( pev->flags, FL_ONGROUND ) ) + { + iBodyVolume = static_cast(pev->velocity.Length()); + + // clamp the noise that can be made by the body, in case a push trigger, + // weapon recoil, or anything shoves the player abnormally fast. + if ( iBodyVolume > 512 ) + { + iBodyVolume = 512; + } + } + else + { + iBodyVolume = 0; + } + + if ( pev->button & IN_JUMP ) + { + iBodyVolume += 100; + } + +// convert player move speed and actions into sound audible by monsters. + if ( m_iWeaponVolume > iBodyVolume ) + { + m_iTargetVolume = m_iWeaponVolume; + + // OR in the bits for COMBAT sound if the weapon is being louder than the player. + pSound->m_iType |= bits_SOUND_COMBAT; + } + else + { + m_iTargetVolume = iBodyVolume; + } + + // decay weapon volume over time so bits_SOUND_COMBAT stays set for a while + m_iWeaponVolume -= static_cast(250 * gpGlobals->frametime); + if ( m_iWeaponVolume < 0 ) + { + iVolume = 0; + } + + + // if target volume is greater than the player sound's current volume, we paste the new volume in + // immediately. If target is less than the current volume, current volume is not set immediately to the + // lower volume, rather works itself towards target volume over time. This gives monsters a much better chance + // to hear a sound, especially if they don't listen every frame. + iVolume = pSound->m_iVolume; + + if ( m_iTargetVolume > iVolume ) + { + iVolume = m_iTargetVolume; + } + else if ( iVolume > m_iTargetVolume ) + { + iVolume -= static_cast(250 * gpGlobals->frametime); + + if ( iVolume < m_iTargetVolume ) + { + iVolume = 0; + } + } + + if ( m_fNoPlayerSound ) + { + // debugging flag, lets players move around and shoot without monsters hearing. + iVolume = 0; + } + + if ( gpGlobals->time > m_flStopExtraSoundTime ) + { + // since the extra sound that a weapon emits only lasts for one client frame, we keep that sound around for a server frame or two + // after actual emission to make sure it gets heard. + m_iExtraSoundTypes = 0; + } + + if ( pSound ) + { + pSound->m_vecOrigin = pev->origin; + pSound->m_iType |= ( bits_SOUND_PLAYER | m_iExtraSoundTypes ); + pSound->m_iVolume = iVolume; + } + + // keep track of virtual muzzle flash + m_iWeaponFlash -= static_cast(256 * gpGlobals->frametime); + if (m_iWeaponFlash < 0) + m_iWeaponFlash = 0; + + UTIL_MakeVectors ( pev->angles ); + gpGlobals->v_forward.z = 0; + + // Below are a couple of useful little bits that make it easier to determine just how much noise the + // player is making. + // UTIL_ParticleEffect ( pev->origin + gpGlobals->v_forward * iVolume, g_vecZero, 255, 25 ); + //ALERT ( at_console, "%d/%d\n", iVolume, m_iTargetVolume ); +} + + +void CBasePlayer::PostThink() +{ + if ( g_fGameOver ) + goto pt_end; // intermission or finale + + if (!IsAlive()) + goto pt_end; + + // Handle Tank controlling + if ( m_pTank != 0 ) + { // if they've moved too far from the gun, or selected a weapon, unuse the gun + if ( m_pTank->OnControls( pev ) && !pev->weaponmodel ) + { + m_pTank->Use( this, this, USE_SET, 2 ); // try fire the gun + } + else + { // they've moved off the platform + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + } + } + +// do weapon stuff + ItemPostFrame( ); + +// check to see if player landed hard enough to make a sound +// falling farther than half of the maximum safe distance, but not as far a max safe distance will +// play a bootscrape sound, and no damage will be inflicted. Fallling a distance shorter than half +// of maximum safe distance will make no sound. Falling farther than max safe distance will play a +// fallpain sound, and damage will be inflicted based on how far the player fell + + if ( (FBitSet(pev->flags, FL_ONGROUND)) && (pev->health > 0) && m_flFallVelocity >= PLAYER_FALL_PUNCH_THRESHHOLD ) + { + float fvol = 0.5; + + // ALERT ( at_console, "%f\n", m_flFallVelocity ); + + if (pev->watertype == CONTENT_WATER) + { + // Did he hit the world or a non-moving entity? + // BUG - this happens all the time in water, especially when + // BUG - water has current force + // if ( !pev->groundentity || VARS(pev->groundentity)->velocity.z == 0 ) + // EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM); + } + else if ( m_flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED ) + {// after this point, we start doing damage + + // No falling damage in discwar + /* + float flFallDamage = g_pGameRules->FlPlayerFallDamage( this ); + + if ( flFallDamage > pev->health ) + {//splat + // note: play on item channel because we play footstep landing on body channel + EMIT_SOUND(ENT(pev), CHAN_ITEM, "common/bodysplat.wav", 1, ATTN_NORM); + } + + if ( flFallDamage > 0 ) + { + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), flFallDamage, DMG_FALL ); + pev->punchangle.x = 0; + } + */ + + fvol = 1.0; + } + else if ( m_flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED / 2 ) + { + // EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_jumpland2.wav", 1, ATTN_NORM); + fvol = 0.85; + } + else if ( m_flFallVelocity < PLAYER_MIN_BOUNCE_SPEED ) + { + fvol = 0; + } + + if ( fvol > 0.0 ) + { + // get current texture under player right away + m_flTimeStepSound = 0; + UpdateStepSound(); + } + + if ( IsAlive() ) + { + SetAnimation( PLAYER_WALK ); + } + } + + if (FBitSet(pev->flags, FL_ONGROUND)) + { + if (m_flFallVelocity > 64 && !g_pGameRules->IsMultiplayer()) + { + CSoundEnt::InsertSound ( bits_SOUND_PLAYER, pev->origin, static_cast(m_flFallVelocity), 0.2 ); + // ALERT( at_console, "fall %f\n", m_flFallVelocity ); + } + m_flFallVelocity = 0; + } + + // select the proper animation for the player character + if ( IsAlive() && (m_flTouchedByJumpPad < gpGlobals->time) ) + { + if (!pev->velocity.x && !pev->velocity.y) + SetAnimation( PLAYER_IDLE ); + else if ((pev->velocity.x || pev->velocity.y) && (FBitSet(pev->flags, FL_ONGROUND))) + SetAnimation( PLAYER_WALK ); + else if (pev->waterlevel > 1) + SetAnimation( PLAYER_WALK ); + } + + if ( IsAlive() && ( m_flThrowTime ) && ( m_flThrowTime + 0.25 < gpGlobals->time ) ) + { + m_Activity = ACT_BASE_WALK; + m_flThrowTime = 0.0; + if (!pev->velocity.x && !pev->velocity.y) + { + SetAnimation( PLAYER_IDLE ); + } + else + { + SetAnimation( PLAYER_WALK ); + } + } + + StudioFrameAdvance( ); + CheckPowerups(pev); + + UpdatePlayerSound(); + + // Track button info so we can detect 'pressed' and 'released' buttons next frame + m_afButtonLast = pev->button; + +pt_end: + + // Store old velocity for use in backpedalling animations + m_vecOldVelocity = pev->velocity.Normalize(); + + // Decay timers on weapons + // go through all of the weapons and make a list of the ones to pack + for ( int i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + CBasePlayerItem *pPlayerItem = m_rgpPlayerItems[ i ]; + + while ( pPlayerItem ) + { + CBasePlayerWeapon *gun; + + gun = (CBasePlayerWeapon *)pPlayerItem->GetWeaponPtr(); + + if ( gun && gun->UseDecrement() ) + { + gun->m_flNextPrimaryAttack = max( gun->m_flNextPrimaryAttack - gpGlobals->frametime, -1.0 ); + gun->m_flNextSecondaryAttack = max( gun->m_flNextSecondaryAttack - gpGlobals->frametime, -0.001 ); + + if ( gun->m_flTimeWeaponIdle != 1000 ) + { + gun->m_flTimeWeaponIdle = max( gun->m_flTimeWeaponIdle - gpGlobals->frametime, -0.001 ); + } + } + + pPlayerItem = pPlayerItem->m_pNext; + } + } + } + + m_flNextAttack -= gpGlobals->frametime; + if ( m_flNextAttack < -0.001 ) + m_flNextAttack = -0.001; +} + + +// checks if the spot is clear of players +BOOL IsSpawnPointValid( CBaseEntity *pPlayer, CBaseEntity *pSpot ) +{ + CBaseEntity *ent = NULL; + + if ( !pSpot->IsTriggered( pPlayer ) ) + { + return FALSE; + } + + while ( (ent = UTIL_FindEntityInSphere( ent, pSpot->pev->origin, 96 )) != NULL ) + { + // if ent is a client, don't spawn on 'em + if ( ent->IsPlayer() && ent != pPlayer ) + { + if ( (pPlayer->pev->groupinfo == 0) || (ent->pev->groupinfo & pPlayer->pev->groupinfo) ) + return FALSE; + } + } + + // DISCWAR + // Make sure they're on the right team + if ( InArenaMode() && pSpot->pev->team != 0 && pSpot->pev->team != pPlayer->pev->team ) + return FALSE; + + return TRUE; +} + + +DLL_GLOBAL CBaseEntity *g_pLastSpawn; +inline int FNullEnt( CBaseEntity *ent ) { return (ent == NULL) || FNullEnt( ent->edict() ); } + +/* +============ +EntSelectSpawnPoint + +Returns the entity to spawn at + +USES AND SETS GLOBAL g_pLastSpawn +============ +*/ +edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ) +{ + CBaseEntity *pSpot; + edict_t *player; + + player = pPlayer->edict(); + + EMIT_SOUND( player, CHAN_AUTO, "r_tele1.wav", 1, ATTN_NORM ); + +// choose a info_player_deathmatch point + if (g_pGameRules->IsCoOp()) + { + pSpot = UTIL_FindEntityByClassname( g_pLastSpawn, "info_player_coop"); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + pSpot = UTIL_FindEntityByClassname( g_pLastSpawn, "info_player_start"); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + } + else if ( g_pGameRules->IsDeathmatch() ) + { + pSpot = g_pLastSpawn; + // Randomize the start spot + for ( int i = RANDOM_LONG(1,5); i > 0; i-- ) + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + if ( FNullEnt( pSpot ) ) // skip over the null point + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + + CBaseEntity *pFirstSpot = pSpot; + + do + { + if ( pSpot ) + { + // check if pSpot is valid + if ( IsSpawnPointValid( pPlayer, pSpot ) ) + { + if ( pSpot->pev->origin == Vector( 0, 0, 0 ) ) + { + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + continue; + } + + // if so, go to pSpot + goto ReturnSpot; + } + } + // increment pSpot + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + } while ( pSpot != pFirstSpot ); // loop if we're not back to the start + + // we haven't found a place to spawn yet, so kill any guy at the first spawn point and spawn there + if ( !FNullEnt( pSpot ) ) + { + CBaseEntity *ent = NULL; + while ( (ent = UTIL_FindEntityInSphere( ent, pSpot->pev->origin, 128 )) != NULL ) + { + // if ent is a client, kill em (unless they are ourselves) + if ( ent->IsPlayer() && !(ent->edict() == player) && (ent->pev->groupinfo & player->v.groupinfo) && ((CBasePlayer*)pPlayer)->IsObserver() == false) + ent->TakeDamage( VARS(INDEXENT(0)), VARS(INDEXENT(0)), 300, DMG_GENERIC | DMG_ALWAYSGIB ); + } + goto ReturnSpot; + } + } + + // If startspot is set, (re)spawn there. + if ( FStringNull( gpGlobals->startspot ) || !strlen(STRING(gpGlobals->startspot))) + { + pSpot = UTIL_FindEntityByClassname(NULL, "info_player_start"); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + } + else + { + pSpot = UTIL_FindEntityByTargetname( NULL, STRING(gpGlobals->startspot) ); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + } + +ReturnSpot: + if ( FNullEnt( pSpot ) ) + { + ALERT(at_error, "PutClientInServer: no info_player_start on level"); + return INDEXENT(0); + } + + g_pLastSpawn = pSpot; + return pSpot->edict(); +} + + +void CBasePlayer::Spawn( void ) +{ + pev->classname = MAKE_STRING("player"); + pev->health = 100; + pev->armorvalue = 0; + pev->takedamage = DAMAGE_AIM; + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_WALK; + pev->max_health = pev->health; + pev->flags = FL_CLIENT; + pev->air_finished = gpGlobals->time + 12; + pev->dmg = 2; // initial water damage + pev->effects = 0; + pev->deadflag = DEAD_NO; + pev->dmg_take = 0; + pev->dmg_save = 0; + pev->friction = 1.0; + pev->gravity = 1.0; + m_bitsHUDDamage = -1; + m_bitsDamageType = 0; + m_afPhysicsFlags = 0; + m_fLongJump = FALSE;// no longjump module. + m_flTouchedByJumpPad = 0; + m_flNextAttack = gpGlobals->time + 0.5; // Prevent fire + + g_engfuncs.pfnSetPhysicsKeyValue( edict(), "slj", "0" ); + g_engfuncs.pfnSetPhysicsKeyValue( edict(), "hl", "1" ); + + m_iFOV = 0;// init field of view. + m_iClientFOV = -1; // make sure fov reset is sent + + m_flNextDecalTime = 0;// let this player decal as soon as he spawns. + + m_flgeigerDelay = gpGlobals->time + 2.0; // wait a few seconds until user-defined message registrations + // are recieved by all clients + + m_flTimeStepSound = 0; + m_iStepLeft = 0; + m_flFieldOfView = 0.5;// some monsters use this to determine whether or not the player is looking at them. + + m_bloodColor = BLOOD_COLOR_RED; + m_flNextAttack = UTIL_WeaponTimeBase(); + StartSneaking(); + + m_iFlashBattery = 99; + m_flFlashLightTime = 1; // force first message + +// dont let uninitialized value here hurt the player + m_flFallVelocity = 0; + + g_pGameRules->GetPlayerSpawnSpot( this ); + + SET_MODEL(ENT(pev), "models/player/male/male.mdl"); + g_ulModelIndexPlayer = pev->modelindex; + pev->sequence = LookupActivity( ACT_IDLE ); + + if ( FBitSet(pev->flags, FL_DUCKING) ) + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + else + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + + pev->view_ofs = VEC_VIEW; + Precache(); + m_HackedGunPos = Vector( 0, 32, 0 ); + + if ( m_iPlayerSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_console, "Couldn't alloc player sound slot!\n" ); + } + + m_fNoPlayerSound = FALSE;// normal sound behavior. + + m_pLastItem = NULL; + m_iClientHideHUD = -1; // force this to be recalculated + m_fWeapon = FALSE; + m_pClientActiveItem = NULL; + m_iClientBattery = -1; + + // reset all ammo values to 0 + for ( int i = 0; i < MAX_AMMO_SLOTS; i++ ) + { + m_rgAmmo[i] = 0; + m_rgAmmoLast[i] = 0; // client ammo values also have to be reset (the death hud clear messages does on the client side) + } + + m_lastx = m_lasty = 0; + + g_pGameRules->PlayerSpawn( this ); + + SetBodygroup( 1, 1 ); + ClearFreezeAndRender(); +} + + +void CBasePlayer :: Precache( void ) +{ + // in the event that the player JUST spawned, and the level node graph + // was loaded, fix all of the node graph pointers before the game starts. + + // !!!BUGBUG - now that we have multiplayer, this needs to be moved! + if ( WorldGraph.m_fGraphPresent && !WorldGraph.m_fGraphPointersSet ) + { + if ( !WorldGraph.FSetGraphPointers() ) + { + ALERT ( at_console, "**Graph pointers were not set!\n"); + } + else + { + ALERT ( at_console, "**Graph Pointers Set!\n" ); + } + } + + // SOUNDS / MODELS ARE PRECACHED in ClientPrecache() (game specific) + // because they need to precache before any clients have connected + + // init geiger counter vars during spawn and each time + // we cross a level transition + + m_flgeigerRange = 1000; + m_igeigerRangePrev = 1000; + + m_bitsDamageType = 0; + m_bitsHUDDamage = -1; + + m_iClientBattery = -1; + + m_iTrain = TRAIN_NEW; + + // Make sure any necessary user messages have been registered + LinkUserMessages(); + + m_iUpdateTime = 5; // won't update for 1/2 a second + + if ( gInitHUD ) + m_fInitHUD = TRUE; +} + + +int CBasePlayer::Save( CSave &save ) +{ + if ( !CBaseMonster::Save(save) ) + return 0; + + return save.WriteFields( "PLAYER", this, m_playerSaveData, ARRAYSIZE(m_playerSaveData) ); +} + + +// +// Marks everything as new so the player will resend this to the hud. +// +void CBasePlayer::RenewItems(void) +{ + +} + + +int CBasePlayer::Restore( CRestore &restore ) +{ + if ( !CBaseMonster::Restore(restore) ) + return 0; + + int status = restore.ReadFields( "PLAYER", this, m_playerSaveData, ARRAYSIZE(m_playerSaveData) ); + + SAVERESTOREDATA *pSaveData = (SAVERESTOREDATA *)gpGlobals->pSaveData; + // landmark isn't present. + if ( !pSaveData->fUseLandmark ) + { + ALERT( at_console, "No Landmark:%s\n", pSaveData->szLandmarkName ); + + // default to normal spawn + edict_t* pentSpawnSpot = EntSelectSpawnPoint( this ); + pev->origin = VARS(pentSpawnSpot)->origin + Vector(0,0,1); + pev->angles = VARS(pentSpawnSpot)->angles; + } + pev->v_angle.z = 0; // Clear out roll + pev->angles = pev->v_angle; + + pev->fixangle = TRUE; // turn this way immediately + +// Copied from spawn() for now + m_bloodColor = BLOOD_COLOR_RED; + + g_ulModelIndexPlayer = pev->modelindex; + + if ( FBitSet(pev->flags, FL_DUCKING) ) + { + // Use the crouch HACK + // FixPlayerCrouchStuck( edict() ); + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + } + else + { + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + } + + RenewItems(); + + return status; +} + + + +void CBasePlayer::SelectNextItem( int iItem ) +{ + CBasePlayerItem *pItem; + + pItem = m_rgpPlayerItems[ iItem ]; + + if (!pItem) + return; + + if (pItem == m_pActiveItem) + { + // select the next one in the chain + pItem = m_pActiveItem->m_pNext; + if (! pItem) + { + return; + } + + CBasePlayerItem *pLast; + pLast = pItem; + while (pLast->m_pNext) + pLast = pLast->m_pNext; + + // relink chain + pLast->m_pNext = m_pActiveItem; + m_pActiveItem->m_pNext = NULL; + m_rgpPlayerItems[ iItem ] = pItem; + } + + ResetAutoaim( ); + + // FIX, this needs to queue them up and delay + if (m_pActiveItem) + { + m_pActiveItem->Holster( ); + } + + m_pActiveItem = pItem; + + if (m_pActiveItem) + { + m_pActiveItem->Deploy( ); + m_pActiveItem->UpdateItemInfo( ); + } +} + +void CBasePlayer::SelectItem(const char *pstr) +{ + if (!pstr) + return; + + CBasePlayerItem *pItem = NULL; + + for (int i = 0; i < MAX_ITEM_TYPES; i++) + { + if (m_rgpPlayerItems[i]) + { + pItem = m_rgpPlayerItems[i]; + + while (pItem) + { + if (FClassnameIs(pItem->pev, pstr)) + break; + pItem = pItem->m_pNext; + } + } + + if (pItem) + break; + } + + if (!pItem) + return; + + + if (pItem == m_pActiveItem) + return; + + ResetAutoaim( ); + + // FIX, this needs to queue them up and delay + if (m_pActiveItem) + m_pActiveItem->Holster( ); + + m_pLastItem = m_pActiveItem; + m_pActiveItem = pItem; + + if (m_pActiveItem) + { + m_pActiveItem->Deploy( ); + m_pActiveItem->UpdateItemInfo( ); + } +} + + +void CBasePlayer::SelectLastItem(void) +{ + if (!m_pLastItem) + { + return; + } + + if ( m_pActiveItem && !m_pActiveItem->CanHolster() ) + { + return; + } + + ResetAutoaim( ); + + // FIX, this needs to queue them up and delay + if (m_pActiveItem) + m_pActiveItem->Holster( ); + + CBasePlayerItem *pTemp = m_pActiveItem; + m_pActiveItem = m_pLastItem; + m_pLastItem = pTemp; + m_pActiveItem->Deploy( ); + m_pActiveItem->UpdateItemInfo( ); +} + +//============================================== +// HasWeapons - do I have any weapons at all? +//============================================== +BOOL CBasePlayer::HasWeapons( void ) +{ + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + return TRUE; + } + } + + return FALSE; +} + +void CBasePlayer::SelectPrevItem( int iItem ) +{ +} + + +const char *CBasePlayer::TeamID( void ) +{ + if ( pev == NULL ) // Not fully connected yet + return ""; + + // return their team name + return m_szTeamName; +} + + +//============================================== +// !!!UNDONE:ultra temporary SprayCan entity to apply +// decal frame at a time. For PreAlpha CD +//============================================== +class CSprayCan : public CBaseEntity +{ +public: + void Spawn ( entvars_t *pevOwner ); + void Think( void ); + + virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +}; + +void CSprayCan::Spawn ( entvars_t *pevOwner ) +{ + pev->origin = pevOwner->origin + Vector ( 0 , 0 , 32 ); + pev->angles = pevOwner->v_angle; + pev->owner = ENT(pevOwner); + pev->frame = 0; + + pev->nextthink = gpGlobals->time + 0.1; + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/sprayer.wav", 1, ATTN_NORM); +} + +void CSprayCan::Think( void ) +{ + TraceResult tr; + int playernum; + int nFrames; + CBasePlayer *pPlayer; + + pPlayer = (CBasePlayer *)GET_PRIVATE(pev->owner); + + if (pPlayer) + nFrames = pPlayer->GetCustomDecalFrames(); + else + nFrames = -1; + + playernum = ENTINDEX(pev->owner); + + // ALERT(at_console, "Spray by player %i, %i of %i\n", playernum, (int)(pev->frame + 1), nFrames); + + UTIL_MakeVectors(pev->angles); + UTIL_TraceLine ( pev->origin, pev->origin + gpGlobals->v_forward * 128, ignore_monsters, pev->owner, & tr); + + // No customization present. + if (nFrames == -1) + { + UTIL_DecalTrace( &tr, DECAL_LAMBDA6 ); + UTIL_Remove( this ); + } + else + { + UTIL_PlayerDecalTrace( &tr, playernum, static_cast(pev->frame), TRUE ); + // Just painted last custom frame. + if ( pev->frame++ >= (nFrames - 1)) + UTIL_Remove( this ); + } + + pev->nextthink = gpGlobals->time + 0.1; +} + +class CBloodSplat : public CBaseEntity +{ +public: + void Spawn ( entvars_t *pevOwner ); + void Spray ( void ); +}; + +void CBloodSplat::Spawn ( entvars_t *pevOwner ) +{ + pev->origin = pevOwner->origin + Vector ( 0 , 0 , 32 ); + pev->angles = pevOwner->v_angle; + pev->owner = ENT(pevOwner); + + SetThink ( &CBloodSplat::Spray ); + pev->nextthink = gpGlobals->time + 0.1; +} + +void CBloodSplat::Spray ( void ) +{ + TraceResult tr; + + if ( g_Language != LANGUAGE_GERMAN ) + { + UTIL_MakeVectors(pev->angles); + UTIL_TraceLine ( pev->origin, pev->origin + gpGlobals->v_forward * 128, ignore_monsters, pev->owner, & tr); + + UTIL_BloodDecalTrace( &tr, BLOOD_COLOR_RED ); + } + SetThink ( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; +} + +//============================================== + + + +void CBasePlayer::GiveNamedItem( const char *pszName ) +{ + edict_t *pent; + + int istr = MAKE_STRING(pszName); + + pent = CREATE_NAMED_ENTITY(istr); + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in GiveNamedItem!\n" ); + return; + } + VARS( pent )->origin = pev->origin; + pent->v.spawnflags |= SF_NORESPAWN; + + DispatchSpawn( pent ); + DispatchTouch( pent, ENT( pev ) ); +} + + + +CBaseEntity *FindEntityForward( CBaseEntity *pMe ) +{ + TraceResult tr; + + UTIL_MakeVectors(pMe->pev->v_angle); + UTIL_TraceLine(pMe->pev->origin + pMe->pev->view_ofs,pMe->pev->origin + pMe->pev->view_ofs + gpGlobals->v_forward * 8192,dont_ignore_monsters, pMe->edict(), &tr ); + if ( tr.flFraction != 1.0 && !FNullEnt( tr.pHit) ) + { + CBaseEntity *pHit = CBaseEntity::Instance( tr.pHit ); + return pHit; + } + return NULL; +} + + +BOOL CBasePlayer :: FlashlightIsOn( void ) +{ + return FBitSet(pev->effects, EF_DIMLIGHT); +} + + +void CBasePlayer :: FlashlightTurnOn( void ) +{ + if ( !g_pGameRules->FAllowFlashlight() ) + { + return; + } + + if ( (pev->weapons & (1<effects, EF_DIMLIGHT); + MESSAGE_BEGIN( MSG_ONE, gmsgFlashlight, NULL, pev ); + WRITE_BYTE(1); + WRITE_BYTE(m_iFlashBattery); + MESSAGE_END(); + + m_flFlashLightTime = FLASH_DRAIN_TIME + gpGlobals->time; + + } +} + + +void CBasePlayer :: FlashlightTurnOff( void ) +{ + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, SOUND_FLASHLIGHT_OFF, 1.0, ATTN_NORM, 0, PITCH_NORM ); + ClearBits(pev->effects, EF_DIMLIGHT); + MESSAGE_BEGIN( MSG_ONE, gmsgFlashlight, NULL, pev ); + WRITE_BYTE(0); + WRITE_BYTE(m_iFlashBattery); + MESSAGE_END(); + + m_flFlashLightTime = FLASH_CHARGE_TIME + gpGlobals->time; + +} + +/* +=============== +ForceClientDllUpdate + +When recording a demo, we need to have the server tell us the entire client state +so that the client side .dll can behave correctly. +Reset stuff so that the state is transmitted. +=============== +*/ +void CBasePlayer :: ForceClientDllUpdate( void ) +{ + m_iClientHealth = -1; + m_iClientBattery = -1; + m_iTrain |= TRAIN_NEW; // Force new train message. + m_fWeapon = FALSE; // Force weapon send + m_fKnownItem = FALSE; // Force weaponinit messages. + + // Now force all the necessary messages + // to be sent. + UpdateClientData(); +} + +/* +============ +ImpulseCommands +============ +*/ +extern float g_flWeaponCheat; + +void CBasePlayer::ImpulseCommands( ) +{ + TraceResult tr;// UNDONE: kill me! This is temporary for PreAlpha CDs + + // Handle use events + PlayerUse(); + + int iImpulse = (int)pev->impulse; + 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() ) + { + FlashlightTurnOff(); + } + else + { + FlashlightTurnOn(); + } + break; + + case 201:// paint decal + + if ( gpGlobals->time < m_flNextDecalTime ) + { + // too early! + break; + } + + UTIL_MakeVectors(pev->v_angle); + UTIL_TraceLine ( pev->origin + pev->view_ofs, pev->origin + pev->view_ofs + gpGlobals->v_forward * 128, ignore_monsters, ENT(pev), & tr); + + if ( tr.flFraction != 1.0 ) + {// line hit something, so paint a decal + m_flNextDecalTime = gpGlobals->time + CVAR_GET_FLOAT("decalfrequency"); + CSprayCan *pCan = GetClassPtr((CSprayCan *)NULL); + pCan->Spawn( pev ); + } + + break; + case 204: // Demo recording, update client dll specific data again. + ForceClientDllUpdate(); + break; + default: + // check all of the cheat impulse commands now + CheatImpulseCommands( iImpulse ); + break; + } + + pev->impulse = 0; +} + +//========================================================= +//========================================================= +void CBasePlayer::CheatImpulseCommands( int iImpulse ) +{ +#if !defined( HLDEMO_BUILD ) + if ( g_flWeaponCheat == 0.0 ) + { + return; + } + + CBaseEntity *pEntity; + TraceResult tr; + + switch ( iImpulse ) + { + case 76: + { + if (!giPrecacheGrunt) + { + giPrecacheGrunt = 1; + ALERT(at_console, "You must now restart to use Grunt-o-matic.\n"); + } + else + { + UTIL_MakeVectors( Vector( 0, pev->v_angle.y, 0 ) ); + Create("monster_human_grunt", pev->origin + gpGlobals->v_forward * 128, pev->angles); + } + break; + } + + + case 101: + gEvilImpulse101 = TRUE; + 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; + + case 102: + // Gibbage!!! + CGib::SpawnRandomGibs( pev, 1, 1 ); + break; + + case 103: + // What the hell are you doing? + pEntity = FindEntityForward( this ); + if ( pEntity ) + { + CBaseMonster *pMonster = pEntity->MyMonsterPointer(); + if ( pMonster ) + pMonster->ReportAIState(); + } + break; + + case 104: + // Dump all of the global state varaibles (and global entity names) + gGlobalState.DumpGlobals(); + break; + + case 105:// player makes no sound for monsters to hear. + { + if ( m_fNoPlayerSound ) + { + ALERT ( at_console, "Player is audible\n" ); + m_fNoPlayerSound = FALSE; + } + else + { + ALERT ( at_console, "Player is silent\n" ); + m_fNoPlayerSound = TRUE; + } + break; + } + + case 106: + // Give me the classname and targetname of this entity. + pEntity = FindEntityForward( this ); + if ( pEntity ) + { + ALERT ( at_console, "Classname: %s", STRING( pEntity->pev->classname ) ); + + if ( !FStringNull ( pEntity->pev->targetname ) ) + { + ALERT ( at_console, " - Targetname: %s\n", STRING( pEntity->pev->targetname ) ); + } + else + { + ALERT ( at_console, " - TargetName: No Targetname\n" ); + } + + ALERT ( at_console, "Model: %s\n", STRING( pEntity->pev->model ) ); + if ( pEntity->pev->globalname ) + ALERT ( at_console, "Globalname: %s\n", STRING( pEntity->pev->globalname ) ); + } + break; + + case 107: + { + TraceResult tr; + + edict_t *pWorld = g_engfuncs.pfnPEntityOfEntIndex( 0 ); + + Vector start = pev->origin + pev->view_ofs; + Vector end = start + gpGlobals->v_forward * 1024; + UTIL_TraceLine( start, end, ignore_monsters, edict(), &tr ); + if ( tr.pHit ) + pWorld = tr.pHit; + const char *pTextureName = TRACE_TEXTURE( pWorld, start, end ); + if ( pTextureName ) + ALERT( at_console, "Texture: %s\n", pTextureName ); + } + break; + case 195:// show shortest paths for entire level to nearest node + { + Create("node_viewer_fly", pev->origin, pev->angles); + } + break; + case 196:// show shortest paths for entire level to nearest node + { + Create("node_viewer_large", pev->origin, pev->angles); + } + break; + case 197:// show shortest paths for entire level to nearest node + { + Create("node_viewer_human", pev->origin, pev->angles); + } + break; + case 199:// show nearest node and all connections + { + ALERT ( at_console, "%d\n", WorldGraph.FindNearestNode ( pev->origin, bits_NODE_GROUP_REALM ) ); + WorldGraph.ShowNodeConnections ( WorldGraph.FindNearestNode ( pev->origin, bits_NODE_GROUP_REALM ) ); + } + break; + case 202:// Random blood splatter + UTIL_MakeVectors(pev->v_angle); + UTIL_TraceLine ( pev->origin + pev->view_ofs, pev->origin + pev->view_ofs + gpGlobals->v_forward * 128, ignore_monsters, ENT(pev), & tr); + + if ( tr.flFraction != 1.0 ) + {// line hit something, so paint a decal + CBloodSplat *pBlood = GetClassPtr((CBloodSplat *)NULL); + pBlood->Spawn( pev ); + } + break; + case 203:// remove creature. + pEntity = FindEntityForward( this ); + if ( pEntity ) + { + if ( pEntity->pev->takedamage ) + pEntity->SetThink(&CBaseEntity::SUB_Remove); + } + break; + } +#endif // HLDEMO_BUILD +} + +// +// Add a weapon to the player (Item == Weapon == Selectable Object) +// +int CBasePlayer::AddPlayerItem( CBasePlayerItem *pItem ) +{ + CBasePlayerItem *pInsert; + + pInsert = m_rgpPlayerItems[pItem->iItemSlot()]; + + while (pInsert) + { + if (FClassnameIs( pInsert->pev, STRING( pItem->pev->classname) )) + { + if (pItem->AddDuplicate( pInsert )) + { + g_pGameRules->PlayerGotWeapon ( this, pItem ); + pItem->CheckRespawn(); + + // ugly hack to update clip w/o an update clip message + pInsert->UpdateItemInfo( ); + if (m_pActiveItem) + m_pActiveItem->UpdateItemInfo( ); + + pItem->Kill( ); + } + else if (gEvilImpulse101) + { + // FIXME: remove anyway for deathmatch testing + pItem->Kill( ); + } + return FALSE; + } + pInsert = pInsert->m_pNext; + } + + + if (pItem->AddToPlayer( this )) + { + g_pGameRules->PlayerGotWeapon ( this, pItem ); + pItem->CheckRespawn(); + + pItem->m_pNext = m_rgpPlayerItems[pItem->iItemSlot()]; + m_rgpPlayerItems[pItem->iItemSlot()] = pItem; + + // should we switch to this item? + if ( g_pGameRules->FShouldSwitchWeapon( this, pItem ) ) + { + SwitchWeapon( pItem ); + } + + return TRUE; + } + else if (gEvilImpulse101) + { + // FIXME: remove anyway for deathmatch testing + pItem->Kill( ); + } + return FALSE; +} + + + +int CBasePlayer::RemovePlayerItem( CBasePlayerItem *pItem ) +{ + if (m_pActiveItem == pItem) + { + ResetAutoaim( ); + pItem->Holster( ); + pItem->pev->nextthink = 0;// crowbar may be trying to swing again, etc. + pItem->SetThink( NULL ); + m_pActiveItem = NULL; + pev->viewmodel = 0; + pev->weaponmodel = 0; + } + else if ( m_pLastItem == pItem ) + m_pLastItem = NULL; + + CBasePlayerItem *pPrev = m_rgpPlayerItems[pItem->iItemSlot()]; + + if (pPrev == pItem) + { + m_rgpPlayerItems[pItem->iItemSlot()] = pItem->m_pNext; + return TRUE; + } + else + { + while (pPrev && pPrev->m_pNext != pItem) + { + pPrev = pPrev->m_pNext; + } + if (pPrev) + { + pPrev->m_pNext = pItem->m_pNext; + return TRUE; + } + } + return FALSE; +} + + +// +// Returns the unique ID for the ammo, or -1 if error +// +int CBasePlayer :: GiveAmmo( int iCount, char *szName, int iMax ) +{ + if ( !szName ) + { + // no ammo. + return -1; + } + + if ( !g_pGameRules->CanHaveAmmo( this, szName, iMax ) ) + { + // game rules say I can't have any more of this ammo type. + return -1; + } + + int i = 0; + + i = GetAmmoIndex( szName ); + + if ( i < 0 || i >= MAX_AMMO_SLOTS ) + return -1; + + int iAdd = min( iCount, iMax - m_rgAmmo[i] ); + if ( iAdd < 1 ) + return i; + + m_rgAmmo[ i ] += iAdd; + + + if ( gmsgAmmoPickup ) // make sure the ammo messages have been linked first + { + // Send the message that ammo has been picked up + MESSAGE_BEGIN( MSG_ONE, gmsgAmmoPickup, NULL, pev ); + WRITE_BYTE( GetAmmoIndex(szName) ); // ammo ID + WRITE_BYTE( iAdd ); // amount + MESSAGE_END(); + } + + return i; +} + + +/* +============ +ItemPreFrame + +Called every frame by the player PreThink +============ +*/ +void CBasePlayer::ItemPreFrame() +{ + if ( m_flNextAttack > 0 ) + { + return; + } + + if (!m_pActiveItem) + return; + + m_pActiveItem->ItemPreFrame( ); +} + + +/* +============ +ItemPostFrame + +Called every frame by the player PostThink +============ +*/ +void CBasePlayer::ItemPostFrame() +{ + // check if the player is using a tank + if ( m_pTank != 0 ) + return; + + if ( m_flNextAttack > 0 ) + { + return; + } + + ImpulseCommands(); + + if (!m_pActiveItem) + return; + + m_pActiveItem->ItemPostFrame( ); +} + +int CBasePlayer::AmmoInventory( int iAmmoIndex ) +{ + if (iAmmoIndex == -1) + { + return -1; + } + + return m_rgAmmo[ iAmmoIndex ]; +} + +int CBasePlayer::GetAmmoIndex(const char *psz) +{ + int i; + + if (!psz) + return -1; + + for (i = 1; i < MAX_AMMO_SLOTS; i++) + { + if ( !CBasePlayerItem::AmmoInfoArray[i].pszName ) + continue; + + if (stricmp( psz, CBasePlayerItem::AmmoInfoArray[i].pszName ) == 0) + return i; + } + + return -1; +} + +// Called from UpdateClientData +// makes sure the client has all the necessary ammo info, if values have changed +void CBasePlayer::SendAmmoUpdate(void) +{ + for (int i=0; i < MAX_AMMO_SLOTS;i++) + { + if (m_rgAmmo[i] != m_rgAmmoLast[i]) + { + m_rgAmmoLast[i] = m_rgAmmo[i]; + + ASSERT( m_rgAmmo[i] >= 0 ); + ASSERT( m_rgAmmo[i] < 255 ); + + // send "Ammo" update message + MESSAGE_BEGIN( MSG_ONE, gmsgAmmoX, NULL, pev ); + WRITE_BYTE( i ); + WRITE_BYTE( max( min( m_rgAmmo[i], 254 ), 0 ) ); // clamp the value to one byte + MESSAGE_END(); + } + } +} + +/* +========================================================= + UpdateClientData + +resends any changed player HUD info to the client. +Called every frame by PlayerPreThink +Also called at start of demo recording and playback by +ForceClientDllUpdate to ensure the demo gets messages +reflecting all of the HUD state info. +========================================================= +*/ +void CBasePlayer :: UpdateClientData( void ) +{ + if (m_fInitHUD) + { + m_fInitHUD = FALSE; + gInitHUD = FALSE; + m_flSendArenaStatus = gpGlobals->time = 1.0f; + + MESSAGE_BEGIN( MSG_ONE, gmsgResetHUD, NULL, pev ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + + if ( !m_fGameHUDInitialized ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgInitHUD, NULL, pev ); + MESSAGE_END(); + + g_pGameRules->InitHUD( this ); + m_fGameHUDInitialized = TRUE; + if ( g_pGameRules->IsMultiplayer() ) + { + FireTargets( "game_playerjoin", this, this, USE_TOGGLE, 0 ); + } + } + + FireTargets( "game_playerspawn", this, this, USE_TOGGLE, 0 ); + } + + if ( m_iHideHUD != m_iClientHideHUD ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgHideWeapon, NULL, pev ); + WRITE_BYTE( m_iHideHUD ); + MESSAGE_END(); + + m_iClientHideHUD = m_iHideHUD; + } + + if ( m_iFOV != m_iClientFOV ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgSetFOV, NULL, pev ); + WRITE_BYTE( m_iFOV ); + MESSAGE_END(); + + // cache FOV change at end of function, so weapon updates can see that FOV has changed + } + + // HACKHACK -- send the message to display the game title + if (gDisplayTitle) + { + MESSAGE_BEGIN( MSG_ONE, gmsgShowGameTitle, NULL, pev ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + gDisplayTitle = 0; + } + + if (pev->health != m_iClientHealth) + { + int iHealth = max( static_cast(pev->health), 0 ); // make sure that no negative health values are sent + + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgHealth, NULL, pev ); + WRITE_BYTE( iHealth ); + MESSAGE_END(); + + m_iClientHealth = static_cast(pev->health); + } + + + if (pev->armorvalue != m_iClientBattery) + { + m_iClientBattery = static_cast(pev->armorvalue); + + ASSERT( gmsgBattery > 0 ); + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgBattery, NULL, pev ); + WRITE_SHORT( (int)pev->armorvalue); + MESSAGE_END(); + } + + if (pev->dmg_take || pev->dmg_save || m_bitsHUDDamage != m_bitsDamageType) + { + // Comes from inside me if not set + Vector damageOrigin = pev->origin; + // send "damage" message + // causes screen to flash, and pain compass to show direction of damage + edict_t *other = pev->dmg_inflictor; + if ( other ) + { + CBaseEntity *pEntity = CBaseEntity::Instance(other); + if ( pEntity ) + damageOrigin = pEntity->Center(); + } + + // only send down damage type that have hud art + int visibleDamageBits = m_bitsDamageType & DMG_SHOWNHUD; + + MESSAGE_BEGIN( MSG_ONE, gmsgDamage, NULL, pev ); + WRITE_BYTE( static_cast(pev->dmg_save) ); + WRITE_BYTE( static_cast(pev->dmg_take) ); + WRITE_LONG( visibleDamageBits ); + WRITE_COORD( damageOrigin.x ); + WRITE_COORD( damageOrigin.y ); + WRITE_COORD( damageOrigin.z ); + MESSAGE_END(); + + pev->dmg_take = 0; + pev->dmg_save = 0; + m_bitsHUDDamage = m_bitsDamageType; + + // Clear off non-time-based damage indicators + m_bitsDamageType &= DMG_TIMEBASED; + } + + // Update Flashlight + if ((m_flFlashLightTime) && (m_flFlashLightTime <= gpGlobals->time)) + { + if (FlashlightIsOn()) + { + if (m_iFlashBattery) + { + m_flFlashLightTime = FLASH_DRAIN_TIME + gpGlobals->time; + m_iFlashBattery--; + + if (!m_iFlashBattery) + FlashlightTurnOff(); + } + } + else + { + if (m_iFlashBattery < 100) + { + m_flFlashLightTime = FLASH_CHARGE_TIME + gpGlobals->time; + m_iFlashBattery++; + } + else + m_flFlashLightTime = 0; + } + + MESSAGE_BEGIN( MSG_ONE, gmsgFlashBattery, NULL, pev ); + WRITE_BYTE(m_iFlashBattery); + MESSAGE_END(); + } + + + if (m_iTrain & TRAIN_NEW) + { + ASSERT( gmsgTrain > 0 ); + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgTrain, NULL, pev ); + WRITE_BYTE(m_iTrain & 0xF); + MESSAGE_END(); + + m_iTrain &= ~TRAIN_NEW; + } + + // + // New Weapon? + // + if (!m_fKnownItem) + { + m_fKnownItem = TRUE; + + // WeaponInit Message + // byte = # of weapons + // + // for each weapon: + // byte name str length (not including null) + // bytes... name + // byte Ammo Type + // byte Ammo2 Type + // byte bucket + // byte bucket pos + // byte flags + // ???? Icons + + // Send ALL the weapon info now + int i; + + for (i = 0; i < MAX_WEAPONS; i++) + { + ItemInfo& II = CBasePlayerItem::ItemInfoArray[i]; + + if ( !II.iId ) + continue; + + const char *pszName; + if (!II.pszName) + pszName = "Empty"; + else + pszName = II.pszName; + + MESSAGE_BEGIN( MSG_ONE, gmsgWeaponList, NULL, pev ); + WRITE_STRING(pszName); // string weapon name + WRITE_BYTE(GetAmmoIndex(II.pszAmmo1)); // byte Ammo Type + WRITE_BYTE(II.iMaxAmmo1); // byte Max Ammo 1 + WRITE_BYTE(GetAmmoIndex(II.pszAmmo2)); // byte Ammo2 Type + WRITE_BYTE(II.iMaxAmmo2); // byte Max Ammo 2 + WRITE_BYTE(II.iSlot); // byte bucket + WRITE_BYTE(II.iPosition); // byte bucket pos + WRITE_BYTE(II.iId); // byte id (bit index into pev->weapons) + WRITE_BYTE(II.iFlags); // byte Flags + MESSAGE_END(); + } + } + + + SendAmmoUpdate(); + + // Update all the items + for ( int i = 0; i < MAX_ITEM_TYPES; i++ ) + { + if ( m_rgpPlayerItems[i] ) // each item updates it's successors + m_rgpPlayerItems[i]->UpdateClientData( this ); + } + + // Cache and client weapon change + m_pClientActiveItem = m_pActiveItem; + m_iClientFOV = m_iFOV; + + if ( (m_iClientFrags != pev->frags) || (m_iClientDeaths != m_iDeaths) || (m_iClientPlayerClass != pev->playerclass) || (m_iClientTeam != pev->team) ) + { + // update all players with this info, for their scoreboards + MESSAGE_BEGIN( MSG_ALL, gmsgScoreInfo ); + WRITE_BYTE( ENTINDEX(edict()) ); + WRITE_SHORT( static_cast(pev->frags) ); + WRITE_SHORT( m_iDeaths ); + WRITE_SHORT( pev->playerclass ); + WRITE_SHORT( pev->team ); + MESSAGE_END(); + + m_iClientPlayerClass = pev->playerclass; + m_iClientTeam = pev->team; + m_iClientDeaths = m_iDeaths; + m_iClientFrags = static_cast(pev->frags); + } + + // Update Freeze + if ( m_iFrozen != m_iClientFrozen ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgFrozen, NULL, pev ); + WRITE_BYTE( m_iFrozen ); + MESSAGE_END(); + + m_iClientFrozen = m_iFrozen; + } +} + + +//========================================================= +// FBecomeProne - Overridden for the player to set the proper +// physics flags when a barnacle grabs player. +//========================================================= +BOOL CBasePlayer :: FBecomeProne ( void ) +{ + m_afPhysicsFlags |= PFLAG_ONBARNACLE; + return TRUE; +} + +//========================================================= +// BarnacleVictimBitten - bad name for a function that is called +// by Barnacle victims when the barnacle pulls their head +// into its mouth. For the player, just die. +//========================================================= +void CBasePlayer :: BarnacleVictimBitten ( entvars_t *pevBarnacle ) +{ + TakeDamage ( pevBarnacle, pevBarnacle, pev->health + pev->armorvalue, DMG_SLASH | DMG_ALWAYSGIB ); +} + +//========================================================= +// BarnacleVictimReleased - overridden for player who has +// physics flags concerns. +//========================================================= +void CBasePlayer :: BarnacleVictimReleased ( void ) +{ + m_afPhysicsFlags &= ~PFLAG_ONBARNACLE; +} + + +//========================================================= +// Illumination +// return player light level plus virtual muzzle flash +//========================================================= +int CBasePlayer :: Illumination( void ) +{ + int iIllum = CBaseEntity::Illumination( ); + + iIllum += m_iWeaponFlash; + if (iIllum > 255) + return 255; + return iIllum; +} + + +void CBasePlayer :: EnableControl(BOOL fControl) +{ + if (!fControl) + pev->flags |= FL_FROZEN; + else + pev->flags &= ~FL_FROZEN; + +} + + +#define DOT_1DEGREE 0.9998476951564 +#define DOT_2DEGREE 0.9993908270191 +#define DOT_3DEGREE 0.9986295347546 +#define DOT_4DEGREE 0.9975640502598 +#define DOT_5DEGREE 0.9961946980917 +#define DOT_6DEGREE 0.9945218953683 +#define DOT_7DEGREE 0.9925461516413 +#define DOT_8DEGREE 0.9902680687416 +#define DOT_9DEGREE 0.9876883405951 +#define DOT_10DEGREE 0.9848077530122 +#define DOT_15DEGREE 0.9659258262891 +#define DOT_20DEGREE 0.9396926207859 +#define DOT_25DEGREE 0.9063077870367 + +//========================================================= +// Autoaim +// set crosshair position to point to enemey +//========================================================= +Vector CBasePlayer :: GetAutoaimVector( float flDelta ) +{ + if (g_iSkillLevel == SKILL_HARD) + { + UTIL_MakeVectors( pev->v_angle + pev->punchangle ); + return gpGlobals->v_forward; + } + + Vector vecSrc = GetGunPosition( ); + float flDist = 8192; + + // always use non-sticky autoaim + // UNDONE: use sever variable to chose! + if (1 || g_iSkillLevel == SKILL_MEDIUM) + { + m_vecAutoAim = Vector( 0, 0, 0 ); + // flDelta *= 0.5; + } + + BOOL m_fOldTargeting = m_fOnTarget; + Vector angles = AutoaimDeflection(vecSrc, flDist, flDelta ); + + // update ontarget if changed + if ( !g_pGameRules->AllowAutoTargetCrosshair() ) + m_fOnTarget = 0; + else if (m_fOldTargeting != m_fOnTarget) + { + m_pActiveItem->UpdateItemInfo( ); + } + + if (angles.x > 180) + angles.x -= 360; + if (angles.x < -180) + angles.x += 360; + if (angles.y > 180) + angles.y -= 360; + if (angles.y < -180) + angles.y += 360; + + if (angles.x > 25) + angles.x = 25; + if (angles.x < -25) + angles.x = -25; + if (angles.y > 12) + angles.y = 12; + if (angles.y < -12) + angles.y = -12; + + + // always use non-sticky autoaim + // UNDONE: use sever variable to chose! + if (0 || g_iSkillLevel == SKILL_EASY) + { + m_vecAutoAim = m_vecAutoAim * 0.67 + angles * 0.33; + } + else + { + m_vecAutoAim = angles * 0.9; + } + + // m_vecAutoAim = m_vecAutoAim * 0.99; + + // Don't send across network if sv_aim is 0 + if ( CVAR_GET_FLOAT( "sv_aim" ) != 0 ) + { + if ( m_vecAutoAim.x != m_lastx || + m_vecAutoAim.y != m_lasty ) + { + SET_CROSSHAIRANGLE( edict(), -m_vecAutoAim.x, m_vecAutoAim.y ); + + m_lastx = static_cast(m_vecAutoAim.x); + m_lasty = static_cast(m_vecAutoAim.y); + } + } + + // ALERT( at_console, "%f %f\n", angles.x, angles.y ); + + UTIL_MakeVectors( pev->v_angle + pev->punchangle + m_vecAutoAim ); + return gpGlobals->v_forward; +} + + +Vector CBasePlayer :: AutoaimDeflection( Vector &vecSrc, float flDist, float flDelta ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + float bestdot; + Vector bestdir; + edict_t *bestent; + TraceResult tr; + + if ( CVAR_GET_FLOAT("sv_aim") == 0 ) + { + m_fOnTarget = FALSE; + return g_vecZero; + } + + UTIL_MakeVectors( pev->v_angle + pev->punchangle + m_vecAutoAim ); + + // try all possible entities + bestdir = gpGlobals->v_forward; + bestdot = flDelta; // +- 10 degrees + bestent = NULL; + + m_fOnTarget = FALSE; + + UTIL_TraceLine( vecSrc, vecSrc + bestdir * flDist, dont_ignore_monsters, edict(), &tr ); + + + if ( tr.pHit && tr.pHit->v.takedamage != DAMAGE_NO) + { + // don't look through water + if (!((pev->waterlevel != 3 && tr.pHit->v.waterlevel == 3) + || (pev->waterlevel == 3 && tr.pHit->v.waterlevel == 0))) + { + if (tr.pHit->v.takedamage == DAMAGE_AIM) + m_fOnTarget = TRUE; + + return m_vecAutoAim; + } + } + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + Vector center; + Vector dir; + float dot; + + if ( pEdict->free ) // Not in use + continue; + + if (pEdict->v.takedamage != DAMAGE_AIM) + continue; + if (pEdict == edict()) + continue; +// if (pev->team > 0 && pEdict->v.team == pev->team) +// continue; // don't aim at teammate + if ( !g_pGameRules->ShouldAutoAim( this, pEdict ) ) + continue; + + pEntity = Instance( pEdict ); + if (pEntity == NULL) + continue; + + if (!pEntity->IsAlive()) + continue; + + // don't look through water + if ((pev->waterlevel != 3 && pEntity->pev->waterlevel == 3) + || (pev->waterlevel == 3 && pEntity->pev->waterlevel == 0)) + continue; + + center = pEntity->BodyTarget( vecSrc ); + + dir = (center - vecSrc).Normalize( ); + + // make sure it's in front of the player + if (DotProduct (dir, gpGlobals->v_forward ) < 0) + continue; + + dot = fabs( DotProduct (dir, gpGlobals->v_right ) ) + + fabs( DotProduct (dir, gpGlobals->v_up ) ) * 0.5; + + // tweek for distance + dot *= 1.0 + 0.2 * ((center - vecSrc).Length() / flDist); + + if (dot > bestdot) + continue; // to far to turn + + UTIL_TraceLine( vecSrc, center, dont_ignore_monsters, edict(), &tr ); + if (tr.flFraction != 1.0 && tr.pHit != pEdict) + { + // ALERT( at_console, "hit %s, can't see %s\n", STRING( tr.pHit->v.classname ), STRING( pEdict->v.classname ) ); + continue; + } + + // don't shoot at friends + if (IRelationship( pEntity ) < 0) + { + if ( !pEntity->IsPlayer() && !g_pGameRules->IsDeathmatch()) + // ALERT( at_console, "friend\n"); + continue; + } + + // can shoot at this one + bestdot = dot; + bestent = pEdict; + bestdir = dir; + } + + if (bestent) + { + bestdir = UTIL_VecToAngles (bestdir); + bestdir.x = -bestdir.x; + bestdir = bestdir - pev->v_angle - pev->punchangle; + + if (bestent->v.takedamage == DAMAGE_AIM) + m_fOnTarget = TRUE; + + return bestdir; + } + + return Vector( 0, 0, 0 ); +} + + +void CBasePlayer :: ResetAutoaim( ) +{ + if (m_vecAutoAim.x != 0 || m_vecAutoAim.y != 0) + { + m_vecAutoAim = Vector( 0, 0, 0 ); + SET_CROSSHAIRANGLE( edict(), 0, 0 ); + } + m_fOnTarget = FALSE; +} + +/* +============= +SetCustomDecalFrames + + UNDONE: Determine real frame limit, 8 is a placeholder. + Note: -1 means no custom frames present. +============= +*/ +void CBasePlayer :: SetCustomDecalFrames( int nFrames ) +{ + if (nFrames > 0 && + nFrames < 8) + m_nCustomSprayFrames = nFrames; + else + m_nCustomSprayFrames = -1; +} + +/* +============= +GetCustomDecalFrames + + Returns the # of custom frames this player's custom clan logo contains. +============= +*/ +int CBasePlayer :: GetCustomDecalFrames( void ) +{ + return m_nCustomSprayFrames; +} + + +//========================================================= +// DropPlayerItem - drop the named item, or if no name, +// the active item. +//========================================================= +void CBasePlayer::DropPlayerItem ( char *pszItemName ) +{ + // no dropping in disc war + return; +} + +//========================================================= +// HasPlayerItem Does the player already have this item? +//========================================================= +BOOL CBasePlayer::HasPlayerItem( CBasePlayerItem *pCheckItem ) +{ + CBasePlayerItem *pItem = m_rgpPlayerItems[pCheckItem->iItemSlot()]; + + while (pItem) + { + if (FClassnameIs( pItem->pev, STRING( pCheckItem->pev->classname) )) + { + return TRUE; + } + pItem = pItem->m_pNext; + } + + return FALSE; +} + +//========================================================= +// HasNamedPlayerItem Does the player already have this item? +//========================================================= +BOOL CBasePlayer::HasNamedPlayerItem( const char *pszItemName ) +{ + CBasePlayerItem *pItem; + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + pItem = m_rgpPlayerItems[ i ]; + + while (pItem) + { + if ( !strcmp( pszItemName, STRING( pItem->pev->classname ) ) ) + { + return TRUE; + } + pItem = pItem->m_pNext; + } + } + + return FALSE; +} + +//========================================================= +// +//========================================================= +BOOL CBasePlayer :: SwitchWeapon( CBasePlayerItem *pWeapon ) +{ + if ( !pWeapon->CanDeploy() ) + { + return FALSE; + } + + ResetAutoaim( ); + + if (m_pActiveItem) + { + m_pActiveItem->Holster( ); + } + + m_pActiveItem = pWeapon; + pWeapon->Deploy( ); + + return TRUE; +} + +//========================================================= +// Dead HEV suit prop +//========================================================= +class CDeadHEV : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_HUMAN_MILITARY; } + + void KeyValue( KeyValueData *pkvd ); + + int m_iPose;// which sequence to display -- temporary, don't need to save + static char *m_szPoses[4]; +}; + +char *CDeadHEV::m_szPoses[] = { "deadback", "deadsitting", "deadstomach", "deadtable" }; + +void CDeadHEV::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "pose")) + { + m_iPose = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +LINK_ENTITY_TO_CLASS( monster_hevsuit_dead, CDeadHEV ); + +//========================================================= +// ********** DeadHEV SPAWN ********** +//========================================================= +void CDeadHEV :: Spawn( void ) +{ + PRECACHE_MODEL("models/player.mdl"); + SET_MODEL(ENT(pev), "models/player.mdl"); + + pev->effects = 0; + pev->yaw_speed = 8; + pev->sequence = 0; + pev->body = 1; + m_bloodColor = BLOOD_COLOR_RED; + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + + if (pev->sequence == -1) + { + ALERT ( at_console, "Dead hevsuit with bad pose\n" ); + pev->sequence = 0; + pev->effects = EF_BRIGHTFIELD; + } + + // Corpses have less health + pev->health = 8; + + MonsterInitDead(); +} + + +class CStripWeapons : public CPointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +private: +}; + +LINK_ENTITY_TO_CLASS( player_weaponstrip, CStripWeapons ); + +void CStripWeapons :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBasePlayer *pPlayer = NULL; + + if ( pActivator && pActivator->IsPlayer() ) + { + pPlayer = (CBasePlayer *)pActivator; + } + else if ( !g_pGameRules->IsDeathmatch() ) + { + pPlayer = (CBasePlayer *)CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) ); + } + + if ( pPlayer ) + pPlayer->RemoveAllItems( FALSE ); +} + + +class CRevertSaved : public CPointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT MessageThink( void ); + void EXPORT LoadThink( void ); + void KeyValue( KeyValueData *pkvd ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + inline float Duration( void ) { return pev->dmg_take; } + inline float HoldTime( void ) { return pev->dmg_save; } + inline float MessageTime( void ) { return m_messageTime; } + inline float LoadTime( void ) { return m_loadTime; } + + inline void SetDuration( float duration ) { pev->dmg_take = duration; } + inline void SetHoldTime( float hold ) { pev->dmg_save = hold; } + inline void SetMessageTime( float time ) { m_messageTime = time; } + inline void SetLoadTime( float time ) { m_loadTime = time; } + +private: + float m_messageTime; + float m_loadTime; +}; + +LINK_ENTITY_TO_CLASS( player_loadsaved, CRevertSaved ); + +TYPEDESCRIPTION CRevertSaved::m_SaveData[] = +{ + DEFINE_FIELD( CRevertSaved, m_messageTime, FIELD_FLOAT ), // These are not actual times, but durations, so save as floats + DEFINE_FIELD( CRevertSaved, m_loadTime, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CRevertSaved, CPointEntity ); + +void CRevertSaved :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "duration")) + { + SetDuration( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "holdtime")) + { + SetHoldTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "messagetime")) + { + SetMessageTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "loadtime")) + { + SetLoadTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CRevertSaved :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + UTIL_ScreenFadeAll( pev->rendercolor, Duration(), HoldTime(), static_cast(pev->renderamt), FFADE_OUT ); + pev->nextthink = gpGlobals->time + MessageTime(); + SetThink( &CRevertSaved::MessageThink ); +} + + +void CRevertSaved :: MessageThink( void ) +{ + UTIL_ShowMessageAll( STRING(pev->message) ); + float nextThink = LoadTime() - MessageTime(); + if ( nextThink > 0 ) + { + pev->nextthink = gpGlobals->time + nextThink; + SetThink( &CRevertSaved::LoadThink ); + } + else + LoadThink(); +} + + +void CRevertSaved :: LoadThink( void ) +{ + if ( !gpGlobals->deathmatch ) + { + SERVER_COMMAND("reload\n"); + } +} + + +//========================================================= +// Multiplayer intermission spots. +//========================================================= +class CInfoIntermission:public CPointEntity +{ + void Spawn( void ); + void Think( void ); +}; + +void CInfoIntermission::Spawn( void ) +{ + UTIL_SetOrigin( pev, pev->origin ); + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; + pev->v_angle = g_vecZero; + + pev->nextthink = gpGlobals->time + 2;// let targets spawn! + +} + +void CInfoIntermission::Think ( void ) +{ + edict_t *pTarget; + + // find my target + pTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) ); + + if ( !FNullEnt(pTarget) ) + { + pev->v_angle = UTIL_VecToAngles( (pTarget->v.origin - pev->origin).Normalize() ); + pev->v_angle.x = -pev->v_angle.x; + } +} + +LINK_ENTITY_TO_CLASS( info_intermission, CInfoIntermission ); + + +//========================================================= +// Freeze +//========================================================= +void CBasePlayer::Freeze( void ) +{ + // Glow blue + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor.z = 200; + pev->renderamt = 25; + + pev->maxspeed = FREEZE_SPEED; + m_iFrozen = 1; + + m_flFreezeTime = gpGlobals->time + FREEZE_TIME; +} + +void CBasePlayer::ClearFreezeAndRender() +{ + pev->renderfx = kRenderFxNone; + pev->rendercolor = g_vecZero; + pev->renderamt = 0; + pev->nextthink = 0; + + m_iFrozen = 0; + + if ( IsObserver() ) + pev->maxspeed = 1; + else + pev->maxspeed = 320; +} + +// Force a client to hear a specific VOX sentence. +// This should eventually be moved into the engine. +void CBasePlayer::ClientHearVox( const char *pSentence ) +{ + MESSAGE_BEGIN( MSG_ONE, SVC_STUFFTEXT, NULL, pev ); + + // If it's a sentence, don't put quotes around it + if (pSentence[0] == '#') + { + WRITE_STRING( UTIL_VarArgs("spk %s\n", pSentence ) ); + } + else + { + WRITE_STRING( UTIL_VarArgs("spk \"%s\"\n", pSentence ) ); + } + + MESSAGE_END(); +} diff --git a/hlsdk/ricochet/dlls/player.h b/hlsdk/ricochet/dlls/player.h new file mode 100644 index 00000000..67bd212c --- /dev/null +++ b/hlsdk/ricochet/dlls/player.h @@ -0,0 +1,356 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PLAYER_H +#define PLAYER_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_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. + +// +// Player PHYSICS FLAGS bits +// +#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. + +// +// 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" + +#define TEAM_NAME_LENGTH 16 + +typedef enum +{ + PLAYER_IDLE, + PLAYER_WALK, + PLAYER_JUMP, + PLAYER_SUPERJUMP, + PLAYER_DIE, + PLAYER_ATTACK1, + PLAYER_FALL, +} PLAYER_ANIM; + +class CDiscArena; + +class CBasePlayer : public CBaseMonster +{ +public: + int random_seed; // See that is shared between client & server for shared weapons code + + int m_iPlayerSound;// the index of the sound list slot reserved for this player + int m_iTargetVolume;// ideal sound volume. + int m_iWeaponVolume;// how loud the player's weapon is right now. + int m_iExtraSoundTypes;// additional classification for this weapon's sound + int m_iWeaponFlash;// brightness of the weapon flash + float m_flStopExtraSoundTime; + + float m_flFlashLightTime; // Time until next battery draw/Recharge + int m_iFlashBattery; // Flashlight Battery Draw + + int m_afButtonLast; + int m_afButtonPressed; + int m_afButtonReleased; + + edict_t *m_pentSndLast; // last sound entity to modify player room type + float m_flSndRoomtype; // last roomtype set by sound entity + float m_flSndRange; // dist from player to sound entity + + float m_flFallVelocity; + + int m_rgItems[MAX_ITEMS]; + int m_fKnownItem; // True when a new item needs to be added + int m_fNewAmmo; // True when a new item has been added + + 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 + + +// these are time-sensitive things that we keep track of + float m_flTimeStepSound; // when the last stepping sound was made + float m_flTimeWeaponIdle; // when to play another weapon idle animation. + float m_flSwimTime; // how long player has been underwater + 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 + + int m_idrowndmg; // track drowning damage taken + int m_idrownrestored; // track drowning damage restored + + int m_bitsHUDDamage; // Damage bits for the current fame. These get sent to + // the hude via the DAMAGE message + BOOL m_fInitHUD; // True when deferred HUD restart msg needs to be sent + BOOL m_fGameHUDInitialized; + int m_iTrain; // Train control position + BOOL m_fWeapon; // Set this to FALSE to force a reset of the current weapon HUD info + + EHANDLE m_pTank; // the tank which the player is currently controlling, NULL if no tank + float m_fDeadTime; // the time at which the player died (used in PlayerDeathThink()) + + BOOL m_fNoPlayerSound; // a debugging feature. Player makes no sound if this is true. + BOOL m_fLongJump; // does this player have the longjump module? + + 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 + int m_iClientFOV; // client's known FOV + // usable player items + CBasePlayerItem *m_rgpPlayerItems[MAX_ITEM_TYPES]; + CBasePlayerItem *m_pActiveItem; + CBasePlayerItem *m_pClientActiveItem; // client version of the active item + CBasePlayerItem *m_pLastItem; + // shared ammo slots + int m_rgAmmo[MAX_AMMO_SLOTS]; + int m_rgAmmoLast[MAX_AMMO_SLOTS]; + + Vector m_vecAutoAim; + BOOL m_fOnTarget; + int m_iDeaths; + float m_iRespawnFrames; // used in PlayerDeathThink() to make sure players can always respawn + + int m_lastx, m_lasty; // These are the previous update's crosshair angles, DON"T SAVE/RESTORE + + int m_nCustomSprayFrames;// Custom clan logo frames for this player + float m_flNextDecalTime;// next time this player can spray a decal + + char m_szTeamName[TEAM_NAME_LENGTH]; + + virtual void Spawn( void ); + void Pain( void ); + +// virtual void Think( void ); + virtual void Jump( void ); + virtual void Duck( void ); + virtual void PreThink( void ); + virtual void PostThink( 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 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 + virtual void StartSneaking( void ) { m_tSneaking = gpGlobals->time - 1; } + 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 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 + // Spectators should return TRUE for this + virtual const char *TeamID( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + void RenewItems(void); + void RemoveAllItems( BOOL removeSuit ); + BOOL SwitchWeapon( CBasePlayerItem *pWeapon ); + + // JOHN: sends custom messages if player HUD data has changed (eg health, ammo) + virtual void UpdateClientData( void ); + + static TYPEDESCRIPTION m_playerSaveData[]; + + // Player is moved across the transition by other means + virtual int ObjectCaps( void ) { return CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual void Precache( void ); + BOOL IsOnLadder( void ); + BOOL FlashlightIsOn( void ); + void FlashlightTurnOn( void ); + void FlashlightTurnOff( void ); + + void UpdatePlayerSound ( void ); + void DeathSound ( void ); + + int Classify ( void ); + void SetAnimation( PLAYER_ANIM playerAnim ); + void SetWeaponAnimType( const char *szExtention ); + char m_szAnimExtention[32]; + + // custom player functions + virtual void ImpulseCommands( void ); + void CheatImpulseCommands( int iImpulse ); + + 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 ); + EHANDLE m_hObserverTarget; + float m_flNextObserverInput; + int IsObserver() { return pev->iuser1; }; + + void AddPoints( int score, BOOL bAllowNegativeScore ); + void AddPointsToTeam( int score, BOOL bAllowNegativeScore ); + BOOL AddPlayerItem( CBasePlayerItem *pItem ); + BOOL RemovePlayerItem( CBasePlayerItem *pItem ); + void DropPlayerItem ( char *pszItemName ); + BOOL HasPlayerItem( CBasePlayerItem *pCheckItem ); + BOOL HasNamedPlayerItem( const char *pszItemName ); + BOOL HasWeapons( void );// do I have ANY weapons? + void SelectPrevItem( int iItem ); + void SelectNextItem( int iItem ); + void SelectLastItem(void); + void SelectItem(const char *pstr); + void ItemPreFrame( void ); + void ItemPostFrame( void ); + void GiveNamedItem( const char *szName ); + void EnableControl(BOOL fControl); + + int GiveAmmo( int iAmount, char *szName, int iMax ); + void SendAmmoUpdate(void); + + void WaterMove( void ); + void EXPORT PlayerDeathThink( void ); + void PlayerUse( void ); + + void CheckSuitUpdate(); + void SetSuitUpdate(char *name, int fgroup, int iNoRepeat); + void UpdateGeigerCounter( void ); + void CheckTimeBasedDamage( void ); + void UpdateStepSound( void ); + void PlayStepSound(int step, float fvol); + + BOOL FBecomeProne ( void ); + void BarnacleVictimBitten ( entvars_t *pevBarnacle ); + void BarnacleVictimReleased ( void ); + static int GetAmmoIndex(const char *psz); + int AmmoInventory( int iAmmoIndex ); + int Illumination( void ); + + void ResetAutoaim( void ); + Vector GetAutoaimVector( float flDelta ); + 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 ); + + // Discwar + void GivePowerup( int iPowerupType ); + void RemovePowerup( int iPowerupType ); + void RemoveAllPowerups( void ); + bool HasPowerup( int iPowerupType ); + void ClearFreezeAndRender( void ); + int m_iPowerups; + int m_iPowerupDiscs; + + void Freeze( void ); + int m_iFrozen; + int m_iClientFrozen; + float m_flFreezeTime; + EHANDLE m_hLastPlayerToHitMe; + float m_flLastDiscHit; + float m_flLastDiscBounces; + float m_flLastDiscHitTeleport; + + Vector m_vecOldVelocity; + + int m_iClientDeaths, m_iClientFrags, m_iClientPlayerClass, m_iClientTeam; + + // Discwar Arena Handling + EHANDLE m_pNextPlayer; + CDiscArena *m_pCurrentArena; + int m_iArenaCombatantNumber; + int m_iLastGameResult; + + // Discwar animation + int GetThrowAnim( void ); + int GetHoldAnim( void ); + int GetFallAnimation( void ); + void Decapitate( entvars_t *pevKiller ); + void Shatter( entvars_t *pevKiller ); + + float m_flThrowTime; + float m_flBackupTime; + float m_flTransitionTime; + Vector m_vecHitVelocity; + BOOL m_bHasDisconnected; + float m_flKnownItemTime; + + void ObserverInput_ChangeMode(); + void ObserverInput_PrevPlayer(); + void ObserverInput_NextPlayer(); + + void ClientHearVox( const char *pSentence ); + + float m_flSendArenaStatus; //Sigh. + float m_flChangeAngles; //Double sigh. +}; + +#define AUTOAIM_2DEGREES 0.0348994967025 +#define AUTOAIM_5DEGREES 0.08715574274766 +#define AUTOAIM_8DEGREES 0.1391731009601 +#define AUTOAIM_10DEGREES 0.1736481776669 + + +extern int gmsgHudText; +extern BOOL gInitHUD; + +// Observer Movement modes (stored in pev->iuser1, so the physics code can get at them) +#define OBS_CHASE_LOCKED 1 +#define OBS_CHASE_FREE 2 +#define OBS_ROAMING 3 +#define OBS_LOCKEDVIEW 4 + +#endif // PLAYER_H diff --git a/hlsdk/ricochet/dlls/saverestore.h b/hlsdk/ricochet/dlls/saverestore.h new file mode 100644 index 00000000..4524ee6a --- /dev/null +++ b/hlsdk/ricochet/dlls/saverestore.h @@ -0,0 +1,170 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Implementation in UTIL.CPP +#ifndef SAVERESTORE_H +#define SAVERESTORE_H + +class CBaseEntity; + +class CSaveRestoreBuffer +{ +public: + CSaveRestoreBuffer( void ); + CSaveRestoreBuffer( SAVERESTOREDATA *pdata ); + ~CSaveRestoreBuffer( void ); + + int EntityIndex( entvars_t *pevLookup ); + int EntityIndex( edict_t *pentLookup ); + int EntityIndex( EOFFSET eoLookup ); + int EntityIndex( CBaseEntity *pEntity ); + + int EntityFlags( int entityIndex, int flags ) { return EntityFlagsSet( entityIndex, 0 ); } + int EntityFlagsSet( int entityIndex, int flags ); + + edict_t *EntityFromIndex( int entityIndex ); + + unsigned short TokenHash( const char *pszToken ); + +protected: + SAVERESTOREDATA *m_pdata; + void BufferRewind( int size ); + unsigned int HashString( const char *pszToken ); +}; + + +class CSave : public CSaveRestoreBuffer +{ +public: + CSave( SAVERESTOREDATA *pdata ) : CSaveRestoreBuffer( pdata ) {}; + + void WriteShort( const char *pname, const short *value, int count ); + void WriteInt( const char *pname, const int *value, int count ); // Save an int + void WriteFloat( const char *pname, const float *value, int count ); // Save a float + void WriteTime( const char *pname, const float *value, int count ); // Save a float (timevalue) + void WriteData( const char *pname, int size, const char *pdata ); // Save a binary data block + void WriteString( const char *pname, const char *pstring ); // Save a null-terminated string + void WriteString( const char *pname, const int *stringId, int count ); // Save a null-terminated string (engine string) + void WriteVector( const char *pname, const Vector &value ); // Save a vector + void WriteVector( const char *pname, const float *value, int count ); // Save a vector + void WritePositionVector( const char *pname, const Vector &value ); // Offset for landmark if necessary + void WritePositionVector( const char *pname, const float *value, int count ); // array of pos vectors + void WriteFunction( const char *pname, const int *value, int count ); // Save a function pointer + int WriteEntVars( const char *pname, entvars_t *pev ); // Save entvars_t (entvars_t) + int WriteFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); + +private: + int DataEmpty( const char *pdata, int size ); + void BufferField( const char *pname, int size, const char *pdata ); + void BufferString( char *pdata, int len ); + void BufferData( const char *pdata, int size ); + void BufferHeader( const char *pname, int size ); +}; + +typedef struct +{ + unsigned short size; + unsigned short token; + char *pData; +} HEADER; + +class CRestore : public CSaveRestoreBuffer +{ +public: + CRestore( SAVERESTOREDATA *pdata ) : CSaveRestoreBuffer( pdata ) { m_global = 0; m_precache = TRUE; } + + int ReadEntVars( const char *pname, entvars_t *pev ); // entvars_t + int ReadFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); + int ReadField( void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount, int startField, int size, char *pName, void *pData ); + int ReadInt( void ); + short ReadShort( void ); + int ReadNamedInt( const char *pName ); + char *ReadNamedString( const char *pName ); + int Empty( void ) { return (m_pdata == NULL) || ((m_pdata->pCurrentData-m_pdata->pBaseData)>=m_pdata->bufferSize); } + inline void SetGlobalMode( int global ) { m_global = global; } + void PrecacheMode( BOOL mode ) { m_precache = mode; } + +private: + char *BufferPointer( void ); + void BufferReadBytes( char *pOutput, int size ); + void BufferSkipBytes( int bytes ); + int BufferSkipZString( void ); + int BufferCheckZString( const char *string ); + + void BufferReadHeader( HEADER *pheader ); + + int m_global; // Restoring a global entity? + BOOL m_precache; +}; + +#define MAX_ENTITYARRAY 64 + +//#define ARRAYSIZE(p) (sizeof(p)/sizeof(p[0])) + +#define IMPLEMENT_SAVERESTORE(derivedClass,baseClass) \ + int derivedClass::Save( CSave &save )\ + {\ + if ( !baseClass::Save(save) )\ + return 0;\ + return save.WriteFields( #derivedClass, this, m_SaveData, ARRAYSIZE(m_SaveData) );\ + }\ + int derivedClass::Restore( CRestore &restore )\ + {\ + if ( !baseClass::Restore(restore) )\ + return 0;\ + return restore.ReadFields( #derivedClass, this, m_SaveData, ARRAYSIZE(m_SaveData) );\ + } + + +typedef enum { GLOBAL_OFF = 0, GLOBAL_ON = 1, GLOBAL_DEAD = 2 } GLOBALESTATE; + +typedef struct globalentity_s globalentity_t; + +struct globalentity_s +{ + char name[64]; + char levelName[32]; + GLOBALESTATE state; + globalentity_t *pNext; +}; + +class CGlobalState +{ +public: + CGlobalState(); + void Reset( void ); + void ClearStates( void ); + void EntityAdd( string_t globalname, string_t mapName, GLOBALESTATE state ); + void EntitySetState( string_t globalname, GLOBALESTATE state ); + void EntityUpdate( string_t globalname, string_t mapname ); + const globalentity_t *EntityFromTable( string_t globalname ); + GLOBALESTATE EntityGetState( string_t globalname ); + int EntityInTable( string_t globalname ) { return (Find( globalname ) != NULL) ? 1 : 0; } + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +//#ifdef _DEBUG + void DumpGlobals( void ); +//#endif + +private: + globalentity_t *Find( string_t globalname ); + globalentity_t *m_pList; + int m_listCount; +}; + +extern CGlobalState gGlobalState; + +#endif //SAVERESTORE_H diff --git a/hlsdk/ricochet/dlls/schedule.h b/hlsdk/ricochet/dlls/schedule.h new file mode 100644 index 00000000..f8ee15ae --- /dev/null +++ b/hlsdk/ricochet/dlls/schedule.h @@ -0,0 +1,31 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +//========================================================= +// Scheduling +//========================================================= +#ifndef SCHEDULE_H +#define SCHEDULE_H + +#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_LIGHT_DAMAGE ( 1 << 8 ) // hurt a little +#define bits_COND_HEAVY_DAMAGE ( 1 << 9 ) // hurt a lot +#define bits_COND_SEE_CLIENT ( 1 << 21) // see a client +#define bits_COND_SEE_NEMESIS ( 1 << 22) // see my nemesis + + +#endif // SCHEDULE_H diff --git a/hlsdk/ricochet/dlls/scriptevent.h b/hlsdk/ricochet/dlls/scriptevent.h new file mode 100644 index 00000000..9aa7140e --- /dev/null +++ b/hlsdk/ricochet/dlls/scriptevent.h @@ -0,0 +1,29 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +#ifndef SCRIPTEVENT_H +#define SCRIPTEVENT_H + +#define SCRIPT_EVENT_DEAD 1000 // character is now dead +#define SCRIPT_EVENT_NOINTERRUPT 1001 // does not allow interrupt +#define SCRIPT_EVENT_CANINTERRUPT 1002 // will allow interrupt +#define SCRIPT_EVENT_FIREEVENT 1003 // event now fires +#define SCRIPT_EVENT_SOUND 1004 // Play named wave file (on CHAN_BODY) +#define SCRIPT_EVENT_SENTENCE 1005 // Play named sentence +#define SCRIPT_EVENT_INAIR 1006 // Leave the character in air at the end of the sequence (don't find the floor) +#define SCRIPT_EVENT_ENDANIMATION 1007 // Set the animation by name after the sequence completes +#define SCRIPT_EVENT_SOUND_VOICE 1008 // Play named wave file (on CHAN_VOICE) +#define SCRIPT_EVENT_SENTENCE_RND1 1009 // Play sentence group 25% of the time +#define SCRIPT_EVENT_NOT_DEAD 1010 // Bring back to life (for life/death sequences) +#endif //SCRIPTEVENT_H diff --git a/hlsdk/ricochet/dlls/singleplay_gamerules.cpp b/hlsdk/ricochet/dlls/singleplay_gamerules.cpp new file mode 100644 index 00000000..9290f586 --- /dev/null +++ b/hlsdk/ricochet/dlls/singleplay_gamerules.cpp @@ -0,0 +1,331 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// teamplay_gamerules.cpp +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "skill.h" +#include "items.h" +#include "discwar.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 ) +{ + pPlayer->GiveNamedItem( "weapon_disc" ); + pPlayer->GiveAmmo( MAX_DISCS, "disc", MAX_DISCS ); +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules :: AllowAutoTargetCrosshair( void ) +{ + return ( g_iSkillLevel == SKILL_EASY ); +} + +//========================================================= +//========================================================= +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/hlsdk/ricochet/dlls/skill.cpp b/hlsdk/ricochet/dlls/skill.cpp new file mode 100644 index 00000000..5e06914c --- /dev/null +++ b/hlsdk/ricochet/dlls/skill.cpp @@ -0,0 +1,47 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +//========================================================= +// skill.cpp - code for skill level concerns +//========================================================= +#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 +// on, and return the value.of that Cvar +//========================================================= +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; +} + diff --git a/hlsdk/ricochet/dlls/skill.h b/hlsdk/ricochet/dlls/skill.h new file mode 100644 index 00000000..30e25f8e --- /dev/null +++ b/hlsdk/ricochet/dlls/skill.h @@ -0,0 +1,147 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +//========================================================= +// skill.h - skill level concerns +//========================================================= + +struct skilldata_t +{ + + int iSkillLevel; // game skill level + +// Monster Health & Damage + float agruntHealth; + float agruntDmgPunch; + + float apacheHealth; + + float barneyHealth; + + float bigmommaHealthFactor; // Multiply each node's health by this + float bigmommaDmgSlash; // melee attack damage + float bigmommaDmgBlast; // mortar attack damage + float bigmommaRadiusBlast; // mortar attack radius + + float bullsquidHealth; + float bullsquidDmgBite; + float bullsquidDmgWhip; + float bullsquidDmgSpit; + + float gargantuaHealth; + float gargantuaDmgSlash; + float gargantuaDmgFire; + float gargantuaDmgStomp; + + float hassassinHealth; + + float headcrabHealth; + float headcrabDmgBite; + + float hgruntHealth; + float hgruntDmgKick; + float hgruntShotgunPellets; + float hgruntGrenadeSpeed; + + float houndeyeHealth; + float houndeyeDmgBlast; + + float slaveHealth; + float slaveDmgClaw; + float slaveDmgClawrake; + float slaveDmgZap; + + float ichthyosaurHealth; + float ichthyosaurDmgShake; + + float leechHealth; + float leechDmgBite; + + float controllerHealth; + float controllerDmgZap; + float controllerSpeedBall; + float controllerDmgBall; + + float nihilanthHealth; + float nihilanthZap; + + float scientistHealth; + + float snarkHealth; + float snarkDmgBite; + float snarkDmgPop; + + float zombieHealth; + float zombieDmgOneSlash; + float zombieDmgBothSlash; + + float turretHealth; + float miniturretHealth; + float sentryHealth; + + +// Player Weapons + float plrDmgCrowbar; + float plrDmg9MM; + float plrDmg357; + float plrDmgMP5; + float plrDmgM203Grenade; + float plrDmgBuckshot; + float plrDmgCrossbowClient; + float plrDmgCrossbowMonster; + float plrDmgRPG; + float plrDmgGauss; + float plrDmgEgonNarrow; + float plrDmgEgonWide; + float plrDmgHornet; + float plrDmgHandGrenade; + float plrDmgSatchel; + float plrDmgTripmine; + +// weapons shared by monsters + float monDmg9MM; + float monDmgMP5; + float monDmg12MM; + float monDmgHornet; + +// health/suit charge + float suitchargerCapacity; + float batteryCapacity; + float healthchargerCapacity; + float healthkitCapacity; + float scientistHeal; + +// monster damage adj + float monHead; + float monChest; + float monStomach; + float monLeg; + float monArm; + +// player damage adj + float plrHead; + float plrChest; + float plrStomach; + float plrLeg; + float plrArm; +}; + +extern DLL_GLOBAL skilldata_t gSkillData; +float GetSkillCvar( char *pName ); + +extern DLL_GLOBAL int g_iSkillLevel; + +#define SKILL_EASY 1 +#define SKILL_MEDIUM 2 +#define SKILL_HARD 3 diff --git a/hlsdk/ricochet/dlls/sound.cpp b/hlsdk/ricochet/dlls/sound.cpp new file mode 100644 index 00000000..d1125439 --- /dev/null +++ b/hlsdk/ricochet/dlls/sound.cpp @@ -0,0 +1,1982 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// sound.cpp +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "player.h" +#include "talkmonster.h" +#include "gamerules.h" + + +static char *memfgets( byte *pMemFile, int fileSize, int &filePos, char *pBuffer, int bufferSize ); + + +// ==================== GENERIC AMBIENT SOUND ====================================== + +// runtime pitch shift and volume fadein/out structure + +// NOTE: IF YOU CHANGE THIS STRUCT YOU MUST CHANGE THE SAVE/RESTORE VERSION NUMBER +// SEE BELOW (in the typedescription for the class) +typedef struct dynpitchvol +{ + // NOTE: do not change the order of these parameters + // NOTE: unless you also change order of rgdpvpreset array elements! + int preset; + + int pitchrun; // pitch shift % when sound is running 0 - 255 + int pitchstart; // pitch shift % when sound stops or starts 0 - 255 + int spinup; // spinup time 0 - 100 + int spindown; // spindown time 0 - 100 + + int volrun; // volume change % when sound is running 0 - 10 + int volstart; // volume change % when sound stops or starts 0 - 10 + int fadein; // volume fade in time 0 - 100 + int fadeout; // volume fade out time 0 - 100 + + // Low Frequency Oscillator + int lfotype; // 0) off 1) square 2) triangle 3) random + int lforate; // 0 - 1000, how fast lfo osciallates + + int lfomodpitch; // 0-100 mod of current pitch. 0 is off. + int lfomodvol; // 0-100 mod of current volume. 0 is off. + + int cspinup; // each trigger hit increments counter and spinup pitch + + + int cspincount; + + int pitch; + int spinupsav; + int spindownsav; + int pitchfrac; + + int vol; + int fadeinsav; + int fadeoutsav; + int volfrac; + + int lfofrac; + int lfomult; + + +} dynpitchvol_t; + +#define CDPVPRESETMAX 27 + +// presets for runtime pitch and vol modulation of ambient sounds + +dynpitchvol_t rgdpvpreset[CDPVPRESETMAX] = +{ +// pitch pstart spinup spindwn volrun volstrt fadein fadeout lfotype lforate modptch modvol cspnup +{1, 255, 75, 95, 95, 10, 1, 50, 95, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{2, 255, 85, 70, 88, 10, 1, 20, 88, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{3, 255, 100, 50, 75, 10, 1, 10, 75, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{4, 100, 100, 0, 0, 10, 1, 90, 90, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{5, 100, 100, 0, 0, 10, 1, 80, 80, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{6, 100, 100, 0, 0, 10, 1, 50, 70, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{7, 100, 100, 0, 0, 5, 1, 40, 50, 1, 50, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, +{8, 100, 100, 0, 0, 5, 1, 40, 50, 1, 150, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, +{9, 100, 100, 0, 0, 5, 1, 40, 50, 1, 750, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, +{10,128, 100, 50, 75, 10, 1, 30, 40, 2, 8, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{11,128, 100, 50, 75, 10, 1, 30, 40, 2, 25, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{12,128, 100, 50, 75, 10, 1, 30, 40, 2, 70, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{13,50, 50, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{14,70, 70, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{15,90, 90, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{16,120, 120, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{17,180, 180, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{18,255, 255, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{19,200, 75, 90, 90, 10, 1, 50, 90, 2, 100, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{20,255, 75, 97, 90, 10, 1, 50, 90, 1, 40, 50, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{21,100, 100, 0, 0, 10, 1, 30, 50, 3, 15, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{22,160, 160, 0, 0, 10, 1, 50, 50, 3, 500, 25, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{23,255, 75, 88, 0, 10, 1, 40, 0, 0, 0, 0, 0, 5, 0,0,0,0,0,0,0,0,0,0}, +{24,200, 20, 95, 70, 10, 1, 70, 70, 3, 20, 50, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{25,180, 100, 50, 60, 10, 1, 40, 60, 2, 90, 100, 100, 0, 0,0,0,0,0,0,0,0,0,0}, +{26,60, 60, 0, 0, 10, 1, 40, 70, 3, 80, 20, 50, 0, 0,0,0,0,0,0,0,0,0,0}, +{27,128, 90, 10, 10, 10, 1, 20, 40, 1, 5, 10, 20, 0, 0,0,0,0,0,0,0,0,0,0} +}; + +class CAmbientGeneric : public CBaseEntity +{ +public: + void KeyValue( KeyValueData* pkvd); + void Spawn( void ); + void Precache( void ); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT RampThink( void ); + void InitModulationParms(void); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + + float m_flAttenuation; // attenuation value + dynpitchvol_t m_dpv; + + BOOL m_fActive; // only TRUE when the entity is playing a looping sound + BOOL m_fLooping; // TRUE when the sound played will loop +}; + +LINK_ENTITY_TO_CLASS( ambient_generic, CAmbientGeneric ); +TYPEDESCRIPTION CAmbientGeneric::m_SaveData[] = +{ + DEFINE_FIELD( CAmbientGeneric, m_flAttenuation, FIELD_FLOAT ), + DEFINE_FIELD( CAmbientGeneric, m_fActive, FIELD_BOOLEAN ), + DEFINE_FIELD( CAmbientGeneric, m_fLooping, FIELD_BOOLEAN ), + + // HACKHACK - This is not really in the spirit of the save/restore design, but save this + // out as a binary data block. If the dynpitchvol_t is changed, old saved games will NOT + // load these correctly, so bump the save/restore version if you change the size of the struct + // The right way to do this is to split the input parms (read in keyvalue) into members and re-init this + // struct in Precache(), but it's unlikely that the struct will change, so it's not worth the time right now. + DEFINE_ARRAY( CAmbientGeneric, m_dpv, FIELD_CHARACTER, sizeof(dynpitchvol_t) ), +}; + +IMPLEMENT_SAVERESTORE( CAmbientGeneric, CBaseEntity ); + +// +// ambient_generic - general-purpose user-defined static sound +// +void CAmbientGeneric :: Spawn( void ) +{ +/* + -1 : "Default" + 0 : "Everywhere" + 200 : "Small Radius" + 125 : "Medium Radius" + 80 : "Large Radius" +*/ + + if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_EVERYWHERE) ) + { + m_flAttenuation = ATTN_NONE; + } + else if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_SMALLRADIUS) ) + { + m_flAttenuation = ATTN_IDLE; + } + else if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_MEDIUMRADIUS) ) + { + m_flAttenuation = ATTN_STATIC; + } + else if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_LARGERADIUS) ) + { + m_flAttenuation = ATTN_NORM; + } + else + {// if the designer didn't set a sound attenuation, default to one. + m_flAttenuation = ATTN_STATIC; + } + + char* szSoundFile = (char*) STRING(pev->message); + + if ( FStringNull( pev->message ) || strlen( szSoundFile ) < 1 ) + { + ALERT( at_error, "EMPTY AMBIENT AT: %f, %f, %f\n", pev->origin.x, pev->origin.y, pev->origin.z ); + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CBaseEntity::SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + // Set up think function for dynamic modification + // of ambient sound's pitch or volume. Don't + // start thinking yet. + + SetThink(&CAmbientGeneric::RampThink); + pev->nextthink = 0; + + // allow on/off switching via 'use' function. + + SetUse ( &CAmbientGeneric::ToggleUse ); + + m_fActive = FALSE; + + if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_NOT_LOOPING ) ) + m_fLooping = FALSE; + else + m_fLooping = TRUE; + Precache( ); +} + + +void CAmbientGeneric :: Precache( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + + if ( !FStringNull( pev->message ) && strlen( szSoundFile ) > 1 ) + { + if (*szSoundFile != '!') + PRECACHE_SOUND(szSoundFile); + } + // init all dynamic modulation parms + InitModulationParms(); + + if ( !FBitSet (pev->spawnflags, AMBIENT_SOUND_START_SILENT ) ) + { + // start the sound ASAP + if (m_fLooping) + m_fActive = TRUE; + } + if ( m_fActive ) + { + UTIL_EmitAmbientSound ( ENT(pev), pev->origin, szSoundFile, + (m_dpv.vol * 0.01), m_flAttenuation, SND_SPAWNING, m_dpv.pitch); + + pev->nextthink = gpGlobals->time + 0.1; + } +} + +// RampThink - Think at 5hz if we are dynamically modifying +// pitch or volume of the playing sound. This function will +// ramp pitch and/or volume up or down, modify pitch/volume +// with lfo if active. + +void CAmbientGeneric :: RampThink( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + int pitch = m_dpv.pitch; + int vol = m_dpv.vol; + int flags = 0; + int fChanged = 0; // FALSE if pitch and vol remain unchanged this round + int prev; + + if (!m_dpv.spinup && !m_dpv.spindown && !m_dpv.fadein && !m_dpv.fadeout && !m_dpv.lfotype) + return; // no ramps or lfo, stop thinking + + // ============== + // pitch envelope + // ============== + if (m_dpv.spinup || m_dpv.spindown) + { + prev = m_dpv.pitchfrac >> 8; + + if (m_dpv.spinup > 0) + m_dpv.pitchfrac += m_dpv.spinup; + else if (m_dpv.spindown > 0) + m_dpv.pitchfrac -= m_dpv.spindown; + + pitch = m_dpv.pitchfrac >> 8; + + if (pitch > m_dpv.pitchrun) + { + pitch = m_dpv.pitchrun; + m_dpv.spinup = 0; // done with ramp up + } + + if (pitch < m_dpv.pitchstart) + { + pitch = m_dpv.pitchstart; + m_dpv.spindown = 0; // done with ramp down + + // shut sound off + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + + // return without setting nextthink + return; + } + + if (pitch > 255) pitch = 255; + if (pitch < 1) pitch = 1; + + m_dpv.pitch = pitch; + + fChanged |= (prev != pitch); + flags |= SND_CHANGE_PITCH; + } + + // ================== + // amplitude envelope + // ================== + if (m_dpv.fadein || m_dpv.fadeout) + { + prev = m_dpv.volfrac >> 8; + + if (m_dpv.fadein > 0) + m_dpv.volfrac += m_dpv.fadein; + else if (m_dpv.fadeout > 0) + m_dpv.volfrac -= m_dpv.fadeout; + + vol = m_dpv.volfrac >> 8; + + if (vol > m_dpv.volrun) + { + vol = m_dpv.volrun; + m_dpv.fadein = 0; // done with ramp up + } + + if (vol < m_dpv.volstart) + { + vol = m_dpv.volstart; + m_dpv.fadeout = 0; // done with ramp down + + // shut sound off + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + + // return without setting nextthink + return; + } + + if (vol > 100) vol = 100; + if (vol < 1) vol = 1; + + m_dpv.vol = vol; + + fChanged |= (prev != vol); + flags |= SND_CHANGE_VOL; + } + + // =================== + // pitch/amplitude LFO + // =================== + if (m_dpv.lfotype) + { + int pos; + + if (m_dpv.lfofrac > 0x6fffffff) + m_dpv.lfofrac = 0; + + // update lfo, lfofrac/255 makes a triangle wave 0-255 + m_dpv.lfofrac += m_dpv.lforate; + pos = m_dpv.lfofrac >> 8; + + if (m_dpv.lfofrac < 0) + { + m_dpv.lfofrac = 0; + m_dpv.lforate = abs(m_dpv.lforate); + pos = 0; + } + else if (pos > 255) + { + pos = 255; + m_dpv.lfofrac = (255 << 8); + m_dpv.lforate = -abs(m_dpv.lforate); + } + + switch(m_dpv.lfotype) + { + case LFO_SQUARE: + if (pos < 128) + m_dpv.lfomult = 255; + else + m_dpv.lfomult = 0; + + break; + case LFO_RANDOM: + if (pos == 255) + m_dpv.lfomult = RANDOM_LONG(0, 255); + break; + case LFO_TRIANGLE: + default: + m_dpv.lfomult = pos; + break; + } + + if (m_dpv.lfomodpitch) + { + prev = pitch; + + // pitch 0-255 + pitch += ((m_dpv.lfomult - 128) * m_dpv.lfomodpitch) / 100; + + if (pitch > 255) pitch = 255; + if (pitch < 1) pitch = 1; + + + fChanged |= (prev != pitch); + flags |= SND_CHANGE_PITCH; + } + + if (m_dpv.lfomodvol) + { + // vol 0-100 + prev = vol; + + vol += ((m_dpv.lfomult - 128) * m_dpv.lfomodvol) / 100; + + if (vol > 100) vol = 100; + if (vol < 0) vol = 0; + + fChanged |= (prev != vol); + flags |= SND_CHANGE_VOL; + } + + } + + // Send update to playing sound only if we actually changed + // pitch or volume in this routine. + + if (flags && fChanged) + { + if (pitch == PITCH_NORM) + pitch = PITCH_NORM + 1; // don't send 'no pitch' ! + + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + (vol * 0.01), m_flAttenuation, flags, pitch); + } + + // update ramps at 5hz + pev->nextthink = gpGlobals->time + 0.2; + return; +} + +// Init all ramp params in preparation to +// play a new sound + +void CAmbientGeneric :: InitModulationParms(void) +{ + int pitchinc; + + m_dpv.volrun = static_cast(pev->health * 10); // 0 - 100 + if (m_dpv.volrun > 100) m_dpv.volrun = 100; + if (m_dpv.volrun < 0) m_dpv.volrun = 0; + + // get presets + if (m_dpv.preset != 0 && m_dpv.preset <= CDPVPRESETMAX) + { + // load preset values + m_dpv = rgdpvpreset[m_dpv.preset - 1]; + + // fixup preset values, just like + // fixups in KeyValue routine. + if (m_dpv.spindown > 0) + m_dpv.spindown = (101 - m_dpv.spindown) * 64; + if (m_dpv.spinup > 0) + m_dpv.spinup = (101 - m_dpv.spinup) * 64; + + m_dpv.volstart *= 10; + m_dpv.volrun *= 10; + + if (m_dpv.fadein > 0) + m_dpv.fadein = (101 - m_dpv.fadein) * 64; + if (m_dpv.fadeout > 0) + m_dpv.fadeout = (101 - m_dpv.fadeout) * 64; + + m_dpv.lforate *= 256; + + m_dpv.fadeinsav = m_dpv.fadein; + m_dpv.fadeoutsav = m_dpv.fadeout; + m_dpv.spinupsav = m_dpv.spinup; + m_dpv.spindownsav = m_dpv.spindown; + } + + m_dpv.fadein = m_dpv.fadeinsav; + m_dpv.fadeout = 0; + + if (m_dpv.fadein) + m_dpv.vol = m_dpv.volstart; + else + m_dpv.vol = m_dpv.volrun; + + m_dpv.spinup = m_dpv.spinupsav; + m_dpv.spindown = 0; + + if (m_dpv.spinup) + m_dpv.pitch = m_dpv.pitchstart; + else + m_dpv.pitch = m_dpv.pitchrun; + + if (m_dpv.pitch == 0) + m_dpv.pitch = PITCH_NORM; + + m_dpv.pitchfrac = m_dpv.pitch << 8; + m_dpv.volfrac = m_dpv.vol << 8; + + m_dpv.lfofrac = 0; + m_dpv.lforate = abs(m_dpv.lforate); + + m_dpv.cspincount = 1; + + if (m_dpv.cspinup) + { + pitchinc = (255 - m_dpv.pitchstart) / m_dpv.cspinup; + + m_dpv.pitchrun = m_dpv.pitchstart + pitchinc; + if (m_dpv.pitchrun > 255) m_dpv.pitchrun = 255; + } + + if ((m_dpv.spinupsav || m_dpv.spindownsav || (m_dpv.lfotype && m_dpv.lfomodpitch)) + && (m_dpv.pitch == PITCH_NORM)) + m_dpv.pitch = PITCH_NORM + 1; // must never send 'no pitch' as first pitch + // if we intend to pitch shift later! +} + +// +// ToggleUse - turns an ambient sound on or off. If the +// ambient is a looping sound, mark sound as active (m_fActive) +// if it's playing, innactive if not. If the sound is not +// a looping sound, never mark it as active. +// +void CAmbientGeneric :: ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + char* szSoundFile = (char*) STRING(pev->message); + float fraction; + + if ( useType != USE_TOGGLE ) + { + if ( (m_fActive && useType == USE_ON) || (!m_fActive && useType == USE_OFF) ) + return; + } + // Directly change pitch if arg passed. Only works if sound is already playing. + + if (useType == USE_SET && m_fActive) // Momentary buttons will pass down a float in here + { + + fraction = value; + + if ( fraction > 1.0 ) + fraction = 1.0; + if (fraction < 0.0) + fraction = 0.01; + + m_dpv.pitch = static_cast(fraction * 255); + + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_CHANGE_PITCH, m_dpv.pitch); + + return; + } + + // Toggle + + // m_fActive is TRUE only if a looping sound is playing. + + if ( m_fActive ) + {// turn sound off + + if (m_dpv.cspinup) + { + // Don't actually shut off. Each toggle causes + // incremental spinup to max pitch + + if (m_dpv.cspincount <= m_dpv.cspinup) + { + int pitchinc; + + // start a new spinup + m_dpv.cspincount++; + + pitchinc = (255 - m_dpv.pitchstart) / m_dpv.cspinup; + + m_dpv.spinup = m_dpv.spinupsav; + m_dpv.spindown = 0; + + m_dpv.pitchrun = m_dpv.pitchstart + pitchinc * m_dpv.cspincount; + if (m_dpv.pitchrun > 255) m_dpv.pitchrun = 255; + + pev->nextthink = gpGlobals->time + 0.1; + } + + } + else + { + m_fActive = FALSE; + + // HACKHACK - this makes the code in Precache() work properly after a save/restore + pev->spawnflags |= AMBIENT_SOUND_START_SILENT; + + if (m_dpv.spindownsav || m_dpv.fadeoutsav) + { + // spin it down (or fade it) before shutoff if spindown is set + m_dpv.spindown = m_dpv.spindownsav; + m_dpv.spinup = 0; + + m_dpv.fadeout = m_dpv.fadeoutsav; + m_dpv.fadein = 0; + pev->nextthink = gpGlobals->time + 0.1; + } + else + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + } + } + else + {// turn sound on + + // only toggle if this is a looping sound. If not looping, each + // trigger will cause the sound to play. If the sound is still + // playing from a previous trigger press, it will be shut off + // and then restarted. + + if (m_fLooping) + m_fActive = TRUE; + else + // shut sound off now - may be interrupting a long non-looping sound + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + + // init all ramp params for startup + + InitModulationParms(); + + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + (m_dpv.vol * 0.01), m_flAttenuation, 0, m_dpv.pitch); + + pev->nextthink = gpGlobals->time + 0.1; + + } +} +// KeyValue - load keyvalue pairs into member data of the +// ambient generic. NOTE: called BEFORE spawn! + +void CAmbientGeneric :: KeyValue( KeyValueData *pkvd ) +{ + // NOTE: changing any of the modifiers in this code + // NOTE: also requires changing InitModulationParms code. + + // preset + if (FStrEq(pkvd->szKeyName, "preset")) + { + m_dpv.preset = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + + // pitchrun + else if (FStrEq(pkvd->szKeyName, "pitch")) + { + m_dpv.pitchrun = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + if (m_dpv.pitchrun > 255) m_dpv.pitchrun = 255; + if (m_dpv.pitchrun < 0) m_dpv.pitchrun = 0; + } + + // pitchstart + else if (FStrEq(pkvd->szKeyName, "pitchstart")) + { + m_dpv.pitchstart = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + if (m_dpv.pitchstart > 255) m_dpv.pitchstart = 255; + if (m_dpv.pitchstart < 0) m_dpv.pitchstart = 0; + } + + // spinup + else if (FStrEq(pkvd->szKeyName, "spinup")) + { + m_dpv.spinup = atoi(pkvd->szValue); + + if (m_dpv.spinup > 100) m_dpv.spinup = 100; + if (m_dpv.spinup < 0) m_dpv.spinup = 0; + + if (m_dpv.spinup > 0) + m_dpv.spinup = (101 - m_dpv.spinup) * 64; + m_dpv.spinupsav = m_dpv.spinup; + pkvd->fHandled = TRUE; + } + + // spindown + else if (FStrEq(pkvd->szKeyName, "spindown")) + { + m_dpv.spindown = atoi(pkvd->szValue); + + if (m_dpv.spindown > 100) m_dpv.spindown = 100; + if (m_dpv.spindown < 0) m_dpv.spindown = 0; + + if (m_dpv.spindown > 0) + m_dpv.spindown = (101 - m_dpv.spindown) * 64; + m_dpv.spindownsav = m_dpv.spindown; + pkvd->fHandled = TRUE; + } + + // volstart + else if (FStrEq(pkvd->szKeyName, "volstart")) + { + m_dpv.volstart = atoi(pkvd->szValue); + + if (m_dpv.volstart > 10) m_dpv.volstart = 10; + if (m_dpv.volstart < 0) m_dpv.volstart = 0; + + m_dpv.volstart *= 10; // 0 - 100 + + pkvd->fHandled = TRUE; + } + + // fadein + else if (FStrEq(pkvd->szKeyName, "fadein")) + { + m_dpv.fadein = atoi(pkvd->szValue); + + if (m_dpv.fadein > 100) m_dpv.fadein = 100; + if (m_dpv.fadein < 0) m_dpv.fadein = 0; + + if (m_dpv.fadein > 0) + m_dpv.fadein = (101 - m_dpv.fadein) * 64; + m_dpv.fadeinsav = m_dpv.fadein; + pkvd->fHandled = TRUE; + } + + // fadeout + else if (FStrEq(pkvd->szKeyName, "fadeout")) + { + m_dpv.fadeout = atoi(pkvd->szValue); + + if (m_dpv.fadeout > 100) m_dpv.fadeout = 100; + if (m_dpv.fadeout < 0) m_dpv.fadeout = 0; + + if (m_dpv.fadeout > 0) + m_dpv.fadeout = (101 - m_dpv.fadeout) * 64; + m_dpv.fadeoutsav = m_dpv.fadeout; + pkvd->fHandled = TRUE; + } + + // lfotype + else if (FStrEq(pkvd->szKeyName, "lfotype")) + { + m_dpv.lfotype = atoi(pkvd->szValue); + if (m_dpv.lfotype > 4) m_dpv.lfotype = LFO_TRIANGLE; + pkvd->fHandled = TRUE; + } + + // lforate + else if (FStrEq(pkvd->szKeyName, "lforate")) + { + m_dpv.lforate = atoi(pkvd->szValue); + + if (m_dpv.lforate > 1000) m_dpv.lforate = 1000; + if (m_dpv.lforate < 0) m_dpv.lforate = 0; + + m_dpv.lforate *= 256; + + pkvd->fHandled = TRUE; + } + // lfomodpitch + else if (FStrEq(pkvd->szKeyName, "lfomodpitch")) + { + m_dpv.lfomodpitch = atoi(pkvd->szValue); + if (m_dpv.lfomodpitch > 100) m_dpv.lfomodpitch = 100; + if (m_dpv.lfomodpitch < 0) m_dpv.lfomodpitch = 0; + + + pkvd->fHandled = TRUE; + } + + // lfomodvol + else if (FStrEq(pkvd->szKeyName, "lfomodvol")) + { + m_dpv.lfomodvol = atoi(pkvd->szValue); + if (m_dpv.lfomodvol > 100) m_dpv.lfomodvol = 100; + if (m_dpv.lfomodvol < 0) m_dpv.lfomodvol = 0; + + pkvd->fHandled = TRUE; + } + + // cspinup + else if (FStrEq(pkvd->szKeyName, "cspinup")) + { + m_dpv.cspinup = atoi(pkvd->szValue); + if (m_dpv.cspinup > 100) m_dpv.cspinup = 100; + if (m_dpv.cspinup < 0) m_dpv.cspinup = 0; + + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +// =================== ROOM SOUND FX ========================================== + +class CEnvSound : public CPointEntity +{ +public: + void KeyValue( KeyValueData* pkvd); + void Spawn( void ); + + void Think( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + float m_flRadius; + float m_flRoomtype; +}; + +LINK_ENTITY_TO_CLASS( env_sound, CEnvSound ); +TYPEDESCRIPTION CEnvSound::m_SaveData[] = +{ + DEFINE_FIELD( CEnvSound, m_flRadius, FIELD_FLOAT ), + DEFINE_FIELD( CEnvSound, m_flRoomtype, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CEnvSound, CBaseEntity ); + + +void CEnvSound :: KeyValue( KeyValueData *pkvd ) +{ + + if (FStrEq(pkvd->szKeyName, "radius")) + { + m_flRadius = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + if (FStrEq(pkvd->szKeyName, "roomtype")) + { + m_flRoomtype = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } +} + +// returns TRUE if the given sound entity (pev) is in range +// and can see the given player entity (pevTarget) + +BOOL FEnvSoundInRange(entvars_t *pev, entvars_t *pevTarget, float *pflRange) +{ + CEnvSound *pSound = GetClassPtr( (CEnvSound *)pev ); + Vector vecSpot1 = pev->origin + pev->view_ofs; + Vector vecSpot2 = pevTarget->origin + pevTarget->view_ofs; + Vector vecRange; + float flRange; + TraceResult tr; + + UTIL_TraceLine(vecSpot1, vecSpot2, ignore_monsters, ENT(pev), &tr); + + // check if line of sight crosses water boundary, or is blocked + + if ((tr.fInOpen && tr.fInWater) || tr.flFraction != 1) + return FALSE; + + // calc range from sound entity to player + + vecRange = tr.vecEndPos - vecSpot1; + flRange = vecRange.Length(); + + if (pSound->m_flRadius < flRange) + return FALSE; + + if (pflRange) + *pflRange = flRange; + + return TRUE; +} + +// +// A client that is visible and in range of a sound entity will +// have its room_type set by that sound entity. If two or more +// sound entities are contending for a client, then the nearest +// sound entity to the client will set the client's room_type. +// A client's room_type will remain set to its prior value until +// a new in-range, visible sound entity resets a new room_type. +// + +// CONSIDER: if player in water state, autoset roomtype to 14,15 or 16. + +void CEnvSound :: Think( void ) +{ + // get pointer to client if visible; FIND_CLIENT_IN_PVS will + // cycle through visible clients on consecutive calls. + + edict_t *pentPlayer = FIND_CLIENT_IN_PVS(edict()); + CBasePlayer *pPlayer = NULL; + + if (FNullEnt(pentPlayer)) + goto env_sound_Think_slow; // no player in pvs of sound entity, slow it down + + pPlayer = GetClassPtr( (CBasePlayer *)VARS(pentPlayer)); + float flRange; + + // check to see if this is the sound entity that is + // currently affecting this player + + if(!FNullEnt(pPlayer->m_pentSndLast) && (pPlayer->m_pentSndLast == ENT(pev))) { + + // this is the entity currently affecting player, check + // for validity + + if (pPlayer->m_flSndRoomtype != 0 && pPlayer->m_flSndRange != 0) { + + // we're looking at a valid sound entity affecting + // player, make sure it's still valid, update range + + if (FEnvSoundInRange(pev, VARS(pentPlayer), &flRange)) { + pPlayer->m_flSndRange = flRange; + goto env_sound_Think_fast; + } else { + + // current sound entity affecting player is no longer valid, + // flag this state by clearing room_type and range. + // NOTE: we do not actually change the player's room_type + // NOTE: until we have a new valid room_type to change it to. + + pPlayer->m_flSndRange = 0; + pPlayer->m_flSndRoomtype = 0; + goto env_sound_Think_slow; + } + } else { + // entity is affecting player but is out of range, + // wait passively for another entity to usurp it... + goto env_sound_Think_slow; + } + } + + // if we got this far, we're looking at an entity that is contending + // for current player sound. the closest entity to player wins. + + if (FEnvSoundInRange(pev, VARS(pentPlayer), &flRange)) + { + if (flRange < pPlayer->m_flSndRange || pPlayer->m_flSndRange == 0) + { + // new entity is closer to player, so it wins. + pPlayer->m_pentSndLast = ENT(pev); + pPlayer->m_flSndRoomtype = m_flRoomtype; + pPlayer->m_flSndRange = flRange; + + // send room_type command to player's server. + // this should be a rare event - once per change of room_type + // only! + + //CLIENT_COMMAND(pentPlayer, "room_type %f", m_flRoomtype); + + MESSAGE_BEGIN( MSG_ONE, SVC_ROOMTYPE, NULL, pentPlayer ); // use the magic #1 for "one client" + WRITE_SHORT( (short)m_flRoomtype ); // sequence number + MESSAGE_END(); + + // crank up nextthink rate for new active sound entity + // by falling through to think_fast... + } + // player is not closer to the contending sound entity, + // just fall through to think_fast. this effectively + // cranks up the think_rate of entities near the player. + } + + // player is in pvs of sound entity, but either not visible or + // not in range. do nothing, fall through to think_fast... + +env_sound_Think_fast: + pev->nextthink = gpGlobals->time + 0.25; + return; + +env_sound_Think_slow: + pev->nextthink = gpGlobals->time + 0.75; + return; +} + +// +// env_sound - spawn a sound entity that will set player roomtype +// when player moves in range and sight. +// +// +void CEnvSound :: Spawn( ) +{ + // spread think times + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(0.0, 0.5); +} + +// ==================== SENTENCE GROUPS, UTILITY FUNCTIONS ====================================== + +#define CSENTENCE_LRU_MAX 32 // max number of elements per sentence group + +// group of related sentences + +typedef struct sentenceg +{ + char szgroupname[CBSENTENCENAME_MAX]; + int count; + unsigned char rgblru[CSENTENCE_LRU_MAX]; + +} SENTENCEG; + +#define CSENTENCEG_MAX 200 // max number of sentence groups +// globals + +SENTENCEG rgsentenceg[CSENTENCEG_MAX]; +int fSentencesInit = FALSE; + +char gszallsentencenames[CVOXFILESENTENCEMAX][CBSENTENCENAME_MAX]; +int gcallsentences = 0; + +// randomize list of sentence name indices + +void USENTENCEG_InitLRU(unsigned char *plru, int count) +{ + int i, j, k; + unsigned char temp; + + if (!fSentencesInit) + return; + + if (count > CSENTENCE_LRU_MAX) + count = CSENTENCE_LRU_MAX; + + for (i = 0; i < count; i++) + plru[i] = (unsigned char) i; + + // randomize array + for (i = 0; i < (count * 4); i++) + { + j = RANDOM_LONG(0,count-1); + k = RANDOM_LONG(0,count-1); + temp = plru[j]; + plru[j] = plru[k]; + plru[k] = temp; + } +} + +// ignore lru. pick next sentence from sentence group. Go in order until we hit the last sentence, +// then repeat list if freset is true. If freset is false, then repeat last sentence. +// ipick is passed in as the requested sentence ordinal. +// ipick 'next' is returned. +// return of -1 indicates an error. + +int USENTENCEG_PickSequential(int isentenceg, char *szfound, int ipick, int freset) +{ + char *szgroupname; + unsigned char count; + char sznum[8]; + + if (!fSentencesInit) + return -1; + + if (isentenceg < 0) + return -1; + + szgroupname = rgsentenceg[isentenceg].szgroupname; + count = rgsentenceg[isentenceg].count; + + if (count == 0) + return -1; + + if (ipick >= count) + ipick = count-1; + + strcpy(szfound, "!"); + strcat(szfound, szgroupname); + itoa(ipick, sznum, 10); + strcat(szfound, sznum); + + if (ipick >= count) + { + if (freset) + // reset at end of list + return 0; + else + return count; + } + + return ipick + 1; +} + + + +// pick a random sentence from rootname0 to rootnameX. +// picks from the rgsentenceg[isentenceg] least +// recently used, modifies lru array. returns the sentencename. +// note, lru must be seeded with 0-n randomized sentence numbers, with the +// rest of the lru filled with -1. The first integer in the lru is +// actually the size of the list. Returns ipick, the ordinal +// of the picked sentence within the group. + +int USENTENCEG_Pick(int isentenceg, char *szfound) +{ + char *szgroupname; + unsigned char *plru; + unsigned char i; + unsigned char count; + char sznum[8]; + unsigned char ipick; + int ffound = FALSE; + + if (!fSentencesInit) + return -1; + + if (isentenceg < 0) + return -1; + + szgroupname = rgsentenceg[isentenceg].szgroupname; + count = rgsentenceg[isentenceg].count; + plru = rgsentenceg[isentenceg].rgblru; + + while (!ffound) + { + for (i = 0; i < count; i++) + if (plru[i] != 0xFF) + { + ipick = plru[i]; + plru[i] = 0xFF; + ffound = TRUE; + break; + } + + if (!ffound) + USENTENCEG_InitLRU(plru, count); + else + { + strcpy(szfound, "!"); + strcat(szfound, szgroupname); + itoa(ipick, sznum, 10); + strcat(szfound, sznum); + return ipick; + } + } + return -1; +} + +// ===================== SENTENCE GROUPS, MAIN ROUTINES ======================== + +// Given sentence group rootname (name without number suffix), +// get sentence group index (isentenceg). Returns -1 if no such name. + +int SENTENCEG_GetIndex(const char *szgroupname) +{ + int i; + + if (!fSentencesInit || !szgroupname) + return -1; + + // search rgsentenceg for match on szgroupname + + i = 0; + while (rgsentenceg[i].count) + { + if (!strcmp(szgroupname, rgsentenceg[i].szgroupname)) + return i; + i++; + } + + return -1; +} + +// given sentence group index, play random sentence for given entity. +// returns ipick - which sentence was picked to +// play from the group. Ipick is only needed if you plan on stopping +// the sound before playback is done (see SENTENCEG_Stop). + +int SENTENCEG_PlayRndI(edict_t *entity, int isentenceg, + float volume, float attenuation, int flags, int pitch) +{ + char name[64]; + int ipick; + + if (!fSentencesInit) + return -1; + + name[0] = 0; + + ipick = USENTENCEG_Pick(isentenceg, name); + if (ipick > 0 && name) + EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch); + return ipick; +} + +// same as above, but takes sentence group name instead of index + +int SENTENCEG_PlayRndSz(edict_t *entity, const char *szgroupname, + float volume, float attenuation, int flags, int pitch) +{ + char name[64]; + int ipick; + int isentenceg; + + if (!fSentencesInit) + return -1; + + name[0] = 0; + + isentenceg = SENTENCEG_GetIndex(szgroupname); + if (isentenceg < 0) + { + ALERT( at_console, "No such sentence group %s\n", szgroupname ); + return -1; + } + + ipick = USENTENCEG_Pick(isentenceg, name); + if (ipick >= 0 && name[0]) + EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch); + + return ipick; +} + +// play sentences in sequential order from sentence group. Reset after last sentence. + +int SENTENCEG_PlaySequentialSz(edict_t *entity, const char *szgroupname, + float volume, float attenuation, int flags, int pitch, int ipick, int freset) +{ + char name[64]; + int ipicknext; + int isentenceg; + + if (!fSentencesInit) + return -1; + + name[0] = 0; + + isentenceg = SENTENCEG_GetIndex(szgroupname); + if (isentenceg < 0) + return -1; + + ipicknext = USENTENCEG_PickSequential(isentenceg, name, ipick, freset); + if (ipicknext >= 0 && name[0]) + EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch); + return ipicknext; +} + + +// for this entity, for the given sentence within the sentence group, stop +// the sentence. + +void SENTENCEG_Stop(edict_t *entity, int isentenceg, int ipick) +{ + char buffer[64]; + char sznum[8]; + + if (!fSentencesInit) + return; + + if (isentenceg < 0 || ipick < 0) + return; + + strcpy(buffer, "!"); + strcat(buffer, rgsentenceg[isentenceg].szgroupname); + itoa(ipick, sznum, 10); + strcat(buffer, sznum); + + STOP_SOUND(entity, CHAN_VOICE, buffer); +} + +// open sentences.txt, scan for groups, build rgsentenceg +// Should be called from world spawn, only works on the +// first call and is ignored subsequently. + +void SENTENCEG_Init() +{ + char buffer[512]; + char szgroup[64]; + int i, j; + int isentencegs; + + if (fSentencesInit) + return; + + memset(gszallsentencenames, 0, CVOXFILESENTENCEMAX * CBSENTENCENAME_MAX); + gcallsentences = 0; + + memset(rgsentenceg, 0, CSENTENCEG_MAX * sizeof(SENTENCEG)); + memset(buffer, 0, 512); + memset(szgroup, 0, 64); + isentencegs = -1; + + + int filePos = 0, fileSize; + byte *pMemFile = g_engfuncs.pfnLoadFileForMe( "sound/sentences.txt", &fileSize ); + if ( !pMemFile ) + return; + + // for each line in the file... + while ( memfgets(pMemFile, fileSize, filePos, buffer, 511) != NULL ) + { + // skip whitespace + i = 0; + while(buffer[i] && buffer[i] == ' ') + i++; + + if (!buffer[i]) + continue; + + if (buffer[i] == '/' || !isalpha(buffer[i])) + continue; + + // get sentence name + j = i; + while (buffer[j] && buffer[j] != ' ') + j++; + + if (!buffer[j]) + continue; + + if (gcallsentences > CVOXFILESENTENCEMAX) + { + ALERT (at_error, "Too many sentences in sentences.txt!\n"); + break; + } + + // null-terminate name and save in sentences array + buffer[j] = 0; + const char *pString = buffer + i; + + if ( strlen( pString ) >= CBSENTENCENAME_MAX ) + ALERT( at_warning, "Sentence %s longer than %d letters\n", pString, CBSENTENCENAME_MAX-1 ); + + strcpy( gszallsentencenames[gcallsentences++], pString ); + + j--; + if (j <= i) + continue; + if (!isdigit(buffer[j])) + continue; + + // cut out suffix numbers + while (j > i && isdigit(buffer[j])) + j--; + + if (j <= i) + continue; + + buffer[j+1] = 0; + + // if new name doesn't match previous group name, + // make a new group. + + if (strcmp(szgroup, &(buffer[i]))) + { + // name doesn't match with prev name, + // copy name into group, init count to 1 + isentencegs++; + if (isentencegs >= CSENTENCEG_MAX) + { + ALERT (at_error, "Too many sentence groups in sentences.txt!\n"); + break; + } + + strcpy(rgsentenceg[isentencegs].szgroupname, &(buffer[i])); + rgsentenceg[isentencegs].count = 1; + + strcpy(szgroup, &(buffer[i])); + + continue; + } + else + { + //name matches with previous, increment group count + if (isentencegs >= 0) + rgsentenceg[isentencegs].count++; + } + } + + g_engfuncs.pfnFreeFile( pMemFile ); + + fSentencesInit = TRUE; + + // init lru lists + + i = 0; + + while (rgsentenceg[i].count && i < CSENTENCEG_MAX) + { + USENTENCEG_InitLRU(&(rgsentenceg[i].rgblru[0]), rgsentenceg[i].count); + i++; + } + +} + +// convert sentence (sample) name to !sentencenum, return !sentencenum + +int SENTENCEG_Lookup(const char *sample, char *sentencenum) +{ + char sznum[8]; + +//#ifdef GERMANY +// return -1; // no constructed sentences in international versions +//#endif // GERMANY + + int i; + // this is a sentence name; lookup sentence number + // and give to engine as string. + for (i = 0; i < gcallsentences; i++) + if (!stricmp(gszallsentencenames[i], sample+1)) + { + if (sentencenum) + { + strcpy(sentencenum, "!"); + itoa(i, sznum, 10); + strcat(sentencenum, sznum); + } + return i; + } + // sentence name not found! + return -1; +} + +void EMIT_SOUND_DYN(edict_t *entity, int channel, const char *sample, float volume, float attenuation, + int flags, int pitch) +{ + if (sample && *sample == '!') + { + char name[32]; + if (SENTENCEG_Lookup(sample, name) >= 0) + EMIT_SOUND_DYN2(entity, channel, name, volume, attenuation, flags, pitch); + else + ALERT( at_aiconsole, "Unable to find %s in sentences.txt\n", sample ); + } + else + 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 +// texture name to a material type. Play footstep sound based +// on material type. + +int fTextureTypeInit = FALSE; + +#define CTEXTURESMAX 512 // max number of textures loaded + +int gcTextures = 0; +char grgszTextureName[CTEXTURESMAX][CBTEXTURENAMEMAX]; // texture names +char grgchTextureType[CTEXTURESMAX]; // parallel array of texture types + +// open materials.txt, get size, alloc space, +// save in array. Only works first time called, +// ignored on subsequent calls. + +static char *memfgets( byte *pMemFile, int fileSize, int &filePos, char *pBuffer, int bufferSize ) +{ + // Bullet-proofing + if ( !pMemFile || !pBuffer ) + return NULL; + + if ( filePos >= fileSize ) + return NULL; + + int i = filePos; + int last = fileSize; + + // fgets always NULL terminates, so only read bufferSize-1 characters + if ( last - filePos > (bufferSize-1) ) + last = filePos + (bufferSize-1); + + int stop = 0; + + // Stop at the next newline (inclusive) or end of buffer + while ( i < last && !stop ) + { + if ( pMemFile[i] == '\n' ) + stop = 1; + i++; + } + + + // If we actually advanced the pointer, copy it over + if ( i != filePos ) + { + // We read in size bytes + int size = i - filePos; + // copy it out + memcpy( pBuffer, pMemFile + filePos, sizeof(byte)*size ); + + // If the buffer isn't full, terminate (this is always true) + if ( size < bufferSize ) + pBuffer[size] = 0; + + // Update file pointer + filePos = i; + return pBuffer; + } + + // No data read, bail + return NULL; +} + + +void TEXTURETYPE_Init() +{ + char buffer[512]; + int i, j; + byte *pMemFile; + int fileSize, filePos; + + 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 ); + if ( !pMemFile ) + return; + + // for each line in the file... + while (memfgets(pMemFile, fileSize, filePos, buffer, 511) != NULL && (gcTextures < CTEXTURESMAX)) + { + // skip whitespace + i = 0; + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // skip comment lines + if (buffer[i] == '/' || !isalpha(buffer[i])) + continue; + + // get texture type + grgchTextureType[gcTextures] = toupper(buffer[i++]); + + // skip whitespace + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // get sentence name + j = i; + while (buffer[j] && !isspace(buffer[j])) + j++; + + if (!buffer[j]) + continue; + + // null-terminate name and save in sentences array + j = min (j, CBTEXTURENAMEMAX-1+i); + buffer[j] = 0; + strcpy(&(grgszTextureName[gcTextures++][0]), &(buffer[i])); + } + + g_engfuncs.pfnFreeFile( pMemFile ); + + fTextureTypeInit = TRUE; +} + +// given texture name, find texture type +// if not found, return type 'concrete' + +// NOTE: this routine should ONLY be called if the +// current texture under the player changes! + +char TEXTURETYPE_Find(char *name) +{ + // CONSIDER: pre-sort texture names and perform faster binary search here + + for (int i = 0; i < gcTextures; i++) + { + if (!_strnicmp(name, &(grgszTextureName[i][0]), CBTEXTURENAMEMAX-1)) + return (grgchTextureType[i]); + } + + return CHAR_TEX_CONCRETE; +} + +// 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 + +float TEXTURETYPE_PlaySound(TraceResult *ptr, Vector vecSrc, Vector vecEnd, int iBulletType) +{ +// hit the world, try to play sound based on texture material type + + char chTextureType; + float fvol; + float fvolbar; + char szbuffer[64]; + const char *pTextureName; + float rgfl1[3]; + float rgfl2[3]; + char *rgsz[4]; + int cnt; + float fattn = ATTN_NORM; + + if ( !g_pGameRules->PlayTextureSounds() ) + return 0.0; + + CBaseEntity *pEntity = CBaseEntity::Instance(ptr->pHit); + + chTextureType = 0; + + if (pEntity && pEntity->Classify() != CLASS_NONE && pEntity->Classify() != CLASS_MACHINE) + // hit body + chTextureType = CHAR_TEX_FLESH; + else + { + // hit world + + // find texture under strike, get material type + + // copy trace vector into array for trace_texture + + vecSrc.CopyToArray(rgfl1); + vecEnd.CopyToArray(rgfl2); + + // get texture from entity or world (world is ent(0)) + if (pEntity) + pTextureName = TRACE_TEXTURE( ENT(pEntity->pev), rgfl1, rgfl2 ); + else + pTextureName = TRACE_TEXTURE( ENT(0), rgfl1, rgfl2 ); + + if ( pTextureName ) + { + // strip leading '-0' or '+0~' or '{' or '!' + if (*pTextureName == '-' || *pTextureName == '+') + pTextureName += 2; + + if (*pTextureName == '{' || *pTextureName == '!' || *pTextureName == '~' || *pTextureName == ' ') + pTextureName++; + // '}}' + strcpy(szbuffer, pTextureName); + szbuffer[CBTEXTURENAMEMAX - 1] = 0; + + // ALERT ( at_console, "texture hit: %s\n", szbuffer); + + // get texture type + chTextureType = TEXTURETYPE_Find(szbuffer); + } + } + + switch (chTextureType) + { + default: + case CHAR_TEX_CONCRETE: fvol = 0.9; fvolbar = 0.6; + rgsz[0] = "player/pl_step1.wav"; + rgsz[1] = "player/pl_step2.wav"; + cnt = 2; + break; + case CHAR_TEX_METAL: fvol = 0.9; fvolbar = 0.3; + rgsz[0] = "player/pl_metal1.wav"; + rgsz[1] = "player/pl_metal2.wav"; + cnt = 2; + break; + case CHAR_TEX_DIRT: fvol = 0.9; fvolbar = 0.1; + rgsz[0] = "player/pl_dirt1.wav"; + rgsz[1] = "player/pl_dirt2.wav"; + rgsz[2] = "player/pl_dirt3.wav"; + cnt = 3; + break; + case CHAR_TEX_VENT: fvol = 0.5; fvolbar = 0.3; + rgsz[0] = "player/pl_duct1.wav"; + rgsz[1] = "player/pl_duct1.wav"; + cnt = 2; + break; + case CHAR_TEX_GRATE: fvol = 0.9; fvolbar = 0.5; + rgsz[0] = "player/pl_grate1.wav"; + rgsz[1] = "player/pl_grate4.wav"; + cnt = 2; + break; + case CHAR_TEX_TILE: fvol = 0.8; fvolbar = 0.2; + rgsz[0] = "player/pl_tile1.wav"; + rgsz[1] = "player/pl_tile3.wav"; + rgsz[2] = "player/pl_tile2.wav"; + rgsz[3] = "player/pl_tile4.wav"; + cnt = 4; + break; + case CHAR_TEX_SLOSH: fvol = 0.9; fvolbar = 0.0; + rgsz[0] = "player/pl_slosh1.wav"; + rgsz[1] = "player/pl_slosh3.wav"; + rgsz[2] = "player/pl_slosh2.wav"; + rgsz[3] = "player/pl_slosh4.wav"; + cnt = 4; + break; + case CHAR_TEX_WOOD: fvol = 0.9; fvolbar = 0.2; + rgsz[0] = "debris/wood1.wav"; + rgsz[1] = "debris/wood2.wav"; + rgsz[2] = "debris/wood3.wav"; + cnt = 3; + break; + case CHAR_TEX_GLASS: + case CHAR_TEX_COMPUTER: + fvol = 0.8; fvolbar = 0.2; + rgsz[0] = "debris/glass1.wav"; + rgsz[1] = "debris/glass2.wav"; + rgsz[2] = "debris/glass3.wav"; + 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"; + fattn = 1.0; + cnt = 2; + break; + } + + // did we hit a breakable? + + if (pEntity && FClassnameIs(pEntity->pev, "func_breakable")) + { + // drop volumes, the object will already play a damaged sound + fvol /= 1.5; + fvolbar /= 2.0; + } + else if (chTextureType == CHAR_TEX_COMPUTER) + { + // play random spark if computer + + if ( ptr->flFraction != 1.0 && RANDOM_LONG(0,1)) + { + UTIL_Sparks( ptr->vecEndPos ); + + float flVolume = RANDOM_FLOAT ( 0.7 , 1.0 );//random volume range + switch ( RANDOM_LONG(0,1) ) + { + case 0: UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, "buttons/spark5.wav", flVolume, ATTN_NORM, 0, 100); break; + case 1: UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, "buttons/spark6.wav", flVolume, ATTN_NORM, 0, 100); break; + // case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark5.wav", flVolume, ATTN_NORM); break; + // case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark6.wav", flVolume, ATTN_NORM); break; + } + } + } + + // play material hit sound + UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, rgsz[RANDOM_LONG(0,cnt-1)], fvol, fattn, 0, 96 + RANDOM_LONG(0,0xf)); + //EMIT_SOUND_DYN( ENT(m_pPlayer->pev), CHAN_WEAPON, rgsz[RANDOM_LONG(0,cnt-1)], fvol, ATTN_NORM, 0, 96 + RANDOM_LONG(0,0xf)); + + return fvolbar; +} + +// =================================================================================== +// +// Speaker class. Used for announcements per level, for door lock/unlock spoken voice. +// + +class CSpeaker : public CBaseEntity +{ +public: + void KeyValue( KeyValueData* pkvd); + void Spawn( void ); + void Precache( void ); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT SpeakerThink( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + + int m_preset; // preset number +}; + +LINK_ENTITY_TO_CLASS( speaker, CSpeaker ); +TYPEDESCRIPTION CSpeaker::m_SaveData[] = +{ + DEFINE_FIELD( CSpeaker, m_preset, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CSpeaker, CBaseEntity ); + +// +// ambient_generic - general-purpose user-defined static sound +// +void CSpeaker :: Spawn( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + + if ( !m_preset && (FStringNull( pev->message ) || strlen( szSoundFile ) < 1 )) + { + ALERT( at_error, "SPEAKER with no Level/Sentence! at: %f, %f, %f\n", pev->origin.x, pev->origin.y, pev->origin.z ); + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CBaseEntity::SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + + SetThink(&CSpeaker::SpeakerThink); + pev->nextthink = 0.0; + + // allow on/off switching via 'use' function. + + SetUse ( &CSpeaker::ToggleUse ); + + Precache( ); +} + +#define ANNOUNCE_MINUTES_MIN 0.25 +#define ANNOUNCE_MINUTES_MAX 2.25 + +void CSpeaker :: Precache( void ) +{ + if ( !FBitSet (pev->spawnflags, SPEAKER_START_SILENT ) ) + // set first announcement time for random n second + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(5.0, 15.0); +} +void CSpeaker :: SpeakerThink( void ) +{ + char *szSoundFile = NULL; + float flvolume = pev->health * 0.1; + 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) + { + // go lookup preset text, assign szSoundFile + switch (m_preset) + { + case 1: szSoundFile = "C1A0_"; break; + case 2: szSoundFile = "C1A1_"; break; + case 3: szSoundFile = "C1A2_"; break; + case 4: szSoundFile = "C1A3_"; break; + case 5: szSoundFile = "C1A4_"; break; + case 6: szSoundFile = "C2A1_"; break; + case 7: szSoundFile = "C2A2_"; break; + case 8: szSoundFile = "C2A3_"; break; + case 9: szSoundFile = "C2A4_"; break; + case 10: szSoundFile = "C2A5_"; break; + case 11: szSoundFile = "C3A1_"; break; + case 12: szSoundFile = "C3A2_"; break; + } + } else + szSoundFile = (char*) STRING(pev->message); + + if (szSoundFile[0] == '!') + { + // play single sentence, one shot + UTIL_EmitAmbientSound ( ENT(pev), pev->origin, szSoundFile, + flvolume, flattenuation, flags, pitch); + + // shut off and reset + pev->nextthink = 0.0; + } + else + { + // make random announcement from sentence group + + if (SENTENCEG_PlayRndSz(ENT(pev), szSoundFile, flvolume, flattenuation, flags, pitch) < 0) + ALERT(at_console, "Level Design Error!\nSPEAKER has bad sentence group name: %s\n",szSoundFile); + + // 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; +} + + +// +// ToggleUse - if an announcement is pending, cancel it. If no announcement is pending, start one. +// +void CSpeaker :: ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int fActive = (pev->nextthink > 0.0); + + // fActive is TRUE only if an announcement is pending + + if ( useType != USE_TOGGLE ) + { + // ignore if we're just turning something on that's already on, or + // turning something off that's already off. + if ( (fActive && useType == USE_ON) || (!fActive && useType == USE_OFF) ) + return; + } + + if ( useType == USE_ON ) + { + // turn on announcements + pev->nextthink = gpGlobals->time + 0.1; + return; + } + + if ( useType == USE_OFF ) + { + // turn off announcements + pev->nextthink = 0.0; + return; + + } + + // Toggle announcements + + + if ( fActive ) + { + // turn off announcements + pev->nextthink = 0.0; + } + else + { + // turn on announcements + pev->nextthink = gpGlobals->time + 0.1; + } +} + +// KeyValue - load keyvalue pairs into member data +// NOTE: called BEFORE spawn! + +void CSpeaker :: KeyValue( KeyValueData *pkvd ) +{ + + // preset + if (FStrEq(pkvd->szKeyName, "preset")) + { + m_preset = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} diff --git a/hlsdk/ricochet/dlls/soundent.cpp b/hlsdk/ricochet/dlls/soundent.cpp new file mode 100644 index 00000000..ad5ef337 --- /dev/null +++ b/hlsdk/ricochet/dlls/soundent.cpp @@ -0,0 +1,379 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "soundent.h" + + +LINK_ENTITY_TO_CLASS( soundent, CSoundEnt ); + +CSoundEnt *pSoundEnt; + +//========================================================= +// CSound - Clear - zeros all fields for a sound +//========================================================= +void CSound :: Clear ( void ) +{ + m_vecOrigin = g_vecZero; + m_iType = 0; + m_iVolume = 0; + m_flExpireTime = 0; + m_iNext = SOUNDLIST_EMPTY; + m_iNextAudible = 0; +} + +//========================================================= +// Reset - clears the volume, origin, and type for a sound, +// but doesn't expire or unlink it. +//========================================================= +void CSound :: Reset ( void ) +{ + m_vecOrigin = g_vecZero; + m_iType = 0; + m_iVolume = 0; + m_iNext = SOUNDLIST_EMPTY; +} + +//========================================================= +// FIsSound - returns TRUE if the sound is an Audible sound +//========================================================= +BOOL CSound :: FIsSound ( void ) +{ + if ( m_iType & ( bits_SOUND_COMBAT | bits_SOUND_WORLD | bits_SOUND_PLAYER | bits_SOUND_DANGER ) ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// FIsScent - returns TRUE if the sound is actually a scent +//========================================================= +BOOL CSound :: FIsScent ( void ) +{ + if ( m_iType & ( bits_SOUND_CARCASS | bits_SOUND_MEAT | bits_SOUND_GARBAGE ) ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// Spawn +//========================================================= +void CSoundEnt :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + Initialize(); + + pev->nextthink = gpGlobals->time + 1; +} + +//========================================================= +// Think - at interval, the entire active sound list is checked +// for sounds that have ExpireTimes less than or equal +// to the current world time, and these sounds are deallocated. +//========================================================= +void CSoundEnt :: Think ( void ) +{ + int iSound; + int iPreviousSound; + + pev->nextthink = gpGlobals->time + 0.3;// how often to check the sound list. + + iPreviousSound = SOUNDLIST_EMPTY; + iSound = m_iActiveSound; + + while ( iSound != SOUNDLIST_EMPTY ) + { + if ( m_SoundPool[ iSound ].m_flExpireTime <= gpGlobals->time && m_SoundPool[ iSound ].m_flExpireTime != SOUND_NEVER_EXPIRE ) + { + int iNext = m_SoundPool[ iSound ].m_iNext; + + // move this sound back into the free list + FreeSound( iSound, iPreviousSound ); + + iSound = iNext; + } + else + { + iPreviousSound = iSound; + iSound = m_SoundPool[ iSound ].m_iNext; + } + } + + if ( m_fShowReport ) + { + ALERT ( at_aiconsole, "Soundlist: %d / %d (%d)\n", ISoundsInList( SOUNDLISTTYPE_ACTIVE ),ISoundsInList( SOUNDLISTTYPE_FREE ), ISoundsInList( SOUNDLISTTYPE_ACTIVE ) - m_cLastActiveSounds ); + m_cLastActiveSounds = ISoundsInList ( SOUNDLISTTYPE_ACTIVE ); + } + +} + +//========================================================= +// Precache - dummy function +//========================================================= +void CSoundEnt :: Precache ( void ) +{ +} + +//========================================================= +// FreeSound - clears the passed active sound and moves it +// to the top of the free list. TAKE CARE to only call this +// function for sounds in the Active list!! +//========================================================= +void CSoundEnt :: FreeSound ( int iSound, int iPrevious ) +{ + if ( !pSoundEnt ) + { + // no sound ent! + return; + } + + if ( iPrevious != SOUNDLIST_EMPTY ) + { + // iSound is not the head of the active list, so + // must fix the index for the Previous sound +// pSoundEnt->m_SoundPool[ iPrevious ].m_iNext = m_SoundPool[ iSound ].m_iNext; + pSoundEnt->m_SoundPool[ iPrevious ].m_iNext = pSoundEnt->m_SoundPool[ iSound ].m_iNext; + } + else + { + // the sound we're freeing IS the head of the active list. + pSoundEnt->m_iActiveSound = pSoundEnt->m_SoundPool [ iSound ].m_iNext; + } + + // make iSound the head of the Free list. + pSoundEnt->m_SoundPool[ iSound ].m_iNext = pSoundEnt->m_iFreeSound; + pSoundEnt->m_iFreeSound = iSound; +} + +//========================================================= +// IAllocSound - moves a sound from the Free list to the +// Active list returns the index of the alloc'd sound +//========================================================= +int CSoundEnt :: IAllocSound( void ) +{ + int iNewSound; + + if ( m_iFreeSound == SOUNDLIST_EMPTY ) + { + // no free sound! + ALERT ( at_console, "Free Sound List is full!\n" ); + return SOUNDLIST_EMPTY; + } + + // there is at least one sound available, so move it to the + // Active sound list, and return its SoundPool index. + + iNewSound = m_iFreeSound;// copy the index of the next free sound + + m_iFreeSound = m_SoundPool[ m_iFreeSound ].m_iNext;// move the index down into the free list. + + m_SoundPool[ iNewSound ].m_iNext = m_iActiveSound;// point the new sound at the top of the active list. + + m_iActiveSound = iNewSound;// now make the new sound the top of the active list. You're done. + + return iNewSound; +} + +//========================================================= +// InsertSound - Allocates a free sound and fills it with +// sound info. +//========================================================= +void CSoundEnt :: InsertSound ( int iType, const Vector &vecOrigin, int iVolume, float flDuration ) +{ + int iThisSound; + + if ( !pSoundEnt ) + { + // no sound ent! + return; + } + + iThisSound = pSoundEnt->IAllocSound(); + + if ( iThisSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_console, "Could not AllocSound() for InsertSound() (DLL)\n" ); + return; + } + + pSoundEnt->m_SoundPool[ iThisSound ].m_vecOrigin = vecOrigin; + pSoundEnt->m_SoundPool[ iThisSound ].m_iType = iType; + pSoundEnt->m_SoundPool[ iThisSound ].m_iVolume = iVolume; + pSoundEnt->m_SoundPool[ iThisSound ].m_flExpireTime = gpGlobals->time + flDuration; +} + +//========================================================= +// Initialize - clears all sounds and moves them into the +// free sound list. +//========================================================= +void CSoundEnt :: Initialize ( void ) +{ + int i; + int iSound; + + m_cLastActiveSounds = 0; + m_iFreeSound = 0; + m_iActiveSound = SOUNDLIST_EMPTY; + + for ( i = 0 ; i < MAX_WORLD_SOUNDS ; i++ ) + {// clear all sounds, and link them into the free sound list. + m_SoundPool[ i ].Clear(); + m_SoundPool[ i ].m_iNext = i + 1; + } + + m_SoundPool[ i - 1 ].m_iNext = SOUNDLIST_EMPTY;// terminate the list here. + + + // now reserve enough sounds for each client + for ( i = 0 ; i < gpGlobals->maxClients ; i++ ) + { + iSound = pSoundEnt->IAllocSound(); + + if ( iSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_console, "Could not AllocSound() for Client Reserve! (DLL)\n" ); + return; + } + + pSoundEnt->m_SoundPool[ iSound ].m_flExpireTime = SOUND_NEVER_EXPIRE; + } + + if ( CVAR_GET_FLOAT("displaysoundlist") == 1 ) + { + m_fShowReport = TRUE; + } + else + { + m_fShowReport = FALSE; + } +} + +//========================================================= +// ISoundsInList - returns the number of sounds in the desired +// sound list. +//========================================================= +int CSoundEnt :: ISoundsInList ( int iListType ) +{ + int i; + int iThisSound = 0; + + if ( iListType == SOUNDLISTTYPE_FREE ) + { + iThisSound = m_iFreeSound; + } + else if ( iListType == SOUNDLISTTYPE_ACTIVE ) + { + iThisSound = m_iActiveSound; + } + else + { + ALERT ( at_console, "Unknown Sound List Type!\n" ); + } + + if ( iThisSound == SOUNDLIST_EMPTY ) + { + return 0; + } + + i = 0; + + while ( iThisSound != SOUNDLIST_EMPTY ) + { + i++; + + iThisSound = m_SoundPool[ iThisSound ].m_iNext; + } + + return i; +} + +//========================================================= +// ActiveList - returns the head of the active sound list +//========================================================= +int CSoundEnt :: ActiveList ( void ) +{ + if ( !pSoundEnt ) + { + return SOUNDLIST_EMPTY; + } + + return pSoundEnt->m_iActiveSound; +} + +//========================================================= +// FreeList - returns the head of the free sound list +//========================================================= +int CSoundEnt :: FreeList ( void ) +{ + if ( !pSoundEnt ) + { + return SOUNDLIST_EMPTY; + } + + return pSoundEnt->m_iFreeSound; +} + +//========================================================= +// SoundPointerForIndex - returns a pointer to the instance +// of CSound at index's position in the sound pool. +//========================================================= +CSound* CSoundEnt :: SoundPointerForIndex( int iIndex ) +{ + if ( !pSoundEnt ) + { + return NULL; + } + + if ( iIndex > ( MAX_WORLD_SOUNDS - 1 ) ) + { + ALERT ( at_console, "SoundPointerForIndex() - Index too large!\n" ); + return NULL; + } + + if ( iIndex < 0 ) + { + ALERT ( at_console, "SoundPointerForIndex() - Index < 0!\n" ); + return NULL; + } + + return &pSoundEnt->m_SoundPool[ iIndex ]; +} + +//========================================================= +// Clients are numbered from 1 to MAXCLIENTS, but the client +// reserved sounds in the soundlist are from 0 to MAXCLIENTS - 1, +// so this function ensures that a client gets the proper index +// to his reserved sound in the soundlist. +//========================================================= +int CSoundEnt :: ClientSoundIndex ( edict_t *pClient ) +{ + int iReturn = ENTINDEX( pClient ) - 1; + +#ifdef _DEBUG + if ( iReturn < 0 || iReturn > gpGlobals->maxClients ) + { + ALERT ( at_console, "** ClientSoundIndex returning a bogus value! **\n" ); + } +#endif // _DEBUG + + return iReturn; +} diff --git a/hlsdk/ricochet/dlls/soundent.h b/hlsdk/ricochet/dlls/soundent.h new file mode 100644 index 00000000..1967e979 --- /dev/null +++ b/hlsdk/ricochet/dlls/soundent.h @@ -0,0 +1,95 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +//========================================================= +// Soundent.h - the entity that spawns when the world +// spawns, and handles the world's active and free sound +// lists. +//========================================================= + +#define MAX_WORLD_SOUNDS 64 // maximum number of sounds handled by the world at one time. + +#define bits_SOUND_NONE 0 +#define bits_SOUND_COMBAT ( 1 << 0 )// gunshots, explosions +#define bits_SOUND_WORLD ( 1 << 1 )// door opening/closing, glass breaking +#define bits_SOUND_PLAYER ( 1 << 2 )// all noises generated by player. walking, shooting, falling, splashing +#define bits_SOUND_CARCASS ( 1 << 3 )// dead body +#define bits_SOUND_MEAT ( 1 << 4 )// gib or pork chop +#define bits_SOUND_DANGER ( 1 << 5 )// pending danger. Grenade that is about to explode, explosive barrel that is damaged, falling crate +#define bits_SOUND_GARBAGE ( 1 << 6 )// trash cans, banana peels, old fast food bags. + +#define bits_ALL_SOUNDS 0xFFFFFFFF + +#define SOUNDLIST_EMPTY -1 + +#define SOUNDLISTTYPE_FREE 1// identifiers passed to functions that can operate on either list, to indicate which list to operate on. +#define SOUNDLISTTYPE_ACTIVE 2 + +#define SOUND_NEVER_EXPIRE -1 // with this set as a sound's ExpireTime, the sound will never expire. + +//========================================================= +// CSound - an instance of a sound in the world. +//========================================================= +class CSound +{ +public: + + void Clear ( void ); + void Reset ( void ); + + Vector m_vecOrigin; // sound's location in space + int m_iType; // what type of sound this is + int m_iVolume; // how loud the sound is + float m_flExpireTime; // when the sound should be purged from the list + int m_iNext; // index of next sound in this list ( Active or Free ) + int m_iNextAudible; // temporary link that monsters use to build a list of audible sounds + + BOOL FIsSound( void ); + BOOL FIsScent( void ); +}; + +//========================================================= +// CSoundEnt - a single instance of this entity spawns when +// the world spawns. The SoundEnt's job is to update the +// world's Free and Active sound lists. +//========================================================= +class CSoundEnt : public CBaseEntity +{ +public: + + void Precache ( void ); + void Spawn( void ); + void Think( void ); + void Initialize ( void ); + + static void InsertSound ( int iType, const Vector &vecOrigin, int iVolume, float flDuration ); + static void FreeSound ( int iSound, int iPrevious ); + static int ActiveList( void );// return the head of the active list + static int FreeList( void );// return the head of the free list + static CSound* SoundPointerForIndex( int iIndex );// return a pointer for this index in the sound list + static int ClientSoundIndex ( edict_t *pClient ); + + BOOL IsEmpty( void ) { return m_iActiveSound == SOUNDLIST_EMPTY; } + int ISoundsInList ( int iListType ); + int IAllocSound ( void ); + virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } + + int m_iFreeSound; // index of the first sound in the free sound list + int m_iActiveSound; // indes of the first sound in the active sound list + int m_cLastActiveSounds; // keeps track of the number of active sounds at the last update. (for diagnostic work) + BOOL m_fShowReport; // if true, dump information about free/active sounds. + +private: + CSound m_SoundPool[ MAX_WORLD_SOUNDS ]; +}; diff --git a/hlsdk/ricochet/dlls/spectator.cpp b/hlsdk/ricochet/dlls/spectator.cpp new file mode 100644 index 00000000..7d7f4a8b --- /dev/null +++ b/hlsdk/ricochet/dlls/spectator.cpp @@ -0,0 +1,149 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +// CBaseSpectator + +// YWB: UNDONE + +// Spectator functions +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "spectator.h" + +/* +=========== +SpectatorConnect + +called when a spectator connects to a server +============ +*/ +void CBaseSpectator::SpectatorConnect(void) +{ + pev->flags = FL_SPECTATOR; + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NOCLIP; + + m_pGoalEnt = NULL; +} + +/* +=========== +SpectatorDisconnect + +called when a spectator disconnects from a server +============ +*/ +void CBaseSpectator::SpectatorDisconnect(void) +{ +} + +/* +================ +SpectatorImpulseCommand + +Called by SpectatorThink if the spectator entered an impulse +================ +*/ +void CBaseSpectator::SpectatorImpulseCommand(void) +{ + static edict_t *pGoal = NULL; + edict_t *pPreviousGoal; + edict_t *pCurrentGoal; + BOOL bFound; + + switch (pev->impulse) + { + case 1: + // teleport the spectator to the next spawn point + // note that if the spectator is tracking, this doesn't do + // much + pPreviousGoal = pGoal; + pCurrentGoal = pGoal; + // Start at the current goal, skip the world, and stop if we looped + // back around + + bFound = FALSE; + while (1) + { + pCurrentGoal = FIND_ENTITY_BY_CLASSNAME(pCurrentGoal, "info_player_deathmatch"); + // Looped around, failure + if (pCurrentGoal == pPreviousGoal) + { + ALERT(at_console, "Could not find a spawn spot.\n"); + break; + } + // Found a non-world entity, set success, otherwise, look for the next one. + if (!FNullEnt(pCurrentGoal)) + { + bFound = TRUE; + break; + } + } + + if (!bFound) // Didn't find a good spot. + break; + + pGoal = pCurrentGoal; + UTIL_SetOrigin( pev, pGoal->v.origin ); + pev->angles = pGoal->v.angles; + pev->fixangle = FALSE; + break; + default: + ALERT(at_console, "Unknown spectator impulse\n"); + break; + } + + pev->impulse = 0; +} + +/* +================ +SpectatorThink + +Called every frame after physics are run +================ +*/ +void CBaseSpectator::SpectatorThink(void) +{ + if (!(pev->flags & FL_SPECTATOR)) + { + pev->flags = FL_SPECTATOR; + } + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NOCLIP; + + if (pev->impulse) + SpectatorImpulseCommand(); +} + +/* +=========== +Spawn + + Called when spectator is initialized: + UNDONE: Is this actually being called because spectators are not allocated in normal fashion? +============ +*/ +void CBaseSpectator::Spawn() +{ + pev->flags = FL_SPECTATOR; + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NOCLIP; + + m_pGoalEnt = NULL; +} diff --git a/hlsdk/ricochet/dlls/spectator.h b/hlsdk/ricochet/dlls/spectator.h new file mode 100644 index 00000000..2f755d6b --- /dev/null +++ b/hlsdk/ricochet/dlls/spectator.h @@ -0,0 +1,27 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Spectator.h + +class CBaseSpectator : public CBaseEntity +{ +public: + void Spawn(); + void SpectatorConnect(void); + void SpectatorDisconnect(void); + void SpectatorThink(void); + +private: + void SpectatorImpulseCommand(void); +}; diff --git a/hlsdk/ricochet/dlls/subs.cpp b/hlsdk/ricochet/dlls/subs.cpp new file mode 100644 index 00000000..b39c39f9 --- /dev/null +++ b/hlsdk/ricochet/dlls/subs.cpp @@ -0,0 +1,582 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== subs.cpp ======================================================== + + frequently used global functions + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "nodes.h" +#include "doors.h" + +extern CGraph WorldGraph; + +extern BOOL FEntIsVisible(entvars_t* pev, entvars_t* pevTarget); + +extern DLL_GLOBAL int g_iSkillLevel; + + +// Landmark class +void CPointEntity :: Spawn( void ) +{ + pev->solid = SOLID_NOT; +// UTIL_SetSize(pev, g_vecZero, g_vecZero); +} + + +class CNullEntity : public CBaseEntity +{ +public: + void Spawn( void ); +}; + + +// Null Entity, remove on startup +void CNullEntity :: Spawn( void ) +{ + REMOVE_ENTITY(ENT(pev)); +} +LINK_ENTITY_TO_CLASS(info_null,CNullEntity); + +class CBaseDMStart : public CPointEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + BOOL IsTriggered( CBaseEntity *pEntity ); + +private: + int m_iPitch; +}; + +// These are the new entry points to entities. +LINK_ENTITY_TO_CLASS(info_player_deathmatch,CBaseDMStart); +LINK_ENTITY_TO_CLASS(info_player_start,CPointEntity); +LINK_ENTITY_TO_CLASS(info_player_spectator,CBaseDMStart); +LINK_ENTITY_TO_CLASS(info_landmark,CPointEntity); + +void CBaseDMStart::Spawn( void ) +{ + pev->angles.x = -m_iPitch; +} + +void CBaseDMStart::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "master")) + { + pev->netname = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitch")) + { + m_iPitch = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +BOOL CBaseDMStart::IsTriggered( CBaseEntity *pEntity ) +{ + BOOL master = UTIL_IsMasterTriggered( pev->netname, pEntity ); + + return master; +} + +// This updates global tables that need to know about entities being removed +void CBaseEntity::UpdateOnRemove( void ) +{ + int i; + + if ( FBitSet( pev->flags, FL_GRAPHED ) ) + { + // this entity was a LinkEnt in the world node graph, so we must remove it from + // the graph since we are removing it from the world. + for ( i = 0 ; i < WorldGraph.m_cLinks ; i++ ) + { + if ( WorldGraph.m_pLinkPool [ i ].m_pLinkEnt == pev ) + { + // if this link has a link ent which is the same ent that is removing itself, remove it! + WorldGraph.m_pLinkPool [ i ].m_pLinkEnt = NULL; + } + } + } + if ( pev->globalname ) + gGlobalState.EntitySetState( pev->globalname, GLOBAL_DEAD ); +} + +// Convenient way to delay removing oneself +void CBaseEntity :: SUB_Remove( void ) +{ + UpdateOnRemove(); + if (pev->health > 0) + { + // this situation can screw up monsters who can't tell their entity pointers are invalid. + pev->health = 0; + ALERT( at_aiconsole, "SUB_Remove called on entity with health > 0\n"); + } + + REMOVE_ENTITY(ENT(pev)); +} + + +// Convenient way to explicitly do nothing (passed to functions that require a method) +void CBaseEntity :: SUB_DoNothing( void ) +{ +} + + +// Global Savedata for Delay +TYPEDESCRIPTION CBaseDelay::m_SaveData[] = +{ + DEFINE_FIELD( CBaseDelay, m_flDelay, FIELD_FLOAT ), + DEFINE_FIELD( CBaseDelay, m_iszKillTarget, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CBaseDelay, CBaseEntity ); + +void CBaseDelay :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "delay")) + { + m_flDelay = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "killtarget")) + { + m_iszKillTarget = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + { + CBaseEntity::KeyValue( pkvd ); + } +} + + +/* +============================== +SUB_UseTargets + +If self.delay is set, a DelayedUse entity will be created that will actually +do the SUB_UseTargets after that many seconds have passed. + +Removes all entities with a targetname that match self.killtarget, +and removes them, so some events can remove other triggers. + +Search for (string)targetname in all entities that +match (string)self.target and call their .use function (if they have one) + +============================== +*/ +void CBaseEntity :: SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ) +{ + // + // fire targets + // + if (!FStringNull(pev->target)) + { + FireTargets( STRING(pev->target), pActivator, this, useType, value ); + } +} + + +void FireTargets( const char *targetName, CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + edict_t *pentTarget = NULL; + if ( !targetName ) + return; + + ALERT( at_aiconsole, "Firing: (%s)\n", targetName ); + + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, targetName); + if (FNullEnt(pentTarget)) + break; + + CBaseEntity *pTarget = CBaseEntity::Instance( pentTarget ); + if ( pTarget && !(pTarget->pev->flags & FL_KILLME) ) // Don't use dying ents + { + ALERT( at_aiconsole, "Found: %s, firing (%s)\n", STRING(pTarget->pev->classname), targetName ); + + // Discwar: Only fire ents that are in the same groupinfo as the activator + if ( pActivator && pActivator->pev->groupinfo && pTarget->pev->groupinfo ) + { + if ( pActivator->pev->groupinfo & pTarget->pev->groupinfo ) + pTarget->Use( pActivator, pCaller, useType, value ); + } + else + { + pTarget->Use( pActivator, pCaller, useType, value ); + } + } + } +} + +LINK_ENTITY_TO_CLASS( DelayedUse, CBaseDelay ); + + +void CBaseDelay :: SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ) +{ + // + // exit immediatly if we don't have a target or kill target + // + if (FStringNull(pev->target) && !m_iszKillTarget) + return; + + // + // check for a delay + // + if (m_flDelay != 0) + { + // create a temp object to fire at a later time + CBaseDelay *pTemp = GetClassPtr( (CBaseDelay *)NULL); + pTemp->pev->classname = MAKE_STRING("DelayedUse"); + + pTemp->pev->nextthink = gpGlobals->time + m_flDelay; + + pTemp->SetThink( &CBaseDelay::DelayThink ); + + // Save the useType + pTemp->pev->button = (int)useType; + pTemp->m_iszKillTarget = m_iszKillTarget; + pTemp->m_flDelay = 0; // prevent "recursion" + pTemp->pev->target = pev->target; + + // HACKHACK + // This wasn't in the release build of Half-Life. We should have moved m_hActivator into this class + // but changing member variable hierarchy would break save/restore without some ugly code. + // This code is not as ugly as that code + if ( pActivator && pActivator->IsPlayer() ) // If a player activates, then save it + { + pTemp->pev->owner = pActivator->edict(); + } + else + { + pTemp->pev->owner = NULL; + } + + return; + } + + // + // kill the killtargets + // + + if ( m_iszKillTarget ) + { + edict_t *pentKillTarget = NULL; + + ALERT( at_aiconsole, "KillTarget: %s\n", STRING(m_iszKillTarget) ); + pentKillTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_iszKillTarget) ); + while ( !FNullEnt(pentKillTarget) ) + { + UTIL_Remove( CBaseEntity::Instance(pentKillTarget) ); + + ALERT( at_aiconsole, "killing %s\n", STRING( pentKillTarget->v.classname ) ); + pentKillTarget = FIND_ENTITY_BY_TARGETNAME( pentKillTarget, STRING(m_iszKillTarget) ); + } + } + + // + // fire targets + // + if (!FStringNull(pev->target)) + { + FireTargets( STRING(pev->target), pActivator, this, useType, value ); + } +} + + +/* +void CBaseDelay :: SUB_UseTargetsEntMethod( void ) +{ + SUB_UseTargets(pev); +} +*/ + +/* +QuakeEd only writes a single float for angles (bad idea), so up and down are +just constant angles. +*/ +void SetMovedir( entvars_t *pev ) +{ + if (pev->angles == Vector(0, -1, 0)) + { + pev->movedir = Vector(0, 0, 1); + } + else if (pev->angles == Vector(0, -2, 0)) + { + pev->movedir = Vector(0, 0, -1); + } + else + { + UTIL_MakeVectors(pev->angles); + pev->movedir = gpGlobals->v_forward; + } + + pev->angles = g_vecZero; +} + + + + +void CBaseDelay::DelayThink( void ) +{ + CBaseEntity *pActivator = NULL; + + if ( pev->owner != NULL ) // A player activated this on delay + { + pActivator = CBaseEntity::Instance( pev->owner ); + } + // The use type is cached (and stashed) in pev->button + SUB_UseTargets( pActivator, (USE_TYPE)pev->button, 0 ); + REMOVE_ENTITY(ENT(pev)); +} + + +// Global Savedata for Toggle +TYPEDESCRIPTION CBaseToggle::m_SaveData[] = +{ + DEFINE_FIELD( CBaseToggle, m_toggle_state, FIELD_INTEGER ), + DEFINE_FIELD( CBaseToggle, m_flActivateFinished, FIELD_TIME ), + DEFINE_FIELD( CBaseToggle, m_flMoveDistance, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flWait, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flLip, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flTWidth, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flTLength, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_vecPosition1, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_vecPosition2, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_vecAngle1, FIELD_VECTOR ), // UNDONE: Position could go through transition, but also angle? + DEFINE_FIELD( CBaseToggle, m_vecAngle2, FIELD_VECTOR ), // UNDONE: Position could go through transition, but also angle? + DEFINE_FIELD( CBaseToggle, m_cTriggersLeft, FIELD_INTEGER ), + DEFINE_FIELD( CBaseToggle, m_flHeight, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_hActivator, FIELD_EHANDLE ), + DEFINE_FIELD( CBaseToggle, m_pfnCallWhenMoveDone, FIELD_FUNCTION ), + DEFINE_FIELD( CBaseToggle, m_vecFinalDest, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_vecFinalAngle, FIELD_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_sMaster, FIELD_STRING), + DEFINE_FIELD( CBaseToggle, m_bitsDamageInflict, FIELD_INTEGER ), // damage type inflicted +}; +IMPLEMENT_SAVERESTORE( CBaseToggle, CBaseAnimating ); + + +void CBaseToggle::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "lip")) + { + m_flLip = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "master")) + { + m_sMaster = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "distance")) + { + m_flMoveDistance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + +/* +============= +LinearMove + +calculate pev->velocity and pev->nextthink to reach vecDest from +pev->origin traveling at flSpeed +=============== +*/ +void CBaseToggle :: LinearMove( Vector vecDest, float flSpeed ) +{ + ASSERTSZ(flSpeed != 0, "LinearMove: no speed is defined!"); +// ASSERTSZ(m_pfnCallWhenMoveDone != NULL, "LinearMove: no post-move function defined"); + + m_vecFinalDest = vecDest; + + // Already there? + if (vecDest == pev->origin) + { + LinearMoveDone(); + return; + } + + // set destdelta to the vector needed to move + Vector vecDestDelta = vecDest - pev->origin; + + // divide vector length by speed to get time to reach dest + float flTravelTime = vecDestDelta.Length() / flSpeed; + + // set nextthink to trigger a call to LinearMoveDone when dest is reached + pev->nextthink = pev->ltime + flTravelTime; + SetThink( &CBaseToggle::LinearMoveDone ); + + // scale the destdelta vector by the time spent traveling to get velocity + pev->velocity = vecDestDelta / flTravelTime; +} + + +/* +============ +After moving, set origin to exact final destination, call "move done" function +============ +*/ +void CBaseToggle :: LinearMoveDone( void ) +{ + UTIL_SetOrigin(pev, m_vecFinalDest); + pev->velocity = g_vecZero; + pev->nextthink = -1; + if ( m_pfnCallWhenMoveDone ) + (this->*m_pfnCallWhenMoveDone)(); +} + +BOOL CBaseToggle :: IsLockedByMaster( void ) +{ + if (m_sMaster && !UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + return TRUE; + else + return FALSE; +} + +/* +============= +AngularMove + +calculate pev->velocity and pev->nextthink to reach vecDest from +pev->origin traveling at flSpeed +Just like LinearMove, but rotational. +=============== +*/ +void CBaseToggle :: AngularMove( Vector vecDestAngle, float flSpeed ) +{ + ASSERTSZ(flSpeed != 0, "AngularMove: no speed is defined!"); +// ASSERTSZ(m_pfnCallWhenMoveDone != NULL, "AngularMove: no post-move function defined"); + + m_vecFinalAngle = vecDestAngle; + + // Already there? + if (vecDestAngle == pev->angles) + { + AngularMoveDone(); + return; + } + + // set destdelta to the vector needed to move + Vector vecDestDelta = vecDestAngle - pev->angles; + + // divide by speed to get time to reach dest + float flTravelTime = vecDestDelta.Length() / flSpeed; + + // set nextthink to trigger a call to AngularMoveDone when dest is reached + pev->nextthink = pev->ltime + flTravelTime; + SetThink( &CBaseToggle::AngularMoveDone ); + + // scale the destdelta vector by the time spent traveling to get velocity + pev->avelocity = vecDestDelta / flTravelTime; +} + + +/* +============ +After rotating, set angle to exact final angle, call "move done" function +============ +*/ +void CBaseToggle :: AngularMoveDone( void ) +{ + pev->angles = m_vecFinalAngle; + pev->avelocity = g_vecZero; + pev->nextthink = -1; + if ( m_pfnCallWhenMoveDone ) + (this->*m_pfnCallWhenMoveDone)(); +} + + +float CBaseToggle :: AxisValue( int flags, const Vector &angles ) +{ + if ( FBitSet(flags, SF_DOOR_ROTATE_Z) ) + return angles.z; + if ( FBitSet(flags, SF_DOOR_ROTATE_X) ) + return angles.x; + + return angles.y; +} + + +void CBaseToggle :: AxisDir( entvars_t *pev ) +{ + if ( FBitSet(pev->spawnflags, SF_DOOR_ROTATE_Z) ) + pev->movedir = Vector ( 0, 0, 1 ); // around z-axis + else if ( FBitSet(pev->spawnflags, SF_DOOR_ROTATE_X) ) + pev->movedir = Vector ( 1, 0, 0 ); // around x-axis + else + pev->movedir = Vector ( 0, 1, 0 ); // around y-axis +} + + +float CBaseToggle :: AxisDelta( int flags, const Vector &angle1, const Vector &angle2 ) +{ + if ( FBitSet (flags, SF_DOOR_ROTATE_Z) ) + return angle1.z - angle2.z; + + if ( FBitSet (flags, SF_DOOR_ROTATE_X) ) + return angle1.x - angle2.x; + + return angle1.y - angle2.y; +} + + +/* +============= +FEntIsVisible + +returns TRUE if the passed entity is visible to caller, even if not infront () +============= +*/ + BOOL +FEntIsVisible( + entvars_t* pev, + entvars_t* pevTarget) + { + Vector vecSpot1 = pev->origin + pev->view_ofs; + Vector vecSpot2 = pevTarget->origin + pevTarget->view_ofs; + TraceResult tr; + + UTIL_TraceLine(vecSpot1, vecSpot2, ignore_monsters, ENT(pev), &tr); + + if (tr.fInOpen && tr.fInWater) + return FALSE; // sight line crossed contents + + if (tr.flFraction == 1) + return TRUE; + + return FALSE; + } + + diff --git a/hlsdk/ricochet/dlls/talkmonster.h b/hlsdk/ricochet/dlls/talkmonster.h new file mode 100644 index 00000000..d8a92eb3 --- /dev/null +++ b/hlsdk/ricochet/dlls/talkmonster.h @@ -0,0 +1,26 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ + +#ifndef TALKMONSTER_H +#define TALKMONSTER_H + +class CTalkMonster : public CBaseMonster +{ +public: + static float g_talkWaitTime; + +}; + +#endif //TALKMONSTER_H diff --git a/hlsdk/ricochet/dlls/teamplay_gamerules.cpp b/hlsdk/ricochet/dlls/teamplay_gamerules.cpp new file mode 100644 index 00000000..9527519f --- /dev/null +++ b/hlsdk/ricochet/dlls/teamplay_gamerules.cpp @@ -0,0 +1,608 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// 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" + +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 = static_cast(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; + + // select the item from the current menu + + return TRUE; + } + + return FALSE; +} + +extern int gmsgGameMode; +extern int gmsgSayText; +extern int gmsgTeamInfo; + + +void CHalfLifeTeamplay :: UpdateGameMode( CBasePlayer *pPlayer ) +{ + MESSAGE_BEGIN( MSG_ONE, gmsgGameMode, NULL, pPlayer->edict() ); + WRITE_BYTE( 1 ); // 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 ) +{ + SetDefaultPlayerTeam( pPlayer ); + CHalfLifeMultiplay::InitHUD( pPlayer ); + + 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 ); + 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 ( int 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(); +} + + +//========================================================= +// 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><%s><%s>\" joined team \"%s\"\n", + STRING(pPlayer->pev->netname), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( 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 ); + + 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_STRING( "teammate" ); // flag this as a teammate kill + 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 ( (CVAR_GET_FLOAT("mp_friendlyfire") == 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] += static_cast(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/hlsdk/ricochet/dlls/teamplay_gamerules.h b/hlsdk/ricochet/dlls/teamplay_gamerules.h new file mode 100644 index 00000000..86d08140 --- /dev/null +++ b/hlsdk/ricochet/dlls/teamplay_gamerules.h @@ -0,0 +1,57 @@ +/*** +* +* Copyright (c) 1999, 2000 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.h +// + +#define MAX_TEAMNAME_LENGTH 16 +#define MAX_TEAMS 32 + +#define TEAMPLAY_TEAMLISTLENGTH MAX_TEAMS*MAX_TEAMNAME_LENGTH + +class CHalfLifeTeamplay : public CHalfLifeMultiplay +{ +public: + CHalfLifeTeamplay(); + + virtual BOOL ClientCommand( CBasePlayer *pPlayer, const char *pcmd ); + virtual void ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ); + virtual BOOL IsTeamplay( void ); + virtual BOOL FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ); + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); + virtual const char *GetTeamID( CBaseEntity *pEntity ); + virtual BOOL ShouldAutoAim( CBasePlayer *pPlayer, edict_t *target ); + 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 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 Think ( void ); + virtual int GetTeamIndex( const char *pTeamName ); + virtual const char *GetIndexedTeamName( int teamIndex ); + virtual BOOL IsValidTeam( const char *pTeamName ); + const char *SetDefaultPlayerTeam( CBasePlayer *pPlayer ); + virtual void ChangePlayerTeam( CBasePlayer *pPlayer, const char *pTeamName, BOOL bKill, BOOL bGib ); + +private: + void RecountTeams( bool bResendInfo = FALSE ); + const char *TeamWithFewestPlayers( void ); + + BOOL m_DisableDeathMessages; + BOOL m_DisableDeathPenalty; + BOOL m_teamLimit; // This means the server set only some teams as valid + char m_szTeamList[TEAMPLAY_TEAMLISTLENGTH]; +}; diff --git a/hlsdk/ricochet/dlls/trains.h b/hlsdk/ricochet/dlls/trains.h new file mode 100644 index 00000000..59124411 --- /dev/null +++ b/hlsdk/ricochet/dlls/trains.h @@ -0,0 +1,127 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +****/ +#ifndef TRAINS_H +#define TRAINS_H + +// Tracktrain spawn flags +#define SF_TRACKTRAIN_NOPITCH 0x0001 +#define SF_TRACKTRAIN_NOCONTROL 0x0002 +#define SF_TRACKTRAIN_FORWARDONLY 0x0004 +#define SF_TRACKTRAIN_PASSABLE 0x0008 + +// Spawnflag for CPathTrack +#define SF_PATH_DISABLED 0x00000001 +#define SF_PATH_FIREONCE 0x00000002 +#define SF_PATH_ALTREVERSE 0x00000004 +#define SF_PATH_DISABLE_TRAIN 0x00000008 +#define SF_PATH_ALTERNATE 0x00008000 + +// Spawnflags of CPathCorner +#define SF_CORNER_WAITFORTRIG 0x001 +#define SF_CORNER_TELEPORT 0x002 +#define SF_CORNER_FIREONCE 0x004 + +//#define PATH_SPARKLE_DEBUG 1 // This makes a particle effect around path_track entities for debugging +class CPathTrack : public CPointEntity +{ +public: + void Spawn( void ); + void Activate( void ); + void KeyValue( KeyValueData* pkvd); + + void SetPrevious( CPathTrack *pprevious ); + void Link( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + CPathTrack *ValidPath( CPathTrack *ppath, int testFlag ); // Returns ppath if enabled, NULL otherwise + void Project( CPathTrack *pstart, CPathTrack *pend, Vector *origin, float dist ); + + static CPathTrack *Instance( edict_t *pent ); + + CPathTrack *LookAhead( Vector *origin, float dist, int move ); + CPathTrack *Nearest( Vector origin ); + + CPathTrack *GetNext( void ); + CPathTrack *GetPrevious( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; +#if PATH_SPARKLE_DEBUG + void EXPORT Sparkle(void); +#endif + + float m_length; + string_t m_altName; + CPathTrack *m_pnext; + CPathTrack *m_pprevious; + CPathTrack *m_paltpath; +}; + + +class CFuncTrackTrain : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + + void Blocked( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData* pkvd ); + + void EXPORT Next( void ); + void EXPORT Find( void ); + void EXPORT NearestPath( void ); + void EXPORT DeadEnd( void ); + + void NextThink( float thinkTime, BOOL alwaysThink ); + + void SetTrack( CPathTrack *track ) { m_ppath = track->Nearest(pev->origin); } + void SetControls( entvars_t *pevControls ); + BOOL OnControls( entvars_t *pev ); + + void StopSound ( void ); + void UpdateSound ( void ); + + static CFuncTrackTrain *Instance( edict_t *pent ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DIRECTIONAL_USE; } + + virtual void OverrideReset( void ); + + CPathTrack *m_ppath; + float m_length; + float m_height; + float m_speed; + float m_dir; + float m_startSpeed; + Vector m_controlMins; + Vector m_controlMaxs; + int m_soundPlaying; + int m_sounds; + float m_flVolume; + float m_flBank; + float m_oldSpeed; + +private: + unsigned short m_usAdjustPitch; +}; + +#endif diff --git a/hlsdk/ricochet/dlls/triggers.cpp b/hlsdk/ricochet/dlls/triggers.cpp new file mode 100644 index 00000000..b63af5b9 --- /dev/null +++ b/hlsdk/ricochet/dlls/triggers.cpp @@ -0,0 +1,2789 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== triggers.cpp ======================================================== + + spawn and use functions for editor-placed triggers + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "effects.h" +#include "player.h" +#include "saverestore.h" +#include "trains.h" // trigger_camera has train functionality +#include "gamerules.h" +#include "weapons.h" +#include "discwar.h" +#include "disc_arena.h" +#include "disc_objects.h" + +#define SF_TRIGGER_PUSH_START_OFF 2//spawnflag that makes trigger_push spawn turned OFF +#define SF_TRIGGER_HURT_TARGETONCE 1// Only fire hurt target once +#define SF_TRIGGER_HURT_START_OFF 2//spawnflag that makes trigger_push spawn turned OFF +#define SF_TRIGGER_HURT_NO_CLIENTS 8//spawnflag that makes trigger_push spawn turned OFF +#define SF_TRIGGER_HURT_CLIENTONLYFIRE 16// trigger hurt will only fire its target if it is hurting a client +#define SF_TRIGGER_HURT_CLIENTONLYTOUCH 32// only clients may touch this trigger. + +extern DLL_GLOBAL BOOL g_fGameOver; + +extern void SetMovedir(entvars_t* pev); +extern Vector VecBModelOrigin( entvars_t* pevBModel ); + +class CFrictionModifier : public CBaseEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT ChangeFriction( CBaseEntity *pOther ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + static TYPEDESCRIPTION m_SaveData[]; + + float m_frictionFraction; // Sorry, couldn't resist this name :) +}; + +LINK_ENTITY_TO_CLASS( func_friction, CFrictionModifier ); + +// Global Savedata for changelevel friction modifier +TYPEDESCRIPTION CFrictionModifier::m_SaveData[] = +{ + DEFINE_FIELD( CFrictionModifier, m_frictionFraction, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE(CFrictionModifier,CBaseEntity); + + +// Modify an entity's friction +void CFrictionModifier :: Spawn( void ) +{ + pev->solid = SOLID_TRIGGER; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->movetype = MOVETYPE_NONE; + SetTouch( &CFrictionModifier::ChangeFriction ); +} + + +// Sets toucher's friction to m_frictionFraction (1.0 = normal friction) +void CFrictionModifier :: ChangeFriction( CBaseEntity *pOther ) +{ + if ( pOther->pev->movetype != MOVETYPE_BOUNCEMISSILE && pOther->pev->movetype != MOVETYPE_BOUNCE ) + pOther->pev->friction = m_frictionFraction; +} + + + +// Sets toucher's friction to m_frictionFraction (1.0 = normal friction) +void CFrictionModifier :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "modifier")) + { + m_frictionFraction = atof(pkvd->szValue) / 100.0; + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +// This trigger will fire when the level spawns (or respawns if not fire once) +// It will check a global state before firing. It supports delay and killtargets + +#define SF_AUTO_FIREONCE 0x0001 + +class CAutoTrigger : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Precache( void ); + void Think( void ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_globalstate; + USE_TYPE triggerType; +}; +LINK_ENTITY_TO_CLASS( trigger_auto, CAutoTrigger ); + +TYPEDESCRIPTION CAutoTrigger::m_SaveData[] = +{ + DEFINE_FIELD( CAutoTrigger, m_globalstate, FIELD_STRING ), + DEFINE_FIELD( CAutoTrigger, triggerType, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE(CAutoTrigger,CBaseDelay); + +void CAutoTrigger::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "globalstate")) + { + m_globalstate = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "triggerstate")) + { + int type = atoi( pkvd->szValue ); + switch( type ) + { + case 0: + triggerType = USE_OFF; + break; + case 2: + triggerType = USE_TOGGLE; + break; + default: + triggerType = USE_ON; + break; + } + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + + +void CAutoTrigger::Spawn( void ) +{ + Precache(); +} + + +void CAutoTrigger::Precache( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CAutoTrigger::Think( void ) +{ + if ( !m_globalstate || gGlobalState.EntityGetState( m_globalstate ) == GLOBAL_ON ) + { + SUB_UseTargets( this, triggerType, 0 ); + if ( pev->spawnflags & SF_AUTO_FIREONCE ) + UTIL_Remove( this ); + } +} + + + +#define SF_RELAY_FIREONCE 0x0001 + +class CTriggerRelay : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + USE_TYPE triggerType; +}; +LINK_ENTITY_TO_CLASS( trigger_relay, CTriggerRelay ); + +TYPEDESCRIPTION CTriggerRelay::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerRelay, triggerType, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerRelay,CBaseDelay); + +void CTriggerRelay::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "triggerstate")) + { + int type = atoi( pkvd->szValue ); + switch( type ) + { + case 0: + triggerType = USE_OFF; + break; + case 2: + triggerType = USE_TOGGLE; + break; + default: + triggerType = USE_ON; + break; + } + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + + +void CTriggerRelay::Spawn( void ) +{ +} + + + + +void CTriggerRelay::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SUB_UseTargets( this, triggerType, 0 ); + if ( pev->spawnflags & SF_RELAY_FIREONCE ) + UTIL_Remove( this ); +} + + +//********************************************************** +// The Multimanager Entity - when fired, will fire up to 16 targets +// at specified times. +// FLAG: THREAD (create clones when triggered) +// FLAG: CLONE (this is a clone for a threaded execution) + +#define SF_MULTIMAN_CLONE 0x80000000 +#define SF_MULTIMAN_THREAD 0x00000001 + +class CMultiManager : public CBaseToggle +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn ( void ); + void EXPORT ManagerThink ( void ); + void EXPORT ManagerUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +#if _DEBUG + void EXPORT ManagerReport( void ); +#endif + + BOOL HasTarget( string_t targetname ); + + int ObjectCaps( void ) { return CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_cTargets; // the total number of targets in this manager's fire list. + int m_index; // Current target + float m_startTime;// Time we started firing + int m_iTargetName [ MAX_MULTI_TARGETS ];// list if indexes into global string array + float m_flTargetDelay [ MAX_MULTI_TARGETS ];// delay (in seconds) from time of manager fire to target fire +private: + inline BOOL IsClone( void ) { return (pev->spawnflags & SF_MULTIMAN_CLONE) ? TRUE : FALSE; } + inline BOOL ShouldClone( void ) + { + if ( IsClone() ) + return FALSE; + + return (pev->spawnflags & SF_MULTIMAN_THREAD) ? TRUE : FALSE; + } + + CMultiManager *Clone( void ); +}; +LINK_ENTITY_TO_CLASS( multi_manager, CMultiManager ); + +// Global Savedata for multi_manager +TYPEDESCRIPTION CMultiManager::m_SaveData[] = +{ + DEFINE_FIELD( CMultiManager, m_cTargets, FIELD_INTEGER ), + DEFINE_FIELD( CMultiManager, m_index, FIELD_INTEGER ), + DEFINE_FIELD( CMultiManager, m_startTime, FIELD_TIME ), + DEFINE_ARRAY( CMultiManager, m_iTargetName, FIELD_STRING, MAX_MULTI_TARGETS ), + DEFINE_ARRAY( CMultiManager, m_flTargetDelay, FIELD_FLOAT, MAX_MULTI_TARGETS ), +}; + +IMPLEMENT_SAVERESTORE(CMultiManager,CBaseToggle); + +void CMultiManager :: KeyValue( KeyValueData *pkvd ) +{ + // UNDONE: Maybe this should do something like this: + //CBaseToggle::KeyValue( pkvd ); + // if ( !pkvd->fHandled ) + // ... etc. + + if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else // add this field to the target list + { + // this assumes that additional fields are targetnames and their values are delay values. + if ( m_cTargets < MAX_MULTI_TARGETS ) + { + char tmp[128]; + + UTIL_StripToken( pkvd->szKeyName, tmp ); + m_iTargetName [ m_cTargets ] = ALLOC_STRING( tmp ); + m_flTargetDelay [ m_cTargets ] = atof (pkvd->szValue); + m_cTargets++; + pkvd->fHandled = TRUE; + } + } +} + + +void CMultiManager :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + SetUse ( &CMultiManager::ManagerUse ); + SetThink ( &CMultiManager::ManagerThink); + + // Sort targets + // Quick and dirty bubble sort + int swapped = 1; + + while ( swapped ) + { + swapped = 0; + for ( int i = 1; i < m_cTargets; i++ ) + { + if ( m_flTargetDelay[i] < m_flTargetDelay[i-1] ) + { + // Swap out of order elements + int name = m_iTargetName[i]; + float delay = m_flTargetDelay[i]; + m_iTargetName[i] = m_iTargetName[i-1]; + m_flTargetDelay[i] = m_flTargetDelay[i-1]; + m_iTargetName[i-1] = name; + m_flTargetDelay[i-1] = delay; + swapped = 1; + } + } + } +} + + +BOOL CMultiManager::HasTarget( string_t targetname ) +{ + for ( int i = 0; i < m_cTargets; i++ ) + if ( FStrEq(STRING(targetname), STRING(m_iTargetName[i])) ) + return TRUE; + + return FALSE; +} + + +// Designers were using this to fire targets that may or may not exist -- +// so I changed it to use the standard target fire code, made it a little simpler. +void CMultiManager :: ManagerThink ( void ) +{ + float time; + + time = gpGlobals->time - m_startTime; + while ( m_index < m_cTargets && m_flTargetDelay[ m_index ] <= time ) + { + FireTargets( STRING( m_iTargetName[ m_index ] ), m_hActivator, this, USE_TOGGLE, 0 ); + m_index++; + } + + if ( m_index >= m_cTargets )// have we fired all targets? + { + SetThink( NULL ); + if ( IsClone() ) + { + UTIL_Remove( this ); + return; + } + SetUse ( &CMultiManager::ManagerUse );// allow manager re-use + } + else + pev->nextthink = m_startTime + m_flTargetDelay[ m_index ]; +} + +CMultiManager *CMultiManager::Clone( void ) +{ + CMultiManager *pMulti = GetClassPtr( (CMultiManager *)NULL ); + + edict_t *pEdict = pMulti->pev->pContainingEntity; + memcpy( pMulti->pev, pev, sizeof(*pev) ); + pMulti->pev->pContainingEntity = pEdict; + + pMulti->pev->spawnflags |= SF_MULTIMAN_CLONE; + pMulti->m_cTargets = m_cTargets; + memcpy( pMulti->m_iTargetName, m_iTargetName, sizeof( m_iTargetName ) ); + memcpy( pMulti->m_flTargetDelay, m_flTargetDelay, sizeof( m_flTargetDelay ) ); + + return pMulti; +} + + +// The USE function builds the time table and starts the entity thinking. +void CMultiManager :: ManagerUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // In multiplayer games, clone the MM and execute in the clone (like a thread) + // to allow multiple players to trigger the same multimanager + if ( ShouldClone() ) + { + CMultiManager *pClone = Clone(); + pClone->ManagerUse( pActivator, pCaller, useType, value ); + return; + } + + m_hActivator = pActivator; + m_index = 0; + m_startTime = gpGlobals->time; + + SetUse( NULL );// disable use until all targets have fired + + SetThink ( &CMultiManager::ManagerThink ); + pev->nextthink = gpGlobals->time; +} + +#if _DEBUG +void CMultiManager :: ManagerReport ( void ) +{ + int cIndex; + + for ( cIndex = 0 ; cIndex < m_cTargets ; cIndex++ ) + { + ALERT ( at_console, "%s %f\n", STRING(m_iTargetName[cIndex]), m_flTargetDelay[cIndex] ); + } +} +#endif + +//*********************************************************** + + +// +// Render parameters trigger +// +// This entity will copy its render parameters (renderfx, rendermode, rendercolor, renderamt) +// to its targets when triggered. +// + + +// Flags to indicate masking off various render parameters that are normally copied to the targets +#define SF_RENDER_MASKFX (1<<0) +#define SF_RENDER_MASKAMT (1<<1) +#define SF_RENDER_MASKMODE (1<<2) +#define SF_RENDER_MASKCOLOR (1<<3) + +class CRenderFxManager : public CBaseEntity +{ +public: + void Spawn( void ); + void Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; + +LINK_ENTITY_TO_CLASS( env_render, CRenderFxManager ); + + +void CRenderFxManager :: Spawn ( void ) +{ + pev->solid = SOLID_NOT; +} + +void CRenderFxManager :: Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (!FStringNull(pev->target)) + { + edict_t* pentTarget = NULL; + while ( 1 ) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->target)); + if (FNullEnt(pentTarget)) + break; + + entvars_t *pevTarget = VARS( pentTarget ); + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKFX ) ) + pevTarget->renderfx = pev->renderfx; + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKAMT ) ) + pevTarget->renderamt = pev->renderamt; + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKMODE ) ) + pevTarget->rendermode = pev->rendermode; + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKCOLOR ) ) + pevTarget->rendercolor = pev->rendercolor; + } + } +} + + + +LINK_ENTITY_TO_CLASS( trigger, CBaseTrigger ); + +/* +================ +InitTrigger +================ +*/ +void CBaseTrigger::InitTrigger( ) +{ + // trigger angles are used for one-way touches. An angle of 0 is assumed + // to mean no restrictions, so use a yaw of 360 instead. + if (pev->angles != g_vecZero) + SetMovedir(pev); + pev->solid = SOLID_TRIGGER; + pev->movetype = MOVETYPE_NONE; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + if ( CVAR_GET_FLOAT("showtriggers") == 0 ) + SetBits( pev->effects, EF_NODRAW ); +} + + +// +// Cache user-entity-field values until spawn is called. +// + +void CBaseTrigger :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "damage")) + { + pev->dmg = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "count")) + { + m_cTriggersLeft = (int) atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damagetype")) + { + m_bitsDamageInflict = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +class CTriggerHurt : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT RadiationThink( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_hurt, CTriggerHurt ); + +// +// trigger_monsterjump +// +class CTriggerMonsterJump : public CBaseTrigger +{ +public: + void Spawn( void ); + void Touch( CBaseEntity *pOther ); + void Think( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_monsterjump, CTriggerMonsterJump ); + + +void CTriggerMonsterJump :: Spawn ( void ) +{ + SetMovedir ( pev ); + + InitTrigger (); + + pev->nextthink = 0; + pev->speed = 200; + m_flHeight = 150; + + if ( !FStringNull ( pev->targetname ) ) + {// if targetted, spawn turned off + pev->solid = SOLID_NOT; + UTIL_SetOrigin( pev, pev->origin ); // Unlink from trigger list + SetUse( &CBaseTrigger::ToggleUse ); + } +} + + +void CTriggerMonsterJump :: Think( void ) +{ + pev->solid = SOLID_NOT;// kill the trigger for now !!!UNDONE + UTIL_SetOrigin( pev, pev->origin ); // Unlink from trigger list + SetThink( NULL ); +} + +void CTriggerMonsterJump :: Touch( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + 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->nextthink = gpGlobals->time; +} + + +//===================================== +// +// trigger_cdaudio - starts/stops cd audio tracks +// +class CTriggerCDAudio : public CBaseTrigger +{ +public: + void Spawn( void ); + + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void PlayTrack( void ); + void Touch ( CBaseEntity *pOther ); +}; + +LINK_ENTITY_TO_CLASS( trigger_cdaudio, CTriggerCDAudio ); + +// +// Changes tracks or stops CD when player touches +// +// !!!HACK - overloaded HEALTH to avoid adding new field +void CTriggerCDAudio :: Touch ( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + {// only clients may trigger these events + return; + } + + PlayTrack(); +} + +void CTriggerCDAudio :: Spawn( void ) +{ + InitTrigger(); +} + +void CTriggerCDAudio::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + PlayTrack(); +} + +void PlayCDTrack( int iTrack ) +{ + edict_t *pClient; + + // manually find the single player. + pClient = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + // Can't play if the client is not connected! + if ( !pClient ) + return; + + if ( iTrack < -1 || iTrack > 30 ) + { + ALERT ( at_console, "TriggerCDAudio - Track %d out of range\n" ); + return; + } + + if ( iTrack == -1 ) + { + CLIENT_COMMAND ( pClient, "cd pause\n"); + } + else + { + char string [ 64 ]; + + sprintf( string, "cd play %3d\n", iTrack ); + CLIENT_COMMAND ( pClient, string); + } +} + + +// only plays for ONE client, so only use in single play! +void CTriggerCDAudio :: PlayTrack( void ) +{ + PlayCDTrack( (int)pev->health ); + + SetTouch( NULL ); + UTIL_Remove( this ); +} + + +// This plays a CD track when fired or when the player enters it's radius +class CTargetCDAudio : public CPointEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Think( void ); + void Play( void ); +}; + +LINK_ENTITY_TO_CLASS( target_cdaudio, CTargetCDAudio ); + +void CTargetCDAudio :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "radius")) + { + pev->scale = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CTargetCDAudio :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + if ( pev->scale > 0 ) + pev->nextthink = gpGlobals->time + 1.0; +} + +void CTargetCDAudio::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + Play(); +} + +// only plays for ONE client, so only use in single play! +void CTargetCDAudio::Think( void ) +{ + edict_t *pClient; + + // manually find the single player. + pClient = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + // Can't play if the client is not connected! + if ( !pClient ) + return; + + pev->nextthink = gpGlobals->time + 0.5; + + if ( (pClient->v.origin - pev->origin).Length() <= pev->scale ) + Play(); + +} + +void CTargetCDAudio::Play( void ) +{ + PlayCDTrack( (int)pev->health ); + UTIL_Remove(this); +} + +//===================================== + +// +// trigger_hurt - hurts anything that touches it. if the trigger has a targetname, firing it will toggle state +// +//int gfToggleState = 0; // used to determine when all radiation trigger hurts have called 'RadiationThink' + +void CTriggerHurt :: Spawn( void ) +{ + InitTrigger(); + SetTouch ( &CBaseTrigger::HurtTouch ); + + if ( !FStringNull ( pev->targetname ) ) + { + SetUse ( &CBaseTrigger::ToggleUse ); + } + else + { + SetUse ( NULL ); + } + + if (m_bitsDamageInflict & DMG_RADIATION) + { + SetThink ( &CTriggerHurt::RadiationThink ); + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(0.0, 0.5); + } + + if ( FBitSet (pev->spawnflags, SF_TRIGGER_HURT_START_OFF) )// if flagged to Start Turned Off, make trigger nonsolid. + pev->solid = SOLID_NOT; + + UTIL_SetOrigin( pev, pev->origin ); // Link into the list +} + +// trigger hurt that causes radiation will do a radius +// check and set the player's geiger counter level +// according to distance from center of trigger + +void CTriggerHurt :: RadiationThink( void ) +{ + + edict_t *pentPlayer; + CBasePlayer *pPlayer = NULL; + float flRange; + entvars_t *pevTarget; + Vector vecSpot1; + Vector vecSpot2; + Vector vecRange; + Vector origin; + Vector view_ofs; + + // check to see if a player is in pvs + // if not, continue + + // set origin to center of trigger so that this check works + origin = pev->origin; + view_ofs = pev->view_ofs; + + pev->origin = (pev->absmin + pev->absmax) * 0.5; + pev->view_ofs = pev->view_ofs * 0.0; + + pentPlayer = FIND_CLIENT_IN_PVS(edict()); + + pev->origin = origin; + pev->view_ofs = view_ofs; + + // reset origin + + if (!FNullEnt(pentPlayer)) + { + + pPlayer = GetClassPtr( (CBasePlayer *)VARS(pentPlayer)); + + pevTarget = VARS(pentPlayer); + + // get range to player; + + vecSpot1 = (pev->absmin + pev->absmax) * 0.5; + vecSpot2 = (pevTarget->absmin + pevTarget->absmax) * 0.5; + + 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; +} + +// +// ToggleUse - If this is the USE function for a trigger, its state will toggle every time it's fired +// +void CBaseTrigger :: ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (pev->solid == SOLID_NOT) + {// if the trigger is off, turn it on + pev->solid = SOLID_TRIGGER; + + // Force retouch + gpGlobals->force_retouch++; + } + else + {// turn the trigger off + pev->solid = SOLID_NOT; + } + UTIL_SetOrigin( pev, pev->origin ); +} + +// When touched, a hurt trigger does DMG points of damage each half-second +void CBaseTrigger :: HurtTouch ( CBaseEntity *pOther ) +{ + float fldmg; + + if ( !pOther->pev->takedamage || pOther->IsAlive() == false ) + return; + + if ( (pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYTOUCH) && !pOther->IsPlayer() ) + { + // this trigger is only allowed to touch clients, and this ain't a client. + return; + } + + if ( (pev->spawnflags & SF_TRIGGER_HURT_NO_CLIENTS) && pOther->IsPlayer() ) + return; + + // HACKHACK -- In multiplayer, players touch this based on packet receipt. + // So the players who send packets later aren't always hurt. Keep track of + // how much time has passed and whether or not you've touched that player + if ( g_pGameRules->IsMultiplayer() ) + { + if ( pev->dmgtime > gpGlobals->time ) + { + if ( gpGlobals->time != pev->pain_finished ) + {// too early to hurt again, and not same frame with a different entity + if ( pOther->IsPlayer() ) + { + int playerMask = 1 << (pOther->entindex() - 1); + + // If I've already touched this player (this time), then bail out + if ( pev->impulse & playerMask ) + return; + + // Mark this player as touched + // BUGBUG - There can be only 32 players! + pev->impulse |= playerMask; + } + else + { + return; + } + } + } + else + { + // New clock, "un-touch" all players + pev->impulse = 0; + if ( pOther->IsPlayer() ) + { + int playerMask = 1 << (pOther->entindex() - 1); + + // Mark this player as touched + // BUGBUG - There can be only 32 players! + pev->impulse |= playerMask; + } + } + } + else // Original code -- single player + { + if ( pev->dmgtime > gpGlobals->time && gpGlobals->time != pev->pain_finished ) + {// too early to hurt again, and not same frame with a different entity + return; + } + } + + + + // If this is time_based damage (poison, radiation), override the pev->dmg with a + // default for the given damage type. Monsters only take time-based damage + // while touching the trigger. Player continues taking damage for a while after + // leaving the trigger + + fldmg = pev->dmg * 0.5; // 0.5 seconds worth of damage, pev->dmg is damage/second + + + // JAY: Cut this because it wasn't fully realized. Damage is simpler now. +#if 0 + switch (m_bitsDamageInflict) + { + default: break; + case DMG_POISON: fldmg = POISON_DAMAGE/4; break; + case DMG_NERVEGAS: fldmg = NERVEGAS_DAMAGE/4; break; + case DMG_RADIATION: fldmg = RADIATION_DAMAGE/4; break; + case DMG_PARALYZE: fldmg = PARALYZE_DAMAGE/4; break; // UNDONE: cut this? should slow movement to 50% + case DMG_ACID: fldmg = ACID_DAMAGE/4; break; + case DMG_SLOWBURN: fldmg = SLOWBURN_DAMAGE/4; break; + case DMG_SLOWFREEZE: fldmg = SLOWFREEZE_DAMAGE/4; break; + } +#endif + + if ( fldmg < 0 ) + pOther->TakeHealth( -fldmg, m_bitsDamageInflict ); + else + pOther->TakeDamage( pev, pev, fldmg, m_bitsDamageInflict ); + + // Store pain time so we can get all of the other entities on this frame + pev->pain_finished = gpGlobals->time; + + // Apply damage every half second + pev->dmgtime = gpGlobals->time + 0.5;// half second delay until this trigger can hurt toucher again + + + + if ( pev->target ) + { + // trigger has a target it wants to fire. + if ( pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYFIRE ) + { + // if the toucher isn't a client, don't fire the target! + if ( !pOther->IsPlayer() ) + { + return; + } + } + + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); + if ( pev->spawnflags & SF_TRIGGER_HURT_TARGETONCE ) + pev->target = 0; + } +} + + +/*QUAKED trigger_multiple (.5 .5 .5) ? notouch +Variable sized repeatable trigger. Must be targeted at one or more entities. +If "health" is set, the trigger must be killed to activate each time. +If "delay" is set, the trigger waits some time after activating before firing. +"wait" : Seconds between triggerings. (.2 default) +If notouch is set, the trigger is only fired by other entities, not by touching. +NOTOUCH has been obsoleted by trigger_relay! +sounds +1) secret +2) beep beep +3) large switch +4) +NEW +if a trigger has a NETNAME, that NETNAME will become the TARGET of the triggered object. +*/ +class CTriggerMultiple : public CBaseTrigger +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_multiple, CTriggerMultiple ); + + +void CTriggerMultiple :: Spawn( void ) +{ + if (m_flWait == 0) + m_flWait = 0.2; + + InitTrigger(); + + ASSERTSZ(pev->health == 0, "trigger_multiple with health"); +// UTIL_SetOrigin(pev, pev->origin); +// SET_MODEL( ENT(pev), STRING(pev->model) ); +// if (pev->health > 0) +// { +// if (FBitSet(pev->spawnflags, SPAWNFLAG_NOTOUCH)) +// ALERT(at_error, "trigger_multiple spawn: health and notouch don't make sense"); +// pev->max_health = pev->health; +//UNDONE: where to get pfnDie from? +// pev->pfnDie = multi_killed; +// pev->takedamage = DAMAGE_YES; +// pev->solid = SOLID_BBOX; +// UTIL_SetOrigin(pev, pev->origin); // make sure it links into the world +// } +// else + { + SetTouch( &CBaseTrigger::MultiTouch ); + } + } + + +/*QUAKED trigger_once (.5 .5 .5) ? notouch +Variable sized trigger. Triggers once, then removes itself. You must set the key "target" to the name of another object in the level that has a matching +"targetname". If "health" is set, the trigger must be killed to activate. +If notouch is set, the trigger is only fired by other entities, not by touching. +if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired. +if "angle" is set, the trigger will only fire when someone is facing the direction of the angle. Use "360" for an angle of 0. +sounds +1) secret +2) beep beep +3) large switch +4) +*/ +class CTriggerOnce : public CTriggerMultiple +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_once, CTriggerOnce ); +void CTriggerOnce::Spawn( void ) +{ + m_flWait = -1; + + CTriggerMultiple :: Spawn(); +} + + + +void CBaseTrigger :: MultiTouch( CBaseEntity *pOther ) +{ + entvars_t *pevToucher; + + pevToucher = pOther->pev; + + // Only touch clients, monsters, or pushables (depending on flags) + if ( ((pevToucher->flags & FL_CLIENT) && !(pev->spawnflags & SF_TRIGGER_NOCLIENTS)) || + ((pevToucher->flags & FL_MONSTER) && (pev->spawnflags & SF_TRIGGER_ALLOWMONSTERS)) || + (pev->spawnflags & SF_TRIGGER_PUSHABLES) && FClassnameIs(pevToucher,"func_pushable") ) + { + +#if 0 + // if the trigger has an angles field, check player's facing direction + if (pev->movedir != g_vecZero) + { + UTIL_MakeVectors( pevToucher->angles ); + if ( DotProduct( gpGlobals->v_forward, pev->movedir ) < 0 ) + return; // not facing the right way + } +#endif + + ActivateMultiTrigger( pOther ); + } +} + + +// +// the trigger was just touched/killed/used +// self.enemy should be set to the activator so it can be held through a delay +// so wait for the delay time before firing +// +void CBaseTrigger :: ActivateMultiTrigger( CBaseEntity *pActivator ) +{ + if (pev->nextthink > gpGlobals->time) + return; // still waiting for reset time + + if (!UTIL_IsMasterTriggered(m_sMaster,pActivator)) + return; + + if (FClassnameIs(pev, "trigger_secret")) + { + if ( pev->enemy == NULL || !FClassnameIs(pev->enemy, "player")) + return; + gpGlobals->found_secrets++; + } + + if (!FStringNull(pev->noise)) + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + +// don't trigger again until reset +// pev->takedamage = DAMAGE_NO; + + m_hActivator = pActivator; + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + + if ( pev->message && pActivator->IsPlayer() ) + { + UTIL_ShowMessage( STRING(pev->message), pActivator ); +// CLIENT_PRINTF( ENT( pActivator->pev ), print_center, STRING(pev->message) ); + } + + if (m_flWait > 0) + { + SetThink( &CBaseTrigger::MultiWaitOver ); + pev->nextthink = gpGlobals->time + m_flWait; + } + else + { + // we can't just remove (self) here, because this is a touch function + // called while C code is looping through area links... + SetTouch( NULL ); + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CBaseEntity::SUB_Remove ); + } +} + + +// the wait time has passed, so set back up for another activation +void CBaseTrigger :: MultiWaitOver( void ) +{ +// if (pev->max_health) +// { +// pev->health = pev->max_health; +// pev->takedamage = DAMAGE_YES; +// pev->solid = SOLID_BBOX; +// } + SetThink( NULL ); +} + + +// ========================= COUNTING TRIGGER ===================================== + +// +// GLOBALS ASSUMED SET: g_eoActivator +// +void CBaseTrigger::CounterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_cTriggersLeft--; + m_hActivator = pActivator; + + if (m_cTriggersLeft < 0) + return; + + BOOL fTellActivator = + (m_hActivator != 0) && + FClassnameIs(m_hActivator->pev, "player") && + !FBitSet(pev->spawnflags, SPAWNFLAG_NOMESSAGE); + if (m_cTriggersLeft != 0) + { + if (fTellActivator) + { + // UNDONE: I don't think we want these Quakesque messages + switch (m_cTriggersLeft) + { + case 1: ALERT(at_console, "Only 1 more to go..."); break; + case 2: ALERT(at_console, "Only 2 more to go..."); break; + case 3: ALERT(at_console, "Only 3 more to go..."); break; + default: ALERT(at_console, "There are more to go..."); break; + } + } + return; + } + + // !!!UNDONE: I don't think we want these Quakesque messages + if (fTellActivator) + ALERT(at_console, "Sequence completed!"); + + ActivateMultiTrigger( m_hActivator ); +} + + +/*QUAKED trigger_counter (.5 .5 .5) ? nomessage +Acts as an intermediary for an action that takes multiple inputs. +If nomessage is not set, it will print "1 more.. " etc when triggered and +"sequence complete" when finished. After the counter has been triggered "cTriggersLeft" +times (default 2), it will fire all of it's targets and remove itself. +*/ +class CTriggerCounter : public CBaseTrigger +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS( trigger_counter, CTriggerCounter ); + +void CTriggerCounter :: Spawn( void ) +{ + // By making the flWait be -1, this counter-trigger will disappear after it's activated + // (but of course it needs cTriggersLeft "uses" before that happens). + m_flWait = -1; + + if (m_cTriggersLeft == 0) + m_cTriggersLeft = 2; + SetUse( &CBaseTrigger::CounterUse ); +} + +// ====================== TRIGGER_CHANGELEVEL ================================ + +class CTriggerVolume : public CPointEntity // Derive from point entity so this doesn't move across levels +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_transition, CTriggerVolume ); + +// Define space that travels across a level transition +void CTriggerVolume :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->model = 0; + pev->modelindex = 0; +} + + +// Fires a target after level transition and then dies +class CFireAndDie : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void Think( void ); + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() | FCAP_FORCE_TRANSITION; } // Always go across transitions +}; +LINK_ENTITY_TO_CLASS( fireanddie, CFireAndDie ); + +void CFireAndDie::Spawn( void ) +{ + pev->classname = MAKE_STRING("fireanddie"); + // Don't call Precache() - it should be called on restore +} + + +void CFireAndDie::Precache( void ) +{ + // This gets called on restore + pev->nextthink = gpGlobals->time + m_flDelay; +} + + +void CFireAndDie::Think( void ) +{ + SUB_UseTargets( this, USE_TOGGLE, 0 ); + UTIL_Remove( this ); +} + + +#define SF_CHANGELEVEL_USEONLY 0x0002 +class CChangeLevel : public CBaseTrigger +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT UseChangeLevel ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT TriggerChangeLevel( void ); + void EXPORT ExecuteChangeLevel( void ); + void EXPORT TouchChangeLevel( CBaseEntity *pOther ); + void ChangeLevelNow( CBaseEntity *pActivator ); + + static edict_t *FindLandmark( const char *pLandmarkName ); + static int ChangeList( LEVELLIST *pLevelList, int maxList ); + static int AddTransitionToList( LEVELLIST *pLevelList, int listCount, const char *pMapName, const char *pLandmarkName, edict_t *pentLandmark ); + static int InTransitionVolume( CBaseEntity *pEntity, char *pVolumeName ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + char m_szMapName[cchMapNameMost]; // trigger_changelevel only: next map + char m_szLandmarkName[cchMapNameMost]; // trigger_changelevel only: landmark on next map + int m_changeTarget; + float m_changeTargetDelay; +}; +LINK_ENTITY_TO_CLASS( trigger_changelevel, CChangeLevel ); + +// Global Savedata for changelevel trigger +TYPEDESCRIPTION CChangeLevel::m_SaveData[] = +{ + DEFINE_ARRAY( CChangeLevel, m_szMapName, FIELD_CHARACTER, cchMapNameMost ), + DEFINE_ARRAY( CChangeLevel, m_szLandmarkName, FIELD_CHARACTER, cchMapNameMost ), + DEFINE_FIELD( CChangeLevel, m_changeTarget, FIELD_STRING ), + DEFINE_FIELD( CChangeLevel, m_changeTargetDelay, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE(CChangeLevel,CBaseTrigger); + +// +// Cache user-entity-field values until spawn is called. +// + +void CChangeLevel :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "map")) + { + if (strlen(pkvd->szValue) >= cchMapNameMost) + ALERT( at_error, "Map name '%s' too long (32 chars)\n", pkvd->szValue ); + strcpy(m_szMapName, pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "landmark")) + { + if (strlen(pkvd->szValue) >= cchMapNameMost) + ALERT( at_error, "Landmark name '%s' too long (32 chars)\n", pkvd->szValue ); + strcpy(m_szLandmarkName, pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "changetarget")) + { + m_changeTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "changedelay")) + { + m_changeTargetDelay = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseTrigger::KeyValue( pkvd ); +} + + +/*QUAKED trigger_changelevel (0.5 0.5 0.5) ? NO_INTERMISSION +When the player touches this, he gets sent to the map listed in the "map" variable. Unless the NO_INTERMISSION flag is set, the view will go to the info_intermission spot and display stats. +*/ + +void CChangeLevel :: Spawn( void ) +{ + if ( FStrEq( m_szMapName, "" ) ) + ALERT( at_console, "a trigger_changelevel doesn't have a map" ); + + if ( FStrEq( m_szLandmarkName, "" ) ) + ALERT( at_console, "trigger_changelevel to %s doesn't have a landmark", m_szMapName ); + + if (!FStringNull ( pev->targetname ) ) + { + SetUse ( &CChangeLevel::UseChangeLevel ); + } + InitTrigger(); + if ( !(pev->spawnflags & SF_CHANGELEVEL_USEONLY) ) + SetTouch( &CChangeLevel::TouchChangeLevel ); +// ALERT( at_console, "TRANSITION: %s (%s)\n", m_szMapName, m_szLandmarkName ); +} + + +void CChangeLevel :: ExecuteChangeLevel( void ) +{ + MESSAGE_BEGIN( MSG_ALL, SVC_CDTRACK ); + WRITE_BYTE( 3 ); + WRITE_BYTE( 3 ); + MESSAGE_END(); + + MESSAGE_BEGIN(MSG_ALL, SVC_INTERMISSION); + MESSAGE_END(); +} + + +FILE_GLOBAL char st_szNextMap[cchMapNameMost]; +FILE_GLOBAL char st_szNextSpot[cchMapNameMost]; + +edict_t *CChangeLevel :: FindLandmark( const char *pLandmarkName ) +{ + edict_t *pentLandmark; + + pentLandmark = FIND_ENTITY_BY_STRING( NULL, "targetname", pLandmarkName ); + while ( !FNullEnt( pentLandmark ) ) + { + // Found the landmark + if ( FClassnameIs( pentLandmark, "info_landmark" ) ) + return pentLandmark; + else + pentLandmark = FIND_ENTITY_BY_STRING( pentLandmark, "targetname", pLandmarkName ); + } + ALERT( at_error, "Can't find landmark %s\n", pLandmarkName ); + return NULL; +} + + +//========================================================= +// CChangeLevel :: Use - allows level transitions to be +// triggered by buttons, etc. +// +//========================================================= +void CChangeLevel :: UseChangeLevel ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + ChangeLevelNow( pActivator ); +} + +void CChangeLevel :: ChangeLevelNow( CBaseEntity *pActivator ) +{ + edict_t *pentLandmark; + + ASSERT(!FStrEq(m_szMapName, "")); + + // Don't work in deathmatch + if ( g_pGameRules->IsDeathmatch() ) + return; + + // Some people are firing these multiple times in a frame, disable + if ( gpGlobals->time == pev->dmgtime ) + return; + + pev->dmgtime = gpGlobals->time; + + + CBaseEntity *pPlayer = CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) ); + if ( !InTransitionVolume( pPlayer, m_szLandmarkName ) ) + { + ALERT( at_aiconsole, "Player isn't in the transition volume %s, aborting\n", m_szLandmarkName ); + return; + } + + // Create an entity to fire the changetarget + if ( m_changeTarget ) + { + CFireAndDie *pFireAndDie = GetClassPtr( (CFireAndDie *)NULL ); + if ( pFireAndDie ) + { + // Set target and delay + pFireAndDie->pev->target = m_changeTarget; + pFireAndDie->m_flDelay = m_changeTargetDelay; + pFireAndDie->pev->origin = pPlayer->pev->origin; + // Call spawn + DispatchSpawn( pFireAndDie->edict() ); + } + } + // This object will get removed in the call to CHANGE_LEVEL, copy the params into "safe" memory + strcpy(st_szNextMap, m_szMapName); + + m_hActivator = pActivator; + SUB_UseTargets( pActivator, USE_TOGGLE, 0 ); + st_szNextSpot[0] = 0; // Init landmark to NULL + + // look for a landmark entity + pentLandmark = FindLandmark( m_szLandmarkName ); + if ( !FNullEnt( pentLandmark ) ) + { + strcpy(st_szNextSpot, m_szLandmarkName); + gpGlobals->vecLandmarkOffset = VARS(pentLandmark)->origin; + } +// ALERT( at_console, "Level touches %d levels\n", ChangeList( levels, 16 ) ); + ALERT( at_console, "CHANGE LEVEL: %s %s\n", st_szNextMap, st_szNextSpot ); + CHANGE_LEVEL( st_szNextMap, st_szNextSpot ); +} + +// +// GLOBALS ASSUMED SET: st_szNextMap +// +void CChangeLevel :: TouchChangeLevel( CBaseEntity *pOther ) +{ + if (!FClassnameIs(pOther->pev, "player")) + return; + + ChangeLevelNow( pOther ); +} + + +// Add a transition to the list, but ignore duplicates +// (a designer may have placed multiple trigger_changelevels with the same landmark) +int CChangeLevel::AddTransitionToList( LEVELLIST *pLevelList, int listCount, const char *pMapName, const char *pLandmarkName, edict_t *pentLandmark ) +{ + int i; + + if ( !pLevelList || !pMapName || !pLandmarkName || !pentLandmark ) + return 0; + + for ( i = 0; i < listCount; i++ ) + { + if ( pLevelList[i].pentLandmark == pentLandmark && strcmp( pLevelList[i].mapName, pMapName ) == 0 ) + return 0; + } + strcpy( pLevelList[listCount].mapName, pMapName ); + strcpy( pLevelList[listCount].landmarkName, pLandmarkName ); + pLevelList[listCount].pentLandmark = pentLandmark; + pLevelList[listCount].vecLandmarkOrigin = VARS(pentLandmark)->origin; + + return 1; +} + +int BuildChangeList( LEVELLIST *pLevelList, int maxList ) +{ + return CChangeLevel::ChangeList( pLevelList, maxList ); +} + + +int CChangeLevel::InTransitionVolume( CBaseEntity *pEntity, char *pVolumeName ) +{ + edict_t *pentVolume; + + + if ( pEntity->ObjectCaps() & FCAP_FORCE_TRANSITION ) + return 1; + + // If you're following another entity, follow it through the transition (weapons follow the player) + if ( pEntity->pev->movetype == MOVETYPE_FOLLOW ) + { + if ( pEntity->pev->aiment != NULL ) + pEntity = CBaseEntity::Instance( pEntity->pev->aiment ); + } + + int inVolume = 1; // Unless we find a trigger_transition, everything is in the volume + + pentVolume = FIND_ENTITY_BY_TARGETNAME( NULL, pVolumeName ); + while ( !FNullEnt( pentVolume ) ) + { + CBaseEntity *pVolume = CBaseEntity::Instance( pentVolume ); + + if ( pVolume && FClassnameIs( pVolume->pev, "trigger_transition" ) ) + { + if ( pVolume->Intersects( pEntity ) ) // It touches one, it's in the volume + return 1; + else + inVolume = 0; // Found a trigger_transition, but I don't intersect it -- if I don't find another, don't go! + } + pentVolume = FIND_ENTITY_BY_TARGETNAME( pentVolume, pVolumeName ); + } + + return inVolume; +} + + +// We can only ever move 512 entities across a transition +#define MAX_ENTITY 512 + +// This has grown into a complicated beast +// Can we make this more elegant? +// This builds the list of all transitions on this level and which entities are in their PVS's and can / should +// be moved across. +int CChangeLevel::ChangeList( LEVELLIST *pLevelList, int maxList ) +{ + edict_t *pentChangelevel, *pentLandmark; + int i, count; + + count = 0; + + // Find all of the possible level changes on this BSP + pentChangelevel = FIND_ENTITY_BY_STRING( NULL, "classname", "trigger_changelevel" ); + if ( FNullEnt( pentChangelevel ) ) + return 0; + while ( !FNullEnt( pentChangelevel ) ) + { + CChangeLevel *pTrigger; + + pTrigger = GetClassPtr((CChangeLevel *)VARS(pentChangelevel)); + if ( pTrigger ) + { + // Find the corresponding landmark + pentLandmark = FindLandmark( pTrigger->m_szLandmarkName ); + if ( pentLandmark ) + { + // Build a list of unique transitions + if ( AddTransitionToList( pLevelList, count, pTrigger->m_szMapName, pTrigger->m_szLandmarkName, pentLandmark ) ) + { + count++; + if ( count >= maxList ) // FULL!! + break; + } + } + } + pentChangelevel = FIND_ENTITY_BY_STRING( pentChangelevel, "classname", "trigger_changelevel" ); + } + + if ( gpGlobals->pSaveData && ((SAVERESTOREDATA *)gpGlobals->pSaveData)->pTable ) + { + CSave saveHelper( (SAVERESTOREDATA *)gpGlobals->pSaveData ); + + for ( i = 0; i < count; i++ ) + { + int j, entityCount = 0; + CBaseEntity *pEntList[ MAX_ENTITY ]; + int entityFlags[ MAX_ENTITY ]; + + // Follow the linked list of entities in the PVS of the transition landmark + edict_t *pent = UTIL_EntitiesInPVS( pLevelList[i].pentLandmark ); + + // Build a list of valid entities in this linked list (we're going to use pent->v.chain again) + while ( !FNullEnt( pent ) ) + { + CBaseEntity *pEntity = CBaseEntity::Instance(pent); + if ( pEntity ) + { +// ALERT( at_console, "Trying %s\n", STRING(pEntity->pev->classname) ); + int caps = pEntity->ObjectCaps(); + if ( !(caps & FCAP_DONT_SAVE) ) + { + int flags = 0; + + // If this entity can be moved or is global, mark it + if ( caps & FCAP_ACROSS_TRANSITION ) + flags |= FENTTABLE_MOVEABLE; + if ( pEntity->pev->globalname && !pEntity->IsDormant() ) + flags |= FENTTABLE_GLOBAL; + if ( flags ) + { + pEntList[ entityCount ] = pEntity; + entityFlags[ entityCount ] = flags; + entityCount++; + if ( entityCount > MAX_ENTITY ) + ALERT( at_error, "Too many entities across a transition!" ); + } +// else +// ALERT( at_console, "Failed %s\n", STRING(pEntity->pev->classname) ); + } +// else +// ALERT( at_console, "DON'T SAVE %s\n", STRING(pEntity->pev->classname) ); + } + pent = pent->v.chain; + } + + for ( j = 0; j < entityCount; j++ ) + { + // Check to make sure the entity isn't screened out by a trigger_transition + if ( entityFlags[j] && InTransitionVolume( pEntList[j], pLevelList[i].landmarkName ) ) + { + // Mark entity table with 1<pev->classname) ); + + } + } + } + + return count; +} + +/* +go to the next level for deathmatch +only called if a time or frag limit has expired +*/ +void NextLevel( void ) +{ + edict_t* pent; + CChangeLevel *pChange; + + // find a trigger_changelevel + pent = FIND_ENTITY_BY_CLASSNAME(NULL, "trigger_changelevel"); + + // go back to start if no trigger_changelevel + if (FNullEnt(pent)) + { + gpGlobals->mapname = ALLOC_STRING("start"); + pChange = GetClassPtr( (CChangeLevel *)NULL ); + strcpy(pChange->m_szMapName, "start"); + } + else + pChange = GetClassPtr( (CChangeLevel *)VARS(pent)); + + strcpy(st_szNextMap, pChange->m_szMapName); + g_fGameOver = TRUE; + + if (pChange->pev->nextthink < gpGlobals->time) + { + pChange->SetThink( &CChangeLevel::ExecuteChangeLevel ); + pChange->pev->nextthink = gpGlobals->time + 0.1; + } +} + + +// ============================== LADDER ======================================= + +class CLadder : public CBaseTrigger +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS( func_ladder, CLadder ); + + +void CLadder :: KeyValue( KeyValueData *pkvd ) +{ + CBaseTrigger::KeyValue( pkvd ); +} + + +//========================================================= +// func_ladder - makes an area vertically negotiable +//========================================================= +void CLadder :: Precache( void ) +{ + // Do all of this in here because we need to 'convert' old saved games + pev->solid = SOLID_NOT; + pev->skin = CONTENTS_LADDER; + if ( CVAR_GET_FLOAT("showtriggers") == 0 ) + { + pev->rendermode = kRenderTransTexture; + pev->renderamt = 0; + } + pev->effects &= ~EF_NODRAW; +} + + +void CLadder :: Spawn( void ) +{ + Precache(); + + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->movetype = MOVETYPE_PUSH; +} + + +// ========================== A TRIGGER THAT PUSHES YOU =============================== + +class CTriggerPush : public CBaseTrigger +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Touch( CBaseEntity *pOther ); +}; +LINK_ENTITY_TO_CLASS( trigger_push, CTriggerPush ); + + +void CTriggerPush :: KeyValue( KeyValueData *pkvd ) +{ + CBaseTrigger::KeyValue( pkvd ); +} + + +/*QUAKED trigger_push (.5 .5 .5) ? TRIG_PUSH_ONCE +Pushes the player +*/ + +void CTriggerPush :: Spawn( ) +{ + if ( pev->angles == g_vecZero ) + pev->angles.y = 360; + InitTrigger(); + + if (pev->speed == 0) + pev->speed = 100; + + if ( FBitSet (pev->spawnflags, SF_TRIGGER_PUSH_START_OFF) )// if flagged to Start Turned Off, make trigger nonsolid. + pev->solid = SOLID_NOT; + + SetUse( &CBaseTrigger::ToggleUse ); + + UTIL_SetOrigin( pev, pev->origin ); // Link into the list +} + + +void CTriggerPush :: Touch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + + // UNDONE: Is there a better way than health to detect things that have physics? (clients/monsters) + switch( pevToucher->movetype ) + { + case MOVETYPE_NONE: + case MOVETYPE_PUSH: + case MOVETYPE_NOCLIP: + case MOVETYPE_FOLLOW: + return; + } + + if ( pevToucher->solid != SOLID_NOT && pevToucher->solid != SOLID_BSP ) + { + // Instant trigger, just transfer velocity and remove + if (FBitSet(pev->spawnflags, SF_TRIG_PUSH_ONCE)) + { + pevToucher->velocity = pevToucher->velocity + (pev->speed * pev->movedir); + if ( pevToucher->velocity.z > 0 ) + pevToucher->flags &= ~FL_ONGROUND; + UTIL_Remove( this ); + } + else + { // Push field, transfer to base velocity + Vector vecPush = (pev->speed * pev->movedir); + if ( pevToucher->flags & FL_BASEVELOCITY ) + vecPush = vecPush + pevToucher->basevelocity; + + pevToucher->basevelocity = vecPush; + + pevToucher->flags |= FL_BASEVELOCITY; +// ALERT( at_console, "Vel %f, base %f\n", pevToucher->velocity.z, pevToucher->basevelocity.z ); + } + } +} + + +//====================================== +// teleport trigger +// +// + +void CBaseTrigger :: TeleportTouch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + edict_t *pentTarget = NULL; + + if (!UTIL_IsMasterTriggered(m_sMaster, pOther)) + return; + + if ( !( pev->spawnflags & SF_TRIGGER_ALLOWMONSTERS ) ) + {// no monsters allowed! + if ( FBitSet( pevToucher->flags, FL_MONSTER ) ) + { + return; + } + } + + if ( ( pev->spawnflags & SF_TRIGGER_NOCLIENTS ) ) + {// no clients allowed + if ( pOther->IsPlayer() ) + { + return; + } + } + + pentTarget = FIND_ENTITY_BY_TARGETNAME( pentTarget, STRING(pev->target) ); + if (FNullEnt(pentTarget)) + return; + + Vector tmp = VARS( pentTarget )->origin; + + if ( pOther->IsPlayer() ) + { + tmp.z -= pOther->pev->mins.z;// make origin adjustments in case the teleportee is a player. (origin in center, not at feet) + } + + tmp.z++; + + // Sprite in old position + CSprite *pSprite = CSprite::SpriteCreate( "sprites/discreturn.spr", pOther->pev->origin, TRUE ); + pSprite->AnimateAndDie( 60 ); + pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); + pSprite->SetScale( 1 ); + pSprite->pev->groupinfo = pOther->pev->groupinfo; + + pevToucher->flags &= ~FL_ONGROUND; + //UTIL_SetOrigin( pevToucher, tmp ); + pevToucher->origin = tmp; + + // Play a warp sound and sprite + pSprite = CSprite::SpriteCreate( "sprites/discreturn.spr", pOther->pev->origin, TRUE ); + pSprite->AnimateAndDie( 60 ); + pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); + pSprite->SetScale( 1 ); + pSprite->pev->groupinfo = pOther->pev->groupinfo; + EMIT_SOUND_DYN( pOther->edict(), CHAN_AUTO, "discreturn.wav", 1.0, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + + pevToucher->angles = pentTarget->v.angles; + + if ( pOther->IsPlayer() ) + { + pevToucher->v_angle = pentTarget->v.angles; + pevToucher->fixangle = TRUE; + } + + // Discs keep their velocity + if ( strcmp( STRING(pOther->pev->classname), "disc" ) ) + { + pevToucher->velocity = pevToucher->basevelocity = g_vecZero; + } + else + { + UTIL_MakeVectors( pevToucher->angles ); + if ( ((CDisc*)pOther)->m_iPowerupFlags & POW_FAST ) + pevToucher->velocity = pevToucher->basevelocity = gpGlobals->v_forward * DISC_VELOCITY * 1.5; + else + pevToucher->velocity = pevToucher->basevelocity = gpGlobals->v_forward * DISC_VELOCITY; + + ((CDisc*)pOther)->m_bTeleported = true; + } +} + + +class CTriggerTeleport : public CBaseTrigger +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS( trigger_teleport, CTriggerTeleport ); + +void CTriggerTeleport :: Spawn( void ) +{ + InitTrigger(); + + SetTouch( &CBaseTrigger::TeleportTouch ); +} + + +LINK_ENTITY_TO_CLASS( info_teleport_destination, CPointEntity ); + + + +class CTriggerSave : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT SaveTouch( CBaseEntity *pOther ); +}; +LINK_ENTITY_TO_CLASS( trigger_autosave, CTriggerSave ); + +void CTriggerSave::Spawn( void ) +{ + if ( g_pGameRules->IsDeathmatch() ) + { + REMOVE_ENTITY( ENT(pev) ); + return; + } + + InitTrigger(); + SetTouch( &CTriggerSave::SaveTouch ); +} + +void CTriggerSave::SaveTouch( CBaseEntity *pOther ) +{ + if ( !UTIL_IsMasterTriggered( m_sMaster, pOther ) ) + return; + + // Only save on clients + if ( !pOther->IsPlayer() ) + return; + + SetTouch( NULL ); + UTIL_Remove( this ); + SERVER_COMMAND( "autosave\n" ); +} + +#define SF_ENDSECTION_USEONLY 0x0001 + +class CTriggerEndSection : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT EndSectionTouch( CBaseEntity *pOther ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT EndSectionUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; +LINK_ENTITY_TO_CLASS( trigger_endsection, CTriggerEndSection ); + + +void CTriggerEndSection::EndSectionUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Only save on clients + if ( !pActivator->IsNetClient() ) + return; + + SetUse( NULL ); + + if ( pev->message ) + { + g_engfuncs.pfnEndSection(STRING(pev->message)); + } + UTIL_Remove( this ); +} + +void CTriggerEndSection::Spawn( void ) +{ + if ( g_pGameRules->IsDeathmatch() ) + { + REMOVE_ENTITY( ENT(pev) ); + return; + } + + InitTrigger(); + + SetUse ( &CTriggerEndSection::EndSectionUse ); + // If it is a "use only" trigger, then don't set the touch function. + if ( ! (pev->spawnflags & SF_ENDSECTION_USEONLY) ) + SetTouch( &CTriggerEndSection::EndSectionTouch ); +} + +void CTriggerEndSection::EndSectionTouch( CBaseEntity *pOther ) +{ + // Only save on clients + if ( !pOther->IsNetClient() ) + return; + + SetTouch( NULL ); + + if (pev->message) + { + g_engfuncs.pfnEndSection(STRING(pev->message)); + } + UTIL_Remove( this ); +} + +void CTriggerEndSection :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "section")) + { +// m_iszSectionName = ALLOC_STRING( pkvd->szValue ); + // Store this in message so we don't have to write save/restore for this ent + pev->message = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseTrigger::KeyValue( pkvd ); +} + + +class CTriggerGravity : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT GravityTouch( CBaseEntity *pOther ); +}; +LINK_ENTITY_TO_CLASS( trigger_gravity, CTriggerGravity ); + +void CTriggerGravity::Spawn( void ) +{ + InitTrigger(); + SetTouch( &CTriggerGravity::GravityTouch ); +} + +void CTriggerGravity::GravityTouch( CBaseEntity *pOther ) +{ + // Only save on clients + if ( !pOther->IsPlayer() ) + return; + + pOther->pev->gravity = pev->gravity; +} + + + + + + + +// this is a really bad idea. +class CTriggerChangeTarget : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_iszNewTarget; +}; +LINK_ENTITY_TO_CLASS( trigger_changetarget, CTriggerChangeTarget ); + +TYPEDESCRIPTION CTriggerChangeTarget::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerChangeTarget, m_iszNewTarget, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerChangeTarget,CBaseDelay); + +void CTriggerChangeTarget::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszNewTarget")) + { + m_iszNewTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + +void CTriggerChangeTarget::Spawn( void ) +{ +} + + +void CTriggerChangeTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBaseEntity *pTarget = UTIL_FindEntityByString( NULL, "targetname", STRING( pev->target ) ); + + if (pTarget) + { + pTarget->pev->target = m_iszNewTarget; + CBaseMonster *pMonster = pTarget->MyMonsterPointer( ); + if (pMonster) + { + pMonster->m_pGoalEnt = NULL; + } + } +} + + + + +#define SF_CAMERA_PLAYER_POSITION 1 +#define SF_CAMERA_PLAYER_TARGET 2 +#define SF_CAMERA_PLAYER_TAKECONTROL 4 + +class CTriggerCamera : public CBaseDelay +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT FollowTarget( void ); + void Move(void); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + static TYPEDESCRIPTION m_SaveData[]; + + EHANDLE m_hPlayer; + EHANDLE m_hTarget; + CBaseEntity *m_pentPath; + int m_sPath; + float m_flWait; + float m_flReturnTime; + float m_flStopTime; + float m_moveDistance; + float m_targetSpeed; + float m_initialSpeed; + float m_acceleration; + float m_deceleration; + int m_state; + +}; +LINK_ENTITY_TO_CLASS( trigger_camera, CTriggerCamera ); + +// Global Savedata for changelevel friction modifier +TYPEDESCRIPTION CTriggerCamera::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerCamera, m_hPlayer, FIELD_EHANDLE ), + DEFINE_FIELD( CTriggerCamera, m_hTarget, FIELD_EHANDLE ), + DEFINE_FIELD( CTriggerCamera, m_pentPath, FIELD_CLASSPTR ), + DEFINE_FIELD( CTriggerCamera, m_sPath, FIELD_STRING ), + DEFINE_FIELD( CTriggerCamera, m_flWait, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_flReturnTime, FIELD_TIME ), + DEFINE_FIELD( CTriggerCamera, m_flStopTime, FIELD_TIME ), + DEFINE_FIELD( CTriggerCamera, m_moveDistance, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_targetSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_initialSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_acceleration, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_deceleration, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_state, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerCamera,CBaseDelay); + +void CTriggerCamera::Spawn( void ) +{ + pev->movetype = MOVETYPE_NOCLIP; + pev->solid = SOLID_NOT; // Remove model & collisions + pev->renderamt = 0; // The engine won't draw this model if this is set to 0 and blending is on + pev->rendermode = kRenderTransTexture; + + m_initialSpeed = pev->speed; + if ( m_acceleration == 0 ) + m_acceleration = 500; + if ( m_deceleration == 0 ) + m_deceleration = 500; +} + + +void CTriggerCamera :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "moveto")) + { + m_sPath = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "acceleration")) + { + m_acceleration = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "deceleration")) + { + m_deceleration = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + + + +void CTriggerCamera::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_state ) ) + return; + + // Toggle state + m_state = !m_state; + if (m_state == 0) + { + m_flReturnTime = gpGlobals->time; + return; + } + if ( !pActivator || !pActivator->IsPlayer() ) + { + pActivator = CBaseEntity::Instance(g_engfuncs.pfnPEntityOfEntIndex( 1 )); + } + + m_hPlayer = pActivator; + + m_flReturnTime = gpGlobals->time + m_flWait; + pev->speed = m_initialSpeed; + m_targetSpeed = m_initialSpeed; + + if (FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_TARGET ) ) + { + m_hTarget = m_hPlayer; + } + else + { + m_hTarget = GetNextTarget(); + } + + // Nothing to look at! + if ( m_hTarget == 0 ) + { + return; + } + + + if (FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_TAKECONTROL ) ) + { + ((CBasePlayer *)pActivator)->EnableControl(FALSE); + } + + if ( m_sPath ) + { + m_pentPath = Instance( FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(m_sPath)) ); + } + else + { + m_pentPath = NULL; + } + + m_flStopTime = gpGlobals->time; + if ( m_pentPath ) + { + if ( m_pentPath->pev->speed != 0 ) + m_targetSpeed = m_pentPath->pev->speed; + + m_flStopTime += m_pentPath->GetDelay(); + } + + // copy over player information + if (FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_POSITION ) ) + { + UTIL_SetOrigin( pev, pActivator->pev->origin + pActivator->pev->view_ofs ); + pev->angles.x = -pActivator->pev->angles.x; + pev->angles.y = pActivator->pev->angles.y; + pev->angles.z = 0; + pev->velocity = pActivator->pev->velocity; + } + else + { + pev->velocity = Vector( 0, 0, 0 ); + } + + SET_VIEW( pActivator->edict(), edict() ); + + SET_MODEL(ENT(pev), STRING(pActivator->pev->model) ); + + // follow the player down + SetThink( &CTriggerCamera::FollowTarget ); + pev->nextthink = gpGlobals->time; + + m_moveDistance = 0; + Move(); +} + + +void CTriggerCamera::FollowTarget( ) +{ + if (m_hPlayer == 0) + return; + + if (m_hTarget == 0 || m_flReturnTime < gpGlobals->time) + { + if (m_hPlayer->IsAlive( )) + { + SET_VIEW( m_hPlayer->edict(), m_hPlayer->edict() ); + ((CBasePlayer *)((CBaseEntity *)m_hPlayer))->EnableControl(TRUE); + } + SUB_UseTargets( this, USE_TOGGLE, 0 ); + pev->avelocity = Vector( 0, 0, 0 ); + m_state = 0; + return; + } + + Vector vecGoal = UTIL_VecToAngles( m_hTarget->pev->origin - pev->origin ); + vecGoal.x = -vecGoal.x; + + if (pev->angles.y > 360) + pev->angles.y -= 360; + + if (pev->angles.y < 0) + pev->angles.y += 360; + + float dx = vecGoal.x - pev->angles.x; + float dy = vecGoal.y - pev->angles.y; + + if (dx < -180) + dx += 360; + if (dx > 180) + dx = dx - 360; + + if (dy < -180) + dy += 360; + if (dy > 180) + dy = dy - 360; + + pev->avelocity.x = dx * 40 * gpGlobals->frametime; + pev->avelocity.y = dy * 40 * gpGlobals->frametime; + + + if (!(FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_TAKECONTROL))) + { + pev->velocity = pev->velocity * 0.8; + if (pev->velocity.Length( ) < 10.0) + pev->velocity = g_vecZero; + } + + pev->nextthink = gpGlobals->time; + + Move(); +} + +void CTriggerCamera::Move() +{ + // Not moving on a path, return + if (!m_pentPath) + return; + + // Subtract movement from the previous frame + m_moveDistance -= pev->speed * gpGlobals->frametime; + + // Have we moved enough to reach the target? + if ( m_moveDistance <= 0 ) + { + // Fire the passtarget if there is one + if ( m_pentPath->pev->message ) + { + FireTargets( STRING(m_pentPath->pev->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( m_pentPath->pev->spawnflags, SF_CORNER_FIREONCE ) ) + m_pentPath->pev->message = 0; + } + // Time to go to the next target + m_pentPath = m_pentPath->GetNextTarget(); + + // Set up next corner + if ( !m_pentPath ) + { + pev->velocity = g_vecZero; + } + else + { + if ( m_pentPath->pev->speed != 0 ) + m_targetSpeed = m_pentPath->pev->speed; + + Vector delta = m_pentPath->pev->origin - pev->origin; + m_moveDistance = delta.Length(); + pev->movedir = delta.Normalize(); + m_flStopTime = gpGlobals->time + m_pentPath->GetDelay(); + } + } + + if ( m_flStopTime > gpGlobals->time ) + pev->speed = UTIL_Approach( 0, pev->speed, m_deceleration * gpGlobals->frametime ); + else + pev->speed = UTIL_Approach( m_targetSpeed, pev->speed, m_acceleration * gpGlobals->frametime ); + + float fraction = 2 * gpGlobals->frametime; + pev->velocity = ((pev->movedir * pev->speed) * fraction) + (pev->velocity * (1-fraction)); +} + + +//=============================================================================== +// DISCWAR OBJECTS +//=============================================================================== +// Brush that's status gets toggled by a disc hit +LINK_ENTITY_TO_CLASS( func_disctoggle, CDiscTarget ); + +void CDiscTarget::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "hitby_friendly") ) + { + m_iszFriendlyHit = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "hitby_enemy") ) + { + m_iszEnemyHit = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "arena") ) + { + pev->groupinfo = 1 << (atoi( pkvd->szValue ) - 1); + pkvd->fHandled = TRUE; + } + else + CBaseTrigger::KeyValue( pkvd ); +} + +void CDiscTarget::Spawn( void ) +{ + Precache(); + + pev->solid = SOLID_TRIGGER; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + + m_iState = 0; + SetTouch( &CDiscTarget::DiscToggleTouch ); +} + +void CDiscTarget::Reset( void ) +{ + m_iState = 0; + pev->frame = 0; + pev->target = m_iszFriendlyHit; +} + +void CDiscTarget::DiscToggleTouch( CBaseEntity *pOther ) +{ + if ( strcmp( STRING(pOther->pev->classname), "disc" ) ) + return; + + int iHitBy = 0; + CBaseEntity *pOwner = (CBaseEntity *)((CDisc *)pOther)->m_hOwner; + + // Hit by friendly if the teams match. Enemy otherwise + if ( pOwner && pOwner->pev->team == pev->team ) + { + Reset(); + iHitBy = LAST_HITBY_FRIENDLY; + } + else + { + pev->target = m_iszEnemyHit; + iHitBy = LAST_HITBY_ENEMY; + + pev->frame = 1; + } + + // Don't toggle if we were last hit by the same team + if (m_iState == iHitBy) + return; + + // Fire the target + SUB_UseTargets( pOwner, USE_TOGGLE, iHitBy ); + m_iState = iHitBy; +} + +//=============================================================================== +// Brush that toggles between gone/there +LINK_ENTITY_TO_CLASS( func_plat_toggleremove, CPlatToggleRemove ); + +void CPlatToggleRemove::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "arena") ) + { + pev->groupinfo = 1 << (atoi( pkvd->szValue ) - 1); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CPlatToggleRemove::Spawn( void ) +{ + Precache(); + + // Remove all but one copy of this ent if we're not in arena mode + if ( InArenaMode() == FALSE ) + { + if ( pev->groupinfo > 1 ) + { + pev->flags |= FL_KILLME; + return; + } + + // Clear groupinfo + pev->groupinfo = 0; + } + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + SET_MODEL(ENT(pev), STRING(pev->model)); + + SetUse( &CPlatToggleRemove::PlatToggleRemoveUse ); +} + +void CPlatToggleRemove::Reset( void ) +{ + pev->rendermode = kRenderNormal; + pev->renderamt = 255; + + pev->effects &= ~EF_NODRAW; + pev->solid = SOLID_BSP; + SET_MODEL(ENT(pev), STRING(pev->model)); + + SetThink( NULL ); + pev->nextthink = 0; +} + +void CPlatToggleRemove::PlatToggleRemoveUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( value == LAST_HITBY_ENEMY ) + { + // Make it fade out + SetThink( &CPlatToggleRemove::PlatRemoveThink ); + pev->nextthink = pev->ltime + 0.1; + m_flRemoveAt = gpGlobals->time + PLAT_FADE_TIME; + + pev->rendermode = kRenderTransAdd; + pev->renderamt = 250; + } + else + { + Reset(); + } +} + +void CPlatToggleRemove::PlatRemoveThink( void ) +{ + float flTimeLeft = m_flRemoveAt - gpGlobals->time; + + // Fade until m_flRemoveAt + if ( flTimeLeft > 0 ) + { + float flPercent = flTimeLeft / PLAT_FADE_TIME; + pev->renderamt = 250 * flPercent; + pev->nextthink = pev->ltime + 0.1; + } + else + { + // Fully gone + pev->effects |= EF_NODRAW; + pev->solid = SOLID_NOT; + SET_MODEL(ENT(pev), STRING(pev->model)); + } +} + +//=============================================================================== +// Brush that jumps a player to a target point +LINK_ENTITY_TO_CLASS( trigger_jump, CTriggerJump ); + +void CTriggerJump::Spawn( void ) +{ + Precache(); + InitTrigger(); + SetTouch( &CTriggerJump::JumpTouch ); + SetUse( &CTriggerJump::JumpUse ); + + if (!m_flHeight) + m_flHeight = 150; +} + +void CTriggerJump::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "height") ) + { + m_flHeight = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseTrigger::KeyValue( pkvd ); +} + +void CTriggerJump::Precache( void ) +{ + PRECACHE_SOUND( "triggerjump.wav" ); + + m_usJump = PRECACHE_EVENT( 1, "events/jump.sc" ); +} + +void CTriggerJump::Activate( void ) +{ + // Find the target point + if (!FStringNull(pev->target)) + { + edict_t* pentTarget = NULL; + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->target)); + if (FNullEnt(pentTarget)) + { + ALERT ( at_console, "trigger_jump - Could not find target %s\n", STRING(pev->target) ); + pev->flags |= FL_KILLME; + } + else + { + m_vecTargetOrg = pentTarget->v.origin; + } + } +} + +void CTriggerJump::JumpUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( m_iState == 0 ) + { + m_iState = 1; + SetTouch( &CTriggerJump::JumpTouch ); + } + else + { + m_iState = 0; + SetTouch( NULL ); + } +} + +void CTriggerJump::JumpTouch( CBaseEntity *pOther ) +{ + TraceResult tr; + float flGravity = CVAR_GET_FLOAT( "sv_gravity" ); + + // Don't touch again immediately + if ( pOther->m_flTouchedByJumpPad > gpGlobals->time ) + { + m_flTouchedByJumpPad = gpGlobals->time + 0.1; + return; + } + + // get a rough idea of how high to launch + Vector vecMidPoint = pOther->pev->origin + (m_vecTargetOrg - pOther->pev->origin) * 0.5; + UTIL_TraceLine(vecMidPoint, vecMidPoint + Vector(0,0,m_flHeight), ignore_monsters, ENT(pev), &tr); + vecMidPoint = tr.vecEndPos; + // (subtract 15 so we don't hit the ceiling) + vecMidPoint.z -= 15; + + // How high should we travel to reach the apex + float distance1 = (vecMidPoint.z - pOther->pev->origin.z); + float distance2 = (vecMidPoint.z - m_vecTargetOrg.z); + + // How long will it take to travel this distance + float time1 = sqrt( distance1 / (0.5 * flGravity) ); + float time2 = sqrt( distance2 / (0.5 * flGravity) ); + if (time1 < 0.1) + return; + + // how hard to launch to get there in time. + Vector vecTargetVel = (m_vecTargetOrg - pOther->pev->origin) / (time1 + time2); + vecTargetVel.z = flGravity * time1; + + // don't affect the player again for a bit + pOther->m_flTouchedByJumpPad = gpGlobals->time + 0.2; + pOther->pev->velocity = vecTargetVel; + if ( pOther->IsPlayer() ) + ((CBasePlayer*)pOther)->SetAnimation( PLAYER_SUPERJUMP ); + + // Play a sound + PLAYBACK_EVENT_FULL( FEV_NOTHOST, pOther->edict(), m_usJump, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0 ); +} + +//=============================================================================== +// Trigger that returns discs to their thrower immediately +LINK_ENTITY_TO_CLASS( trigger_discreturn, CTriggerDiscReturn ); + +void CTriggerDiscReturn::Spawn( void ) +{ + Precache(); + InitTrigger(); + SetTouch( &CTriggerDiscReturn::DiscReturnTouch ); +} + +void CTriggerDiscReturn::Precache( void ) +{ + PRECACHE_MODEL( "sprites/discreturn.spr" ); + PRECACHE_SOUND( "discreturn.wav" ); +} + +void CTriggerDiscReturn::DiscReturnTouch( CBaseEntity *pOther ) +{ + if ( strcmp( STRING(pOther->pev->classname), "disc" ) ) + return; + + // Play a warp sound and sprite + CSprite *pSprite = CSprite::SpriteCreate( "sprites/discreturn.spr", pOther->pev->origin, TRUE ); + pSprite->AnimateAndDie( 60 ); + pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); + pSprite->SetScale( 1 ); + pSprite->pev->groupinfo = pOther->pev->groupinfo; + + EMIT_SOUND_DYN( pOther->edict(), CHAN_AUTO, "discreturn.wav", 0.2, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + + // Return + ((CDisc*)pOther)->ReturnToThrower(); +} + +//=============================================================================== +// Trigger that starts the fall animation for players +LINK_ENTITY_TO_CLASS( trigger_fall, CTriggerFall ); + +void CTriggerFall::Spawn( void ) +{ + Precache(); + InitTrigger(); + SetTouch( &CTriggerFall::FallTouch ); +} + +void CTriggerFall::FallTouch( CBaseEntity *pOther ) +{ + if ( pOther->IsPlayer() == FALSE ) + return; + + if ( pOther->IsAlive() == FALSE ) + return; + + switch( RANDOM_LONG(0,2) ) + { + default: + case 0: EMIT_SOUND(ENT(pOther->pev), CHAN_STATIC, "scream1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pOther->pev), CHAN_STATIC, "scream2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pOther->pev), CHAN_STATIC, "scream3.wav", 1, ATTN_NORM); break; + } + + pOther->TakeDamage( pev, pev, 500, DMG_NEVERGIB ); + ((CBasePlayer*)pOther)->SetAnimation( PLAYER_FALL ); + + // Make their model shrink away to nothing + pOther->pev->renderfx = kRenderFxExplode; + pOther->pev->health = -5; // Setting this to -5 starts the client-side fall animation +} + diff --git a/hlsdk/ricochet/dlls/util.cpp b/hlsdk/ricochet/dlls/util.cpp new file mode 100644 index 00000000..01863a99 --- /dev/null +++ b/hlsdk/ricochet/dlls/util.cpp @@ -0,0 +1,2567 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== util.cpp ======================================================== + + Utility code. Really not optional after all. + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include +#include "../engine/shake.h" +#include "decals.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" + +/* +===================== +UTIL_WeaponTimeBase + +Time basis for weapons ( zero based of predicting client weapons ) +===================== +*/ +float UTIL_WeaponTimeBase( void ) +{ + return 0.0; +} + +static unsigned int glSeed = 0; + +unsigned int seed_table[ 256 ] = +{ + 28985, 27138, 26457, 9451, 17764, 10909, 28790, 8716, 6361, 4853, 17798, 21977, 19643, 20662, 10834, 20103, + 27067, 28634, 18623, 25849, 8576, 26234, 23887, 18228, 32587, 4836, 3306, 1811, 3035, 24559, 18399, 315, + 26766, 907, 24102, 12370, 9674, 2972, 10472, 16492, 22683, 11529, 27968, 30406, 13213, 2319, 23620, 16823, + 10013, 23772, 21567, 1251, 19579, 20313, 18241, 30130, 8402, 20807, 27354, 7169, 21211, 17293, 5410, 19223, + 10255, 22480, 27388, 9946, 15628, 24389, 17308, 2370, 9530, 31683, 25927, 23567, 11694, 26397, 32602, 15031, + 18255, 17582, 1422, 28835, 23607, 12597, 20602, 10138, 5212, 1252, 10074, 23166, 19823, 31667, 5902, 24630, + 18948, 14330, 14950, 8939, 23540, 21311, 22428, 22391, 3583, 29004, 30498, 18714, 4278, 2437, 22430, 3439, + 28313, 23161, 25396, 13471, 19324, 15287, 2563, 18901, 13103, 16867, 9714, 14322, 15197, 26889, 19372, 26241, + 31925, 14640, 11497, 8941, 10056, 6451, 28656, 10737, 13874, 17356, 8281, 25937, 1661, 4850, 7448, 12744, + 21826, 5477, 10167, 16705, 26897, 8839, 30947, 27978, 27283, 24685, 32298, 3525, 12398, 28726, 9475, 10208, + 617, 13467, 22287, 2376, 6097, 26312, 2974, 9114, 21787, 28010, 4725, 15387, 3274, 10762, 31695, 17320, + 18324, 12441, 16801, 27376, 22464, 7500, 5666, 18144, 15314, 31914, 31627, 6495, 5226, 31203, 2331, 4668, + 12650, 18275, 351, 7268, 31319, 30119, 7600, 2905, 13826, 11343, 13053, 15583, 30055, 31093, 5067, 761, + 9685, 11070, 21369, 27155, 3663, 26542, 20169, 12161, 15411, 30401, 7580, 31784, 8985, 29367, 20989, 14203, + 29694, 21167, 10337, 1706, 28578, 887, 3373, 19477, 14382, 675, 7033, 15111, 26138, 12252, 30996, 21409, + 25678, 18555, 13256, 23316, 22407, 16727, 991, 9236, 5373, 29402, 6117, 15241, 27715, 19291, 19888, 19847 +}; + +unsigned int U_Random( void ) +{ + glSeed *= 69069; + glSeed += seed_table[ glSeed & 0xff ]; + + return ( ++glSeed & 0x0fffffff ); +} + +void U_Srand( unsigned int seed ) +{ + glSeed = seed_table[ seed & 0xff ]; +} + +/* +===================== +UTIL_SharedRandomLong +===================== +*/ +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ) +{ + + unsigned int range; + + U_Srand( (int)seed + low + high ); + + range = high - low + 1; + if ( !(range - 1) ) + { + return low; + } + else + { + int offset; + int rnum; + + rnum = U_Random(); + + offset = rnum % range; + + return (low + offset); + } +} + +/* +===================== +UTIL_SharedRandomFloat +===================== +*/ +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ) +{ + // + unsigned int range; + + U_Srand( (int)seed + *(int *)&low + *(int *)&high ); + + U_Random(); + U_Random(); + + range = static_cast(high - low); + if ( !range ) + { + return low; + } + else + { + int tensixrand; + float offset; + + tensixrand = U_Random() & 65535; + + offset = (float)tensixrand / 65536.0; + + return (low + offset * range ); + } +} + +void UTIL_ParametricRocket( entvars_t *pev, Vector vecOrigin, Vector vecAngles, edict_t *owner ) +{ + pev->startpos = vecOrigin; + // Trace out line to end pos + TraceResult tr; + UTIL_MakeVectors( vecAngles ); + UTIL_TraceLine( pev->startpos, pev->startpos + gpGlobals->v_forward * 8192, ignore_monsters, owner, &tr); + pev->endpos = tr.vecEndPos; + + // Now compute how long it will take based on current velocity + Vector vecTravel = pev->endpos - pev->startpos; + float travelTime = 0.0; + if ( pev->velocity.Length() > 0 ) + { + travelTime = vecTravel.Length() / pev->velocity.Length(); + } + pev->starttime = gpGlobals->time; + pev->impacttime = gpGlobals->time + travelTime; +} + +int g_groupmask = 0; +int g_groupop = 0; + +// Normal overrides +void UTIL_SetGroupTrace( int groupmask, int op ) +{ + g_groupmask = groupmask; + g_groupop = op; + + ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); +} + +void UTIL_UnsetGroupTrace( void ) +{ + g_groupmask = 0; + g_groupop = 0; + + ENGINE_SETGROUPMASK( 0, 0 ); +} + +// Smart version, it'll clean itself up when it pops off stack +UTIL_GroupTrace::UTIL_GroupTrace( int groupmask, int op ) +{ + m_oldgroupmask = g_groupmask; + m_oldgroupop = g_groupop; + + g_groupmask = groupmask; + g_groupop = op; + + ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); +} + +UTIL_GroupTrace::~UTIL_GroupTrace( void ) +{ + g_groupmask = m_oldgroupmask; + g_groupop = m_oldgroupop; + + ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); +} + +TYPEDESCRIPTION gEntvarsDescription[] = +{ + DEFINE_ENTITY_FIELD( classname, FIELD_STRING ), + DEFINE_ENTITY_GLOBAL_FIELD( globalname, FIELD_STRING ), + + DEFINE_ENTITY_FIELD( origin, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_FIELD( oldorigin, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_FIELD( velocity, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( basevelocity, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( movedir, FIELD_VECTOR ), + + DEFINE_ENTITY_FIELD( angles, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( avelocity, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( punchangle, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( v_angle, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( fixangle, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( idealpitch, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( pitch_speed, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( ideal_yaw, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( yaw_speed, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( modelindex, FIELD_INTEGER ), + DEFINE_ENTITY_GLOBAL_FIELD( model, FIELD_MODELNAME ), + + DEFINE_ENTITY_FIELD( viewmodel, FIELD_MODELNAME ), + DEFINE_ENTITY_FIELD( weaponmodel, FIELD_MODELNAME ), + + DEFINE_ENTITY_FIELD( absmin, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_FIELD( absmax, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_GLOBAL_FIELD( mins, FIELD_VECTOR ), + DEFINE_ENTITY_GLOBAL_FIELD( maxs, FIELD_VECTOR ), + DEFINE_ENTITY_GLOBAL_FIELD( size, FIELD_VECTOR ), + + DEFINE_ENTITY_FIELD( ltime, FIELD_TIME ), + DEFINE_ENTITY_FIELD( nextthink, FIELD_TIME ), + + DEFINE_ENTITY_FIELD( solid, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( movetype, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( skin, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( body, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( effects, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( gravity, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( friction, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( light_level, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( frame, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( scale, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( sequence, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( animtime, FIELD_TIME ), + DEFINE_ENTITY_FIELD( framerate, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( controller, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( blending, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( rendermode, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( renderamt, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( rendercolor, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( renderfx, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( health, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( frags, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( weapons, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( takedamage, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( deadflag, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( view_ofs, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( button, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( impulse, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( chain, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( dmg_inflictor, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( enemy, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( aiment, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( owner, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( groundentity, FIELD_EDICT ), + + DEFINE_ENTITY_FIELD( spawnflags, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( flags, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( colormap, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( team, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( max_health, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( teleport_time, FIELD_TIME ), + DEFINE_ENTITY_FIELD( armortype, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( armorvalue, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( waterlevel, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( watertype, FIELD_INTEGER ), + + // Having these fields be local to the individual levels makes it easier to test those levels individually. + DEFINE_ENTITY_GLOBAL_FIELD( target, FIELD_STRING ), + DEFINE_ENTITY_GLOBAL_FIELD( targetname, FIELD_STRING ), + DEFINE_ENTITY_FIELD( netname, FIELD_STRING ), + DEFINE_ENTITY_FIELD( message, FIELD_STRING ), + + DEFINE_ENTITY_FIELD( dmg_take, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( dmg_save, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( dmg, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( dmgtime, FIELD_TIME ), + + DEFINE_ENTITY_FIELD( noise, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( noise1, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( noise2, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( noise3, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( speed, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( air_finished, FIELD_TIME ), + DEFINE_ENTITY_FIELD( pain_finished, FIELD_TIME ), + DEFINE_ENTITY_FIELD( radsuit_finished, FIELD_TIME ), +}; + +#define ENTVARS_COUNT (sizeof(gEntvarsDescription)/sizeof(gEntvarsDescription[0])) + + +#ifdef DEBUG +edict_t *DBG_EntOfVars( const entvars_t *pev ) +{ + if (pev->pContainingEntity != NULL) + return pev->pContainingEntity; + ALERT(at_console, "entvars_t pContainingEntity is NULL, calling into engine"); + edict_t* pent = (*g_engfuncs.pfnFindEntityByVars)((entvars_t*)pev); + if (pent == NULL) + ALERT(at_console, "DAMN! Even the engine couldn't FindEntityByVars!"); + ((entvars_t *)pev)->pContainingEntity = pent; + return pent; +} +#endif //DEBUG + + +#ifdef DEBUG + void +DBG_AssertFunction( + BOOL fExpr, + const char* szExpr, + const char* szFile, + int szLine, + const char* szMessage) + { + if (fExpr) + return; + char szOut[512]; + if (szMessage != NULL) + sprintf(szOut, "ASSERT FAILED:\n %s \n(%s@%d)\n%s", szExpr, szFile, szLine, szMessage); + else + sprintf(szOut, "ASSERT FAILED:\n %s \n(%s@%d)", szExpr, szFile, szLine); +// ALERT(at_console, szOut); + } +#endif // DEBUG + +BOOL UTIL_GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) +{ + return g_pGameRules->GetNextBestWeapon( pPlayer, pCurrentWeapon ); +} + +// ripped this out of the engine +float UTIL_AngleMod(float a) +{ + if (a < 0) + { + a = a + 360 * ((int)(a / 360) + 1); + } + else if (a >= 360) + { + a = a - 360 * ((int)(a / 360)); + } + // a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); + return a; +} + +float UTIL_AngleDiff( float destAngle, float srcAngle ) +{ + float delta; + + delta = destAngle - srcAngle; + if ( destAngle > srcAngle ) + { + if ( delta >= 180 ) + delta -= 360; + } + else + { + if ( delta <= -180 ) + delta += 360; + } + return delta; +} + +Vector UTIL_VecToAngles( const Vector &vec ) +{ + float rgflVecOut[3]; + VEC_TO_ANGLES(vec, rgflVecOut); + return Vector(rgflVecOut); +} + +// float UTIL_MoveToOrigin( edict_t *pent, const Vector vecGoal, float flDist, int iMoveType ) +void UTIL_MoveToOrigin( edict_t *pent, const Vector &vecGoal, float flDist, int iMoveType ) +{ + float rgfl[3]; + vecGoal.CopyToArray(rgfl); +// return MOVE_TO_ORIGIN ( pent, rgfl, flDist, iMoveType ); + MOVE_TO_ORIGIN ( pent, rgfl, flDist, iMoveType ); +} + + +int UTIL_EntitiesInBox( CBaseEntity **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + int count; + + count = 0; + + if ( !pEdict ) + return count; + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + if ( pEdict->free ) // Not in use + continue; + + if ( flagMask && !(pEdict->v.flags & flagMask) ) // Does it meet the criteria? + continue; + + if ( mins.x > pEdict->v.absmax.x || + mins.y > pEdict->v.absmax.y || + mins.z > pEdict->v.absmax.z || + maxs.x < pEdict->v.absmin.x || + maxs.y < pEdict->v.absmin.y || + maxs.z < pEdict->v.absmin.z ) + continue; + + pEntity = CBaseEntity::Instance(pEdict); + if ( !pEntity ) + continue; + + pList[ count ] = pEntity; + count++; + + if ( count >= listMax ) + return count; + } + + return count; +} + + +int UTIL_MonstersInSphere( CBaseEntity **pList, int listMax, const Vector ¢er, float radius ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + int count; + float distance, delta; + + count = 0; + float radiusSquared = radius * radius; + + if ( !pEdict ) + return count; + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + if ( pEdict->free ) // Not in use + continue; + + if ( !(pEdict->v.flags & (FL_CLIENT|FL_MONSTER)) ) // Not a client/monster ? + continue; + + // Use origin for X & Y since they are centered for all monsters + // Now X + delta = center.x - pEdict->v.origin.x;//(pEdict->v.absmin.x + pEdict->v.absmax.x)*0.5; + delta *= delta; + + if ( delta > radiusSquared ) + continue; + distance = delta; + + // Now Y + delta = center.y - pEdict->v.origin.y;//(pEdict->v.absmin.y + pEdict->v.absmax.y)*0.5; + delta *= delta; + + distance += delta; + if ( distance > radiusSquared ) + continue; + + // Now Z + delta = center.z - (pEdict->v.absmin.z + pEdict->v.absmax.z)*0.5; + delta *= delta; + + distance += delta; + if ( distance > radiusSquared ) + continue; + + pEntity = CBaseEntity::Instance(pEdict); + if ( !pEntity ) + continue; + + pList[ count ] = pEntity; + count++; + + if ( count >= listMax ) + return count; + } + + + return count; +} + + +CBaseEntity *UTIL_FindEntityInSphere( CBaseEntity *pStartEntity, const Vector &vecCenter, float flRadius ) +{ + edict_t *pentEntity; + + if (pStartEntity) + pentEntity = pStartEntity->edict(); + else + pentEntity = NULL; + + pentEntity = FIND_ENTITY_IN_SPHERE( pentEntity, vecCenter, flRadius); + + if (!FNullEnt(pentEntity)) + return CBaseEntity::Instance(pentEntity); + return NULL; +} + + +CBaseEntity *UTIL_FindEntityByString( CBaseEntity *pStartEntity, const char *szKeyword, const char *szValue ) +{ + edict_t *pentEntity; + + if (pStartEntity) + pentEntity = pStartEntity->edict(); + else + pentEntity = NULL; + + pentEntity = FIND_ENTITY_BY_STRING( pentEntity, szKeyword, szValue ); + + if (!FNullEnt(pentEntity)) + return CBaseEntity::Instance(pentEntity); + return NULL; +} + +CBaseEntity *UTIL_FindEntityByClassname( CBaseEntity *pStartEntity, const char *szName ) +{ + return UTIL_FindEntityByString( pStartEntity, "classname", szName ); +} + +CBaseEntity *UTIL_FindEntityByTargetname( CBaseEntity *pStartEntity, const char *szName ) +{ + return UTIL_FindEntityByString( pStartEntity, "targetname", szName ); +} + + +CBaseEntity *UTIL_FindEntityGeneric( const char *szWhatever, Vector &vecSrc, float flRadius ) +{ + CBaseEntity *pEntity = NULL; + + pEntity = UTIL_FindEntityByTargetname( NULL, szWhatever ); + if (pEntity) + return pEntity; + + CBaseEntity *pSearch = NULL; + float flMaxDist2 = flRadius * flRadius; + while ((pSearch = UTIL_FindEntityByClassname( pSearch, szWhatever )) != NULL) + { + float flDist2 = (pSearch->pev->origin - vecSrc).Length(); + flDist2 = flDist2 * flDist2; + if (flMaxDist2 > flDist2) + { + pEntity = pSearch; + flMaxDist2 = flDist2; + } + } + return pEntity; +} + + +// returns a CBaseEntity pointer to a player by index. Only returns if the player is spawned and connected +// otherwise returns NULL +// Index is 1 based +CBaseEntity *UTIL_PlayerByIndex( int playerIndex ) +{ + CBaseEntity *pPlayer = NULL; + + if ( playerIndex > 0 && playerIndex <= gpGlobals->maxClients ) + { + edict_t *pPlayerEdict = INDEXENT( playerIndex ); + if ( pPlayerEdict && !pPlayerEdict->free ) + { + pPlayer = CBaseEntity::Instance( pPlayerEdict ); + } + } + + return pPlayer; +} + + +void UTIL_MakeVectors( const Vector &vecAngles ) +{ + MAKE_VECTORS( vecAngles ); +} + + +void UTIL_MakeAimVectors( const Vector &vecAngles ) +{ + float rgflVec[3]; + vecAngles.CopyToArray(rgflVec); + rgflVec[0] = -rgflVec[0]; + MAKE_VECTORS(rgflVec); +} + + +#define SWAP(a,b,temp) ((temp)=(a),(a)=(b),(b)=(temp)) + +void UTIL_MakeInvVectors( const Vector &vec, globalvars_t *pgv ) +{ + MAKE_VECTORS(vec); + + float tmp; + pgv->v_right = pgv->v_right * -1; + + SWAP(pgv->v_forward.y, pgv->v_right.x, tmp); + SWAP(pgv->v_forward.z, pgv->v_up.x, tmp); + SWAP(pgv->v_right.z, pgv->v_up.y, tmp); +} + + +void UTIL_EmitAmbientSound( edict_t *entity, const Vector &vecOrigin, const char *samp, float vol, float attenuation, int fFlags, int pitch ) +{ + float rgfl[3]; + vecOrigin.CopyToArray(rgfl); + + if (samp && *samp == '!') + { + char name[32]; + if (SENTENCEG_Lookup(samp, name) >= 0) + EMIT_AMBIENT_SOUND(entity, rgfl, name, vol, attenuation, fFlags, pitch); + } + else + EMIT_AMBIENT_SOUND(entity, rgfl, samp, vol, attenuation, fFlags, pitch); +} + +static unsigned short FixedUnsigned16( float value, float scale ) +{ + int output; + + output = static_cast(value * scale); + if ( output < 0 ) + output = 0; + if ( output > 0xFFFF ) + output = 0xFFFF; + + return (unsigned short)output; +} + +static short FixedSigned16( float value, float scale ) +{ + int output; + + output = static_cast(value * scale); + + if ( output > 32767 ) + output = 32767; + + if ( output < -32768 ) + output = -32768; + + return (short)output; +} + +// Shake the screen of all clients within radius +// radius == 0, shake all clients +// UNDONE: Allow caller to shake clients not ONGROUND? +// UNDONE: Fix falloff model (disabled)? +// UNDONE: Affect user controls? +void UTIL_ScreenShake( const Vector ¢er, float amplitude, float frequency, float duration, float radius ) +{ + int i; + float localAmplitude; + ScreenShake shake; + + shake.duration = FixedUnsigned16( duration, 1<<12 ); // 4.12 fixed + shake.frequency = FixedUnsigned16( frequency, 1<<8 ); // 8.8 fixed + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + if ( !pPlayer || !(pPlayer->pev->flags & FL_ONGROUND) ) // Don't shake if not onground + continue; + + localAmplitude = 0; + + if ( radius <= 0 ) + localAmplitude = amplitude; + else + { + Vector delta = center - pPlayer->pev->origin; + float distance = delta.Length(); + + // Had to get rid of this falloff - it didn't work well + if ( distance < radius ) + localAmplitude = amplitude;//radius - distance; + } + if ( localAmplitude ) + { + shake.amplitude = FixedUnsigned16( localAmplitude, 1<<12 ); // 4.12 fixed + + MESSAGE_BEGIN( MSG_ONE, gmsgShake, NULL, pPlayer->edict() ); // use the magic #1 for "one client" + + WRITE_SHORT( shake.amplitude ); // shake amount + WRITE_SHORT( shake.duration ); // shake lasts this long + WRITE_SHORT( shake.frequency ); // shake noise frequency + + MESSAGE_END(); + } + } +} + + + +void UTIL_ScreenShakeAll( const Vector ¢er, float amplitude, float frequency, float duration ) +{ + UTIL_ScreenShake( center, amplitude, frequency, duration, 0 ); +} + + +void UTIL_ScreenFadeBuild( ScreenFade &fade, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) +{ + fade.duration = FixedUnsigned16( fadeTime, 1<<12 ); // 4.12 fixed + fade.holdTime = FixedUnsigned16( fadeHold, 1<<12 ); // 4.12 fixed + fade.r = (int)color.x; + fade.g = (int)color.y; + fade.b = (int)color.z; + fade.a = alpha; + fade.fadeFlags = flags; +} + + +void UTIL_ScreenFadeWrite( const ScreenFade &fade, CBaseEntity *pEntity ) +{ + if ( !pEntity || !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, gmsgFade, NULL, pEntity->edict() ); // use the magic #1 for "one client" + + WRITE_SHORT( fade.duration ); // fade lasts this long + WRITE_SHORT( fade.holdTime ); // fade lasts this long + WRITE_SHORT( fade.fadeFlags ); // fade type (in / out) + WRITE_BYTE( fade.r ); // fade red + WRITE_BYTE( fade.g ); // fade green + WRITE_BYTE( fade.b ); // fade blue + WRITE_BYTE( fade.a ); // fade blue + + MESSAGE_END(); +} + + +void UTIL_ScreenFadeAll( const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) +{ + int i; + ScreenFade fade; + + + UTIL_ScreenFadeBuild( fade, color, fadeTime, fadeHold, alpha, flags ); + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + UTIL_ScreenFadeWrite( fade, pPlayer ); + } +} + + +void UTIL_ScreenFade( CBaseEntity *pEntity, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) +{ + ScreenFade fade; + + UTIL_ScreenFadeBuild( fade, color, fadeTime, fadeHold, alpha, flags ); + UTIL_ScreenFadeWrite( fade, pEntity ); +} + + +void UTIL_HudMessage( CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage ) +{ + if ( !pEntity || !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, SVC_TEMPENTITY, NULL, pEntity->edict() ); + WRITE_BYTE( TE_TEXTMESSAGE ); + WRITE_BYTE( textparms.channel & 0xFF ); + + WRITE_SHORT( FixedSigned16( textparms.x, 1<<13 ) ); + WRITE_SHORT( FixedSigned16( textparms.y, 1<<13 ) ); + WRITE_BYTE( textparms.effect ); + + WRITE_BYTE( textparms.r1 ); + WRITE_BYTE( textparms.g1 ); + WRITE_BYTE( textparms.b1 ); + WRITE_BYTE( textparms.a1 ); + + WRITE_BYTE( textparms.r2 ); + WRITE_BYTE( textparms.g2 ); + WRITE_BYTE( textparms.b2 ); + WRITE_BYTE( textparms.a2 ); + + WRITE_SHORT( FixedUnsigned16( textparms.fadeinTime, 1<<8 ) ); + WRITE_SHORT( FixedUnsigned16( textparms.fadeoutTime, 1<<8 ) ); + WRITE_SHORT( FixedUnsigned16( textparms.holdTime, 1<<8 ) ); + + if ( textparms.effect == 2 ) + WRITE_SHORT( FixedUnsigned16( textparms.fxTime, 1<<8 ) ); + + if ( strlen( pMessage ) < 512 ) + { + WRITE_STRING( pMessage ); + } + else + { + char tmp[512]; + strncpy( tmp, pMessage, 511 ); + tmp[511] = 0; + WRITE_STRING( tmp ); + } + MESSAGE_END(); +} + +void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage ) +{ + int i; + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + UTIL_HudMessage( pPlayer, textparms, pMessage ); + } +} + + +extern int gmsgTextMsg, gmsgSayText; +void UTIL_ClientPrintAll( int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) +{ + MESSAGE_BEGIN( MSG_ALL, gmsgTextMsg ); + WRITE_BYTE( msg_dest ); + WRITE_STRING( msg_name ); + + if ( param1 ) + WRITE_STRING( param1 ); + if ( param2 ) + WRITE_STRING( param2 ); + if ( param3 ) + WRITE_STRING( param3 ); + if ( param4 ) + WRITE_STRING( param4 ); + + MESSAGE_END(); +} + +void ClientPrint( entvars_t *client, int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) +{ + MESSAGE_BEGIN( MSG_ONE, gmsgTextMsg, NULL, client ); + WRITE_BYTE( msg_dest ); + WRITE_STRING( msg_name ); + + if ( param1 ) + WRITE_STRING( param1 ); + if ( param2 ) + WRITE_STRING( param2 ); + if ( param3 ) + WRITE_STRING( param3 ); + if ( param4 ) + WRITE_STRING( param4 ); + + MESSAGE_END(); +} + +void UTIL_SayText( const char *pText, CBaseEntity *pEntity ) +{ + if ( !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, pEntity->edict() ); + WRITE_BYTE( pEntity->entindex() ); + WRITE_STRING( pText ); + MESSAGE_END(); +} + +void UTIL_SayTextAll( const char *pText, CBaseEntity *pEntity ) +{ + MESSAGE_BEGIN( MSG_ALL, gmsgSayText, NULL ); + WRITE_BYTE( pEntity->entindex() ); + WRITE_STRING( pText ); + MESSAGE_END(); +} + + +char *UTIL_dtos1( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +char *UTIL_dtos2( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +char *UTIL_dtos3( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +char *UTIL_dtos4( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +void UTIL_ShowMessage( const char *pString, CBaseEntity *pEntity ) +{ + if ( !pEntity || !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, gmsgHudText, NULL, pEntity->edict() ); + WRITE_STRING( pString ); + MESSAGE_END(); +} + + +void UTIL_ShowMessageAll( const char *pString ) +{ + int i; + + // loop through all players + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + UTIL_ShowMessage( pString, pPlayer ); + } +} + +// Overloaded to add IGNORE_GLASS +void UTIL_TraceLine( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, IGNORE_GLASS ignoreGlass, edict_t *pentIgnore, TraceResult *ptr ) +{ + TRACE_LINE( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE) | (ignoreGlass?0x100:0), pentIgnore, ptr ); +} + + +void UTIL_TraceLine( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, edict_t *pentIgnore, TraceResult *ptr ) +{ + TRACE_LINE( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE), pentIgnore, ptr ); +} + + +void UTIL_TraceHull( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, int hullNumber, edict_t *pentIgnore, TraceResult *ptr ) +{ + TRACE_HULL( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE), hullNumber, pentIgnore, ptr ); +} + +void UTIL_TraceModel( const Vector &vecStart, const Vector &vecEnd, int hullNumber, edict_t *pentModel, TraceResult *ptr ) +{ + g_engfuncs.pfnTraceModel( vecStart, vecEnd, hullNumber, pentModel, ptr ); +} + + +TraceResult UTIL_GetGlobalTrace( ) +{ + TraceResult tr; + + tr.fAllSolid = static_cast(gpGlobals->trace_allsolid); + tr.fStartSolid = static_cast(gpGlobals->trace_startsolid); + tr.fInOpen = static_cast(gpGlobals->trace_inopen); + tr.fInWater = static_cast(gpGlobals->trace_inwater); + tr.flFraction = gpGlobals->trace_fraction; + tr.flPlaneDist = gpGlobals->trace_plane_dist; + tr.pHit = gpGlobals->trace_ent; + tr.vecEndPos = gpGlobals->trace_endpos; + tr.vecPlaneNormal = gpGlobals->trace_plane_normal; + tr.iHitgroup = gpGlobals->trace_hitgroup; + return tr; +} + + +void UTIL_SetSize( entvars_t *pev, const Vector &vecMin, const Vector &vecMax ) +{ + SET_SIZE( ENT(pev), vecMin, vecMax ); +} + + +float UTIL_VecToYaw( const Vector &vec ) +{ + return VEC_TO_YAW(vec); +} + + +void UTIL_SetOrigin( entvars_t *pev, const Vector &vecOrigin ) +{ + SET_ORIGIN(ENT(pev), vecOrigin ); +} + +void UTIL_ParticleEffect( const Vector &vecOrigin, const Vector &vecDirection, ULONG ulColor, ULONG ulCount ) +{ + PARTICLE_EFFECT( vecOrigin, vecDirection, (float)ulColor, (float)ulCount ); +} + + +float UTIL_Approach( float target, float value, float speed ) +{ + float delta = target - value; + + if ( delta > speed ) + value += speed; + else if ( delta < -speed ) + value -= speed; + else + value = target; + + return value; +} + + +float UTIL_ApproachAngle( float target, float value, float speed ) +{ + target = UTIL_AngleMod( target ); + value = UTIL_AngleMod( target ); + + float delta = target - value; + + // Speed is assumed to be positive + if ( speed < 0 ) + speed = -speed; + + if ( delta < -180 ) + delta += 360; + else if ( delta > 180 ) + delta -= 360; + + if ( delta > speed ) + value += speed; + else if ( delta < -speed ) + value -= speed; + else + value = target; + + return value; +} + + +float UTIL_AngleDistance( float next, float cur ) +{ + float delta = next - cur; + + if ( delta < -180 ) + delta += 360; + else if ( delta > 180 ) + delta -= 360; + + return delta; +} + + +float UTIL_SplineFraction( float value, float scale ) +{ + value = scale * value; + float valueSquared = value * value; + + // Nice little ease-in, ease-out spline-like curve + return 3 * valueSquared - 2 * valueSquared * value; +} + + +char* UTIL_VarArgs( char *format, ... ) +{ + va_list argptr; + static char string[1024]; + + va_start (argptr, format); + vsprintf (string, format,argptr); + va_end (argptr); + + return string; +} + +Vector UTIL_GetAimVector( edict_t *pent, float flSpeed ) +{ + Vector tmp; + GET_AIM_VECTOR(pent, flSpeed, tmp); + return tmp; +} + +int UTIL_IsMasterTriggered(string_t sMaster, CBaseEntity *pActivator) +{ + if (sMaster) + { + edict_t *pentTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(sMaster)); + + if ( !FNullEnt(pentTarget) ) + { + CBaseEntity *pMaster = CBaseEntity::Instance(pentTarget); + if ( pMaster && (pMaster->ObjectCaps() & FCAP_MASTER) ) + return pMaster->IsTriggered( pActivator ); + } + + ALERT(at_console, "Master was null or not a master!\n"); + } + + // if this isn't a master entity, just say yes. + return 1; +} + +BOOL UTIL_ShouldShowBlood( int color ) +{ + if ( color != DONT_BLEED ) + { + if ( color == BLOOD_COLOR_RED ) + { + if ( CVAR_GET_FLOAT("violence_hblood") != 0 ) + return TRUE; + } + else + { + if ( CVAR_GET_FLOAT("violence_ablood") != 0 ) + return TRUE; + } + } + return FALSE; +} + +int UTIL_PointContents( const Vector &vec ) +{ + return POINT_CONTENTS(vec); +} + +void UTIL_BloodStream( const Vector &origin, const Vector &direction, int color, int amount ) +{ + if ( !UTIL_ShouldShowBlood( color ) ) + return; + + if ( g_Language == LANGUAGE_GERMAN && color == BLOOD_COLOR_RED ) + color = 0; + + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, origin ); + WRITE_BYTE( TE_BLOODSTREAM ); + WRITE_COORD( origin.x ); + WRITE_COORD( origin.y ); + WRITE_COORD( origin.z ); + WRITE_COORD( direction.x ); + WRITE_COORD( direction.y ); + WRITE_COORD( direction.z ); + WRITE_BYTE( color ); + WRITE_BYTE( min( amount, 255 ) ); + MESSAGE_END(); +} + +void UTIL_BloodDrips( const Vector &origin, const Vector &direction, int color, int amount ) +{ + if ( !UTIL_ShouldShowBlood( color ) ) + return; + + if ( color == DONT_BLEED || amount == 0 ) + return; + + if ( g_Language == LANGUAGE_GERMAN && color == BLOOD_COLOR_RED ) + color = 0; + + if ( g_pGameRules->IsMultiplayer() ) + { + // scale up blood effect in multiplayer for better visibility + amount *= 2; + } + + if ( amount > 255 ) + amount = 255; + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, origin ); + WRITE_BYTE( TE_BLOODSPRITE ); + WRITE_COORD( origin.x); // pos + WRITE_COORD( origin.y); + WRITE_COORD( origin.z); + WRITE_SHORT( g_sModelIndexBloodSpray ); // initial sprite model + 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(); +} + +Vector UTIL_RandomBloodVector( void ) +{ + Vector direction; + + direction.x = RANDOM_FLOAT ( -1, 1 ); + direction.y = RANDOM_FLOAT ( -1, 1 ); + direction.z = RANDOM_FLOAT ( 0, 1 ); + + return direction; +} + + +void UTIL_BloodDecalTrace( TraceResult *pTrace, int bloodColor ) +{ + if ( UTIL_ShouldShowBlood( bloodColor ) ) + { + if ( bloodColor == BLOOD_COLOR_RED ) + UTIL_DecalTrace( pTrace, DECAL_BLOOD1 + RANDOM_LONG(0,5) ); + else + UTIL_DecalTrace( pTrace, DECAL_YBLOOD1 + RANDOM_LONG(0,5) ); + } +} + + +void UTIL_DecalTrace( TraceResult *pTrace, int decalNumber ) +{ + short entityIndex; + int index; + int message; + + if ( decalNumber < 0 ) + return; + + index = gDecals[ decalNumber ].index; + + if ( index < 0 ) + return; + + if (pTrace->flFraction == 1.0) + return; + + // Only decal BSP models + if ( pTrace->pHit ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( pTrace->pHit ); + if ( pEntity && !pEntity->IsBSPModel() ) + return; + entityIndex = ENTINDEX( pTrace->pHit ); + } + else + entityIndex = 0; + + message = TE_DECAL; + if ( entityIndex != 0 ) + { + if ( index > 255 ) + { + message = TE_DECALHIGH; + index -= 256; + } + } + else + { + message = TE_WORLDDECAL; + if ( index > 255 ) + { + message = TE_WORLDDECALHIGH; + index -= 256; + } + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( message ); + WRITE_COORD( pTrace->vecEndPos.x ); + WRITE_COORD( pTrace->vecEndPos.y ); + WRITE_COORD( pTrace->vecEndPos.z ); + WRITE_BYTE( index ); + if ( entityIndex ) + WRITE_SHORT( entityIndex ); + MESSAGE_END(); +} + +/* +============== +UTIL_PlayerDecalTrace + +A player is trying to apply his custom decal for the spray can. +Tell connected clients to display it, or use the default spray can decal +if the custom can't be loaded. +============== +*/ +void UTIL_PlayerDecalTrace( TraceResult *pTrace, int playernum, int decalNumber, BOOL bIsCustom ) +{ + int index; + + if (!bIsCustom) + { + if ( decalNumber < 0 ) + return; + + index = gDecals[ decalNumber ].index; + if ( index < 0 ) + return; + } + else + index = decalNumber; + + if (pTrace->flFraction == 1.0) + return; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_PLAYERDECAL ); + WRITE_BYTE ( playernum ); + WRITE_COORD( pTrace->vecEndPos.x ); + WRITE_COORD( pTrace->vecEndPos.y ); + WRITE_COORD( pTrace->vecEndPos.z ); + WRITE_SHORT( (short)ENTINDEX(pTrace->pHit) ); + WRITE_BYTE( index ); + MESSAGE_END(); +} + +void UTIL_GunshotDecalTrace( TraceResult *pTrace, int decalNumber ) +{ + if ( decalNumber < 0 ) + return; + + int index = gDecals[ decalNumber ].index; + if ( index < 0 ) + return; + + if (pTrace->flFraction == 1.0) + return; + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pTrace->vecEndPos ); + WRITE_BYTE( TE_GUNSHOTDECAL ); + WRITE_COORD( pTrace->vecEndPos.x ); + WRITE_COORD( pTrace->vecEndPos.y ); + WRITE_COORD( pTrace->vecEndPos.z ); + WRITE_SHORT( (short)ENTINDEX(pTrace->pHit) ); + WRITE_BYTE( index ); + MESSAGE_END(); +} + + +void UTIL_Sparks( const Vector &position, edict_t * ed ) +{ +// MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, position ); +// WRITE_BYTE( TE_SPARKS ); +// WRITE_COORD( position.x ); +// WRITE_COORD( position.y ); +// WRITE_COORD( position.z ); +// MESSAGE_END(); + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer * pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if ( + pPlayer && + ( pPlayer->m_bHasDisconnected != TRUE ) && + ( !ed || ( pPlayer->pev->groupinfo & VARS(ed)->groupinfo ) ) + ) + { + MESSAGE_BEGIN( MSG_ONE, SVC_TEMPENTITY, position, pPlayer->edict() ); + WRITE_BYTE( TE_SPARKS ); + WRITE_COORD( position.x ); + WRITE_COORD( position.y ); + WRITE_COORD( position.z ); + MESSAGE_END(); + } + } +} + + +void UTIL_Ricochet( const Vector &position, float scale ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, position ); + WRITE_BYTE( TE_ARMOR_RICOCHET ); + WRITE_COORD( position.x ); + WRITE_COORD( position.y ); + WRITE_COORD( position.z ); + WRITE_BYTE( (int)(scale*10) ); + MESSAGE_END(); +} + + +BOOL UTIL_TeamsMatch( const char *pTeamName1, const char *pTeamName2 ) +{ + // Everyone matches unless it's teamplay + if ( !g_pGameRules->IsTeamplay() ) + return TRUE; + + // Both on a team? + if ( *pTeamName1 != 0 && *pTeamName2 != 0 ) + { + if ( !stricmp( pTeamName1, pTeamName2 ) ) // Same Team? + return TRUE; + } + + return FALSE; +} + + +void UTIL_StringToVector( float *pVector, const char *pString ) +{ + char *pstr, *pfront, tempString[128]; + int j; + + strcpy( tempString, pString ); + pstr = pfront = tempString; + + for ( j = 0; j < 3; j++ ) // lifted from pr_edict.c + { + pVector[j] = atof( pfront ); + + while ( *pstr && *pstr != ' ' ) + pstr++; + if (!*pstr) + break; + pstr++; + pfront = pstr; + } + if (j < 2) + { + /* + ALERT( at_error, "Bad field in entity!! %s:%s == \"%s\"\n", + pkvd->szClassName, pkvd->szKeyName, pkvd->szValue ); + */ + for (j = j+1;j < 3; j++) + pVector[j] = 0; + } +} + + +void UTIL_StringToIntArray( int *pVector, int count, const char *pString ) +{ + char *pstr, *pfront, tempString[128]; + int j; + + strcpy( tempString, pString ); + pstr = pfront = tempString; + + for ( j = 0; j < count; j++ ) // lifted from pr_edict.c + { + pVector[j] = atoi( pfront ); + + while ( *pstr && *pstr != ' ' ) + pstr++; + if (!*pstr) + break; + pstr++; + pfront = pstr; + } + + for ( j++; j < count; j++ ) + { + pVector[j] = 0; + } +} + +Vector UTIL_ClampVectorToBox( const Vector &input, const Vector &clampSize ) +{ + Vector sourceVector = input; + + if ( sourceVector.x > clampSize.x ) + sourceVector.x -= clampSize.x; + else if ( sourceVector.x < -clampSize.x ) + sourceVector.x += clampSize.x; + else + sourceVector.x = 0; + + if ( sourceVector.y > clampSize.y ) + sourceVector.y -= clampSize.y; + else if ( sourceVector.y < -clampSize.y ) + sourceVector.y += clampSize.y; + else + sourceVector.y = 0; + + if ( sourceVector.z > clampSize.z ) + sourceVector.z -= clampSize.z; + else if ( sourceVector.z < -clampSize.z ) + sourceVector.z += clampSize.z; + else + sourceVector.z = 0; + + return sourceVector.Normalize(); +} + + +float UTIL_WaterLevel( const Vector &position, float minz, float maxz ) +{ + Vector midUp = position; + midUp.z = minz; + + if (UTIL_PointContents(midUp) != CONTENTS_WATER) + return minz; + + midUp.z = maxz; + if (UTIL_PointContents(midUp) == CONTENTS_WATER) + return maxz; + + float diff = maxz - minz; + while (diff > 1.0) + { + midUp.z = minz + diff/2.0; + if (UTIL_PointContents(midUp) == CONTENTS_WATER) + { + minz = midUp.z; + } + else + { + maxz = midUp.z; + } + diff = maxz - minz; + } + + return midUp.z; +} + + +extern DLL_GLOBAL short g_sModelIndexBubbles;// holds the index for the bubbles model + +void UTIL_Bubbles( Vector mins, Vector maxs, int count ) +{ + Vector mid = (mins + maxs) * 0.5; + + float flHeight = UTIL_WaterLevel( mid, mid.z, mid.z + 1024 ); + flHeight = flHeight - mins.z; + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, mid ); + WRITE_BYTE( TE_BUBBLES ); + WRITE_COORD( mins.x ); // mins + WRITE_COORD( mins.y ); + WRITE_COORD( mins.z ); + WRITE_COORD( maxs.x ); // maxz + WRITE_COORD( maxs.y ); + WRITE_COORD( maxs.z ); + WRITE_COORD( flHeight ); // height + WRITE_SHORT( g_sModelIndexBubbles ); + WRITE_BYTE( count ); // count + WRITE_COORD( 8 ); // speed + MESSAGE_END(); +} + +void UTIL_BubbleTrail( Vector from, Vector to, int count ) +{ + float flHeight = UTIL_WaterLevel( from, from.z, from.z + 256 ); + flHeight = flHeight - from.z; + + if (flHeight < 8) + { + flHeight = UTIL_WaterLevel( to, to.z, to.z + 256 ); + flHeight = flHeight - to.z; + if (flHeight < 8) + return; + + // UNDONE: do a ploink sound + flHeight = flHeight + to.z - from.z; + } + + if (count > 255) + count = 255; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BUBBLETRAIL ); + WRITE_COORD( from.x ); // mins + WRITE_COORD( from.y ); + WRITE_COORD( from.z ); + WRITE_COORD( to.x ); // maxz + WRITE_COORD( to.y ); + WRITE_COORD( to.z ); + WRITE_COORD( flHeight ); // height + WRITE_SHORT( g_sModelIndexBubbles ); + WRITE_BYTE( count ); // count + WRITE_COORD( 8 ); // speed + MESSAGE_END(); +} + + +void UTIL_Remove( CBaseEntity *pEntity ) +{ + if ( !pEntity ) + return; + + pEntity->UpdateOnRemove(); + pEntity->pev->flags |= FL_KILLME; + pEntity->pev->targetname = 0; +} + + +BOOL UTIL_IsValidEntity( edict_t *pent ) +{ + if ( !pent || pent->free || (pent->v.flags & FL_KILLME) ) + return FALSE; + return TRUE; +} + + +void UTIL_PrecacheOther( const char *szClassname ) +{ + edict_t *pent; + + pent = CREATE_NAMED_ENTITY( MAKE_STRING( szClassname ) ); + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in UTIL_PrecacheOther\n" ); + return; + } + + CBaseEntity *pEntity = CBaseEntity::Instance (VARS( pent )); + if (pEntity) + pEntity->Precache( ); + REMOVE_ENTITY(pent); +} + +//========================================================= +// UTIL_LogPrintf - Prints a logged message to console. +// Preceded by LOG: ( timestamp ) < message > +//========================================================= +void UTIL_LogPrintf( char *fmt, ... ) +{ + va_list argptr; + static char string[1024]; + + va_start ( argptr, fmt ); + vsprintf ( string, fmt, argptr ); + va_end ( argptr ); + + // Print to server console + ALERT( at_logged, "%s", string ); +} + +//========================================================= +// UTIL_DotPoints - returns the dot product of a line from +// src to check and vecdir. +//========================================================= +float UTIL_DotPoints ( const Vector &vecSrc, const Vector &vecCheck, const Vector &vecDir ) +{ + Vector2D vec2LOS; + + vec2LOS = ( vecCheck - vecSrc ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + return DotProduct (vec2LOS , ( vecDir.Make2D() ) ); +} + + +//========================================================= +// UTIL_StripToken - for redundant keynames +//========================================================= +void UTIL_StripToken( const char *pKey, char *pDest ) +{ + int i = 0; + + while ( pKey[i] && pKey[i] != '#' ) + { + pDest[i] = pKey[i]; + i++; + } + pDest[i] = 0; +} + + +// -------------------------------------------------------------- +// +// CSave +// +// -------------------------------------------------------------- +static int gSizes[FIELD_TYPECOUNT] = +{ + sizeof(float), // FIELD_FLOAT + sizeof(int), // FIELD_STRING + sizeof(int), // FIELD_ENTITY + sizeof(int), // FIELD_CLASSPTR + sizeof(int), // FIELD_EHANDLE + sizeof(int), // FIELD_entvars_t + sizeof(int), // FIELD_EDICT + sizeof(float)*3, // FIELD_VECTOR + sizeof(float)*3, // FIELD_POSITION_VECTOR + sizeof(int *), // FIELD_POINTER + sizeof(int), // FIELD_INTEGER + sizeof(int *), // FIELD_FUNCTION + sizeof(int), // FIELD_BOOLEAN + sizeof(short), // FIELD_SHORT + sizeof(char), // FIELD_CHARACTER + sizeof(float), // FIELD_TIME + sizeof(int), // FIELD_MODELNAME + sizeof(int), // FIELD_SOUNDNAME +}; + + +// Base class includes common SAVERESTOREDATA pointer, and manages the entity table +CSaveRestoreBuffer :: CSaveRestoreBuffer( void ) +{ + m_pdata = NULL; +} + + +CSaveRestoreBuffer :: CSaveRestoreBuffer( SAVERESTOREDATA *pdata ) +{ + m_pdata = pdata; +} + + +CSaveRestoreBuffer :: ~CSaveRestoreBuffer( void ) +{ +} + +int CSaveRestoreBuffer :: EntityIndex( CBaseEntity *pEntity ) +{ + if ( pEntity == NULL ) + return -1; + return EntityIndex( pEntity->pev ); +} + + +int CSaveRestoreBuffer :: EntityIndex( entvars_t *pevLookup ) +{ + if ( pevLookup == NULL ) + return -1; + return EntityIndex( ENT( pevLookup ) ); +} + +int CSaveRestoreBuffer :: EntityIndex( EOFFSET eoLookup ) +{ + return EntityIndex( ENT( eoLookup ) ); +} + + +int CSaveRestoreBuffer :: EntityIndex( edict_t *pentLookup ) +{ + if ( !m_pdata || pentLookup == NULL ) + return -1; + + int i; + ENTITYTABLE *pTable; + + for ( i = 0; i < m_pdata->tableCount; i++ ) + { + pTable = m_pdata->pTable + i; + if ( pTable->pent == pentLookup ) + return i; + } + return -1; +} + + +edict_t *CSaveRestoreBuffer :: EntityFromIndex( int entityIndex ) +{ + if ( !m_pdata || entityIndex < 0 ) + return NULL; + + int i; + ENTITYTABLE *pTable; + + for ( i = 0; i < m_pdata->tableCount; i++ ) + { + pTable = m_pdata->pTable + i; + if ( pTable->id == entityIndex ) + return pTable->pent; + } + return NULL; +} + + +int CSaveRestoreBuffer :: EntityFlagsSet( int entityIndex, int flags ) +{ + if ( !m_pdata || entityIndex < 0 ) + return 0; + if ( entityIndex > m_pdata->tableCount ) + return 0; + + m_pdata->pTable[ entityIndex ].flags |= flags; + + return m_pdata->pTable[ entityIndex ].flags; +} + + +void CSaveRestoreBuffer :: BufferRewind( int size ) +{ + if ( !m_pdata ) + return; + + if ( m_pdata->size < size ) + size = m_pdata->size; + + m_pdata->pCurrentData -= size; + m_pdata->size -= size; +} + +#ifndef _WIN32 +extern "C" { +unsigned _rotr ( unsigned val, int shift) +{ + register unsigned lobit; /* non-zero means lo bit set */ + register unsigned num = val; /* number to rotate */ + + shift &= 0x1f; /* modulo 32 -- this will also make + negative shifts work */ + + while (shift--) { + lobit = num & 1; /* get high bit */ + num >>= 1; /* shift right one bit */ + if (lobit) + num |= 0x80000000; /* set hi bit if lo bit was set */ + } + + return num; +} +} +#endif + +unsigned int CSaveRestoreBuffer :: HashString( const char *pszToken ) +{ + unsigned int hash = 0; + + while ( *pszToken ) + hash = _rotr( hash, 4 ) ^ *pszToken++; + + return hash; +} + +unsigned short CSaveRestoreBuffer :: TokenHash( const char *pszToken ) +{ + unsigned short hash = (unsigned short)(HashString( pszToken ) % (unsigned)m_pdata->tokenCount ); + +#if _DEBUG + static int tokensparsed = 0; + tokensparsed++; + if ( !m_pdata->tokenCount || !m_pdata->pTokens ) + ALERT( at_error, "No token table array in TokenHash()!" ); +#endif + + for ( int i=0; itokenCount; i++ ) + { +#if _DEBUG + static qboolean beentheredonethat = FALSE; + if ( i > 50 && !beentheredonethat ) + { + beentheredonethat = TRUE; + ALERT( at_error, "CSaveRestoreBuffer :: TokenHash() is getting too full!" ); + } +#endif + + int index = hash + i; + if ( index >= m_pdata->tokenCount ) + index -= m_pdata->tokenCount; + + if ( !m_pdata->pTokens[index] || strcmp( pszToken, m_pdata->pTokens[index] ) == 0 ) + { + m_pdata->pTokens[index] = (char *)pszToken; + return index; + } + } + + // Token hash table full!!! + // [Consider doing overflow table(s) after the main table & limiting linear hash table search] + ALERT( at_error, "CSaveRestoreBuffer :: TokenHash() is COMPLETELY FULL!" ); + return 0; +} + +void CSave :: WriteData( const char *pname, int size, const char *pdata ) +{ + BufferField( pname, size, pdata ); +} + + +void CSave :: WriteShort( const char *pname, const short *data, int count ) +{ + BufferField( pname, sizeof(short) * count, (const char *)data ); +} + + +void CSave :: WriteInt( const char *pname, const int *data, int count ) +{ + BufferField( pname, sizeof(int) * count, (const char *)data ); +} + + +void CSave :: WriteFloat( const char *pname, const float *data, int count ) +{ + BufferField( pname, sizeof(float) * count, (const char *)data ); +} + + +void CSave :: WriteTime( const char *pname, const float *data, int count ) +{ + int i; + Vector tmp, input; + + BufferHeader( pname, sizeof(float) * count ); + for ( i = 0; i < count; i++ ) + { + float tmp = data[0]; + + // Always encode time as a delta from the current time so it can be re-based if loaded in a new level + // Times of 0 are never written to the file, so they will be restored as 0, not a relative time + if ( m_pdata ) + tmp -= m_pdata->time; + + BufferData( (const char *)&tmp, sizeof(float) ); + data ++; + } +} + + +void CSave :: WriteString( const char *pname, const char *pdata ) +{ +#ifdef TOKENIZE + short token = (short)TokenHash( pdata ); + WriteShort( pname, &token, 1 ); +#else + BufferField( pname, strlen(pdata) + 1, pdata ); +#endif +} + + +void CSave :: WriteString( const char *pname, const int *stringId, int count ) +{ + int i, size; + +#ifdef TOKENIZE + short token = (short)TokenHash( STRING( *stringId ) ); + WriteShort( pname, &token, 1 ); +#else +#if 0 + if ( count != 1 ) + ALERT( at_error, "No string arrays!\n" ); + WriteString( pname, (char *)STRING(*stringId) ); +#endif + + size = 0; + for ( i = 0; i < count; i++ ) + size += strlen( STRING( stringId[i] ) ) + 1; + + BufferHeader( pname, size ); + for ( i = 0; i < count; i++ ) + { + const char *pString = STRING(stringId[i]); + BufferData( pString, strlen(pString)+1 ); + } +#endif +} + + +void CSave :: WriteVector( const char *pname, const Vector &value ) +{ + WriteVector( pname, &value.x, 1 ); +} + + +void CSave :: WriteVector( const char *pname, const float *value, int count ) +{ + BufferHeader( pname, sizeof(float) * 3 * count ); + BufferData( (const char *)value, sizeof(float) * 3 * count ); +} + + + +void CSave :: WritePositionVector( const char *pname, const Vector &value ) +{ + + if ( m_pdata && m_pdata->fUseLandmark ) + { + Vector tmp = value - m_pdata->vecLandmarkOffset; + WriteVector( pname, tmp ); + } + + WriteVector( pname, value ); +} + + +void CSave :: WritePositionVector( const char *pname, const float *value, int count ) +{ + int i; + Vector tmp, input; + + BufferHeader( pname, sizeof(float) * 3 * count ); + for ( i = 0; i < count; i++ ) + { + Vector tmp( value[0], value[1], value[2] ); + + if ( m_pdata && m_pdata->fUseLandmark ) + tmp = tmp - m_pdata->vecLandmarkOffset; + + BufferData( (const char *)&tmp.x, sizeof(float) * 3 ); + value += 3; + } +} + + +void CSave :: WriteFunction( const char *pname, const int *data, int count ) +{ + const char *functionName; + + functionName = NAME_FOR_FUNCTION( *data ); + if ( functionName ) + BufferField( pname, strlen(functionName) + 1, functionName ); + else + ALERT( at_error, "Invalid function pointer in entity!" ); +} + + +void EntvarsKeyvalue( entvars_t *pev, KeyValueData *pkvd ) +{ + int i; + TYPEDESCRIPTION *pField; + + for ( i = 0; i < static_cast(ENTVARS_COUNT); i++ ) + { + pField = &gEntvarsDescription[i]; + + if ( !stricmp( pField->fieldName, pkvd->szKeyName ) ) + { + switch( pField->fieldType ) + { + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + case FIELD_STRING: + (*(int *)((char *)pev + pField->fieldOffset)) = ALLOC_STRING( pkvd->szValue ); + break; + + case FIELD_TIME: + case FIELD_FLOAT: + (*(float *)((char *)pev + pField->fieldOffset)) = atof( pkvd->szValue ); + break; + + case FIELD_INTEGER: + (*(int *)((char *)pev + pField->fieldOffset)) = atoi( pkvd->szValue ); + break; + + case FIELD_POSITION_VECTOR: + case FIELD_VECTOR: + UTIL_StringToVector( (float *)((char *)pev + pField->fieldOffset), pkvd->szValue ); + break; + + default: + case FIELD_EVARS: + case FIELD_CLASSPTR: + case FIELD_EDICT: + case FIELD_ENTITY: + case FIELD_POINTER: + ALERT( at_error, "Bad field in entity!!\n" ); + break; + } + pkvd->fHandled = TRUE; + return; + } + } +} + + + +int CSave :: WriteEntVars( const char *pname, entvars_t *pev ) +{ + return WriteFields( pname, pev, gEntvarsDescription, ENTVARS_COUNT ); +} + + + +int CSave :: WriteFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + int i, j, actualCount, emptyCount; + TYPEDESCRIPTION *pTest; + int entityArray[MAX_ENTITYARRAY]; + + // Precalculate the number of empty fields + emptyCount = 0; + for ( i = 0; i < fieldCount; i++ ) + { + void *pOutputData; + pOutputData = ((char *)pBaseData + pFields[i].fieldOffset ); + if ( DataEmpty( (const char *)pOutputData, pFields[i].fieldSize * gSizes[pFields[i].fieldType] ) ) + emptyCount++; + } + + // Empty fields will not be written, write out the actual number of fields to be written + actualCount = fieldCount - emptyCount; + WriteInt( pname, &actualCount, 1 ); + + for ( i = 0; i < fieldCount; i++ ) + { + void *pOutputData; + pTest = &pFields[ i ]; + pOutputData = ((char *)pBaseData + pTest->fieldOffset ); + + // UNDONE: Must we do this twice? + if ( DataEmpty( (const char *)pOutputData, pTest->fieldSize * gSizes[pTest->fieldType] ) ) + continue; + + switch( pTest->fieldType ) + { + case FIELD_FLOAT: + WriteFloat( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + case FIELD_TIME: + WriteTime( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + case FIELD_STRING: + WriteString( pTest->fieldName, (int *)pOutputData, pTest->fieldSize ); + break; + case FIELD_CLASSPTR: + case FIELD_EVARS: + case FIELD_EDICT: + case FIELD_ENTITY: + case FIELD_EHANDLE: + if ( pTest->fieldSize > MAX_ENTITYARRAY ) + ALERT( at_error, "Can't save more than %d entities in an array!!!\n", MAX_ENTITYARRAY ); + for ( j = 0; j < pTest->fieldSize; j++ ) + { + switch( pTest->fieldType ) + { + case FIELD_EVARS: + entityArray[j] = EntityIndex( ((entvars_t **)pOutputData)[j] ); + break; + case FIELD_CLASSPTR: + entityArray[j] = EntityIndex( ((CBaseEntity **)pOutputData)[j] ); + break; + case FIELD_EDICT: + entityArray[j] = EntityIndex( ((edict_t **)pOutputData)[j] ); + break; + case FIELD_ENTITY: + entityArray[j] = EntityIndex( ((EOFFSET *)pOutputData)[j] ); + break; + case FIELD_EHANDLE: + entityArray[j] = EntityIndex( (CBaseEntity *)(((EHANDLE *)pOutputData)[j]) ); + break; + default: + break; + } + } + WriteInt( pTest->fieldName, entityArray, pTest->fieldSize ); + break; + case FIELD_POSITION_VECTOR: + WritePositionVector( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + case FIELD_VECTOR: + WriteVector( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + + case FIELD_BOOLEAN: + case FIELD_INTEGER: + WriteInt( pTest->fieldName, (int *)pOutputData, pTest->fieldSize ); + break; + + case FIELD_SHORT: + WriteData( pTest->fieldName, 2 * pTest->fieldSize, ((char *)pOutputData) ); + break; + + case FIELD_CHARACTER: + WriteData( pTest->fieldName, pTest->fieldSize, ((char *)pOutputData) ); + break; + + // For now, just write the address out, we're not going to change memory while doing this yet! + case FIELD_POINTER: + WriteInt( pTest->fieldName, (int *)(char *)pOutputData, pTest->fieldSize ); + break; + + case FIELD_FUNCTION: + WriteFunction( pTest->fieldName, (int *)(char *)pOutputData, pTest->fieldSize ); + break; + default: + ALERT( at_error, "Bad field type\n" ); + } + } + + return 1; +} + + +void CSave :: BufferString( char *pdata, int len ) +{ + char c = 0; + + BufferData( pdata, len ); // Write the string + BufferData( &c, 1 ); // Write a null terminator +} + + +int CSave :: DataEmpty( const char *pdata, int size ) +{ + for ( int i = 0; i < size; i++ ) + { + if ( pdata[i] ) + return 0; + } + return 1; +} + + +void CSave :: BufferField( const char *pname, int size, const char *pdata ) +{ + BufferHeader( pname, size ); + BufferData( pdata, size ); +} + + +void CSave :: BufferHeader( const char *pname, int size ) +{ + short hashvalue = TokenHash( pname ); + if ( size > 1<<(sizeof(short)*8) ) + ALERT( at_error, "CSave :: BufferHeader() size parameter exceeds 'short'!" ); + BufferData( (const char *)&size, sizeof(short) ); + BufferData( (const char *)&hashvalue, sizeof(short) ); +} + + +void CSave :: BufferData( const char *pdata, int size ) +{ + if ( !m_pdata ) + return; + + if ( m_pdata->size + size > m_pdata->bufferSize ) + { + ALERT( at_error, "Save/Restore overflow!" ); + m_pdata->size = m_pdata->bufferSize; + return; + } + + memcpy( m_pdata->pCurrentData, pdata, size ); + m_pdata->pCurrentData += size; + m_pdata->size += size; +} + + + +// -------------------------------------------------------------- +// +// CRestore +// +// -------------------------------------------------------------- + +int CRestore::ReadField( void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount, int startField, int size, char *pName, void *pData ) +{ + int i, j, stringCount, fieldNumber, entityIndex; + TYPEDESCRIPTION *pTest; + float time, timeData; + Vector position; + edict_t *pent; + char *pString; + + time = 0; + position = Vector(0,0,0); + + if ( m_pdata ) + { + time = m_pdata->time; + if ( m_pdata->fUseLandmark ) + position = m_pdata->vecLandmarkOffset; + } + + for ( i = 0; i < fieldCount; i++ ) + { + fieldNumber = (i+startField)%fieldCount; + pTest = &pFields[ fieldNumber ]; + if ( !stricmp( pTest->fieldName, pName ) ) + { + if ( !m_global || !(pTest->flags & FTYPEDESC_GLOBAL) ) + { + for ( j = 0; j < pTest->fieldSize; j++ ) + { + void *pOutputData = ((char *)pBaseData + pTest->fieldOffset + (j*gSizes[pTest->fieldType]) ); + void *pInputData = (char *)pData + j * gSizes[pTest->fieldType]; + + switch( pTest->fieldType ) + { + case FIELD_TIME: + timeData = *(float *)pInputData; + // Re-base time variables + timeData += time; + *((float *)pOutputData) = timeData; + break; + case FIELD_FLOAT: + *((float *)pOutputData) = *(float *)pInputData; + break; + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + case FIELD_STRING: + // Skip over j strings + pString = (char *)pData; + for ( stringCount = 0; stringCount < j; stringCount++ ) + { + while (*pString) + pString++; + pString++; + } + pInputData = pString; + if ( strlen( (char *)pInputData ) == 0 ) + *((int *)pOutputData) = 0; + else + { + int string; + + string = ALLOC_STRING( (char *)pInputData ); + + *((int *)pOutputData) = string; + + if ( !FStringNull( string ) && m_precache ) + { + if ( pTest->fieldType == FIELD_MODELNAME ) + PRECACHE_MODEL( (char *)STRING( string ) ); + else if ( pTest->fieldType == FIELD_SOUNDNAME ) + PRECACHE_SOUND( (char *)STRING( string ) ); + } + } + break; + case FIELD_EVARS: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((entvars_t **)pOutputData) = VARS(pent); + else + *((entvars_t **)pOutputData) = NULL; + break; + case FIELD_CLASSPTR: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((CBaseEntity **)pOutputData) = CBaseEntity::Instance(pent); + else + *((CBaseEntity **)pOutputData) = NULL; + break; + case FIELD_EDICT: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + *((edict_t **)pOutputData) = pent; + break; + case FIELD_EHANDLE: + // Input and Output sizes are different! + pOutputData = (char *)pOutputData + j*(sizeof(EHANDLE) - gSizes[pTest->fieldType]); + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((EHANDLE *)pOutputData) = CBaseEntity::Instance(pent); + else + *((EHANDLE *)pOutputData) = NULL; + break; + case FIELD_ENTITY: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((EOFFSET *)pOutputData) = OFFSET(pent); + else + *((EOFFSET *)pOutputData) = 0; + break; + case FIELD_VECTOR: + ((float *)pOutputData)[0] = ((float *)pInputData)[0]; + ((float *)pOutputData)[1] = ((float *)pInputData)[1]; + ((float *)pOutputData)[2] = ((float *)pInputData)[2]; + break; + case FIELD_POSITION_VECTOR: + ((float *)pOutputData)[0] = ((float *)pInputData)[0] + position.x; + ((float *)pOutputData)[1] = ((float *)pInputData)[1] + position.y; + ((float *)pOutputData)[2] = ((float *)pInputData)[2] + position.z; + break; + + case FIELD_BOOLEAN: + case FIELD_INTEGER: + *((int *)pOutputData) = *( int *)pInputData; + break; + + case FIELD_SHORT: + *((short *)pOutputData) = *( short *)pInputData; + break; + + case FIELD_CHARACTER: + *((char *)pOutputData) = *( char *)pInputData; + break; + + case FIELD_POINTER: + *((int *)pOutputData) = *( int *)pInputData; + break; + case FIELD_FUNCTION: + if ( strlen( (char *)pInputData ) == 0 ) + *((int *)pOutputData) = 0; + else + *((int *)pOutputData) = FUNCTION_FROM_NAME( (char *)pInputData ); + break; + + default: + ALERT( at_error, "Bad field type\n" ); + } + } + } +#if 0 + else + { + ALERT( at_console, "Skipping global field %s\n", pName ); + } +#endif + return fieldNumber; + } + } + + return -1; +} + + +int CRestore::ReadEntVars( const char *pname, entvars_t *pev ) +{ + return ReadFields( pname, pev, gEntvarsDescription, ENTVARS_COUNT ); +} + + +int CRestore::ReadFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + unsigned short i, token; + int lastField, fileCount; + HEADER header; + + i = ReadShort(); + ASSERT( i == sizeof(int) ); // First entry should be an int + + token = ReadShort(); + + // Check the struct name + if ( token != TokenHash(pname) ) // Field Set marker + { +// ALERT( at_error, "Expected %s found %s!\n", pname, BufferPointer() ); + BufferRewind( 2*sizeof(short) ); + return 0; + } + + // Skip over the struct name + fileCount = ReadInt(); // Read field count + + lastField = 0; // Make searches faster, most data is read/written in the same order + + // Clear out base data + for ( i = 0; i < fieldCount; i++ ) + { + // Don't clear global fields + if ( !m_global || !(pFields[i].flags & FTYPEDESC_GLOBAL) ) + memset( ((char *)pBaseData + pFields[i].fieldOffset), 0, pFields[i].fieldSize * gSizes[pFields[i].fieldType] ); + } + + for ( i = 0; i < fileCount; i++ ) + { + BufferReadHeader( &header ); + lastField = ReadField( pBaseData, pFields, fieldCount, lastField, header.size, m_pdata->pTokens[header.token], header.pData ); + lastField++; + } + + return 1; +} + + +void CRestore::BufferReadHeader( HEADER *pheader ) +{ + ASSERT( pheader!=NULL ); + pheader->size = ReadShort(); // Read field size + pheader->token = ReadShort(); // Read field name token + pheader->pData = BufferPointer(); // Field Data is next + BufferSkipBytes( pheader->size ); // Advance to next field +} + + +short CRestore::ReadShort( void ) +{ + short tmp = 0; + + BufferReadBytes( (char *)&tmp, sizeof(short) ); + + return tmp; +} + +int CRestore::ReadInt( void ) +{ + int tmp = 0; + + BufferReadBytes( (char *)&tmp, sizeof(int) ); + + return tmp; +} + +int CRestore::ReadNamedInt( const char *pName ) +{ + HEADER header; + + BufferReadHeader( &header ); + return ((int *)header.pData)[0]; +} + +char *CRestore::ReadNamedString( const char *pName ) +{ + HEADER header; + + BufferReadHeader( &header ); +#ifdef TOKENIZE + return (char *)(m_pdata->pTokens[*(short *)header.pData]); +#else + return (char *)header.pData; +#endif +} + + +char *CRestore::BufferPointer( void ) +{ + if ( !m_pdata ) + return NULL; + + return m_pdata->pCurrentData; +} + +void CRestore::BufferReadBytes( char *pOutput, int size ) +{ + ASSERT( m_pdata !=NULL ); + + if ( !m_pdata || Empty() ) + return; + + if ( (m_pdata->size + size) > m_pdata->bufferSize ) + { + ALERT( at_error, "Restore overflow!" ); + m_pdata->size = m_pdata->bufferSize; + return; + } + + if ( pOutput ) + memcpy( pOutput, m_pdata->pCurrentData, size ); + m_pdata->pCurrentData += size; + m_pdata->size += size; +} + + +void CRestore::BufferSkipBytes( int bytes ) +{ + BufferReadBytes( NULL, bytes ); +} + +int CRestore::BufferSkipZString( void ) +{ + char *pszSearch; + int len; + + if ( !m_pdata ) + return 0; + + int maxLen = m_pdata->bufferSize - m_pdata->size; + + len = 0; + pszSearch = m_pdata->pCurrentData; + while ( *pszSearch++ && len < maxLen ) + len++; + + len++; + + BufferSkipBytes( len ); + + return len; +} + +int CRestore::BufferCheckZString( const char *string ) +{ + if ( !m_pdata ) + return 0; + + int maxLen = m_pdata->bufferSize - m_pdata->size; + int len = strlen( string ); + if ( len <= maxLen ) + { + if ( !strncmp( string, m_pdata->pCurrentData, len ) ) + return 1; + } + return 0; +} diff --git a/hlsdk/ricochet/dlls/util.h b/hlsdk/ricochet/dlls/util.h new file mode 100644 index 00000000..b6634436 --- /dev/null +++ b/hlsdk/ricochet/dlls/util.h @@ -0,0 +1,562 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Misc utility code +// + +#include + +#ifndef ACTIVITY_H +#include "activity.h" +#endif + +#ifndef ENGINECALLBACK_H +#include "enginecallback.h" +#endif +inline void MESSAGE_BEGIN( int msg_dest, int msg_type, const float *pOrigin, entvars_t *ent ); // implementation later in this file + +extern globalvars_t *gpGlobals; + +// Use this instead of ALLOC_STRING on constant strings +#define STRING(offset) (const char *)(gpGlobals->pStringBase + (int)offset) +#define MAKE_STRING(str) ((int)str - (int)STRING(0)) + +inline edict_t *FIND_ENTITY_BY_CLASSNAME(edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING(entStart, "classname", pszName); +} + +inline edict_t *FIND_ENTITY_BY_TARGETNAME(edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING(entStart, "targetname", pszName); +} + +// for doing a reverse lookup. Say you have a door, and want to find its button. +inline edict_t *FIND_ENTITY_BY_TARGET(edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING(entStart, "target", pszName); +} + +// Keeps clutter down a bit, when writing key-value pairs +#define WRITEKEY_INT(pf, szKeyName, iKeyValue) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%d\"\n", szKeyName, iKeyValue) +#define WRITEKEY_FLOAT(pf, szKeyName, flKeyValue) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%f\"\n", szKeyName, flKeyValue) +#define WRITEKEY_STRING(pf, szKeyName, szKeyValue) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%s\"\n", szKeyName, szKeyValue) +#define WRITEKEY_VECTOR(pf, szKeyName, flX, flY, flZ) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%f %f %f\"\n", szKeyName, flX, flY, flZ) + +// Keeps clutter down a bit, when using a float as a bit-vector +#define SetBits(flBitVector, bits) ((flBitVector) = (int)(flBitVector) | (bits)) +#define ClearBits(flBitVector, bits) ((flBitVector) = (int)(flBitVector) & ~(bits)) +#define FBitSet(flBitVector, bit) ((int)(flBitVector) & (bit)) + +// Makes these more explicit, and easier to find +#define FILE_GLOBAL static +#define DLL_GLOBAL + +// Until we figure out why "const" gives the compiler problems, we'll just have to use +// this bogus "empty" define to mark things as constant. +#define CONSTANT + +// More explicit than "int" +typedef int EOFFSET; + +// In case it's not alread defined +typedef int BOOL; + +// In case this ever changes +#define M_PI 3.14159265358979323846 + +// Keeps clutter down a bit, when declaring external entity/global method prototypes +#define DECLARE_GLOBAL_METHOD(MethodName) \ + extern void DLLEXPORT MethodName( void ) +#define GLOBAL_METHOD(funcname) void DLLEXPORT funcname(void) + +// This is the glue that hooks .MAP entity class names to our CPP classes +// The _declspec forces them to be exported by name so we can do a lookup with GetProcAddress() +// The function is used to intialize / allocate the object for the entity +#ifdef _WIN32 +#define LINK_ENTITY_TO_CLASS(mapClassName,DLLClassName) \ + extern "C" _declspec( dllexport ) void mapClassName( entvars_t *pev ); \ + void mapClassName( entvars_t *pev ) { GetClassPtr( (DLLClassName *)pev ); } +#else +#define LINK_ENTITY_TO_CLASS(mapClassName,DLLClassName) \ + extern "C" void mapClassName( entvars_t *pev ); \ + void mapClassName( entvars_t *pev ) { GetClassPtr( (DLLClassName *)pev ); } +#endif + + +// +// Conversion among the three types of "entity", including identity-conversions. +// +#ifdef DEBUG + extern edict_t *DBG_EntOfVars(const entvars_t *pev); + inline edict_t *ENT(const entvars_t *pev) { return DBG_EntOfVars(pev); } +#else + inline edict_t *ENT(const entvars_t *pev) { return pev->pContainingEntity; } +#endif +inline edict_t *ENT(edict_t *pent) { return pent; } +inline edict_t *ENT(EOFFSET eoffset) { return (*g_engfuncs.pfnPEntityOfEntOffset)(eoffset); } +inline EOFFSET OFFSET(EOFFSET eoffset) { return eoffset; } +inline EOFFSET OFFSET(const edict_t *pent) +{ +#ifdef _DEBUG + if ( !pent ) + ALERT( at_error, "Bad ent in OFFSET()\n" ); +#endif + return (*g_engfuncs.pfnEntOffsetOfPEntity)(pent); +} +inline EOFFSET OFFSET(entvars_t *pev) +{ +#ifdef _DEBUG + if ( !pev ) + ALERT( at_error, "Bad pev in OFFSET()\n" ); +#endif + return OFFSET(ENT(pev)); +} +inline entvars_t *VARS(entvars_t *pev) { return pev; } + +inline entvars_t *VARS(edict_t *pent) +{ + if ( !pent ) + return NULL; + + return &pent->v; +} + +inline entvars_t* VARS(EOFFSET eoffset) { return VARS(ENT(eoffset)); } +inline int ENTINDEX(edict_t *pEdict) { return (*g_engfuncs.pfnIndexOfEdict)(pEdict); } +inline edict_t* INDEXENT( int iEdictNum ) { return (*g_engfuncs.pfnPEntityOfEntIndex)(iEdictNum); } +inline void MESSAGE_BEGIN( int msg_dest, int msg_type, const float *pOrigin, entvars_t *ent ) { + (*g_engfuncs.pfnMessageBegin)(msg_dest, msg_type, pOrigin, ENT(ent)); +} + +// Testing the three types of "entity" for nullity +#define eoNullEntity 0 +inline BOOL FNullEnt(EOFFSET eoffset) { return eoffset == 0; } +inline BOOL FNullEnt(const edict_t* pent) { return pent == NULL || FNullEnt(OFFSET(pent)); } +inline BOOL FNullEnt(entvars_t* pev) { return pev == NULL || FNullEnt(OFFSET(pev)); } + +// Testing strings for nullity +#define iStringNull 0 +inline BOOL FStringNull(int iString) { return iString == iStringNull; } + +#define cchMapNameMost 32 + +// Dot products for view cone checking +#define VIEW_FIELD_FULL (float)-1.0 // +-180 degrees +#define VIEW_FIELD_WIDE (float)-0.7 // +-135 degrees 0.1 // +-85 degrees, used for full FOV checks +#define VIEW_FIELD_NARROW (float)0.7 // +-45 degrees, more narrow check used to set up ranged attacks +#define VIEW_FIELD_ULTRA_NARROW (float)0.9 // +-25 degrees, more narrow check used to set up ranged attacks + +// All monsters need this data +#define DONT_BLEED -1 +#define BLOOD_COLOR_RED (BYTE)247 +#define BLOOD_COLOR_YELLOW (BYTE)195 +#define BLOOD_COLOR_GREEN BLOOD_COLOR_YELLOW + +typedef enum +{ + + MONSTERSTATE_NONE = 0, + MONSTERSTATE_IDLE, + MONSTERSTATE_COMBAT, + MONSTERSTATE_ALERT, + MONSTERSTATE_HUNT, + MONSTERSTATE_PRONE, + MONSTERSTATE_SCRIPT, + MONSTERSTATE_PLAYDEAD, + MONSTERSTATE_DEAD + +} MONSTERSTATE; + + + +// Things that toggle (buttons/triggers/doors) need this +typedef enum + { + TS_AT_TOP, + TS_AT_BOTTOM, + TS_GOING_UP, + TS_GOING_DOWN + } TOGGLE_STATE; + +// Misc useful +inline BOOL FStrEq(const char*sz1, const char*sz2) + { return (strcmp(sz1, sz2) == 0); } +inline BOOL FClassnameIs(edict_t* pent, const char* szClassname) + { return FStrEq(STRING(VARS(pent)->classname), szClassname); } +inline BOOL FClassnameIs(entvars_t* pev, const char* szClassname) + { return FStrEq(STRING(pev->classname), szClassname); } + +class CBaseEntity; + +// Misc. Prototypes +extern void UTIL_SetSize (entvars_t* pev, const Vector &vecMin, const Vector &vecMax); +extern float UTIL_VecToYaw (const Vector &vec); +extern Vector UTIL_VecToAngles (const Vector &vec); +extern float UTIL_AngleMod (float a); +extern float UTIL_AngleDiff ( float destAngle, float srcAngle ); + +extern CBaseEntity *UTIL_FindEntityInSphere(CBaseEntity *pStartEntity, const Vector &vecCenter, float flRadius); +extern CBaseEntity *UTIL_FindEntityByString(CBaseEntity *pStartEntity, const char *szKeyword, const char *szValue ); +extern CBaseEntity *UTIL_FindEntityByClassname(CBaseEntity *pStartEntity, const char *szName ); +extern CBaseEntity *UTIL_FindEntityByTargetname(CBaseEntity *pStartEntity, const char *szName ); +extern CBaseEntity *UTIL_FindEntityGeneric(const char *szName, Vector &vecSrc, float flRadius ); + +// returns a CBaseEntity pointer to a player by index. Only returns if the player is spawned and connected +// otherwise returns NULL +// Index is 1 based +extern CBaseEntity *UTIL_PlayerByIndex( int playerIndex ); + +#define UTIL_EntitiesInPVS(pent) (*g_engfuncs.pfnEntitiesInPVS)(pent) +extern void UTIL_MakeVectors (const Vector &vecAngles); + +// Pass in an array of pointers and an array size, it fills the array and returns the number inserted +extern int UTIL_MonstersInSphere( CBaseEntity **pList, int listMax, const Vector ¢er, float radius ); +extern int UTIL_EntitiesInBox( CBaseEntity **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask ); + +inline void UTIL_MakeVectorsPrivate( const Vector &vecAngles, float *p_vForward, float *p_vRight, float *p_vUp ) +{ + g_engfuncs.pfnAngleVectors( vecAngles, p_vForward, p_vRight, p_vUp ); +} + +extern void UTIL_MakeAimVectors ( const Vector &vecAngles ); // like MakeVectors, but assumes pitch isn't inverted +extern void UTIL_MakeInvVectors ( const Vector &vec, globalvars_t *pgv ); + +extern void UTIL_SetOrigin ( entvars_t* pev, const Vector &vecOrigin ); +extern void UTIL_EmitAmbientSound ( edict_t *entity, const Vector &vecOrigin, const char *samp, float vol, float attenuation, int fFlags, int pitch ); +extern void UTIL_ParticleEffect ( const Vector &vecOrigin, const Vector &vecDirection, ULONG ulColor, ULONG ulCount ); +extern void UTIL_ScreenShake ( const Vector ¢er, float amplitude, float frequency, float duration, float radius ); +extern void UTIL_ScreenShakeAll ( const Vector ¢er, float amplitude, float frequency, float duration ); +extern void UTIL_ShowMessage ( const char *pString, CBaseEntity *pPlayer ); +extern void UTIL_ShowMessageAll ( const char *pString ); +extern void UTIL_ScreenFadeAll ( const Vector &color, float fadeTime, float holdTime, int alpha, int flags ); +extern void UTIL_ScreenFade ( CBaseEntity *pEntity, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ); + +typedef enum { ignore_monsters=1, dont_ignore_monsters=0, missile=2 } IGNORE_MONSTERS; +typedef enum { ignore_glass=1, dont_ignore_glass=0 } IGNORE_GLASS; +extern void UTIL_TraceLine (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, edict_t *pentIgnore, TraceResult *ptr); +extern void UTIL_TraceLine (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, IGNORE_GLASS ignoreGlass, edict_t *pentIgnore, TraceResult *ptr); +typedef enum { point_hull=0, human_hull=1, large_hull=2, head_hull=3 }; +extern void UTIL_TraceHull (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, int hullNumber, edict_t *pentIgnore, TraceResult *ptr); +extern TraceResult UTIL_GetGlobalTrace (void); +extern void UTIL_TraceModel (const Vector &vecStart, const Vector &vecEnd, int hullNumber, edict_t *pentModel, TraceResult *ptr); +extern Vector UTIL_GetAimVector (edict_t* pent, float flSpeed); +extern int UTIL_PointContents (const Vector &vec); + +extern int UTIL_IsMasterTriggered (string_t sMaster, CBaseEntity *pActivator); +extern void UTIL_BloodStream( const Vector &origin, const Vector &direction, int color, int amount ); +extern void UTIL_BloodDrips( const Vector &origin, const Vector &direction, int color, int amount ); +extern Vector UTIL_RandomBloodVector( void ); +extern BOOL UTIL_ShouldShowBlood( int bloodColor ); +extern void UTIL_BloodDecalTrace( TraceResult *pTrace, int bloodColor ); +extern void UTIL_DecalTrace( TraceResult *pTrace, int decalNumber ); +extern void UTIL_PlayerDecalTrace( TraceResult *pTrace, int playernum, int decalNumber, BOOL bIsCustom ); +extern void UTIL_GunshotDecalTrace( TraceResult *pTrace, int decalNumber ); +extern void UTIL_Sparks( const Vector &position, edict_t * ed = NULL ); +extern void UTIL_Ricochet( const Vector &position, float scale ); +extern void UTIL_StringToVector( float *pVector, const char *pString ); +extern void UTIL_StringToIntArray( int *pVector, int count, const char *pString ); +extern Vector UTIL_ClampVectorToBox( const Vector &input, const Vector &clampSize ); +extern float UTIL_Approach( float target, float value, float speed ); +extern float UTIL_ApproachAngle( float target, float value, float speed ); +extern float UTIL_AngleDistance( float next, float cur ); + +extern char *UTIL_VarArgs( char *format, ... ); +extern void UTIL_Remove( CBaseEntity *pEntity ); +extern BOOL UTIL_IsValidEntity( edict_t *pent ); +extern BOOL UTIL_TeamsMatch( const char *pTeamName1, const char *pTeamName2 ); + +// Use for ease-in, ease-out style interpolation (accel/decel) +extern float UTIL_SplineFraction( float value, float scale ); + +// Search for water transition along a vertical line +extern float UTIL_WaterLevel( const Vector &position, float minz, float maxz ); +extern void UTIL_Bubbles( Vector mins, Vector maxs, int count ); +extern void UTIL_BubbleTrail( Vector from, Vector to, int count ); + +// allows precacheing of other entities +extern void UTIL_PrecacheOther( const char *szClassname ); + +// prints a message to each client +extern void UTIL_ClientPrintAll( int msg_dest, const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ); +inline void UTIL_CenterPrintAll( const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ) +{ + UTIL_ClientPrintAll( HUD_PRINTCENTER, msg_name, param1, param2, param3, param4 ); +} + +class CBasePlayerItem; +class CBasePlayer; +extern BOOL UTIL_GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ); + + +// prints messages through the HUD +extern void ClientPrint( entvars_t *client, int msg_dest, const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ); + +// prints a message to the HUD say (chat) +extern void UTIL_SayText( const char *pText, CBaseEntity *pEntity ); +extern void UTIL_SayTextAll( const char *pText, CBaseEntity *pEntity ); + + +typedef struct hudtextparms_s +{ + float x; + float y; + int effect; + byte r1, g1, b1, a1; + byte r2, g2, b2, a2; + float fadeinTime; + float fadeoutTime; + float holdTime; + float fxTime; + int channel; +} hudtextparms_t; + +// prints as transparent 'title' to the HUD +extern void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage ); +extern void UTIL_HudMessage( CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage ); + +// for handy use with ClientPrint params +extern char *UTIL_dtos1( int d ); +extern char *UTIL_dtos2( int d ); +extern char *UTIL_dtos3( int d ); +extern char *UTIL_dtos4( int d ); + +// Writes message to console with timestamp and FragLog header. +extern void UTIL_LogPrintf( char *fmt, ... ); + +// Sorta like FInViewCone, but for nonmonsters. +extern float UTIL_DotPoints ( const Vector &vecSrc, const Vector &vecCheck, const Vector &vecDir ); + +extern void UTIL_StripToken( const char *pKey, char *pDest );// for redundant keynames + +// Misc functions +extern void SetMovedir(entvars_t* pev); +extern Vector VecBModelOrigin( entvars_t* pevBModel ); +extern int BuildChangeList( LEVELLIST *pLevelList, int maxList ); + +// +// How did I ever live without ASSERT? +// +#ifdef DEBUG +void DBG_AssertFunction(BOOL fExpr, const char* szExpr, const char* szFile, int szLine, const char* szMessage); +#define ASSERT(f) DBG_AssertFunction(f, #f, __FILE__, __LINE__, NULL) +#define ASSERTSZ(f, sz) DBG_AssertFunction(f, #f, __FILE__, __LINE__, sz) +#else // !DEBUG +#define ASSERT(f) +#define ASSERTSZ(f, sz) +#endif // !DEBUG + + +extern DLL_GLOBAL const Vector g_vecZero; + +// +// Constants that were used only by QC (maybe not used at all now) +// +// Un-comment only as needed +// +#define LANGUAGE_ENGLISH 0 +#define LANGUAGE_GERMAN 1 +#define LANGUAGE_FRENCH 2 +#define LANGUAGE_BRITISH 3 + +extern DLL_GLOBAL int g_Language; + +#define AMBIENT_SOUND_STATIC 0 // medium radius attenuation +#define AMBIENT_SOUND_EVERYWHERE 1 +#define AMBIENT_SOUND_SMALLRADIUS 2 +#define AMBIENT_SOUND_MEDIUMRADIUS 4 +#define AMBIENT_SOUND_LARGERADIUS 8 +#define AMBIENT_SOUND_START_SILENT 16 +#define AMBIENT_SOUND_NOT_LOOPING 32 + +#define SPEAKER_START_SILENT 1 // wait for trigger 'on' to start announcements + +#define SND_SPAWNING (1<<8) // duplicated in protocol.h we're spawing, used in some cases for ambients +#define SND_STOP (1<<5) // duplicated in protocol.h stop sound +#define SND_CHANGE_VOL (1<<6) // duplicated in protocol.h change sound vol +#define SND_CHANGE_PITCH (1<<7) // duplicated in protocol.h change sound pitch + +#define LFO_SQUARE 1 +#define LFO_TRIANGLE 2 +#define LFO_RANDOM 3 + +// func_rotating +#define SF_BRUSH_ROTATE_Y_AXIS 0 +#define SF_BRUSH_ROTATE_INSTANT 1 +#define SF_BRUSH_ROTATE_BACKWARDS 2 +#define SF_BRUSH_ROTATE_Z_AXIS 4 +#define SF_BRUSH_ROTATE_X_AXIS 8 +#define SF_PENDULUM_AUTO_RETURN 16 +#define SF_PENDULUM_PASSABLE 32 + + +#define SF_BRUSH_ROTATE_SMALLRADIUS 128 +#define SF_BRUSH_ROTATE_MEDIUMRADIUS 256 +#define SF_BRUSH_ROTATE_LARGERADIUS 512 + +#define PUSH_BLOCK_ONLY_X 1 +#define PUSH_BLOCK_ONLY_Y 2 + +#define VEC_HULL_MIN Vector(-16, -16, -36) +#define VEC_HULL_MAX Vector( 16, 16, 36) +#define VEC_HUMAN_HULL_MIN Vector( -16, -16, 0 ) +#define VEC_HUMAN_HULL_MAX Vector( 16, 16, 72 ) +#define VEC_HUMAN_HULL_DUCK Vector( 16, 16, 36 ) + +#define VEC_VIEW Vector( 0, 0, 28 ) + +#define VEC_DUCK_HULL_MIN Vector(-16, -16, -18 ) +#define VEC_DUCK_HULL_MAX Vector( 16, 16, 18) +#define VEC_DUCK_VIEW Vector( 0, 0, 12 ) + +#define SVC_TEMPENTITY 23 +#define SVC_INTERMISSION 30 +#define SVC_CDTRACK 32 +#define SVC_WEAPONANIM 35 +#define SVC_ROOMTYPE 37 + +// Added to stuff text to the clients +#define SVC_STUFFTEXT 9 // [string] stuffed into client's console buffer + +// triggers +#define SF_TRIGGER_ALLOWMONSTERS 1// monsters allowed to fire this trigger +#define SF_TRIGGER_NOCLIENTS 2// players not allowed to fire this trigger +#define SF_TRIGGER_PUSHABLES 4// only pushables can fire this trigger + +// func breakable +#define SF_BREAK_TRIGGER_ONLY 1// may only be broken by trigger +#define SF_BREAK_TOUCH 2// can be 'crashed through' by running player (plate glass) +#define SF_BREAK_PRESSURE 4// can be broken by a player standing on it +#define SF_BREAK_CROWBAR 256// instant break if hit with crowbar + +// func_pushable (it's also func_breakable, so don't collide with those flags) +#define SF_PUSH_BREAKABLE 128 + +#define SF_LIGHT_START_OFF 1 + +#define SPAWNFLAG_NOMESSAGE 1 +#define SPAWNFLAG_NOTOUCH 1 +#define SPAWNFLAG_DROIDONLY 4 + +#define SPAWNFLAG_USEONLY 1 // can't be touched, must be used (buttons) + +#define TELE_PLAYER_ONLY 1 +#define TELE_SILENT 2 + +#define SF_TRIG_PUSH_ONCE 1 + + +// Sound Utilities + +// sentence groups +#define CBSENTENCENAME_MAX 16 +#define CVOXFILESENTENCEMAX 1536 // max number of sentences in game. NOTE: this must match + // CVOXFILESENTENCEMAX in engine\sound.h!!! + +extern char gszallsentencenames[CVOXFILESENTENCEMAX][CBSENTENCENAME_MAX]; +extern int gcallsentences; + +int USENTENCEG_Pick(int isentenceg, char *szfound); +int USENTENCEG_PickSequential(int isentenceg, char *szfound, int ipick, int freset); +void USENTENCEG_InitLRU(unsigned char *plru, int count); + +void SENTENCEG_Init(); +void SENTENCEG_Stop(edict_t *entity, int isentenceg, int ipick); +int SENTENCEG_PlayRndI(edict_t *entity, int isentenceg, float volume, float attenuation, int flags, int pitch); +int SENTENCEG_PlayRndSz(edict_t *entity, const char *szrootname, float volume, float attenuation, int flags, int pitch); +int SENTENCEG_PlaySequentialSz(edict_t *entity, const char *szrootname, float volume, float attenuation, int flags, int pitch, int ipick, int freset); +int SENTENCEG_GetIndex(const char *szrootname); +int SENTENCEG_Lookup(const char *sample, char *sentencenum); + +void TEXTURETYPE_Init(); +char TEXTURETYPE_Find(char *name); +float TEXTURETYPE_PlaySound(TraceResult *ptr, Vector vecSrc, Vector vecEnd, int iBulletType); + +#define CBTEXTURENAMEMAX 13 // only load first n chars of name + +#define CHAR_TEX_CONCRETE 'C' // texture types +#define CHAR_TEX_METAL 'M' +#define CHAR_TEX_DIRT 'D' +#define CHAR_TEX_VENT 'V' +#define CHAR_TEX_GRATE 'G' +#define CHAR_TEX_TILE 'T' +#define CHAR_TEX_SLOSH 'S' +#define CHAR_TEX_WOOD 'W' +#define CHAR_TEX_COMPUTER 'P' +#define CHAR_TEX_GLASS 'Y' +#define CHAR_TEX_FLESH 'F' + +// NOTE: use EMIT_SOUND_DYN to set the pitch of a sound. Pitch of 100 +// is no pitch shift. Pitch > 100 up to 255 is a higher pitch, pitch < 100 +// down to 1 is a lower pitch. 150 to 70 is the realistic range. +// EMIT_SOUND_DYN with pitch != 100 should be used sparingly, as it's not quite as +// fast as EMIT_SOUND (the pitchshift mixer is not native coded). + +void EMIT_SOUND_DYN(edict_t *entity, int channel, const char *sample, float volume, float attenuation, + int flags, int pitch); + + +inline void EMIT_SOUND(edict_t *entity, int channel, const char *sample, float volume, float attenuation) +{ + EMIT_SOUND_DYN(entity, channel, sample, volume, attenuation, 0, PITCH_NORM); +} + +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 < static_cast(ARRAYSIZE( a )); i++ ) PRECACHE_SOUND((char *) a [i]); } + +#define EMIT_SOUND_ARRAY_DYN( chan, array ) \ + EMIT_SOUND_DYN ( ENT(pev), chan , array [ RANDOM_LONG(0,ARRAYSIZE( array )-1) ], 1.0, ATTN_NORM, 0, RANDOM_LONG(95,105) ); + +#define RANDOM_SOUND_ARRAY( array ) (array) [ RANDOM_LONG(0,ARRAYSIZE( (array) )-1) ] + +#define PLAYBACK_EVENT( flags, who, index ) PLAYBACK_EVENT_FULL( flags, who, index, 0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0 ); +#define PLAYBACK_EVENT_DELAY( flags, who, index, delay ) PLAYBACK_EVENT_FULL( flags, who, index, delay, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0 ); + +#define GROUP_OP_AND 0 +#define GROUP_OP_NAND 1 + +extern int g_groupmask; +extern int g_groupop; + +class UTIL_GroupTrace +{ +public: + UTIL_GroupTrace( int groupmask, int op ); + ~UTIL_GroupTrace( void ); + +private: + int m_oldgroupmask, m_oldgroupop; +}; + +void UTIL_SetGroupTrace( int groupmask, int op ); +void UTIL_UnsetGroupTrace( void ); + +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ); +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ); + +float UTIL_WeaponTimeBase( void ); diff --git a/hlsdk/ricochet/dlls/vector.h b/hlsdk/ricochet/dlls/vector.h new file mode 100644 index 00000000..576016d6 --- /dev/null +++ b/hlsdk/ricochet/dlls/vector.h @@ -0,0 +1,109 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef VECTOR_H +#define VECTOR_H + +//========================================================= +// 2DVector - used for many pathfinding and many other +// operations that are treated as planar rather than 3d. +//========================================================= +class Vector2D +{ +public: + inline Vector2D(void): x(0.0), y(0.0) { } + inline Vector2D(float X, float Y): x(0.0), y(0.0) { x = X; y = Y; } + inline Vector2D operator+(const Vector2D& v) const { return Vector2D(x+v.x, y+v.y); } + inline Vector2D operator-(const Vector2D& v) const { return Vector2D(x-v.x, y-v.y); } + inline Vector2D operator*(float fl) const { return Vector2D(x*fl, y*fl); } + inline Vector2D operator/(float fl) const { return Vector2D(x/fl, y/fl); } + + inline float Length(void) const { return sqrt(x*x + y*y ); } + + inline Vector2D Normalize ( void ) const + { + + float flLen = Length(); + if ( flLen == 0 ) + { + return Vector2D( 0, 0 ); + } + else + { + flLen = 1 / flLen; + return Vector2D( x * flLen, y * flLen ); + } + } + + vec_t x, y; +}; + +inline float DotProduct(const Vector2D& a, const Vector2D& b) { return( a.x*b.x + a.y*b.y ); } +inline Vector2D operator*(float fl, const Vector2D& v) { return v * fl; } + +//========================================================= +// 3D Vector +//========================================================= +class Vector // same data-layout as engine's vec3_t, +{ // which is a vec_t[3] +public: + // Construction/destruction + inline Vector(void): x(0.0), y(0.0), z(0.0) { } + inline Vector(float X, float Y, float Z): x(0.0), y(0.0), z(0.0) { x = X; y = Y; z = Z; } + //inline Vector(double X, double Y, double Z) { x = (float)X; y = (float)Y; z = (float)Z; } + //inline Vector(int X, int Y, int Z) { x = (float)X; y = (float)Y; z = (float)Z; } + inline Vector(const Vector& v): x(0.0), y(0.0), z(0.0) { x = v.x; y = v.y; z = v.z; } + inline Vector(float rgfl[3]): x(0.0), y(0.0), z(0.0) { x = rgfl[0]; y = rgfl[1]; z = rgfl[2]; } + + // Operators + inline Vector operator-(void) const { return Vector(-x,-y,-z); } + inline int operator==(const Vector& v) const { return x==v.x && y==v.y && z==v.z; } + inline int operator!=(const Vector& v) const { return !(*this==v); } + inline Vector operator+(const Vector& v) const { return Vector(x+v.x, y+v.y, z+v.z); } + inline Vector operator-(const Vector& v) const { return Vector(x-v.x, y-v.y, z-v.z); } + inline Vector operator*(float fl) const { return Vector(x*fl, y*fl, z*fl); } + inline Vector operator/(float fl) const { return Vector(x/fl, y/fl, z/fl); } + + // Methods + inline void CopyToArray(float* rgfl) const { rgfl[0] = x, rgfl[1] = y, rgfl[2] = z; } + inline float Length(void) const { return sqrt(x*x + y*y + z*z); } + operator float *() { return &x; } // Vectors will now automatically convert to float * when needed + operator const float *() const { return &x; } // Vectors will now automatically convert to float * when needed + inline Vector Normalize(void) const + { + float flLen = Length(); + if (flLen == 0) return Vector(0,0,1); // ???? + flLen = 1 / flLen; + return Vector(x * flLen, y * flLen, z * flLen); + } + + inline Vector2D Make2D ( void ) const + { + Vector2D Vec2; + + Vec2.x = x; + Vec2.y = y; + + return Vec2; + } + inline float Length2D(void) const { return sqrt(x*x + y*y); } + + // Members + vec_t x, y, z; +}; +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 ); } + +#endif diff --git a/hlsdk/ricochet/dlls/weapons.cpp b/hlsdk/ricochet/dlls/weapons.cpp new file mode 100644 index 00000000..3627a07f --- /dev/null +++ b/hlsdk/ricochet/dlls/weapons.cpp @@ -0,0 +1,1058 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== weapons.cpp ======================================================== + + functions governing the selection/use of weapons for players + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "soundent.h" +#include "decals.h" +#include "gamerules.h" + +extern CGraph WorldGraph; +extern int gEvilImpulse101; + + +#define NOT_USED 255 + +DLL_GLOBAL short g_sModelIndexLaser;// holds the index for the laser beam +DLL_GLOBAL const char *g_pModelNameLaser = "sprites/laserbeam.spr"; +DLL_GLOBAL short g_sModelIndexLaserDot;// holds the index for the laser beam dot +DLL_GLOBAL short g_sModelIndexFireball;// holds the index for the fireball +DLL_GLOBAL short g_sModelIndexSmoke;// holds the index for the smoke cloud +DLL_GLOBAL short g_sModelIndexWExplosion;// holds the index for the underwater explosion +DLL_GLOBAL short g_sModelIndexBubbles;// holds the index for the bubbles model +DLL_GLOBAL short g_sModelIndexBloodDrop;// holds the sprite index for the initial blood +DLL_GLOBAL short g_sModelIndexBloodSpray;// holds the sprite index for splattered blood + +ItemInfo CBasePlayerItem::ItemInfoArray[MAX_WEAPONS]; +AmmoInfo CBasePlayerItem::AmmoInfoArray[MAX_AMMO_SLOTS]; + +extern int gmsgCurWeapon; + +MULTIDAMAGE gMultiDamage; + +#define TRACER_FREQ 4 // Tracers fire every fourth bullet + + +//========================================================= +// MaxAmmoCarry - pass in a name and this function will tell +// you the maximum amount of that type of ammunition that a +// player can carry. +//========================================================= +int MaxAmmoCarry( int iszName ) +{ + for ( int i = 0; i < MAX_WEAPONS; i++ ) + { + if ( CBasePlayerItem::ItemInfoArray[i].pszAmmo1 && !strcmp( STRING(iszName), CBasePlayerItem::ItemInfoArray[i].pszAmmo1 ) ) + return CBasePlayerItem::ItemInfoArray[i].iMaxAmmo1; + if ( CBasePlayerItem::ItemInfoArray[i].pszAmmo2 && !strcmp( STRING(iszName), CBasePlayerItem::ItemInfoArray[i].pszAmmo2 ) ) + return CBasePlayerItem::ItemInfoArray[i].iMaxAmmo2; + } + + ALERT( at_console, "MaxAmmoCarry() doesn't recognize '%s'!\n", STRING( iszName ) ); + return -1; +} + + +/* +============================================================================== + +MULTI-DAMAGE + +Collects multiple small damages into a single damage + +============================================================================== +*/ + +// +// ClearMultiDamage - resets the global multi damage accumulator +// +void ClearMultiDamage(void) +{ + gMultiDamage.pEntity = NULL; + gMultiDamage.amount = 0; + gMultiDamage.type = 0; +} + + +// +// ApplyMultiDamage - inflicts contents of global multi damage register on gMultiDamage.pEntity +// +// GLOBALS USED: +// gMultiDamage + +void ApplyMultiDamage(entvars_t *pevInflictor, entvars_t *pevAttacker ) +{ + Vector vecSpot1;//where blood comes from + Vector vecDir;//direction blood should go + TraceResult tr; + + if ( !gMultiDamage.pEntity ) + return; + + gMultiDamage.pEntity->TakeDamage(pevInflictor, pevAttacker, gMultiDamage.amount, gMultiDamage.type ); +} + + +// GLOBALS USED: +// gMultiDamage + +void AddMultiDamage( entvars_t *pevInflictor, CBaseEntity *pEntity, float flDamage, int bitsDamageType) +{ + if ( !pEntity ) + return; + + gMultiDamage.type |= bitsDamageType; + + if ( pEntity != gMultiDamage.pEntity ) + { + ApplyMultiDamage(pevInflictor,pevInflictor); // UNDONE: wrong attacker! + gMultiDamage.pEntity = pEntity; + gMultiDamage.amount = 0; + } + + gMultiDamage.amount += flDamage; +} + +/* +================ +SpawnBlood +================ +*/ +void SpawnBlood(Vector vecSpot, int bloodColor, float flDamage) +{ + UTIL_BloodDrips( vecSpot, g_vecAttackDir, bloodColor, (int)flDamage ); +} + + +int DamageDecal( CBaseEntity *pEntity, int bitsDamageType ) +{ + if ( !pEntity ) + return (DECAL_GUNSHOT1 + RANDOM_LONG(0,4)); + + return pEntity->DamageDecal( bitsDamageType ); +} + +void DecalGunshot( TraceResult *pTrace, int iBulletType ) +{ + // Is the entity valid + if ( !UTIL_IsValidEntity( pTrace->pHit ) ) + return; + + if ( VARS(pTrace->pHit)->solid == SOLID_BSP || VARS(pTrace->pHit)->movetype == MOVETYPE_PUSHSTEP ) + { + CBaseEntity *pEntity = NULL; + // Decal the wall with a gunshot + if ( !FNullEnt(pTrace->pHit) ) + pEntity = CBaseEntity::Instance(pTrace->pHit); + + switch( iBulletType ) + { + 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; + } + } +} + + + +// +// EjectBrass - tosses a brass shell from passed origin at passed velocity +// +void EjectBrass ( const Vector &vecOrigin, const Vector &vecVelocity, float rotation, int model, int soundtype ) +{ + // FIX: when the player shoots, their gun isn't in the same position as it is on the model other players see. + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecOrigin ); + WRITE_BYTE( TE_MODEL); + WRITE_COORD( vecOrigin.x); + WRITE_COORD( vecOrigin.y); + WRITE_COORD( vecOrigin.z); + WRITE_COORD( vecVelocity.x); + WRITE_COORD( vecVelocity.y); + WRITE_COORD( vecVelocity.z); + WRITE_ANGLE( rotation ); + WRITE_SHORT( model ); + WRITE_BYTE ( soundtype); + WRITE_BYTE ( 25 );// 2.5 seconds + MESSAGE_END(); +} + + +#if 0 +// UNDONE: This is no longer used? +void ExplodeModel( const Vector &vecOrigin, float speed, int model, int count ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecOrigin ); + WRITE_BYTE ( TE_EXPLODEMODEL ); + WRITE_COORD( vecOrigin.x ); + WRITE_COORD( vecOrigin.y ); + WRITE_COORD( vecOrigin.z ); + WRITE_COORD( speed ); + WRITE_SHORT( model ); + WRITE_SHORT( count ); + WRITE_BYTE ( 15 );// 1.5 seconds + MESSAGE_END(); +} +#endif + + +int giAmmoIndex = 0; + +// Precaches the ammo and queues the ammo info for sending to clients +void AddAmmoNameToAmmoRegistry( const char *szAmmoname ) +{ + // make sure it's not already in the registry + for ( int i = 0; i < MAX_AMMO_SLOTS; i++ ) + { + if ( !CBasePlayerItem::AmmoInfoArray[i].pszName) + continue; + + if ( stricmp( CBasePlayerItem::AmmoInfoArray[i].pszName, szAmmoname ) == 0 ) + return; // ammo already in registry, just quite + } + + + giAmmoIndex++; + ASSERT( giAmmoIndex < MAX_AMMO_SLOTS ); + if ( giAmmoIndex >= MAX_AMMO_SLOTS ) + giAmmoIndex = 0; + + CBasePlayerItem::AmmoInfoArray[giAmmoIndex].pszName = szAmmoname; + CBasePlayerItem::AmmoInfoArray[giAmmoIndex].iId = giAmmoIndex; // yes, this info is redundant +} + + +// Precaches the weapon and queues the weapon info for sending to clients +void UTIL_PrecacheOtherWeapon( const char *szClassname ) +{ + edict_t *pent; + + pent = CREATE_NAMED_ENTITY( MAKE_STRING( szClassname ) ); + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in UTIL_PrecacheOtherWeapon\n" ); + return; + } + + CBaseEntity *pEntity = CBaseEntity::Instance (VARS( pent )); + + if (pEntity) + { + ItemInfo II; + pEntity->Precache( ); + memset( &II, 0, sizeof II ); + if ( ((CBasePlayerItem*)pEntity)->GetItemInfo( &II ) ) + { + CBasePlayerItem::ItemInfoArray[II.iId] = II; + + if ( II.pszAmmo1 && *II.pszAmmo1 ) + { + AddAmmoNameToAmmoRegistry( II.pszAmmo1 ); + } + + if ( II.pszAmmo2 && *II.pszAmmo2 ) + { + AddAmmoNameToAmmoRegistry( II.pszAmmo2 ); + } + + memset( &II, 0, sizeof II ); + } + } + + REMOVE_ENTITY(pent); +} + +// called by worldspawn +void W_Precache(void) +{ + memset( CBasePlayerItem::ItemInfoArray, 0, sizeof(CBasePlayerItem::ItemInfoArray) ); + memset( CBasePlayerItem::AmmoInfoArray, 0, sizeof(CBasePlayerItem::AmmoInfoArray) ); + giAmmoIndex = 0; + + // Discwar + UTIL_PrecacheOtherWeapon( "weapon_disc" ); + UTIL_PrecacheOther( "disc" ); +} + + + + +TYPEDESCRIPTION CBasePlayerItem::m_SaveData[] = +{ + DEFINE_FIELD( CBasePlayerItem, m_pPlayer, FIELD_CLASSPTR ), + DEFINE_FIELD( CBasePlayerItem, m_pNext, FIELD_CLASSPTR ), + //DEFINE_FIELD( CBasePlayerItem, m_fKnown, FIELD_INTEGER ),Reset to zero on load + DEFINE_FIELD( CBasePlayerItem, m_iId, FIELD_INTEGER ), + // DEFINE_FIELD( CBasePlayerItem, m_iIdPrimary, FIELD_INTEGER ), + // DEFINE_FIELD( CBasePlayerItem, m_iIdSecondary, FIELD_INTEGER ), +}; +IMPLEMENT_SAVERESTORE( CBasePlayerItem, CBaseAnimating ); + + +TYPEDESCRIPTION CBasePlayerWeapon::m_SaveData[] = +{ + DEFINE_FIELD( CBasePlayerWeapon, m_flNextPrimaryAttack, FIELD_TIME ), + DEFINE_FIELD( CBasePlayerWeapon, m_flNextSecondaryAttack, FIELD_TIME ), + DEFINE_FIELD( CBasePlayerWeapon, m_flTimeWeaponIdle, FIELD_TIME ), + DEFINE_FIELD( CBasePlayerWeapon, m_iPrimaryAmmoType, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerWeapon, m_iSecondaryAmmoType, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerWeapon, m_iClip, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerWeapon, m_iDefaultAmmo, FIELD_INTEGER ), +// DEFINE_FIELD( CBasePlayerWeapon, m_iClientClip, FIELD_INTEGER ) , reset to zero on load so hud gets updated correctly +// DEFINE_FIELD( CBasePlayerWeapon, m_iClientWeaponState, FIELD_INTEGER ), reset to zero on load so hud gets updated correctly +}; + +IMPLEMENT_SAVERESTORE( CBasePlayerWeapon, CBasePlayerItem ); + + +void CBasePlayerItem :: SetObjectCollisionBox( void ) +{ + pev->absmin = pev->origin + Vector(-24, -24, 0); + pev->absmax = pev->origin + Vector(24, 24, 16); +} + + +//========================================================= +// Sets up movetype, size, solidtype for a new weapon. +//========================================================= +void CBasePlayerItem :: FallInit( void ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_BBOX; + + UTIL_SetOrigin( pev, pev->origin ); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0) );//pointsize until it lands on the ground. + + SetTouch( &CBasePlayerItem::DefaultTouch ); + SetThink( &CBasePlayerItem::FallThink ); + + pev->nextthink = gpGlobals->time + 0.1; +} + +//========================================================= +// FallThink - Items that have just spawned run this think +// to catch them when they hit the ground. Once we're sure +// that the object is grounded, we change its solid type +// to trigger and set it in a large box that helps the +// player get it. +//========================================================= +void CBasePlayerItem::FallThink ( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + if ( pev->flags & FL_ONGROUND ) + { + // clatter if we have an owner (i.e., dropped by someone) + // don't clatter if the gun is waiting to respawn (if it's waiting, it is invisible!) + if ( !FNullEnt( pev->owner ) ) + { + int pitch = 95 + RANDOM_LONG(0,29); + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "items/weapondrop1.wav", 1, ATTN_NORM, 0, pitch); + } + + // lie flat + pev->angles.x = 0; + pev->angles.z = 0; + + Materialize(); + } +} + +//========================================================= +// Materialize - make a CBasePlayerItem visible and tangible +//========================================================= +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; + } + + pev->solid = SOLID_TRIGGER; + + UTIL_SetOrigin( pev, pev->origin );// link into world. + SetTouch (&CBasePlayerItem::DefaultTouch); + SetThink (NULL); + +} + +//========================================================= +// AttemptToMaterialize - the item is trying to rematerialize, +// should it do so now or wait longer? +//========================================================= +void CBasePlayerItem::AttemptToMaterialize( void ) +{ + float time = g_pGameRules->FlWeaponTryRespawn( this ); + + if ( time == 0 ) + { + Materialize(); + return; + } + + pev->nextthink = gpGlobals->time + time; +} + +//========================================================= +// CheckRespawn - a player is taking this weapon, should +// it respawn? +//========================================================= +void CBasePlayerItem :: CheckRespawn ( void ) +{ + switch ( g_pGameRules->WeaponShouldRespawn( this ) ) + { + case GR_WEAPON_RESPAWN_YES: + Respawn(); + break; + case GR_WEAPON_RESPAWN_NO: + return; + break; + } +} + +//========================================================= +// Respawn- this item is already in the world, but it is +// invisible and intangible. Make it visible and tangible. +//========================================================= +CBaseEntity* CBasePlayerItem::Respawn( void ) +{ + // make a copy of this weapon that is invisible and inaccessible to players (no touch function). The weapon spawn/respawn code + // will decide when to make the weapon visible and touchable. + CBaseEntity *pNewWeapon = CBaseEntity::Create( (char *)STRING( pev->classname ), g_pGameRules->VecWeaponRespawnSpot( this ), pev->angles, pev->owner ); + + if ( pNewWeapon ) + { + pNewWeapon->pev->effects |= EF_NODRAW;// invisible for now + pNewWeapon->SetTouch( NULL );// no touch + pNewWeapon->SetThink( &CBasePlayerItem::AttemptToMaterialize ); + + DROP_TO_FLOOR ( ENT(pev) ); + + // not a typo! We want to know when the weapon the player just picked up should respawn! This new entity we created is the replacement, + // but when it should respawn is based on conditions belonging to the weapon that was taken. + pNewWeapon->pev->nextthink = g_pGameRules->FlWeaponRespawnTime( this ); + } + else + { + ALERT ( at_console, "Respawn failed to create %s!\n", STRING( pev->classname ) ); + } + + return pNewWeapon; +} + +void CBasePlayerItem::DefaultTouch( CBaseEntity *pOther ) +{ + // if it's not a player, ignore + if ( !pOther->IsPlayer() ) + return; + + CBasePlayer *pPlayer = (CBasePlayer *)pOther; + + // can I have this? + if ( !g_pGameRules->CanHavePlayerItem( pPlayer, this ) ) + { + if ( gEvilImpulse101 ) + { + UTIL_Remove( this ); + } + return; + } + + if (pOther->AddPlayerItem( this )) + { + AttachToPlayer( pPlayer ); + EMIT_SOUND(ENT(pPlayer->pev), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM); + } + + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); // UNDONE: when should this happen? +} + +BOOL CanAttack( float attack_time, float curtime, BOOL isPredicted ) +{ + if ( !isPredicted ) + { + return ( attack_time <= curtime ) ? TRUE : FALSE; + } + else + { + return ( attack_time <= 0.0 ) ? TRUE : FALSE; + } +} + +void CBasePlayerWeapon::ItemPostFrame( void ) +{ + if ((m_fInReload) && ( m_pPlayer->m_flNextAttack <= UTIL_WeaponTimeBase() ) ) + { + // 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; + + m_fInReload = FALSE; + } + + if ((m_pPlayer->pev->button & IN_ATTACK2) && CanAttack( m_flNextSecondaryAttack, gpGlobals->time, UseDecrement() ) ) + { + 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) && CanAttack( m_flNextPrimaryAttack, gpGlobals->time, UseDecrement() ) ) + { + 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; + + 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; + } + + // catch all + if ( ShouldWeaponIdle() ) + { + WeaponIdle(); + } +} + +void CBasePlayerItem::DestroyItem( void ) +{ + if ( m_pPlayer ) + { + // if attached to a player, remove. + m_pPlayer->RemovePlayerItem( this ); + } + + Kill( ); +} + +int CBasePlayerItem::AddToPlayer( CBasePlayer *pPlayer ) +{ + m_pPlayer = pPlayer; + + return TRUE; +} + +void CBasePlayerItem::Drop( void ) +{ + SetTouch( NULL ); + SetThink(&CBaseEntity::SUB_Remove); + pev->nextthink = gpGlobals->time + .1; +} + +void CBasePlayerItem::Kill( void ) +{ + SetTouch( NULL ); + SetThink(&CBaseEntity::SUB_Remove); + pev->nextthink = gpGlobals->time + .1; +} + +void CBasePlayerItem::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->pev->viewmodel = 0; + m_pPlayer->pev->weaponmodel = 0; +} + +void CBasePlayerItem::AttachToPlayer ( CBasePlayer *pPlayer ) +{ + pev->movetype = MOVETYPE_FOLLOW; + pev->solid = SOLID_NOT; + 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->owner = pPlayer->edict(); + pev->nextthink = gpGlobals->time + .1; + SetTouch( NULL ); +} + +// CALLED THROUGH the newly-touched weapon's instance. The existing player weapon is pOriginal +int CBasePlayerWeapon::AddDuplicate( CBasePlayerItem *pOriginal ) +{ + if ( m_iDefaultAmmo ) + { + return ExtractAmmo( (CBasePlayerWeapon *)pOriginal ); + } + else + { + // a dead player dropped this. + return ExtractClipAmmo( (CBasePlayerWeapon *)pOriginal ); + } +} + + +int CBasePlayerWeapon::AddToPlayer( CBasePlayer *pPlayer ) +{ + int bResult = CBasePlayerItem::AddToPlayer( pPlayer ); + + pPlayer->pev->weapons |= (1<GetAmmoIndex( pszAmmo1() ); + m_iSecondaryAmmoType = pPlayer->GetAmmoIndex( pszAmmo2() ); + } + + + if (bResult) + return AddWeapon( ); + return FALSE; +} + +int CBasePlayerWeapon::UpdateClientData( CBasePlayer *pPlayer ) +{ + BOOL bSend = FALSE; + int state = 0; + if ( pPlayer->m_pActiveItem == this ) + { + if ( pPlayer->m_fOnTarget ) + state = WEAPON_IS_ONTARGET; + else + state = 1; + } + + // Forcing send of all data! + if ( !pPlayer->m_fWeapon ) + { + bSend = TRUE; + } + + // This is the current or last weapon, so the state will need to be updated + if ( this == pPlayer->m_pActiveItem || + this == pPlayer->m_pClientActiveItem ) + { + if ( pPlayer->m_pActiveItem != pPlayer->m_pClientActiveItem ) + { + bSend = TRUE; + } + } + + // If the ammo, state, or fov has changed, update the weapon + if ( m_iClip != m_iClientClip || + state != m_iClientWeaponState || + pPlayer->m_iFOV != pPlayer->m_iClientFOV ) + { + bSend = TRUE; + } + + if ( bSend ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pPlayer->pev ); + WRITE_BYTE( state ); + WRITE_BYTE( m_iId ); + WRITE_BYTE( m_iClip ); + MESSAGE_END(); + + m_iClientClip = m_iClip; + m_iClientWeaponState = state; + pPlayer->m_fWeapon = TRUE; + } + + if ( m_pNext ) + m_pNext->UpdateClientData( pPlayer ); + + return 1; +} + + +void CBasePlayerWeapon::SendWeaponAnim( int iAnim, int skiplocal ) +{ + m_pPlayer->pev->weaponanim = iAnim; + + if ( skiplocal && ENGINE_CANSKIP( m_pPlayer->edict() ) ) + return; + + MESSAGE_BEGIN( MSG_ONE, SVC_WEAPONANIM, NULL, m_pPlayer->pev ); + WRITE_BYTE( iAnim ); // sequence number + WRITE_BYTE( pev->body ); // weaponmodel bodygroup. + MESSAGE_END(); +} + +BOOL CBasePlayerWeapon :: AddPrimaryAmmo( int iCount, char *szName, int iMaxClip, int iMaxCarry ) +{ + int iIdAmmo; + + if (iMaxClip < 1) + { + m_iClip = -1; + iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName, iMaxCarry ); + } + else if (m_iClip == 0) + { + int i; + i = min( m_iClip + iCount, iMaxClip ) - m_iClip; + m_iClip += i; + iIdAmmo = m_pPlayer->GiveAmmo( iCount - i, szName, iMaxCarry ); + } + else + { + iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName, iMaxCarry ); + } + + // m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] = iMaxCarry; // hack for testing + + if (iIdAmmo > 0) + { + m_iPrimaryAmmoType = iIdAmmo; + if (m_pPlayer->HasPlayerItem( this ) ) + { + // 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); + } + } + + return iIdAmmo > 0 ? TRUE : FALSE; +} + + +BOOL CBasePlayerWeapon :: AddSecondaryAmmo( int iCount, char *szName, int iMax ) +{ + int iIdAmmo; + + iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName, iMax ); + + //m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] = iMax; // hack for testing + + if (iIdAmmo > 0) + { + m_iSecondaryAmmoType = iIdAmmo; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + } + return iIdAmmo > 0 ? TRUE : FALSE; +} + +//========================================================= +// IsUseable - this function determines whether or not a +// weapon is useable by the player in its current state. +// (does it have ammo loaded? do I have any ammo for the +// weapon?, etc) +//========================================================= +BOOL CBasePlayerWeapon :: IsUseable( void ) +{ + if ( m_iClip <= 0 ) + { + if ( m_pPlayer->m_rgAmmo[ PrimaryAmmoIndex() ] <= 0 && iMaxAmmo1() != -1 ) + { + // clip is empty (or nonexistant) and the player has no more ammo of this type. + return FALSE; + } + } + + return TRUE; +} + +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; +} + +BOOL CBasePlayerWeapon :: DefaultDeploy( char *szViewModel, char *szWeaponModel, int iAnim, char *szAnimExt, int skiplocal /* = 0 */ ) +{ + if (!CanDeploy( )) + return FALSE; + + m_pPlayer->pev->viewmodel = MAKE_STRING(szViewModel); + m_pPlayer->pev->weaponmodel = MAKE_STRING(szWeaponModel); + strcpy( m_pPlayer->m_szAnimExtention, szAnimExt ); + SendWeaponAnim( iAnim, skiplocal ); + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.0; + + return TRUE; +} + + +BOOL CBasePlayerWeapon :: DefaultReload( int iClipSize, int iAnim, float fDelay ) +{ + 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() ? 1 : 0 ); + + m_fInReload = TRUE; + + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 3; + return TRUE; +} + +BOOL CBasePlayerWeapon :: PlayEmptySound( void ) +{ + if (m_iPlayEmptySound) + { + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/357_cock1.wav", 0.8, ATTN_NORM); + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +void CBasePlayerWeapon :: ResetEmptySound( void ) +{ + m_iPlayEmptySound = 1; +} + +//========================================================= +//========================================================= +int CBasePlayerWeapon::PrimaryAmmoIndex( void ) +{ + return m_iPrimaryAmmoType; +} + +//========================================================= +//========================================================= +int CBasePlayerWeapon::SecondaryAmmoIndex( void ) +{ + return -1; +} + +void CBasePlayerWeapon::Holster( int skiplocal /* = 0 */ ) +{ + m_fInReload = FALSE; // cancel any reload in progress. + m_pPlayer->pev->viewmodel = 0; + m_pPlayer->pev->weaponmodel = 0; +} + +void CBasePlayerAmmo::Spawn( void ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 16)); + UTIL_SetOrigin( pev, pev->origin ); + + SetTouch( &CBasePlayerAmmo::DefaultTouch ); +} + +CBaseEntity* CBasePlayerAmmo::Respawn( void ) +{ + pev->effects |= EF_NODRAW; + SetTouch( NULL ); + + UTIL_SetOrigin( pev, g_pGameRules->VecAmmoRespawnSpot( this ) );// move to wherever I'm supposed to repawn. + + SetThink( &CBasePlayerAmmo::Materialize ); + pev->nextthink = g_pGameRules->FlAmmoRespawnTime( this ); + + return this; +} + +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; + } + + SetTouch( &CBasePlayerAmmo::DefaultTouch ); +} + +void CBasePlayerAmmo :: DefaultTouch( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + { + return; + } + + if (AddAmmo( pOther )) + { + if ( g_pGameRules->AmmoShouldRespawn( this ) == GR_AMMO_RESPAWN_YES ) + { + Respawn(); + } + else + { + SetTouch( NULL ); + SetThink(&CBaseEntity::SUB_Remove); + pev->nextthink = gpGlobals->time + .1; + } + } + else if (gEvilImpulse101) + { + // evil impulse 101 hack, kill always + SetTouch( NULL ); + SetThink(&CBaseEntity::SUB_Remove); + pev->nextthink = gpGlobals->time + .1; + } +} + +//========================================================= +// called by the new item with the existing item as parameter +// +// if we call ExtractAmmo(), it's because the player is picking up this type of weapon for +// the first time. If it is spawned by the world, m_iDefaultAmmo will have a default ammo amount in it. +// if this is a weapon dropped by a dying player, has 0 m_iDefaultAmmo, which means only the ammo in +// the weapon clip comes along. +//========================================================= +int CBasePlayerWeapon::ExtractAmmo( CBasePlayerWeapon *pWeapon ) +{ + int iReturn = 0; + + if ( pszAmmo1() != NULL ) + { + // blindly call with m_iDefaultAmmo. It's either going to be a value or zero. If it is zero, + // we only get the ammo in the weapon's clip, which is what we want. + iReturn = pWeapon->AddPrimaryAmmo( m_iDefaultAmmo, (char *)pszAmmo1(), iMaxClip(), iMaxAmmo1() ); + m_iDefaultAmmo = 0; + } + + if ( pszAmmo2() != NULL ) + { + iReturn = pWeapon->AddSecondaryAmmo( 0, (char *)pszAmmo2(), iMaxAmmo2() ); + } + + return iReturn; +} + +//========================================================= +// called by the new item's class with the existing item as parameter +//========================================================= +int CBasePlayerWeapon::ExtractClipAmmo( CBasePlayerWeapon *pWeapon ) +{ + int iAmmo; + + if ( m_iClip == WEAPON_NOCLIP ) + { + iAmmo = 0;// guns with no clips always come empty if they are second-hand + } + else + { + iAmmo = m_iClip; + } + + return pWeapon->m_pPlayer->GiveAmmo( iAmmo, (char *)pszAmmo1(), iMaxAmmo1() ); // , &m_iPrimaryAmmoType +} + +//========================================================= +// RetireWeapon - no more ammo for this gun, put it away. +//========================================================= +void CBasePlayerWeapon::RetireWeapon( void ) +{ + // first, no viewmodel at all. + m_pPlayer->pev->viewmodel = iStringNull; + m_pPlayer->pev->weaponmodel = iStringNull; + //m_pPlayer->pev->viewmodelindex = NULL; + + g_pGameRules->GetNextBestWeapon( m_pPlayer, this ); +} + +void CBasePlayerWeapon::PrintState( void ) +{ +} diff --git a/hlsdk/ricochet/dlls/weapons.h b/hlsdk/ricochet/dlls/weapons.h new file mode 100644 index 00000000..57ccbf54 --- /dev/null +++ b/hlsdk/ricochet/dlls/weapons.h @@ -0,0 +1,451 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef WEAPONS_H +#define WEAPONS_H + + +class CBasePlayer; +extern int gmsgWeapPickup; + +// Contact Grenade / Timed grenade / Satchel Charge +class CGrenade : public CBaseMonster +{ +public: + void Spawn( void ); + + typedef enum { SATCHEL_DETONATE = 0, SATCHEL_RELEASE } SATCHELCODE; + + static CGrenade *ShootTimed( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time ); + static CGrenade *ShootContact( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ); + static CGrenade *ShootSatchelCharge( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ); + static void UseSatchelCharges( entvars_t *pevOwner, SATCHELCODE code ); + + void Explode( Vector vecSrc, Vector vecAim ); + void Explode( TraceResult *pTrace, int bitsDamageType ); + void EXPORT Smoke( void ); + + void EXPORT BounceTouch( CBaseEntity *pOther ); + void EXPORT SlideTouch( CBaseEntity *pOther ); + void EXPORT ExplodeTouch( CBaseEntity *pOther ); + void EXPORT DangerSoundThink( void ); + void EXPORT PreDetonate( void ); + void EXPORT Detonate( void ); + void EXPORT DetonateUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT TumbleThink( void ); + + virtual void BounceSound( void ); + virtual int BloodColor( void ) { return DONT_BLEED; } + virtual void Killed( entvars_t *pevAttacker, int iGib ); + + BOOL m_fRegisteredSound;// whether or not this grenade has issued its DANGER sound to the world sound list yet. +}; + + +// 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<skin < 0 || (gpGlobals->deathmatch && FBitSet( pev->spawnflags, SF_DECAL_NOTINDEATHMATCH )) ) + { + REMOVE_ENTITY(ENT(pev)); + return; + } + + if ( FStringNull ( pev->targetname ) ) + { + SetThink( &CDecal::StaticDecal ); + // if there's no targetname, the decal will spray itself on as soon as the world is done spawning. + pev->nextthink = gpGlobals->time; + } + else + { + // if there IS a targetname, the decal sprays itself on when it is triggered. + SetThink ( &CBaseEntity::SUB_DoNothing ); + SetUse(&CDecal::TriggerDecal); + } +} + +void CDecal :: TriggerDecal ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // this is set up as a USE function for infodecals that have targetnames, so that the + // decal doesn't get applied until it is fired. (usually by a scripted sequence) + TraceResult trace; + int entityIndex; + + UTIL_TraceLine( pev->origin - Vector(5,5,5), pev->origin + Vector(5,5,5), ignore_monsters, ENT(pev), &trace ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY); + WRITE_BYTE( TE_BSPDECAL ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( (int)pev->skin ); + entityIndex = (short)ENTINDEX(trace.pHit); + WRITE_SHORT( entityIndex ); + if ( entityIndex ) + WRITE_SHORT( (int)VARS(trace.pHit)->modelindex ); + MESSAGE_END(); + + SetThink( &CBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CDecal :: StaticDecal( void ) +{ + TraceResult trace; + int entityIndex, modelIndex; + + UTIL_TraceLine( pev->origin - Vector(5,5,5), pev->origin + Vector(5,5,5), ignore_monsters, ENT(pev), &trace ); + + entityIndex = (short)ENTINDEX(trace.pHit); + if ( entityIndex ) + modelIndex = (int)VARS(trace.pHit)->modelindex; + else + modelIndex = 0; + + g_engfuncs.pfnStaticDecal( pev->origin, (int)pev->skin, entityIndex, modelIndex ); + + SUB_Remove(); +} + + +void CDecal :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "texture")) + { + pev->skin = DECAL_INDEX( pkvd->szValue ); + + // Found + if ( pev->skin >= 0 ) + return; + ALERT( at_console, "Can't find decal %s\n", pkvd->szValue ); + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +// Body queue class here.... It's really just CBaseEntity +class CCorpse : public CBaseEntity +{ + virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +}; + +LINK_ENTITY_TO_CLASS( bodyque, CCorpse ); + +static void InitBodyQue(void) +{ + string_t istrClassname = MAKE_STRING("bodyque"); + + g_pBodyQueueHead = CREATE_NAMED_ENTITY( istrClassname ); + entvars_t *pev = VARS(g_pBodyQueueHead); + + // Reserve 3 more slots for dead bodies + for ( int i = 0; i < 3; i++ ) + { + pev->owner = CREATE_NAMED_ENTITY( istrClassname ); + pev = VARS(pev->owner); + } + + pev->owner = g_pBodyQueueHead; +} + + +// +// make a body que entry for the given ent so the ent can be respawned elsewhere +// +// GLOBALS ASSUMED SET: g_eoBodyQueueHead +// +void CopyToBodyQue(entvars_t *pev) +{ + // DISCWAR: No corpses + return; + + if (pev->effects & EF_NODRAW) + return; + + entvars_t *pevHead = VARS(g_pBodyQueueHead); + + pevHead->angles = pev->angles; + pevHead->model = pev->model; + pevHead->modelindex = pev->modelindex; + pevHead->frame = pev->frame; + pevHead->colormap = pev->colormap; + pevHead->movetype = MOVETYPE_TOSS; + pevHead->velocity = pev->velocity; + pevHead->flags = 0; + pevHead->deadflag = pev->deadflag; + pevHead->renderfx = kRenderFxDeadPlayer; + pevHead->renderamt = ENTINDEX( ENT( pev ) ); + + pevHead->effects = pev->effects | EF_NOINTERP; + //pevHead->goalstarttime = pev->goalstarttime; + //pevHead->goalframe = pev->goalframe; + //pevHead->goalendtime = pev->goalendtime ; + + pevHead->sequence = pev->sequence; + pevHead->animtime = pev->animtime; + + UTIL_SetOrigin(pevHead, pev->origin); + UTIL_SetSize(pevHead, pev->mins, pev->maxs); + g_pBodyQueueHead = pevHead->owner; +} + + +CGlobalState::CGlobalState( void ) +{ + Reset(); +} + +void CGlobalState::Reset( void ) +{ + m_pList = NULL; + m_listCount = 0; +} + +globalentity_t *CGlobalState :: Find( string_t globalname ) +{ + if ( !globalname ) + return NULL; + + globalentity_t *pTest; + const char *pEntityName = STRING(globalname); + + + pTest = m_pList; + while ( pTest ) + { + if ( FStrEq( pEntityName, pTest->name ) ) + break; + + pTest = pTest->pNext; + } + + return pTest; +} + + +// This is available all the time now on impulse 104, remove later +//#ifdef _DEBUG +void CGlobalState :: DumpGlobals( void ) +{ + static char *estates[] = { "Off", "On", "Dead" }; + globalentity_t *pTest; + + ALERT( at_console, "-- Globals --\n" ); + pTest = m_pList; + while ( pTest ) + { + ALERT( at_console, "%s: %s (%s)\n", pTest->name, pTest->levelName, estates[pTest->state] ); + pTest = pTest->pNext; + } +} +//#endif + + +void CGlobalState :: EntityAdd( string_t globalname, string_t mapName, GLOBALESTATE state ) +{ + ASSERT( !Find(globalname) ); + + globalentity_t *pNewEntity = (globalentity_t *)calloc( sizeof( globalentity_t ), 1 ); + ASSERT( pNewEntity != NULL ); + pNewEntity->pNext = m_pList; + m_pList = pNewEntity; + strcpy( pNewEntity->name, STRING( globalname ) ); + strcpy( pNewEntity->levelName, STRING(mapName) ); + pNewEntity->state = state; + m_listCount++; +} + + +void CGlobalState :: EntitySetState( string_t globalname, GLOBALESTATE state ) +{ + globalentity_t *pEnt = Find( globalname ); + + if ( pEnt ) + pEnt->state = state; +} + + +const globalentity_t *CGlobalState :: EntityFromTable( string_t globalname ) +{ + globalentity_t *pEnt = Find( globalname ); + + return pEnt; +} + + +GLOBALESTATE CGlobalState :: EntityGetState( string_t globalname ) +{ + globalentity_t *pEnt = Find( globalname ); + if ( pEnt ) + return pEnt->state; + + return GLOBAL_OFF; +} + + +// Global Savedata for Delay +TYPEDESCRIPTION CGlobalState::m_SaveData[] = +{ + DEFINE_FIELD( CGlobalState, m_listCount, FIELD_INTEGER ), +}; + +// Global Savedata for Delay +TYPEDESCRIPTION gGlobalEntitySaveData[] = +{ + DEFINE_ARRAY( globalentity_t, name, FIELD_CHARACTER, 64 ), + DEFINE_ARRAY( globalentity_t, levelName, FIELD_CHARACTER, 32 ), + DEFINE_FIELD( globalentity_t, state, FIELD_INTEGER ), +}; + + +int CGlobalState::Save( CSave &save ) +{ + int i; + globalentity_t *pEntity; + + if ( !save.WriteFields( "GLOBAL", this, m_SaveData, ARRAYSIZE(m_SaveData) ) ) + return 0; + + pEntity = m_pList; + for ( i = 0; i < m_listCount && pEntity; i++ ) + { + if ( !save.WriteFields( "GENT", pEntity, gGlobalEntitySaveData, ARRAYSIZE(gGlobalEntitySaveData) ) ) + return 0; + + pEntity = pEntity->pNext; + } + + return 1; +} + +int CGlobalState::Restore( CRestore &restore ) +{ + int i, listCount; + globalentity_t tmpEntity; + + + ClearStates(); + if ( !restore.ReadFields( "GLOBAL", this, m_SaveData, ARRAYSIZE(m_SaveData) ) ) + return 0; + + listCount = m_listCount; // Get new list count + m_listCount = 0; // Clear loaded data + + for ( i = 0; i < listCount; i++ ) + { + if ( !restore.ReadFields( "GENT", &tmpEntity, gGlobalEntitySaveData, ARRAYSIZE(gGlobalEntitySaveData) ) ) + return 0; + EntityAdd( MAKE_STRING(tmpEntity.name), MAKE_STRING(tmpEntity.levelName), tmpEntity.state ); + } + return 1; +} + +void CGlobalState::EntityUpdate( string_t globalname, string_t mapname ) +{ + globalentity_t *pEnt = Find( globalname ); + + if ( pEnt ) + strcpy( pEnt->levelName, STRING(mapname) ); +} + + +void CGlobalState::ClearStates( void ) +{ + globalentity_t *pFree = m_pList; + while ( pFree ) + { + globalentity_t *pNext = pFree->pNext; + free( pFree ); + pFree = pNext; + } + Reset(); +} + + +void SaveGlobalState( SAVERESTOREDATA *pSaveData ) +{ + CSave saveHelper( pSaveData ); + gGlobalState.Save( saveHelper ); +} + + +void RestoreGlobalState( SAVERESTOREDATA *pSaveData ) +{ + CRestore restoreHelper( pSaveData ); + gGlobalState.Restore( restoreHelper ); +} + + +void ResetGlobalState( void ) +{ + gGlobalState.ClearStates(); + gInitHUD = TRUE; // Init the HUD on a new game / load game + g_iPlayersPerTeam = 0; +} + +// moved CWorld class definition to cbase.h +//======================= +// CWorld +// +// This spawns first when each level begins. +//======================= + +LINK_ENTITY_TO_CLASS( worldspawn, CWorld ); + +#define SF_WORLD_DARK 0x0001 // Fade from black at startup +#define SF_WORLD_TITLE 0x0002 // Display game title at startup +#define SF_WORLD_FORCETEAM 0x0004 // Force teams + +extern DLL_GLOBAL BOOL g_fGameOver; +float g_flWeaponCheat; + +void CWorld :: Spawn( void ) +{ + g_fGameOver = FALSE; + Precache( ); + g_flWeaponCheat = CVAR_GET_FLOAT( "sv_cheats" ); // Is the impulse 101 command allowed? + + if ( m_iArenaOff ) + g_iMapTurnedOffArena = TRUE; + else + g_iMapTurnedOffArena = FALSE; +} + +void CWorld :: Precache( void ) +{ + g_pLastSpawn = NULL; + + CVAR_SET_STRING("sv_gravity", "800"); // 67ft/sec + CVAR_SET_STRING("sv_stepsize", "18"); + CVAR_SET_STRING("room_type", "0"); // clear DSP + + // Create all the arenas + for (int i = 0; i < MAX_ARENAS; i++) + { + g_pArenaList[i] = GetClassPtr( ( CDiscArena *)NULL ); + g_pArenaList[i]->Spawn(); + } + + // Set up game rules + if (g_pGameRules) + { + delete g_pGameRules; + } + + g_pGameRules = InstallGameRules( ); + + //!!!UNDONE why is there so much Spawn code in the Precache function? I'll just keep it here + + ///!!!LATER - do we want a sound ent in deathmatch? (sjb) + //pSoundEnt = CBaseEntity::Create( "soundent", g_vecZero, g_vecZero, edict() ); + pSoundEnt = GetClassPtr( ( CSoundEnt *)NULL ); + pSoundEnt->Spawn(); + + if ( !pSoundEnt ) + { + ALERT ( at_console, "**COULD NOT CREATE SOUNDENT**\n" ); + } + + InitBodyQue(); + +// init sentence group playback stuff from sentences.txt. +// ok to call this multiple times, calls after first are ignored. + + SENTENCEG_Init(); + +// init texture type array from materials.txt + + TEXTURETYPE_Init(); + + +// the area based ambient sounds MUST be the first precache_sounds + +// player precaches + W_Precache (); // get weapon precaches + + ClientPrecache(); + +// 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) + PRECACHE_SOUND( "common/bodydrop4.wav" ); + + PRECACHE_SOUND( "r_tele1.wav" ); // respawn sound + PRECACHE_SOUND( "scream1.wav" ); // falling scream sound + PRECACHE_SOUND( "scream2.wav" ); // falling scream sound + PRECACHE_SOUND( "scream3.wav" ); // falling scream sound + PRECACHE_SOUND( "decap.wav" ); // decapitation sound + PRECACHE_SOUND( "shatter.wav" ); // freeze decapitation sound + PRECACHE_MODEL( "models/head.mdl" ); // head + + g_Language = (int)CVAR_GET_FLOAT( "sv_language" ); + if ( g_Language == LANGUAGE_GERMAN ) + { + PRECACHE_MODEL( "models/germangibs.mdl" ); + } + else + { + PRECACHE_MODEL( "models/hgibs.mdl" ); + 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"); + + // 1 FLICKER (first variety) + LIGHT_STYLE(1, "mmnmmommommnonmmonqnmmo"); + + // 2 SLOW STRONG PULSE + LIGHT_STYLE(2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"); + + // 3 CANDLE (first variety) + LIGHT_STYLE(3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg"); + + // 4 FAST STROBE + LIGHT_STYLE(4, "mamamamamama"); + + // 5 GENTLE PULSE 1 + LIGHT_STYLE(5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj"); + + // 6 FLICKER (second variety) + LIGHT_STYLE(6, "nmonqnmomnmomomno"); + + // 7 CANDLE (second variety) + LIGHT_STYLE(7, "mmmaaaabcdefgmmmmaaaammmaamm"); + + // 8 CANDLE (third variety) + LIGHT_STYLE(8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa"); + + // 9 SLOW STROBE (fourth variety) + LIGHT_STYLE(9, "aaaaaaaazzzzzzzz"); + + // 10 FLUORESCENT FLICKER + LIGHT_STYLE(10, "mmamammmmammamamaaamammma"); + + // 11 SLOW PULSE NOT FADE TO BLACK + LIGHT_STYLE(11, "abcdefghijklmnopqrrqponmlkjihgfedcba"); + + // 12 UNDERWATER LIGHT MUTATION + // this light only distorts the lightmap - no contribution + // is made to the brightness of affected surfaces + LIGHT_STYLE(12, "mmnnmmnnnmmnn"); + + // styles 32-62 are assigned by the light program for switchable lights + + // 63 testing + LIGHT_STYLE(63, "a"); + + for ( int i = 0; i < static_cast(ARRAYSIZE(gDecals)); i++ ) + gDecals[i].index = DECAL_INDEX( gDecals[i].name ); + +// init the WorldGraph. + WorldGraph.InitGraph(); + +// make sure the .NOD file is newer than the .BSP file. + if ( !WorldGraph.CheckNODFile ( ( char * )STRING( gpGlobals->mapname ) ) ) + {// NOD file is not present, or is older than the BSP file. + WorldGraph.AllocNodes (); + } + else + {// Load the node graph for this level + if ( !WorldGraph.FLoadGraph ( (char *)STRING( gpGlobals->mapname ) ) ) + {// couldn't load, so alloc and prepare to build a graph. + ALERT ( at_console, "*Error opening .NOD file\n" ); + WorldGraph.AllocNodes (); + } + else + { + ALERT ( at_console, "\n*Graph Loaded!\n" ); + } + } + + if ( pev->speed > 0 ) + CVAR_SET_FLOAT( "sv_zmax", pev->speed ); + else + CVAR_SET_FLOAT( "sv_zmax", 4096 ); + + if ( pev->netname ) + { + ALERT( at_aiconsole, "Chapter title: %s\n", STRING(pev->netname) ); + CBaseEntity *pEntity = CBaseEntity::Create( "env_message", g_vecZero, g_vecZero, NULL ); + if ( pEntity ) + { + pEntity->SetThink( &CBaseEntity::SUB_CallUseToggle ); + pEntity->pev->message = pev->netname; + pev->netname = 0; + pEntity->pev->nextthink = gpGlobals->time + 0.3; + pEntity->pev->spawnflags = SF_MESSAGE_ONCE; + } + } + + if ( pev->spawnflags & SF_WORLD_DARK ) + CVAR_SET_FLOAT( "v_dark", 1.0 ); + else + CVAR_SET_FLOAT( "v_dark", 0.0 ); + + if ( pev->spawnflags & SF_WORLD_TITLE ) + gDisplayTitle = TRUE; // display the game title if this key is set + else + gDisplayTitle = FALSE; + + if ( pev->spawnflags & SF_WORLD_FORCETEAM ) + { + CVAR_SET_FLOAT( "mp_defaultteam", 1 ); + } + else + { + CVAR_SET_FLOAT( "mp_defaultteam", 0 ); + } + + // Discwar + if ( g_iPlayersPerTeam < 1 ) + g_iPlayersPerTeam = static_cast(CVAR_GET_FLOAT("rc_playersperteam")); +} + + +// +// Just to ignore the "wad" field. +// +void CWorld :: KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "skyname") ) + { + // Sent over net now. + CVAR_SET_STRING( "sv_skyname", pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "sounds") ) + { + gpGlobals->cdAudioTrack = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "WaveHeight") ) + { + // Sent over net now. + pev->scale = atof(pkvd->szValue) * (1.0/8.0); + pkvd->fHandled = TRUE; + CVAR_SET_FLOAT( "sv_wateramp", pev->scale ); + } + else if ( FStrEq(pkvd->szKeyName, "MaxRange") ) + { + pev->speed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "chaptertitle") ) + { + pev->netname = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "startdark") ) + { + // UNDONE: This is a gross hack!!! The CVAR is NOT sent over the client/sever link + // but it will work for single player + int flag = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + if ( flag ) + pev->spawnflags |= SF_WORLD_DARK; + } + else if ( FStrEq(pkvd->szKeyName, "newunit") ) + { + // Single player only. Clear save directory if set + if ( atoi(pkvd->szValue) ) + CVAR_SET_FLOAT( "sv_newunit", 1 ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "gametitle") ) + { + if ( atoi(pkvd->szValue) ) + pev->spawnflags |= SF_WORLD_TITLE; + + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "mapteams") ) + { + pev->team = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "defaultteam") ) + { + if ( atoi(pkvd->szValue) ) + { + pev->spawnflags |= SF_WORLD_FORCETEAM; + } + pkvd->fHandled = TRUE; + } + + // Discwar + else if ( FStrEq(pkvd->szKeyName, "playersperteam") ) + { + g_iPlayersPerTeam = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "no_arena") ) + { + if ( atoi(pkvd->szValue) ) + m_iArenaOff = TRUE; + pkvd->fHandled = TRUE; + } + + else + CBaseEntity::KeyValue( pkvd ); +} diff --git a/hlsdk/ricochet/dlls/wpn_shared/disc_weapon_disc.cpp b/hlsdk/ricochet/dlls/wpn_shared/disc_weapon_disc.cpp new file mode 100644 index 00000000..2c32114a --- /dev/null +++ b/hlsdk/ricochet/dlls/wpn_shared/disc_weapon_disc.cpp @@ -0,0 +1,750 @@ + +//=========== (C) Copyright 1999 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: Weapon functionality for Discwar +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "effects.h" +#include "discwar.h" +#include "disc_objects.h" +#include "disc_arena.h" + +// Disc trail colors +float g_iaDiscColors[33][3] = +{ + { 255, 255, 255, }, + { 250, 0, 0 }, + { 0, 0, 250 }, + { 0, 250, 0 }, + { 128, 128, 0 }, + { 128, 0, 128 }, + { 0, 128, 128 }, + { 128, 128, 128 }, + { 64, 128, 0 }, + { 128, 64, 0 }, + { 128, 0, 64 }, + { 64, 0, 128 }, + { 0, 64, 128 }, + { 64, 64, 128 }, + { 128, 64, 64 }, + { 64, 128, 64 }, + { 128, 128, 64 }, + { 128, 64, 128 }, + { 64, 128, 128 }, + { 250, 128, 0 }, + { 128, 250, 0 }, + { 128, 0, 250 }, + { 250, 0, 128 }, + { 0, 250, 128 }, + { 250, 250, 128 }, + { 250, 128, 250 }, + { 128, 250, 250 }, + { 250, 128, 64 }, + { 250, 64, 128 }, + { 128, 250, 64 }, + { 64, 128, 250 }, + { 128, 64, 250 }, +}; + +enum disc_e +{ + DISC_IDLE = 0, + DISC_FIDGET, + DISC_PINPULL, + DISC_THROW1, // toss + DISC_THROW2, // medium + DISC_THROW3, // hard + DISC_HOLSTER, + DISC_DRAW +}; + +#include "disc_weapon.h" + +LINK_ENTITY_TO_CLASS( weapon_disc, CDiscWeapon ); + +#if !defined( CLIENT_DLL ) +LINK_ENTITY_TO_CLASS( disc, CDisc ); + +//======================================================================================== +// DISC +//======================================================================================== +void CDisc::Spawn( void ) +{ + Precache( ); + + pev->classname = MAKE_STRING("disc"); + pev->movetype = MOVETYPE_BOUNCEMISSILE; + pev->solid = SOLID_TRIGGER; + + // Setup model + if ( m_iPowerupFlags & POW_HARD ) + SET_MODEL(ENT(pev), "models/disc_hard.mdl"); + else + SET_MODEL(ENT(pev), "models/disc.mdl"); + UTIL_SetSize(pev, Vector( -4,-4,-4 ), Vector(4, 4, 4)); + + UTIL_SetOrigin( pev, pev->origin ); + SetTouch( &CDisc::DiscTouch ); + SetThink( &CDisc::DiscThink ); + + m_iBounces = 0; + m_fDontTouchOwner = gpGlobals->time + 0.2; + m_fDontTouchEnemies = 0; + m_bRemoveSelf = false; + m_bTeleported = false; + m_pLockTarget = NULL; + + UTIL_MakeVectors( pev->angles ); + + // Fast powerup makes discs go faster + if ( m_iPowerupFlags & POW_FAST ) + pev->velocity = gpGlobals->v_forward * DISC_VELOCITY * 1.5; + else + pev->velocity = gpGlobals->v_forward * DISC_VELOCITY; + + // Pull our owner out so we will still touch it + if ( pev->owner ) + m_hOwner = Instance(pev->owner); + pev->owner = NULL; + + // Trail + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMFOLLOW ); + WRITE_SHORT(entindex()); // entity + WRITE_SHORT(m_iTrail ); // model + + if (m_bDecapitate) + WRITE_BYTE( 5 ); // life + else + WRITE_BYTE( 3 ); // life + + WRITE_BYTE( 5 ); // width + + WRITE_BYTE( static_cast(g_iaDiscColors[pev->team][0]) ); // r, g, b + WRITE_BYTE( static_cast(g_iaDiscColors[pev->team][1]) ); // r, g, b + WRITE_BYTE( static_cast(g_iaDiscColors[pev->team][2]) ); // r, g, b + + WRITE_BYTE( 250 ); // brightness + MESSAGE_END(); + + // Decapitator's make sound + if (m_bDecapitate) + EMIT_SOUND( ENT(pev), CHAN_VOICE, "weapons/rocket1.wav", 0.5, 0.5 ); + + // Highlighter + pev->renderfx = kRenderFxGlowShell; + for (int i = 0; i <= 2;i ++) + pev->rendercolor[i] = g_iaDiscColors[pev->team][i]; + pev->renderamt = 100; + + pev->nextthink = gpGlobals->time + 0.1; +} + +void CDisc::Precache( void ) +{ + PRECACHE_MODEL("models/disc.mdl"); + PRECACHE_MODEL("models/disc_hard.mdl"); + PRECACHE_SOUND("weapons/cbar_hitbod1.wav"); + PRECACHE_SOUND("weapons/cbar_hitbod2.wav"); + PRECACHE_SOUND("weapons/cbar_hitbod3.wav"); + PRECACHE_SOUND("weapons/altfire.wav"); + PRECACHE_SOUND("items/gunpickup2.wav"); + PRECACHE_SOUND("weapons/electro5.wav"); + PRECACHE_SOUND("weapons/xbow_hit1.wav"); + PRECACHE_SOUND("weapons/xbow_hit2.wav"); + PRECACHE_SOUND("weapons/rocket1.wav"); + PRECACHE_SOUND("dischit.wav"); + m_iTrail = PRECACHE_MODEL("sprites/smoke.spr"); + m_iSpriteTexture = PRECACHE_MODEL( "sprites/lgtning.spr" ); +} + +/* +void CDisc::SetObjectCollisionBox( void ) +{ + pev->absmin = pev->origin + Vector( -8, -8, 8 ); + pev->absmax = pev->origin + Vector( 8, 8, 8 ); +} +*/ + +// Give the disc back to it's owner +void CDisc::ReturnToThrower( void ) +{ + if (m_bDecapitate) + { + STOP_SOUND( edict(), CHAN_VOICE, "weapons/rocket1.wav" ); + if ( !m_bRemoveSelf ) + ((CBasePlayer*)(CBaseEntity*)m_hOwner)->GiveAmmo( MAX_DISCS, "disc", MAX_DISCS ); + } + else + { + if ( !m_bRemoveSelf ) + ((CBasePlayer*)(CBaseEntity*)m_hOwner)->GiveAmmo( 1, "disc", MAX_DISCS ); + } + + UTIL_Remove( this ); +} + +void CDisc::DiscTouch ( CBaseEntity *pOther ) +{ + // Push players backwards + if ( pOther->IsPlayer() ) + { + if ( ((CBaseEntity*)m_hOwner) == pOther ) + { + if (m_fDontTouchOwner < gpGlobals->time) + { + // Play catch sound + EMIT_SOUND_DYN( pOther->edict(), CHAN_WEAPON, "items/gunpickup2.wav", 1.0, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + + ReturnToThrower(); + } + + return; + } + else if ( m_fDontTouchEnemies < gpGlobals->time) + { + if ( pev->team != pOther->pev->team ) + { + ((CBasePlayer*)pOther)->m_LastHitGroup = HITGROUP_GENERIC; + + // Do freeze seperately so you can freeze and shatter a person with a single shot + if ( m_iPowerupFlags & POW_FREEZE && ((CBasePlayer*)pOther)->m_iFrozen == FALSE ) + { + // Freeze the player and make them glow blue + EMIT_SOUND_DYN( pOther->edict(), CHAN_WEAPON, "weapons/electro5.wav", 1.0, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + ((CBasePlayer*)pOther)->Freeze(); + + // If it's not a decap, return now. If it's a decap, continue to shatter + if ( !m_bDecapitate ) + { + m_fDontTouchEnemies = gpGlobals->time + 2.0; + return; + } + } + + // Decap or push + if (m_bDecapitate) + { + // Decapitate! + if ( m_bTeleported ) + ((CBasePlayer*)pOther)->m_flLastDiscHitTeleport = gpGlobals->time; + ((CBasePlayer*)pOther)->Decapitate( ((CBaseEntity*)m_hOwner)->pev ); + + m_fDontTouchEnemies = gpGlobals->time + 0.5; + } + else + { + // Play thwack sound + switch( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND_DYN( pOther->edict(), CHAN_ITEM, "weapons/cbar_hitbod1.wav", 1.0, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + break; + case 1: + EMIT_SOUND_DYN( pOther->edict(), CHAN_ITEM, "weapons/cbar_hitbod2.wav", 1.0, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + break; + case 2: + EMIT_SOUND_DYN( pOther->edict(), CHAN_ITEM, "weapons/cbar_hitbod3.wav", 1.0, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + break; + } + + // Push the player + Vector vecDir = pev->velocity.Normalize(); + pOther->pev->flags &= ~FL_ONGROUND; + ((CBasePlayer*)pOther)->m_vecHitVelocity = vecDir * DISC_PUSH_MULTIPLIER; + + // Shield flash only if the player isnt frozen + if ( ((CBasePlayer*)pOther)->m_iFrozen == false ) + { + pOther->pev->renderfx = kRenderFxGlowShell; + pOther->pev->rendercolor.x = 255; + pOther->pev->renderamt = 150; + } + + ((CBasePlayer*)pOther)->m_hLastPlayerToHitMe = m_hOwner; + ((CBasePlayer*)pOther)->m_flLastDiscHit = gpGlobals->time; + ((CBasePlayer*)pOther)->m_flLastDiscBounces = m_iBounces; + if ( m_bTeleported ) + ((CBasePlayer*)pOther)->m_flLastDiscHitTeleport = gpGlobals->time; + + m_fDontTouchEnemies = gpGlobals->time + 2.0; + } + } + } + } + // Hit a disc? + else if ( pOther->pev->iuser4 ) + { + // Enemy Discs destroy each other + if ( pOther->pev->iuser4 != pev->iuser4 ) + { + // Play a warp sound and sprite + CSprite *pSprite = CSprite::SpriteCreate( "sprites/discreturn.spr", pev->origin, TRUE ); + pSprite->AnimateAndDie( 60 ); + pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); + pSprite->SetScale( 1 ); + EMIT_SOUND_DYN( edict(), CHAN_ITEM, "dischit.wav", 1.0, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + + // Return both discs to their owners + ((CDisc*)pOther)->ReturnToThrower(); + ReturnToThrower(); + } + else + { + // Friendly discs just pass through each other + } + } + else + { + m_iBounces++; + + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: EMIT_SOUND_DYN( edict(), CHAN_ITEM, "weapons/xbow_hit1.wav", 1.0, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); break; + case 1: EMIT_SOUND_DYN( edict(), CHAN_ITEM, "weapons/xbow_hit2.wav", 1.0, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); break; + } + + UTIL_Sparks( pev->origin, edict() ); + } +} + +void CDisc::DiscThink() +{ + // Make Freeze discs home towards any player ahead of them + if ( (m_iPowerupFlags & POW_FREEZE) && (m_iBounces == 0) ) + { + // Use an existing target if he's still in the view cone + if ( m_pLockTarget != 0 ) + { + Vector vecDir = (m_pLockTarget->pev->origin - pev->origin).Normalize(); + UTIL_MakeVectors( pev->angles ); + float flDot = DotProduct( gpGlobals->v_forward, vecDir ); + if ( flDot < 0.6 ) + m_pLockTarget = NULL; + } + + // Get a new target if we don't have one + if ( m_pLockTarget == 0 ) + { + CBaseEntity *pOther = NULL; + + // Examine all entities within a reasonable radius + while ((pOther = UTIL_FindEntityByClassname( pOther, "player" )) != NULL) + { + // Skip the guy who threw this + if ( ((CBaseEntity*)m_hOwner) == pOther ) + continue; + // Skip observers + if ( ((CBasePlayer*)pOther)->IsObserver() ) + continue; + + // Make sure the enemy's in a cone ahead of us + Vector vecDir = (pOther->pev->origin - pev->origin).Normalize(); + UTIL_MakeVectors( pev->angles ); + float flDot = DotProduct( gpGlobals->v_forward, vecDir ); + if ( flDot > 0.6 ) + { + m_pLockTarget = pOther; + break; + } + } + } + + // Track towards our target + if ( m_pLockTarget != 0 ) + { + // Calculate new velocity + Vector vecDir = (m_pLockTarget->pev->origin - pev->origin).Normalize(); + pev->velocity = ( pev->velocity.Normalize() + (vecDir.Normalize() * 0.25)).Normalize(); + pev->velocity = pev->velocity * DISC_VELOCITY; + pev->angles = UTIL_VecToAngles( pev->velocity ); + } + } + + // Track the player if we've bounced 3 or more times ( Fast discs remove immediately ) + if ( m_iBounces >= 3 || (m_iPowerupFlags & POW_FAST && m_iBounces >= 1) ) + { + // Remove myself if my owner's died + if (m_bRemoveSelf) + { + STOP_SOUND( edict(), CHAN_VOICE, "weapons/rocket1.wav" ); + UTIL_Remove( this ); + return; + } + + // 7 Bounces, just remove myself + if ( m_iBounces > 7 ) + { + ReturnToThrower(); + return; + } + + // Start heading for the player + if ( m_hOwner ) + { + Vector vecDir = ( m_hOwner->pev->origin - pev->origin ); + vecDir = vecDir.Normalize(); + pev->velocity = vecDir * DISC_VELOCITY; + pev->nextthink = gpGlobals->time + 0.1; + } + else + { + UTIL_Remove( this ); + } + } + + // Sanity check + if ( pev->velocity == g_vecZero ) + ReturnToThrower(); + + pev->nextthink = gpGlobals->time + 0.1; +} + +CDisc *CDisc::CreateDisc( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner, CDiscWeapon *pLauncher, bool bDecapitator, int iPowerupFlags ) +{ + CDisc *pDisc = GetClassPtr( (CDisc *)NULL ); + + UTIL_SetOrigin( pDisc->pev, vecOrigin ); + pDisc->m_iPowerupFlags = iPowerupFlags; + // Hard shots always decapitate + if ( pDisc->m_iPowerupFlags & POW_HARD ) + pDisc->m_bDecapitate = TRUE; + else + pDisc->m_bDecapitate = bDecapitator; + + pDisc->pev->angles = vecAngles; + pDisc->pev->owner = pOwner->edict(); + pDisc->pev->team = pOwner->pev->team; + pDisc->pev->iuser4 = pOwner->pev->iuser4; + + // Set the Group Info + pDisc->pev->groupinfo = pOwner->pev->groupinfo; + + pDisc->m_pLauncher = pLauncher; + + pDisc->Spawn(); + + return pDisc; +} +#endif // !CLIENT_DLL + +//======================================================================================== +// DISC WEAPON +//======================================================================================== +void CDiscWeapon::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_DISC; + SET_MODEL(ENT(pev), "models/disc.mdl"); + +#if !defined( CLIENT_DLL ) + pev->dmg = gSkillData.plrDmgHandGrenade; +#endif + + m_iDefaultAmmo = STARTING_DISCS; + m_iFastShotDiscs = NUM_FASTSHOT_DISCS; + + FallInit();// get ready to fall down. +} + + +void CDiscWeapon::Precache( void ) +{ + PRECACHE_MODEL("models/disc.mdl"); + PRECACHE_MODEL("models/disc_hard.mdl"); + PRECACHE_MODEL("models/v_disc.mdl"); + PRECACHE_MODEL("models/p_disc.mdl"); + PRECACHE_SOUND("weapons/cbar_miss1.wav"); + m_iSpriteTexture = PRECACHE_MODEL( "sprites/lgtning.spr" ); + + m_usFireDisc = PRECACHE_EVENT( 1, "events/firedisc.sc" ); +} + +int CDiscWeapon::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "disc"; + p->iMaxAmmo1 = MAX_DISCS; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 4; + p->iPosition = 0; + p->iId = WEAPON_DISC; + p->iWeight = 100; + p->iFlags = ITEM_FLAG_NOAUTORELOAD | ITEM_FLAG_NOAUTOSWITCHEMPTY; + + return 1; +} + + +BOOL CDiscWeapon::Deploy( ) +{ + return DefaultDeploy( "models/v_disc.mdl", "models/p_disc.mdl", DISC_DRAW, "crowbar" ); +} + +BOOL CDiscWeapon::CanHolster( void ) +{ + return TRUE; +} + +void CDiscWeapon::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + SendWeaponAnim( DISC_HOLSTER, 1 ); + } + else + { + // no more grenades! + m_pPlayer->pev->weapons &= ~(1<nextthink = gpGlobals->time + 0.1; + } + + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "common/null.wav", 1.0, ATTN_NORM); +} + +CDisc *CDiscWeapon::FireDisc( bool bDecapitator ) +{ + CDisc *pReturnDisc = NULL; + + SendWeaponAnim( DISC_THROW1, 1 ); + + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + +#if !defined( CLIENT_DLL ) + Vector vecFireDir = g_vecZero; + vecFireDir[1] = m_pPlayer->pev->v_angle[1]; + UTIL_MakeVectors( vecFireDir ); + Vector vecSrc = m_pPlayer->pev->origin + (m_pPlayer->pev->view_ofs * 0.25) + gpGlobals->v_forward * 16; + CDisc *pDisc = CDisc::CreateDisc( vecSrc, vecFireDir, m_pPlayer, this, bDecapitator, m_pPlayer->m_iPowerups ); + pReturnDisc = pDisc; + + // Triple shot fires 2 more disks + if ( m_pPlayer->HasPowerup( POW_TRIPLE ) ) + { + // The 2 extra discs from triple shot are removed after their 3rd bounce + vecFireDir[1] = m_pPlayer->pev->v_angle[1] - 7; + UTIL_MakeVectors( vecFireDir ); + vecSrc = m_pPlayer->pev->origin + (m_pPlayer->pev->view_ofs * 0.25) + gpGlobals->v_forward * 16; + pDisc = CDisc::CreateDisc( vecSrc, vecFireDir, m_pPlayer, this, bDecapitator, POW_TRIPLE ); + pDisc->m_bRemoveSelf = true; + + vecFireDir[1] = m_pPlayer->pev->v_angle[1] + 7; + UTIL_MakeVectors( vecFireDir ); + vecSrc = m_pPlayer->pev->origin + (m_pPlayer->pev->view_ofs * 0.25) + gpGlobals->v_forward * 16; + pDisc = CDisc::CreateDisc( vecSrc, vecFireDir, m_pPlayer, this, bDecapitator, POW_TRIPLE ); + pDisc->m_bRemoveSelf = true; + } + +#endif + + // Fast shot allows faster throwing + float flTimeToNextShot = 0.5; + if ( m_pPlayer->HasPowerup( POW_FAST ) ) + flTimeToNextShot = 0.2; + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + flTimeToNextShot; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + flTimeToNextShot; + + return pReturnDisc; +} + +void CDiscWeapon::PrimaryAttack() +{ +#if !defined( CLIENT_DLL ) + if ( m_pPlayer->m_pCurrentArena ) + { + if ( m_pPlayer->m_pCurrentArena->AllowedToFire() == false ) + return; + } +#endif + + if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] ) + { + PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), m_usFireDisc, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0 ); + CDisc *pDisc = FireDisc( false ); + + // Fast powerup has a number of discs per 1 normal disc + if ( m_pPlayer->HasPowerup( POW_FAST ) ) + { + m_iFastShotDiscs--; + if ( m_iFastShotDiscs ) + { + // Make this disc remove itself + pDisc->m_bRemoveSelf = true; + return; + } + + m_iFastShotDiscs = NUM_FASTSHOT_DISCS; + } + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + // If we have powered discs, remove one + if ( m_pPlayer->m_iPowerupDiscs ) + { + m_pPlayer->m_iPowerupDiscs--; + if ( !m_pPlayer->m_iPowerupDiscs ) + m_pPlayer->RemoveAllPowerups(); + } + } +} + +void CDiscWeapon::SecondaryAttack() +{ +#if !defined( CLIENT_DLL ) + if ( m_pPlayer->m_pCurrentArena ) + { + if ( m_pPlayer->m_pCurrentArena->AllowedToFire() == false ) + return; + } +#endif + + // Fast powerup has a number of discs per 1 normal disc (so it can throw a decap when it has at least 1 real disc) + if ( (m_pPlayer->HasPowerup( POW_FAST ) && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] > 0 ) || + ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] == MAX_DISCS ) ) + { + PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), m_usFireDisc, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 1, 0 ); + + FireDisc( true ); + + // Deduct MAX_DISCS from fast shot, or deduct all discs if we don't have fast shot + if ( m_pPlayer->HasPowerup( POW_FAST ) ) + { + for ( int i = 1; i <= MAX_DISCS; i++ ) + { + m_iFastShotDiscs--; + if ( m_iFastShotDiscs == 0 ) + { + m_iFastShotDiscs = NUM_FASTSHOT_DISCS; + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + // Remove a powered disc + m_pPlayer->m_iPowerupDiscs--; + if ( !m_pPlayer->m_iPowerupDiscs ) + m_pPlayer->RemoveAllPowerups(); + } + } + } + else + { + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] = 0; + + // If we have powered discs, remove one + if ( m_pPlayer->m_iPowerupDiscs ) + { + m_pPlayer->m_iPowerupDiscs--; + if ( !m_pPlayer->m_iPowerupDiscs ) + m_pPlayer->RemoveAllPowerups(); + } + } + } +} + + +void CDiscWeapon::WeaponIdle( void ) +{ +#if !defined( CLIENT_DLL ) + if ( m_pPlayer->HasPowerup(POW_VISUALIZE_REBOUNDS) ) + { + Vector vecFireDir = g_vecZero; + Vector vecSrc = m_pPlayer->pev->origin + (m_pPlayer->pev->view_ofs * 0.25); + vecFireDir[1] = m_pPlayer->pev->v_angle[1]; + + // Draw beams to show where rebounds will go + for (int i = 0; i < 3; i++) + { + TraceResult tr; + UTIL_MakeVectors( vecFireDir ); + UTIL_TraceLine( vecSrc, (vecSrc + gpGlobals->v_forward * 2048), ignore_monsters, ENT(pev), &tr ); + + 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( m_iSpriteTexture ); + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 1 ); // life + WRITE_BYTE( 40 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( i * 50 ); // r, g, b + WRITE_BYTE( i * 50 ); // r, g, b + WRITE_BYTE( 200 - (i * 50)); // r, g, b + WRITE_BYTE( 128 - (i * 30) ); // r, g, b + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + // Calculate rebound angle + Vector vecOut; + Vector vecIn = tr.vecEndPos - (tr.vecEndPos - (gpGlobals->v_forward * 5)); + float backoff = DotProduct( vecIn, tr.vecPlaneNormal ) * 2.0; + for (int i=0 ; i<3 ; i++) + { + float change = tr.vecPlaneNormal[i] * backoff; + vecOut[i] = vecIn[i] - change; + if (vecOut[i] > -0.1 && vecOut[i] < 0.1) + vecOut[i] = 0; + } + + vecOut = vecOut.Normalize(); + vecSrc = tr.vecEndPos; + vecFireDir = UTIL_VecToAngles(vecOut); + } + } +#endif + + if (m_flTimeWeaponIdle > UTIL_WeaponTimeBase()) + return; + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + int iAnim; + float flRand = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 0, 1 ); + if (flRand <= 0.75) + { + iAnim = DISC_IDLE; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 );// how long till we do this again. + } + else + { + iAnim = DISC_FIDGET; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 75.0 / 30.0; + } + + SendWeaponAnim( iAnim, 1 ); + } +} + +// Prevent disc weapons lying around on the ground +int CDiscWeapon::AddDuplicate( CBasePlayerItem *pOriginal ) +{ + pev->flags |= FL_KILLME; + return FALSE; +} diff --git a/hlsdk/ricochet/dlls/xen.cpp b/hlsdk/ricochet/dlls/xen.cpp new file mode 100644 index 00000000..2118f9b5 --- /dev/null +++ b/hlsdk/ricochet/dlls/xen.cpp @@ -0,0 +1,528 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "animation.h" +#include "effects.h" + + +#define XEN_PLANT_GLOW_SPRITE "sprites/flare3.spr" +#define XEN_PLANT_HIDE_TIME 5 + + +class CActAnimating : public CBaseAnimating +{ +public: + void SetActivity( Activity act ); + inline Activity GetActivity( void ) { return m_Activity; } + + virtual int ObjectCaps( void ) { return CBaseAnimating :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + Activity m_Activity; +}; + +TYPEDESCRIPTION CActAnimating::m_SaveData[] = +{ + DEFINE_FIELD( CActAnimating, m_Activity, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CActAnimating, CBaseAnimating ); + +void CActAnimating :: SetActivity( Activity act ) +{ + int sequence = LookupActivity( act ); + if ( sequence != ACTIVITY_NOT_AVAILABLE ) + { + pev->sequence = sequence; + m_Activity = act; + pev->frame = 0; + ResetSequenceInfo( ); + } +} + + + + +class CXenPLight : public CActAnimating +{ +public: + void Spawn( void ); + void Precache( void ); + void Touch( CBaseEntity *pOther ); + void Think( void ); + + void LightOn( void ); + void LightOff( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + CSprite *m_pGlow; +}; + +LINK_ENTITY_TO_CLASS( xen_plantlight, CXenPLight ); + +TYPEDESCRIPTION CXenPLight::m_SaveData[] = +{ + DEFINE_FIELD( CXenPLight, m_pGlow, FIELD_CLASSPTR ), +}; + +IMPLEMENT_SAVERESTORE( CXenPLight, CActAnimating ); + +void CXenPLight :: Spawn( void ) +{ + Precache(); + + SET_MODEL( ENT(pev), "models/light.mdl" ); + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_TRIGGER; + + UTIL_SetSize( pev, Vector(-80,-80,0), Vector(80,80,32)); + SetActivity( ACT_IDLE ); + pev->nextthink = gpGlobals->time + 0.1; + pev->frame = RANDOM_FLOAT(0,255); + + m_pGlow = CSprite::SpriteCreate( XEN_PLANT_GLOW_SPRITE, pev->origin + Vector(0,0,(pev->mins.z+pev->maxs.z)*0.5), FALSE ); + m_pGlow->SetTransparency( kRenderGlow, static_cast(pev->rendercolor.x), static_cast(pev->rendercolor.y), static_cast(pev->rendercolor.z), static_cast(pev->renderamt), static_cast(pev->renderfx) ); + m_pGlow->SetAttachment( edict(), 1 ); +} + + +void CXenPLight :: Precache( void ) +{ + PRECACHE_MODEL( "models/light.mdl" ); + PRECACHE_MODEL( XEN_PLANT_GLOW_SPRITE ); +} + + +void CXenPLight :: Think( void ) +{ + StudioFrameAdvance(); + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CXenPLight :: Touch( CBaseEntity *pOther ) +{ + if ( pOther->IsPlayer() ) + { + pev->dmgtime = gpGlobals->time + XEN_PLANT_HIDE_TIME; + } +} + + +void CXenPLight :: LightOn( void ) +{ + SUB_UseTargets( this, USE_ON, 0 ); + if ( m_pGlow ) + m_pGlow->pev->effects &= ~EF_NODRAW; +} + + +void CXenPLight :: LightOff( void ) +{ + SUB_UseTargets( this, USE_OFF, 0 ); + if ( m_pGlow ) + m_pGlow->pev->effects |= EF_NODRAW; +} + + + +class CXenHair : public CActAnimating +{ +public: + void Spawn( void ); + void Precache( void ); + void Think( void ); +}; + +LINK_ENTITY_TO_CLASS( xen_hair, CXenHair ); + +#define SF_HAIR_SYNC 0x0001 + +void CXenHair::Spawn( void ) +{ + Precache(); + SET_MODEL( edict(), "models/hair.mdl" ); + UTIL_SetSize( pev, Vector(-4,-4,0), Vector(4,4,32)); + pev->sequence = 0; + + if ( !(pev->spawnflags & SF_HAIR_SYNC) ) + { + pev->frame = RANDOM_FLOAT(0,255); + pev->framerate = RANDOM_FLOAT( 0.7, 1.4 ); + } + ResetSequenceInfo( ); + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->nextthink = gpGlobals->time + RANDOM_FLOAT( 0.1, 0.4 ); // Load balance these a bit +} + + +void CXenHair::Think( void ) +{ + StudioFrameAdvance(); + pev->nextthink = gpGlobals->time + 0.5; +} + + +void CXenHair::Precache( void ) +{ + PRECACHE_MODEL( "models/hair.mdl" ); +} + + +class CXenTreeTrigger : public CBaseEntity +{ +public: + void Touch( CBaseEntity *pOther ); + static CXenTreeTrigger *TriggerCreate( edict_t *pOwner, const Vector &position ); +}; +LINK_ENTITY_TO_CLASS( xen_ttrigger, CXenTreeTrigger ); + +CXenTreeTrigger *CXenTreeTrigger :: TriggerCreate( edict_t *pOwner, const Vector &position ) +{ + CXenTreeTrigger *pTrigger = GetClassPtr( (CXenTreeTrigger *)NULL ); + pTrigger->pev->origin = position; + pTrigger->pev->classname = MAKE_STRING("xen_ttrigger"); + pTrigger->pev->solid = SOLID_TRIGGER; + pTrigger->pev->movetype = MOVETYPE_NONE; + pTrigger->pev->owner = pOwner; + + return pTrigger; +} + + +void CXenTreeTrigger::Touch( CBaseEntity *pOther ) +{ + if ( pev->owner ) + { + CBaseEntity *pEntity = CBaseEntity::Instance(pev->owner); + pEntity->Touch( pOther ); + } +} + + +#define TREE_AE_ATTACK 1 + +class CXenTree : public CActAnimating +{ +public: + void Spawn( void ); + void Precache( void ); + void Touch( CBaseEntity *pOther ); + void Think( void ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { Attack(); return 0; } + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void Attack( void ); + int Classify( void ) { return CLASS_BARNACLE; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + static const char *pAttackHitSounds[]; + static const char *pAttackMissSounds[]; + +private: + CXenTreeTrigger *m_pTrigger; +}; + +LINK_ENTITY_TO_CLASS( xen_tree, CXenTree ); + +TYPEDESCRIPTION CXenTree::m_SaveData[] = +{ + DEFINE_FIELD( CXenTree, m_pTrigger, FIELD_CLASSPTR ), +}; + +IMPLEMENT_SAVERESTORE( CXenTree, CActAnimating ); + +void CXenTree :: Spawn( void ) +{ + Precache(); + + SET_MODEL( ENT(pev), "models/tree.mdl" ); + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_BBOX; + + pev->takedamage = DAMAGE_YES; + + UTIL_SetSize( pev, Vector(-30,-30,0), Vector(30,30,188)); + SetActivity( ACT_IDLE ); + pev->nextthink = gpGlobals->time + 0.1; + pev->frame = RANDOM_FLOAT(0,255); + pev->framerate = RANDOM_FLOAT( 0.7, 1.4 ); + + Vector triggerPosition; + UTIL_MakeVectorsPrivate( pev->angles, triggerPosition, NULL, NULL ); + triggerPosition = pev->origin + (triggerPosition * 64); + // Create the trigger + m_pTrigger = CXenTreeTrigger::TriggerCreate( edict(), triggerPosition ); + UTIL_SetSize( m_pTrigger->pev, Vector( -24, -24, 0 ), Vector( 24, 24, 128 ) ); +} + +const char *CXenTree::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CXenTree::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +void CXenTree :: Precache( void ) +{ + PRECACHE_MODEL( "models/tree.mdl" ); + PRECACHE_MODEL( XEN_PLANT_GLOW_SPRITE ); + PRECACHE_SOUND_ARRAY( pAttackHitSounds ); + PRECACHE_SOUND_ARRAY( pAttackMissSounds ); +} + + +void CXenTree :: Touch( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() && FClassnameIs( pOther->pev, "monster_bigmomma" ) ) + return; + + Attack(); +} + + +void CXenTree :: Attack( void ) +{ +} + + +void CXenTree :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case TREE_AE_ATTACK: + { + CBaseEntity *pList[8]; + BOOL sound = FALSE; + int count = UTIL_EntitiesInBox( pList, 8, m_pTrigger->pev->absmin, m_pTrigger->pev->absmax, FL_MONSTER|FL_CLIENT ); + Vector forward; + + UTIL_MakeVectorsPrivate( pev->angles, forward, NULL, NULL ); + + for ( int i = 0; i < count; i++ ) + { + if ( pList[i] != this ) + { + if ( pList[i]->pev->owner != edict() ) + { + sound = TRUE; + pList[i]->TakeDamage( pev, pev, 25, DMG_CRUSH | DMG_SLASH ); + pList[i]->pev->punchangle.x = 15; + pList[i]->pev->velocity = pList[i]->pev->velocity + forward * 100; + } + } + } + + if ( sound ) + { + EMIT_SOUND_ARRAY_DYN( CHAN_WEAPON, pAttackHitSounds ); + } + } + return; + } + + CActAnimating::HandleAnimEvent( pEvent ); +} + +void CXenTree :: Think( void ) +{ + float flInterval = StudioFrameAdvance(); + pev->nextthink = gpGlobals->time + 0.1; + DispatchAnimEvents( flInterval ); +} + + +// UNDONE: These need to smoke somehow when they take damage +// Touch behavior? +// Cause damage in smoke area + +// +// Spores +// +class CXenSpore : public CActAnimating +{ +public: + void Spawn( void ); + void Precache( void ); + void Touch( CBaseEntity *pOther ); + void Think( void ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { Attack(); return 0; } +// void HandleAnimEvent( MonsterEvent_t *pEvent ); + void Attack( void ) {} + + static const char *pModelNames[]; +}; + +class CXenSporeSmall : public CXenSpore +{ + void Spawn( void ); +}; + +class CXenSporeMed : public CXenSpore +{ + void Spawn( void ); +}; + +class CXenSporeLarge : public CXenSpore +{ + void Spawn( void ); + + static const Vector m_hullSizes[]; +}; + +// Fake collision box for big spores +class CXenHull : public CPointEntity +{ +public: + static CXenHull *CreateHull( CBaseEntity *source, const Vector &mins, const Vector &maxs, const Vector &offset ); + int Classify( void ) { return CLASS_BARNACLE; } +}; + +CXenHull *CXenHull :: CreateHull( CBaseEntity *source, const Vector &mins, const Vector &maxs, const Vector &offset ) +{ + CXenHull *pHull = GetClassPtr( (CXenHull *)NULL ); + + UTIL_SetOrigin( pHull->pev, source->pev->origin + offset ); + SET_MODEL( pHull->edict(), STRING(source->pev->model) ); + pHull->pev->solid = SOLID_BBOX; + pHull->pev->classname = MAKE_STRING("xen_hull"); + pHull->pev->movetype = MOVETYPE_NONE; + pHull->pev->owner = source->edict(); + UTIL_SetSize( pHull->pev, mins, maxs ); + pHull->pev->renderamt = 0; + pHull->pev->rendermode = kRenderTransTexture; + // pHull->pev->effects = EF_NODRAW; + + return pHull; +} + + +LINK_ENTITY_TO_CLASS( xen_spore_small, CXenSporeSmall ); +LINK_ENTITY_TO_CLASS( xen_spore_medium, CXenSporeMed ); +LINK_ENTITY_TO_CLASS( xen_spore_large, CXenSporeLarge ); +LINK_ENTITY_TO_CLASS( xen_hull, CXenHull ); + +void CXenSporeSmall::Spawn( void ) +{ + pev->skin = 0; + CXenSpore::Spawn(); + UTIL_SetSize( pev, Vector(-16,-16,0), Vector(16,16,64)); +} +void CXenSporeMed::Spawn( void ) +{ + pev->skin = 1; + CXenSpore::Spawn(); + UTIL_SetSize( pev, Vector(-40,-40,0), Vector(40,40,120)); +} + + +// I just eyeballed these -- fill in hulls for the legs +const Vector CXenSporeLarge::m_hullSizes[] = +{ + Vector( 90, -25, 0 ), + Vector( 25, 75, 0 ), + Vector( -15, -100, 0 ), + Vector( -90, -35, 0 ), + Vector( -90, 60, 0 ), +}; + +void CXenSporeLarge::Spawn( void ) +{ + pev->skin = 2; + CXenSpore::Spawn(); + UTIL_SetSize( pev, Vector(-48,-48,110), Vector(48,48,240)); + + Vector forward, right; + + UTIL_MakeVectorsPrivate( pev->angles, forward, right, NULL ); + + // Rotate the leg hulls into position + for ( int i = 0; i < static_cast(ARRAYSIZE(m_hullSizes)); i++ ) + CXenHull :: CreateHull( this, Vector(-12, -12, 0 ), Vector( 12, 12, 120 ), (m_hullSizes[i].x * forward) + (m_hullSizes[i].y * right) ); +} + +void CXenSpore :: Spawn( void ) +{ + Precache(); + + SET_MODEL( ENT(pev), pModelNames[pev->skin] ); + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_BBOX; + pev->takedamage = DAMAGE_YES; + +// SetActivity( ACT_IDLE ); + pev->sequence = 0; + pev->frame = RANDOM_FLOAT(0,255); + pev->framerate = RANDOM_FLOAT( 0.7, 1.4 ); + ResetSequenceInfo( ); + pev->nextthink = gpGlobals->time + RANDOM_FLOAT( 0.1, 0.4 ); // Load balance these a bit +} + +const char *CXenSpore::pModelNames[] = +{ + "models/fungus(small).mdl", + "models/fungus.mdl", + "models/fungus(large).mdl", +}; + + +void CXenSpore :: Precache( void ) +{ + PRECACHE_MODEL( (char *)pModelNames[pev->skin] ); +} + + +void CXenSpore :: Touch( CBaseEntity *pOther ) +{ +} + + +void CXenSpore :: Think( void ) +{ + StudioFrameAdvance(); + pev->nextthink = gpGlobals->time + 0.1; + +#if 0 + DispatchAnimEvents( flInterval ); + + switch( GetActivity() ) + { + default: + case ACT_IDLE: + break; + + } +#endif +} diff --git a/hlsdk/ricochet/pm_shared/pm_debug.c b/hlsdk/ricochet/pm_shared/pm_debug.c new file mode 100644 index 00000000..3b0879c7 --- /dev/null +++ b/hlsdk/ricochet/pm_shared/pm_debug.c @@ -0,0 +1,322 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include "mathlib.h" +#include "const.h" +#include "usercmd.h" +#include "pm_defs.h" +#include "pm_shared.h" +#include "pm_movevars.h" +#include "pm_debug.h" + +#include + +#ifdef _MSC_VER +#pragma warning(disable : 4244) +#pragma warning(disable : 4305) +#endif + +extern playermove_t *pmove; + +// Expand debugging BBOX particle hulls by this many units. +#define BOX_GAP 0.0f + +static int PM_boxpnt[6][4] = +{ + { 0, 4, 6, 2 }, // +X + { 0, 1, 5, 4 }, // +Y + { 0, 2, 3, 1 }, // +Z + { 7, 5, 1, 3 }, // -X + { 7, 3, 2, 6 }, // -Y + { 7, 6, 4, 5 }, // -Z +}; + +void PM_ShowClipBox( void ) +{ +#if defined( _DEBUG ) + vec3_t org; + vec3_t offset = { 0, 0, 0 }; + + if ( !pmove->runfuncs ) + return; + + // More debugging, draw the particle bbox for player and for the entity we are looking directly at. + // aslo prints entity info to the console overlay. + //if ( !pmove->server ) + // return; + + // Draw entity in center of view + // Also draws the normal to the clip plane that intersects our movement ray. Leaves a particle + // trail at the intersection point. + PM_ViewEntity(); + + VectorCopy( pmove->origin, org ); + + if ( pmove->server ) + { + VectorAdd( org, offset, org ); + } + else + { + VectorSubtract( org, offset, org ); + } + + // Show our BBOX in particles. + PM_DrawBBox( pmove->player_mins[pmove->usehull], pmove->player_maxs[pmove->usehull], org, pmove->server ? 132 : 0, 0.1 ); + + PM_ParticleLine( org, org, pmove->server ? 132 : 0, 0.1, 5.0 ); +/* + { + int i; + for ( i = 0; i < pmove->numphysent; i++ ) + { + if ( pmove->physents[ i ].info >= 1 && pmove->physents[ i ].info <= 4 ) + { + PM_DrawBBox( pmove->player_mins[pmove->usehull], pmove->player_maxs[pmove->usehull], pmove->physents[i].origin, 132, 0.1 ); + } + } + } +*/ +#endif +} + +/* +=============== +PM_ParticleLine(vec3_t start, vec3_t end, int color, float life) + +================ +*/ +void PM_ParticleLine(vec3_t start, vec3_t end, int pcolor, float life, float vert) +{ + float linestep = 2.0f; + float curdist; + float len; + vec3_t curpos; + vec3_t diff; + int i; + // Determine distance; + + VectorSubtract(end, start, diff); + + len = VectorNormalize(diff); + + curdist = 0; + while (curdist <= len) + { + for (i = 0; i < 3; i++) + curpos[i] = start[i] + curdist * diff[i]; + + pmove->PM_Particle( curpos, pcolor, life, 0, vert); + curdist += linestep; + } + +} + +/* +================ +PM_DrawRectangle(vec3_t tl, vec3_t br) + +================ +*/ +void PM_DrawRectangle(vec3_t tl, vec3_t bl, vec3_t tr, vec3_t br, int pcolor, float life) +{ + PM_ParticleLine(tl, bl, pcolor, life, 0); + PM_ParticleLine(bl, br, pcolor, life, 0); + PM_ParticleLine(br, tr, pcolor, life, 0); + PM_ParticleLine(tr, tl, pcolor, life, 0); +} + +/* +================ +PM_DrawPhysEntBBox(int num) + +================ +*/ +void PM_DrawPhysEntBBox(int num, int pcolor, float life) +{ + physent_t *pe; + vec3_t org; + int j; + vec3_t tmp; + vec3_t p[8]; + float gap = BOX_GAP; + vec3_t modelmins, modelmaxs; + + if (num >= pmove->numphysent || + num <= 0) + return; + + pe = &pmove->physents[num]; + + if (pe->model) + { + VectorCopy(pe->origin, org); + + pmove->PM_GetModelBounds( pe->model, modelmins, modelmaxs ); + for (j = 0; j < 8; j++) + { + tmp[0] = (j & 1) ? modelmins[0] - gap : modelmaxs[0] + gap; + tmp[1] = (j & 2) ? modelmins[1] - gap : modelmaxs[1] + gap; + tmp[2] = (j & 4) ? modelmins[2] - gap : modelmaxs[2] + gap; + + VectorCopy(tmp, p[j]); + } + + // If the bbox should be rotated, do that + if (pe->angles[0] || pe->angles[1] || pe->angles[2]) + { + vec3_t forward, right, up; + + AngleVectorsTranspose(pe->angles, forward, right, up); + for (j = 0; j < 8; j++) + { + VectorCopy(p[j], tmp); + p[j][0] = DotProduct ( tmp, forward ); + p[j][1] = DotProduct ( tmp, right ); + p[j][2] = DotProduct ( tmp, up ); + } + } + + // Offset by entity origin, if any. + for (j = 0; j < 8; j++) + VectorAdd(p[j], org, p[j]); + + for (j = 0; j < 6; j++) + { + PM_DrawRectangle( + p[PM_boxpnt[j][1]], + p[PM_boxpnt[j][0]], + p[PM_boxpnt[j][2]], + p[PM_boxpnt[j][3]], + pcolor, life); + } + } + else + { + for (j = 0; j < 8; j++) + { + tmp[0] = (j & 1) ? pe->mins[0] : pe->maxs[0]; + tmp[1] = (j & 2) ? pe->mins[1] : pe->maxs[1]; + tmp[2] = (j & 4) ? pe->mins[2] : pe->maxs[2]; + + VectorAdd(tmp, pe->origin, tmp); + VectorCopy(tmp, p[j]); + } + + for (j = 0; j < 6; j++) + { + PM_DrawRectangle( + p[PM_boxpnt[j][1]], + p[PM_boxpnt[j][0]], + p[PM_boxpnt[j][2]], + p[PM_boxpnt[j][3]], + pcolor, life); + } + + } +} + +/* +================ +PM_DrawBBox(vec3_t mins, vec3_t maxs, vec3_t origin, int pcolor, float life) + +================ +*/ +void PM_DrawBBox(vec3_t mins, vec3_t maxs, vec3_t origin, int pcolor, float life) +{ + int j; + + vec3_t tmp; + vec3_t p[8]; + float gap = BOX_GAP; + + for (j = 0; j < 8; j++) + { + tmp[0] = (j & 1) ? mins[0] - gap : maxs[0] + gap; + tmp[1] = (j & 2) ? mins[1] - gap : maxs[1] + gap ; + tmp[2] = (j & 4) ? mins[2] - gap : maxs[2] + gap ; + + VectorAdd(tmp, origin, tmp); + VectorCopy(tmp, p[j]); + } + + for (j = 0; j < 6; j++) + { + PM_DrawRectangle( + p[PM_boxpnt[j][1]], + p[PM_boxpnt[j][0]], + p[PM_boxpnt[j][2]], + p[PM_boxpnt[j][3]], + pcolor, life); + } +} + + +#ifndef DEDICATED + +/* +================ +PM_ViewEntity + +Shows a particle trail from player to entity in crosshair. +Shows particles at that entities bbox + +Tries to shoot a ray out by about 128 units. +================ +*/ +void PM_ViewEntity( void ) +{ + vec3_t forward, right, up; + float raydist = 256.0f; + vec3_t origin; + vec3_t end; + int i; + pmtrace_t trace; + int pcolor = 77; + float fup; + +#if 0 + if ( !pm_showclip.value ) + return; +#endif + + AngleVectors (pmove->angles, forward, right, up); // Determine movement angles + + VectorCopy( pmove->origin, origin); + + fup = 0.5*( pmove->player_mins[pmove->usehull][2] + pmove->player_maxs[pmove->usehull][2] ); + fup += pmove->view_ofs[2]; + fup -= 4; + + for (i = 0; i < 3; i++) + { + end[i] = origin[i] + raydist * forward[i]; + } + + trace = pmove->PM_PlayerTrace( origin, end, PM_STUDIO_BOX, -1 ); + + if (trace.ent > 0) // Not the world + { + pcolor = 111; + } + + // Draw the hull or bbox. + if (trace.ent > 0) + { + PM_DrawPhysEntBBox(trace.ent, pcolor, 0.3f); + } +} + +#endif diff --git a/hlsdk/ricochet/pm_shared/pm_debug.h b/hlsdk/ricochet/pm_shared/pm_debug.h new file mode 100644 index 00000000..a506d37e --- /dev/null +++ b/hlsdk/ricochet/pm_shared/pm_debug.h @@ -0,0 +1,28 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PM_DEBUG_H +#define PM_DEBUG_H +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +void PM_ViewEntity( void ); +void PM_DrawBBox(vec3_t mins, vec3_t maxs, vec3_t origin, int pcolor, float life); +void PM_ParticleLine(vec3_t start, vec3_t end, int pcolor, float life, float vert); +void PM_ShowClipBox( void ); + +#endif // PMOVEDBG_H diff --git a/hlsdk/ricochet/pm_shared/pm_defs.h b/hlsdk/ricochet/pm_shared/pm_defs.h new file mode 100644 index 00000000..8069db9c --- /dev/null +++ b/hlsdk/ricochet/pm_shared/pm_defs.h @@ -0,0 +1,265 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// pm_defs.h +#if !defined( PM_DEFSH ) +#define PM_DEFSH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +#define MAX_PHYSENTS 600 // Must have room for all entities in the world. +#define MAX_MOVEENTS 64 +#define MAX_CLIP_PLANES 5 + +#define PM_NORMAL 0x00000000 +#define PM_STUDIO_IGNORE 0x00000001 // Skip studio models +#define PM_STUDIO_BOX 0x00000002 // Use boxes for non-complex studio models (even in traceline) +#define PM_GLASS_IGNORE 0x00000004 // Ignore entities with non-normal rendermode +#define PM_WORLD_ONLY 0x00000008 // Only trace against the world + +// Values for flags parameter of PM_TraceLine +#define PM_TRACELINE_ANYVISIBLE 0 +#define PM_TRACELINE_PHYSENTSONLY 1 +#if defined _MSC_VER && _MSC_VER >= 1400 + #ifndef _CRT_SECURE_NO_DEPRECATE + #define _CRT_SECURE_NO_DEPRECATE + #endif + + #pragma warning(disable: 4996) // deprecated functions +#endif + +#include "pm_info.h" + +// PM_PlayerTrace results. +#include "pmtrace.h" + +#if !defined ( USERCMD_H ) +#include "usercmd.h" +#endif + +// physent_t +typedef struct physent_s +{ + char name[32]; // Name of model, or "player" or "world". + int player; + vec3_t origin; // Model's origin in world coordinates. + struct model_s *model; // only for bsp models + struct model_s *studiomodel; // SOLID_BBOX, but studio clip intersections. + vec3_t mins, maxs; // only for non-bsp models + int info; // For client or server to use to identify (index into edicts or cl_entities) + vec3_t angles; // rotated entities need this info for hull testing to work. + + int solid; // Triggers and func_door type WATER brushes are SOLID_NOT + int skin; // BSP Contents for such things like fun_door water brushes. + int rendermode; // So we can ignore glass + + // Complex collision detection. + float frame; + int sequence; + byte controller[4]; + byte blending[2]; + + int movetype; + int takedamage; + int blooddecal; + int team; + int classnumber; + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; +} physent_t; + +#ifndef _DCLIPNODE_DEFINED_ +#define _DCLIPNODE_DEFINED_ +typedef struct +{ + int planenum; + short children[2]; // negative numbers are contents +} dclipnode_t; +#endif + +#ifndef _MPLANE_DEFINED_ +#define _MPLANE_DEFINED_ +typedef struct mplane_s +{ + vec3_t normal; // surface normal + float dist; // closest appoach to origin + byte type; // for texture axis selection and fast side tests + byte signbits; // signx + signy<<1 + signz<<1 + byte pad[2]; +} mplane_t; +#endif + +#ifndef _HULL_DEFINED_ +#define _HULL_DEFINED_ +typedef struct hull_s +{ + dclipnode_t *clipnodes; + mplane_t *planes; + int firstclipnode; + int lastclipnode; + vec3_t clip_mins; + vec3_t clip_maxs; +} hull_t; +#endif + +typedef struct playermove_s +{ + int player_index; // So we don't try to run the PM_CheckStuck nudging too quickly. + qboolean server; // For debugging, are we running physics code on server side? + + qboolean multiplayer; // 1 == multiplayer server + float time; // realtime on host, for reckoning duck timing + float frametime; // Duration of this frame + + vec3_t forward, right, up; // Vectors for angles + // player state + vec3_t origin; // Movement origin. + vec3_t angles; // Movement view angles. + vec3_t oldangles; // Angles before movement view angles were looked at. + vec3_t velocity; // Current movement direction. + vec3_t movedir; // For waterjumping, a forced forward velocity so we can fly over lip of ledge. + vec3_t basevelocity; // Velocity of the conveyor we are standing, e.g. + + // For ducking/dead + vec3_t view_ofs; // Our eye position. + float flDuckTime; // Time we started duck + qboolean bInDuck; // In process of ducking or ducked already? + + // For walking/falling + int flTimeStepSound; // Next time we can play a step sound + int iStepLeft; + + float flFallVelocity; + vec3_t punchangle; + + float flSwimTime; + + float flNextPrimaryAttack; + + int effects; // MUZZLE FLASH, e.g. + + int flags; // FL_ONGROUND, FL_DUCKING, etc. + int usehull; // 0 = regular player hull, 1 = ducked player hull, 2 = point hull + float gravity; // Our current gravity and friction. + float friction; + int oldbuttons; // Buttons last usercmd + float waterjumptime; // Amount of time left in jumping out of water cycle. + qboolean dead; // Are we a dead player? + int deadflag; + int spectator; // Should we use spectator physics model? + int movetype; // Our movement type, NOCLIP, WALK, FLY + + int onground; + int waterlevel; + int watertype; + int oldwaterlevel; + + char sztexturename[256]; + char chtexturetype; + + float maxspeed; + float clientmaxspeed; // Player specific maxspeed + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; + // world state + // Number of entities to clip against. + int numphysent; + physent_t physents[MAX_PHYSENTS]; + // Number of momvement entities (ladders) + int nummoveent; + // just a list of ladders + physent_t moveents[MAX_MOVEENTS]; + + // All things being rendered, for tracing against things you don't actually collide with + int numvisent; + physent_t visents[ MAX_PHYSENTS ]; + + // input to run through physics. + usercmd_t cmd; + + // Trace results for objects we collided with. + int numtouch; + pmtrace_t touchindex[MAX_PHYSENTS]; + + char physinfo[ MAX_PHYSINFO_STRING ]; // Physics info string + + struct movevars_s *movevars; + vec3_t player_mins[ 4 ]; + vec3_t player_maxs[ 4 ]; + + // Common functions + const char *(*PM_Info_ValueForKey) ( const char *s, const char *key ); + void (*PM_Particle)( float *origin, int color, float life, int zpos, int zvel); + int (*PM_TestPlayerPosition) (float *pos, pmtrace_t *ptrace ); + void (*Con_NPrintf)( int idx, char *fmt, ... ); + void (*Con_DPrintf)( char *fmt, ... ); + void (*Con_Printf)( char *fmt, ... ); + double (*Sys_FloatTime)( void ); + void (*PM_StuckTouch)( int hitent, pmtrace_t *ptraceresult ); + int (*PM_PointContents) (float *p, int *truecontents /*filled in if this is non-null*/ ); + int (*PM_TruePointContents) (float *p); + int (*PM_HullPointContents) ( struct hull_s *hull, int num, float *p); + pmtrace_t (*PM_PlayerTrace) (float *start, float *end, int traceFlags, int ignore_pe ); + struct pmtrace_s *(*PM_TraceLine)( float *start, float *end, int flags, int usehulll, int ignore_pe ); + long (*RandomLong)( long lLow, long lHigh ); + float (*RandomFloat)( float flLow, float flHigh ); + int (*PM_GetModelType)( struct model_s *mod ); + void (*PM_GetModelBounds)( struct model_s *mod, float *mins, float *maxs ); + void *(*PM_HullForBsp)( physent_t *pe, float *offset ); + float (*PM_TraceModel)( physent_t *pEnt, float *start, float *end, trace_t *trace ); + int (*COM_FileSize)(char *filename); + byte *(*COM_LoadFile) (char *path, int usehunk, int *pLength); + void (*COM_FreeFile) ( void *buffer ); + char *(*memfgets)( byte *pMemFile, int fileSize, int *pFilePos, char *pBuffer, int bufferSize ); + + // Functions + // Run functions for this frame? + qboolean runfuncs; + void (*PM_PlaySound) ( int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch ); + const char *(*PM_TraceTexture) ( int ground, float *vstart, float *vend ); + void (*PM_PlaybackEventFull) ( int flags, int clientindex, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); + + pmtrace_t (*PM_PlayerTraceEx) (float *start, float *end, int traceFlags, int (*pfnIgnore)( physent_t *pe ) ); + int (*PM_TestPlayerPositionEx) (float *pos, pmtrace_t *ptrace, int (*pfnIgnore)( physent_t *pe ) ); + struct pmtrace_s *(*PM_TraceLineEx)( float *start, float *end, int flags, int usehulll, int (*pfnIgnore)( physent_t *pe ) ); +} playermove_t; + +#endif diff --git a/hlsdk/ricochet/pm_shared/pm_info.h b/hlsdk/ricochet/pm_shared/pm_info.h new file mode 100644 index 00000000..fa3cc192 --- /dev/null +++ b/hlsdk/ricochet/pm_shared/pm_info.h @@ -0,0 +1,26 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Physics info string definition +#if !defined( PM_INFOH ) +#define PM_INFOH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +#define MAX_PHYSINFO_STRING 256 + +#endif // PM_INFOH diff --git a/hlsdk/ricochet/pm_shared/pm_materials.h b/hlsdk/ricochet/pm_shared/pm_materials.h new file mode 100644 index 00000000..44ed4397 --- /dev/null +++ b/hlsdk/ricochet/pm_shared/pm_materials.h @@ -0,0 +1,37 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( PM_MATERIALSH ) +#define PM_MATERIALSH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +#define CBTEXTURENAMEMAX 13 // only load first n chars of name + +#define CHAR_TEX_CONCRETE 'C' // texture types +#define CHAR_TEX_METAL 'M' +#define CHAR_TEX_DIRT 'D' +#define CHAR_TEX_VENT 'V' +#define CHAR_TEX_GRATE 'G' +#define CHAR_TEX_TILE 'T' +#define CHAR_TEX_SLOSH 'S' +#define CHAR_TEX_WOOD 'W' +#define CHAR_TEX_COMPUTER 'P' +#define CHAR_TEX_GLASS 'Y' +#define CHAR_TEX_FLESH 'F' + +#endif // !PM_MATERIALSH \ No newline at end of file diff --git a/hlsdk/ricochet/pm_shared/pm_math.c b/hlsdk/ricochet/pm_shared/pm_math.c new file mode 100644 index 00000000..91a08448 --- /dev/null +++ b/hlsdk/ricochet/pm_shared/pm_math.c @@ -0,0 +1,424 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// pm_math.c -- math primitives + +#include "mathlib.h" +#include "const.h" +#include + +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 + +#ifdef _MSC_VER +#pragma warning(disable : 4244) +#endif + +vec3_t vec3_origin = {0,0,0}; +int nanmask = 255<<23; + +float anglemod(float a) +{ + a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); + return a; +} + +void AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + 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; + } +} + +void AngleVectorsTranspose (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + 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] = (sr*sp*cy+cr*-sy); + forward[2] = (cr*sp*cy+-sr*-sy); + } + if (right) + { + right[0] = cp*sy; + right[1] = (sr*sp*sy+cr*cy); + right[2] = (cr*sp*sy+-sr*cy); + } + if (up) + { + up[0] = -sp; + up[1] = sr*cp; + up[2] = cr*cp; + } +} + + +void AngleMatrix (const vec3_t angles, float (*matrix)[4] ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + 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); + + // matrix = (YAW * PITCH) * ROLL + matrix[0][0] = cp*cy; + matrix[1][0] = cp*sy; + matrix[2][0] = -sp; + matrix[0][1] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[2][1] = sr*cp; + matrix[0][2] = (cr*sp*cy+-sr*-sy); + matrix[1][2] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.0; + matrix[1][3] = 0.0; + matrix[2][3] = 0.0; +} + +void AngleIMatrix (const vec3_t angles, float matrix[3][4] ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + 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); + + // matrix = (YAW * PITCH) * ROLL + matrix[0][0] = cp*cy; + matrix[0][1] = cp*sy; + matrix[0][2] = -sp; + matrix[1][0] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[1][2] = sr*cp; + matrix[2][0] = (cr*sp*cy+-sr*-sy); + matrix[2][1] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.0; + matrix[1][3] = 0.0; + matrix[2][3] = 0.0; +} + +void NormalizeAngles( float *angles ) +{ + int i; + // Normalize angles + for ( i = 0; i < 3; i++ ) + { + if ( angles[i] > 180.0 ) + { + angles[i] -= 360.0; + } + else if ( angles[i] < -180.0 ) + { + angles[i] += 360.0; + } + } +} + +/* +=================== +InterpolateAngles + +Interpolate Euler angles. +FIXME: Use Quaternions to avoid discontinuities +Frac is 0.0 to 1.0 ( i.e., should probably be clamped, but doesn't have to be ) +=================== +*/ +void InterpolateAngles( float *start, float *end, float *output, float frac ) +{ + int i; + float ang1, ang2; + float d; + + NormalizeAngles( start ); + NormalizeAngles( end ); + + for ( i = 0 ; i < 3 ; i++ ) + { + ang1 = start[i]; + ang2 = end[i]; + + d = ang2 - ang1; + if ( d > 180 ) + { + d -= 360; + } + else if ( d < -180 ) + { + d += 360; + } + + output[i] = ang1 + d * frac; + } + + NormalizeAngles( output ); +} + + +/* +=================== +AngleBetweenVectors + +=================== +*/ +float AngleBetweenVectors( const vec3_t v1, const vec3_t v2 ) +{ + float angle; + float l1 = Length( v1 ); + float l2 = Length( v2 ); + + if ( !l1 || !l2 ) + return 0.0f; + + angle = acos( DotProduct( v1, v2 ) ) / (l1*l2); + angle = ( angle * 180.0f ) / M_PI; + + return angle; +} + + +void VectorTransform (const vec3_t in1, float in2[3][4], vec3_t out) +{ + out[0] = DotProduct(in1, in2[0]) + in2[0][3]; + out[1] = DotProduct(in1, in2[1]) + in2[1][3]; + out[2] = DotProduct(in1, in2[2]) + in2[2][3]; +} + + +int VectorCompare (const vec3_t v1, const vec3_t v2) +{ + int i; + + for (i=0 ; i<3 ; i++) + if (v1[i] != v2[i]) + return 0; + + return 1; +} + +void VectorMA (const vec3_t veca, float scale, const vec3_t vecb, vec3_t vecc) +{ + vecc[0] = veca[0] + scale*vecb[0]; + vecc[1] = veca[1] + scale*vecb[1]; + vecc[2] = veca[2] + scale*vecb[2]; +} + + +vec_t _DotProduct (vec3_t v1, vec3_t v2) +{ + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} + +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]-vecb[0]; + out[1] = veca[1]-vecb[1]; + out[2] = veca[2]-vecb[2]; +} + +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]+vecb[0]; + out[1] = veca[1]+vecb[1]; + out[2] = veca[2]+vecb[2]; +} + +void _VectorCopy (vec3_t in, vec3_t out) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +void CrossProduct (const vec3_t v1, const vec3_t v2, vec3_t cross) +{ + cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; + cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; + cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} + +double sqrt(double x); + +float Length(const vec3_t v) +{ + int i; + float length = 0.0f; + + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = sqrt (length); // FIXME + + return length; +} + +float Distance(const vec3_t v1, const vec3_t v2) +{ + vec3_t d; + VectorSubtract(v2,v1,d); + return Length(d); +} + +float VectorNormalize (vec3_t v) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; + +} + +void VectorInverse (vec3_t v) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void VectorScale (const vec3_t in, vec_t scale, vec3_t out) +{ + out[0] = in[0]*scale; + out[1] = in[1]*scale; + out[2] = in[2]*scale; +} + + +int Q_log2(int val) +{ + int answer=0; + while (val>>=1) + answer++; + return answer; +} + +void VectorMatrix( vec3_t forward, vec3_t right, vec3_t up) +{ + vec3_t tmp; + + if (forward[0] == 0 && forward[1] == 0) + { + right[0] = 1; + right[1] = 0; + right[2] = 0; + up[0] = -forward[2]; + up[1] = 0; + up[2] = 0; + return; + } + + tmp[0] = 0; tmp[1] = 0; tmp[2] = 1.0; + CrossProduct( forward, tmp, right ); + VectorNormalize( right ); + CrossProduct( right, forward, up ); + VectorNormalize( up ); +} + + +void VectorAngles( const vec3_t forward, vec3_t 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; +} diff --git a/hlsdk/ricochet/pm_shared/pm_movevars.h b/hlsdk/ricochet/pm_shared/pm_movevars.h new file mode 100644 index 00000000..66c99ee7 --- /dev/null +++ b/hlsdk/ricochet/pm_shared/pm_movevars.h @@ -0,0 +1,47 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// pm_movevars.h +#if !defined( PM_MOVEVARSH ) +#define PM_MOVEVARSH + +// movevars_t // Physics variables. +typedef struct movevars_s movevars_t; + +struct movevars_s +{ + float gravity; // Gravity for map + float stopspeed; // Deceleration when not moving + float maxspeed; // Max allowed speed + float spectatormaxspeed; + float accelerate; // Acceleration factor + float airaccelerate; // Same for when in open air + float wateraccelerate; // Same for when in water + float friction; + float edgefriction; // Extra friction near dropofs + float waterfriction; // Less in water + float entgravity; // 1.0 + float bounce; // Wall bounce value. 1.0 + float stepsize; // sv_stepsize; + float maxvelocity; // maximum server velocity. + float zmax; // Max z-buffer range (for GL) + float waveHeight; // Water wave height (for GL) + qboolean footsteps; // Play footstep sounds + char skyName[32]; // Name of the sky map + float rollangle; + float rollspeed; + float skycolor_r; // Sky color + float skycolor_g; // + float skycolor_b; // + float skyvec_x; // Sky vector + float skyvec_y; // + float skyvec_z; // +}; + +extern movevars_t movevars; + +#endif diff --git a/hlsdk/ricochet/pm_shared/pm_shared.c b/hlsdk/ricochet/pm_shared/pm_shared.c new file mode 100644 index 00000000..46a6f2ec --- /dev/null +++ b/hlsdk/ricochet/pm_shared/pm_shared.c @@ -0,0 +1,3327 @@ +/*** +* +* 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. +* 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 +#include "mathlib.h" +#include "const.h" +#include "usercmd.h" +#include "pm_defs.h" +#include "pm_shared.h" +#include "pm_movevars.h" +#include "pm_debug.h" +#include // NULL +#include // sqrt +#include // strcpy +#include // atoi +#include // isspace + +#ifdef CLIENT_DLL + // Spectator Mode + float vecNewViewAngles[3]; + float vecNewViewOrigin[3]; + int iHasNewViewAngles; + int iHasNewViewOrigin; + int iIsSpectator; +#endif + +static int pm_shared_initialized = 0; + +#ifdef _MSC_VER +#pragma warning( disable : 4305 ) +#endif + +typedef enum {mod_brush, mod_sprite, mod_alias, mod_studio} modtype_t; + +playermove_t *pmove = NULL; + +// Ducking time +#define TIME_TO_DUCK 0.4 +#define VEC_DUCK_HULL_MIN -18 +#define VEC_DUCK_HULL_MAX 18 +#define VEC_DUCK_VIEW 12 +#define PM_DEAD_VIEWHEIGHT -8 +#define MAX_CLIMB_SPEED 200 +#define STUCK_MOVEUP 1 +#define STUCK_MOVEDOWN -1 +#define VEC_HULL_MIN -36 +#define VEC_HULL_MAX 36 +#define VEC_VIEW 28 +#define STOP_EPSILON 0.1 + +#define CTEXTURESMAX 512 // max number of textures loaded +#define CBTEXTURENAMEMAX 13 // only load first n chars of name + +#define CHAR_TEX_CONCRETE 'C' // texture types +#define CHAR_TEX_METAL 'M' +#define CHAR_TEX_DIRT 'D' +#define CHAR_TEX_VENT 'V' +#define CHAR_TEX_GRATE 'G' +#define CHAR_TEX_TILE 'T' +#define CHAR_TEX_SLOSH 'S' +#define CHAR_TEX_WOOD 'W' +#define CHAR_TEX_COMPUTER 'P' +#define CHAR_TEX_GLASS 'Y' +#define CHAR_TEX_FLESH 'F' + +#define STEP_CONCRETE 0 // default step sound +#define STEP_METAL 1 // metal floor +#define STEP_DIRT 2 // dirt, sand, rock +#define STEP_VENT 3 // ventillation duct +#define STEP_GRATE 4 // metal grating +#define STEP_TILE 5 // floor tiles +#define STEP_SLOSH 6 // shallow liquid puddle +#define STEP_WADE 7 // wading in liquid +#define STEP_LADDER 8 // climbing ladder + +#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_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_LONGJUMP_SPEED 350 // how fast we longjump + +// double to float warning +#ifdef _MSC_VER +#pragma warning(disable : 4244) +#endif +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#define min(a, b) (((a) < (b)) ? (a) : (b)) +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 + +#define MAX_CLIENTS 32 + +#define CONTENTS_CURRENT_0 -9 +#define CONTENTS_CURRENT_90 -10 +#define CONTENTS_CURRENT_180 -11 +#define CONTENTS_CURRENT_270 -12 +#define CONTENTS_CURRENT_UP -13 +#define CONTENTS_CURRENT_DOWN -14 + +#define CONTENTS_TRANSLUCENT -15 + +static vec3_t rgv3tStuckTable[54]; +static int rgStuckLast[MAX_CLIENTS][2]; + +// Texture names +static int gcTextures = 0; +static char grgszTextureName[CTEXTURESMAX][CBTEXTURENAMEMAX]; +static char grgchTextureType[CTEXTURESMAX]; + +int g_onladder = 0; + +int PM_Ignore( physent_t *pe ) +{ + //if ( !stricmp( pe->name, "models/disc.mdl" ) ) + // return 1; + return 0; +} + +void PM_SwapTextures( int i, int j ) +{ + char chTemp; + char szTemp[ CBTEXTURENAMEMAX ]; + + strcpy( szTemp, grgszTextureName[ i ] ); + chTemp = grgchTextureType[ i ]; + + strcpy( grgszTextureName[ i ], grgszTextureName[ j ] ); + grgchTextureType[ i ] = grgchTextureType[ j ]; + + strcpy( grgszTextureName[ j ], szTemp ); + grgchTextureType[ j ] = chTemp; +} + +void PM_SortTextures( void ) +{ + // Bubble sort, yuck, but this only occurs at startup and it's only 512 elements... + // + int i, j; + + for ( i = 0 ; i < gcTextures; i++ ) + { + for ( j = i + 1; j < gcTextures; j++ ) + { + if ( stricmp( grgszTextureName[ i ], grgszTextureName[ j ] ) > 0 ) + { + // Swap + // + PM_SwapTextures( i, j ); + } + } + } +} + +void PM_InitTextureTypes() +{ + char buffer[512]; + int i, j; + byte *pMemFile; + int fileSize, filePos; + static qboolean bTextureTypeInit = false; + + if ( bTextureTypeInit ) + 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 ); + if ( !pMemFile ) + return; + + filePos = 0; + // for each line in the file... + while ( pmove->memfgets( pMemFile, fileSize, &filePos, buffer, 511 ) != NULL && (gcTextures < CTEXTURESMAX) ) + { + // skip whitespace + i = 0; + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // skip comment lines + if (buffer[i] == '/' || !isalpha(buffer[i])) + continue; + + // get texture type + grgchTextureType[gcTextures] = toupper(buffer[i++]); + + // skip whitespace + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // get sentence name + j = i; + while (buffer[j] && !isspace(buffer[j])) + j++; + + if (!buffer[j]) + continue; + + // null-terminate name and save in sentences array + j = min (j, CBTEXTURENAMEMAX-1+i); + buffer[j] = 0; + 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 ) +{ + int left, right, pivot; + int val; + + assert( pm_shared_initialized ); + + left = 0; + right = gcTextures - 1; + + while ( left <= right ) + { + pivot = ( left + right ) / 2; + + val = strnicmp( name, grgszTextureName[ pivot ], CBTEXTURENAMEMAX-1 ); + if ( val == 0 ) + { + return grgchTextureType[ pivot ]; + } + else if ( val > 0 ) + { + left = pivot + 1; + } + else if ( val < 0 ) + { + right = pivot - 1; + } + } + + return CHAR_TEX_CONCRETE; +} + +void PM_PlayStepSound( int step, float fvol ) +{ + static int iSkipStep = 0; + int irand; + vec3_t hvel; + + pmove->iStepLeft = !pmove->iStepLeft; + + if ( !pmove->runfuncs ) + { + return; + } + + irand = pmove->RandomLong(0,1) + ( pmove->iStepLeft * 2 ); + + // FIXME mp_footsteps needs to be a movevar + if ( pmove->multiplayer && !pmove->movevars->footsteps ) + return; + + VectorCopy( pmove->velocity, hvel ); + hvel[2] = 0.0; + + 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 + // FIXME, move to player state + + switch (step) + { + default: + case STEP_CONCRETE: + switch (irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_step1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_step3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_step2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_step4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_METAL: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_metal1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_metal3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_metal2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_metal4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_DIRT: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_dirt1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_dirt3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_dirt2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_dirt4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_VENT: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_duct1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_duct3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_duct2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_duct4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_GRATE: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_grate1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_grate3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_grate2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_grate4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_TILE: + if ( !pmove->RandomLong(0,4) ) + irand = 4; + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_tile1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_tile3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_tile2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_tile4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 4: pmove->PM_PlaySound( CHAN_BODY, "player/pl_tile5.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_SLOSH: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_slosh1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_slosh3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_slosh2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_slosh4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_WADE: + if ( iSkipStep == 0 ) + { + iSkipStep++; + break; + } + + if ( iSkipStep++ == 3 ) + { + iSkipStep = 0; + } + + switch (irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_LADDER: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_ladder1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_ladder3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_ladder2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_ladder4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + } +} + +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; + } +} + +/* +==================== +PM_CatagorizeTextureType + +Determine texture info for the texture we are standing on. +==================== +*/ +void PM_CatagorizeTextureType( void ) +{ + vec3_t start, end; + const char *pTextureName; + + VectorCopy( pmove->origin, start ); + VectorCopy( pmove->origin, end ); + + // Straight down + end[2] -= 64; + + // Fill in default values, just in case. + pmove->sztexturename[0] = '\0'; + pmove->chtexturetype = CHAR_TEX_CONCRETE; + + pTextureName = pmove->PM_TraceTexture( pmove->onground, start, end ); + if ( !pTextureName ) + return; + + // strip leading '-0' or '+0~' or '{' or '!' + if (*pTextureName == '-' || *pTextureName == '+') + pTextureName += 2; + + if (*pTextureName == '{' || *pTextureName == '!' || *pTextureName == '~' || *pTextureName == ' ') + pTextureName++; + // '}}' + + strcpy( pmove->sztexturename, pTextureName); + pmove->sztexturename[ CBTEXTURENAMEMAX - 1 ] = 0; + + // get texture type + pmove->chtexturetype = PM_FindTextureType( pmove->sztexturename ); +} + +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; + + if ( pmove->flTimeStepSound > 0 ) + return; + + if ( pmove->flags & FL_FROZEN ) + return; + + 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!!!! + 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; + } + + // 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 ) ) + { + fWalking = speed < velrun; + + VectorCopy( pmove->origin, center ); + VectorCopy( pmove->origin, knee ); + VectorCopy( pmove->origin, feet ); + + height = pmove->player_maxs[ pmove->usehull ][ 2 ] - pmove->player_mins[ pmove->usehull ][ 2 ]; + + knee[2] = pmove->origin[2] - 0.3 * height; + feet[2] = pmove->origin[2] - 0.5 * height; + + // find out what we're stepping in or on... + if (fLadder) + { + step = STEP_LADDER; + fvol = 0.35; + pmove->flTimeStepSound = 350; + } + else if ( pmove->PM_PointContents ( knee, NULL ) == CONTENTS_WATER ) + { + step = STEP_WADE; + fvol = 0.65; + 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; + } + else + { + // find texture under player, if different from current texture, + // get material type + step = PM_MapTextureTypeStepType( pmove->chtexturetype ); + + switch ( pmove->chtexturetype ) + { + default: + case CHAR_TEX_CONCRETE: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_METAL: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_DIRT: + fvol = fWalking ? 0.25 : 0.55; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_VENT: + fvol = fWalking ? 0.4 : 0.7; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_GRATE: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_TILE: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_SLOSH: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + } + } + + pmove->flTimeStepSound += flduck; // slower step time if ducking + + // play the sound + // 35% volume if ducking + if ( pmove->flags & FL_DUCKING ) + { + fvol *= 0.35; + } + + PM_PlayStepSound( step, fvol ); + } +} + +/* +================ +PM_AddToTouched + +Add's the trace result to touch list, if contact is not already in list. +================ +*/ +qboolean PM_AddToTouched(pmtrace_t tr, vec3_t impactvelocity) +{ + int i; + + for (i = 0; i < pmove->numtouch; i++) + { + if (pmove->touchindex[i].ent == tr.ent) + break; + } + if (i != pmove->numtouch) // Already in list. + return false; + + VectorCopy( impactvelocity, tr.deltavelocity ); + + if (pmove->numtouch >= MAX_PHYSENTS) + pmove->Con_DPrintf("Too many entities were touched!\n"); + + pmove->touchindex[pmove->numtouch++] = tr; + return true; +} + +/* +================ +PM_CheckVelocity + +See if the player has a bogus velocity value. +================ +*/ +void PM_CheckVelocity () +{ + int i; + +// +// bound velocity +// + for (i=0 ; i<3 ; i++) + { + // See if it's bogus. + if (IS_NAN(pmove->velocity[i])) + { + pmove->Con_Printf ("PM Got a NaN velocity %i\n", i); + pmove->velocity[i] = 0; + } + if (IS_NAN(pmove->origin[i])) + { + pmove->Con_Printf ("PM Got a NaN origin on %i\n", i); + pmove->origin[i] = 0; + } + + // Bound it. + if (pmove->velocity[i] > pmove->movevars->maxvelocity) + { + pmove->Con_DPrintf ("PM Got a velocity too high on %i\n", i); + pmove->velocity[i] = pmove->movevars->maxvelocity; + } + else if (pmove->velocity[i] < -pmove->movevars->maxvelocity) + { + pmove->Con_DPrintf ("PM Got a velocity too low on %i\n", i); + pmove->velocity[i] = -pmove->movevars->maxvelocity; + } + } +} + +/* +================== +PM_ClipVelocity + +Slide off of the impacting object +returns the blocked flags: +0x01 == floor +0x02 == step / wall +================== +*/ +int PM_ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce) +{ + float backoff; + float change; + float angle; + int i, blocked; + + angle = normal[ 2 ]; + + blocked = 0x00; // Assume unblocked. + if (angle > 0) // If the plane that is blocking us has a positive z component, then assume it's a floor. + blocked |= 0x01; // + if (!angle) // If the plane has no Z, it is vertical (wall/step) + blocked |= 0x02; // + + // Determine how far along plane to slide based on incoming direction. + // Scale by overbounce factor. + backoff = DotProduct (in, normal) * overbounce; + + for (i=0 ; i<3 ; i++) + { + change = normal[i]*backoff; + out[i] = in[i] - change; + // If out velocity is too small, zero it out. + if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) + out[i] = 0; + } + + // Return blocking flags. + return blocked; +} + +void PM_AddCorrectGravity () +{ + float ent_gravity; + + if ( pmove->waterjumptime ) + return; + + if (pmove->gravity) + ent_gravity = pmove->gravity; + else + ent_gravity = 1.0; + + // Add gravity so they'll be in the correct position during movement + // yes, this 0.5 looks wrong, but it's not. + pmove->velocity[2] -= (ent_gravity * pmove->movevars->gravity * 0.5 * pmove->frametime ); + pmove->velocity[2] += pmove->basevelocity[2] * pmove->frametime; + pmove->basevelocity[2] = 0; + + PM_CheckVelocity(); +} + + +void PM_FixupGravityVelocity () +{ + float ent_gravity; + + if ( pmove->waterjumptime ) + return; + + if (pmove->gravity) + ent_gravity = pmove->gravity; + else + ent_gravity = 1.0; + + // Get the correct velocity for the end of the dt + pmove->velocity[2] -= (ent_gravity * pmove->movevars->gravity * pmove->frametime * 0.5 ); + + PM_CheckVelocity(); +} + +/* +============ +PM_FlyMove + +The basic solid body movement clip that slides along multiple planes +============ +*/ +int PM_FlyMove (void) +{ + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity, original_velocity; + vec3_t new_velocity; + int i, j; + pmtrace_t trace; + vec3_t end; + float time_left, allFraction; + int blocked; + + numbumps = 4; // Bump up to four times + + blocked = 0; // Assume not blocked + numplanes = 0; // and not sliding along any planes + VectorCopy (pmove->velocity, original_velocity); // Store original velocity + VectorCopy (pmove->velocity, primal_velocity); + + allFraction = 0; + time_left = pmove->frametime; // Total time for this movement operation. + + for (bumpcount=0 ; bumpcountvelocity[0] && !pmove->velocity[1] && !pmove->velocity[2]) + break; + + // Assume we can move all the way from the current origin to the + // end point. + for (i=0 ; i<3 ; i++) + end[i] = pmove->origin[i] + time_left * pmove->velocity[i]; + + // See if we can make it from origin to end point. + trace = pmove->PM_PlayerTraceEx (pmove->origin, end, PM_NORMAL, PM_Ignore ); + + allFraction += trace.fraction; + // If we started in a solid object, or we were in solid space + // the whole way, zero out our velocity and return that we + // are blocked by floor and wall. + if (trace.allsolid) + { // entity is trapped in another solid + VectorCopy (vec3_origin, pmove->velocity); + //Con_DPrintf("Trapped 4\n"); + return 4; + } + + // If we moved some portion of the total distance, then + // copy the end position into the pmove->origin and + // zero the plane counter. + if (trace.fraction > 0) + { // actually covered some distance + VectorCopy (trace.endpos, pmove->origin); + VectorCopy (pmove->velocity, original_velocity); + numplanes = 0; + } + + // If we covered the entire distance, we are done + // and can return. + if (trace.fraction == 1) + break; // moved the entire distance + + //if (!trace.ent) + // Sys_Error ("PM_PlayerTrace: !trace.ent"); + + // Save entity that blocked us (since fraction was < 1.0) + // for contact + // Add it if it's not already in the list!!! + PM_AddToTouched(trace, pmove->velocity); + + // If the plane we hit has a high z component in the normal, then + // it's probably a floor + if (trace.plane.normal[2] > 0.7) + { + blocked |= 1; // floor + } + // If the plane has a zero z component in the normal, then it's a + // step or wall + if (!trace.plane.normal[2]) + { + blocked |= 2; // step / wall + //Con_DPrintf("Blocked by %i\n", trace.ent); + } + + // Reduce amount of pmove->frametime left by total time left * fraction + // that we covered. + time_left -= time_left * trace.fraction; + + // Did we run out of planes to clip against? + if (numplanes >= MAX_CLIP_PLANES) + { // this shouldn't really happen + // Stop our movement if so. + VectorCopy (vec3_origin, pmove->velocity); + //Con_DPrintf("Too many planes 4\n"); + + break; + } + + // Set up next clipping plane + VectorCopy (trace.plane.normal, planes[numplanes]); + numplanes++; +// + +// modify original_velocity so it parallels all of the clip planes +// + if ( pmove->movetype == MOVETYPE_WALK && + ((pmove->onground == -1) || (pmove->friction != 1)) ) // relfect player velocity + { + for ( i = 0; i < numplanes; i++ ) + { + if ( planes[i][2] > 0.7 ) + {// floor or slope + PM_ClipVelocity( original_velocity, planes[i], new_velocity, 1 ); + VectorCopy( new_velocity, original_velocity ); + } + else + PM_ClipVelocity( original_velocity, planes[i], new_velocity, 1.0 + pmove->movevars->bounce * (1-pmove->friction) ); + } + + VectorCopy( new_velocity, pmove->velocity ); + VectorCopy( new_velocity, original_velocity ); + } + else + { + for (i=0 ; ivelocity, + 1); + for (j=0 ; jvelocity, planes[j]) < 0) + break; // not ok + } + if (j == numplanes) // Didn't have to clip, so we're ok + break; + } + + // Did we go all the way through plane set + if (i != numplanes) + { // go along this plane + // pmove->velocity is set in clipping call, no need to set again. + ; + } + else + { // go along the crease + if (numplanes != 2) + { + //Con_Printf ("clip velocity, numplanes == %i\n",numplanes); + VectorCopy (vec3_origin, pmove->velocity); + //Con_DPrintf("Trapped 4\n"); + + break; + } + CrossProduct (planes[0], planes[1], dir); + d = DotProduct (dir, pmove->velocity); + VectorScale (dir, d, pmove->velocity ); + } + + // + // if original velocity is against the original velocity, stop dead + // to avoid tiny occilations in sloping corners + // + if (DotProduct (pmove->velocity, primal_velocity) <= 0) + { + //Con_DPrintf("Back\n"); + VectorCopy (vec3_origin, pmove->velocity); + break; + } + } + } + + if ( allFraction == 0 ) + { + VectorCopy (vec3_origin, pmove->velocity); + //Con_DPrintf( "Don't stick\n" ); + } + + return blocked; +} + +/* +============== +PM_Accelerate +============== +*/ +void PM_Accelerate (vec3_t wishdir, float wishspeed, float accel) +{ + int i; + float addspeed, accelspeed, currentspeed; + + // Dead player's don't accelerate + if (pmove->dead) + return; + + // If waterjumping, don't accelerate + if (pmove->waterjumptime) + return; + + // See if we are changing direction a bit + currentspeed = DotProduct (pmove->velocity, wishdir); + + // Reduce wishspeed by the amount of veer. + addspeed = wishspeed - currentspeed; + + // If not going to add any speed, done. + if (addspeed <= 0) + return; + + // Determine amount of accleration. + accelspeed = accel * pmove->frametime * wishspeed * pmove->friction; + + // Cap at addspeed + if (accelspeed > addspeed) + accelspeed = addspeed; + + // Adjust velocity. + for (i=0 ; i<3 ; i++) + { + pmove->velocity[i] += accelspeed * wishdir[i]; + } +} + +/* +===================== +PM_WalkMove + +Only used by players. Moves along the ground when player is a MOVETYPE_WALK. +====================== +*/ +void PM_WalkMove () +{ + int clip; + int oldonground; + int i; + + vec3_t wishvel; + float spd; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + + vec3_t dest, start; + vec3_t original, originalvel; + vec3_t down, downvel; + float downdist, updist; + + pmtrace_t trace; + + // Copy movement amounts + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + // Zero out z components of movement vectors + pmove->forward[2] = 0; + pmove->right[2] = 0; + + VectorNormalize (pmove->forward); // Normalize remainder of vectors. + VectorNormalize (pmove->right); // + + for (i=0 ; i<2 ; i++) // Determine x and y parts of velocity + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + + wishvel[2] = 0; // Zero out z part of velocity + + VectorCopy (wishvel, wishdir); // Determine maginitude of speed of move + wishspeed = VectorNormalize(wishdir); + +// +// Clamp to server defined max speed +// + if (wishspeed > pmove->maxspeed) + { + VectorScale (wishvel, pmove->maxspeed/wishspeed, wishvel); + wishspeed = pmove->maxspeed; + } + + // Set pmove velocity + pmove->velocity[2] = 0; + PM_Accelerate (wishdir, wishspeed, pmove->movevars->accelerate); + pmove->velocity[2] = 0; + + // Add in any base velocity to the current velocity. + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity ); + + spd = Length( pmove->velocity ); + + if (spd < 1.0f) + { + VectorClear( pmove->velocity ); + return; + } + + // If we are not moving, do nothing + //if (!pmove->velocity[0] && !pmove->velocity[1] && !pmove->velocity[2]) + // return; + + oldonground = pmove->onground; + +// first try just moving to the destination + dest[0] = pmove->origin[0] + pmove->velocity[0]*pmove->frametime; + dest[1] = pmove->origin[1] + pmove->velocity[1]*pmove->frametime; + dest[2] = pmove->origin[2]; + + // first try moving directly to the next spot + VectorCopy (dest, start); + trace = pmove->PM_PlayerTraceEx (pmove->origin, dest, PM_NORMAL, PM_Ignore ); + // If we made it all the way, then copy trace end + // as new player position. + if (trace.fraction == 1) + { + VectorCopy (trace.endpos, pmove->origin); + return; + } + + if (oldonground == -1 && // Don't walk up stairs if not on ground. + pmove->waterlevel == 0) + return; + + if (pmove->waterjumptime) // If we are jumping out of water, don't do anything more. + return; + + // Try sliding forward both on ground and up 16 pixels + // take the move that goes farthest + VectorCopy (pmove->origin, original); // Save out original pos & + VectorCopy (pmove->velocity, originalvel); // velocity. + + // Slide move + clip = PM_FlyMove (); + + // Copy the results out + VectorCopy (pmove->origin , down); + VectorCopy (pmove->velocity, downvel); + + // Reset original values. + VectorCopy (original, pmove->origin); + + VectorCopy (originalvel, pmove->velocity); + + // Start out up one stair height + VectorCopy (pmove->origin, dest); + dest[2] += pmove->movevars->stepsize; + + trace = pmove->PM_PlayerTraceEx (pmove->origin, dest, PM_NORMAL, PM_Ignore ); + // If we started okay and made it part of the way at least, + // copy the results to the movement start position and then + // run another move try. + if (!trace.startsolid && !trace.allsolid) + { + VectorCopy (trace.endpos, pmove->origin); + } + +// slide move the rest of the way. + clip = PM_FlyMove (); + +// Now try going back down from the end point +// press down the stepheight + VectorCopy (pmove->origin, dest); + dest[2] -= pmove->movevars->stepsize; + + trace = pmove->PM_PlayerTraceEx (pmove->origin, dest, PM_NORMAL, PM_Ignore ); + + // If we are not on the ground any more then + // use the original movement attempt + if ( trace.plane.normal[2] < 0.7) + goto usedown; + // If the trace ended up in empty space, copy the end + // over to the origin. + if (!trace.startsolid && !trace.allsolid) + { + VectorCopy (trace.endpos, pmove->origin); + } + // Copy this origion to up. + VectorCopy (pmove->origin, pmove->up); + + // decide which one went farther + downdist = (down[0] - original[0])*(down[0] - original[0]) + + (down[1] - original[1])*(down[1] - original[1]); + updist = (pmove->up[0] - original[0])*(pmove->up[0] - original[0]) + + (pmove->up[1] - original[1])*(pmove->up[1] - original[1]); + + if (downdist > updist) + { +usedown: + VectorCopy (down , pmove->origin); + VectorCopy (downvel, pmove->velocity); + } else // copy z value from slide move + pmove->velocity[2] = downvel[2]; + +} + +/* +================== +PM_Friction + +Handles both ground friction and water friction +================== +*/ +void PM_Friction (void) +{ + float *vel; + float speed, newspeed, control; + float friction; + float drop; + vec3_t newvel; + + // If we are in water jump cycle, don't apply friction + if (pmove->waterjumptime) + return; + + // Get velocity + vel = pmove->velocity; + + // Calculate speed + speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1] + vel[2]*vel[2]); + + // If too slow, return + if (speed < 0.1f) + { + return; + } + + drop = 0; + +// apply ground friction + if (pmove->onground != -1) // On an entity that is the ground + { + vec3_t start, stop; + pmtrace_t trace; + + start[0] = stop[0] = pmove->origin[0] + vel[0]/speed*16; + start[1] = stop[1] = pmove->origin[1] + vel[1]/speed*16; + start[2] = pmove->origin[2] + pmove->player_mins[pmove->usehull][2]; + stop[2] = start[2] - 34; + + trace = pmove->PM_PlayerTraceEx (start, stop, PM_NORMAL, PM_Ignore ); + + if (trace.fraction == 1.0) + friction = pmove->movevars->friction*pmove->movevars->edgefriction; + else + friction = pmove->movevars->friction; + + // Grab friction value. + //friction = pmove->movevars->friction; + + friction *= pmove->friction; // player friction? + + // Bleed off some speed, but if we have less than the bleed + // threshhold, bleed the theshold amount. + control = (speed < pmove->movevars->stopspeed) ? + pmove->movevars->stopspeed : speed; + // Add the amount to t'he drop amount. + drop += control*friction*pmove->frametime; + } + +// apply water friction +// if (pmove->waterlevel) +// drop += speed * pmove->movevars->waterfriction * waterlevel * pmove->frametime; + +// scale the velocity + newspeed = speed - drop; + if (newspeed < 0) + newspeed = 0; + + // Determine proportion of old speed we are using. + newspeed /= speed; + + // Adjust velocity according to proportion. + newvel[0] = vel[0] * newspeed; + newvel[1] = vel[1] * newspeed; + newvel[2] = vel[2] * newspeed; + + VectorCopy( newvel, pmove->velocity ); +} + +void PM_AirAccelerate (vec3_t wishdir, float wishspeed, float accel) +{ + int i; + float addspeed, accelspeed, currentspeed, wishspd = wishspeed; + + if (pmove->dead) + return; + if (pmove->waterjumptime) + return; + + // Cap speed + //wishspd = VectorNormalize (pmove->wishveloc); + + if (wishspd > 30) + wishspd = 30; + // Determine veer amount + currentspeed = DotProduct (pmove->velocity, wishdir); + // See how much to add + addspeed = wishspd - currentspeed; + // If not adding any, done. + if (addspeed <= 0) + return; + // Determine acceleration speed after acceleration + + accelspeed = accel * wishspeed * pmove->frametime * pmove->friction; + // Cap it + if (accelspeed > addspeed) + accelspeed = addspeed; + + // Adjust pmove vel. + for (i=0 ; i<3 ; i++) + { + pmove->velocity[i] += accelspeed*wishdir[i]; + } +} + +/* +=================== +PM_WaterMove + +=================== +*/ +void PM_WaterMove (void) +{ + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + vec3_t start, dest; + vec3_t temp; + pmtrace_t trace; + + float speed, newspeed, addspeed, accelspeed; + +// +// user intentions +// + for (i=0 ; i<3 ; i++) + wishvel[i] = pmove->forward[i]*pmove->cmd.forwardmove + pmove->right[i]*pmove->cmd.sidemove; + + // Sinking after no other movement occurs + if (!pmove->cmd.forwardmove && !pmove->cmd.sidemove && !pmove->cmd.upmove) + wishvel[2] -= 60; // drift towards bottom + else // Go straight up by upmove amount. + wishvel[2] += pmove->cmd.upmove; + + // Copy it over and determine speed + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + // Cap speed. + if (wishspeed > pmove->maxspeed) + { + VectorScale (wishvel, pmove->maxspeed/wishspeed, wishvel); + wishspeed = pmove->maxspeed; + } + // Slow us down a bit. + wishspeed *= 0.8; + + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity); +// Water friction + VectorCopy(pmove->velocity, temp); + speed = VectorNormalize(temp); + if (speed) + { + newspeed = speed - pmove->frametime * speed * pmove->movevars->friction * pmove->friction; + + if (newspeed < 0) + newspeed = 0; + VectorScale (pmove->velocity, newspeed/speed, pmove->velocity); + } + else + newspeed = 0; + +// +// water acceleration +// + if ( wishspeed < 0.1f ) + { + return; + } + + addspeed = wishspeed - newspeed; + if (addspeed > 0) + { + + VectorNormalize(wishvel); + accelspeed = pmove->movevars->accelerate * wishspeed * pmove->frametime * pmove->friction; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i = 0; i < 3; i++) + pmove->velocity[i] += accelspeed * wishvel[i]; + } + +// Now move +// assume it is a stair or a slope, so press down from stepheight above + VectorMA (pmove->origin, pmove->frametime, pmove->velocity, dest); + VectorCopy (dest, start); + start[2] += pmove->movevars->stepsize + 1; + trace = pmove->PM_PlayerTraceEx (start, dest, PM_NORMAL, PM_Ignore ); + if (!trace.startsolid && !trace.allsolid) // FIXME: check steep slope? + { // walked up the step, so just keep result and exit + VectorCopy (trace.endpos, pmove->origin); + return; + } + + // Try moving straight along out normal path. + PM_FlyMove (); +} + + +/* +=================== +PM_AirMove + +=================== +*/ +void PM_AirMove (void) +{ + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + + // Copy movement amounts + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + // Zero out z components of movement vectors + pmove->forward[2] = 0; + pmove->right[2] = 0; + // Renormalize + VectorNormalize (pmove->forward); + VectorNormalize (pmove->right); + + // Determine x and y parts of velocity + for (i=0 ; i<2 ; i++) + { + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + } + // Zero out z part of velocity + wishvel[2] = 0; + + // Determine maginitude of speed of move + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + // Clamp to server defined max speed + if (wishspeed > pmove->maxspeed) + { + VectorScale (wishvel, pmove->maxspeed/wishspeed, wishvel); + wishspeed = pmove->maxspeed; + } + + PM_AirAccelerate (wishdir, wishspeed, pmove->movevars->airaccelerate); + + // Add in any base velocity to the current velocity. + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity ); + + PM_FlyMove (); +} + +qboolean PM_InWater( void ) +{ + return ( pmove->waterlevel > 1 ); +} + +/* +============= +PM_CheckWater + +Sets pmove->waterlevel and pmove->watertype values. +============= +*/ +qboolean PM_CheckWater () +{ + vec3_t point; + int cont; + int truecont; + float height; + float heightover2; + + // Pick a spot just above the players feet. + point[0] = pmove->origin[0] + (pmove->player_mins[pmove->usehull][0] + pmove->player_maxs[pmove->usehull][0]) * 0.5; + point[1] = pmove->origin[1] + (pmove->player_mins[pmove->usehull][1] + pmove->player_maxs[pmove->usehull][1]) * 0.5; + point[2] = pmove->origin[2] + pmove->player_mins[pmove->usehull][2] + 1; + + // Assume that we are not in water at all. + pmove->waterlevel = 0; + pmove->watertype = CONTENTS_EMPTY; + + // Grab point contents. + cont = pmove->PM_PointContents (point, &truecont ); + // Are we under water? (not solid and not empty?) + if (cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) + { + // Set water type + pmove->watertype = cont; + + // We are at least at level one + pmove->waterlevel = 1; + + height = (pmove->player_mins[pmove->usehull][2] + pmove->player_maxs[pmove->usehull][2]); + heightover2 = height * 0.5; + + // Now check a point that is at the player hull midpoint. + point[2] = pmove->origin[2] + heightover2; + cont = pmove->PM_PointContents (point, NULL ); + // If that point is also under water... + if (cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) + { + // Set a higher water level. + pmove->waterlevel = 2; + + // Now check the eye position. (view_ofs is relative to the origin) + point[2] = pmove->origin[2] + pmove->view_ofs[2]; + + cont = pmove->PM_PointContents (point, NULL ); + if (cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) + pmove->waterlevel = 3; // In over our eyes + } + + // Adjust velocity based on water current, if any. + if ( ( truecont <= CONTENTS_CURRENT_0 ) && + ( truecont >= CONTENTS_CURRENT_DOWN ) ) + { + // The deeper we are, the stronger the current. + static vec3_t current_table[] = + { + {1, 0, 0}, {0, 1, 0}, {-1, 0, 0}, + {0, -1, 0}, {0, 0, 1}, {0, 0, -1} + }; + + VectorMA (pmove->basevelocity, 50.0*pmove->waterlevel, current_table[CONTENTS_CURRENT_0 - truecont], pmove->basevelocity); + } + } + + return pmove->waterlevel > 1; +} + +/* +============= +PM_CatagorizePosition +============= +*/ +void PM_CatagorizePosition (void) +{ + vec3_t point; + pmtrace_t tr; + +// if the player hull point one unit down is solid, the player +// is on ground + +// see if standing on something solid + + // Doing this before we move may introduce a potential latency in water detection, but + // doing it after can get us stuck on the bottom in water if the amount we move up + // is less than the 1 pixel 'threshold' we're about to snap to. Also, we'll call + // this several times per frame, so we really need to avoid sticking to the bottom of + // water on each call, and the converse case will correct itself if called twice. + PM_CheckWater(); + + point[0] = pmove->origin[0]; + point[1] = pmove->origin[1]; + point[2] = pmove->origin[2] - 2; + + if (pmove->velocity[2] > 180) // Shooting up really fast. Definitely not on ground. + { + pmove->onground = -1; + } + else + { + // Try and move down. + tr = pmove->PM_PlayerTraceEx (pmove->origin, point, PM_NORMAL, PM_Ignore ); + // If we hit a steep plane, we are not on ground + if ( tr.plane.normal[2] < 0.7) + pmove->onground = -1; // too steep + else + pmove->onground = tr.ent; // Otherwise, point to index of ent under us. + + // If we are on something... + if (pmove->onground != -1) + { + // Then we are not in water jump sequence + pmove->waterjumptime = 0; + // If we could make the move, drop us down that 1 pixel + if (pmove->waterlevel < 2 && !tr.startsolid && !tr.allsolid) + VectorCopy (tr.endpos, pmove->origin); + } + + // Standing on an entity other than the world + if (tr.ent > 0) // So signal that we are touching something. + { + PM_AddToTouched(tr, pmove->velocity); + } + } +} + +/* +================= +PM_GetRandomStuckOffsets + +When a player is stuck, it's costly to try and unstick them +Grab a test offset for the player based on a passed in index +================= +*/ +int PM_GetRandomStuckOffsets(int nIndex, int server, vec3_t offset) +{ + // Last time we did a full + int idx; + idx = rgStuckLast[nIndex][server]++; + + VectorCopy(rgv3tStuckTable[idx % 54], offset); + + return (idx % 54); +} + +void PM_ResetStuckOffsets(int nIndex, int server) +{ + rgStuckLast[nIndex][server] = 0; +} + +/* +================= +NudgePosition + +If pmove->origin is in a solid position, +try nudging slightly on all axis to +allow for the cut precision of the net coordinates +================= +*/ +#define PM_CHECKSTUCK_MINTIME 0.05 // Don't check again too quickly. + +int PM_CheckStuck (void) +{ + vec3_t base; + vec3_t offset; + vec3_t test; + int hitent; + int idx; + float fTime; + int i; + pmtrace_t traceresult; + + static float rgStuckCheckTime[MAX_CLIENTS][2]; // Last time we did a full + + // If position is okay, exit + hitent = pmove->PM_TestPlayerPositionEx (pmove->origin, &traceresult, PM_Ignore ); + if (hitent == -1 ) + { + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + return 0; + } + + VectorCopy (pmove->origin, base); + + // + // Deal with precision error in network. + // + if (!pmove->server) + { + // World or BSP model + if ( ( hitent == 0 ) || + ( pmove->physents[hitent].model != NULL ) ) + { + int nReps = 0; + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + do + { + i = PM_GetRandomStuckOffsets(pmove->player_index, pmove->server, offset); + + VectorAdd(base, offset, test); + if (pmove->PM_TestPlayerPositionEx (test, &traceresult, PM_Ignore ) == -1) + { + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + + VectorCopy ( test, pmove->origin ); + return 0; + } + nReps++; + } while (nReps < 54); + } + } + + // Only an issue on the client. + + if (pmove->server) + idx = 0; + else + idx = 1; + + fTime = pmove->Sys_FloatTime(); + // Too soon? + if (rgStuckCheckTime[pmove->player_index][idx] >= + ( fTime - PM_CHECKSTUCK_MINTIME ) ) + { + return 1; + } + rgStuckCheckTime[pmove->player_index][idx] = fTime; + + pmove->PM_StuckTouch( hitent, &traceresult ); + + i = PM_GetRandomStuckOffsets(pmove->player_index, pmove->server, offset); + + VectorAdd(base, offset, test); + if ( ( hitent = pmove->PM_TestPlayerPositionEx ( test, NULL, PM_Ignore ) ) == -1 ) + { + //Con_DPrintf("Nudged\n"); + + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + + if (i >= 27) + VectorCopy ( test, pmove->origin ); + + return 0; + } + + // If player is flailing while stuck in another player ( should never happen ), then see + // if we can't "unstick" them forceably. + if ( pmove->cmd.buttons & ( IN_JUMP | IN_DUCK | IN_ATTACK ) && ( pmove->physents[ hitent ].player != 0 ) ) + { + float x, y, z; + float xystep = 8.0; + float zstep = 18.0; + float xyminmax = xystep; + float zminmax = 4 * zstep; + + for ( z = 0; z <= zminmax; z += zstep ) + { + for ( x = -xyminmax; x <= xyminmax; x += xystep ) + { + for ( y = -xyminmax; y <= xyminmax; y += xystep ) + { + VectorCopy( base, test ); + test[0] += x; + test[1] += y; + test[2] += z; + + if ( pmove->PM_TestPlayerPositionEx ( test, NULL, PM_Ignore ) == -1 ) + { + VectorCopy( test, pmove->origin ); + return 0; + } + } + } + } + } + + //VectorCopy (base, pmove->origin); + + return 1; +} + +#define CHASE_DISTANCE 112 // Desired distance from target +#define CHASE_PADDING 4 // Minimum allowable distance between the view and a solid face + +// Get the origin of the Observer based around the target's position and angles +void GetChaseOrigin( vec3_t targetangles, int iTargetIndex, vec3_t offset, vec3_t *returnvec ) +{ + vec3_t forward; + vec3_t vecEnd; + vec3_t vecStart; + struct pmtrace_s *trace; + physent_t *target; + + target = &(pmove->physents[ iTargetIndex ]); + + // Trace back from the target using the player's view angles + AngleVectors(targetangles, forward, NULL, NULL); + + // Without view_ofs, just guess at adding 28 (standing player) to the origin to get the eye-height + VectorCopy( target->origin, vecStart ); + vecStart[2] += 28; + VectorMA(offset, CHASE_DISTANCE, forward, vecEnd); + VectorSubtract( vecStart, vecEnd, vecEnd ); + + trace = pmove->PM_TraceLine( vecStart, vecEnd, 0, 2, iTargetIndex ); + + // Return the position + VectorMA( trace->endpos, CHASE_PADDING, trace->plane.normal, *returnvec ); + +#ifdef CLIENT_DLL + // pmove->Con_NPrintf( 9, "vecStart %f %f %f.\n", vecStart[0], vecStart[1], vecStart[2] ); + // pmove->Con_NPrintf( 10, " vecEnd %f %f %f.\n", vecEnd[0], vecEnd[1], vecEnd[2] ); + // pmove->Con_NPrintf( 11, " EndPos %f %f %f.\n", trace->endpos[0], trace->endpos[1], trace->endpos[2] ); +#endif +} + +/* +=============== +PM_SpectatorMove +=============== +*/ +void PM_SpectatorMove (void) +{ + float speed, drop, friction, control, newspeed; + //float accel; + float currentspeed, addspeed, accelspeed; + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + +#ifdef CLIENT_DLL + if ( pmove->runfuncs ) + { + // Set spectator flag + iIsSpectator = SPEC_IS_SPECTATOR; + } +#endif + + // Are we locked onto a target? + if ( pmove->iuser2 ) + { + vec3_t vecViewAngle; + vec3_t vecNewOrg; + vec3_t vecOffset; + int i; + + // Find the client this player's targeting + for (i = 0; i < pmove->numphysent; i++) + { + if ( pmove->physents[i].info == pmove->iuser2 ) + break; + } + + if (i == pmove->numphysent) + return; + + VectorCopy( vec3_origin, vecOffset ); + + // Calculate a camera position based upon the target's origin and angles + if (pmove->iuser1 == 1) + { + // Locked onto the target + VectorCopy( pmove->physents[i].angles, vecViewAngle ); + vecViewAngle[0] = 0; + +#ifdef CLIENT_DLL + if ( pmove->runfuncs ) + { + // Force the client to start smoothing both the spectator's origin and angles + iIsSpectator |= (SPEC_SMOOTH_ANGLES | SPEC_SMOOTH_ORIGIN); + } +#endif + } + else + { + // Freelooking around the target + VectorCopy( pmove->angles, vecViewAngle ); + } + + GetChaseOrigin( vecViewAngle, i, vecOffset, &vecNewOrg); + VectorCopy( vecNewOrg, pmove->origin ); + VectorCopy( vecViewAngle, pmove->angles ); + VectorCopy( vec3_origin, pmove->velocity ); + +#ifdef CLIENT_DLL + if ( pmove->runfuncs ) + { + // Copy the desired angles into the client global var so we can force them to the player's view + VectorCopy( pmove->angles, vecNewViewAngles ); + iHasNewViewAngles = true; + VectorCopy( pmove->origin, vecNewViewOrigin ); + iHasNewViewOrigin = true; + } +#endif + } + else + { + // Move around in normal spectator method + // friction + speed = Length (pmove->velocity); + if (speed < 1) + { + VectorCopy (vec3_origin, pmove->velocity) + } + else + { + drop = 0; + + friction = pmove->movevars->friction*1.5; // extra friction + control = speed < pmove->movevars->stopspeed ? pmove->movevars->stopspeed : speed; + drop += control*friction*pmove->frametime; + + // scale the velocity + newspeed = speed - drop; + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + + VectorScale (pmove->velocity, newspeed, pmove->velocity); + } + + // accelerate + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + VectorNormalize (pmove->forward); + VectorNormalize (pmove->right); + + for (i=0 ; i<3 ; i++) + { + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + } + wishvel[2] += pmove->cmd.upmove; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + // + // clamp to server defined max speed + // + if (wishspeed > pmove->movevars->spectatormaxspeed) + { + VectorScale (wishvel, pmove->movevars->spectatormaxspeed/wishspeed, wishvel); + wishspeed = pmove->movevars->spectatormaxspeed; + } + + currentspeed = DotProduct(pmove->velocity, wishdir); + addspeed = wishspeed - currentspeed; + if (addspeed <= 0) + return; + accelspeed = pmove->movevars->accelerate*pmove->frametime*wishspeed; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i=0 ; i<3 ; i++) + pmove->velocity[i] += accelspeed*wishdir[i]; + + // move + VectorMA (pmove->origin, pmove->frametime, pmove->velocity, pmove->origin); + } +} + +/* +================== +PM_SplineFraction + +Use for ease-in, ease-out style interpolation (accel/decel) +Used by ducking code. +================== +*/ +float PM_SplineFraction( float value, float scale ) +{ + float valueSquared; + + value = scale * value; + valueSquared = value * value; + + // Nice little ease-in, ease-out spline-like curve + return 3 * valueSquared - 2 * valueSquared * value; +} + +void PM_FixPlayerCrouchStuck( int direction ) +{ + int hitent; + int i; + vec3_t test; + + hitent = pmove->PM_TestPlayerPositionEx ( pmove->origin, NULL, PM_Ignore ); + if (hitent == -1 ) + return; + + VectorCopy( pmove->origin, test ); + for ( i = 0; i < 36; i++ ) + { + pmove->origin[2] += direction; + hitent = pmove->PM_TestPlayerPositionEx ( pmove->origin, NULL, PM_Ignore ); + if (hitent == -1 ) + return; + } + + VectorCopy( test, pmove->origin ); // Failed +} + +void PM_Duck( void ) +{ + int i; + float time; + float duckFraction; + + int buttonsChanged = ( pmove->oldbuttons ^ pmove->cmd.buttons ); // These buttons have changed this frame + int nButtonPressed = buttonsChanged & pmove->cmd.buttons; // The changed ones still down are "pressed" + + if ( pmove->cmd.buttons & IN_DUCK ) + { + pmove->oldbuttons |= IN_DUCK; + } + else + { + pmove->oldbuttons &= ~IN_DUCK; + } + + // Discwar: Prevent ducking + return; + + if ( pmove->dead ) + return; + + if ( ( pmove->cmd.buttons & IN_DUCK ) || ( pmove->bInDuck ) || ( pmove->flags & FL_DUCKING ) ) + { + pmove->cmd.forwardmove *= 0.333; + pmove->cmd.sidemove *= 0.333; + pmove->cmd.upmove *= 0.333; + + if ( pmove->cmd.buttons & IN_DUCK ) + { + if ( (nButtonPressed & IN_DUCK ) && !( pmove->flags & FL_DUCKING ) ) + { + // Use 1 second so super long jump will work + pmove->flDuckTime = 1000; + pmove->bInDuck = true; + } + + time = max( 0.0, ( 1.0 - (float)pmove->flDuckTime / 1000.0 ) ); + + if ( pmove->bInDuck ) + { + // Finish ducking immediately if duck time is over or not on ground + if ( ( (float)pmove->flDuckTime / 1000.0 <= ( 1.0 - TIME_TO_DUCK ) ) || + ( pmove->onground == -1 ) ) + { + pmove->usehull = 1; + pmove->view_ofs[2] = VEC_DUCK_VIEW; + pmove->flags |= FL_DUCKING; + pmove->bInDuck = false; + + // HACKHACK - Fudge for collision bug - no time to fix this properly + if ( pmove->onground != -1 ) + { + for ( i = 0; i < 3; i++ ) + { + pmove->origin[i] -= ( pmove->player_mins[1][i] - pmove->player_mins[0][i] ); + } + // See if we are stuck? + PM_FixPlayerCrouchStuck( STUCK_MOVEUP ); + + // Recatagorize position since ducking can change origin + PM_CatagorizePosition(); + } + } + else + { + float fMore = (VEC_DUCK_HULL_MIN - VEC_HULL_MIN); + + // Calc parametric time + duckFraction = PM_SplineFraction( time, (1.0/TIME_TO_DUCK) ); + pmove->view_ofs[2] = ((VEC_DUCK_VIEW - fMore ) * duckFraction) + (VEC_VIEW * (1-duckFraction)); + } + } + } + else + { + pmtrace_t trace; + vec3_t newOrigin; + + VectorCopy( pmove->origin, newOrigin ); + + if ( pmove->onground != -1 ) + { + for ( i = 0; i < 3; i++ ) + { + newOrigin[i] += ( pmove->player_mins[1][i] - pmove->player_mins[0][i] ); + } + } + + trace = pmove->PM_PlayerTraceEx ( newOrigin, newOrigin, PM_NORMAL, PM_Ignore ); + + if ( !trace.startsolid ) + { + pmove->usehull = 0; + + // Oh, no, changing hulls stuck us into something, try unsticking downward first. + trace = pmove->PM_PlayerTraceEx( newOrigin, newOrigin, PM_NORMAL, PM_Ignore ); + if ( trace.startsolid ) + { + // See if we are stuck? If so, stay ducked with the duck hull until we have a clear spot + //Con_Printf( "unstick got stuck\n" ); + pmove->usehull = 1; + return; + } + + pmove->flags &= ~FL_DUCKING; + pmove->bInDuck = false; + pmove->view_ofs[2] = VEC_VIEW; + pmove->flDuckTime = 0; + + VectorCopy( newOrigin, pmove->origin ); + + // Recatagorize position since ducking can change origin + PM_CatagorizePosition(); + } + } + } +} + +void PM_LadderMove( physent_t *pLadder ) +{ + vec3_t ladderCenter; + trace_t trace; + qboolean onFloor; + vec3_t floor; + vec3_t modelmins, modelmaxs; + + if ( pmove->movetype == MOVETYPE_NOCLIP ) + return; + + pmove->PM_GetModelBounds( pLadder->model, modelmins, modelmaxs ); + + VectorAdd( modelmins, modelmaxs, ladderCenter ); + VectorScale( ladderCenter, 0.5, ladderCenter ); + + pmove->movetype = MOVETYPE_FLY; + + // On ladder, convert movement to be relative to the ladder + + VectorCopy( pmove->origin, floor ); + floor[2] += pmove->player_mins[pmove->usehull][2] - 1; + + if ( pmove->PM_PointContents( floor, NULL ) == CONTENTS_SOLID ) + onFloor = true; + else + onFloor = false; + + pmove->gravity = 0; + pmove->PM_TraceModel( pLadder, pmove->origin, ladderCenter, &trace ); + if ( trace.fraction != 1.0 ) + { + float forward = 0, right = 0; + vec3_t vpn, v_right; + + AngleVectors( pmove->angles, vpn, v_right, NULL ); + if ( pmove->cmd.buttons & IN_BACK ) + forward -= MAX_CLIMB_SPEED; + if ( pmove->cmd.buttons & IN_FORWARD ) + forward += MAX_CLIMB_SPEED; + if ( pmove->cmd.buttons & IN_MOVELEFT ) + right -= MAX_CLIMB_SPEED; + if ( pmove->cmd.buttons & IN_MOVERIGHT ) + right += MAX_CLIMB_SPEED; + + if ( pmove->cmd.buttons & IN_JUMP ) + { + pmove->movetype = MOVETYPE_WALK; + VectorScale( trace.plane.normal, 270, pmove->velocity ); + } + else + { + if ( forward != 0 || right != 0 ) + { + vec3_t velocity, perp, cross, lateral, tmp; + float normal; + + //ALERT(at_console, "pev %.2f %.2f %.2f - ", + // pev->velocity.x, pev->velocity.y, pev->velocity.z); + // Calculate player's intended velocity + //Vector velocity = (forward * gpGlobals->v_forward) + (right * gpGlobals->v_right); + VectorScale( vpn, forward, velocity ); + VectorMA( velocity, right, v_right, velocity ); + + + // Perpendicular in the ladder plane + // Vector perp = CrossProduct( Vector(0,0,1), trace.vecPlaneNormal ); + // perp = perp.Normalize(); + VectorClear( tmp ); + tmp[2] = 1; + CrossProduct( tmp, trace.plane.normal, perp ); + VectorNormalize( perp ); + + + // decompose velocity into ladder plane + normal = DotProduct( velocity, trace.plane.normal ); + // This is the velocity into the face of the ladder + VectorScale( trace.plane.normal, normal, cross ); + + + // This is the player's additional velocity + VectorSubtract( velocity, cross, lateral ); + + // This turns the velocity into the face of the ladder into velocity that + // is roughly vertically perpendicular to the face of the ladder. + // NOTE: It IS possible to face up and move down or face down and move up + // because the velocity is a sum of the directional velocity and the converted + // velocity through the face of the ladder -- by design. + CrossProduct( trace.plane.normal, perp, tmp ); + VectorMA( lateral, -normal, tmp, pmove->velocity ); + if ( onFloor && normal > 0 ) // On ground moving away from the ladder + { + VectorMA( pmove->velocity, MAX_CLIMB_SPEED, trace.plane.normal, pmove->velocity ); + } + //pev->velocity = lateral - (CrossProduct( trace.vecPlaneNormal, perp ) * normal); + } + else + { + VectorClear( pmove->velocity ); + } + } + } +} + +physent_t *PM_Ladder( void ) +{ + int i; + physent_t *pe; + hull_t *hull; + int num; + vec3_t test; + + for ( i = 0; i < pmove->nummoveent; i++ ) + { + pe = &pmove->moveents[i]; + + if ( pe->model && (modtype_t)pmove->PM_GetModelType( pe->model ) == mod_brush && pe->skin == CONTENTS_LADDER ) + { + + hull = (hull_t *)pmove->PM_HullForBsp( pe, test ); + num = hull->firstclipnode; + + // Offset the test point appropriately for this hull. + VectorSubtract ( pmove->origin, test, test); + + // Test the player's hull for intersection with this model + if ( pmove->PM_HullPointContents (hull, num, test) == CONTENTS_EMPTY) + continue; + + return pe; + } + } + + return NULL; +} + + + +void PM_WaterJump (void) +{ + if ( pmove->waterjumptime > 10000 ) + { + pmove->waterjumptime = 10000; + } + + if ( !pmove->waterjumptime ) + return; + + pmove->waterjumptime -= pmove->cmd.msec; + if ( pmove->waterjumptime < 0 || + !pmove->waterlevel ) + { + pmove->waterjumptime = 0; + pmove->flags &= ~FL_WATERJUMP; + } + + pmove->velocity[0] = pmove->movedir[0]; + pmove->velocity[1] = pmove->movedir[1]; +} + +/* +============ +PM_AddGravity + +============ +*/ +void PM_AddGravity () +{ + float ent_gravity; + + if (pmove->gravity) + ent_gravity = pmove->gravity; + else + ent_gravity = 1.0; + + // Add gravity incorrectly + pmove->velocity[2] -= (ent_gravity * pmove->movevars->gravity * pmove->frametime ); + pmove->velocity[2] += pmove->basevelocity[2] * pmove->frametime; + pmove->basevelocity[2] = 0; + PM_CheckVelocity(); +} +/* +============ +PM_PushEntity + +Does not change the entities velocity at all +============ +*/ +pmtrace_t PM_PushEntity (vec3_t push) +{ + pmtrace_t trace; + vec3_t end; + + VectorAdd (pmove->origin, push, end); + + trace = pmove->PM_PlayerTraceEx (pmove->origin, end, PM_NORMAL, PM_Ignore ); + + VectorCopy (trace.endpos, pmove->origin); + + // So we can run impact function afterwards. + if (trace.fraction < 1.0 && + !trace.allsolid) + { + PM_AddToTouched(trace, pmove->velocity); + } + + return trace; +} + +/* +============ +PM_Physics_Toss() + +Dead player flying through air., e.g. +============ +*/ +void PM_Physics_Toss() +{ + pmtrace_t trace; + vec3_t move; + float backoff; + + PM_CheckWater(); + + if (pmove->velocity[2] > 0) + pmove->onground = -1; + + // If on ground and not moving, return. + if ( pmove->onground != -1 ) + { + if (VectorCompare(pmove->basevelocity, vec3_origin) && + VectorCompare(pmove->velocity, vec3_origin)) + return; + } + + PM_CheckVelocity (); + +// add gravity + if ( pmove->movetype != MOVETYPE_FLY && + pmove->movetype != MOVETYPE_BOUNCEMISSILE && + pmove->movetype != MOVETYPE_FLYMISSILE ) + PM_AddGravity (); + +// move origin + // Base velocity is not properly accounted for since this entity will move again after the bounce without + // taking it into account + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity); + + PM_CheckVelocity(); + VectorScale (pmove->velocity, pmove->frametime, move); + VectorSubtract (pmove->velocity, pmove->basevelocity, pmove->velocity); + + trace = PM_PushEntity (move); // Should this clear basevelocity + + PM_CheckVelocity(); + + if (trace.allsolid) + { + // entity is trapped in another solid + pmove->onground = trace.ent; + VectorCopy (vec3_origin, pmove->velocity); + return; + } + + if (trace.fraction == 1) + { + PM_CheckWater(); + return; + } + + + if (pmove->movetype == MOVETYPE_BOUNCE) + backoff = 2.0 - pmove->friction; + else if (pmove->movetype == MOVETYPE_BOUNCEMISSILE) + backoff = 2.0; + else + backoff = 1; + + PM_ClipVelocity (pmove->velocity, trace.plane.normal, pmove->velocity, backoff); + + // stop if on ground + if (trace.plane.normal[2] > 0.7) + { + float vel; + vec3_t base; + + VectorClear( base ); + if (pmove->velocity[2] < pmove->movevars->gravity * pmove->frametime) + { + // we're rolling on the ground, add static friction. + pmove->onground = trace.ent; + pmove->velocity[2] = 0; + } + + vel = DotProduct( pmove->velocity, pmove->velocity ); + + // Con_DPrintf("%f %f: %.0f %.0f %.0f\n", vel, trace.fraction, ent->velocity[0], ent->velocity[1], ent->velocity[2] ); + + if (vel < (30 * 30) || (pmove->movetype != MOVETYPE_BOUNCE && pmove->movetype != MOVETYPE_BOUNCEMISSILE)) + { + pmove->onground = trace.ent; + VectorCopy (vec3_origin, pmove->velocity); + } + else + { + VectorScale (pmove->velocity, (1.0 - trace.fraction) * pmove->frametime * 0.9, move); + trace = PM_PushEntity (move); + } + VectorSubtract( pmove->velocity, base, pmove->velocity ) + } + +// check for in water + PM_CheckWater(); +} + +/* +==================== +PM_NoClip + +==================== +*/ +void PM_NoClip() +{ + int i; + vec3_t wishvel; + float fmove, smove; +// float currentspeed, addspeed, accelspeed; + + // Copy movement amounts + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + VectorNormalize ( pmove->forward ); + VectorNormalize ( pmove->right ); + + for (i=0 ; i<3 ; i++) // Determine x and y parts of velocity + { + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + } + wishvel[2] += pmove->cmd.upmove; + + VectorMA (pmove->origin, pmove->frametime, wishvel, pmove->origin); + + // Zero out the velocity so that we don't accumulate a huge downward velocity from + // gravity, etc. + VectorClear( pmove->velocity ); + +} + +/* +============= +PM_Jump +============= +*/ +void PM_Jump (void) +{ + int i; + qboolean tfc = false; + + qboolean cansuperjump = false; + + // Discwar: Prevent jumping + return; + + if (pmove->dead) + { + pmove->oldbuttons |= IN_JUMP ; // don't jump again until released + 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 ) + { + pmove->waterjumptime -= pmove->cmd.msec; + if (pmove->waterjumptime < 0) + { + pmove->waterjumptime = 0; + } + return; + } + + // If we are in the water most of the way... + if (pmove->waterlevel >= 2) + { // swimming, not jumping + pmove->onground = -1; + + if (pmove->watertype == CONTENTS_WATER) // We move up a certain amount + pmove->velocity[2] = 100; + else if (pmove->watertype == CONTENTS_SLIME) + pmove->velocity[2] = 80; + else // LAVA + pmove->velocity[2] = 50; + + // play swiming sound + if ( pmove->flSwimTime <= 0 ) + { + // Don't play sound again for 1 second + pmove->flSwimTime = 1000; + switch ( pmove->RandomLong( 0, 3 ) ) + { + case 0: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 1: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 2: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 3: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + } + } + + return; + } + + // No more effect + if ( pmove->onground == -1 ) + { + // Flag that we jumped. + // HACK HACK HACK + // Remove this when the game .dll no longer does physics code!!!! + pmove->oldbuttons |= IN_JUMP; // don't jump again until released + return; // in air, so no effect + } + + if ( pmove->oldbuttons & IN_JUMP ) + return; // don't pogo stick + + // In the air now. + pmove->onground = -1; + + 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 ); + } + + // See if user can super long jump? + cansuperjump = atoi( pmove->PM_Info_ValueForKey( pmove->physinfo, "slj" ) ) == 1 ? true : false; + + // Acclerate upward + // If we are ducking... + if ( ( pmove->bInDuck ) || ( pmove->flags & FL_DUCKING ) ) + { + // Adjust for super long jump module + // UNDONE -- note this should be based on forward angles, not current velocity. + if ( cansuperjump && + ( pmove->cmd.buttons & IN_DUCK ) && + ( pmove->flDuckTime > 0 ) && + Length( pmove->velocity ) > 50 ) + { + pmove->punchangle[0] = -5; + + for (i =0; i < 2; i++) + { + pmove->velocity[i] = pmove->forward[i] * PLAYER_LONGJUMP_SPEED * 1.6; + } + + pmove->velocity[2] = sqrt(2 * 800 * 56.0); + } + else + { + pmove->velocity[2] = sqrt(2 * 800 * 45.0); + } + } + else + { + pmove->velocity[2] = sqrt(2 * 800 * 45.0); + } + + // Decay it for simulation + PM_FixupGravityVelocity(); + + // Flag that we jumped. + pmove->oldbuttons |= IN_JUMP; // don't jump again until released +} + +/* +============= +PM_CheckWaterJump +============= +*/ +#define WJ_HEIGHT 8 +void PM_CheckWaterJump (void) +{ + vec3_t vecStart, vecEnd; + vec3_t flatforward; + vec3_t flatvelocity; + float curspeed; + pmtrace_t tr; + int savehull; + + // Already water jumping. + if ( pmove->waterjumptime ) + return; + + // Don't hop out if we just jumped in + if ( pmove->velocity[2] < -180 ) + return; // only hop out if we are moving up + + // See if we are backing up + flatvelocity[0] = pmove->velocity[0]; + flatvelocity[1] = pmove->velocity[1]; + flatvelocity[2] = 0; + + // Must be moving + curspeed = VectorNormalize( flatvelocity ); + + // see if near an edge + flatforward[0] = pmove->forward[0]; + flatforward[1] = pmove->forward[1]; + flatforward[2] = 0; + VectorNormalize (flatforward); + + // Are we backing into water from steps or something? If so, don't pop forward + if ( curspeed != 0.0 && ( DotProduct( flatvelocity, flatforward ) < 0.0 ) ) + return; + + VectorCopy( pmove->origin, vecStart ); + vecStart[2] += WJ_HEIGHT; + + VectorMA ( vecStart, 24, flatforward, vecEnd ); + + // Trace, this trace should use the point sized collision hull + savehull = pmove->usehull; + pmove->usehull = 2; + tr = pmove->PM_PlayerTraceEx( vecStart, vecEnd, PM_NORMAL, PM_Ignore ); + if ( tr.fraction < 1.0 && fabs( tr.plane.normal[2] ) < 0.1f ) // Facing a near vertical wall? + { + vecStart[2] += pmove->player_maxs[ savehull ][2] - WJ_HEIGHT; + VectorMA( vecStart, 24, flatforward, vecEnd ); + VectorMA( vec3_origin, -50, tr.plane.normal, pmove->movedir ); + + tr = pmove->PM_PlayerTraceEx( vecStart, vecEnd, PM_NORMAL, PM_Ignore ); + if ( tr.fraction == 1.0 ) + { + pmove->waterjumptime = 2000; + pmove->velocity[2] = 225; + pmove->oldbuttons |= IN_JUMP; + pmove->flags |= FL_WATERJUMP; + } + } + + // Reset the collision hull + pmove->usehull = savehull; +} + +void PM_CheckFalling( void ) +{ + if ( pmove->onground != -1 && + !pmove->dead && + pmove->flFallVelocity >= PLAYER_FALL_PUNCH_THRESHHOLD ) + { + float fvol = 0.5; + + if ( pmove->waterlevel > 0 ) + { + } + else if ( pmove->flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED ) + { + // NOTE: In the original game dll , there were no breaks after these cases, causing the first one to + // cascade into the second + //switch ( RandomLong(0,1) ) + //{ + //case 0: + //pmove->PM_PlaySound( CHAN_VOICE, "player/pl_fallpain2.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + //break; + //case 1: + pmove->PM_PlaySound( CHAN_VOICE, "player/pl_fallpain3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + // break; + //} + fvol = 1.0; + } + 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 ) + { + fvol = 0; + } + + if ( fvol > 0.0 ) + { + // Play landing step right away + pmove->flTimeStepSound = 0; + + PM_UpdateStepSound(); + + // play step sound for current texture + PM_PlayStepSound( PM_MapTextureTypeStepType( pmove->chtexturetype ), fvol ); + + // Knock the screen around a little bit, temporary effect + //pmove->punchangle[ 2 ] = pmove->flFallVelocity * 0.013; // punch z axis + + if ( pmove->punchangle[ 0 ] > 8 ) + { + pmove->punchangle[ 0 ] = 8; + } + } + } + + if ( pmove->onground != -1 ) + { + pmove->flFallVelocity = 0; + } +} + +/* +================= +PM_PlayWaterSounds + +================= +*/ +void PM_PlayWaterSounds( void ) +{ + // Did we enter or leave water? + if ( ( pmove->oldwaterlevel == 0 && pmove->waterlevel != 0 ) || + ( pmove->oldwaterlevel != 0 && pmove->waterlevel == 0 ) ) + { + switch ( pmove->RandomLong(0,3) ) + { + case 0: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 1: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 2: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 3: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + } + } +} + +/* +=============== +PM_CalcRoll + +=============== +*/ +float PM_CalcRoll (vec3_t angles, vec3_t velocity, float rollangle, float rollspeed ) +{ + float sign; + float side; + float value; + vec3_t forward, right, up; + + AngleVectors (angles, forward, right, up); + + side = DotProduct (velocity, right); + + sign = side < 0 ? -1 : 1; + + side = fabs(side); + + value = rollangle; + + if (side < rollspeed) + { + side = side * value / rollspeed; + } + else + { + side = value; + } + + return side * sign; +} + +/* +============= +PM_DropPunchAngle + +============= +*/ +void PM_DropPunchAngle ( vec3_t punchangle ) +{ + float len; + + len = VectorNormalize ( punchangle ); + len -= (10.0 + len * 0.5) * pmove->frametime; + len = max( len, 0.0 ); + VectorScale ( punchangle, len, punchangle); +} + +/* +============== +PM_CheckParamters + +============== +*/ +void PM_CheckParamters( void ) +{ + float spd; + float maxspeed; + vec3_t v_angle; + + spd = ( pmove->cmd.forwardmove * pmove->cmd.forwardmove ) + + ( pmove->cmd.sidemove * pmove->cmd.sidemove ) + + ( pmove->cmd.upmove * pmove->cmd.upmove ); + spd = sqrt( spd ); + + maxspeed = pmove->clientmaxspeed; //atof( pmove->PM_Info_ValueForKey( pmove->physinfo, "maxspd" ) ); + if ( maxspeed != 0.0 ) + { + pmove->maxspeed = min( maxspeed, pmove->maxspeed ); + } + + if ( ( spd != 0.0 ) && + ( spd > pmove->maxspeed ) ) + { + float fRatio = pmove->maxspeed / spd; + pmove->cmd.forwardmove *= fRatio; + pmove->cmd.sidemove *= fRatio; + pmove->cmd.upmove *= fRatio; + } + + if ( pmove->flags & FL_FROZEN || + pmove->flags & FL_ONTRAIN || + pmove->dead ) + { + pmove->cmd.forwardmove = 0; + pmove->cmd.sidemove = 0; + pmove->cmd.upmove = 0; + } + + + PM_DropPunchAngle( pmove->punchangle ); + + // Take angles from command. + if ( !pmove->dead ) + { + VectorCopy ( pmove->cmd.viewangles, v_angle ); + VectorAdd( v_angle, pmove->punchangle, v_angle ); + + // Set up view angles. + pmove->angles[ROLL] = PM_CalcRoll ( v_angle, pmove->velocity, pmove->movevars->rollangle, pmove->movevars->rollspeed )*4; + pmove->angles[PITCH] = v_angle[PITCH]; + pmove->angles[YAW] = v_angle[YAW]; + } + else + { + VectorCopy( pmove->oldangles, pmove->angles ); + } + + // Set dead player view_offset + if ( pmove->dead ) + { + pmove->view_ofs[2] = PM_DEAD_VIEWHEIGHT; + } + + // Adjust client view angles to match values used on server. + if (pmove->angles[YAW] > 180.0f) + { + pmove->angles[YAW] -= 360.0f; + } + +} + +void PM_ReduceTimers( void ) +{ + if ( pmove->flTimeStepSound > 0 ) + { + pmove->flTimeStepSound -= pmove->cmd.msec; + if ( pmove->flTimeStepSound < 0 ) + { + pmove->flTimeStepSound = 0; + } + } + if ( pmove->flDuckTime > 0 ) + { + pmove->flDuckTime -= pmove->cmd.msec; + if ( pmove->flDuckTime < 0 ) + { + pmove->flDuckTime = 0; + } + } + if ( pmove->flSwimTime > 0 ) + { + pmove->flSwimTime -= pmove->cmd.msec; + if ( pmove->flSwimTime < 0 ) + { + pmove->flSwimTime = 0; + } + } +} + +/* +============= +PlayerMove + +Returns with origin, angles, and velocity modified in place. + +Numtouch and touchindex[] will be set if any of the physents +were contacted during the move. +============= +*/ +void PM_PlayerMove ( qboolean server ) +{ + physent_t *pLadder = NULL; + + // Are we running server code? + pmove->server = server; + + // Adjust speeds etc. + PM_CheckParamters(); + + // Assume we don't touch anything + pmove->numtouch = 0; + + // # of msec to apply movement + pmove->frametime = pmove->cmd.msec * 0.001; + + PM_ReduceTimers(); + + // Convert view angles to vectors + AngleVectors (pmove->angles, pmove->forward, pmove->right, pmove->up); + + // PM_ShowClipBox(); + +#ifdef CLIENT_DLL + if ( pmove->runfuncs ) + { + iIsSpectator = false; + iHasNewViewAngles = false; + iHasNewViewOrigin = false; + } +#endif + + if ( pmove->iuser1 == 4 ) + return; + + // Special handling for spectator and observers. (iuser1 is set if the player's in observer mode) + if ( pmove->spectator || pmove->iuser1 > 0 ) + { + PM_SpectatorMove(); + PM_CatagorizePosition(); + return; + } + + // Always try and unstick us unless we are in NOCLIP mode + if ( pmove->movetype != MOVETYPE_NOCLIP && pmove->movetype != MOVETYPE_NONE ) + { + if ( PM_CheckStuck() ) + { + return; // Can't move, we're stuck + } + } + + // Now that we are "unstuck", see where we are ( waterlevel and type, pmove->onground ). + PM_CatagorizePosition(); + + // Store off the starting water level + pmove->oldwaterlevel = pmove->waterlevel; + + // If we are not on ground, store off how fast we are moving down + if ( pmove->onground == -1 ) + { + pmove->flFallVelocity = -pmove->velocity[2]; + } + + g_onladder = 0; + // Don't run ladder code if dead or on a train + if ( !pmove->dead && !(pmove->flags & FL_ONTRAIN) ) + { + pLadder = PM_Ladder(); + if ( pLadder ) + { + g_onladder = 1; + } + } + + PM_UpdateStepSound(); + + PM_Duck(); + + // Don't run ladder code if dead or on a train + if ( !pmove->dead && !(pmove->flags & FL_ONTRAIN) ) + { + if ( pLadder ) + { + PM_LadderMove( pLadder ); + } + else if ( pmove->movetype != MOVETYPE_WALK && + pmove->movetype != MOVETYPE_NOCLIP ) + { + // Clear ladder stuff unless player is noclipping + // it will be set immediately again next frame if necessary + pmove->movetype = MOVETYPE_WALK; + } + } + + // Slow down, I'm pulling it! (a box maybe) but only when I'm standing on ground + if ( ( pmove->onground != -1 ) && ( pmove->cmd.buttons & IN_USE) ) + { + VectorScale( pmove->velocity, 0.3, pmove->velocity ); + } + + // Handle movement + switch ( pmove->movetype ) + { + default: + pmove->Con_DPrintf("Bogus pmove player movetype %i on (%i) 0=cl 1=sv\n", pmove->movetype, pmove->server); + break; + + case MOVETYPE_NONE: + break; + + case MOVETYPE_NOCLIP: + PM_NoClip(); + break; + + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + PM_Physics_Toss(); + break; + + case MOVETYPE_FLY: + + PM_CheckWater(); + + // Was jump button pressed? + // If so, set velocity to 270 away from ladder. This is currently wrong. + // Also, set MOVE_TYPE to walk, too. + if ( pmove->cmd.buttons & IN_JUMP ) + { + if ( !pLadder ) + { + PM_Jump (); + } + } + else + { + pmove->oldbuttons &= ~IN_JUMP; + } + + // Perform the move accounting for any base velocity. + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity); + PM_FlyMove (); + VectorSubtract (pmove->velocity, pmove->basevelocity, pmove->velocity); + break; + + case MOVETYPE_WALK: + if ( !PM_InWater() ) + { + PM_AddCorrectGravity(); + } + + // If we are leaping out of the water, just update the counters. + if ( pmove->waterjumptime ) + { + PM_WaterJump(); + PM_FlyMove(); + + // Make sure waterlevel is set correctly + PM_CheckWater(); + return; + } + + // If we are swimming in the water, see if we are nudging against a place we can jump up out + // of, and, if so, start out jump. Otherwise, if we are not moving up, then reset jump timer to 0 + if ( pmove->waterlevel >= 2 ) + { + if ( pmove->waterlevel == 2 ) + { + PM_CheckWaterJump(); + } + + // If we are falling again, then we must not trying to jump out of water any more. + if ( pmove->velocity[2] < 0 && pmove->waterjumptime ) + { + pmove->waterjumptime = 0; + } + + // Was jump button pressed? + if (pmove->cmd.buttons & IN_JUMP) + { + PM_Jump (); + } + else + { + pmove->oldbuttons &= ~IN_JUMP; + } + + // Perform regular water movement + PM_WaterMove(); + + VectorSubtract (pmove->velocity, pmove->basevelocity, pmove->velocity); + + // Get a final position + PM_CatagorizePosition(); + } + else + + // Not underwater + { + // Was jump button pressed? + if ( pmove->cmd.buttons & IN_JUMP ) + { + if ( !pLadder ) + { + PM_Jump (); + } + } + else + { + pmove->oldbuttons &= ~IN_JUMP; + } + + // Fricion is handled before we add in any base velocity. That way, if we are on a conveyor, + // we don't slow when standing still, relative to the conveyor. + if ( pmove->onground != -1 ) + { + pmove->velocity[2] = 0.0; + PM_Friction(); + } + + // Make sure velocity is valid. + PM_CheckVelocity(); + + // Are we on ground now + if ( pmove->onground != -1 ) + { + PM_WalkMove(); + } + else + { + PM_AirMove(); // Take into account movement when in air. + } + + // Set final flags. + PM_CatagorizePosition(); + + // Now pull the base velocity back out. + // Base velocity is set if you are on a moving object, like + // a conveyor (or maybe another monster?) + VectorSubtract (pmove->velocity, pmove->basevelocity, pmove->velocity ); + + // Make sure velocity is valid. + PM_CheckVelocity(); + + // Add any remaining gravitational component. + if ( !PM_InWater() ) + { + PM_FixupGravityVelocity(); + } + + // If we are on ground, no downward velocity. + if ( pmove->onground != -1 ) + { + pmove->velocity[2] = 0; + } + + // See if we landed on the ground with enough force to play + // a landing sound. + PM_CheckFalling(); + } + + // Did we enter or leave the water? + PM_PlayWaterSounds(); + break; + } +} + +void PM_CreateStuckTable( void ) +{ + float x, y, z; + int idx; + int i; + float zi[3]; + + memset(rgv3tStuckTable, 0, 54 * sizeof(vec3_t)); + + idx = 0; + // Little Moves. + x = y = 0; + // Z moves + for (z = -0.125 ; z <= 0.125 ; z += 0.125) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + x = z = 0; + // Y moves + for (y = -0.125 ; y <= 0.125 ; y += 0.125) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + y = z = 0; + // X moves + for (x = -0.125 ; x <= 0.125 ; x += 0.125) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + + // Remaining multi axis nudges. + for ( x = - 0.125; x <= 0.125; x += 0.250 ) + { + for ( y = - 0.125; y <= 0.125; y += 0.250 ) + { + for ( z = - 0.125; z <= 0.125; z += 0.250 ) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + } + } + + // Big Moves. + x = y = 0; + zi[0] = 0.0f; + zi[1] = 1.0f; + zi[2] = 6.0f; + + for (i = 0; i < 3; i++) + { + // Z moves + z = zi[i]; + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + + x = z = 0; + + // Y moves + for (y = -2.0f ; y <= 2.0f ; y += 2.0) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + y = z = 0; + // X moves + for (x = -2.0f ; x <= 2.0f ; x += 2.0f) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + + // Remaining multi axis nudges. + for (i = 0 ; i < 3; i++) + { + z = zi[i]; + + for (x = -2.0f ; x <= 2.0f ; x += 2.0f) + { + for (y = -2.0f ; y <= 2.0f ; y += 2.0) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + } + } +} + + + +/* +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 +invoked by each side as appropriate. There should be no distinction, internally, between server +and client. This will ensure that prediction behaves appropriately. +*/ + +void PM_Move ( struct playermove_s *ppmove, int server ) +{ + assert( pm_shared_initialized ); + + pmove = ppmove; + + PM_PlayerMove( ( server != 0 ) ? true : false ); + + if ( pmove->onground != -1 ) + { + pmove->flags |= FL_ONGROUND; + } + else + { + pmove->flags &= ~FL_ONGROUND; + } + + // In single player, reset friction after each movement to FrictionModifier Triggers work still. + if ( !pmove->multiplayer && ( pmove->movetype == MOVETYPE_WALK ) ) + { + pmove->friction = 1.0f; + } +} + +int PM_GetInfo( int ent ) +{ + if ( ent >= 0 && ent <= pmove->numvisent ) + { + return pmove->visents[ ent ].info; + } + return -1; +} + +void PM_Init( struct playermove_s *ppmove ) +{ + assert( !pm_shared_initialized ); + + pmove = ppmove; + + PM_CreateStuckTable(); + PM_InitTextureTypes(); + + pm_shared_initialized = 1; +} diff --git a/hlsdk/ricochet/pm_shared/pm_shared.h b/hlsdk/ricochet/pm_shared/pm_shared.h new file mode 100644 index 00000000..32ae7e15 --- /dev/null +++ b/hlsdk/ricochet/pm_shared/pm_shared.h @@ -0,0 +1,36 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +// +// pm_shared.h +// +#if !defined( PM_SHAREDH ) +#define PM_SHAREDH +#ifdef _WIN32 +#ifndef __MINGW32__ +#pragma once +#endif /* not __MINGW32__ */ +#endif + +void PM_Init( struct playermove_s *ppmove ); +void PM_Move ( struct playermove_s *ppmove, int server ); +char PM_FindTextureType( char *name ); + +// Spectator flags +#define SPEC_IS_SPECTATOR (1<<0) +#define SPEC_SMOOTH_ANGLES (1<<1) +#define SPEC_SMOOTH_ORIGIN (1<<2) + +#endif diff --git a/hlsdk/utils/bspinfo/bspinfo.c b/hlsdk/utils/bspinfo/bspinfo.c new file mode 100644 index 00000000..7a7d97a1 --- /dev/null +++ b/hlsdk/utils/bspinfo/bspinfo.c @@ -0,0 +1,48 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "cmdlib.h" +#include "mathlib.h" +#include "bspfile.h" + +void main (int argc, char **argv) +{ + int i; + char source[1024]; + int size; + FILE *f; + + printf( "bspinfo.exe v2.1 (%s)\n", __DATE__ ); + printf ("---- bspinfo ----\n" ); + + + if (argc == 1) + Error ("usage: bspinfo bspfile [bspfiles]"); + + for (i=1 ; i +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=bspinfo - 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 "bspinfo.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 "bspinfo.mak" CFG="bspinfo - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "bspinfo - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "bspinfo - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/SDKSrc/Tools/utils/bspinfo", HUGBAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "bspinfo - 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 ".\Release" +# PROP Intermediate_Dir ".\Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /GX /O2 /I "..\..\common" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /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 /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 /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "bspinfo - 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 ".\Debug" +# PROP Intermediate_Dir ".\Debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /Gm /GX /ZI /Od /I "..\..\common" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /YX /FD /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 /nologo /subsystem:console /debug /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:console /debug /machine:I386 + +!ENDIF + +# Begin Target + +# Name "bspinfo - Win32 Release" +# Name "bspinfo - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=..\..\common\bspfile.c +# End Source File +# Begin Source File + +SOURCE=..\bspinfo.c +# End Source File +# Begin Source File + +SOURCE=..\..\common\cmdlib.c +# End Source File +# Begin Source File + +SOURCE=..\..\common\scriplib.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=..\..\common\bspfile.h +# End Source File +# Begin Source File + +SOURCE=..\..\common\cmdlib.h +# End Source File +# Begin Source File + +SOURCE=..\..\common\scriplib.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/hlsdk/utils/bspinfo/msvc6/bspinfo.dsw b/hlsdk/utils/bspinfo/msvc6/bspinfo.dsw new file mode 100644 index 00000000..2e0e97d7 --- /dev/null +++ b/hlsdk/utils/bspinfo/msvc6/bspinfo.dsw @@ -0,0 +1,37 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "bspinfo"=.\bspinfo.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/SDKSrc/Tools/utils/bspinfo", HUGBAAAA + . + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ + begin source code control + "$/SDKSrc/Tools/utils/bspinfo", HUGBAAAA + . + end source code control +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/hlsdk/utils/bspinfo/msvc7/bspinfo.vcproj b/hlsdk/utils/bspinfo/msvc7/bspinfo.vcproj new file mode 100644 index 00000000..ad130b24 --- /dev/null +++ b/hlsdk/utils/bspinfo/msvc7/bspinfo.vcproj @@ -0,0 +1,233 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hlsdk/utils/bspinfo/msvc8/bspinfo.vcproj b/hlsdk/utils/bspinfo/msvc8/bspinfo.vcproj new file mode 100644 index 00000000..6e92fdbb --- /dev/null +++ b/hlsdk/utils/bspinfo/msvc8/bspinfo.vcproj @@ -0,0 +1,321 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hlsdk/utils/common/bspfile.c b/hlsdk/utils/common/bspfile.c new file mode 100644 index 00000000..688f10da --- /dev/null +++ b/hlsdk/utils/common/bspfile.c @@ -0,0 +1,709 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "cmdlib.h" +#include "mathlib.h" +#include "bspfile.h" +#include "scriplib.h" + +//============================================================================= + +int nummodels; +dmodel_t dmodels[MAX_MAP_MODELS]; +int dmodels_checksum; + +int visdatasize; +byte dvisdata[MAX_MAP_VISIBILITY]; +int dvisdata_checksum; + +int lightdatasize; +byte dlightdata[MAX_MAP_LIGHTING]; +int dlightdata_checksum; + +int texdatasize; +byte dtexdata[MAX_MAP_MIPTEX]; // (dmiptexlump_t) +int dtexdata_checksum; + +int entdatasize; +char dentdata[MAX_MAP_ENTSTRING]; +int dentdata_checksum; + +int numleafs; +dleaf_t dleafs[MAX_MAP_LEAFS]; +int dleafs_checksum; + +int numplanes; +dplane_t dplanes[MAX_MAP_PLANES]; +int dplanes_checksum; + +int numvertexes; +dvertex_t dvertexes[MAX_MAP_VERTS]; +int dvertexes_checksum; + +int numnodes; +dnode_t dnodes[MAX_MAP_NODES]; +int dnodes_checksum; + +int numtexinfo; +texinfo_t texinfo[MAX_MAP_TEXINFO]; +int texinfo_checksum; + +int numfaces; +dface_t dfaces[MAX_MAP_FACES]; +int dfaces_checksum; + +int numclipnodes; +dclipnode_t dclipnodes[MAX_MAP_CLIPNODES]; +int dclipnodes_checksum; + +int numedges; +dedge_t dedges[MAX_MAP_EDGES]; +int dedges_checksum; + +int nummarksurfaces; +unsigned short dmarksurfaces[MAX_MAP_MARKSURFACES]; +int dmarksurfaces_checksum; + +int numsurfedges; +int dsurfedges[MAX_MAP_SURFEDGES]; +int dsurfedges_checksum; + +int num_entities; +entity_t entities[MAX_MAP_ENTITIES]; + +/* +=============== +FastChecksum +=============== +*/ + +int FastChecksum(void *buffer, int bytes) +{ + int checksum = 0; + + while( bytes-- ) + checksum = _rotl(checksum, 4) ^ *((char *)buffer)++; + + return checksum; +} + +/* +=============== +CompressVis +=============== +*/ +int CompressVis (byte *vis, byte *dest) +{ + int j; + int rep; + int visrow; + byte *dest_p; + + dest_p = dest; + visrow = (numleafs + 7)>>3; + + for (j=0 ; j>3; + out = decompressed; + + do + { + if (*in) + { + *out++ = *in++; + continue; + } + + c = in[1]; + in += 2; + while (c) + { + *out++ = 0; + c--; + } + } while (out - decompressed < row); +} + +//============================================================================= + +/* +============= +SwapBSPFile + +Byte swaps all data in a bsp file. +============= +*/ +void SwapBSPFile (qboolean todisk) +{ + int i, j, c; + dmodel_t *d; + dmiptexlump_t *mtl; + + +// models + for (i=0 ; iheadnode[j] = LittleLong (d->headnode[j]); + + d->visleafs = LittleLong (d->visleafs); + d->firstface = LittleLong (d->firstface); + d->numfaces = LittleLong (d->numfaces); + + for (j=0 ; j<3 ; j++) + { + d->mins[j] = LittleFloat(d->mins[j]); + d->maxs[j] = LittleFloat(d->maxs[j]); + d->origin[j] = LittleFloat(d->origin[j]); + } + } + +// +// vertexes +// + for (i=0 ; inummiptex; + else + c = LittleLong(mtl->nummiptex); + mtl->nummiptex = LittleLong (mtl->nummiptex); + for (i=0 ; idataofs[i] = LittleLong(mtl->dataofs[i]); + } + +// +// marksurfaces +// + for (i=0 ; ilumps[lump].filelen; + ofs = header->lumps[lump].fileofs; + + if (length % size) + Error ("LoadBSPFile: odd lump size"); + + memcpy (dest, (byte *)header + ofs, length); + + return length / size; +} + +/* +============= +LoadBSPFile +============= +*/ +void LoadBSPFile (char *filename) +{ + int i; + +// +// load the file header +// + LoadFile (filename, (void **)&header); + +// swap the header + for (i=0 ; i< sizeof(dheader_t)/4 ; i++) + ((int *)header)[i] = LittleLong ( ((int *)header)[i]); + + if (header->version != BSPVERSION) + Error ("%s is version %i, not %i", filename, header->version, BSPVERSION); + + nummodels = CopyLump (LUMP_MODELS, dmodels, sizeof(dmodel_t)); + numvertexes = CopyLump (LUMP_VERTEXES, dvertexes, sizeof(dvertex_t)); + numplanes = CopyLump (LUMP_PLANES, dplanes, sizeof(dplane_t)); + numleafs = CopyLump (LUMP_LEAFS, dleafs, sizeof(dleaf_t)); + numnodes = CopyLump (LUMP_NODES, dnodes, sizeof(dnode_t)); + numtexinfo = CopyLump (LUMP_TEXINFO, texinfo, sizeof(texinfo_t)); + numclipnodes = CopyLump (LUMP_CLIPNODES, dclipnodes, sizeof(dclipnode_t)); + numfaces = CopyLump (LUMP_FACES, dfaces, sizeof(dface_t)); + nummarksurfaces = CopyLump (LUMP_MARKSURFACES, dmarksurfaces, sizeof(dmarksurfaces[0])); + numsurfedges = CopyLump (LUMP_SURFEDGES, dsurfedges, sizeof(dsurfedges[0])); + numedges = CopyLump (LUMP_EDGES, dedges, sizeof(dedge_t)); + + texdatasize = CopyLump (LUMP_TEXTURES, dtexdata, 1); + visdatasize = CopyLump (LUMP_VISIBILITY, dvisdata, 1); + lightdatasize = CopyLump (LUMP_LIGHTING, dlightdata, 1); + entdatasize = CopyLump (LUMP_ENTITIES, dentdata, 1); + + free (header); // everything has been copied out + +// +// swap everything +// + SwapBSPFile (false); + + dmodels_checksum = FastChecksum( dmodels, nummodels*sizeof(dmodels[0]) ); + dvertexes_checksum = FastChecksum( dvertexes, numvertexes*sizeof(dvertexes[0]) ); + dplanes_checksum = FastChecksum( dplanes, numplanes*sizeof(dplanes[0]) ); + dleafs_checksum = FastChecksum( dleafs, numleafs*sizeof(dleafs[0]) ); + dnodes_checksum = FastChecksum( dnodes, numnodes*sizeof(dnodes[0]) ); + texinfo_checksum = FastChecksum( texinfo, numtexinfo*sizeof(texinfo[0]) ); + dclipnodes_checksum = FastChecksum( dclipnodes, numclipnodes*sizeof(dclipnodes[0]) ); + dfaces_checksum = FastChecksum( dfaces, numfaces*sizeof(dfaces[0]) ); + dmarksurfaces_checksum = FastChecksum( dmarksurfaces, nummarksurfaces*sizeof(dmarksurfaces[0]) ); + dsurfedges_checksum = FastChecksum( dsurfedges, numsurfedges*sizeof(dsurfedges[0]) ); + dedges_checksum = FastChecksum( dedges, numedges*sizeof(dedges[0]) ); + dtexdata_checksum = FastChecksum( dtexdata, numedges*sizeof(dtexdata[0]) ); + dvisdata_checksum = FastChecksum( dvisdata, visdatasize*sizeof(dvisdata[0]) ); + dlightdata_checksum = FastChecksum( dlightdata, lightdatasize*sizeof(dlightdata[0]) ); + dentdata_checksum = FastChecksum( dentdata, entdatasize*sizeof(dentdata[0]) ); + +} + +//============================================================================ + +FILE *wadfile; +dheader_t outheader; + +void AddLump (int lumpnum, void *data, int len) +{ + lump_t *lump; + + lump = &header->lumps[lumpnum]; + + lump->fileofs = LittleLong( ftell(wadfile) ); + lump->filelen = LittleLong(len); + SafeWrite (wadfile, data, (len+3)&~3); +} + +/* +============= +WriteBSPFile + +Swaps the bsp file in place, so it should not be referenced again +============= +*/ +void WriteBSPFile (char *filename) +{ + header = &outheader; + memset (header, 0, sizeof(dheader_t)); + + SwapBSPFile (true); + + header->version = LittleLong (BSPVERSION); + + wadfile = SafeOpenWrite (filename); + SafeWrite (wadfile, header, sizeof(dheader_t)); // overwritten later + + AddLump (LUMP_PLANES, dplanes, numplanes*sizeof(dplane_t)); + AddLump (LUMP_LEAFS, dleafs, numleafs*sizeof(dleaf_t)); + AddLump (LUMP_VERTEXES, dvertexes, numvertexes*sizeof(dvertex_t)); + AddLump (LUMP_NODES, dnodes, numnodes*sizeof(dnode_t)); + AddLump (LUMP_TEXINFO, texinfo, numtexinfo*sizeof(texinfo_t)); + AddLump (LUMP_FACES, dfaces, numfaces*sizeof(dface_t)); + AddLump (LUMP_CLIPNODES, dclipnodes, numclipnodes*sizeof(dclipnode_t)); + AddLump (LUMP_MARKSURFACES, dmarksurfaces, nummarksurfaces*sizeof(dmarksurfaces[0])); + AddLump (LUMP_SURFEDGES, dsurfedges, numsurfedges*sizeof(dsurfedges[0])); + AddLump (LUMP_EDGES, dedges, numedges*sizeof(dedge_t)); + AddLump (LUMP_MODELS, dmodels, nummodels*sizeof(dmodel_t)); + + AddLump (LUMP_LIGHTING, dlightdata, lightdatasize); + AddLump (LUMP_VISIBILITY, dvisdata, visdatasize); + AddLump (LUMP_ENTITIES, dentdata, entdatasize); + AddLump (LUMP_TEXTURES, dtexdata, texdatasize); + + fseek (wadfile, 0, SEEK_SET); + SafeWrite (wadfile, header, sizeof(dheader_t)); + fclose (wadfile); +} + +//============================================================================ + +#define ENTRIES(a) (sizeof(a)/sizeof(*(a))) +#define ENTRYSIZE(a) (sizeof(*(a))) + +ArrayUsage( char *szItem, int items, int maxitems, int itemsize ) +{ + float percentage = maxitems ? items * 100.0 / maxitems : 0.0; + + printf("%-12s %7i/%-7i %7i/%-7i (%4.1f%%)", + szItem, items, maxitems, items * itemsize, maxitems * itemsize, percentage ); + if ( percentage > 80.0 ) + printf( "VERY FULL!\n" ); + else if ( percentage > 95.0 ) + printf( "SIZE DANGER!\n" ); + else if ( percentage > 99.9 ) + printf( "SIZE OVERFLOW!!!\n" ); + else + printf( "\n" ); + return items * itemsize; +} + +GlobUsage( char *szItem, int itemstorage, int maxstorage ) +{ + float percentage = maxstorage ? itemstorage * 100.0 / maxstorage : 0.0; + printf("%-12s [variable] %7i/%-7i (%4.1f%%)", + szItem, itemstorage, maxstorage, percentage ); + if ( percentage > 80.0 ) + printf( "VERY FULL!\n" ); + else if ( percentage > 95.0 ) + printf( "SIZE DANGER!\n" ); + else if ( percentage > 99.9 ) + printf( "SIZE OVERFLOW!!!\n" ); + else + printf( "\n" ); + return itemstorage; +} + +/* +============= +PrintBSPFileSizes + +Dumps info about current file +============= +*/ +void PrintBSPFileSizes (void) +{ + int numtextures = texdatasize ? ((dmiptexlump_t*)dtexdata)->nummiptex : 0; + int totalmemory = 0; + + printf("\n"); + printf("Object names Objects/Maxobjs Memory / Maxmem Fullness\n" ); + printf("------------ --------------- --------------- --------\n" ); + + totalmemory += ArrayUsage( "models", nummodels, ENTRIES(dmodels), ENTRYSIZE(dmodels) ); + totalmemory += ArrayUsage( "planes", numplanes, ENTRIES(dplanes), ENTRYSIZE(dplanes) ); + totalmemory += ArrayUsage( "vertexes", numvertexes, ENTRIES(dvertexes), ENTRYSIZE(dvertexes) ); + totalmemory += ArrayUsage( "nodes", numnodes, ENTRIES(dnodes), ENTRYSIZE(dnodes) ); + totalmemory += ArrayUsage( "texinfos", numtexinfo, ENTRIES(texinfo), ENTRYSIZE(texinfo) ); + totalmemory += ArrayUsage( "faces", numfaces, ENTRIES(dfaces), ENTRYSIZE(dfaces) ); + totalmemory += ArrayUsage( "clipnodes", numclipnodes, ENTRIES(dclipnodes), ENTRYSIZE(dclipnodes) ); + totalmemory += ArrayUsage( "leaves", numleafs, ENTRIES(dleafs), ENTRYSIZE(dleafs) ); + totalmemory += ArrayUsage( "marksurfaces", nummarksurfaces,ENTRIES(dmarksurfaces), ENTRYSIZE(dmarksurfaces) ); + totalmemory += ArrayUsage( "surfedges", numsurfedges, ENTRIES(dsurfedges), ENTRYSIZE(dsurfedges) ); + totalmemory += ArrayUsage( "edges", numedges, ENTRIES(dedges), ENTRYSIZE(dedges) ); + + totalmemory += GlobUsage( "texdata", texdatasize, sizeof(dtexdata) ); + totalmemory += GlobUsage( "lightdata", lightdatasize, sizeof(dlightdata) ); + totalmemory += GlobUsage( "visdata", visdatasize, sizeof(dvisdata) ); + totalmemory += GlobUsage( "entdata", entdatasize, sizeof(dentdata) ); + + printf( "=== Total BSP file data space used: %d bytes ===\n", totalmemory ); +} + + +/* +================= +ParseEpair +================= +*/ +epair_t *ParseEpair (void) +{ + epair_t *e; + + e = malloc (sizeof(epair_t)); + memset (e, 0, sizeof(epair_t)); + + if (strlen(token) >= MAX_KEY-1) + Error ("ParseEpar: token too long"); + e->key = copystring(token); + GetToken (false); + if (strlen(token) >= MAX_VALUE-1) + Error ("ParseEpar: token too long"); + e->value = copystring(token); + + return e; +} + + +/* +================ +ParseEntity +================ +*/ +qboolean ParseEntity (void) +{ + epair_t *e; + entity_t *mapent; + + if (!GetToken (true)) + return false; + + if (strcmp (token, "{") ) + Error ("ParseEntity: { not found"); + + if (num_entities == MAX_MAP_ENTITIES) + Error ("num_entities == MAX_MAP_ENTITIES"); + + mapent = &entities[num_entities]; + num_entities++; + + do + { + if (!GetToken (true)) + Error ("ParseEntity: EOF without closing brace"); + if (!strcmp (token, "}") ) + break; + e = ParseEpair (); + e->next = mapent->epairs; + mapent->epairs = e; + } while (1); + + return true; +} + +/* +================ +ParseEntities + +Parses the dentdata string into entities +================ +*/ +void ParseEntities (void) +{ + num_entities = 0; + ParseFromMemory (dentdata, entdatasize); + + while (ParseEntity ()) + { + } +} + + +/* +================ +UnparseEntities + +Generates the dentdata string from all the entities +================ +*/ +void UnparseEntities (void) +{ + char *buf, *end; + epair_t *ep; + char line[2048]; + int i; + + buf = dentdata; + end = buf; + *end = 0; + + for (i=0 ; inext) + { + sprintf (line, "\"%s\" \"%s\"\n", ep->key, ep->value); + strcat (end, line); + end += strlen(line); + } + strcat (end,"}\n"); + end += 2; + + if (end > buf + MAX_MAP_ENTSTRING) + Error ("Entity text too long"); + } + entdatasize = end - buf + 1; +} + + + +void SetKeyValue (entity_t *ent, char *key, char *value) +{ + epair_t *ep; + + for (ep=ent->epairs ; ep ; ep=ep->next) + if (!strcmp (ep->key, key) ) + { + free (ep->value); + ep->value = copystring(value); + return; + } + ep = malloc (sizeof(*ep)); + ep->next = ent->epairs; + ent->epairs = ep; + ep->key = copystring(key); + ep->value = copystring(value); +} + +char *ValueForKey (entity_t *ent, char *key) +{ + epair_t *ep; + + for (ep=ent->epairs ; ep ; ep=ep->next) + if (!strcmp (ep->key, key) ) + return ep->value; + return ""; +} + +vec_t FloatForKey (entity_t *ent, char *key) +{ + char *k; + + k = ValueForKey (ent, key); + return atof(k); +} + +void GetVectorForKey (entity_t *ent, char *key, vec3_t vec) +{ + char *k; + double v1, v2, v3; + + k = ValueForKey (ent, key); +// scanf into doubles, then assign, so it is vec_t size independent + v1 = v2 = v3 = 0; + sscanf (k, "%lf %lf %lf", &v1, &v2, &v3); + vec[0] = v1; + vec[1] = v2; + vec[2] = v3; +} + diff --git a/hlsdk/utils/common/bspfile.h b/hlsdk/utils/common/bspfile.h new file mode 100644 index 00000000..1804d77b --- /dev/null +++ b/hlsdk/utils/common/bspfile.h @@ -0,0 +1,332 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + + +// upper design bounds + +#define MAX_MAP_HULLS 4 + +#define MAX_MAP_MODELS 400 +#define MAX_MAP_BRUSHES 4096 +#define MAX_MAP_ENTITIES 1024 +#define MAX_MAP_ENTSTRING (128*1024) + +#define MAX_MAP_PLANES 32767 +#define MAX_MAP_NODES 32767 // because negative shorts are contents +#define MAX_MAP_CLIPNODES 32767 // +#define MAX_MAP_LEAFS 8192 +#define MAX_MAP_VERTS 65535 +#define MAX_MAP_FACES 65535 +#define MAX_MAP_MARKSURFACES 65535 +#define MAX_MAP_TEXINFO 8192 +#define MAX_MAP_EDGES 256000 +#define MAX_MAP_SURFEDGES 512000 +#define MAX_MAP_TEXTURES 512 +#define MAX_MAP_MIPTEX 0x200000 +#define MAX_MAP_LIGHTING 0x200000 +#define MAX_MAP_VISIBILITY 0x200000 + +#define MAX_MAP_PORTALS 65536 + +// key / value pair sizes + +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +//============================================================================= + + +#define BSPVERSION 30 +#define TOOLVERSION 2 + + +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 +{ + float mins[3], maxs[3]; + float origin[3]; + int headnode[MAX_MAP_HULLS]; + int visleafs; // not including the solid leaf 0 + int firstface, numfaces; +} dmodel_t; + +typedef struct +{ + int version; + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct +{ + int nummiptex; + int dataofs[4]; // [nummiptex] +} dmiptexlump_t; + +#define MIPLEVELS 4 +typedef struct miptex_s +{ + char name[16]; + unsigned width, height; + unsigned offsets[MIPLEVELS]; // four mip maps stored +} miptex_t; + + +typedef struct +{ + float point[3]; +} dvertex_t; + + +// 0-2 are axial planes +#define PLANE_X 0 +#define PLANE_Y 1 +#define PLANE_Z 2 + +// 3-5 are non-axial planes snapped to the nearest +#define PLANE_ANYX 3 +#define PLANE_ANYY 4 +#define PLANE_ANYZ 5 + +typedef struct +{ + float normal[3]; + float dist; + int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate +} dplane_t; + + + +#define CONTENTS_EMPTY -1 +#define CONTENTS_SOLID -2 +#define CONTENTS_WATER -3 +#define CONTENTS_SLIME -4 +#define CONTENTS_LAVA -5 +#define CONTENTS_SKY -6 +#define CONTENTS_ORIGIN -7 // removed at csg time +#define CONTENTS_CLIP -8 // changed to contents_solid + +#define CONTENTS_CURRENT_0 -9 +#define CONTENTS_CURRENT_90 -10 +#define CONTENTS_CURRENT_180 -11 +#define CONTENTS_CURRENT_270 -12 +#define CONTENTS_CURRENT_UP -13 +#define CONTENTS_CURRENT_DOWN -14 + +#define CONTENTS_TRANSLUCENT -15 + +// !!! if this is changed, it must be changed in asm_i386.h too !!! +typedef struct +{ + int planenum; + short children[2]; // negative numbers are -(leafs+1), not nodes + short mins[3]; // for sphere culling + short maxs[3]; + unsigned short firstface; + unsigned short numfaces; // counting both sides +} dnode_t; + +typedef struct +{ + int planenum; + short children[2]; // negative numbers are contents +} dclipnode_t; + + +typedef struct texinfo_s +{ + float vecs[2][4]; // [s/t][xyz offset] + int miptex; + int flags; +} texinfo_t; +#define TEX_SPECIAL 1 // sky or slime, no lightmap or 256 subdivision + +// note that edge 0 is never used, because negative edge nums are used for +// counterclockwise use of the edge in a face +typedef struct +{ + unsigned short v[2]; // vertex numbers +} dedge_t; + +#define MAXLIGHTMAPS 4 +typedef struct +{ + short planenum; + short side; + + int firstedge; // we must support > 64k edges + short numedges; + short texinfo; + +// lighting info + byte styles[MAXLIGHTMAPS]; + int lightofs; // start of [numstyles*surfsize] samples +} dface_t; + + + +#define AMBIENT_WATER 0 +#define AMBIENT_SKY 1 +#define AMBIENT_SLIME 2 +#define AMBIENT_LAVA 3 + +#define NUM_AMBIENTS 4 // automatic ambient sounds + +// leaf 0 is the generic CONTENTS_SOLID leaf, used for all solid areas +// all other leafs need visibility info +typedef struct +{ + int contents; + int visofs; // -1 = no visibility info + + short mins[3]; // for frustum culling + short maxs[3]; + + unsigned short firstmarksurface; + unsigned short nummarksurfaces; + + byte ambient_level[NUM_AMBIENTS]; +} dleaf_t; + + +//============================================================================ + +#ifndef QUAKE_GAME + +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + + +// the utilities get to be lazy and just use large static arrays + +extern int nummodels; +extern dmodel_t dmodels[MAX_MAP_MODELS]; +extern int dmodels_checksum; + +extern int visdatasize; +extern byte dvisdata[MAX_MAP_VISIBILITY]; +extern int dvisdata_checksum; + +extern int lightdatasize; +extern byte dlightdata[MAX_MAP_LIGHTING]; +extern int dlightdata_checksum; + +extern int texdatasize; +extern byte dtexdata[MAX_MAP_MIPTEX]; // (dmiptexlump_t) +extern int dtexdata_checksum; + +extern int entdatasize; +extern char dentdata[MAX_MAP_ENTSTRING]; +extern int dentdata_checksum; + +extern int numleafs; +extern dleaf_t dleafs[MAX_MAP_LEAFS]; +extern int dleafs_checksum; + +extern int numplanes; +extern dplane_t dplanes[MAX_MAP_PLANES]; +extern int dplanes_checksum; + +extern int numvertexes; +extern dvertex_t dvertexes[MAX_MAP_VERTS]; +extern int dvertexes_checksum; + +extern int numnodes; +extern dnode_t dnodes[MAX_MAP_NODES]; +extern int dnodes_checksum; + +extern int numtexinfo; +extern texinfo_t texinfo[MAX_MAP_TEXINFO]; +extern int texinfo_checksum; + +extern int numfaces; +extern dface_t dfaces[MAX_MAP_FACES]; +extern int dfaces_checksum; + +extern int numclipnodes; +extern dclipnode_t dclipnodes[MAX_MAP_CLIPNODES]; +extern int dclipnodes_checksum; + +extern int numedges; +extern dedge_t dedges[MAX_MAP_EDGES]; +extern int dedges_checksum; + +extern int nummarksurfaces; +extern unsigned short dmarksurfaces[MAX_MAP_MARKSURFACES]; +extern int dmarksurfaces_checksum; + +extern int numsurfedges; +extern int dsurfedges[MAX_MAP_SURFEDGES]; +extern int dsurfedges_checksum; + +int FastChecksum(void *buffer, int bytes); + +void DecompressVis (byte *in, byte *decompressed); +int CompressVis (byte *vis, byte *dest); + +void LoadBSPFile (char *filename); +void WriteBSPFile (char *filename); +void PrintBSPFileSizes (void); + +//=============== + + +typedef struct epair_s +{ + struct epair_s *next; + char *key; + char *value; +} epair_t; + +typedef struct +{ + vec3_t origin; + int firstbrush; + int numbrushes; + epair_t *epairs; +} entity_t; + +extern int num_entities; +extern entity_t entities[MAX_MAP_ENTITIES]; + +void ParseEntities (void); +void UnparseEntities (void); + +void SetKeyValue (entity_t *ent, char *key, char *value); +char *ValueForKey (entity_t *ent, char *key); +// will return "" if not present + +vec_t FloatForKey (entity_t *ent, char *key); +void GetVectorForKey (entity_t *ent, char *key, vec3_t vec); + +epair_t *ParseEpair (void); + +#endif diff --git a/hlsdk/utils/common/cmdlib.c b/hlsdk/utils/common/cmdlib.c new file mode 100644 index 00000000..05b5aa5e --- /dev/null +++ b/hlsdk/utils/common/cmdlib.c @@ -0,0 +1,1040 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// cmdlib.c + +#include "cmdlib.h" +#include +#include + +#ifdef WIN32 +#include +#endif + +#ifdef NeXT +#include +#endif + +#ifdef WIN32 +#define PATHSEPARATOR(c) ((c) == '\\' || (c) == '/') +#else //WIN32 +#define PATHSEPARATOR(c) ((c) == '/') +#endif //WIN32 + +// set these before calling CheckParm +int myargc; +char **myargv; + +char com_token[1024]; +qboolean com_eof; + +qboolean archive; +char archivedir[1024]; + + +void COM_FixSlashes( char *pname ) +{ +#if 0 + while ( *pname ) { + if ( *pname == '/' ) + *pname = '\\'; + pname++; + } +#else + while ( *pname ) { + if ( *pname == '\\' ) + *pname = '/'; + pname++; + } +#endif +} + + +/* +================= +Error + +For abnormal program terminations +================= +*/ +void Error (char *error, ...) +{ + va_list argptr; + + printf ("\n************ ERROR ************\n"); + + va_start (argptr,error); + vprintf (error,argptr); + va_end (argptr); + printf ("\n"); + + exit (1); +} + +// only printf if in verbose mode +qboolean verbose = false; +void qprintf (char *format, ...) +{ + va_list argptr; + + if (!verbose) + return; + va_start (argptr,format); + vprintf (format,argptr); + va_end (argptr); +} + + +/* + +qdir will hold the path up to the quake directory, including the slash + + f:\quake\ + /raid/quake/ + +gamedir will hold qdir + the game directory (id1, id2, etc) + + */ + +char qproject[ 1024 ]={'\0'}; +char qdir[1024]={'\0'}; +char gamedir[1024]={'\0'}; + +void SetQdirFromPath (char *path) +{ +#ifndef OLD_BOGUS_PATH_CODE + + if ( qproject[0]=='\0' ) + { + if ( getenv("QPROJECT") ) + { + char c = qproject[ strlen(qproject)-1 ]; + strcpy( qproject, getenv("QPROJECT") ); + if ( !PATHSEPARATOR( c ) ) + strcat( qproject, "\\" ); + } + else + strcpy( qproject, "quiver\\" ); + } + if ( qproject[0] != '\\' && qproject[0] != '/' && qproject[1] != ':' ) + { + strcpy( qdir, "\\" ); + } + + strcat( qdir, qproject ); + strcpy( gamedir, qdir ); + strcat( gamedir, "\\valve\\" ); + +#else + char temp[1024]; + char *c; + + if (!(path[0] == '/' || path[0] == '\\' || path[1] == ':')) + { // path is partial + Q_getwd (temp); + strcat (temp, path); + path = temp; + } + +// search for "quake" or quiver in path + if( !qproject[0] ) { + char *pszProj; + + pszProj = getenv("QPROJECT"); + + if (pszProj != NULL) + strcpy(qproject, pszProj); + else + strcpy(qproject, "quiver"); + } + +try_again: + + for (c=path ; *c ; c++) + { + int iSize = 0; + + if (!Q_strncasecmp( c, qproject, strlen( qproject ) ) ) + iSize = strlen( qproject ) + 1; + + if (iSize > 0) + { + strncpy (qdir, path, c + iSize - path); + printf ("qdir: %s\n", qdir); + c += iSize; + while (*c) + { + if (*c == '/' || *c == '\\') + { + strncpy (gamedir, path, c+1-path); + printf ("gamedir: %s\n", gamedir); + return; + } + c++; + } + Error ("No gamedir in %s", path); + return; + } + } + + if (!strcmp(qproject, "quiver")) + { + strcpy(qproject, "prospero"); + goto try_again; + } + + Error ("SetQdirFromPath: no '%s' in %s", qproject, path); +#endif +} + + +char *ExpandArg (char *path) +{ + static char full[1024]; + + if (path[0] != '/' && path[0] != '\\' && path[1] != ':') + { + Q_getwd (full); + strcat (full, path); + } + else + strcpy (full, path); + return full; +} + +char *ExpandPath (char *path) +{ + char *psz; + static char full[1024]; + if (!qdir) + Error ("ExpandPath called without qdir set"); + if (path[0] == '/' || path[0] == '\\' || path[1] == ':') + return path; + psz = strstr(path, qdir); + if (psz) + strcpy(full, path); + else + sprintf (full, "%s%s", qdir, path); + + return full; +} + +char *ExpandPathAndArchive (char *path) +{ + char *expanded; + char archivename[1024]; + + expanded = ExpandPath (path); + + if (archive) + { + sprintf (archivename, "%s/%s", archivedir, path); + QCopyFile (expanded, archivename); + } + return expanded; +} + + +char *copystring(char *s) +{ + char *b; + b = malloc(strlen(s)+1); + strcpy (b, s); + return b; +} + + + +/* +================ +I_FloatTime +================ +*/ +double I_FloatTime (void) +{ + time_t t; + + time (&t); + + return (double)t; +#if 0 +// more precise, less portable + struct timeval tp; + struct timezone tzp; + static int secbase; + + gettimeofday(&tp, &tzp); + + if (!secbase) + { + secbase = tp.tv_sec; + return tp.tv_usec/1000000.0; + } + + return (tp.tv_sec - secbase) + tp.tv_usec/1000000.0; +#endif +} + +void Q_getwd (char *out) +{ +#ifdef WIN32 + _getcwd (out, 256); + strcat (out, "\\"); +#else + getwd (out); +#endif +} + + +void Q_mkdir (char *path) +{ +#ifdef WIN32 + if (_mkdir (path) != -1) + return; +#else + if (mkdir (path, 0777) != -1) + return; +#endif + if (errno != EEXIST) + Error ("mkdir %s: %s",path, strerror(errno)); +} + +/* +============ +FileTime + +returns -1 if not present +============ +*/ +int FileTime (char *path) +{ + struct stat buf; + + if (stat (path,&buf) == -1) + return -1; + + return (int)buf.st_mtime; +} + + + +/* +============== +COM_Parse + + Parse a token out of a string (into global: char com_token[1024]) +============== +*/ +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) + { + com_eof = true; + 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++; + do + { + c = *data++; + if (c=='\"') + { + com_token[len] = 0; + return data; + } + com_token[len] = c; + len++; + } while (1); + } + +// 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; +} + + +int Q_strncasecmp (char *s1, char *s2, int n) +{ + int c1, c2; + + while (1) + { + c1 = *s1++; + c2 = *s2++; + + if (!n--) + return 0; // strings are equal until end point + + if (c1 != c2) + { + if (c1 >= 'a' && c1 <= 'z') + c1 -= ('a' - 'A'); + if (c2 >= 'a' && c2 <= 'z') + c2 -= ('a' - 'A'); + if (c1 != c2) + return -1; // strings not equal + } + if (!c1) + return 0; // strings are equal + } + + return -1; +} + +int Q_strcasecmp (char *s1, char *s2) +{ + return Q_strncasecmp (s1, s2, 99999); +} + + +char *strupr (char *start) +{ + char *in; + in = start; + while (*in) + { + *in = toupper(*in); + in++; + } + return start; +} + +char *strlower (char *start) +{ + char *in; + in = start; + while (*in) + { + *in = tolower(*in); + in++; + } + return start; +} + + +/* +============================================================================= + + MISC FUNCTIONS + +============================================================================= +*/ + + +/* +================= +CheckParm + +Checks for the given parameter in the program's command line arguments +Returns the argument number (1 to argc-1) or 0 if not present +================= +*/ +int CheckParm (char *check) +{ + int i; + + for (i = 1;i 0 && !PATHSEPARATOR(path[length])) + length--; + path[length] = 0; +} + +void StripExtension (char *path) +{ + int length; + + length = strlen(path)-1; + while (length > 0 && path[length] != '.') + { + length--; + if (path[length] == '/') + return; // no extension + } + if (length) + path[length] = 0; +} + + +/* +==================== +Extract file parts +==================== +*/ +void ExtractFilePath (char *path, char *dest) +{ + char *src; + + src = path + strlen(path) - 1; + +// +// back up until a \ or the start +// + while (src != path && !PATHSEPARATOR(*(src-1))) + src--; + + memcpy (dest, path, src-path); + dest[src-path] = 0; +} + +void ExtractFileBase (char *path, char *dest) +{ + char *src; + + src = path + strlen(path) - 1; + +// +// back up until a \ or the start +// + while (src != path && !PATHSEPARATOR(*(src-1))) + src--; + + while (*src && *src != '.') + { + *dest++ = *src++; + } + *dest = 0; +} + +void ExtractFileExtension (char *path, char *dest) +{ + char *src; + + src = path + strlen(path) - 1; + +// +// back up until a . or the start +// + while (src != path && *(src-1) != '.') + src--; + if (src == path) + { + *dest = 0; // no extension + return; + } + + strcpy (dest,src); +} + + +/* +============== +ParseNum / ParseHex +============== +*/ +int ParseHex (char *hex) +{ + char *str; + int num; + + num = 0; + str = hex; + + while (*str) + { + num <<= 4; + if (*str >= '0' && *str <= '9') + num += *str-'0'; + else if (*str >= 'a' && *str <= 'f') + num += 10 + *str-'a'; + else if (*str >= 'A' && *str <= 'F') + num += 10 + *str-'A'; + else + Error ("Bad hex number: %s",hex); + str++; + } + + return num; +} + + +int ParseNum (char *str) +{ + if (str[0] == '$') + return ParseHex (str+1); + if (str[0] == '0' && str[1] == 'x') + return ParseHex (str+2); + return atol (str); +} + + + +/* +============================================================================ + + BYTE ORDER FUNCTIONS + +============================================================================ +*/ + +#ifdef _SGI_SOURCE +#define __BIG_ENDIAN__ +#endif + +#ifdef __BIG_ENDIAN__ + +short LittleShort (short l) +{ + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +short BigShort (short l) +{ + return l; +} + + +int LittleLong (int l) +{ + byte b1,b2,b3,b4; + + b1 = l&255; + b2 = (l>>8)&255; + b3 = (l>>16)&255; + b4 = (l>>24)&255; + + return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; +} + +int BigLong (int l) +{ + return l; +} + + +float LittleFloat (float l) +{ + union {byte b[4]; float f;} in, out; + + in.f = l; + out.b[0] = in.b[3]; + out.b[1] = in.b[2]; + out.b[2] = in.b[1]; + out.b[3] = in.b[0]; + + return out.f; +} + +float BigFloat (float l) +{ + return l; +} + + +#else + + +short BigShort (short l) +{ + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +short LittleShort (short l) +{ + return l; +} + + +int BigLong (int l) +{ + byte b1,b2,b3,b4; + + b1 = l&255; + b2 = (l>>8)&255; + b3 = (l>>16)&255; + b4 = (l>>24)&255; + + return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; +} + +int LittleLong (int l) +{ + return l; +} + +float BigFloat (float l) +{ + union {byte b[4]; float f;} in, out; + + in.f = l; + out.b[0] = in.b[3]; + out.b[1] = in.b[2]; + out.b[2] = in.b[1]; + out.b[3] = in.b[0]; + + return out.f; +} + +float LittleFloat (float l) +{ + return l; +} + + +#endif + + +//======================================================= + + +// FIXME: byte swap? + +// this is a 16 bit, non-reflected CRC using the polynomial 0x1021 +// and the initial and final xor values shown below... in other words, the +// CCITT standard CRC used by XMODEM + +#define CRC_INIT_VALUE 0xffff +#define CRC_XOR_VALUE 0x0000 + +static unsigned short crctable[256] = +{ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +}; + +void CRC_Init(unsigned short *crcvalue) +{ + *crcvalue = CRC_INIT_VALUE; +} + +void CRC_ProcessByte(unsigned short *crcvalue, byte data) +{ + *crcvalue = (*crcvalue << 8) ^ crctable[(*crcvalue >> 8) ^ data]; +} + +unsigned short CRC_Value(unsigned short crcvalue) +{ + return crcvalue ^ CRC_XOR_VALUE; +} +//============================================================================= + +/* +============ +CreatePath +============ +*/ +void CreatePath (char *path) +{ + char *ofs, c; + + for (ofs = path+1 ; *ofs ; ofs++) + { + c = *ofs; + if (c == '/' || c == '\\') + { // create the directory + *ofs = 0; + Q_mkdir (path); + *ofs = c; + } + } +} + + +/* +============ +QCopyFile + + Used to archive source files +============ +*/ +void QCopyFile (char *from, char *to) +{ + void *buffer; + int length; + + length = LoadFile (from, &buffer); + CreatePath (to); + SaveFile (to, buffer, length); + free (buffer); +} + + +/* +============ +ListPak + + Prints the contents of the specified pak file to stdout +============ +*/ + +void ListPak(char* pakname) +{ + FILE* f = SafeOpenRead(pakname); + packheader_t head; + packfile_t* pdir; + long i=0,imax=0; + long totlen=0; + + SafeRead(f,&head,sizeof(packheader_t)); + pdir=malloc(head.dirlen); + + fseek(f,head.dirofs,SEEK_SET); + SafeRead(f,pdir,head.dirlen); + + fseek(f,0,SEEK_END); + totlen=ftell(f); + + fclose(f); + + imax=head.dirlen/sizeof(packfile_t); + + for(i;i= 1400 + #ifndef _CRT_SECURE_NO_DEPRECATE + #define _CRT_SECURE_NO_DEPRECATE + #endif + + #pragma warning(disable: 4996) // deprecated functions +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifndef __CMDUTIL__ +#define __CMDUTIL__ +#ifndef _NOENUMQBOOL +typedef enum {false, true} qboolean; +#else +typedef int qboolean; +#undef true +#undef false +#define true 1 +#define false 0 +#endif + +typedef unsigned char byte; +#endif + +// the dec offsetof macro doesn't work very well... +#define myoffsetof(type,identifier) ((size_t)&((type *)0)->identifier) + + +// set these before calling CheckParm +extern int myargc; +extern char **myargv; + +void COM_FixSlashes( char *pname ); +char *strupr (char *in); +char *strlower (char *in); +int Q_strncasecmp (char *s1, char *s2, int n); +int Q_strcasecmp (char *s1, char *s2); +void Q_getwd (char *out); + +int filelength (FILE *f); +int FileTime (char *path); + +void Q_mkdir (char *path); + +extern char qdir[1024]; +extern char gamedir[1024]; +void SetQdirFromPath (char *path); +char *ExpandArg (char *path); // from cmd line +char *ExpandPath (char *path); // from scripts +char *ExpandPathAndArchive (char *path); + + +double I_FloatTime (void); + +void Error (char *error, ...); +int CheckParm (char *check); + +FILE *SafeOpenWrite (char *filename); +FILE *SafeOpenRead (char *filename); +void SafeRead (FILE *f, void *buffer, int count); +void SafeWrite (FILE *f, void *buffer, int count); + +int LoadFile (char *filename, void **bufferptr); +void SaveFile (char *filename, void *buffer, int count); + +void DefaultExtension (char *path, char *extension); +void DefaultPath (char *path, char *basepath); +void StripFilename (char *path); +void StripExtension (char *path); + +void ExtractFilePath (char *path, char *dest); +void ExtractFileBase (char *path, char *dest); +void ExtractFileExtension (char *path, char *dest); + +int ParseNum (char *str); + +short BigShort (short l); +short LittleShort (short l); +int BigLong (int l); +int LittleLong (int l); +float BigFloat (float l); +float LittleFloat (float l); + +long flen(FILE* f); + + + +char *COM_Parse (char *data); + +extern char com_token[1024]; +extern qboolean com_eof; + +char *copystring(char *s); + + +void CRC_Init(unsigned short *crcvalue); +void CRC_ProcessByte(unsigned short *crcvalue, byte data); +unsigned short CRC_Value(unsigned short crcvalue); + +void CreatePath (char *path); +void QCopyFile (char *from, char *to); + +extern qboolean archive; +extern char archivedir[1024]; + + +extern qboolean verbose; +void qprintf (char *format, ...); + + +typedef struct +{ + char name[56]; + int filepos, filelen; +} packfile_t; + +typedef struct +{ + char id[4]; + int dirofs; + int dirlen; +} packheader_t; + + +void ListPak(char* pakname); + +#endif +#ifdef __cplusplus +} +#endif + diff --git a/hlsdk/utils/common/lbmlib.c b/hlsdk/utils/common/lbmlib.c new file mode 100644 index 00000000..045dbdf6 --- /dev/null +++ b/hlsdk/utils/common/lbmlib.c @@ -0,0 +1,744 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// lbmlib.c + +#include +#include + +#include "cmdlib.h" +#include "lbmlib.h" + + + +/* +============================================================================ + + LBM STUFF + +============================================================================ +*/ + + +#define FORMID ('F'+('O'<<8)+((int)'R'<<16)+((int)'M'<<24)) +#define ILBMID ('I'+('L'<<8)+((int)'B'<<16)+((int)'M'<<24)) +#define PBMID ('P'+('B'<<8)+((int)'M'<<16)+((int)' '<<24)) +#define BMHDID ('B'+('M'<<8)+((int)'H'<<16)+((int)'D'<<24)) +#define BODYID ('B'+('O'<<8)+((int)'D'<<16)+((int)'Y'<<24)) +#define CMAPID ('C'+('M'<<8)+((int)'A'<<16)+((int)'P'<<24)) + + +bmhd_t bmhd; + +int Align (int l) +{ + if (l&1) + return l+1; + return l; +} + + + +/* +================ += += LBMRLEdecompress += += Source must be evenly aligned! += +================ +*/ + +byte *LBMRLEDecompress (byte *source,byte *unpacked, int bpwidth) +{ + int count; + byte b,rept; + + count = 0; + + do + { + rept = *source++; + + if (rept > 0x80) + { + rept = (rept^0xff)+2; + b = *source++; + memset(unpacked,b,rept); + unpacked += rept; + } + else if (rept < 0x80) + { + rept++; + memcpy(unpacked,source,rept); + unpacked += rept; + source += rept; + } + else + rept = 0; // rept of 0x80 is NOP + + count += rept; + + } while (countbpwidth) + Error ("Decompression exceeded width!\n"); + + + return source; +} + + +#define BPLANESIZE 128 +byte bitplanes[9][BPLANESIZE]; // max size 1024 by 9 bit planes + + +/* +================= += += MungeBitPlanes8 += += This destroys the bit plane data! += +================= +*/ + +void MungeBitPlanes8 (int width, byte *dest) +{ + *dest=width; // shut up the compiler warning + Error ("MungeBitPlanes8 not rewritten!"); +#if 0 +asm les di,[dest] +asm mov si,-1 +asm mov cx,[width] +mungebyte: +asm inc si +asm mov dx,8 +mungebit: +asm shl [BYTE PTR bitplanes + BPLANESIZE*7 +si],1 +asm rcl al,1 +asm shl [BYTE PTR bitplanes + BPLANESIZE*6 +si],1 +asm rcl al,1 +asm shl [BYTE PTR bitplanes + BPLANESIZE*5 +si],1 +asm rcl al,1 +asm shl [BYTE PTR bitplanes + BPLANESIZE*4 +si],1 +asm rcl al,1 +asm shl [BYTE PTR bitplanes + BPLANESIZE*3 +si],1 +asm rcl al,1 +asm shl [BYTE PTR bitplanes + BPLANESIZE*2 +si],1 +asm rcl al,1 +asm shl [BYTE PTR bitplanes + BPLANESIZE*1 +si],1 +asm rcl al,1 +asm shl [BYTE PTR bitplanes + BPLANESIZE*0 +si],1 +asm rcl al,1 +asm stosb +asm dec cx +asm jz done +asm dec dx +asm jnz mungebit +asm jmp mungebyte + +done: +#endif +} + + +void MungeBitPlanes4 (int width, byte *dest) +{ + *dest=width; // shut up the compiler warning + Error ("MungeBitPlanes4 not rewritten!"); +#if 0 + +asm les di,[dest] +asm mov si,-1 +asm mov cx,[width] +mungebyte: +asm inc si +asm mov dx,8 +mungebit: +asm xor al,al +asm shl [BYTE PTR bitplanes + BPLANESIZE*3 +si],1 +asm rcl al,1 +asm shl [BYTE PTR bitplanes + BPLANESIZE*2 +si],1 +asm rcl al,1 +asm shl [BYTE PTR bitplanes + BPLANESIZE*1 +si],1 +asm rcl al,1 +asm shl [BYTE PTR bitplanes + BPLANESIZE*0 +si],1 +asm rcl al,1 +asm stosb +asm dec cx +asm jz done +asm dec dx +asm jnz mungebit +asm jmp mungebyte + +done: +#endif +} + + +void MungeBitPlanes2 (int width, byte *dest) +{ + *dest=width; // shut up the compiler warning + Error ("MungeBitPlanes2 not rewritten!"); +#if 0 +asm les di,[dest] +asm mov si,-1 +asm mov cx,[width] +mungebyte: +asm inc si +asm mov dx,8 +mungebit: +asm xor al,al +asm shl [BYTE PTR bitplanes + BPLANESIZE*1 +si],1 +asm rcl al,1 +asm shl [BYTE PTR bitplanes + BPLANESIZE*0 +si],1 +asm rcl al,1 +asm stosb +asm dec cx +asm jz done +asm dec dx +asm jnz mungebit +asm jmp mungebyte + +done: +#endif +} + + +void MungeBitPlanes1 (int width, byte *dest) +{ + *dest=width; // shut up the compiler warning + Error ("MungeBitPlanes1 not rewritten!"); +#if 0 +asm les di,[dest] +asm mov si,-1 +asm mov cx,[width] +mungebyte: +asm inc si +asm mov dx,8 +mungebit: +asm xor al,al +asm shl [BYTE PTR bitplanes + BPLANESIZE*0 +si],1 +asm rcl al,1 +asm stosb +asm dec cx +asm jz done +asm dec dx +asm jnz mungebit +asm jmp mungebyte + +done: +#endif +} + +int LoadBMP (const char* szFile, BYTE** ppbBits, BYTE** ppbPalette) +{ + int i, rc = 0; + FILE *pfile = NULL; + BITMAPFILEHEADER bmfh; + BITMAPINFOHEADER bmih; + RGBQUAD rgrgbPalette[256]; + ULONG cbBmpBits; + BYTE* pbBmpBits; + byte *pb, *pbPal = NULL; + ULONG cbPalBytes; + ULONG biTrueWidth; + + // Bogus parameter check + if (!(ppbPalette != NULL && ppbBits != NULL)) + { fprintf(stderr, "invalid BMP file\n"); rc = -1000; goto GetOut; } + + // File exists? + if ((pfile = fopen(szFile, "rb")) == NULL) + { fprintf(stderr, "unable to open BMP file\n"); rc = -1; goto GetOut; } + + // Read file header + if (fread(&bmfh, sizeof bmfh, 1/*count*/, pfile) != 1) + { rc = -2; goto GetOut; } + + // Bogus file header check + if (!(bmfh.bfReserved1 == 0 && bmfh.bfReserved2 == 0)) + { rc = -2000; goto GetOut; } + + // Read info header + if (fread(&bmih, sizeof bmih, 1/*count*/, pfile) != 1) + { rc = -3; goto GetOut; } + + // Bogus info header check + if (!(bmih.biSize == sizeof bmih && bmih.biPlanes == 1)) + { fprintf(stderr, "invalid BMP file header\n"); rc = -3000; goto GetOut; } + + // Bogus bit depth? Only 8-bit supported. + if (bmih.biBitCount != 8) + { fprintf(stderr, "BMP file not 8 bit\n"); rc = -4; goto GetOut; } + + // Bogus compression? Only non-compressed supported. + if (bmih.biCompression != BI_RGB) + { fprintf(stderr, "invalid BMP compression type\n"); rc = -5; goto GetOut; } + + // Figure out how many entires are actually in the table + if (bmih.biClrUsed == 0) + { + bmih.biClrUsed = 256; + cbPalBytes = (1 << bmih.biBitCount) * sizeof( RGBQUAD ); + } + else + { + cbPalBytes = bmih.biClrUsed * sizeof( RGBQUAD ); + } + + // Read palette (bmih.biClrUsed entries) + if (fread(rgrgbPalette, cbPalBytes, 1/*count*/, pfile) != 1) + { rc = -6; goto GetOut; } + + // convert to a packed 768 byte palette + pbPal = malloc(768); + if (pbPal == NULL) + { rc = -7; goto GetOut; } + + pb = pbPal; + + // Copy over used entries + for (i = 0; i < (int)bmih.biClrUsed; i++) + { + *pb++ = rgrgbPalette[i].rgbRed; + *pb++ = rgrgbPalette[i].rgbGreen; + *pb++ = rgrgbPalette[i].rgbBlue; + } + + // Fill in unused entires will 0,0,0 + for (i = bmih.biClrUsed; i < 256; i++) + { + *pb++ = 0; + *pb++ = 0; + *pb++ = 0; + } + + // Read bitmap bits (remainder of file) + cbBmpBits = bmfh.bfSize - ftell(pfile); + pb = malloc(cbBmpBits); + if (fread(pb, cbBmpBits, 1/*count*/, pfile) != 1) + { rc = -7; goto GetOut; } + + pbBmpBits = malloc(cbBmpBits); + + // data is actually stored with the width being rounded up to a multiple of 4 + biTrueWidth = (bmih.biWidth + 3) & ~3; + + // reverse the order of the data. + pb += (bmih.biHeight - 1) * biTrueWidth; + for(i = 0; i < bmih.biHeight; i++) + { + memmove(&pbBmpBits[biTrueWidth * i], pb, biTrueWidth); + pb -= biTrueWidth; + } + + pb += biTrueWidth; + free(pb); + + bmhd.w = (WORD)bmih.biWidth; + bmhd.h = (WORD)bmih.biHeight; + // Set output parameters + *ppbPalette = pbPal; + *ppbBits = pbBmpBits; + +GetOut: + if (pfile) + fclose(pfile); + + return rc; +} + + +int WriteBMPfile (char *szFile, byte *pbBits, int width, int height, byte *pbPalette) +{ + int i, rc = 0; + FILE *pfile = NULL; + BITMAPFILEHEADER bmfh; + BITMAPINFOHEADER bmih; + RGBQUAD rgrgbPalette[256]; + ULONG cbBmpBits; + BYTE* pbBmpBits; + byte *pb, *pbPal = NULL; + ULONG cbPalBytes; + ULONG biTrueWidth; + + // Bogus parameter check + if (!(pbPalette != NULL && pbBits != NULL)) + { rc = -1000; goto GetOut; } + + // File exists? + if ((pfile = fopen(szFile, "wb")) == NULL) + { rc = -1; goto GetOut; } + + biTrueWidth = ((width + 3) & ~3); + cbBmpBits = biTrueWidth * height; + cbPalBytes = 256 * sizeof( RGBQUAD ); + + // Bogus file header check + bmfh.bfType = MAKEWORD( 'B', 'M' ); + bmfh.bfSize = sizeof bmfh + sizeof bmih + cbBmpBits + cbPalBytes; + bmfh.bfReserved1 = 0; + bmfh.bfReserved2 = 0; + bmfh.bfOffBits = sizeof bmfh + sizeof bmih + cbPalBytes; + + // Write file header + if (fwrite(&bmfh, sizeof bmfh, 1/*count*/, pfile) != 1) + { rc = -2; goto GetOut; } + + // Size of structure + bmih.biSize = sizeof bmih; + // Width + bmih.biWidth = biTrueWidth; + // Height + bmih.biHeight = height; + // Only 1 plane + bmih.biPlanes = 1; + // Only 8-bit supported. + bmih.biBitCount = 8; + // Only non-compressed supported. + bmih.biCompression = BI_RGB; + bmih.biSizeImage = 0; + + // huh? + bmih.biXPelsPerMeter = 0; + bmih.biYPelsPerMeter = 0; + + // Always full palette + bmih.biClrUsed = 256; + bmih.biClrImportant = 0; + + // Write info header + if (fwrite(&bmih, sizeof bmih, 1/*count*/, pfile) != 1) + { rc = -3; goto GetOut; } + + + // convert to expanded palette + pb = pbPalette; + + // Copy over used entries + for (i = 0; i < (int)bmih.biClrUsed; i++) + { + rgrgbPalette[i].rgbRed = *pb++; + rgrgbPalette[i].rgbGreen = *pb++; + rgrgbPalette[i].rgbBlue = *pb++; + rgrgbPalette[i].rgbReserved = 0; + } + + // Write palette (bmih.biClrUsed entries) + cbPalBytes = bmih.biClrUsed * sizeof( RGBQUAD ); + if (fwrite(rgrgbPalette, cbPalBytes, 1/*count*/, pfile) != 1) + { rc = -6; goto GetOut; } + + + pbBmpBits = malloc(cbBmpBits); + + pb = pbBits; + // reverse the order of the data. + pb += (height - 1) * width; + for(i = 0; i < bmih.biHeight; i++) + { + memmove(&pbBmpBits[biTrueWidth * i], pb, width); + pb -= width; + } + + // Write bitmap bits (remainder of file) + if (fwrite(pbBmpBits, cbBmpBits, 1/*count*/, pfile) != 1) + { rc = -7; goto GetOut; } + + free(pbBmpBits); + +GetOut: + if (pfile) + fclose(pfile); + + return rc; +} + +/* +================= += += LoadLBM += +================= +*/ + +void LoadLBM (char *filename, byte **picture, byte **palette) +{ + byte *LBMbuffer, *picbuffer, *cmapbuffer; + int y,p,planes; + byte *LBM_P, *LBMEND_P; + byte *pic_p; + byte *body_p; + unsigned rowsize; + + int formtype,formlength; + int chunktype,chunklength; + void (*mungecall) (int, byte *); + +// qiet compiler warnings + picbuffer = NULL; + cmapbuffer = NULL; + mungecall = NULL; + +// +// load the LBM +// + LoadFile (filename, (void **)&LBMbuffer); + +// +// parse the LBM header +// + LBM_P = LBMbuffer; + if ( *(int *)LBMbuffer != LittleLong(FORMID) ) + Error ("No FORM ID at start of file!\n"); + + LBM_P += 4; + formlength = BigLong( *(int *)LBM_P ); + LBM_P += 4; + LBMEND_P = LBM_P + Align(formlength); + + formtype = LittleLong(*(int *)LBM_P); + + if (formtype != ILBMID && formtype != PBMID) + Error ("Unrecognized form type: %c%c%c%c\n", formtype&0xff + ,(formtype>>8)&0xff,(formtype>>16)&0xff,(formtype>>24)&0xff); + + LBM_P += 4; + +// +// parse chunks +// + + while (LBM_P < LBMEND_P) + { + chunktype = LBM_P[0] + (LBM_P[1]<<8) + (LBM_P[2]<<16) + (LBM_P[3]<<24); + LBM_P += 4; + chunklength = LBM_P[3] + (LBM_P[2]<<8) + (LBM_P[1]<<16) + (LBM_P[0]<<24); + LBM_P += 4; + + switch ( chunktype ) + { + case BMHDID: + memcpy (&bmhd,LBM_P,sizeof(bmhd)); + bmhd.w = BigShort(bmhd.w); + bmhd.h = BigShort(bmhd.h); + bmhd.x = BigShort(bmhd.x); + bmhd.y = BigShort(bmhd.y); + bmhd.pageWidth = BigShort(bmhd.pageWidth); + bmhd.pageHeight = BigShort(bmhd.pageHeight); + break; + + case CMAPID: + cmapbuffer = malloc (768); + memset (cmapbuffer, 0, 768); + memcpy (cmapbuffer, LBM_P, chunklength); + break; + + case BODYID: + body_p = LBM_P; + + pic_p = picbuffer = malloc (bmhd.w*bmhd.h); + if (formtype == PBMID) + { + // + // unpack PBM + // + for (y=0 ; y EQUAL_EPSILON) + return false; + + return true; +} + +vec_t Q_rint (vec_t in) +{ + return floor (in + 0.5); +} + +void VectorMA (vec3_t va, double scale, vec3_t vb, vec3_t vc) +{ + vc[0] = va[0] + scale*vb[0]; + vc[1] = va[1] + scale*vb[1]; + vc[2] = va[2] + scale*vb[2]; +} + +void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross) +{ + cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; + cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; + cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} + +vec_t _DotProduct (vec3_t v1, vec3_t v2) +{ + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} + +void _VectorSubtract (vec3_t va, vec3_t vb, vec3_t out) +{ + out[0] = va[0]-vb[0]; + out[1] = va[1]-vb[1]; + out[2] = va[2]-vb[2]; +} + +void _VectorAdd (vec3_t va, vec3_t vb, vec3_t out) +{ + out[0] = va[0]+vb[0]; + out[1] = va[1]+vb[1]; + out[2] = va[2]+vb[2]; +} + +void _VectorCopy (vec3_t in, vec3_t out) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +void _VectorScale (vec3_t v, vec_t scale, vec3_t out) +{ + out[0] = v[0] * scale; + out[1] = v[1] * scale; + out[2] = v[2] * scale; +} + +vec_t VectorNormalize (vec3_t v) +{ + int i; + double length; + +if ( fabs(v[1] - 0.000215956) < 0.0001) +i=1; + + length = 0; + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = sqrt (length); + if (length == 0) + return 0; + + for (i=0 ; i< 3 ; i++) + v[i] /= length; + + return length; +} + +void VectorInverse (vec3_t v) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void ClearBounds (vec3_t mins, vec3_t maxs) +{ + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; +} + +void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs) +{ + int i; + vec_t val; + + for (i=0 ; i<3 ; i++) + { + val = v[i]; + if (val < mins[i]) + mins[i] = val; + if (val > maxs[i]) + maxs[i] = val; + } +} + +void AngleMatrix (const vec3_t angles, float (*matrix)[4] ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = angles[2] * (Q_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[1] * (Q_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[0] * (Q_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + // matrix = (Z * Y) * X + matrix[0][0] = cp*cy; + matrix[1][0] = cp*sy; + matrix[2][0] = -sp; + matrix[0][1] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[2][1] = sr*cp; + matrix[0][2] = (cr*sp*cy+-sr*-sy); + matrix[1][2] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.0; + matrix[1][3] = 0.0; + matrix[2][3] = 0.0; +} + +void AngleIMatrix (const vec3_t angles, float matrix[3][4] ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = angles[2] * (Q_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[1] * (Q_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[0] * (Q_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + // matrix = (Z * Y) * X + matrix[0][0] = cp*cy; + matrix[0][1] = cp*sy; + matrix[0][2] = -sp; + matrix[1][0] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[1][2] = sr*cp; + matrix[2][0] = (cr*sp*cy+-sr*-sy); + matrix[2][1] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.0; + matrix[1][3] = 0.0; + matrix[2][3] = 0.0; +} + +void R_ConcatTransforms (const float in1[3][4], const float in2[3][4], float out[3][4]) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] + + in1[0][2] * in2[2][3] + in1[0][3]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] + + in1[1][2] * in2[2][3] + in1[1][3]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; + out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + + in1[2][2] * in2[2][3] + in1[2][3]; +} + + + +void VectorRotate (const vec3_t in1, const float in2[3][4], vec3_t out) +{ + out[0] = DotProduct(in1, in2[0]); + out[1] = DotProduct(in1, in2[1]); + out[2] = DotProduct(in1, in2[2]); +} + + +// rotate by the inverse of the matrix +void VectorIRotate (const vec3_t in1, const float in2[3][4], vec3_t out) +{ + out[0] = in1[0]*in2[0][0] + in1[1]*in2[1][0] + in1[2]*in2[2][0]; + out[1] = in1[0]*in2[0][1] + in1[1]*in2[1][1] + in1[2]*in2[2][1]; + out[2] = in1[0]*in2[0][2] + in1[1]*in2[1][2] + in1[2]*in2[2][2]; +} + + +void VectorTransform (const vec3_t in1, const float in2[3][4], vec3_t out) +{ + out[0] = DotProduct(in1, in2[0]) + in2[0][3]; + out[1] = DotProduct(in1, in2[1]) + in2[1][3]; + out[2] = DotProduct(in1, in2[2]) + in2[2][3]; +} + + + +void AngleQuaternion( const vec3_t angles, vec4_t quaternion ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + // FIXME: rescale the inputs to 1/2 angle + angle = angles[2] * 0.5; + sy = sin(angle); + cy = cos(angle); + angle = angles[1] * 0.5; + sp = sin(angle); + cp = cos(angle); + angle = angles[0] * 0.5; + sr = sin(angle); + cr = cos(angle); + + quaternion[0] = sr*cp*cy-cr*sp*sy; // X + quaternion[1] = cr*sp*cy+sr*cp*sy; // Y + quaternion[2] = cr*cp*sy-sr*sp*cy; // Z + quaternion[3] = cr*cp*cy+sr*sp*sy; // W +} + +void QuaternionMatrix( const vec4_t quaternion, float (*matrix)[4] ) +{ + + matrix[0][0] = 1.0 - 2.0 * quaternion[1] * quaternion[1] - 2.0 * quaternion[2] * quaternion[2]; + matrix[1][0] = 2.0 * quaternion[0] * quaternion[1] + 2.0 * quaternion[3] * quaternion[2]; + matrix[2][0] = 2.0 * quaternion[0] * quaternion[2] - 2.0 * quaternion[3] * quaternion[1]; + + matrix[0][1] = 2.0 * quaternion[0] * quaternion[1] - 2.0 * quaternion[3] * quaternion[2]; + matrix[1][1] = 1.0 - 2.0 * quaternion[0] * quaternion[0] - 2.0 * quaternion[2] * quaternion[2]; + matrix[2][1] = 2.0 * quaternion[1] * quaternion[2] + 2.0 * quaternion[3] * quaternion[0]; + + matrix[0][2] = 2.0 * quaternion[0] * quaternion[2] + 2.0 * quaternion[3] * quaternion[1]; + matrix[1][2] = 2.0 * quaternion[1] * quaternion[2] - 2.0 * quaternion[3] * quaternion[0]; + matrix[2][2] = 1.0 - 2.0 * quaternion[0] * quaternion[0] - 2.0 * quaternion[1] * quaternion[1]; +} + +void QuaternionSlerp( const vec4_t p, vec4_t q, float t, vec4_t qt ) +{ + int i; + float omega, cosom, sinom, sclp, sclq; + + // decide if one of the quaternions is backwards + float a = 0; + float b = 0; + for (i = 0; i < 4; i++) { + a += (p[i]-q[i])*(p[i]-q[i]); + b += (p[i]+q[i])*(p[i]+q[i]); + } + if (a > b) { + for (i = 0; i < 4; i++) { + q[i] = -q[i]; + } + } + + cosom = p[0]*q[0] + p[1]*q[1] + p[2]*q[2] + p[3]*q[3]; + + if ((1.0 + cosom) > 0.00000001) { + if ((1.0 - cosom) > 0.00000001) { + omega = acos( cosom ); + sinom = sin( omega ); + sclp = sin( (1.0 - t)*omega) / sinom; + sclq = sin( t*omega ) / sinom; + } + else { + sclp = 1.0 - t; + sclq = t; + } + for (i = 0; i < 4; i++) { + qt[i] = sclp * p[i] + sclq * q[i]; + } + } + else { + qt[0] = -p[1]; + qt[1] = p[0]; + qt[2] = -p[3]; + qt[3] = p[2]; + sclp = sin( (1.0 - t) * 0.5 * Q_PI); + sclq = sin( t * 0.5 * Q_PI); + for (i = 0; i < 3; i++) { + qt[i] = sclp * p[i] + sclq * qt[i]; + } + } +} + + diff --git a/hlsdk/utils/common/mathlib.h b/hlsdk/utils/common/mathlib.h new file mode 100644 index 00000000..14861f1c --- /dev/null +++ b/hlsdk/utils/common/mathlib.h @@ -0,0 +1,89 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#ifndef __MATHLIB__ +#define __MATHLIB__ + +// mathlib.h + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef DOUBLEVEC_T +typedef double vec_t; +#else +typedef float vec_t; +#endif +typedef vec_t vec3_t[3]; // x,y,z +typedef vec_t vec4_t[4]; // x,y,z,w + +#define SIDE_FRONT 0 +#define SIDE_ON 2 +#define SIDE_BACK 1 +#define SIDE_CROSS -2 + +#define Q_PI 3.14159265358979323846 + +extern vec3_t vec3_origin; + +// Use this definition globally +#define ON_EPSILON 0.01 +#define EQUAL_EPSILON 0.001 + +int VectorCompare (vec3_t v1, vec3_t v2); + +#define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]) +#define VectorFill(a,b) { (a)[0]=(b); (a)[1]=(b); (a)[2]=(b);} +#define VectorAvg(a) ( ( (a)[0] + (a)[1] + (a)[2] ) / 3 ) +#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 VectorScale(a,b,c) {(c)[0]=(b)*(a)[0];(c)[1]=(b)*(a)[1];(c)[2]=(b)*(a)[2];} + +vec_t Q_rint (vec_t in); +vec_t _DotProduct (vec3_t v1, vec3_t v2); +void _VectorSubtract (vec3_t va, vec3_t vb, vec3_t out); +void _VectorAdd (vec3_t va, vec3_t vb, vec3_t out); +void _VectorCopy (vec3_t in, vec3_t out); +void _VectorScale (vec3_t v, vec_t scale, vec3_t out); + +double VectorLength(vec3_t v); + +void VectorMA (vec3_t va, double scale, vec3_t vb, vec3_t vc); + +void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross); +vec_t VectorNormalize (vec3_t v); +void VectorInverse (vec3_t v); + +void ClearBounds (vec3_t mins, vec3_t maxs); +void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs); + +void AngleMatrix (const vec3_t angles, float matrix[3][4] ); +void AngleIMatrix (const vec3_t angles, float matrix[3][4] ); +void R_ConcatTransforms (const float in1[3][4], const float in2[3][4], float out[3][4]); + +void VectorIRotate (const vec3_t in1, const float in2[3][4], vec3_t out); +void VectorRotate (const vec3_t in1, const float in2[3][4], vec3_t out); + +void VectorTransform (const vec3_t in1, const float in2[3][4], vec3_t out); + +void AngleQuaternion( const vec3_t angles, vec4_t quaternion ); +void QuaternionMatrix( const vec4_t quaternion, float (*matrix)[4] ); +void QuaternionSlerp( const vec4_t p, vec4_t q, float t, vec4_t qt ); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/hlsdk/utils/common/movie.h b/hlsdk/utils/common/movie.h new file mode 100644 index 00000000..e0fb7a73 --- /dev/null +++ b/hlsdk/utils/common/movie.h @@ -0,0 +1,36 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#ifndef _MOVIE_H_ +#define _MOVIE_H_ + +/* + movie.h + + definitions and such for dumping screen shots to make a movie +*/ + +typedef struct +{ + unsigned long tag; + unsigned long size; +} movieblockheader_t; + + +typedef struct +{ + short width; + short height; + short depth; +} movieframe_t; + + + +#endif _MOVIE_H_ diff --git a/hlsdk/utils/common/polylib.c b/hlsdk/utils/common/polylib.c new file mode 100644 index 00000000..6420f5bc --- /dev/null +++ b/hlsdk/utils/common/polylib.c @@ -0,0 +1,708 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + + +#include "cmdlib.h" +#include "mathlib.h" +#include "polylib.h" + +int c_active_windings; +int c_peak_windings; +int c_winding_allocs; +int c_winding_points; + +#define BOGUS_RANGE 8192 + +void pw(winding_t *w) +{ + int i; + for (i=0 ; inumpoints ; i++) + printf ("(%5.1f, %5.1f, %5.1f)\n",w->p[i][0], w->p[i][1],w->p[i][2]); +} + + +/* +============= +AllocWinding +============= +*/ +winding_t *AllocWinding (int points) +{ + winding_t *w; + int s; + + c_winding_allocs++; + c_winding_points += points; + c_active_windings++; + if (c_active_windings > c_peak_windings) + c_peak_windings = c_active_windings; + s = sizeof(vec_t)*3*points + sizeof(int); + s += sizeof(vec_t) - sizeof(w->numpoints); // padding + + w = malloc (s); + memset (w, 0, s); + + return w; +} + +void FreeWinding (winding_t *w) +{ + c_active_windings--; + free (w); +} + +/* +============ +RemoveColinearPoints +============ +*/ +int c_removed; + +void RemoveColinearPoints (winding_t *w) +{ + int i, j, k; + vec3_t v1, v2; + int nump; + vec3_t p[MAX_POINTS_ON_WINDING]; + + nump = 0; + for (i=0 ; inumpoints ; i++) + { + j = (i+1)%w->numpoints; + k = (i+w->numpoints-1)%w->numpoints; + VectorSubtract (w->p[j], w->p[i], v1); + VectorSubtract (w->p[i], w->p[k], v2); + VectorNormalize(v1); + VectorNormalize(v2); + if (DotProduct(v1, v2) < 1.0-ON_EPSILON) + { + VectorCopy (w->p[i], p[nump]); + nump++; + } + } + + if (nump == w->numpoints) + return; + + c_removed += w->numpoints - nump; + w->numpoints = nump; + memcpy (w->p, p, nump*sizeof(p[0])); +} + +/* +============ +WindingPlane +============ +*/ +void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist) +{ + vec3_t v1, v2; + + VectorSubtract (w->p[1], w->p[0], v1); + VectorSubtract (w->p[2], w->p[0], v2); + CrossProduct (v2, v1, normal); + VectorNormalize (normal); + *dist = DotProduct (w->p[0], normal); + +} + +/* +============= +WindingArea +============= +*/ +vec_t WindingArea (winding_t *w) +{ + int i; + vec3_t d1, d2, cross; + vec_t total; + + total = 0; + for (i=2 ; inumpoints ; i++) + { + VectorSubtract (w->p[i-1], w->p[0], d1); + VectorSubtract (w->p[i], w->p[0], d2); + CrossProduct (d1, d2, cross); + total += 0.5 * VectorLength ( cross ); + } + return total; +} + +void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs) +{ + vec_t v; + int i,j; + + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; + + for (i=0 ; inumpoints ; i++) + { + for (j=0 ; j<3 ; j++) + { + v = w->p[i][j]; + if (v < mins[j]) + mins[j] = v; + if (v > maxs[j]) + maxs[j] = v; + } + } +} + +/* +============= +WindingCenter +============= +*/ +void WindingCenter (winding_t *w, vec3_t center) +{ + int i; + vec3_t d1, d2, cross; + float scale; + + VectorCopy (vec3_origin, center); + for (i=0 ; inumpoints ; i++) + VectorAdd (w->p[i], center, center); + + scale = 1.0/w->numpoints; + VectorScale (center, scale, center); +} + +/* +================= +BaseWindingForPlane +================= +*/ +winding_t *BaseWindingForPlane (vec3_t normal, float dist) +{ + int i, x; + vec_t max, v; + vec3_t org, vright, vup; + winding_t *w; + +// find the major axis + + max = -BOGUS_RANGE; + x = -1; + for (i=0 ; i<3; i++) + { + v = fabs(normal[i]); + if (v > max) + { + x = i; + max = v; + } + } + if (x==-1) + Error ("BaseWindingForPlane: no axis found"); + + VectorCopy (vec3_origin, vup); + switch (x) + { + case 0: + case 1: + vup[2] = 1; + break; + case 2: + vup[0] = 1; + break; + } + + v = DotProduct (vup, normal); + VectorMA (vup, -v, normal, vup); + VectorNormalize (vup); + + VectorScale (normal, dist, org); + + CrossProduct (vup, normal, vright); + + VectorScale (vup, 9000, vup); + VectorScale (vright, 9000, vright); + +// project a really big axis aligned box onto the plane + w = AllocWinding (4); + + VectorSubtract (org, vright, w->p[0]); + VectorAdd (w->p[0], vup, w->p[0]); + + VectorAdd (org, vright, w->p[1]); + VectorAdd (w->p[1], vup, w->p[1]); + + VectorAdd (org, vright, w->p[2]); + VectorSubtract (w->p[2], vup, w->p[2]); + + VectorSubtract (org, vright, w->p[3]); + VectorSubtract (w->p[3], vup, w->p[3]); + + w->numpoints = 4; + + return w; +} + +/* +================== +CopyWinding +================== +*/ +winding_t *CopyWinding (winding_t *w) +{ + int size; + winding_t *c; + + size = (int)((winding_t *)0)->p[w->numpoints]; + c = malloc (size); + memcpy (c, w, size); + return c; +} + + +/* +============= +ClipWinding +============= +*/ +void ClipWinding (winding_t *in, vec3_t normal, vec_t dist, + winding_t **front, winding_t **back) +{ + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + vec_t dot; + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f, *b; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > ON_EPSILON) + sides[i] = SIDE_FRONT; + else if (dot < -ON_EPSILON) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + if (!counts[0]) + { + *back = CopyWinding (in); + return; + } + if (!counts[1]) + { + *front = CopyWinding (in); + return; + } + + maxpts = in->numpoints+4; // can't use counts[0]+2 because + // of fp grouping errors + + *front = f = AllocWinding (maxpts); + *back = b = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + if (sides[i] == SIDE_BACK) + { + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (mid, b->p[b->numpoints]); + b->numpoints++; + } + + if (f->numpoints > maxpts || b->numpoints > maxpts) + Error ("ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING) + Error ("ClipWinding: MAX_POINTS_ON_WINDING"); +} + + + +/* +============= +ClipWindingNoCopy +============= +*/ +void ClipWindingNoCopy (winding_t *in, vec3_t normal, vec_t dist, + winding_t **front, winding_t **back) +{ + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + vec_t dot; + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f, *b; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > ON_EPSILON) + sides[i] = SIDE_FRONT; + else if (dot < -ON_EPSILON) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + if (!counts[0]) + { + *back = in; + return; + } + if (!counts[1]) + { + *front = in; + return; + } + + maxpts = in->numpoints+4; // can't use counts[0]+2 because + // of fp grouping errors + + *front = f = AllocWinding (maxpts); + *back = b = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + if (sides[i] == SIDE_BACK) + { + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (mid, b->p[b->numpoints]); + b->numpoints++; + } + + if (f->numpoints > maxpts || b->numpoints > maxpts) + Error ("ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING) + Error ("ClipWinding: MAX_POINTS_ON_WINDING"); +} + + +/* +============= +ChopWindingNoFree +============= +*/ +winding_t *ChopWindingNoFree (winding_t *in, vec3_t normal, vec_t dist) +{ + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + vec_t dot; + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > ON_EPSILON) + sides[i] = SIDE_FRONT; + else if (dot < -ON_EPSILON) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + if (!counts[0]) + return NULL; + if (!counts[1]) + return in; + + maxpts = in->numpoints+4; // can't use counts[0]+2 because + // of fp grouping errors + + f = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + } + + if (f->numpoints > maxpts) + Error ("ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING) + Error ("ClipWinding: MAX_POINTS_ON_WINDING"); + + return f; +} + + +/* +================= +ChopWinding + +Returns the fragment of in that is on the front side +of the cliping plane. The original is freed. +================= +*/ +winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist) +{ + winding_t *f, *b; + + ClipWinding (in, normal, dist, &f, &b); + FreeWinding (in); + if (b) + FreeWinding (b); + return f; +} + + +/* +================= +CheckWinding + +================= +*/ +void CheckWinding (winding_t *w) +{ + int i, j; + vec_t *p1, *p2; + vec_t d, edgedist; + vec3_t dir, edgenormal, facenormal; + vec_t area; + vec_t facedist; + + if (w->numpoints < 3) + Error ("CheckWinding: %i points",w->numpoints); + + area = WindingArea(w); + if (area < 1) + Error ("CheckWinding: %f area", area); + + WindingPlane (w, facenormal, &facedist); + + for (i=0 ; inumpoints ; i++) + { + p1 = w->p[i]; + + for (j=0 ; j<3 ; j++) + if (p1[j] > BOGUS_RANGE || p1[j] < -BOGUS_RANGE) + Error ("CheckFace: BUGUS_RANGE: %f",p1[j]); + + j = i+1 == w->numpoints ? 0 : i+1; + + // check the point is on the face plane + d = DotProduct (p1, facenormal) - facedist; + if (d < -ON_EPSILON || d > ON_EPSILON) + Error ("CheckWinding: point off plane"); + + // check the edge isn't degenerate + p2 = w->p[j]; + VectorSubtract (p2, p1, dir); + + if (VectorLength (dir) < ON_EPSILON) + Error ("CheckWinding: degenerate edge"); + + CrossProduct (facenormal, dir, edgenormal); + VectorNormalize (edgenormal); + edgedist = DotProduct (p1, edgenormal); + edgedist += ON_EPSILON; + + // all other points must be on front side + for (j=0 ; jnumpoints ; j++) + { + if (j == i) + continue; + d = DotProduct (w->p[j], edgenormal); + if (d > edgedist) + Error ("CheckWinding: non-convex"); + } + } +} + + +/* +============ +WindingOnPlaneSide +============ +*/ +int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist) +{ + qboolean front, back; + int i; + vec_t d; + + front = false; + back = false; + for (i=0 ; inumpoints ; i++) + { + d = DotProduct (w->p[i], normal) - dist; + if (d < -ON_EPSILON) + { + if (front) + return SIDE_CROSS; + back = true; + continue; + } + if (d > ON_EPSILON) + { + if (back) + return SIDE_CROSS; + front = true; + continue; + } + } + + if (back) + return SIDE_BACK; + if (front) + return SIDE_FRONT; + return SIDE_ON; +} + diff --git a/hlsdk/utils/common/polylib.h b/hlsdk/utils/common/polylib.h new file mode 100644 index 00000000..9a4bb658 --- /dev/null +++ b/hlsdk/utils/common/polylib.h @@ -0,0 +1,36 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + + +typedef struct +{ + int numpoints; + vec3_t p[8]; // variable sized +} winding_t; + +#define MAX_POINTS_ON_WINDING 128 + +winding_t *AllocWinding (int points); +vec_t WindingArea (winding_t *w); +void WindingCenter (winding_t *w, vec3_t center); +void ClipWinding (winding_t *in, vec3_t normal, vec_t dist, + winding_t **front, winding_t **back); +void ClipWindingNoCopy (winding_t *in, vec3_t normal, vec_t dist, + winding_t **front, winding_t **back); +winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist); +winding_t *ChopWindingNoFree (winding_t *in, vec3_t normal, vec_t dist); +winding_t *CopyWinding (winding_t *w); +winding_t *BaseWindingForPlane (vec3_t normal, float dist); +void CheckWinding (winding_t *w); +void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist); +void RemoveColinearPoints (winding_t *w); +int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist); +void FreeWinding (winding_t *w); +void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs); diff --git a/hlsdk/utils/common/scriplib.c b/hlsdk/utils/common/scriplib.c new file mode 100644 index 00000000..71bc0bd7 --- /dev/null +++ b/hlsdk/utils/common/scriplib.c @@ -0,0 +1,268 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// scriplib.c + +#include "cmdlib.h" +#include "scriplib.h" + +/* +============================================================================= + + PARSING STUFF + +============================================================================= +*/ + +typedef struct +{ + char filename[1024]; + char *buffer,*script_p,*end_p; + int line; +} script_t; + +#define MAX_INCLUDES 8 +script_t scriptstack[MAX_INCLUDES]; +script_t *script; +int scriptline; + +char token[MAXTOKEN]; +qboolean endofscript; +qboolean tokenready; // only true if UnGetToken was just called + +/* +============== +AddScriptToStack +============== +*/ +void AddScriptToStack (char *filename) +{ + int size; + + script++; + if (script == &scriptstack[MAX_INCLUDES]) + Error ("script file exceeded MAX_INCLUDES"); + strcpy (script->filename, ExpandPath (filename) ); + + size = LoadFile (script->filename, (void **)&script->buffer); + + printf ("entering %s\n", script->filename); + + script->line = 1; + + script->script_p = script->buffer; + script->end_p = script->buffer + size; +} + + +/* +============== +LoadScriptFile +============== +*/ +void LoadScriptFile (char *filename) +{ + script = scriptstack; + AddScriptToStack (filename); + + endofscript = false; + tokenready = false; +} + + +/* +============== +ParseFromMemory +============== +*/ +void ParseFromMemory (char *buffer, int size) +{ + script = scriptstack; + script++; + if (script == &scriptstack[MAX_INCLUDES]) + Error ("script file exceeded MAX_INCLUDES"); + strcpy (script->filename, "memory buffer" ); + + script->buffer = buffer; + script->line = 1; + script->script_p = script->buffer; + script->end_p = script->buffer + size; + + endofscript = false; + tokenready = false; +} + + +/* +============== +UnGetToken + +Signals that the current token was not used, and should be reported +for the next GetToken. Note that + +GetToken (true); +UnGetToken (); +GetToken (false); + +could cross a line boundary. +============== +*/ +void UnGetToken (void) +{ + tokenready = true; +} + + +qboolean EndOfScript (qboolean crossline) +{ + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + + if (!strcmp (script->filename, "memory buffer")) + { + endofscript = true; + return false; + } + + free (script->buffer); + if (script == scriptstack+1) + { + endofscript = true; + return false; + } + script--; + scriptline = script->line; + printf ("returning to %s\n", script->filename); + return GetToken (crossline); +} + +/* +============== +GetToken +============== +*/ +qboolean GetToken (qboolean crossline) +{ + char *token_p; + + if (tokenready) // is a token allready waiting? + { + tokenready = false; + return true; + } + + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + +// +// skip space +// +skipspace: + while (*script->script_p <= 32) + { + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + if (*script->script_p++ == '\n') + { + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + scriptline = script->line++; + } + } + + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + + if (*script->script_p == ';' || *script->script_p == '#' || // semicolon and # is comment field + (*script->script_p == '/' && *((script->script_p)+1) == '/')) // also make // a comment field + { + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + while (*script->script_p++ != '\n') + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + goto skipspace; + } + +// +// copy token +// + token_p = token; + + if (*script->script_p == '"') + { + // quoted token + script->script_p++; + while (*script->script_p != '"') + { + *token_p++ = *script->script_p++; + if (script->script_p == script->end_p) + break; + if (token_p == &token[MAXTOKEN]) + Error ("Token too large on line %i\n",scriptline); + } + script->script_p++; + } + else // regular token + while ( *script->script_p > 32 && *script->script_p != ';') + { + *token_p++ = *script->script_p++; + if (script->script_p == script->end_p) + break; + if (token_p == &token[MAXTOKEN]) + Error ("Token too large on line %i\n",scriptline); + } + + *token_p = 0; + + if (!strcmp (token, "$include")) + { + GetToken (false); + AddScriptToStack (token); + return GetToken (crossline); + } + + return true; +} + + +/* +============== +TokenAvailable + +Returns true if there is another token on the line +============== +*/ +qboolean TokenAvailable (void) +{ + char *search_p; + + search_p = script->script_p; + + if (search_p >= script->end_p) + return false; + + while ( *search_p <= 32) + { + if (*search_p == '\n') + return false; + search_p++; + if (search_p == script->end_p) + return false; + + } + + if (*search_p == ';') + return false; + + return true; +} + + diff --git a/hlsdk/utils/common/scriplib.h b/hlsdk/utils/common/scriplib.h new file mode 100644 index 00000000..d228d015 --- /dev/null +++ b/hlsdk/utils/common/scriplib.h @@ -0,0 +1,33 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// scriplib.h + +#ifndef __CMDLIB__ +#include "cmdlib.h" +#endif + +#define MAXTOKEN 512 + +extern char token[MAXTOKEN]; +extern char *scriptbuffer,*script_p,*scriptend_p; +extern int grabbed; +extern int scriptline; +extern qboolean endofscript; + + +void LoadScriptFile (char *filename); +void ParseFromMemory (char *buffer, int size); + +qboolean GetToken (qboolean crossline); +void UnGetToken (void); +qboolean TokenAvailable (void); + + diff --git a/hlsdk/utils/common/threads.c b/hlsdk/utils/common/threads.c new file mode 100644 index 00000000..51d657a2 --- /dev/null +++ b/hlsdk/utils/common/threads.c @@ -0,0 +1,330 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "cmdlib.h" +#define NO_THREAD_NAMES +#include "threads.h" + +#define MAX_THREADS 64 + +int dispatch; +int workcount; +int oldf; +qboolean pacifier; + +qboolean threaded; + +/* +============= +GetThreadWork + +============= +*/ +int GetThreadWork (void) +{ + int r; + int f; + + ThreadLock (); + + if (dispatch == workcount) + { + ThreadUnlock (); + return -1; + } + + f = 10*dispatch / workcount; + if (f != oldf) + { + oldf = f; + if (pacifier) + printf ("%i...", f); + } + + r = dispatch; + dispatch++; + ThreadUnlock (); + + return r; +} + + +void (*workfunction) (int); + +void ThreadWorkerFunction (int threadnum) +{ + int work; + + while (1) + { + work = GetThreadWork (); + if (work == -1) + break; + workfunction(work); + } +} + +void RunThreadsOnIndividual (int workcnt, qboolean showpacifier, void(*func)(int)) +{ + workfunction = func; + RunThreadsOn (workcnt, showpacifier, ThreadWorkerFunction); +} + + +/* +=================================================================== + +WIN32 + +=================================================================== +*/ +#ifdef WIN32 + +#define USED + +#include + +int numthreads = -1; +CRITICAL_SECTION crit; +static int enter; + +void ThreadSetDefault (void) +{ + SYSTEM_INFO info; + + if (numthreads == -1) // not set manually + { + GetSystemInfo (&info); + numthreads = info.dwNumberOfProcessors; + if (numthreads < 1 || numthreads > 32) + numthreads = 1; + } + + qprintf ("%i threads\n", numthreads); +} + + +void ThreadLock (void) +{ + if (!threaded) + return; + EnterCriticalSection (&crit); + if (enter) + Error ("Recursive ThreadLock\n"); + enter = 1; +} + +void ThreadUnlock (void) +{ + if (!threaded) + return; + if (!enter) + Error ("ThreadUnlock without lock\n"); + enter = 0; + LeaveCriticalSection (&crit); +} + +/* +============= +RunThreadsOn +============= +*/ +void RunThreadsOn (int workcnt, qboolean showpacifier, void(*func)(int)) +{ + int threadid[MAX_THREADS]; + HANDLE threadhandle[MAX_THREADS]; + int i; + int start, end; + + start = I_FloatTime (); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = true; + // + // run threads in parallel + // + InitializeCriticalSection (&crit); + for (i=0 ; i + +pthread_mutex_t *my_mutex; + +void ThreadLock (void) +{ + if (my_mutex) + pthread_mutex_lock (my_mutex); +} + +void ThreadUnlock (void) +{ + if (my_mutex) + pthread_mutex_unlock (my_mutex); +} + + +/* +============= +RunThreadsOn +============= +*/ +void RunThreadsOn (int workcnt, qboolean showpacifier, void(*func)(int)) +{ + int i; + pthread_t work_threads[MAX_THREADS]; + pthread_addr_t status; + pthread_attr_t attrib; + pthread_mutexattr_t mattrib; + int start, end; + + start = I_FloatTime (); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = true; + + if (pacifier) + setbuf (stdout, NULL); + + if (!my_mutex) + { + my_mutex = malloc (sizeof(*my_mutex)); + if (pthread_mutexattr_create (&mattrib) == -1) + Error ("pthread_mutex_attr_create failed"); + if (pthread_mutexattr_setkind_np (&mattrib, MUTEX_FAST_NP) == -1) + Error ("pthread_mutexattr_setkind_np failed"); + if (pthread_mutex_init (my_mutex, mattrib) == -1) + Error ("pthread_mutex_init failed"); + } + + if (pthread_attr_create (&attrib) == -1) + Error ("pthread_attr_create failed"); + if (pthread_attr_setstacksize (&attrib, 0x100000) == -1) + Error ("pthread_attr_setstacksize failed"); + + for (i=0 ; i +#include "cmdlib.h" +#include "mathlib.h" +#include "trilib.h" + +// on disk representation of a face + + +#define FLOAT_START 99999.0 +#define FLOAT_END -FLOAT_START +#define MAGIC 123322 + +//#define NOISY 1 + +typedef struct { + float v[3]; +} vector; + +typedef struct +{ + vector n; /* normal */ + vector p; /* point */ + vector c; /* color */ + float u; /* u */ + float v; /* v */ +} aliaspoint_t; + +typedef struct { + aliaspoint_t pt[3]; +} tf_triangle; + + +void ByteSwapTri (tf_triangle *tri) +{ + int i; + + for (i=0 ; iverts[j][k] = tri.pt[j].p.v[k]; + } + } + + ptri++; + + if ((ptri - *pptri) >= MAXTRIANGLES) + Error ("Error: too many triangles; increase MAXTRIANGLES\n"); + } + } + + *numtriangles = ptri - *pptri; + + fclose (input); +} + diff --git a/hlsdk/utils/common/trilib.h b/hlsdk/utils/common/trilib.h new file mode 100644 index 00000000..09271666 --- /dev/null +++ b/hlsdk/utils/common/trilib.h @@ -0,0 +1,21 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// +// trilib.h: header file for loading triangles from an Alias triangle file +// +#define MAXTRIANGLES 2048 + +typedef struct { + vec3_t verts[3]; +} triangle_t; + +void LoadTriangleList (char *filename, triangle_t **pptri, int *numtriangles); + diff --git a/hlsdk/utils/common/wadlib.c b/hlsdk/utils/common/wadlib.c new file mode 100644 index 00000000..5b6485bf --- /dev/null +++ b/hlsdk/utils/common/wadlib.c @@ -0,0 +1,339 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// wad2lib.c + +#include +#include +#include +#include +#include +#include +#include +//#include +#include + +#ifdef NeXT +#include +#endif +#include "cmdlib.h" +#include "wadlib.h" + +/* +============================================================================ + + WAD READING + +============================================================================ +*/ + + +lumpinfo_t *lumpinfo; // location of each lump on disk +int numlumps; + +wadinfo_t header; +FILE *wadhandle; + + +/* +==================== +W_OpenWad +==================== +*/ +void W_OpenWad (char *filename) +{ + lumpinfo_t *lump_p; + unsigned int i; + int length; + +// +// open the file and add to directory +// + wadhandle = SafeOpenRead (filename); + SafeRead (wadhandle, &header, sizeof(header)); + + if (strncmp(header.identification,"WAD2",4) && + strncmp(header.identification, "WAD3", 4)) + Error ("Wad file %s doesn't have WAD2/WAD3 id\n",filename); + + header.numlumps = LittleLong(header.numlumps); + header.infotableofs = LittleLong(header.infotableofs); + + numlumps = header.numlumps; + + length = numlumps*sizeof(lumpinfo_t); + lumpinfo = malloc (length); + lump_p = lumpinfo; + + fseek (wadhandle, header.infotableofs, SEEK_SET); + SafeRead (wadhandle, lumpinfo, length); + +// +// Fill in lumpinfo +// + + for (i = 0 ; i < (unsigned int)numlumps ; i++,lump_p++) + { + lump_p->filepos = LittleLong(lump_p->filepos); + lump_p->size = LittleLong(lump_p->size); + } +} + + + +void CleanupName (char *in, char *out) +{ + int i; + + for (i=0 ; iname ) ; i++ ) + { + if (!in[i]) + break; + + out[i] = toupper(in[i]); + } + + for ( ; iname ); i++ ) + out[i] = 0; +} + + +/* +==================== +W_CheckNumForName + +Returns -1 if name not found +==================== +*/ +int W_CheckNumForName (char *name) +{ + char cleanname[16]; + int v1,v2, v3, v4; + int i; + lumpinfo_t *lump_p; + + CleanupName (name, cleanname); + +// make the name into four integers for easy compares + + v1 = *(int *)cleanname; + v2 = *(int *)&cleanname[4]; + v3 = *(int *)&cleanname[8]; + v4 = *(int *)&cleanname[12]; + +// find it + + lump_p = lumpinfo; + for (i=0 ; iname == v1 + && *(int *)&lump_p->name[4] == v2 + && *(int *)&lump_p->name[8] == v3 + && *(int *)&lump_p->name[12] == v4) + return i; + } + + return -1; +} + + +/* +==================== +W_GetNumForName + +Calls W_CheckNumForName, but bombs out if not found +==================== +*/ +int W_GetNumForName (char *name) +{ + int i; + + i = W_CheckNumForName (name); + if (i != -1) + return i; + + Error ("W_GetNumForName: %s not found!",name); + return -1; +} + + +/* +==================== +W_LumpLength + +Returns the buffer size needed to load the given lump +==================== +*/ +int W_LumpLength (int lump) +{ + if (lump >= numlumps) + Error ("W_LumpLength: %i >= numlumps",lump); + return lumpinfo[lump].size; +} + + +/* +==================== +W_ReadLumpNum + +Loads the lump into the given buffer, which must be >= W_LumpLength() +==================== +*/ +void W_ReadLumpNum (int lump, void *dest) +{ + lumpinfo_t *l; + + if (lump >= numlumps) + Error ("W_ReadLump: %i >= numlumps",lump); + l = lumpinfo+lump; + + fseek (wadhandle, l->filepos, SEEK_SET); + SafeRead (wadhandle, dest, l->size); +} + + + +/* +==================== +W_LoadLumpNum +==================== +*/ +void *W_LoadLumpNum (int lump) +{ + void *buf; + + if (lump >= numlumps) + Error ("W_CacheLumpNum: %i >= numlumps",lump); + + buf = malloc (W_LumpLength (lump)); + W_ReadLumpNum (lump, buf); + + return buf; +} + + +/* +==================== +W_LoadLumpName +==================== +*/ +void *W_LoadLumpName (char *name) +{ + return W_LoadLumpNum (W_GetNumForName(name)); +} + + +/* +=============================================================================== + + WAD CREATION + +=============================================================================== +*/ + +FILE *outwad; + +lumpinfo_t outinfo[4096]; +int outlumps; + +short (*wadshort) (short l); +int (*wadlong) (int l); + +/* +=============== +NewWad +=============== +*/ + +void NewWad (char *pathname, qboolean bigendien) +{ + outwad = SafeOpenWrite (pathname); + fseek (outwad, sizeof(wadinfo_t), SEEK_SET); + memset (outinfo, 0, sizeof(outinfo)); + + if (bigendien) + { + wadshort = BigShort; + wadlong = BigLong; + } + else + { + wadshort = LittleShort; + wadlong = LittleLong; + } + + outlumps = 0; +} + + +/* +=============== +AddLump +=============== +*/ + +void AddLump (char *name, void *buffer, int length, int type, int compress) +{ + lumpinfo_t *info; + int ofs; + + info = &outinfo[outlumps]; + outlumps++; + + memset (info,0,sizeof(info)); + + strcpy (info->name, name); + strupr (info->name); + + ofs = ftell(outwad); + info->filepos = wadlong(ofs); + info->size = info->disksize = wadlong(length); + info->type = type; + info->compression = compress; + +// FIXME: do compression + + SafeWrite (outwad, buffer, length); +} + + +/* +=============== +WriteWad +=============== +*/ + +void WriteWad (int wad3) +{ + wadinfo_t header; + int ofs; + +// write the lumpingo + ofs = ftell(outwad); + + SafeWrite (outwad, outinfo, outlumps*sizeof(lumpinfo_t) ); + +// write the header + +// a program will be able to tell the ednieness of a wad by the id + header.identification[0] = 'W'; + header.identification[1] = 'A'; + header.identification[2] = 'D'; + header.identification[3] = wad3 ? '3' : '2'; + + header.numlumps = wadlong(outlumps); + header.infotableofs = wadlong(ofs); + + fseek (outwad, 0, SEEK_SET); + SafeWrite (outwad, &header, sizeof(header)); + fclose (outwad); +} + + diff --git a/hlsdk/utils/common/wadlib.h b/hlsdk/utils/common/wadlib.h new file mode 100644 index 00000000..5b836185 --- /dev/null +++ b/hlsdk/utils/common/wadlib.h @@ -0,0 +1,63 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// wadlib.h + +// +// wad reading +// + +#define CMP_NONE 0 +#define CMP_LZSS 1 + +#define TYP_NONE 0 +#define TYP_LABEL 1 +#define TYP_LUMPY 64 // 64 + grab command number + +typedef struct +{ + char identification[4]; // should be WAD2 or 2DAW + int numlumps; + int infotableofs; +} wadinfo_t; + + +typedef struct +{ + int filepos; + int disksize; + int size; // uncompressed + char type; + char compression; + char pad1, pad2; + char name[16]; // must be null terminated +} lumpinfo_t; + +extern lumpinfo_t *lumpinfo; // location of each lump on disk +extern int numlumps; +extern wadinfo_t header; + +void W_OpenWad (char *filename); +int W_CheckNumForName (char *name); +int W_GetNumForName (char *name); +int W_LumpLength (int lump); +void W_ReadLumpNum (int lump, void *dest); +void *W_LoadLumpNum (int lump); +void *W_LoadLumpName (char *name); + +void CleanupName (char *in, char *out); + +// +// wad creation +// +void NewWad (char *pathname, qboolean bigendien); +void AddLump (char *name, void *buffer, int length, int type, int compress); +void WriteWad (int wad3); + diff --git a/hlsdk/utils/light/light.c b/hlsdk/utils/light/light.c new file mode 100644 index 00000000..cb35f562 --- /dev/null +++ b/hlsdk/utils/light/light.c @@ -0,0 +1,228 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// lighting.c + +#include "light.h" + +/* + +NOTES +----- + +*/ + +float scaledist = 1.0; +float scalecos = 0.5; +float rangescale = 0.5; + +byte *filebase, *file_p, *file_end; + +dmodel_t *bspmodel; + +vec3_t bsp_origin; + +qboolean extrasamples; +qboolean hicolor; +qboolean clamp192 = true; + +float minlights[MAX_MAP_FACES]; + +lightentity_t lightentities[MAX_MAP_ENTITIES]; +int numlightentities; + + +/* +================== +LoadEntities +================== +*/ +void LoadEntities (void) +{ + char *s, *s2; + entity_t *e; + lightentity_t *le; + int i, j; + + ParseEntities (); + +// go through all the entities + for (i=1 ; iclassname, s); + s = ValueForKey( e, "_light" ); + if( s ) + { + double v1, v2, v3; + + v1 = v2 = v3 = 0; + if( sscanf( s, "%lf %lf %lf", &v1, &v2, &v3) != 3 ) + v2 = v3 = v1; + + le->light[0] = v1; + le->light[1] = v2; + le->light[2] = v3; + } + else + { + le->light[0] = DEFAULTLIGHTLEVEL; + le->light[1] = DEFAULTLIGHTLEVEL; + le->light[2] = DEFAULTLIGHTLEVEL; + } + + le->style = FloatForKey (e, "style"); + le->angle = FloatForKey (e, "angle"); + GetVectorForKey (e, "origin", le->origin); + + s = ValueForKey (e, "target"); + if (!s[0]) + continue; + + // find matching targetname + for (j=1 ; jtargetent = true; + GetVectorForKey (&entities[j], "origin", le->targetorigin); + break; + } + } + if (j == num_entities) + printf ("WARNING: entity %i has unmatched target %s\n", i, s); + } + + qprintf ("%d lightentities\n", numlightentities); + +} + + +byte *GetFileSpace (int size) +{ + byte *buf; + + ThreadLock(); + file_p = (byte *)(((long)file_p + 3)&~3); + buf = file_p; + file_p += size; + ThreadUnlock(); + if (file_p > file_end) + Error ("GetFileSpace: overrun"); + return buf; +} + + + +/* +============= +LightWorld +============= +*/ +void LightWorld (void) +{ + filebase = file_p = dlightdata; + file_end = filebase + MAX_MAP_LIGHTING; + + RunThreadsOnIndividual (numfaces, true, LightFace); + + lightdatasize = file_p - filebase; + + printf ("lightdatasize: %i\n", lightdatasize); +} + + +/* +======== +main + +light modelfile +======== +*/ +int main (int argc, char **argv) +{ + int i; + double start, end; + char source[1024]; + + printf("Light.exe Version 1.3 Id Software and valve (%s)\n", __DATE__ ); + printf ("----- LightFaces ----\n"); + + // default to 24-bit light info + hicolor = true; + + for (i=1 ; iface->texinfo]; + +// convert from float to vec_t + for (i=0 ; i<2 ; i++) + for (j=0 ; j<3 ; j++) + l->worldtotex[i][j] = tex->vecs[i][j]; + +// calculate a normal to the texture axis. points can be moved along this +// without changing their S/T + texnormal[0] = tex->vecs[1][1]*tex->vecs[0][2] + - tex->vecs[1][2]*tex->vecs[0][1]; + texnormal[1] = tex->vecs[1][2]*tex->vecs[0][0] + - tex->vecs[1][0]*tex->vecs[0][2]; + texnormal[2] = tex->vecs[1][0]*tex->vecs[0][1] + - tex->vecs[1][1]*tex->vecs[0][0]; + VectorNormalize (texnormal); + +// flip it towards plane normal + distscale = DotProduct (texnormal, l->facenormal); + if (!distscale) + Error ("Texture axis perpendicular to face"); + if (distscale < 0) + { + distscale = -distscale; + VectorSubtract (vec3_origin, texnormal, texnormal); + } + +// distscale is the ratio of the distance along the texture normal to +// the distance along the plane normal + distscale = 1/distscale; + + for (i=0 ; i<2 ; i++) + { + len = VectorLength (l->worldtotex[i]); + dist = DotProduct (l->worldtotex[i], l->facenormal); + dist *= distscale; + VectorMA (l->worldtotex[i], -dist, texnormal, l->textoworld[i]); + VectorScale (l->textoworld[i], (1/len)*(1/len), l->textoworld[i]); + } + + +// calculate texorg on the texture plane + for (i=0 ; i<3 ; i++) + l->texorg[i] = -tex->vecs[0][3]* l->textoworld[0][i] - tex->vecs[1][3] * l->textoworld[1][i]; + +// project back to the face plane + dist = DotProduct (l->texorg, l->facenormal) - l->facedist - 1; + dist *= distscale; + VectorMA (l->texorg, -dist, texnormal, l->texorg); + +} + +/* +================ +CalcFaceExtents + +Fills in s->texmins[] and s->texsize[] +also sets exactmins[] and exactmaxs[] +================ +*/ +void CalcFaceExtents (lightinfo_t *l) +{ + dface_t *s; + vec_t mins[2], maxs[2], val; + int i,j, e; + dvertex_t *v; + texinfo_t *tex; + + s = l->face; + + mins[0] = mins[1] = 999999; + maxs[0] = maxs[1] = -99999; + + tex = &texinfo[s->texinfo]; + + for (i=0 ; inumedges ; i++) + { + e = dsurfedges[s->firstedge+i]; + if (e >= 0) + v = dvertexes + dedges[e].v[0]; + else + v = dvertexes + dedges[-e].v[1]; + + for (j=0 ; j<2 ; j++) + { + val = v->point[0] * tex->vecs[j][0] + + v->point[1] * tex->vecs[j][1] + + v->point[2] * tex->vecs[j][2] + + tex->vecs[j][3]; + if (val < mins[j]) + mins[j] = val; + if (val > maxs[j]) + maxs[j] = val; + } + } + + for (i=0 ; i<2 ; i++) + { + l->exactmins[i] = mins[i]; + l->exactmaxs[i] = maxs[i]; + + mins[i] = floor(mins[i]/16); + maxs[i] = ceil(maxs[i]/16); + + l->texmins[i] = mins[i]; + l->texsize[i] = maxs[i] - mins[i]; + if (l->texsize[i] > 17) + Error ("Bad surface extents"); + } +} + +/* +================= +CalcPoints + +For each texture aligned grid point, back project onto the plane +to get the world xyz value of the sample point +================= +*/ +int c_bad; +void CalcPoints (lightinfo_t *l) +{ + int i; + int s, t, j; + int w, h, step; + vec_t starts, startt, us, ut; + vec_t *surf; + vec_t mids, midt; + vec3_t facemid, move; + +// +// fill in surforg +// the points are biased towards the center of the surface +// to help avoid edge cases just inside walls +// + surf = l->surfpt[0]; + mids = (l->exactmaxs[0] + l->exactmins[0])/2; + midt = (l->exactmaxs[1] + l->exactmins[1])/2; + + for (j=0 ; j<3 ; j++) + facemid[j] = l->texorg[j] + l->textoworld[0][j]*mids + l->textoworld[1][j]*midt; + + if (extrasamples) + { // extra filtering + h = (l->texsize[1]+1)*2; + w = (l->texsize[0]+1)*2; + starts = (l->texmins[0]-0.5)*16; + startt = (l->texmins[1]-0.5)*16; + step = 8; + } + else + { + h = l->texsize[1]+1; + w = l->texsize[0]+1; + starts = l->texmins[0]*16; + startt = l->texmins[1]*16; + step = 16; + } + + l->numsurfpt = w * h; + for (t=0 ; ttexorg[j] + l->textoworld[0][j]*us + + l->textoworld[1][j]*ut; + + if (CastRay (facemid, surf) != -1) + break; // got it + if (i & 1) + { + if (us > mids) + { + us -= 8; + if (us < mids) + us = mids; + } + else + { + us += 8; + if (us > mids) + us = mids; + } + } + else + { + if (ut > midt) + { + ut -= 8; + if (ut < midt) + ut = midt; + } + else + { + ut += 8; + if (ut > midt) + ut = midt; + } + } + + // move surf 8 pixels towards the center + VectorSubtract (facemid, surf, move); + VectorNormalize (move); + VectorMA (surf, 8, move, surf); + } + if (i == 2) + c_bad++; + } + } + +} + + +/* +=============================================================================== + +FACE LIGHTING + +=============================================================================== +*/ + +int c_culldistplane, c_proper; + +/* +================ +SingleLightFace +================ +*/ +void SingleLightFace (lightentity_t *light, lightinfo_t *l) +{ + vec_t dist; + vec3_t incoming; + vec_t angle; + vec_t add; + vec_t *surf; + qboolean hit; + int mapnum; + int size; + int c, i; + vec3_t rel; + vec3_t spotvec; + vec_t falloff; + vec3_t *lightsamp; + float intensity; + + VectorSubtract (light->origin, bsp_origin, rel); + dist = scaledist * (DotProduct (rel, l->facenormal) - l->facedist); + +// don't bother with lights behind the surface + if (dist <= 0) + return; + +// don't bother with light too far away + intensity = ( light->light[ 0 ] + light->light[ 1 ] + light->light[ 2 ] ) / 3.0; + if( dist > intensity ) + { + c_culldistplane++; + return; + } + + if (light->targetent) + { + VectorSubtract (light->targetorigin, light->origin, spotvec); + VectorNormalize (spotvec); + if (!light->angle) + falloff = -cos(20*Q_PI/180); + else + falloff = -cos(light->angle/2*Q_PI/180); + } + else + falloff = 0; // shut up compiler warnings + + mapnum = 0; + for (mapnum=0 ; mapnumnumlightstyles ; mapnum++) + if (l->lightstyles[mapnum] == light->style) + break; + lightsamp = l->lightmaps[mapnum]; + if (mapnum == l->numlightstyles) + { // init a new light map + if (mapnum == MAXLIGHTMAPS) + { + printf ("WARNING: Too many light styles on a face\n"); + return; + } + size = (l->texsize[1]+1)*(l->texsize[0]+1); + for (i=0 ; isurfpt[0]; + for (c=0 ; cnumsurfpt ; c++, surf+=3) + { + dist = CastRay(light->origin, surf)*scaledist; + if (dist < 0) + continue; // light doesn't reach + + VectorSubtract (light->origin, surf, incoming); + VectorNormalize (incoming); + angle = DotProduct (incoming, l->facenormal); + if (light->targetent) + { // spotlight cutoff + if (DotProduct (spotvec, incoming) > falloff) + continue; + } + + angle = (1.0-scalecos) + scalecos*angle; + for( i=0; i<3; i++ ) + { + add = light->light[i] - dist; + add *= angle; + if (add < 0) + continue; + + lightsamp[c][i] += add; + } + + // check intensity + intensity = ( lightsamp[ c ][ 0 ] + lightsamp[ c ][ 1 ] + lightsamp[ c ][ 2 ] ) / 3.0; + if( intensity > 1 ) // ignore real tiny lights + hit = true; + } + + if (mapnum == l->numlightstyles && hit) + { + l->lightstyles[mapnum] = light->style; + l->numlightstyles++; // the style has some real data now + } +} + +/* +============ +FixMinlight +============ +*/ +void FixMinlight (lightinfo_t *l) +{ + int i, j; + float minlight; + + minlight = minlights[l->surfnum]; + + // if minlight is set, there must be a style 0 light map + if (!minlight) + return; + + for (i=0 ; i< l->numlightstyles ; i++) + { + if (l->lightstyles[i] == 0) + break; + } + if (i == l->numlightstyles) + { + if (l->numlightstyles == MAXLIGHTMAPS) + return; // oh well.. + + for (j=0 ; jnumsurfpt ; j++) + { + l->lightmaps[i][j][0] = minlight; + l->lightmaps[i][j][1] = minlight; + l->lightmaps[i][j][2] = minlight; + } + + l->lightstyles[i] = 0; + l->numlightstyles++; + } + else + { + for (j=0 ; jnumsurfpt ; j++) + { + float intensity = ( l->lightmaps[i][j][0] + l->lightmaps[i][j][1] + l->lightmaps[i][j][2] ) / 3.0; + + if ( intensity < minlight ) + { + l->lightmaps[i][j][0] = minlight; + l->lightmaps[i][j][1] = minlight; + l->lightmaps[i][j][2] = minlight; + } + } + } +} + + +/* +============ +LightFace +============ +*/ +void LightFace (int surfnum) +{ + dface_t *f; + lightinfo_t l; + int s, t; + int i,j,c; + vec3_t total; + int size; + int lightmapwidth, lightmapsize; + byte *out; + vec3_t *light; + int w, h; + int clamp = 192; + float clampfactor = 0.75; + + if ( !clamp192 ) + { + clamp = 255; + clampfactor = 1.0; + } + + f = dfaces + surfnum; + +// +// some surfaces don't need lightmaps +// + f->lightofs = -1; + for (j=0 ; jstyles[j] = 255; + + if ( texinfo[f->texinfo].flags & TEX_SPECIAL) + { // non-lit texture + return; + } + + memset (&l, 0, sizeof(l)); + l.surfnum = surfnum; + l.face = f; + +// +// rotate plane +// + VectorCopy (dplanes[f->planenum].normal, l.facenormal); + l.facedist = dplanes[f->planenum].dist; + if (f->side) + { + VectorSubtract (vec3_origin, l.facenormal, l.facenormal); + l.facedist = -l.facedist; + } + + + + CalcFaceVectors (&l); + CalcFaceExtents (&l); + CalcPoints (&l); + + lightmapwidth = l.texsize[0]+1; + + size = lightmapwidth*(l.texsize[1]+1); + if (size > SINGLEMAP) + Error ("Bad lightmap size"); + + for (i=0 ; istyles[i] = l.lightstyles[i]; + + if( hicolor ) + lightmapsize = size*l.numlightstyles*3; + else + lightmapsize = size * l.numlightstyles; + + out = GetFileSpace (lightmapsize); + f->lightofs = out - filebase; + +// extra filtering + h = (l.texsize[1]+1)*2; + w = (l.texsize[0]+1)*2; + + for (i=0 ; i< l.numlightstyles ; i++) + { + if (l.lightstyles[i] == 0xff) + Error ("Wrote empty lightmap"); + light = l.lightmaps[i]; + c = 0; + for (t=0 ; t<=l.texsize[1] ; t++) + { + for (s=0 ; s<=l.texsize[0] ; s++, c++) + { + if (extrasamples) + { +#ifdef OLD_CODE + // filtered sample + VectorCopy( light[t*2*w+s*2], total ); + VectorAdd( total, light[t*2*w+s*2+1], total ); + VectorAdd( total, light[(t*2+1)*w+s*2], total ); + VectorAdd( total, light[(t*2+1)*w+s*2+1], total ); + VectorScale( total, 0.25, total ); +#else + int u, v; + float weight[3][3] = + { + { 5, 9, 5 }, + { 9, 16, 9 }, + { 5, 9, 5 }, + }; + float divisor = 0.0; + VectorFill(total,0); + for ( u = 0; u < 3; u++ ) + { + for ( v = 0; v < 3; v++ ) + { + if ( s+u-2>=0 && t+v-1>=0 && s+u-1 <= w && t+v-1 <= h) + { + vec3_t sample; + VectorScale( light[((t*2)+(v-1))*w + ((s*2)+(u-1))], weight[u][v], sample ); + divisor += weight[u][v]; + VectorAdd( total, sample, total ); + } + } + } +#endif + if ( divisor > 1.0 ) + VectorScale( total, 1/divisor, total ); + total[0] = max(total[0],0.0); + total[1] = max(total[1],0.0); + total[2] = max(total[2],0.0); + } + else + VectorCopy( light[ c ], total ); + + // Scale + VectorScale( total, rangescale, total ); + + // Clamp + if( hicolor ) + { + for( j=0; j<3; j++ ) + { + total[ j ] *= clampfactor; + + if( total[j] > clamp) + total[j] = clamp; + else if (total[j] < 0) + Error ("light < 0"); + + *out++ = (byte) total[j]; + } + } + else + { + int intensity = total[ 0 ] + total[ 1 ] + total[ 2 ]; + if( intensity > 255 ) + intensity = 255; + else if( intensity < 0 ) + Error( "light < 0" ); + + *out++ = (byte) intensity; + } + } + } + } +} + diff --git a/hlsdk/utils/light/msvc6/light.dsp b/hlsdk/utils/light/msvc6/light.dsp new file mode 100644 index 00000000..238f766d --- /dev/null +++ b/hlsdk/utils/light/msvc6/light.dsp @@ -0,0 +1,132 @@ +# Microsoft Developer Studio Project File - Name="light" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=light - 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 "light.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 "light.mak" CFG="light - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "light - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "light - 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)" == "light - 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 ".\Release" +# PROP Intermediate_Dir ".\Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /GX /O2 /I "..\..\common" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /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 /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 /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "light - 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 ".\Debug" +# PROP Intermediate_Dir ".\Debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /Gm /GX /ZI /Od /I "..\..\common" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /FR /YX /FD /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 /nologo /subsystem:console /debug /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:console /debug /machine:I386 + +!ENDIF + +# Begin Target + +# Name "light - Win32 Release" +# Name "light - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=..\..\common\bspfile.c +# End Source File +# Begin Source File + +SOURCE=..\..\common\cmdlib.c +# End Source File +# Begin Source File + +SOURCE=..\light.c +# End Source File +# Begin Source File + +SOURCE=..\ltface.c +# End Source File +# Begin Source File + +SOURCE=..\..\common\mathlib.c +# End Source File +# Begin Source File + +SOURCE=..\..\common\scriplib.c +# End Source File +# Begin Source File + +SOURCE=..\..\common\threads.c +# End Source File +# Begin Source File + +SOURCE=..\trace.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=..\light.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/hlsdk/utils/light/msvc7/light.vcproj b/hlsdk/utils/light/msvc7/light.vcproj new file mode 100644 index 00000000..4d865ad1 --- /dev/null +++ b/hlsdk/utils/light/msvc7/light.vcproj @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hlsdk/utils/light/msvc8/light.vcproj b/hlsdk/utils/light/msvc8/light.vcproj new file mode 100644 index 00000000..46ee4bd8 --- /dev/null +++ b/hlsdk/utils/light/msvc8/light.vcproj @@ -0,0 +1,418 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hlsdk/utils/light/trace.c b/hlsdk/utils/light/trace.c new file mode 100644 index 00000000..9889ce95 --- /dev/null +++ b/hlsdk/utils/light/trace.c @@ -0,0 +1,206 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// trace.c + +#include "light.h" + +typedef struct tnode_s +{ + int type; + vec3_t normal; + float dist; + int children[2]; + int pad; +} tnode_t; + +tnode_t *tnodes, *tnode_p; + +/* +============== +MakeTnode + +Converts the disk node structure into the efficient tracing structure +============== +*/ +void MakeTnode (int nodenum) +{ + tnode_t *t; + dplane_t *plane; + int i; + dnode_t *node; + + t = tnode_p++; + + node = dnodes + nodenum; + plane = dplanes + node->planenum; + + t->type = plane->type; + VectorCopy (plane->normal, t->normal); + t->dist = plane->dist; + + for (i=0 ; i<2 ; i++) + { + if (node->children[i] < 0) + t->children[i] = dleafs[-node->children[i] - 1].contents; + else + { + t->children[i] = tnode_p - tnodes; + MakeTnode (node->children[i]); + } + } + +} + + +/* +============= +MakeTnodes + +Loads the node structure out of a .bsp file to be used for light occlusion +============= +*/ +void MakeTnodes (dmodel_t *bm) +{ + if (!numnodes) + Error ("Map has no nodes\n"); + tnode_p = tnodes = malloc(numnodes * sizeof(tnode_t)); + + MakeTnode (0); +} + + + +/* +============================================================================== + +LINE TRACING + +The major lighting operation is a point to point visibility test, performed +by recursive subdivision of the line by the BSP tree. + +============================================================================== +*/ + +typedef struct +{ + vec3_t backpt; + int side; + int node; +} tracestack_t; + + +/* +============== +TestLine +============== +*/ +qboolean TestLine (vec3_t start, vec3_t stop) +{ + int node; + float front, back; + tracestack_t *tstack_p; + int side; + float frontx,fronty, frontz, backx, backy, backz; + tracestack_t tracestack[64]; + tnode_t *tnode; + + frontx = start[0]; + fronty = start[1]; + frontz = start[2]; + backx = stop[0]; + backy = stop[1]; + backz = stop[2]; + + tstack_p = tracestack; + node = 0; + + while (1) + { + while (node < 0 && node != CONTENTS_SOLID) + { + // pop up the stack for a back side + tstack_p--; + if (tstack_p < tracestack) + return true; + node = tstack_p->node; + + // set the hit point for this plane + + frontx = backx; + fronty = backy; + frontz = backz; + + // go down the back side + + backx = tstack_p->backpt[0]; + backy = tstack_p->backpt[1]; + backz = tstack_p->backpt[2]; + + node = tnodes[tstack_p->node].children[!tstack_p->side]; + } + + if (node == CONTENTS_SOLID) + return false; // DONE! + + tnode = &tnodes[node]; + + switch (tnode->type) + { + case PLANE_X: + front = frontx - tnode->dist; + back = backx - tnode->dist; + break; + case PLANE_Y: + front = fronty - tnode->dist; + back = backy - tnode->dist; + break; + case PLANE_Z: + front = frontz - tnode->dist; + back = backz - tnode->dist; + break; + default: + front = (frontx*tnode->normal[0] + fronty*tnode->normal[1] + frontz*tnode->normal[2]) - tnode->dist; + back = (backx*tnode->normal[0] + backy*tnode->normal[1] + backz*tnode->normal[2]) - tnode->dist; + break; + } + + if (front > -ON_EPSILON && back > -ON_EPSILON) +// if (front > 0 && back > 0) + { + node = tnode->children[0]; + continue; + } + + if (front < ON_EPSILON && back < ON_EPSILON) +// if (front <= 0 && back <= 0) + { + node = tnode->children[1]; + continue; + } + + side = front < 0; + + front = front / (front-back); + + tstack_p->node = node; + tstack_p->side = side; + tstack_p->backpt[0] = backx; + tstack_p->backpt[1] = backy; + tstack_p->backpt[2] = backz; + + tstack_p++; + + backx = frontx + front*(backx-frontx); + backy = fronty + front*(backy-fronty); + backz = frontz + front*(backz-frontz); + + node = tnode->children[side]; + } +} + + diff --git a/hlsdk/utils/makefont/makefont.cpp b/hlsdk/utils/makefont/makefont.cpp new file mode 100644 index 00000000..3af09e65 --- /dev/null +++ b/hlsdk/utils/makefont/makefont.cpp @@ -0,0 +1,494 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#define _NOENUMQBOOL + +#include + +extern "C" +{ + #include "cmdlib.h" + #include "wadlib.h" +} +#include "qfont.h" + +#define DEFAULT_FONT "Arial" + +#define FONT_TAG 6 // Font's are the 6th tag after the TYP_LUMPY base ( 64 )...i.e., type == 70 + +BOOL bItalic = FALSE; +BOOL bBold = FALSE; +BOOL bUnderline = FALSE; + +char fontname[ 256 ]; +int pointsize[3] = { 9, 11, 15 }; + +/* +================= +zeromalloc + +Allocates and zeroes memory +================= +*/ +void *zeromalloc( size_t size ) +{ + unsigned char *pbuffer; + pbuffer = ( unsigned char * )malloc( size ); + if ( !pbuffer ) + { + printf( "Failed on allocation of %i bytes", size ); + exit( -1 ); + } + + memset( pbuffer, 0, size ); + return ( void * )pbuffer; +} + +/* +================= +Draw_SetupConsolePalette + +Set's the palette to full brightness ( 192 ) and +set's up palette entry 0 -- black +================= +*/ +void Draw_SetupConsolePalette( unsigned char *pal ) +{ + unsigned char *pPalette; + int i; + + pPalette = pal; + + *(short *)pPalette = 3 * 256; + pPalette += sizeof( short ); + + for ( i = 0; i < 256; i++ ) + { + pPalette[3 * i + 0 ] = i; + pPalette[3 * i + 1 ] = i; + pPalette[3 * i + 2 ] = i; + } + + // Set palette zero correctly + pPalette[ 0 ] = 0; + pPalette[ 1 ] = 0; + pPalette[ 2 ] = 0; +} + +/* +================= +CreateConsoleFont + +Renders TT font into memory dc and creates appropriate qfont_t structure +================= +*/ + +// YWB: Sigh, VC 6.0's global optimizer causes weird stack fixups in release builds. Disable the globabl optimizer for this function. +#pragma optimize( "g", off ) +qfont_t *CreateConsoleFont( char *pszFont, int nPointSize, BOOL bItalic, BOOL bUnderline, BOOL bBold, int *outsize ) +{ + HDC hdc; + HDC hmemDC; + HBITMAP hbm, oldbm; + RECT rc; + HFONT fnt, oldfnt; + int startchar = 32; + int c; + int i, j; + int x, y; + int nScans; + unsigned char *bits; + BITMAPINFO tempbmi; + BITMAPINFO *pbmi; + BITMAPINFOHEADER *pbmheader; + unsigned char *pqdata; + unsigned char *pCur; + int x1, y1; + unsigned char *pPalette; + qfont_t *pqf = NULL; + int fullsize; + int w = 16; + int h = (128-32)/16; + int charheight = nPointSize + 5; + int charwidth = 16; + RECT rcChar; + + // Create the font + fnt = CreateFont( -nPointSize, 0, 0, 0, bBold ? FW_HEAVY : FW_MEDIUM, bItalic, bUnderline, 0, ANSI_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, VARIABLE_PITCH | FF_DONTCARE, pszFont ); + + bits = NULL; + + fullsize = sizeof( qfont_t ) - 4 + ( 128 * w * charwidth ) + sizeof(short) + 768 + 64; + + // Store off final size + *outsize = fullsize; + + pqf = ( qfont_t * )zeromalloc( fullsize ); + pqdata = (unsigned char *)pqf + sizeof( qfont_t ) - 4; + + pPalette = pqdata + ( 128 * w * charwidth ); + + // Configure palette + Draw_SetupConsolePalette( pPalette ); + + hdc = GetDC( NULL ); + hmemDC = CreateCompatibleDC( hdc ); + + rc.top = 0; + rc.left = 0; + rc.right = charwidth * w; + rc.bottom = charheight * h; + + hbm = CreateBitmap( charwidth * w, charheight * h, 1, 1, NULL ); + oldbm = (HBITMAP)SelectObject( hmemDC, hbm ); + oldfnt = (HFONT)SelectObject( hmemDC, fnt ); + + SetTextColor( hmemDC, 0x00ffffff ); + SetBkMode( hmemDC, TRANSPARENT ); + + // Paint black background + FillRect( hmemDC, &rc, (HBRUSH)GetStockObject( BLACK_BRUSH ) ); + + // Draw character set into memory DC + for ( j = 0; j < h; j++ ) + { + for ( i = 0; i < w; i++ ) + { + x = i * charwidth; + y = j * charheight; + + c = (char)( startchar + j * w + i ); + + // Only draw printable characters, of course + if ( isprint( c ) && c <= 127 ) + { + // Draw it. + rcChar.left = x + 1; + rcChar.top = y + 1; + rcChar.right = x + charwidth - 1; + rcChar.bottom = y + charheight - 1; + + DrawText( hmemDC, (char *)&c, 1, &rcChar, DT_NOPREFIX | DT_LEFT ); + } + } + } + + // Now turn the qfont into raw format + memset( &tempbmi, 0, sizeof( BITMAPINFO ) ); + pbmheader = ( BITMAPINFOHEADER * )&tempbmi; + + pbmheader->biSize = sizeof( BITMAPINFOHEADER ); + pbmheader->biWidth = w * charwidth; + pbmheader->biHeight = -h * charheight; + pbmheader->biPlanes = 1; + pbmheader->biBitCount = 1; + pbmheader->biCompression = BI_RGB; + + // Find out how big the bitmap is + nScans = GetDIBits( hmemDC, hbm, 0, h * charheight, NULL, &tempbmi, DIB_RGB_COLORS ); + + // Allocate space for all bits + pbmi = ( BITMAPINFO * )zeromalloc( sizeof ( BITMAPINFOHEADER ) + 2 * sizeof( RGBQUAD ) + pbmheader->biSizeImage ); + + memcpy( pbmi, &tempbmi, sizeof( BITMAPINFO ) ); + + bits = ( unsigned char * )pbmi + sizeof( BITMAPINFOHEADER ) + 2 * sizeof( RGBQUAD ); + + // Now read in bits + nScans = GetDIBits( hmemDC, hbm, 0, h * charheight, bits, pbmi, DIB_RGB_COLORS ); + + if ( nScans > 0 ) + { + // Now convert to proper raw format + // + // Now get results from dib + pqf->height = 128; // Always set to 128 + pqf->width = charwidth; + pqf->rowheight = charheight; + pqf->rowcount = h; + pCur = pqdata; + + // Set everything to index 255 ( 0xff ) == transparent + memset( pCur, 0xFF, w * charwidth * pqf->height ); + + for ( j = 0; j < h; j++ ) + { + for ( i = 0; i < w; i++ ) + { + int edge = 1; + int bestwidth; + x = i * charwidth; + y = j * charheight; + + + c = (char)( startchar + j * w + i ); + + pqf->fontinfo[ c ].charwidth = charwidth; + pqf->fontinfo[ c ].startoffset = y * w * charwidth + x; + + bestwidth = 0; + + // In this first pass, place the black drop shadow so characters draw ok in the engine against + // most backgrounds. + // YWB: FIXME, apply a box filter and enable blending? + + // Make it all transparent for starters + for ( y1 = 0; y1 < charheight; y1++ ) + { + for ( x1 = 0; x1 < charwidth; x1++ ) + { + int offset; + offset = ( y + y1 ) * w * charwidth + x + x1 ; + // Dest + pCur = pqdata + offset; + // Assume transparent + pCur[0] = 255; + } + } + + // Put black pixels below and to the right of each pixel + for ( y1 = edge; y1 < charheight - edge; y1++ ) + { + for ( x1 = 0; x1 < charwidth; x1++ ) + { + int offset; + int srcoffset; + + int xx0, yy0; + + offset = ( y + y1 ) * w * charwidth + x + x1 ; + + // Dest + pCur = pqdata + offset; + + for ( xx0 = -edge; xx0 <= edge; xx0++ ) + { + for ( yy0 = -edge; yy0 <= edge; yy0++ ) + { + srcoffset = ( y + y1 + yy0 ) * w * charwidth + x + x1 + xx0; + + if ( bits[ srcoffset >> 3 ] & ( 1 << ( 7 - srcoffset & 7 ) ) ) + { + // Near Black + pCur[0] = 32; + } + } + } + } + } + + // Now copy in the actual font pixels + for ( y1 = 0; y1 < charheight; y1++ ) + { + for ( x1 = 0; x1 < charwidth; x1++ ) + { + int offset; + + offset = ( y + y1 ) * w * charwidth + x + x1; + + // Dest + pCur = pqdata + offset; + + if ( bits[ offset >> 3 ] & ( 1 << ( 7 - offset & 7 ) ) ) + { + if ( x1 > bestwidth ) + { + bestwidth = x1; + } + + // Full color + // FIXME: Enable true palette support in engine? + pCur[0] = 192; + } + } + } + + // bestwidth += 1; + /* + // Now blend it + for ( y1 = 0; y1 < charheight; y1++ ) + { + for ( x1 = 0; x1 < charwidth; x1++ ) + { + int offset; + + offset = ( y + y1 ) * w * charwidth + x + x1; + + // Dest + pCur = pqdata + offset; + + if ( bits[ offset >> 3 ] & ( 1 << ( 7 - offset & 7 ) ) ) + { + if ( x1 > bestwidth ) + { + bestwidth = x1; + } + + // Full color + // FIXME: Enable true palette support in engine? + pCur[0] = 192; + } + } + } + */ + + // Space character width + if ( c == 32 ) + { + bestwidth = 8; + } + else + { + // Small characters needs can be padded a bit so they don't run into each other + if ( bestwidth <= 14 ) + { + bestwidth += 2; + } + } + + // Store off width + pqf->fontinfo[ c ].charwidth = bestwidth; + } + } + } + + // Free memory bits + free ( pbmi ); + + SelectObject( hmemDC, oldfnt ); + DeleteObject( fnt ); + + SelectObject( hmemDC, oldbm ); + + DeleteObject( hbm ); + + DeleteDC( hmemDC ); + ReleaseDC( NULL, hdc ); + + return pqf; +} + +#pragma optimize( "g", on ) + +/* +================= +main + +================= +*/ +int main(int argc, char* argv[]) +{ + int i; + DWORD start, end; + char destfile[1024]; + char sz[ 32 ]; + int outsize[ 3 ]; + + qfont_t *fonts[ 3 ]; + + strcpy( fontname, DEFAULT_FONT ); + + printf("makefont.exe Version 1.0 by valve (%s)\n", __DATE__ ); + + printf ("----- Creating Console Font ----\n"); + + for (i=1 ; i= argc ) + { + Error( "Makefont: Insufficient point sizes specified\n" ); + } + pointsize[0] = atoi( argv[i+1] ); + pointsize[1] = atoi( argv[i+2] ); + pointsize[2] = atoi( argv[i+3] ); + i += 3; + } + else if (!strcmp(argv[i],"-italic")) + { + bItalic = TRUE; + printf ( "italic set\n"); + } + else if (!strcmp(argv[i],"-bold")) + { + bBold = TRUE; + printf ( "bold set\n"); + } + else if (!strcmp(argv[i],"-underline")) + { + bUnderline = TRUE; + printf ( "underline set\n"); + } + else if ( argv[i][0] == '-' ) + { + Error ("Unknown option \"%s\"", argv[i]); + } + else + break; + } + + if ( i != argc - 1 ) + { + Error ("usage: makefont [-font \"fontname\"] [-italic] [-underline] [-bold] [-pointsizes sm med lg] outfile"); + } + + printf( "Creating %i, %i, and %i point %s fonts\n", pointsize[0], pointsize[1], pointsize[2], fontname ); + + start = timeGetTime(); + + // Create the fonts + for ( i = 0 ; i < 3; i++ ) + { + fonts[ i ] = CreateConsoleFont( fontname, pointsize[i], bItalic, bUnderline, bBold, &outsize[ i ] ); + } + + // Create wad file + strcpy (destfile, argv[argc - 1]); + StripExtension (destfile); + DefaultExtension (destfile, ".wad"); + + NewWad( destfile, false ); + + // Add fonts as lumps + for ( i = 0; i < 3; i++ ) + { + sprintf( sz, "font%i", i ); + AddLump( sz, fonts[ i ], outsize[ i ], TYP_LUMPY + FONT_TAG, false ); + } + + // Store results as a WAD3 + // NOTE: ( should be named fonts.wad in the valve\ subdirectory ) + WriteWad( 3 ); + + // Clean up memory + for ( i = 0 ; i < 3; i++ ) + { + free( fonts[ i ] ); + } + + end = timeGetTime (); + + printf ( "%5.5f seconds elapsed\n", (float)( end - start )/1000.0 ); + + // Display for a second since it might not be running from command prompt + Sleep( 1000 ); + return 0; +} diff --git a/hlsdk/utils/makefont/msvc6/makefont.dsp b/hlsdk/utils/makefont/msvc6/makefont.dsp new file mode 100644 index 00000000..03b41ff0 --- /dev/null +++ b/hlsdk/utils/makefont/msvc6/makefont.dsp @@ -0,0 +1,134 @@ +# Microsoft Developer Studio Project File - Name="makefont" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=makefont - 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 "makefont.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 "makefont.mak" CFG="makefont - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "makefont - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "makefont - 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)" == "makefont - 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 "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /Yu"stdafx.h" /FD /c +# ADD CPP /nologo /W3 /GX /Zi /O2 /I "../../../common" /I "../../common" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX"qfont.h" /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 winmm.lib /nologo /subsystem:console /debug /machine:I386 +# Begin Special Build Tool +TargetPath=.\Release\makefont.exe +SOURCE="$(InputPath)" +# End Special Build Tool + +!ELSEIF "$(CFG)" == "makefont - 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 "Debug" +# PROP Intermediate_Dir "Debug" +# 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" /Yu"stdafx.h" /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "../../../common" /I "../../common" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX"qfont.h" /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 winmm.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# Begin Special Build Tool +TargetPath=.\Debug\makefont.exe +SOURCE="$(InputPath)" +# End Special Build Tool + +!ENDIF + +# Begin Target + +# Name "makefont - Win32 Release" +# Name "makefont - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\..\common\cmdlib.c +# End Source File +# Begin Source File + +SOURCE=..\makefont.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\common\wadlib.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\..\common\cmdlib.h +# End Source File +# Begin Source File + +SOURCE=..\StdAfx.h +# End Source File +# Begin Source File + +SOURCE=..\..\common\wadlib.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 +# Begin Source File + +SOURCE=..\ReadMe.txt +# End Source File +# End Target +# End Project diff --git a/hlsdk/utils/makefont/msvc6/makefont.dsw b/hlsdk/utils/makefont/msvc6/makefont.dsw new file mode 100644 index 00000000..a717f8c5 --- /dev/null +++ b/hlsdk/utils/makefont/msvc6/makefont.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "makefont"=.\makefont.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/hlsdk/utils/makefont/msvc7/makefont.vcproj b/hlsdk/utils/makefont/msvc7/makefont.vcproj new file mode 100644 index 00000000..6a2465ac --- /dev/null +++ b/hlsdk/utils/makefont/msvc7/makefont.vcproj @@ -0,0 +1,231 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hlsdk/utils/makefont/msvc8/makefont.vcproj b/hlsdk/utils/makefont/msvc8/makefont.vcproj new file mode 100644 index 00000000..5e1a9f14 --- /dev/null +++ b/hlsdk/utils/makefont/msvc8/makefont.vcproj @@ -0,0 +1,315 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hlsdk/utils/makels/makels.cpp b/hlsdk/utils/makels/makels.cpp new file mode 100644 index 00000000..b63674dd --- /dev/null +++ b/hlsdk/utils/makels/makels.cpp @@ -0,0 +1,181 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#if defined _MSC_VER && _MSC_VER >= 1400 + #ifndef _CRT_SECURE_NO_DEPRECATE + #define _CRT_SECURE_NO_DEPRECATE + #endif + + #pragma warning(disable: 4996) // deprecated functions +#endif + +#include +#include +#include +#include + + +char **ppszFiles = NULL; +int nFiles = 0; +int nMaxFiles = 0; + +int +string_comparator( const void *string1, const void *string2 ) +{ + char *s1 = *(char **)string1; + char *s2 = *(char **)string2; + return strcmp( s1, s2 ); +} + +void PrintUsage(char *pname) +{ + printf("\n\tusage:%s