From 425decdf7e9284d15aa726e3ae96b9942fb0e3ea Mon Sep 17 00:00:00 2001 From: IronClawTrem Date: Sun, 16 Feb 2020 03:40:06 +0000 Subject: create tremded branch --- src/cgame/CMakeLists.txt | 83 ++ src/cgame/binaryshader.h | 42 + src/cgame/cg_animation.c | 13 +- src/cgame/cg_animmapobj.c | 15 +- src/cgame/cg_attachment.c | 9 +- src/cgame/cg_buildable.c | 410 +++--- src/cgame/cg_consolecmds.c | 293 +++-- src/cgame/cg_draw.c | 2981 +++++++++++++++++++++++------------------- src/cgame/cg_drawtools.c | 107 +- src/cgame/cg_ents.c | 226 +++- src/cgame/cg_event.c | 474 ++++--- src/cgame/cg_local.h | 463 ++++--- src/cgame/cg_main.c | 837 ++++++------ src/cgame/cg_marks.c | 11 +- src/cgame/cg_mem.c | 202 --- src/cgame/cg_particles.c | 737 +++++------ src/cgame/cg_players.c | 416 ++---- src/cgame/cg_playerstate.c | 34 +- src/cgame/cg_predict.c | 51 +- src/cgame/cg_ptr.c | 81 -- src/cgame/cg_public.h | 413 +++--- src/cgame/cg_rangemarker.c | 399 ++++++ src/cgame/cg_scanner.c | 165 ++- src/cgame/cg_servercmds.c | 1235 ++++++++++------- src/cgame/cg_snapshot.c | 20 +- src/cgame/cg_syscalls.asm | 4 +- src/cgame/cg_syscalls.c | 54 +- src/cgame/cg_syscalls_11.asm | 3 +- src/cgame/cg_trails.c | 61 +- src/cgame/cg_tutorial.c | 280 ++-- src/cgame/cg_view.c | 563 +++++--- src/cgame/cg_weapons.c | 657 +++++++--- 32 files changed, 6596 insertions(+), 4743 deletions(-) create mode 100644 src/cgame/CMakeLists.txt create mode 100644 src/cgame/binaryshader.h delete mode 100644 src/cgame/cg_mem.c delete mode 100644 src/cgame/cg_ptr.c create mode 100644 src/cgame/cg_rangemarker.c (limited to 'src/cgame') diff --git a/src/cgame/CMakeLists.txt b/src/cgame/CMakeLists.txt new file mode 100644 index 0000000..026e6eb --- /dev/null +++ b/src/cgame/CMakeLists.txt @@ -0,0 +1,83 @@ +# +## ____ _ +## / ___|__ _ __ _ _ __ ___ ___ ___ ___ __| | ___ +##| | / _` |/ _` | '_ ` _ \ / _ \ / __/ _ \ / _` |/ _ \ +##| |__| (_| | (_| | | | | | | __/ | (_| (_) | (_| | __/ +## \____\__, |\__,_|_| |_| |_|\___| \___\___/ \__,_|\___| +## |___/ +# + +set(CMAKE_INSTALL_NAME_DIR ${PROJECT_BINARY_DIR}/gpp) + +set(BG_SOURCE_DIR ../game) +set(QC_SOURCE_DIR ../qcommon) +set(UI_SOURCE_DIR ../ui) +set(RC_SOURCE_DIR ../renderercommon) + +add_definitions( -DCGAME ) + +set( CGAME_SOURCES + cg_main.c # Must be listed first + cg_public.h + cg_local.h + binaryshader.h + ${BG_SOURCE_DIR}/bg_lib.h + ${BG_SOURCE_DIR}/bg_public.h + ${BG_SOURCE_DIR}/bg_alloc.c + ${BG_SOURCE_DIR}/bg_lib.c + ${BG_SOURCE_DIR}/bg_misc.c + ${BG_SOURCE_DIR}/bg_voice.c + ${BG_SOURCE_DIR}/bg_pmove.c + ${BG_SOURCE_DIR}/bg_slidemove.c + cg_animation.c + cg_animmapobj.c + cg_attachment.c + cg_buildable.c + cg_consolecmds.c + cg_draw.c + cg_drawtools.c + cg_ents.c + cg_event.c + cg_marks.c + cg_particles.c + cg_players.c + cg_playerstate.c + cg_predict.c + cg_rangemarker.c + cg_scanner.c + cg_servercmds.c + cg_snapshot.c + cg_trails.c + cg_tutorial.c + cg_view.c + cg_weapons.c + # + ${UI_SOURCE_DIR}/ui_shared.h + ${UI_SOURCE_DIR}/ui_shared.c + # + ${QC_SOURCE_DIR}/q_shared.h + ${QC_SOURCE_DIR}/q_shared.c + ${QC_SOURCE_DIR}/q_math.c + # + ${RC_SOURCE_DIR}/tr_types.h + ) + +add_library( cgame SHARED ${CGAME_SOURCES} cg_syscalls.c ) + +target_include_directories( + cgame PUBLIC + ${BG_SOURCE_DIR} + ${QC_SOURCE_DIR} + ${RC_SOURCE_DIR} + ${UI_SOURCE_DIR} + ) + +include( ${CMAKE_SOURCE_DIR}/cmake/AddQVM.cmake ) +add_qvm( cgame ${CGAME_SOURCES} cg_syscalls.asm ) + + +add_custom_command( + TARGET cgame POST_BUILD + COMMAND ${CMAKE_COMMAND} + ARGS -E copy ${CMAKE_CURRENT_BINARY_DIR}/libcgame${CMAKE_SHARED_LIBRARY_SUFFIX} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/gpp/cgame${CMAKE_SHARED_LIBRARY_SUFFIX} + ) diff --git a/src/cgame/binaryshader.h b/src/cgame/binaryshader.h new file mode 100644 index 0000000..371f758 --- /dev/null +++ b/src/cgame/binaryshader.h @@ -0,0 +1,42 @@ +#ifndef BINARY_SHADER_H +#define BINARY_SHADER_H 1 + +typedef struct +{ + byte color[ 3 ]; + qboolean drawIntersection; + qboolean drawFrontline; +} cgBinaryShaderSetting_t; + +#define NUM_BINARY_SHADERS 256 + +typedef struct +{ + qhandle_t f1; + qhandle_t f2; + qhandle_t f3; + qhandle_t b1; + qhandle_t b2; + qhandle_t b3; +} cgMediaBinaryShader_t; + +typedef enum +{ + SHC_DARK_BLUE, + SHC_LIGHT_BLUE, + SHC_GREEN_CYAN, + SHC_VIOLET, + SHC_YELLOW, + SHC_ORANGE, + SHC_LIGHT_GREEN, + SHC_DARK_GREEN, + SHC_RED, + SHC_PINK, + SHC_GREY, + SHC_NUM_SHADER_COLORS +} shaderColorEnum_t; + +extern const vec3_t cg_shaderColors[ SHC_NUM_SHADER_COLORS ]; + + +#endif diff --git a/src/cgame/cg_animation.c b/src/cgame/cg_animation.c index c370c53..78311b9 100644 --- a/src/cgame/cg_animation.c +++ b/src/cgame/cg_animation.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -31,7 +32,7 @@ Sets cg.snap, cg.oldFrame, and cg.backlerp cg.time should be between oldFrameTime and frameTime after exit =============== */ -void CG_RunLerpFrame( lerpFrame_t *lf ) +void CG_RunLerpFrame( lerpFrame_t *lf, float scale ) { int f, numFrames; animation_t *anim; @@ -61,7 +62,9 @@ void CG_RunLerpFrame( lerpFrame_t *lf ) lf->frameTime = lf->oldFrameTime + anim->frameLerp; f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp; + f *= scale; numFrames = anim->numFrames; + if( anim->flipflop ) numFrames *= 2; diff --git a/src/cgame/cg_animmapobj.c b/src/cgame/cg_animmapobj.c index 1225314..9edbed4 100644 --- a/src/cgame/cg_animmapobj.c +++ b/src/cgame/cg_animmapobj.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,14 +17,13 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ #include "cg_local.h" - /* =============== CG_DoorAnimation @@ -31,7 +31,7 @@ CG_DoorAnimation */ static void CG_DoorAnimation( centity_t *cent, int *old, int *now, float *backLerp ) { - CG_RunLerpFrame( ¢->lerpFrame ); + CG_RunLerpFrame( ¢->lerpFrame, 1.0f ); *old = cent->lerpFrame.oldFrame; *now = cent->lerpFrame.frame; @@ -117,7 +117,7 @@ static void CG_AMOAnimation( centity_t *cent, int *old, int *now, float *backLer cent->lerpFrame.frameTime += delta; } - CG_RunLerpFrame( ¢->lerpFrame ); + CG_RunLerpFrame( ¢->lerpFrame, 1.0f ); cent->miscTime = cg.time; } @@ -147,7 +147,6 @@ void CG_AnimMapObj( centity_t *cent ) memset( &ent, 0, sizeof( ent ) ); - VectorCopy( es->angles, cent->lerpAngles ); AnglesToAxis( cent->lerpAngles, ent.axis ); ent.hModel = cgs.gameModels[ es->modelindex ]; diff --git a/src/cgame/cg_attachment.c b/src/cgame/cg_attachment.c index 0d3c8ea..2c52c06 100644 --- a/src/cgame/cg_attachment.c +++ b/src/cgame/cg_attachment.c @@ -1,12 +1,13 @@ /* =========================================================================== -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -15,8 +16,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ diff --git a/src/cgame/cg_buildable.c b/src/cgame/cg_buildable.c index e678ebd..7d7596b 100644 --- a/src/cgame/cg_buildable.c +++ b/src/cgame/cg_buildable.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,12 +17,11 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ - #include "cg_local.h" char *cg_buildableSoundNames[ MAX_BUILDABLE_ANIMATIONS ] = @@ -95,6 +95,7 @@ void CG_HumanBuildableExplosion( vec3_t origin, vec3_t dir ) #define CREEP_SIZE 64.0f +#define CREEP_DISTANCE 64.0f /* ================== @@ -107,7 +108,7 @@ static void CG_Creep( centity_t *cent ) float size, frac; trace_t tr; vec3_t temp, origin; - int scaleUpTime = BG_FindBuildTimeForBuildable( cent->currentState.modelindex ); + int scaleUpTime = BG_Buildable( cent->currentState.modelindex )->buildTime; int time; time = cent->currentState.time; @@ -121,7 +122,7 @@ static void CG_Creep( centity_t *cent ) else frac = 1.0f; } - else + else if( time < 0 ) { msec = cg.time + time; if( msec >= 0 && msec < CREEP_SCALEDOWN_TIME ) @@ -131,7 +132,7 @@ static void CG_Creep( centity_t *cent ) } VectorCopy( cent->currentState.origin2, temp ); - VectorScale( temp, -4096, temp ); + VectorScale( temp, -CREEP_DISTANCE, temp ); VectorAdd( temp, cent->lerpOrigin, temp ); CG_Trace( &tr, cent->lerpOrigin, NULL, NULL, temp, cent->currentState.number, MASK_PLAYERSOLID ); @@ -140,7 +141,7 @@ static void CG_Creep( centity_t *cent ) size = CREEP_SIZE * frac; - if( size > 0.0f ) + if( size > 0.0f && tr.fraction < 1.0f ) CG_ImpactMark( cgs.media.creepShader, origin, cent->currentState.origin2, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, size, qtrue ); } @@ -168,12 +169,13 @@ static qboolean CG_ParseBuildableAnimationFile( const char *filename, buildable_ // load the file len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if( len <= 0 ) + if( len < 0 ) return qfalse; - if( len >= sizeof( text ) - 1 ) + if( len == 0 || len >= sizeof( text ) - 1 ) { - CG_Printf( "File %s too long\n", filename ); + trap_FS_FCloseFile( f ); + CG_Printf( "File %s is %s\n", filename, len == 0 ? "empty" : "too long" ); return qfalse; } @@ -258,12 +260,13 @@ static qboolean CG_ParseBuildableSoundFile( const char *filename, buildable_t bu // load the file len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if ( len <= 0 ) + if ( len < 0 ) return qfalse; - if ( len >= sizeof( text ) - 1 ) + if ( len == 0 || len >= sizeof( text ) - 1 ) { - CG_Printf( "File %s too long\n", filename ); + trap_FS_FCloseFile( f ); + CG_Printf( "File %s is %s\n", filename, len == 0 ? "empty" : "too long" ); return qfalse; } @@ -335,7 +338,7 @@ void CG_InitBuildables( void ) for( i = BA_NONE + 1; i < BA_NUM_BUILDABLES; i++ ) { - buildableName = BG_FindNameForBuildable( i ); + buildableName = BG_Buildable( i )->name; //animation.cfg Com_sprintf( filename, sizeof( filename ), "models/buildables/%s/animation.cfg", buildableName ); @@ -350,7 +353,8 @@ void CG_InitBuildables( void ) //models for( j = 0; j <= 3; j++ ) { - if( ( modelFile = BG_FindModelsForBuildable( i, j ) ) ) + modelFile = BG_BuildableConfig( i )->models[ j ]; + if( strlen( modelFile ) > 0 ) cg_buildables[ i ].models[ j ] = trap_R_RegisterModel( modelFile ); } @@ -372,7 +376,7 @@ void CG_InitBuildables( void ) else { //file doesn't exist - use default - if( BG_FindTeamForBuildable( i ) == BIT_ALIENS ) + if( BG_Buildable( i )->team == TEAM_ALIENS ) cg_buildables[ i ].sounds[ j ].sound = defaultAlienSounds[ j ]; else cg_buildables[ i ].sounds[ j ].sound = defaultHumanSounds[ j ]; @@ -426,24 +430,15 @@ cg.time should be between oldFrameTime and frameTime after exit */ static void CG_RunBuildableLerpFrame( centity_t *cent ) { - int f, numFrames; buildable_t buildable = cent->currentState.modelindex; lerpFrame_t *lf = ¢->lerpFrame; - animation_t *anim; buildableAnimNumber_t newAnimation = cent->buildableAnim & ~( ANIM_TOGGLEBIT|ANIM_FORCEBIT ); - // debugging tool to get no animations - if( cg_animSpeed.integer == 0 ) - { - lf->oldFrame = lf->frame = lf->backlerp = 0; - return; - } - // see if the animation sequence is switching if( newAnimation != lf->animationNumber || !lf->animation ) { if( cg_debugRandom.integer ) - CG_Printf( "newAnimation: %d lf->animationNumber: %d lf->animation: %d\n", + CG_Printf( "newAnimation: %d lf->animationNumber: %d lf->animation: 0x%p\n", newAnimation, lf->animationNumber, lf->animation ); CG_SetBuildableLerpFrameAnimation( buildable, lf, newAnimation ); @@ -453,7 +448,7 @@ static void CG_RunBuildableLerpFrame( centity_t *cent ) { if( cg_debugRandom.integer ) CG_Printf( "Sound for animation %d for a %s\n", - newAnimation, BG_FindHumanNameForBuildable( buildable ) ); + newAnimation, BG_Buildable( buildable )->humanName ); trap_S_StartSound( cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, cg_buildables[ buildable ].sounds[ newAnimation ].sound ); @@ -465,72 +460,14 @@ static void CG_RunBuildableLerpFrame( centity_t *cent ) trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cg_buildables[ buildable ].sounds[ lf->animationNumber ].sound ); - // if we have passed the current frame, move it to - // oldFrame and calculate a new frame - if( cg.time >= lf->frameTime ) - { - lf->oldFrame = lf->frame; - lf->oldFrameTime = lf->frameTime; + CG_RunLerpFrame( lf, 1.0f ); - // get the next frame based on the animation - anim = lf->animation; - if( !anim->frameLerp ) - return; // shouldn't happen - - if ( cg.time < lf->animationTime ) - lf->frameTime = lf->animationTime; // initial lerp - else - lf->frameTime = lf->oldFrameTime + anim->frameLerp; - - f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp; - numFrames = anim->numFrames; - if(anim->flipflop) - numFrames *= 2; - - if( f >= numFrames ) - { - f -= numFrames; - if( anim->loopFrames ) - { - f %= anim->loopFrames; - f += anim->numFrames - anim->loopFrames; - } - else - { - f = numFrames - 1; - // the animation is stuck at the end, so it - // can immediately transition to another sequence - lf->frameTime = cg.time; - cent->buildableAnim = cent->currentState.torsoAnim; - } - } - - if( anim->reversed ) - lf->frame = anim->firstFrame + anim->numFrames - 1 - f; - else if( anim->flipflop && f >= anim->numFrames ) - lf->frame = anim->firstFrame + anim->numFrames - 1 - ( f % anim->numFrames ); - else - lf->frame = anim->firstFrame + f; - - if( cg.time > lf->frameTime ) - { - lf->frameTime = cg.time; - if( cg_debugAnim.integer ) - CG_Printf( "Clamp lf->frameTime\n"); - } + // animation ended + if( lf->frameTime == cg.time ) + { + cent->buildableAnim = cent->currentState.torsoAnim; + cent->buildableIdleAnim = qtrue; } - - if( lf->frameTime > cg.time + 200 ) - lf->frameTime = cg.time; - - if( lf->oldFrameTime > cg.time ) - lf->oldFrameTime = cg.time; - - // calculate current lerp value - if( lf->frameTime == lf->oldFrameTime ) - lf->backlerp = 0; - else - lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime ); } /* @@ -544,10 +481,13 @@ static void CG_BuildableAnimation( centity_t *cent, int *old, int *now, float *b //if no animation is set default to idle anim if( cent->buildableAnim == BANIM_NONE ) + { cent->buildableAnim = es->torsoAnim; + cent->buildableIdleAnim = qtrue; + } //display the first frame of the construction anim if not yet spawned - if( !( es->generic1 & B_SPAWNED_TOGGLEBIT ) ) + if( !( es->eFlags & EF_B_SPAWNED ) ) { animation_t *anim = &cg_buildables[ es->modelindex ].animations[ BANIM_CONSTRUCT1 ]; @@ -572,12 +512,23 @@ static void CG_BuildableAnimation( centity_t *cent, int *old, int *now, float *b CG_Printf( "%d->%d l:%d t:%d %s(%d)\n", cent->oldBuildableAnim, cent->buildableAnim, es->legsAnim, es->torsoAnim, - BG_FindHumanNameForBuildable( es->modelindex ), es->number ); + BG_Buildable( es->modelindex )->humanName, es->number ); if( cent->buildableAnim == es->torsoAnim || es->legsAnim & ANIM_FORCEBIT ) + { cent->buildableAnim = cent->oldBuildableAnim = es->legsAnim; + cent->buildableIdleAnim = qfalse; + } else + { cent->buildableAnim = cent->oldBuildableAnim = es->torsoAnim; + cent->buildableIdleAnim = qtrue; + } + } + else if( cent->buildableIdleAnim == qtrue && + cent->buildableAnim != es->torsoAnim ) + { + cent->buildableAnim = es->torsoAnim; } CG_RunBuildableLerpFrame( cent ); @@ -588,7 +539,7 @@ static void CG_BuildableAnimation( centity_t *cent, int *old, int *now, float *b } } -#define TRACE_DEPTH 64.0f +#define TRACE_DEPTH 15.0f /* =============== @@ -600,8 +551,9 @@ static void CG_PositionAndOrientateBuildable( const vec3_t angles, const vec3_t const vec3_t mins, const vec3_t maxs, vec3_t outAxis[ 3 ], vec3_t outOrigin ) { - vec3_t forward, start, end; + vec3_t forward, end; trace_t tr; + float fraction; AngleVectors( angles, forward, NULL, NULL ); VectorCopy( normal, outAxis[ 2 ] ); @@ -620,16 +572,21 @@ static void CG_PositionAndOrientateBuildable( const vec3_t angles, const vec3_t outAxis[ 1 ][ 2 ] = -outAxis[ 1 ][ 2 ]; VectorMA( inOrigin, -TRACE_DEPTH, normal, end ); - VectorMA( inOrigin, 1.0f, normal, start ); - CG_CapTrace( &tr, start, mins, maxs, end, skipNumber, MASK_PLAYERSOLID ); - if( tr.fraction == 1.0f ) + CG_CapTrace( &tr, inOrigin, mins, maxs, end, skipNumber, + CONTENTS_SOLID | CONTENTS_PLAYERCLIP ); + + fraction = tr.fraction; + if( tr.startsolid ) + fraction = 0; + else if( tr.fraction == 1.0f ) { - //erm we missed completely - try again with a box trace - CG_Trace( &tr, start, mins, maxs, end, skipNumber, MASK_PLAYERSOLID ); + // this is either too far off of the bbox to be useful for gameplay purposes + // or the model is positioned in thin air anyways. + fraction = 0; } - VectorMA( inOrigin, tr.fraction * -TRACE_DEPTH, normal, outOrigin ); + VectorMA( inOrigin, fraction * -TRACE_DEPTH, normal, outOrigin ); } /* @@ -650,15 +607,18 @@ void CG_GhostBuildable( buildable_t buildable ) memset( &ent, 0, sizeof( ent ) ); - BG_FindBBoxForBuildable( buildable, mins, maxs ); + BG_BuildableBoundingBox( buildable, mins, maxs ); BG_PositionBuildableRelativeToPlayer( ps, mins, maxs, CG_Trace, entity_origin, angles, &tr ); + if( cg_rangeMarkerForBlueprint.integer && tr.entityNum != ENTITYNUM_NONE ) + CG_GhostBuildableRangeMarker( buildable, entity_origin, tr.plane.normal ); + CG_PositionAndOrientateBuildable( ps->viewangles, entity_origin, tr.plane.normal, ps->clientNum, mins, maxs, ent.axis, ent.origin ); //offset on the Z axis if required - VectorMA( ent.origin, BG_FindZOffsetForBuildable( buildable ), tr.plane.normal, ent.origin ); + VectorMA( ent.origin, BG_BuildableConfig( buildable )->zOffset, tr.plane.normal, ent.origin ); VectorCopy( ent.origin, ent.lightingOrigin ); VectorCopy( ent.origin, ent.oldorigin ); // don't positionally lerp at all @@ -671,7 +631,7 @@ void CG_GhostBuildable( buildable_t buildable ) ent.customShader = cgs.media.redBuildShader; //rescale the model - scale = BG_FindModelScaleForBuildable( buildable ); + scale = BG_BuildableConfig( buildable )->modelScale; if( scale != 1.0f ) { @@ -696,14 +656,14 @@ CG_BuildableParticleEffects static void CG_BuildableParticleEffects( centity_t *cent ) { entityState_t *es = ¢->currentState; - buildableTeam_t team = BG_FindTeamForBuildable( es->modelindex ); - int health = es->generic1 & B_HEALTH_MASK; - float healthFrac = (float)health / B_HEALTH_MASK; + team_t team = BG_Buildable( es->modelindex )->team; + int health = es->misc; + float healthFrac = (float)health / BG_Buildable( es->modelindex )->health; - if( !( es->generic1 & B_SPAWNED_TOGGLEBIT ) ) + if( !( es->eFlags & EF_B_SPAWNED ) ) return; - if( team == BIT_HUMANS ) + if( team == TEAM_HUMANS ) { if( healthFrac < 0.33f && !CG_IsParticleSystemValid( ¢->buildablePS ) ) { @@ -718,7 +678,7 @@ static void CG_BuildableParticleEffects( centity_t *cent ) else if( healthFrac >= 0.33f && CG_IsParticleSystemValid( ¢->buildablePS ) ) CG_DestroyParticleSystem( ¢->buildablePS ); } - else if( team == BIT_ALIENS ) + else if( team == TEAM_ALIENS ) { if( healthFrac < 0.33f && !CG_IsParticleSystemValid( ¢->buildablePS ) ) { @@ -736,7 +696,11 @@ static void CG_BuildableParticleEffects( centity_t *cent ) } } - +/* +================== +CG_BuildableStatusParse +================== +*/ void CG_BuildableStatusParse( const char *filename, buildStat_t *bs ) { pc_token_t token; @@ -866,10 +830,12 @@ void CG_BuildableStatusParse( const char *filename, buildStat_t *bs ) Com_Printf("CG_BuildableStatusParse: unknown token %s in %s\n", token.string, filename ); bs->loaded = qfalse; + trap_Parse_FreeSource( handle ); return; } } bs->loaded = qtrue; + trap_Parse_FreeSource( handle ); } #define STATUS_FADE_TIME 200 @@ -899,23 +865,30 @@ static void CG_BuildableStatusDisplay( centity_t *cent ) qboolean visible = qfalse; vec3_t mins, maxs; entityState_t *hit; + int anim; - if( BG_FindTeamForBuildable( es->modelindex ) == BIT_ALIENS ) + if( BG_Buildable( es->modelindex )->team == TEAM_ALIENS ) bs = &cgs.alienBuildStat; else bs = &cgs.humanBuildStat; if( !bs->loaded ) return; - + d = Distance( cent->lerpOrigin, cg.refdef.vieworg ); if( d > STATUS_MAX_VIEW_DIST ) return; - + Vector4Copy( bs->foreColor, color ); - // trace for center point - BG_FindBBoxForBuildable( es->modelindex, mins, maxs ); + // trace for center point + BG_BuildableBoundingBox( es->modelindex, mins, maxs ); + + // hack for shrunken barricades + anim = es->torsoAnim & ~( ANIM_FORCEBIT | ANIM_TOGGLEBIT ); + if( es->modelindex == BA_A_BARRICADE && + ( anim == BANIM_DESTROYED || !( es->eFlags & EF_B_SPAWNED ) ) ) + maxs[ 2 ] = (int)( maxs[ 2 ] * BARRICADE_SHRINKPROP ); VectorCopy( cent->lerpOrigin, origin ); @@ -960,8 +933,8 @@ static void CG_BuildableStatusDisplay( centity_t *cent ) hit = &cg_entities[ tr.entityNum ].currentState; if( tr.entityNum < MAX_CLIENTS || ( hit->eType == ET_BUILDABLE && - ( !( es->generic1 & B_SPAWNED_TOGGLEBIT ) || - BG_FindTransparentTestForBuildable( hit->modelindex ) ) ) ) + ( !( es->eFlags & EF_B_SPAWNED ) || + BG_Buildable( hit->modelindex )->transparentTest ) ) ) { entNum = tr.entityNum; VectorCopy( tr.endpos, trOrigin ); @@ -972,7 +945,7 @@ static void CG_BuildableStatusDisplay( centity_t *cent ) } // hack to make the kit obscure view if( cg_drawGun.integer && visible && - cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_HUMANS && + cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_HUMANS && CG_WorldToScreen( origin, &x, &y ) ) { if( x > 450 && y > 290 ) @@ -1006,8 +979,8 @@ static void CG_BuildableStatusDisplay( centity_t *cent ) return; } - health = es->generic1 & B_HEALTH_MASK; - healthScale = (float)health / B_HEALTH_MASK; + health = es->misc; + healthScale = (float)health / BG_Buildable( es->modelindex )->health; if( health > 0 && healthScale < 0.01f ) healthScale = 0.01f; @@ -1024,13 +997,14 @@ static void CG_BuildableStatusDisplay( centity_t *cent ) float picY = y; float scale; float subH, subY; + float clipX, clipY, clipW, clipH; vec4_t frameColor; // this is fudged to get the width/height in the cfg to be more realistic scale = ( picH / d ) * 3; - powered = es->generic1 & B_POWERED_TOGGLEBIT; - marked = es->generic1 & B_MARKED_TOGGLEBIT; + powered = es->eFlags & EF_B_POWERED; + marked = es->eFlags & EF_B_MARKED; picH *= scale; picW *= scale; @@ -1041,6 +1015,12 @@ static void CG_BuildableStatusDisplay( centity_t *cent ) subH = picH - ( picH * bs->verticalMargin ); subY = picY + ( picH * 0.5f ) - ( subH * 0.5f ); + clipW = ( 640.0f * cg_viewsize.integer ) / 100.0f; + clipH = ( 480.0f * cg_viewsize.integer ) / 100.0f; + clipX = 320.0f - ( clipW * 0.5f ); + clipY = 240.0f - ( clipH * 0.5f ); + CG_SetClipRegion( clipX, clipY, clipW, clipH ); + if( bs->frameShader ) { Vector4Copy( bs->backColor, frameColor ); @@ -1073,7 +1053,7 @@ static void CG_BuildableStatusDisplay( centity_t *cent ) healthColor[ 3 ] = color[ 3 ]; trap_R_SetColor( healthColor ); - + CG_DrawPic( hX, hY, hW, hH, cgs.media.whiteShader ); trap_R_SetColor( NULL ); } @@ -1089,7 +1069,7 @@ static void CG_BuildableStatusDisplay( centity_t *cent ) oW *= scale; oX -= ( oW * 0.5f ); oY -= ( oH * 0.5f ); - + trap_R_SetColor( frameColor ); CG_DrawPic( oX, oY, oW, oH, bs->overlayShader ); trap_R_SetColor( NULL ); @@ -1117,12 +1097,12 @@ static void CG_BuildableStatusDisplay( centity_t *cent ) int healthMax; int healthPoints; - healthMax = BG_FindHealthForBuildable( es->modelindex ); + healthMax = BG_Buildable( es->modelindex )->health; healthPoints = (int)( healthScale * healthMax ); if( health > 0 && healthPoints < 1 ) healthPoints = 1; - nX = picX + ( picW * 0.5f ) - 2.0f - ( ( subH * 4 ) * 0.5f ); - + nX = picX + ( picW * 0.5f ) - 2.0f - ( ( subH * 4 ) * 0.5f ); + if( healthPoints > 999 ) nX -= 0.0f; else if( healthPoints > 99 ) @@ -1131,14 +1111,21 @@ static void CG_BuildableStatusDisplay( centity_t *cent ) nX -= subH * 1.0f; else nX -= subH * 1.5f; - + CG_DrawField( nX, subY, 4, subH, subH, healthPoints ); } + trap_R_SetColor( NULL ); + CG_ClearClipRegion( ); } } -static int QDECL CG_SortDistance( const void *a, const void *b ) +/* +================== +CG_SortDistance +================== +*/ +static int CG_SortDistance( const void *a, const void *b ) { centity_t *aent, *bent; float adist, bdist; @@ -1155,6 +1142,48 @@ static int QDECL CG_SortDistance( const void *a, const void *b ) return 0; } +/* +================== +CG_PlayerIsBuilder +================== +*/ +static qboolean CG_PlayerIsBuilder( buildable_t buildable ) +{ + switch( cg.predictedPlayerState.weapon ) + { + case WP_ABUILD: + case WP_ABUILD2: + case WP_HBUILD: + return BG_Buildable( buildable )->team == + BG_Weapon( cg.predictedPlayerState.weapon )->team; + + default: + return qfalse; + } +} + +/* +================== +CG_BuildableRemovalPending +================== +*/ +static qboolean CG_BuildableRemovalPending( int entityNum ) +{ + int i; + playerState_t *ps = &cg.snap->ps; + + if( !( ps->stats[ STAT_BUILDABLE ] & SB_VALID_TOGGLEBIT ) ) + return qfalse; + + for( i = 0; i < MAX_MISC; i++ ) + { + if( ps->misc[ i ] == entityNum ) + return qtrue; + } + + return qfalse; +} + /* ================== CG_DrawBuildableStatus @@ -1168,30 +1197,18 @@ void CG_DrawBuildableStatus( void ) int buildableList[ MAX_ENTITIES_IN_SNAPSHOT ]; int buildables = 0; - switch( cg.predictedPlayerState.weapon ) + for( i = 0; i < cg.snap->numEntities; i++ ) { - case WP_ABUILD: - case WP_ABUILD2: - case WP_HBUILD: - case WP_HBUILD2: - for( i = 0; i < cg.snap->numEntities; i++ ) - { - cent = &cg_entities[ cg.snap->entities[ i ].number ]; - es = ¢->currentState; - - if( es->eType == ET_BUILDABLE && - BG_FindTeamForBuildable( es->modelindex ) == - BG_FindTeamForWeapon( cg.predictedPlayerState.weapon ) ) - buildableList[ buildables++ ] = cg.snap->entities[ i ].number; - } - qsort( buildableList, buildables, sizeof( int ), CG_SortDistance ); - for( i = 0; i < buildables; i++ ) - CG_BuildableStatusDisplay( &cg_entities[ buildableList[ i ] ] ); - break; + cent = &cg_entities[ cg.snap->entities[ i ].number ]; + es = ¢->currentState; - default: - break; + if( es->eType == ET_BUILDABLE && CG_PlayerIsBuilder( es->modelindex ) ) + buildableList[ buildables++ ] = cg.snap->entities[ i ].number; } + + qsort( buildableList, buildables, sizeof( int ), CG_SortDistance ); + for( i = 0; i < buildables; i++ ) + CG_BuildableStatusDisplay( &cg_entities[ buildableList[ i ] ] ); } #define BUILDABLE_SOUND_PERIOD 500 @@ -1205,17 +1222,15 @@ void CG_Buildable( centity_t *cent ) { refEntity_t ent; entityState_t *es = ¢->currentState; - vec3_t angles; vec3_t surfNormal, xNormal, mins, maxs; vec3_t refNormal = { 0.0f, 0.0f, 1.0f }; float rotAngle; - buildableTeam_t team = BG_FindTeamForBuildable( es->modelindex ); + team_t team = BG_Buildable( es->modelindex )->team; float scale; int health; - float healthScale; //must be before EF_NODRAW check - if( team == BIT_ALIENS ) + if( team == TEAM_ALIENS ) CG_Creep( cent ); // if set to invisible, skip @@ -1229,37 +1244,63 @@ void CG_Buildable( centity_t *cent ) memset ( &ent, 0, sizeof( ent ) ); - VectorCopy( cent->lerpOrigin, ent.origin ); - VectorCopy( cent->lerpOrigin, ent.oldorigin ); - VectorCopy( cent->lerpOrigin, ent.lightingOrigin ); - VectorCopy( es->origin2, surfNormal ); - VectorCopy( es->angles, angles ); - BG_FindBBoxForBuildable( es->modelindex, mins, maxs ); + BG_BuildableBoundingBox( es->modelindex, mins, maxs ); if( es->pos.trType == TR_STATIONARY ) - CG_PositionAndOrientateBuildable( angles, ent.origin, surfNormal, es->number, - mins, maxs, ent.axis, ent.origin ); + { + // seeing as buildables rarely move, we cache the results and recalculate + // only if the buildable moves or changes orientation + if( VectorCompare( cent->buildableCache.cachedOrigin, cent->lerpOrigin ) && + VectorCompare( cent->buildableCache.cachedAngles, cent->lerpAngles ) && + VectorCompare( cent->buildableCache.cachedNormal, surfNormal ) && + cent->buildableCache.cachedType == es->modelindex ) + { + VectorCopy( cent->buildableCache.axis[ 0 ], ent.axis[ 0 ] ); + VectorCopy( cent->buildableCache.axis[ 1 ], ent.axis[ 1 ] ); + VectorCopy( cent->buildableCache.axis[ 2 ], ent.axis[ 2 ] ); + VectorCopy( cent->buildableCache.origin, ent.origin ); + } + else + { + CG_PositionAndOrientateBuildable( cent->lerpAngles, cent->lerpOrigin, surfNormal, + es->number, mins, maxs, ent.axis, + ent.origin ); + VectorCopy( ent.axis[ 0 ], cent->buildableCache.axis[ 0 ] ); + VectorCopy( ent.axis[ 1 ], cent->buildableCache.axis[ 1 ] ); + VectorCopy( ent.axis[ 2 ], cent->buildableCache.axis[ 2 ] ); + VectorCopy( ent.origin, cent->buildableCache.origin ); + VectorCopy( cent->lerpOrigin, cent->buildableCache.cachedOrigin ); + VectorCopy( cent->lerpAngles, cent->buildableCache.cachedAngles ); + VectorCopy( surfNormal, cent->buildableCache.cachedNormal ); + cent->buildableCache.cachedType = es->modelindex; + } + } + else + { + VectorCopy( cent->lerpOrigin, ent.origin ); + AnglesToAxis( cent->lerpAngles, ent.axis ); + } //offset on the Z axis if required - VectorMA( ent.origin, BG_FindZOffsetForBuildable( es->modelindex ), surfNormal, ent.origin ); + VectorMA( ent.origin, BG_BuildableConfig( es->modelindex )->zOffset, surfNormal, ent.origin ); VectorCopy( ent.origin, ent.oldorigin ); // don't positionally lerp at all VectorCopy( ent.origin, ent.lightingOrigin ); ent.hModel = cg_buildables[ es->modelindex ].models[ 0 ]; - if( !( es->generic1 & B_SPAWNED_TOGGLEBIT ) ) + if( !( es->eFlags & EF_B_SPAWNED ) ) { sfxHandle_t prebuildSound = cgs.media.humanBuildablePrebuild; - if( team == BIT_HUMANS ) + if( team == TEAM_HUMANS ) { ent.customShader = cgs.media.humanSpawningShader; prebuildSound = cgs.media.humanBuildablePrebuild; } - else if( team == BIT_ALIENS ) + else if( team == TEAM_ALIENS ) prebuildSound = cgs.media.alienBuildablePrebuild; trap_S_AddLoopingSound( es->number, cent->lerpOrigin, vec3_origin, prebuildSound ); @@ -1268,7 +1309,7 @@ void CG_Buildable( centity_t *cent ) CG_BuildableAnimation( cent, &ent.oldframe, &ent.frame, &ent.backlerp ); //rescale the model - scale = BG_FindModelScaleForBuildable( es->modelindex ); + scale = BG_BuildableConfig( es->modelindex )->modelScale; if( scale != 1.0f ) { @@ -1281,6 +1322,8 @@ void CG_Buildable( centity_t *cent ) else ent.nonNormalizedAxes = qfalse; + if( CG_PlayerIsBuilder( es->modelindex ) && CG_BuildableRemovalPending( es->number ) ) + ent.customShader = cgs.media.redBuildShader; //add to refresh list trap_R_AddRefEntityToScene( &ent ); @@ -1324,6 +1367,9 @@ void CG_Buildable( centity_t *cent ) else turretBarrel.nonNormalizedAxes = qfalse; + if( CG_PlayerIsBuilder( es->modelindex ) && CG_BuildableRemovalPending( es->number ) ) + turretBarrel.customShader = cgs.media.redBuildShader; + trap_R_AddRefEntityToScene( &turretBarrel ); } @@ -1366,6 +1412,9 @@ void CG_Buildable( centity_t *cent ) else turretTop.nonNormalizedAxes = qfalse; + if( CG_PlayerIsBuilder( es->modelindex ) && CG_BuildableRemovalPending( es->number ) ) + turretTop.customShader = cgs.media.redBuildShader; + trap_R_AddRefEntityToScene( &turretTop ); } @@ -1375,7 +1424,7 @@ void CG_Buildable( centity_t *cent ) weaponInfo_t *weapon = &cg_weapons[ es->weapon ]; if( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME || - BG_FindProjTypeForBuildable( es->modelindex ) == WP_TESLAGEN ) + BG_Buildable( es->modelindex )->turretProjType == WP_TESLAGEN ) { if( weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 0 ] || weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 1 ] || @@ -1397,27 +1446,34 @@ void CG_Buildable( centity_t *cent ) trap_S_AddLoopingSound( es->number, cent->lerpOrigin, vec3_origin, weapon->readySound ); } - health = es->generic1 & B_HEALTH_MASK; - healthScale = (float)health / B_HEALTH_MASK; + health = es->misc; - if( healthScale < cent->lastBuildableHealthScale && ( es->generic1 & B_SPAWNED_TOGGLEBIT ) ) + if( health < cent->lastBuildableHealth && + ( es->eFlags & EF_B_SPAWNED ) ) { if( cent->lastBuildableDamageSoundTime + BUILDABLE_SOUND_PERIOD < cg.time ) { - if( team == BIT_HUMANS ) + if( team == TEAM_HUMANS ) { int i = rand( ) % 4; trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.humanBuildableDamage[ i ] ); } - else if( team == BIT_ALIENS ) + else if( team == TEAM_ALIENS ) trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.alienBuildableDamage ); cent->lastBuildableDamageSoundTime = cg.time; } } - cent->lastBuildableHealthScale = healthScale; + cent->lastBuildableHealth = health; //smoke etc for damaged buildables CG_BuildableParticleEffects( cent ); + + if ( cg_rangeMarkerForBlueprint.integer ) + { + // only light up the powered buildables. + if ( es->eFlags & EF_B_POWERED ) + CG_GhostBuildableRangeMarker( es->modelindex, ent.origin, surfNormal ); + } } diff --git a/src/cgame/cg_consolecmds.c b/src/cgame/cg_consolecmds.c index 68aab6c..90e2e2d 100644 --- a/src/cgame/cg_consolecmds.c +++ b/src/cgame/cg_consolecmds.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,34 +17,16 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_consolecmds.c -- text commands typed in at the local console, or // executed by a key binding - #include "cg_local.h" - - -void CG_TargetCommand_f( void ) -{ - int targetNum; - char test[ 4 ]; - - targetNum = CG_CrosshairPlayer( ); - if( !targetNum ) - return; - - trap_Argv( 1, test, 4 ); - trap_SendConsoleCommand( va( "gc %i %i", targetNum, atoi( test ) ) ); -} - - - /* ================= CG_SizeUp_f @@ -53,7 +36,7 @@ Keybinding command */ static void CG_SizeUp_f( void ) { - trap_Cvar_Set( "cg_viewsize", va( "%i", (int)( cg_viewsize.integer + 10 ) ) ); + trap_Cvar_Set( "cg_viewsize", va( "%i", MIN( cg_viewsize.integer + 10, 100 ) ) ); } @@ -66,7 +49,7 @@ Keybinding command */ static void CG_SizeDown_f( void ) { - trap_Cvar_Set( "cg_viewsize", va( "%i", (int)( cg_viewsize.integer - 10 ) ) ); + trap_Cvar_Set( "cg_viewsize", va( "%i", MAX( cg_viewsize.integer - 10, 30 ) ) ); } @@ -91,7 +74,6 @@ qboolean CG_RequestScores( void ) // the scores are more than two seconds out of data, // so request new ones cg.scoresRequestTime = cg.time; - //TA: added \n SendClientCommand doesn't call flush( )? trap_SendClientCommand( "score\n" ); return qtrue; @@ -127,26 +109,12 @@ static void CG_ScoresDown_f( void ) { Menu_SetFeederSelection( menuScoreboard, FEEDER_ALIENTEAM_LIST, 0, NULL ); Menu_SetFeederSelection( menuScoreboard, FEEDER_HUMANTEAM_LIST, 0, NULL ); - } - - if( CG_RequestScores( ) ) - { - // leave the current scores up if they were already - // displayed, but if this is the first hit, clear them out - if( !cg.showScores ) - { - if( cg_debugRandom.integer ) - CG_Printf( "CG_ScoresDown_f: scores out of date\n" ); - - cg.showScores = qtrue; - cg.numScores = 0; - } + cg.showScores = qtrue; } else { - // show the cached contents even if they just pressed if it - // is within two seconds - cg.showScores = qtrue; + cg.showScores = qfalse; + cg.numScores = 0; } } @@ -159,70 +127,111 @@ static void CG_ScoresUp_f( void ) } } -static void CG_TellTarget_f( void ) +void CG_ClientList_f( void ) { - int clientNum; - char command[ 128 ]; - char message[ 128 ]; + clientInfo_t *ci; + int i; + int count = 0; - clientNum = CG_CrosshairPlayer( ); - if( clientNum == -1 ) - return; + for( i = 0; i < MAX_CLIENTS; i++ ) + { + ci = &cgs.clientinfo[ i ]; + if( !ci->infoValid ) + continue; - trap_Args( message, 128 ); - Com_sprintf( command, 128, "tell %i %s", clientNum, message ); - trap_SendClientCommand( command ); + switch( ci->team ) + { + case TEAM_ALIENS: + Com_Printf( "%2d " S_COLOR_RED "A " S_COLOR_WHITE "%s\n", i, + ci->name ); + break; + + case TEAM_HUMANS: + Com_Printf( "%2d " S_COLOR_CYAN "H " S_COLOR_WHITE "%s\n", i, + ci->name ); + break; + + default: + case TEAM_NONE: + case NUM_TEAMS: + Com_Printf( "%2d S %s\n", i, ci->name ); + break; + } + + count++; + } + + Com_Printf( "Listed %2d clients\n", count ); } -static void CG_TellAttacker_f( void ) +static void CG_VoiceMenu_f( void ) { - int clientNum; - char command[ 128 ]; - char message[ 128 ]; - - clientNum = CG_LastAttacker( ); - if( clientNum == -1 ) - return; + char cmd[sizeof("voicemenu3")]; + + trap_Argv(0, cmd, sizeof(cmd)); + + switch (cmd[9]) { + default: + case '\0': + trap_Cvar_Set("ui_voicemenu", "1"); + break; + case '2': + trap_Cvar_Set("ui_voicemenu", "2"); + break; + case '3': + trap_Cvar_Set("ui_voicemenu", "3"); + break; + }; + + trap_SendConsoleCommand("menu tremulous_voicecmd\n"); +} - trap_Args( message, 128 ); - Com_sprintf( command, 128, "tell %i %s", clientNum, message ); - trap_SendClientCommand( command ); +static void CG_UIMenu_f( void ) +{ + trap_SendConsoleCommand( va( "menu \"%s\"\n", CG_Argv( 1 ) ) ); } -typedef struct +static void CG_KillMessage_f( void ) { - char *cmd; - void (*function)( void ); -} consoleCommand_t; + char msg1[ 33 * 3 + 1]; + char msg2[ 33 * 3 + 1 ]; + trap_Argv( 1, msg1, sizeof(msg1) ); + trap_Argv( 2, msg2, sizeof(msg2) ); + CG_AddToKillMsg(msg1, msg2, WP_GRENADE); +} static consoleCommand_t commands[ ] = { - { "testgun", CG_TestGun_f }, - { "testmodel", CG_TestModel_f }, + { "+scores", CG_ScoresDown_f }, + { "-scores", CG_ScoresUp_f }, + { "cgame_memory", BG_MemoryInfo }, + { "clientlist", CG_ClientList_f }, + { "destroyTestPS", CG_DestroyTestPS_f }, + { "destroyTestTS", CG_DestroyTestTS_f }, { "nextframe", CG_TestModelNextFrame_f }, - { "prevframe", CG_TestModelPrevFrame_f }, { "nextskin", CG_TestModelNextSkin_f }, + { "prevframe", CG_TestModelPrevFrame_f }, { "prevskin", CG_TestModelPrevSkin_f }, - { "viewpos", CG_Viewpos_f }, - { "+scores", CG_ScoresDown_f }, - { "-scores", CG_ScoresUp_f }, - { "scoresUp", CG_scrollScoresUp_f }, { "scoresDown", CG_scrollScoresDown_f }, - { "sizeup", CG_SizeUp_f }, + { "scoresUp", CG_scrollScoresUp_f }, { "sizedown", CG_SizeDown_f }, - { "weapnext", CG_NextWeapon_f }, - { "weapprev", CG_PrevWeapon_f }, - { "weapon", CG_Weapon_f }, - { "tell_target", CG_TellTarget_f }, - { "tell_attacker", CG_TellAttacker_f }, - { "tcmd", CG_TargetCommand_f }, + { "sizeup", CG_SizeUp_f }, + { "testgun", CG_TestGun_f }, + { "testmodel", CG_TestModel_f }, { "testPS", CG_TestPS_f }, - { "destroyTestPS", CG_DestroyTestPS_f }, { "testTS", CG_TestTS_f }, - { "destroyTestTS", CG_DestroyTestTS_f }, + { "ui_menu", CG_UIMenu_f }, + { "viewpos", CG_Viewpos_f }, + { "voicemenu", CG_VoiceMenu_f }, + { "voicemenu2", CG_VoiceMenu_f }, + { "voicemenu3", CG_VoiceMenu_f }, + { "weapnext", CG_NextWeapon_f }, + { "weapon", CG_Weapon_f }, + { "weapprev", CG_PrevWeapon_f }, + { "zcp", CG_CenterPrint_f }, + { "zkill", CG_KillMessage_f } }; - /* ================= CG_ConsoleCommand @@ -233,33 +242,81 @@ Cmd_Argc() / Cmd_Argv() */ qboolean CG_ConsoleCommand( void ) { - const char *cmd; - const char *arg1; - int i; + consoleCommand_t *cmd; - cmd = CG_Argv( 0 ); + cmd = bsearch( CG_Argv( 0 ), commands, + ARRAY_LEN( commands ), sizeof( commands[ 0 ] ), + cmdcmp ); - //TA: ugly hacky special case - if( !Q_stricmp( cmd, "ui_menu" ) ) - { - arg1 = CG_Argv( 1 ); - trap_SendConsoleCommand( va( "menu %s\n", arg1 ) ); - return qtrue; - } + if( !cmd ) + return qfalse; - for( i = 0; i < sizeof( commands ) / sizeof( commands[ 0 ] ); i++ ) - { - if( !Q_stricmp( cmd, commands[ i ].cmd ) ) - { - commands[ i ].function( ); - return qtrue; - } - } + cmd->function( ); + return qtrue; +} - return qfalse; +#ifndef MODULE_INTERFACE_11 +/* +================== +CG_CompleteCallVote_f +================== +*/ +void CG_CompleteCallVote_f( int argNum ) { + switch( argNum ) { + case 2: + trap_Field_CompleteList( + "[" + "\"allowbuild\"," + "\"cancel\"," + "\"denybuild\"," + "\"draw\"," + "\"extend\"," + "\"kick\"," + "\"map\"," + "\"map_restart\"," + "\"mute\"," + "\"nextmap\"," + "\"poll\"," + "\"sudden_death\"," + "\"unmute\" ]" ); + break; + } } +static consoleCommandCompletions_t commandCompletions[ ] = +{ + { "callvote", CG_CompleteCallVote_f } +}; + +/* +================= +CG_Console_CompleteArgument + +Try to complete the client command line argument given in +argNum. Returns true if a completion function is found in CGAME, +otherwise client tries another completion method. +The string has been tokenized and can be retrieved with +Cmd_Argc() / Cmd_Argv() +================= +*/ +qboolean CG_Console_CompleteArgument( int argNum ) +{ + consoleCommandCompletions_t *cmd; + + // Skip command prefix character + cmd = bsearch( CG_Argv( 0 ) + 1, commandCompletions, + ARRAY_LEN( commandCompletions ), sizeof( commandCompletions[ 0 ] ), + cmdcmp ); + + if( !cmd ) + return qfalse; + + cmd->function( argNum ); + return qtrue; +} +#endif + /* ================= CG_InitConsoleCommands @@ -272,7 +329,7 @@ void CG_InitConsoleCommands( void ) { int i; - for( i = 0 ; i < sizeof( commands ) / sizeof( commands[ 0 ] ) ; i++ ) + for( i = 0; i < ARRAY_LEN( commands ); i++ ) trap_AddCommand( commands[ i ].cmd ); // @@ -280,31 +337,28 @@ void CG_InitConsoleCommands( void ) // forwarded to the server after they are not recognized locally // trap_AddCommand( "kill" ); + trap_AddCommand( "ui_messagemode" ); + trap_AddCommand( "ui_messagemode2" ); + trap_AddCommand( "ui_messagemode3" ); + trap_AddCommand( "ui_messagemode4" ); trap_AddCommand( "say" ); trap_AddCommand( "say_team" ); - trap_AddCommand( "tell" ); trap_AddCommand( "vsay" ); trap_AddCommand( "vsay_team" ); - trap_AddCommand( "vtell" ); - trap_AddCommand( "vtaunt" ); - trap_AddCommand( "vosay" ); - trap_AddCommand( "vosay_team" ); - trap_AddCommand( "votell" ); + trap_AddCommand( "vsay_local" ); + trap_AddCommand( "m" ); + trap_AddCommand( "mt" ); trap_AddCommand( "give" ); trap_AddCommand( "god" ); trap_AddCommand( "notarget" ); trap_AddCommand( "noclip" ); trap_AddCommand( "team" ); trap_AddCommand( "follow" ); - trap_AddCommand( "levelshot" ); - trap_AddCommand( "addbot" ); trap_AddCommand( "setviewpos" ); trap_AddCommand( "callvote" ); trap_AddCommand( "vote" ); trap_AddCommand( "callteamvote" ); trap_AddCommand( "teamvote" ); - trap_AddCommand( "stats" ); - trap_AddCommand( "teamtask" ); trap_AddCommand( "class" ); trap_AddCommand( "build" ); trap_AddCommand( "buy" ); @@ -315,11 +369,6 @@ void CG_InitConsoleCommands( void ) trap_AddCommand( "itemtoggle" ); trap_AddCommand( "destroy" ); trap_AddCommand( "deconstruct" ); - trap_AddCommand( "menu" ); - trap_AddCommand( "ui_menu" ); - trap_AddCommand( "mapRotation" ); - trap_AddCommand( "stopMapRotation" ); - trap_AddCommand( "advanceMapRotation" ); - trap_AddCommand( "alienWin" ); - trap_AddCommand( "humanWin" ); + trap_AddCommand( "ignore" ); + trap_AddCommand( "unignore" ); } diff --git a/src/cgame/cg_draw.c b/src/cgame/cg_draw.c index 1a3ecae..38c9578 100644 --- a/src/cgame/cg_draw.c +++ b/src/cgame/cg_draw.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,284 +17,84 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_draw.c -- draw all of the graphical elements during // active (after loading) gameplay - #include "cg_local.h" -#include "../ui/ui_shared.h" +#include "ui/ui_shared.h" -// used for scoreboard -extern displayContextDef_t cgDC; menuDef_t *menuScoreboard = NULL; -int drawTeamOverlayModificationCount = -1; - -int sortedTeamPlayers[ TEAM_MAXOVERLAY ]; -int numSortedTeamPlayers; - -//TA UI -int CG_Text_Width( const char *text, float scale, int limit ) +static void CG_AlignText( rectDef_t *rect, const char *text, float scale, + float w, float h, + int align, int valign, + float *x, float *y ) { - int count,len; - float out; - glyphInfo_t *glyph; - float useScale; -// FIXME: see ui_main.c, same problem -// const unsigned char *s = text; - const char *s = text; - fontInfo_t *font = &cgDC.Assets.textFont; + float tx, ty; - if( scale <= cg_smallFont.value ) - font = &cgDC.Assets.smallFont; - else if( scale > cg_bigFont.value ) - font = &cgDC.Assets.bigFont; - - useScale = scale * font->glyphScale; - out = 0; - - if( text ) + if( scale > 0.0f ) { - len = strlen( text ); - if( limit > 0 && len > limit ) - len = limit; - - count = 0; - while( s && *s && count < len ) - { - if( Q_IsColorString( s ) ) - { - s += 2; - continue; - } - else - { - glyph = &font->glyphs[ (int)*s ]; - //TTimo: FIXME: getting nasty warnings without the cast, - //hopefully this doesn't break the VM build - out += glyph->xSkip; - s++; - count++; - } - } + w = UI_Text_Width( text, scale ); + h = UI_Text_Height( text, scale ); } - return out * useScale; -} - -int CG_Text_Height( const char *text, float scale, int limit ) -{ - int len, count; - float max; - glyphInfo_t *glyph; - float useScale; -// TTimo: FIXME -// const unsigned char *s = text; - const char *s = text; - fontInfo_t *font = &cgDC.Assets.textFont; - - if( scale <= cg_smallFont.value ) - font = &cgDC.Assets.smallFont; - else if( scale > cg_bigFont.value ) - font = &cgDC.Assets.bigFont; - - useScale = scale * font->glyphScale; - max = 0; - - if( text ) + switch( align ) { - len = strlen( text ); - if( limit > 0 && len > limit ) - len = limit; - - count = 0; - while( s && *s && count < len ) - { - if( Q_IsColorString( s ) ) - { - s += 2; - continue; - } - else - { - glyph = &font->glyphs[ (int)*s ]; - //TTimo: FIXME: getting nasty warnings without the cast, - //hopefully this doesn't break the VM build - if( max < glyph->height ) - max = glyph->height; - - s++; - count++; - } - } - } - - return max * useScale; -} + default: + case ALIGN_LEFT: + tx = 0.0f; + break; -void CG_Text_PaintChar( float x, float y, float width, float height, float scale, - float s, float t, float s2, float t2, qhandle_t hShader ) -{ - float w, h; - w = width * scale; - h = height * scale; - CG_AdjustFrom640( &x, &y, &w, &h ); - trap_R_DrawStretchPic( x, y, w, h, s, t, s2, t2, hShader ); -} + case ALIGN_RIGHT: + tx = rect->w - w; + break; -void CG_Text_Paint( float x, float y, float scale, vec4_t color, const char *text, - float adjust, int limit, int style ) -{ - int len, count; - vec4_t newColor; - glyphInfo_t *glyph; - float useScale; - fontInfo_t *font = &cgDC.Assets.textFont; + case ALIGN_CENTER: + tx = ( rect->w - w ) / 2.0f; + break; - if( scale <= cg_smallFont.value ) - font = &cgDC.Assets.smallFont; - else if( scale > cg_bigFont.value ) - font = &cgDC.Assets.bigFont; + case ALIGN_NONE: + tx = 0; + break; + } - useScale = scale * font->glyphScale; - if( text ) + switch( valign ) { -// TTimo: FIXME -// const unsigned char *s = text; - const char *s = text; - - trap_R_SetColor( color ); - memcpy( &newColor[ 0 ], &color[ 0 ], sizeof( vec4_t ) ); - len = strlen( text ); - - if( limit > 0 && len > limit ) - len = limit; - - count = 0; - while( s && *s && count < len ) - { - glyph = &font->glyphs[ (int)*s ]; - //TTimo: FIXME: getting nasty warnings without the cast, - //hopefully this doesn't break the VM build + default: + case VALIGN_BOTTOM: + ty = rect->h; + break; - if( Q_IsColorString( s ) ) - { - memcpy( newColor, g_color_table[ ColorIndex( *( s + 1 ) ) ], sizeof( newColor ) ); - newColor[ 3 ] = color[ 3 ]; - trap_R_SetColor( newColor ); - s += 2; - continue; - } - else - { - float yadj = useScale * glyph->top; - if( style == ITEM_TEXTSTYLE_SHADOWED || - style == ITEM_TEXTSTYLE_SHADOWEDMORE ) - { - int ofs = style == ITEM_TEXTSTYLE_SHADOWED ? 1 : 2; - colorBlack[ 3 ] = newColor[ 3 ]; - trap_R_SetColor( colorBlack ); - CG_Text_PaintChar( x + ofs, y - yadj + ofs, - glyph->imageWidth, - glyph->imageHeight, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph ); - - colorBlack[ 3 ] = 1.0; - trap_R_SetColor( newColor ); - } - else if( style == ITEM_TEXTSTYLE_NEON ) - { - vec4_t glow, outer, inner, white; - - glow[ 0 ] = newColor[ 0 ] * 0.5; - glow[ 1 ] = newColor[ 1 ] * 0.5; - glow[ 2 ] = newColor[ 2 ] * 0.5; - glow[ 3 ] = newColor[ 3 ] * 0.2; - - outer[ 0 ] = newColor[ 0 ]; - outer[ 1 ] = newColor[ 1 ]; - outer[ 2 ] = newColor[ 2 ]; - outer[ 3 ] = newColor[ 3 ]; - - inner[ 0 ] = newColor[ 0 ] * 1.5 > 1.0f ? 1.0f : newColor[ 0 ] * 1.5; - inner[ 1 ] = newColor[ 1 ] * 1.5 > 1.0f ? 1.0f : newColor[ 1 ] * 1.5; - inner[ 2 ] = newColor[ 2 ] * 1.5 > 1.0f ? 1.0f : newColor[ 2 ] * 1.5; - inner[ 3 ] = newColor[ 3 ]; - - white[ 0 ] = white[ 1 ] = white[ 2 ] = white[ 3 ] = 1.0f; - - trap_R_SetColor( glow ); - CG_Text_PaintChar( x - 3, y - yadj - 3, - glyph->imageWidth + 6, - glyph->imageHeight + 6, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph ); - - trap_R_SetColor( outer ); - CG_Text_PaintChar( x - 1, y - yadj - 1, - glyph->imageWidth + 2, - glyph->imageHeight + 2, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph ); - - trap_R_SetColor( inner ); - CG_Text_PaintChar( x - 0.5, y - yadj - 0.5, - glyph->imageWidth + 1, - glyph->imageHeight + 1, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph ); - - trap_R_SetColor( white ); - } + case VALIGN_TOP: + ty = h; + break; + case VALIGN_CENTER: + ty = h + ( ( rect->h - h ) / 2.0f ); + break; - CG_Text_PaintChar( x, y - yadj, - glyph->imageWidth, - glyph->imageHeight, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph ); + case VALIGN_NONE: + ty = 0; + break; + } - x += ( glyph->xSkip * useScale ) + adjust; - s++; - count++; - } - } + if( x ) + *x = rect->x + tx; - trap_R_SetColor( NULL ); - } + if( y ) + *y = rect->y + ty; } /* ============== CG_DrawFieldPadded -Draws large numbers for status bar and powerups +Draws large numbers for status bar ============== */ static void CG_DrawFieldPadded( int x, int y, int width, int cw, int ch, int value ) @@ -307,7 +108,7 @@ static void CG_DrawFieldPadded( int x, int y, int width, int cw, int ch, int val charWidth = CHAR_WIDTH; if( !( charHeight = ch ) ) - charWidth = CHAR_HEIGHT; + charHeight = CHAR_HEIGHT; if( width < 1 ) return; @@ -344,7 +145,7 @@ static void CG_DrawFieldPadded( int x, int y, int width, int cw, int ch, int val orgL = l; - x += 2; + x += ( 2.0f * cgDC.aspectScale ); ptr = num; while( *ptr && l ) @@ -373,7 +174,7 @@ static void CG_DrawFieldPadded( int x, int y, int width, int cw, int ch, int val ============== CG_DrawField -Draws large numbers for status bar and powerups +Draws large numbers for status bar ============== */ void CG_DrawField( float x, float y, int width, float cw, float ch, int value ) @@ -387,7 +188,7 @@ void CG_DrawField( float x, float y, int width, float cw, float ch, int value ) charWidth = CHAR_WIDTH; if( !( charHeight = ch ) ) - charWidth = CHAR_HEIGHT; + charHeight = CHAR_HEIGHT; if( width < 1 ) return; @@ -422,7 +223,7 @@ void CG_DrawField( float x, float y, int width, float cw, float ch, int value ) if( l > width ) l = width; - x += 2 + charWidth * ( width - l ); + x += ( 2.0f * cgDC.aspectScale ) + charWidth * ( width - l ); ptr = num; while( *ptr && l ) @@ -440,18 +241,22 @@ void CG_DrawField( float x, float y, int width, float cw, float ch, int value ) } static void CG_DrawProgressBar( rectDef_t *rect, vec4_t color, float scale, - int align, int textStyle, int special, float progress ) + int align, int textalign, int textStyle, + float borderSize, float progress ) { - float rimWidth = rect->h / 20.0f; + float rimWidth; float doneWidth, leftWidth; - float tx, ty, tw, th; + float tx, ty; char textBuffer[ 8 ]; - if( rimWidth < 0.6f ) - rimWidth = 0.6f; - - if( special >= 0.0f ) - rimWidth = special; + if( borderSize >= 0.0f ) + rimWidth = borderSize; + else + { + rimWidth = rect->h / 20.0f; + if( rimWidth < 0.6f ) + rimWidth = 0.6f; + } if( progress < 0.0f ) progress = 0.0f; @@ -464,7 +269,7 @@ static void CG_DrawProgressBar( rectDef_t *rect, vec4_t color, float scale, trap_R_SetColor( color ); //draw rim and bar - if( align == ITEM_ALIGN_RIGHT ) + if( align == ALIGN_RIGHT ) { CG_DrawPic( rect->x, rect->y, rimWidth, rect->h, cgs.media.whiteShader ); CG_DrawPic( rect->x + rimWidth, rect->y, @@ -490,31 +295,9 @@ static void CG_DrawProgressBar( rectDef_t *rect, vec4_t color, float scale, if( scale > 0.0 ) { Com_sprintf( textBuffer, sizeof( textBuffer ), "%d%%", (int)( progress * 100 ) ); - tw = CG_Text_Width( textBuffer, scale, 0 ); - th = scale * 40.0f; + CG_AlignText( rect, textBuffer, scale, 0.0f, 0.0f, textalign, VALIGN_CENTER, &tx, &ty ); - switch( align ) - { - case ITEM_ALIGN_LEFT: - tx = rect->x + ( rect->w / 10.0f ); - ty = rect->y + ( rect->h / 2.0f ) + ( th / 2.0f ); - break; - - case ITEM_ALIGN_RIGHT: - tx = rect->x + rect->w - ( rect->w / 10.0f ) - tw; - ty = rect->y + ( rect->h / 2.0f ) + ( th / 2.0f ); - break; - - case ITEM_ALIGN_CENTER: - tx = rect->x + ( rect->w / 2.0f ) - ( tw / 2.0f ); - ty = rect->y + ( rect->h / 2.0f ) + ( th / 2.0f ); - break; - - default: - tx = ty = 0.0f; - } - - CG_Text_Paint( tx, ty, scale, color, textBuffer, 0, 0, textStyle ); + UI_Text_Paint( tx, ty, scale, color, textBuffer, 0, 0, textStyle ); } } @@ -539,37 +322,19 @@ static void CG_DrawPlayerCreditsValue( rectDef_t *rect, vec4_t color, qboolean p value = ps->persistant[ PERS_CREDIT ]; if( value > -1 ) { - if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS && - !CG_AtHighestClass( ) ) + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_ALIENS ) { - if( cg.time - cg.lastEvolveAttempt <= NO_CREDITS_TIME ) + if( !BG_AlienCanEvolve( cg.predictedPlayerState.stats[ STAT_CLASS ], + value, cgs.alienStage ) && + cg.time - cg.lastEvolveAttempt <= NO_CREDITS_TIME && + ( ( cg.time - cg.lastEvolveAttempt ) / 300 ) & 1 ) { - if( ( ( cg.time - cg.lastEvolveAttempt ) / 300 ) % 2 ) - color[ 3 ] = 0.0f; + color[ 3 ] = 0.0f; } - } - - trap_R_SetColor( color ); - - if( padding ) - CG_DrawFieldPadded( rect->x, rect->y, 4, rect->w / 4, rect->h, value ); - else - CG_DrawField( rect->x, rect->y, 1, rect->w, rect->h, value ); - - trap_R_SetColor( NULL ); - } -} -static void CG_DrawPlayerBankValue( rectDef_t *rect, vec4_t color, qboolean padding ) -{ - int value; - playerState_t *ps; - - ps = &cg.snap->ps; + value /= ALIEN_CREDITS_PER_KILL; + } - value = ps->persistant[ PERS_BANK ]; - if( value > -1 ) - { trap_R_SetColor( color ); if( padding ) @@ -581,114 +346,66 @@ static void CG_DrawPlayerBankValue( rectDef_t *rect, vec4_t color, qboolean padd } } -#define HH_MIN_ALPHA 0.2f -#define HH_MAX_ALPHA 0.8f -#define HH_ALPHA_DIFF (HH_MAX_ALPHA-HH_MIN_ALPHA) - -#define AH_MIN_ALPHA 0.2f -#define AH_MAX_ALPHA 0.8f -#define AH_ALPHA_DIFF (AH_MAX_ALPHA-AH_MIN_ALPHA) - -/* -============== -CG_DrawPlayerStamina1 -============== -*/ -static void CG_DrawPlayerStamina1( rectDef_t *rect, vec4_t color, qhandle_t shader ) +static void CG_DrawPlayerCreditsFraction( rectDef_t *rect, vec4_t color, qhandle_t shader ) { - playerState_t *ps = &cg.snap->ps; - float stamina = ps->stats[ STAT_STAMINA ]; - float maxStaminaBy3 = (float)MAX_STAMINA / 3.0f; - float progress; + float fraction; + float height; - stamina -= ( 2 * (int)maxStaminaBy3 ); - progress = stamina / maxStaminaBy3; + if( cg.predictedPlayerState.stats[ STAT_TEAM ] != TEAM_ALIENS ) + return; - if( progress > 1.0f ) - progress = 1.0f; - else if( progress < 0.0f ) - progress = 0.0f; + fraction = ((float)(cg.predictedPlayerState.persistant[ PERS_CREDIT ] % + ALIEN_CREDITS_PER_KILL)) / ALIEN_CREDITS_PER_KILL; - color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF ); + CG_AdjustFrom640( &rect->x, &rect->y, &rect->w, &rect->h ); + height = rect->h * fraction; trap_R_SetColor( color ); - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + trap_R_DrawStretchPic( rect->x, rect->y - height + rect->h, rect->w, + height, 0.0f, 1.0f - fraction, 1.0f, 1.0f, shader ); trap_R_SetColor( NULL ); } -/* -============== -CG_DrawPlayerStamina2 -============== -*/ -static void CG_DrawPlayerStamina2( rectDef_t *rect, vec4_t color, qhandle_t shader ) -{ - playerState_t *ps = &cg.snap->ps; - float stamina = ps->stats[ STAT_STAMINA ]; - float maxStaminaBy3 = (float)MAX_STAMINA / 3.0f; - float progress; - - stamina -= (int)maxStaminaBy3; - progress = stamina / maxStaminaBy3; - - if( progress > 1.0f ) - progress = 1.0f; - else if( progress < 0.0f ) - progress = 0.0f; - - color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF ); - - trap_R_SetColor( color ); - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); - trap_R_SetColor( NULL ); -} /* ============== -CG_DrawPlayerStamina3 +CG_DrawPlayerStamina ============== */ -static void CG_DrawPlayerStamina3( rectDef_t *rect, vec4_t color, qhandle_t shader ) +static void CG_DrawPlayerStamina( int ownerDraw, rectDef_t *rect, + vec4_t backColor, vec4_t foreColor, + qhandle_t shader ) { playerState_t *ps = &cg.snap->ps; float stamina = ps->stats[ STAT_STAMINA ]; - float maxStaminaBy3 = (float)MAX_STAMINA / 3.0f; + float maxStaminaBy3 = (float)STAMINA_MAX / 3.0f; float progress; + vec4_t color; + switch( ownerDraw ) + { + case CG_PLAYER_STAMINA_1: + progress = ( stamina - 2 * (int)maxStaminaBy3 ) / maxStaminaBy3; + break; + case CG_PLAYER_STAMINA_2: + progress = ( stamina - (int)maxStaminaBy3 ) / maxStaminaBy3; + break; + case CG_PLAYER_STAMINA_3: progress = stamina / maxStaminaBy3; + break; + case CG_PLAYER_STAMINA_4: + progress = ( stamina + STAMINA_MAX ) / STAMINA_MAX; + break; + default: + return; + } if( progress > 1.0f ) progress = 1.0f; else if( progress < 0.0f ) progress = 0.0f; - color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF ); - - trap_R_SetColor( color ); - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); - trap_R_SetColor( NULL ); -} - -/* -============== -CG_DrawPlayerStamina4 -============== -*/ -static void CG_DrawPlayerStamina4( rectDef_t *rect, vec4_t color, qhandle_t shader ) -{ - playerState_t *ps = &cg.snap->ps; - float stamina = ps->stats[ STAT_STAMINA ]; - float progress; - - stamina += (float)MAX_STAMINA; - progress = stamina / (float)MAX_STAMINA; - - if( progress > 1.0f ) - progress = 1.0f; - else if( progress < 0.0f ) - progress = 0.0f; - - color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF ); + Vector4Lerp( progress, backColor, foreColor, color ); trap_R_SetColor( color ); CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); @@ -700,15 +417,28 @@ static void CG_DrawPlayerStamina4( rectDef_t *rect, vec4_t color, qhandle_t shad CG_DrawPlayerStaminaBolt ============== */ -static void CG_DrawPlayerStaminaBolt( rectDef_t *rect, vec4_t color, qhandle_t shader ) +static void CG_DrawPlayerStaminaBolt( rectDef_t *rect, vec4_t backColor, + vec4_t foreColor, qhandle_t shader ) { - playerState_t *ps = &cg.snap->ps; - float stamina = ps->stats[ STAT_STAMINA ]; + float stamina = cg.snap->ps.stats[ STAT_STAMINA ]; + vec4_t color; - if( stamina < 0 ) - color[ 3 ] = HH_MIN_ALPHA; + if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_SPEEDBOOST ) + { + if( stamina >= 0 ) + Vector4Lerp( ( sin( cg.time / 150.0f ) + 1 ) / 2, + backColor, foreColor, color ); + else + Vector4Lerp( ( sin( cg.time / 2000.0f ) + 1 ) / 2, + backColor, foreColor, color ); + } else - color[ 3 ] = HH_MAX_ALPHA; + { + if( stamina < 0 ) + Vector4Copy( backColor, color ); + else + Vector4Copy( foreColor, color ); + } trap_R_SetColor( color ); CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); @@ -720,40 +450,42 @@ static void CG_DrawPlayerStaminaBolt( rectDef_t *rect, vec4_t color, qhandle_t s CG_DrawPlayerClipsRing ============== */ -static void CG_DrawPlayerClipsRing( rectDef_t *rect, vec4_t color, qhandle_t shader ) +static void CG_DrawPlayerClipsRing( rectDef_t *rect, vec4_t backColor, + vec4_t foreColor, qhandle_t shader ) { playerState_t *ps = &cg.snap->ps; centity_t *cent; float buildTime = ps->stats[ STAT_MISC ]; float progress; float maxDelay; + weapon_t weapon; + vec4_t color; cent = &cg_entities[ cg.snap->ps.clientNum ]; + weapon = BG_GetPlayerWeapon( ps ); - switch( cent->currentState.weapon ) + switch( weapon ) { case WP_ABUILD: case WP_ABUILD2: case WP_HBUILD: - case WP_HBUILD2: - maxDelay = (float)BG_FindBuildDelayForWeapon( cent->currentState.weapon ); - - if( buildTime > maxDelay ) - buildTime = maxDelay; + if( buildTime > MAXIMUM_BUILD_TIME ) + buildTime = MAXIMUM_BUILD_TIME; + progress = ( MAXIMUM_BUILD_TIME - buildTime ) / MAXIMUM_BUILD_TIME; - progress = ( maxDelay - buildTime ) / maxDelay; - - color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF ); + Vector4Lerp( progress, backColor, foreColor, color ); break; default: if( ps->weaponstate == WEAPON_RELOADING ) { - maxDelay = (float)BG_FindReloadTimeForWeapon( cent->currentState.weapon ); + maxDelay = (float)BG_Weapon( cent->currentState.weapon )->reloadTime; progress = ( maxDelay - (float)ps->weaponTime ) / maxDelay; - color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF ); + Vector4Lerp( progress, backColor, foreColor, color ); } + else + Com_Memcpy( color, foreColor, sizeof( color ) ); break; } @@ -767,24 +499,20 @@ static void CG_DrawPlayerClipsRing( rectDef_t *rect, vec4_t color, qhandle_t sha CG_DrawPlayerBuildTimerRing ============== */ -static void CG_DrawPlayerBuildTimerRing( rectDef_t *rect, vec4_t color, qhandle_t shader ) +static void CG_DrawPlayerBuildTimerRing( rectDef_t *rect, vec4_t backColor, + vec4_t foreColor, qhandle_t shader ) { playerState_t *ps = &cg.snap->ps; - centity_t *cent; float buildTime = ps->stats[ STAT_MISC ]; float progress; - float maxDelay; + vec4_t color; - cent = &cg_entities[ cg.snap->ps.clientNum ]; - - maxDelay = (float)BG_FindBuildDelayForWeapon( cent->currentState.weapon ); + if( buildTime > MAXIMUM_BUILD_TIME ) + buildTime = MAXIMUM_BUILD_TIME; - if( buildTime > maxDelay ) - buildTime = maxDelay; + progress = ( MAXIMUM_BUILD_TIME - buildTime ) / MAXIMUM_BUILD_TIME; - progress = ( maxDelay - buildTime ) / maxDelay; - - color[ 3 ] = AH_MIN_ALPHA + ( progress * AH_ALPHA_DIFF ); + Vector4Lerp( progress, backColor, foreColor, color ); trap_R_SetColor( color ); CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); @@ -796,17 +524,14 @@ static void CG_DrawPlayerBuildTimerRing( rectDef_t *rect, vec4_t color, qhandle_ CG_DrawPlayerBoosted ============== */ -static void CG_DrawPlayerBoosted( rectDef_t *rect, vec4_t color, qhandle_t shader ) +static void CG_DrawPlayerBoosted( rectDef_t *rect, vec4_t backColor, + vec4_t foreColor, qhandle_t shader ) { - playerState_t *ps = &cg.snap->ps; - qboolean boosted = ps->stats[ STAT_STATE ] & SS_BOOSTED; - - if( boosted ) - color[ 3 ] = AH_MAX_ALPHA; + if( cg.snap->ps.stats[ STAT_STATE ] & SS_BOOSTED ) + trap_R_SetColor( foreColor ); else - color[ 3 ] = AH_MIN_ALPHA; + trap_R_SetColor( backColor ); - trap_R_SetColor( color ); CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); trap_R_SetColor( NULL ); } @@ -816,26 +541,20 @@ static void CG_DrawPlayerBoosted( rectDef_t *rect, vec4_t color, qhandle_t shade CG_DrawPlayerBoosterBolt ============== */ -static void CG_DrawPlayerBoosterBolt( rectDef_t *rect, vec4_t color, qhandle_t shader ) +static void CG_DrawPlayerBoosterBolt( rectDef_t *rect, vec4_t backColor, + vec4_t foreColor, qhandle_t shader ) { - playerState_t *ps = &cg.snap->ps; - qboolean boosted = ps->stats[ STAT_STATE ] & SS_BOOSTED; - vec4_t localColor; + vec4_t color; - Vector4Copy( color, localColor ); - - if( boosted ) - { - if( ps->stats[ STAT_BOOSTTIME ] > BOOST_TIME - 3000 ) - { - qboolean flash = ( ps->stats[ STAT_BOOSTTIME ] / 500 ) % 2; - - if( flash ) - localColor[ 3 ] = 1.0f; - } - } + // Flash bolts when the boost is almost out + if( ( cg.snap->ps.stats[ STAT_STATE ] & SS_BOOSTED ) && + ( cg.snap->ps.stats[ STAT_STATE ] & SS_BOOSTEDWARNING ) ) + Vector4Lerp( ( sin( cg.time / 100.0f ) + 1 ) / 2, + backColor, foreColor, color ); + else + Vector4Copy( foreColor, color ); - trap_R_SetColor( localColor ); + trap_R_SetColor( color ); CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); trap_R_SetColor( NULL ); } @@ -847,38 +566,49 @@ CG_DrawPlayerPoisonBarbs */ static void CG_DrawPlayerPoisonBarbs( rectDef_t *rect, vec4_t color, qhandle_t shader ) { - playerState_t *ps = &cg.snap->ps; - int x = rect->x; - int y = rect->y; - int width = rect->w; - int height = rect->h; - qboolean vertical; - int iconsize, numBarbs, i; + qboolean vertical; + float x = rect->x, y = rect->y; + float width = rect->w, height = rect->h; + float diff; + int iconsize, numBarbs, maxBarbs; + + maxBarbs = BG_Weapon( cg.snap->ps.weapon )->maxAmmo; + numBarbs = cg.snap->ps.ammo; + if( maxBarbs <= 0 || numBarbs <= 0 ) + return; - numBarbs = ps->ammo; + // adjust these first to ensure the aspect ratio of the barb image is + // preserved + CG_AdjustFrom640( &x, &y, &width, &height ); if( height > width ) { vertical = qtrue; iconsize = width; + if( maxBarbs != 1 ) // avoid division by zero + diff = ( height - iconsize ) / (float)( maxBarbs - 1 ); + else + diff = 0; // doesn't matter, won't be used } - else if( height <= width ) + else { vertical = qfalse; iconsize = height; + if( maxBarbs != 1 ) + diff = ( width - iconsize ) / (float)( maxBarbs - 1 ); + else + diff = 0; } - if( color[ 3 ] != 0.0 ) - trap_R_SetColor( color ); + trap_R_SetColor( color ); - for( i = 0; i < numBarbs; i ++ ) + for( ; numBarbs > 0; numBarbs-- ) { + trap_R_DrawStretchPic( x, y, iconsize, iconsize, 0, 0, 1, 1, shader ); if( vertical ) - y += iconsize; + y += diff; else - x += iconsize; - - CG_DrawPic( x, y, iconsize, iconsize, shader ); + x += diff; } trap_R_SetColor( NULL ); @@ -889,70 +619,83 @@ static void CG_DrawPlayerPoisonBarbs( rectDef_t *rect, vec4_t color, qhandle_t s CG_DrawPlayerWallclimbing ============== */ -static void CG_DrawPlayerWallclimbing( rectDef_t *rect, vec4_t color, qhandle_t shader ) +static void CG_DrawPlayerWallclimbing( rectDef_t *rect, vec4_t backColor, vec4_t foreColor, qhandle_t shader ) { - playerState_t *ps = &cg.snap->ps; - qboolean ww = ps->stats[ STAT_STATE ] & SS_WALLCLIMBING; - - if( ww ) - color[ 3 ] = AH_MAX_ALPHA; + if( cg.snap->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING ) + trap_R_SetColor( foreColor ); else - color[ 3 ] = AH_MIN_ALPHA; + trap_R_SetColor( backColor ); - trap_R_SetColor( color ); CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); trap_R_SetColor( NULL ); } -static void CG_DrawPlayerStamina( rectDef_t *rect, vec4_t color, float scale, - int align, int textStyle, int special ) -{ - playerState_t *ps = &cg.snap->ps; - int stamina = ps->stats[ STAT_STAMINA ]; - float progress = ( (float)stamina + (float)MAX_STAMINA ) / ( (float)MAX_STAMINA * 2.0f ); - - CG_DrawProgressBar( rect, color, scale, align, textStyle, special, progress ); -} - static void CG_DrawPlayerAmmoValue( rectDef_t *rect, vec4_t color ) { - int value; - centity_t *cent; - playerState_t *ps; - - cent = &cg_entities[ cg.snap->ps.clientNum ]; - ps = &cg.snap->ps; + int value; + int valueMarked = -1; + qboolean bp = qfalse; - if( cent->currentState.weapon ) + switch( cg.snap->ps.stats[ STAT_WEAPON ] ) { - switch( cent->currentState.weapon ) - { - case WP_ABUILD: - case WP_ABUILD2: - //percentage of BP remaining - value = cgs.alienBuildPoints; - break; + case WP_NONE: + case WP_BLASTER: + return; - case WP_HBUILD: - case WP_HBUILD2: - //percentage of BP remaining - value = cgs.humanBuildPoints; - break; + case WP_ABUILD: + case WP_ABUILD2: + case WP_HBUILD: + value = cg.snap->ps.persistant[ PERS_BP ]; + valueMarked = cg.snap->ps.persistant[ PERS_MARKEDBP ]; + bp = qtrue; + break; - default: - value = ps->ammo; - break; - } + default: + value = cg.snap->ps.ammo; + break; + } - if( value > 999 ) - value = 999; + if( value > 999 ) + value = 999; + if( valueMarked > 999 ) + valueMarked = 999; - if( value > -1 ) + if( value > -1 ) + { + float tx, ty; + const char *text; + float scale; + int len; + + trap_R_SetColor( color ); + if( !bp ) { - trap_R_SetColor( color ); - CG_DrawField( rect->x, rect->y, 4, rect->w / 4, rect->h, value ); + CG_DrawField( rect->x - 5, rect->y, 4, rect->w / 4, rect->h, value ); trap_R_SetColor( NULL ); + return; } + + if( valueMarked > 0 ) + text = va( "%d+(%d)", value, valueMarked ); + else + text = va( "%d", value ); + + len = strlen( text ); + + if( len <= 4 ) + scale = 0.50; + else if( len <= 6 ) + scale = 0.43; + else if( len == 7 ) + scale = 0.36; + else if( len == 8 ) + scale = 0.33; + else + scale = 0.31; + + CG_AlignText( rect, text, scale, 0.0f, 0.0f, ALIGN_RIGHT, VALIGN_CENTER, &tx, &ty ); + UI_Text_Paint( tx + 1, ty, scale, color, text, 0, 0, ITEM_TEXTSTYLE_NORMAL ); + trap_R_SetColor( NULL ); } } @@ -964,7 +707,7 @@ CG_DrawAlienSense */ static void CG_DrawAlienSense( rectDef_t *rect ) { - if( BG_ClassHasAbility( cg.snap->ps.stats[ STAT_PCLASS ], SCA_ALIENSENSE ) ) + if( BG_ClassHasAbility( cg.snap->ps.stats[ STAT_CLASS ], SCA_ALIENSENSE ) ) CG_AlienSense( rect ); } @@ -999,19 +742,25 @@ static void CG_DrawUsableBuildable( rectDef_t *rect, qhandle_t shader, vec4_t co es = &cg_entities[ trace.entityNum ].currentState; - if( es->eType == ET_BUILDABLE && BG_FindUsableForBuildable( es->modelindex ) && - cg.predictedPlayerState.stats[ STAT_PTEAM ] == BG_FindTeamForBuildable( es->modelindex ) ) + if( es->eType == ET_BUILDABLE && BG_Buildable( es->modelindex )->usable && + cg.predictedPlayerState.stats[ STAT_TEAM ] == BG_Buildable( es->modelindex )->team ) { //hack to prevent showing the usable buildable when you aren't carrying an energy weapon if( ( es->modelindex == BA_H_REACTOR || es->modelindex == BA_H_REPEATER ) && - ( !BG_FindUsesEnergyForWeapon( cg.snap->ps.weapon ) || - BG_FindInfinteAmmoForWeapon( cg.snap->ps.weapon ) ) ) + ( !BG_Weapon( cg.snap->ps.weapon )->usesEnergy || + BG_Weapon( cg.snap->ps.weapon )->infiniteAmmo ) ) + { + cg.nearUsableBuildable = BA_NONE; return; + } trap_R_SetColor( color ); CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); trap_R_SetColor( NULL ); + cg.nearUsableBuildable = es->modelindex; } + else + cg.nearUsableBuildable = BA_NONE; } @@ -1019,221 +768,381 @@ static void CG_DrawUsableBuildable( rectDef_t *rect, qhandle_t shader, vec4_t co static void CG_DrawPlayerBuildTimer( rectDef_t *rect, vec4_t color ) { - float progress; int index; - centity_t *cent; playerState_t *ps; - cent = &cg_entities[ cg.snap->ps.clientNum ]; ps = &cg.snap->ps; - if( cent->currentState.weapon ) - { - switch( cent->currentState.weapon ) - { - case WP_ABUILD: - progress = (float)ps->stats[ STAT_MISC ] / (float)ABUILDER_BASE_DELAY; - break; - - case WP_ABUILD2: - progress = (float)ps->stats[ STAT_MISC ] / (float)ABUILDER_ADV_DELAY; - break; - - case WP_HBUILD: - progress = (float)ps->stats[ STAT_MISC ] / (float)HBUILD_DELAY; - break; - - case WP_HBUILD2: - progress = (float)ps->stats[ STAT_MISC ] / (float)HBUILD2_DELAY; - break; + if( ps->stats[ STAT_MISC ] <= 0 ) + return; - default: - return; - break; - } + switch( ps->stats[ STAT_WEAPON ] ) + { + case WP_ABUILD: + case WP_ABUILD2: + case WP_HBUILD: + break; - if( !ps->stats[ STAT_MISC ] ) + default: return; + } - index = (int)( progress * 8.0f ); - - if( index > 7 ) - index = 7; - else if( index < 0 ) - index = 0; + index = 8 * ( ps->stats[ STAT_MISC ] - 1 ) / MAXIMUM_BUILD_TIME; + if( index > 7 ) + index = 7; + else if( index < 0 ) + index = 0; - if( cg.time - cg.lastBuildAttempt <= BUILD_DELAY_TIME ) - { - if( ( ( cg.time - cg.lastBuildAttempt ) / 300 ) % 2 ) - { - color[ 0 ] = 1.0f; - color[ 1 ] = color[ 2 ] = 0.0f; - color[ 3 ] = 1.0f; - } - } - - trap_R_SetColor( color ); - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, - cgs.media.buildWeaponTimerPie[ index ] ); - trap_R_SetColor( NULL ); + if( cg.time - cg.lastBuildAttempt <= BUILD_DELAY_TIME && + ( ( cg.time - cg.lastBuildAttempt ) / 300 ) % 2 ) + { + color[ 0 ] = 1.0f; + color[ 1 ] = color[ 2 ] = 0.0f; + color[ 3 ] = 1.0f; } + + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, + cgs.media.buildWeaponTimerPie[ index ] ); + trap_R_SetColor( NULL ); } static void CG_DrawPlayerClipsValue( rectDef_t *rect, vec4_t color ) { int value; - centity_t *cent; - playerState_t *ps; - - cent = &cg_entities[ cg.snap->ps.clientNum ]; - ps = &cg.snap->ps; + playerState_t *ps = &cg.snap->ps; - if( cent->currentState.weapon ) + switch( ps->stats[ STAT_WEAPON ] ) { - switch( cent->currentState.weapon ) - { - case WP_ABUILD: - case WP_ABUILD2: - case WP_HBUILD: - case WP_HBUILD2: - break; + case WP_NONE: + case WP_BLASTER: + case WP_ABUILD: + case WP_ABUILD2: + case WP_HBUILD: + return; - default: - value = ps->clips; + default: + value = ps->clips; - if( value > -1 ) - { - trap_R_SetColor( color ); - CG_DrawField( rect->x, rect->y, 4, rect->w / 4, rect->h, value ); - trap_R_SetColor( NULL ); - } - break; - } + if( value > -1 ) + { + trap_R_SetColor( color ); + CG_DrawField( rect->x, rect->y, 4, rect->w / 4, rect->h, value ); + trap_R_SetColor( NULL ); + } + break; } } static void CG_DrawPlayerHealthValue( rectDef_t *rect, vec4_t color ) { - playerState_t *ps; - int value; - - ps = &cg.snap->ps; - - value = ps->stats[ STAT_HEALTH ]; - trap_R_SetColor( color ); - CG_DrawField( rect->x, rect->y, 4, rect->w / 4, rect->h, value ); + CG_DrawField( rect->x, rect->y, 4, rect->w / 4, rect->h, + cg.snap->ps.stats[ STAT_HEALTH ] ); trap_R_SetColor( NULL ); } -static void CG_DrawPlayerHealthBar( rectDef_t *rect, vec4_t color, float scale, - int align, int textStyle, int special ) -{ - playerState_t *ps; - float total; - - ps = &cg.snap->ps; - - total = ( (float)ps->stats[ STAT_HEALTH ] / (float)ps->stats[ STAT_MAX_HEALTH ] ); - CG_DrawProgressBar( rect, color, scale, align, textStyle, special, total ); -} - /* ============== CG_DrawPlayerHealthCross ============== */ -static void CG_DrawPlayerHealthCross( rectDef_t *rect, vec4_t color, qhandle_t shader ) +static void CG_DrawPlayerHealthCross( rectDef_t *rect, vec4_t ref_color ) { - playerState_t *ps = &cg.snap->ps; - int health = ps->stats[ STAT_HEALTH ]; + qhandle_t shader; + vec4_t color; + float ref_alpha; + + // Pick the current icon + shader = cgs.media.healthCross; + if( cg.snap->ps.stats[ STAT_STATE ] & SS_HEALING_3X ) + shader = cgs.media.healthCross3X; + else if( cg.snap->ps.stats[ STAT_STATE ] & SS_HEALING_2X ) + { + if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + shader = cgs.media.healthCross2X; + else + shader = cgs.media.healthCrossMedkit; + } + else if( cg.snap->ps.stats[ STAT_STATE ] & SS_POISONED ) + shader = cgs.media.healthCrossPoisoned; - if( health < 10 ) + // Pick the alpha value + Vector4Copy( ref_color, color ); + if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_HUMANS && + cg.snap->ps.stats[ STAT_HEALTH ] < 10 ) { color[ 0 ] = 1.0f; color[ 1 ] = color[ 2 ] = 0.0f; } + ref_alpha = ref_color[ 3 ]; + if( cg.snap->ps.stats[ STAT_STATE ] & SS_HEALING_ACTIVE ) + ref_alpha = 1.0f; + + // Don't fade from nothing + if( !cg.lastHealthCross ) + cg.lastHealthCross = shader; + // Fade the icon during transition + if( cg.lastHealthCross != shader ) + { + cg.healthCrossFade += cg.frametime / 500.0f; + if( cg.healthCrossFade > 1.0f ) + { + cg.healthCrossFade = 0.0f; + cg.lastHealthCross = shader; + } + else + { + // Fading between two icons + color[ 3 ] = ref_alpha * cg.healthCrossFade; + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + color[ 3 ] = ref_alpha * ( 1.0f - cg.healthCrossFade ); + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg.lastHealthCross ); + trap_R_SetColor( NULL ); + return; + } + } + + // Not fading, draw a single icon + color[ 3 ] = ref_alpha; trap_R_SetColor( color ); CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); trap_R_SetColor( NULL ); } -static void CG_DrawProgressLabel( rectDef_t *rect, float text_x, float text_y, vec4_t color, - float scale, int align, const char *s, float fraction ) +static float CG_ChargeProgress( void ) { - vec4_t white = { 1.0f, 1.0f, 1.0f, 1.0f }; - float tx, tw = CG_Text_Width( s, scale, 0 ); + float progress; + int min = 0, max = 0; - switch( align ) + if( cg.snap->ps.weapon == WP_ALEVEL3 ) { - case ITEM_ALIGN_LEFT: - tx = 0.0f; - break; + min = LEVEL3_POUNCE_TIME_MIN; + max = LEVEL3_POUNCE_TIME; + } + else if( cg.snap->ps.weapon == WP_ALEVEL3_UPG ) + { + min = LEVEL3_POUNCE_TIME_MIN; + max = LEVEL3_POUNCE_TIME_UPG; + } + else if( cg.snap->ps.weapon == WP_ALEVEL4 ) + { + if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_CHARGING ) + { + min = 0; + max = LEVEL4_TRAMPLE_DURATION; + } + else + { + min = LEVEL4_TRAMPLE_CHARGE_MIN; + max = LEVEL4_TRAMPLE_CHARGE_MAX; + } + } + else if( cg.snap->ps.weapon == WP_LUCIFER_CANNON ) + { + min = LCANNON_CHARGE_TIME_MIN; + max = LCANNON_CHARGE_TIME_MAX; + } - case ITEM_ALIGN_RIGHT: - tx = rect->w - tw; - break; + if( max - min <= 0.0f ) + return 0.0f; - case ITEM_ALIGN_CENTER: - tx = ( rect->w / 2.0f ) - ( tw / 2.0f ); - break; + progress = ( (float)cg.predictedPlayerState.stats[ STAT_MISC ] - min ) / + ( max - min ); - default: - tx = 0.0f; + if( progress > 1.0f ) + return 1.0f; + + if( progress < 0.0f ) + return 0.0f; + + return progress; +} + +#define CHARGE_BAR_FADE_RATE 0.002f + +static void CG_DrawPlayerChargeBarBG( rectDef_t *rect, vec4_t ref_color, + qhandle_t shader ) +{ + vec4_t color; + + if( !cg_drawChargeBar.integer || cg.chargeMeterAlpha <= 0.0f ) + return; + + color[ 0 ] = ref_color[ 0 ]; + color[ 1 ] = ref_color[ 1 ]; + color[ 2 ] = ref_color[ 2 ]; + color[ 3 ] = ref_color[ 3 ] * cg.chargeMeterAlpha; + + // Draw meter background + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + trap_R_SetColor( NULL ); +} + +// FIXME: This should come from the element info +#define CHARGE_BAR_CAP_SIZE 3 + +static void CG_DrawPlayerChargeBar( rectDef_t *rect, vec4_t ref_color, + qhandle_t shader ) +{ + vec4_t color; + float x, y, width, height, cap_size, progress; + + if( !cg_drawChargeBar.integer ) + return; + + // Get progress proportion and pump fade + progress = CG_ChargeProgress(); + if( progress <= 0.0f ) + { + cg.chargeMeterAlpha -= CHARGE_BAR_FADE_RATE * cg.frametime; + if( cg.chargeMeterAlpha <= 0.0f ) + { + cg.chargeMeterAlpha = 0.0f; + return; + } + } + else + { + cg.chargeMeterValue = progress; + cg.chargeMeterAlpha += CHARGE_BAR_FADE_RATE * cg.frametime; + if( cg.chargeMeterAlpha > 1.0f ) + cg.chargeMeterAlpha = 1.0f; + } + + color[ 0 ] = ref_color[ 0 ]; + color[ 1 ] = ref_color[ 1 ]; + color[ 2 ] = ref_color[ 2 ]; + color[ 3 ] = ref_color[ 3 ] * cg.chargeMeterAlpha; + + // Flash red for Lucifer Cannon warning + if( cg.snap->ps.weapon == WP_LUCIFER_CANNON && + cg.snap->ps.stats[ STAT_MISC ] >= LCANNON_CHARGE_TIME_WARN && + ( cg.time & 128 ) ) + { + color[ 0 ] = 1.0f; + color[ 1 ] = 0.0f; + color[ 2 ] = 0.0f; + } + + x = rect->x; + y = rect->y; + + // Horizontal charge bar + if( rect->w >= rect->h ) + { + width = ( rect->w - CHARGE_BAR_CAP_SIZE * 2 ) * cg.chargeMeterValue; + height = rect->h; + CG_AdjustFrom640( &x, &y, &width, &height ); + cap_size = CHARGE_BAR_CAP_SIZE * cgs.screenXScale; + + // Draw the meter + trap_R_SetColor( color ); + trap_R_DrawStretchPic( x, y, cap_size, height, 0, 0, 1, 1, shader ); + trap_R_DrawStretchPic( x + width + cap_size, y, cap_size, height, + 1, 0, 0, 1, shader ); + trap_R_DrawStretchPic( x + cap_size, y, width, height, 1, 0, 1, 1, shader ); + trap_R_SetColor( NULL ); } + // Vertical charge bar + else + { + y += rect->h; + width = rect->w; + height = ( rect->h - CHARGE_BAR_CAP_SIZE * 2 ) * cg.chargeMeterValue; + CG_AdjustFrom640( &x, &y, &width, &height ); + cap_size = CHARGE_BAR_CAP_SIZE * cgs.screenYScale; + + // Draw the meter + trap_R_SetColor( color ); + trap_R_DrawStretchPic( x, y - cap_size, width, cap_size, + 0, 1, 1, 0, shader ); + trap_R_DrawStretchPic( x, y - height - cap_size * 2, width, + cap_size, 0, 0, 1, 1, shader ); + trap_R_DrawStretchPic( x, y - height - cap_size, width, height, + 0, 1, 1, 1, shader ); + trap_R_SetColor( NULL ); + } +} + +static void CG_DrawProgressLabel( rectDef_t *rect, float text_x, float text_y, vec4_t color, + float scale, int textalign, int textvalign, + const char *s, float fraction ) +{ + vec4_t white = { 1.0f, 1.0f, 1.0f, 1.0f }; + float tx, ty; + + CG_AlignText( rect, s, scale, 0.0f, 0.0f, textalign, textvalign, &tx, &ty ); + if( fraction < 1.0f ) - CG_Text_Paint( rect->x + text_x + tx, rect->y + text_y, scale, white, + UI_Text_Paint( text_x + tx, text_y + ty, scale, white, s, 0, 0, ITEM_TEXTSTYLE_NORMAL ); else - CG_Text_Paint( rect->x + text_x + tx, rect->y + text_y, scale, color, + UI_Text_Paint( text_x + tx, text_y + ty, scale, color, s, 0, 0, ITEM_TEXTSTYLE_NEON ); } static void CG_DrawMediaProgress( rectDef_t *rect, vec4_t color, float scale, - int align, int textStyle, int special ) + int align, int textalign, int textStyle, + float borderSize ) { - CG_DrawProgressBar( rect, color, scale, align, textStyle, special, cg.mediaFraction ); + CG_DrawProgressBar( rect, color, scale, align, textalign, textStyle, + borderSize, cg.mediaFraction ); } static void CG_DrawMediaProgressLabel( rectDef_t *rect, float text_x, float text_y, - vec4_t color, float scale, int align ) + vec4_t color, float scale, int textalign, int textvalign ) { - CG_DrawProgressLabel( rect, text_x, text_y, color, scale, align, "Map and Textures", cg.mediaFraction ); + CG_DrawProgressLabel( rect, text_x, text_y, color, scale, textalign, textvalign, + "Map and Textures", cg.mediaFraction ); } -static void CG_DrawBuildablesProgress( rectDef_t *rect, vec4_t color, float scale, - int align, int textStyle, int special ) +static void CG_DrawBuildablesProgress( rectDef_t *rect, vec4_t color, + float scale, int align, int textalign, + int textStyle, float borderSize ) { - CG_DrawProgressBar( rect, color, scale, align, textStyle, special, cg.buildablesFraction ); + CG_DrawProgressBar( rect, color, scale, align, textalign, textStyle, + borderSize, cg.buildablesFraction ); } static void CG_DrawBuildablesProgressLabel( rectDef_t *rect, float text_x, float text_y, - vec4_t color, float scale, int align ) + vec4_t color, float scale, int textalign, int textvalign ) { - CG_DrawProgressLabel( rect, text_x, text_y, color, scale, align, "Buildable Models", cg.buildablesFraction ); + CG_DrawProgressLabel( rect, text_x, text_y, color, scale, textalign, textvalign, + "Buildable Models", cg.buildablesFraction ); } -static void CG_DrawCharModelProgress( rectDef_t *rect, vec4_t color, float scale, - int align, int textStyle, int special ) +static void CG_DrawCharModelProgress( rectDef_t *rect, vec4_t color, + float scale, int align, int textalign, + int textStyle, float borderSize ) { - CG_DrawProgressBar( rect, color, scale, align, textStyle, special, cg.charModelFraction ); + CG_DrawProgressBar( rect, color, scale, align, textalign, textStyle, + borderSize, cg.charModelFraction ); } static void CG_DrawCharModelProgressLabel( rectDef_t *rect, float text_x, float text_y, - vec4_t color, float scale, int align ) + vec4_t color, float scale, int textalign, int textvalign ) { - CG_DrawProgressLabel( rect, text_x, text_y, color, scale, align, "Character Models", cg.charModelFraction ); + CG_DrawProgressLabel( rect, text_x, text_y, color, scale, textalign, textvalign, + "Character Models", cg.charModelFraction ); } static void CG_DrawOverallProgress( rectDef_t *rect, vec4_t color, float scale, - int align, int textStyle, int special ) + int align, int textalign, int textStyle, + float borderSize ) { float total; - total = ( cg.charModelFraction + cg.buildablesFraction + cg.mediaFraction ) / 3.0f; - CG_DrawProgressBar( rect, color, scale, align, textStyle, special, total ); + total = cg.charModelFraction + cg.buildablesFraction + cg.mediaFraction; + total /= 3.0f; + + CG_DrawProgressBar( rect, color, scale, align, textalign, textStyle, + borderSize, total ); } static void CG_DrawLevelShot( rectDef_t *rect ) @@ -1258,97 +1167,44 @@ static void CG_DrawLevelShot( rectDef_t *rect ) CG_DrawPic( rect->x, rect->y, rect->w, rect->h, detail ); } -static void CG_DrawLoadingString( rectDef_t *rect, float text_x, float text_y, vec4_t color, - float scale, int align, int textStyle, const char *s ) -{ - float tw, th, tx; - int pos, i; - char buffer[ 1024 ]; - char *end; - - if( !s[ 0 ] ) - return; - - strcpy( buffer, s ); - tw = CG_Text_Width( s, scale, 0 ); - th = scale * 40.0f; - - pos = i = 0; - - while( pos < strlen( s ) ) - { - strcpy( buffer, &s[ pos ] ); - tw = CG_Text_Width( buffer, scale, 0 ); - - while( tw > rect->w ) - { - end = strrchr( buffer, ' ' ); - - if( end == NULL ) - break; - - *end = '\0'; - tw = CG_Text_Width( buffer, scale, 0 ); - } - - switch( align ) - { - case ITEM_ALIGN_LEFT: - tx = rect->x; - break; - - case ITEM_ALIGN_RIGHT: - tx = rect->x + rect->w - tw; - break; - - case ITEM_ALIGN_CENTER: - tx = rect->x + ( rect->w / 2.0f ) - ( tw / 2.0f ); - break; - - default: - tx = 0.0f; - } - - CG_Text_Paint( tx + text_x, rect->y + text_y + i * ( th + 3 ), scale, color, - buffer, 0, 0, textStyle ); - - pos += strlen( buffer ) + 1; - i++; - } -} - static void CG_DrawLevelName( rectDef_t *rect, float text_x, float text_y, - vec4_t color, float scale, int align, int textStyle ) + vec4_t color, float scale, + int textalign, int textvalign, int textStyle ) { const char *s; s = CG_ConfigString( CS_MESSAGE ); - CG_DrawLoadingString( rect, text_x, text_y, color, scale, align, textStyle, s ); + UI_DrawTextBlock( rect, text_x, text_y, color, scale, textalign, textvalign, textStyle, s ); } static void CG_DrawMOTD( rectDef_t *rect, float text_x, float text_y, - vec4_t color, float scale, int align, int textStyle ) + vec4_t color, float scale, + int textalign, int textvalign, int textStyle ) { const char *s; + char parsed[ MAX_STRING_CHARS ]; s = CG_ConfigString( CS_MOTD ); - CG_DrawLoadingString( rect, text_x, text_y, color, scale, align, textStyle, s ); + Q_ParseNewlines( parsed, s, sizeof( parsed ) ); + + UI_DrawTextBlock( rect, text_x, text_y, color, scale, textalign, textvalign, textStyle, parsed ); } static void CG_DrawHostname( rectDef_t *rect, float text_x, float text_y, - vec4_t color, float scale, int align, int textStyle ) + vec4_t color, float scale, + int textalign, int textvalign, int textStyle ) { char buffer[ 1024 ]; const char *info; info = CG_ConfigString( CS_SERVERINFO ); - Q_strncpyz( buffer, Info_ValueForKey( info, "sv_hostname" ), 1024 ); + UI_EscapeEmoticons( buffer, Info_ValueForKey( info, "sv_hostname" ), sizeof( buffer ) ); Q_CleanStr( buffer ); - CG_DrawLoadingString( rect, text_x, text_y, color, scale, align, textStyle, buffer ); + UI_DrawTextBlock( rect, text_x, text_y, color, scale, textalign, textvalign, textStyle, buffer ); } /* @@ -1409,30 +1265,29 @@ Draw all the status / pacifier stuff during level loading */ void CG_DrawLoadingScreen( void ) { - Menu_Paint( Menus_FindByName( "Loading" ), qtrue ); + menuDef_t *menu = Menus_FindByName( "Loading" ); + + Menu_Update( menu ); + Menu_Paint( menu, qtrue ); } float CG_GetValue( int ownerDraw ) { - centity_t *cent; playerState_t *ps; + weapon_t weapon; - cent = &cg_entities[ cg.snap->ps.clientNum ]; ps = &cg.snap->ps; + weapon = BG_GetPlayerWeapon( ps ); switch( ownerDraw ) { case CG_PLAYER_AMMO_VALUE: - if( cent->currentState.weapon ) - { + if( weapon ) return ps->ammo; - } break; case CG_PLAYER_CLIPS_VALUE: - if( cent->currentState.weapon ) - { + if( weapon ) return ps->clips; - } break; case CG_PLAYER_HEALTH: return ps->stats[ STAT_HEALTH ]; @@ -1461,165 +1316,131 @@ static void CG_DrawKiller( rectDef_t *rect, float scale, vec4_t color, if( cg.killerName[ 0 ] ) { int x = rect->x + rect->w / 2; - CG_Text_Paint( x - CG_Text_Width( CG_GetKillerText( ), scale, 0 ) / 2, + UI_Text_Paint( x - UI_Text_Width( CG_GetKillerText( ), scale ) / 2, rect->y + rect->h, scale, color, CG_GetKillerText( ), 0, 0, textStyle ); } } -static void CG_Text_Paint_Limit( float *maxX, float x, float y, float scale, - vec4_t color, const char* text, float adjust, int limit ) -{ - int len, count; - vec4_t newColor; - glyphInfo_t *glyph; +#define SPECTATORS_PIXELS_PER_SECOND 30.0f - if( text ) - { -// TTimo: FIXME -// const unsigned char *s = text; // bk001206 - unsigned - const char *s = text; - float max = *maxX; - float useScale; - fontInfo_t *font = &cgDC.Assets.textFont; - - if( scale <= cg_smallFont.value ) - font = &cgDC.Assets.smallFont; - else if( scale > cg_bigFont.value ) - font = &cgDC.Assets.bigFont; +/* +================== +CG_DrawTeamSpectators +================== +*/ +static void CG_DrawTeamSpectators( rectDef_t *rect, float scale, int textvalign, vec4_t color, qhandle_t shader ) +{ + float y; + char *text = cg.spectatorList; + float textWidth = UI_Text_Width( text, scale ); - useScale = scale * font->glyphScale; - trap_R_SetColor( color ); - len = strlen( text ); + CG_AlignText( rect, text, scale, 0.0f, 0.0f, ALIGN_LEFT, textvalign, NULL, &y ); - if( limit > 0 && len > limit ) - len = limit; + if( textWidth > rect->w ) + { + // The text is too wide to fit, so scroll it + int now = trap_Milliseconds( ); + int delta = now - cg.spectatorTime; - count = 0; + CG_SetClipRegion( rect->x, rect->y, rect->w, rect->h ); - while( s && *s && count < len ) - { - glyph = &font->glyphs[ (int)*s ]; - //TTimo: FIXME: getting nasty warnings without the cast, - //hopefully this doesn't break the VM build + UI_Text_Paint( rect->x - cg.spectatorOffset, y, scale, color, text, 0, 0, 0 ); + UI_Text_Paint( rect->x + textWidth - cg.spectatorOffset, y, scale, color, text, 0, 0, 0 ); - if( Q_IsColorString( s ) ) - { - memcpy( newColor, g_color_table[ ColorIndex( *(s+1) ) ], sizeof( newColor ) ); - newColor[ 3 ] = color[ 3 ]; - trap_R_SetColor( newColor ); - s += 2; - continue; - } - else - { - float yadj = useScale * glyph->top; + CG_ClearClipRegion( ); - if( CG_Text_Width( s, useScale, 1 ) + x > max ) - { - *maxX = 0; - break; - } + cg.spectatorOffset += ( delta / 1000.0f ) * SPECTATORS_PIXELS_PER_SECOND; - CG_Text_PaintChar( x, y - yadj, - glyph->imageWidth, - glyph->imageHeight, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph ); - x += ( glyph->xSkip * useScale ) + adjust; - *maxX = x; - count++; - s++; - } - } + while( cg.spectatorOffset > textWidth ) + cg.spectatorOffset -= textWidth; - trap_R_SetColor( NULL ); + cg.spectatorTime = now; + } + else + { + UI_Text_Paint( rect->x, y, scale, color, text, 0, 0, 0 ); } } -static void CG_DrawTeamSpectators( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader ) +#define FOLLOWING_STRING "following " +#define CHASING_STRING "chasing " + +/* +================== +CG_DrawFollow +================== +*/ +static void CG_DrawFollow( rectDef_t *rect, float text_x, float text_y, + vec4_t color, float scale, int textalign, int textvalign, int textStyle ) { - if( cg.spectatorLen ) - { - float maxX; + float tx, ty; - if( cg.spectatorWidth == -1 ) - { - cg.spectatorWidth = 0; - cg.spectatorPaintX = rect->x + 1; - cg.spectatorPaintX2 = -1; - } + if( cg.snap && cg.snap->ps.pm_flags & PMF_FOLLOW ) + { + char buffer[ MAX_STRING_CHARS ]; - if( cg.spectatorOffset > cg.spectatorLen ) - { - cg.spectatorOffset = 0; - cg.spectatorPaintX = rect->x + 1; - cg.spectatorPaintX2 = -1; - } + if( !cg.chaseFollow ) + strcpy( buffer, FOLLOWING_STRING ); + else + strcpy( buffer, CHASING_STRING ); - if( cg.time > cg.spectatorTime ) - { - cg.spectatorTime = cg.time + 10; + strcat( buffer, cgs.clientinfo[ cg.snap->ps.clientNum ].name ); - if( cg.spectatorPaintX <= rect->x + 2 ) - { - if( cg.spectatorOffset < cg.spectatorLen ) - { - //TA: skip colour directives - if( Q_IsColorString( &cg.spectatorList[ cg.spectatorOffset ] ) ) - cg.spectatorOffset += 2; - else - { - cg.spectatorPaintX += CG_Text_Width( &cg.spectatorList[ cg.spectatorOffset ], scale, 1 ) - 1; - cg.spectatorOffset++; - } - } - else - { - cg.spectatorOffset = 0; + CG_AlignText( rect, buffer, scale, 0, 0, textalign, textvalign, &tx, &ty ); + UI_Text_Paint( text_x + tx, text_y + ty, scale, color, buffer, 0, 0, + textStyle ); + } +} - if( cg.spectatorPaintX2 >= 0 ) - cg.spectatorPaintX = cg.spectatorPaintX2; - else - cg.spectatorPaintX = rect->x + rect->w - 2; +/* +================== +CG_DrawTeamLabel +================== +*/ +static void CG_DrawTeamLabel( rectDef_t *rect, team_t team, float text_x, float text_y, + vec4_t color, float scale, int textalign, int textvalign, int textStyle ) +{ + char *t; + char stage[ MAX_TOKEN_CHARS ]; + const char *s; + float tx, ty; - cg.spectatorPaintX2 = -1; - } - } - else - { - cg.spectatorPaintX--; + stage[ 0 ] = '\0'; - if( cg.spectatorPaintX2 >= 0 ) - cg.spectatorPaintX2--; - } - } + switch( team ) + { + case TEAM_ALIENS: + t = "Aliens"; + if( cg.intermissionStarted ) + Com_sprintf( stage, MAX_TOKEN_CHARS, "(Stage %d)", cgs.alienStage + 1 ); + break; - maxX = rect->x + rect->w - 2; + case TEAM_HUMANS: + t = "Humans"; + if( cg.intermissionStarted ) + Com_sprintf( stage, MAX_TOKEN_CHARS, "(Stage %d)", cgs.humanStage + 1 ); + break; - CG_Text_Paint_Limit( &maxX, cg.spectatorPaintX, rect->y + rect->h - 3, scale, color, - &cg.spectatorList[ cg.spectatorOffset ], 0, 0 ); + default: + t = ""; + break; + } - if( cg.spectatorPaintX2 >= 0 ) - { - float maxX2 = rect->x + rect->w - 2; - CG_Text_Paint_Limit( &maxX2, cg.spectatorPaintX2, rect->y + rect->h - 3, scale, - color, cg.spectatorList, 0, cg.spectatorOffset ); - } + switch( textalign ) + { + default: + case ALIGN_LEFT: + s = va( "%s %s", t, stage ); + break; - if( cg.spectatorOffset && maxX > 0 ) - { - // if we have an offset ( we are skipping the first part of the string ) and we fit the string - if( cg.spectatorPaintX2 == -1 ) - cg.spectatorPaintX2 = rect->x + rect->w - 2; - } - else - cg.spectatorPaintX2 = -1; + case ALIGN_RIGHT: + s = va( "%s %s", stage, t ); + break; } + + CG_AlignText( rect, s, scale, 0.0f, 0.0f, textalign, textvalign, &tx, &ty ); + UI_Text_Paint( text_x + tx, text_y + ty, scale, color, s, 0, 0, textStyle ); } /* @@ -1628,70 +1449,52 @@ CG_DrawStageReport ================== */ static void CG_DrawStageReport( rectDef_t *rect, float text_x, float text_y, - vec4_t color, float scale, int align, int textStyle ) + vec4_t color, float scale, int textalign, int textvalign, int textStyle ) { char s[ MAX_TOKEN_CHARS ]; - int tx, w, kills; + float tx, ty; - if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_NONE && !cg.intermissionStarted ) + if( cg.intermissionStarted ) return; - if( cg.intermissionStarted ) - { - Com_sprintf( s, MAX_TOKEN_CHARS, - "Stage %d" //PH34R MY MAD-LEET CODING SKILLZ - " " - "Stage %d", - cgs.alienStage + 1, cgs.humanStage + 1 ); - } - else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_NONE ) + return; + + if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) { - kills = cgs.alienNextStageThreshold - cgs.alienKills; + int kills = ceil( (float)(cgs.alienNextStageThreshold - cgs.alienCredits) / ALIEN_CREDITS_PER_KILL ); + if( kills < 0 ) + kills = 0; if( cgs.alienNextStageThreshold < 0 ) Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d", cgs.alienStage + 1 ); else if( kills == 1 ) - Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d kill for next stage", - cgs.alienStage + 1, kills ); + Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, 1 frag for next stage", + cgs.alienStage + 1 ); else - Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d kills for next stage", + Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d frags for next stage", cgs.alienStage + 1, kills ); } - else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + else if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) { - kills = cgs.humanNextStageThreshold - cgs.humanKills; + int credits = cgs.humanNextStageThreshold - cgs.humanCredits; + + if( credits < 0 ) + credits = 0; if( cgs.humanNextStageThreshold < 0 ) Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d", cgs.humanStage + 1 ); - else if( kills == 1 ) - Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d kill for next stage", - cgs.humanStage + 1, kills ); + else if( credits == 1 ) + Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, 1 credit for next stage", + cgs.humanStage + 1 ); else - Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d kills for next stage", - cgs.humanStage + 1, kills ); + Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d credits for next stage", + cgs.humanStage + 1, credits ); } - w = CG_Text_Width( s, scale, 0 ); - - switch( align ) - { - case ITEM_ALIGN_LEFT: - tx = rect->x; - break; - - case ITEM_ALIGN_RIGHT: - tx = rect->x + rect->w - w; - break; - - case ITEM_ALIGN_CENTER: - tx = rect->x + ( rect->w / 2.0f ) - ( w / 2.0f ); - break; - - default: - tx = 0.0f; - } + CG_AlignText( rect, s, scale, 0.0f, 0.0f, textalign, textvalign, &tx, &ty ); - CG_Text_Paint( text_x + tx, rect->y + text_y, scale, color, s, 0, 0, textStyle ); + UI_Text_Paint( text_x + tx, text_y + ty, scale, color, s, 0, 0, textStyle ); } /* @@ -1699,15 +1502,17 @@ static void CG_DrawStageReport( rectDef_t *rect, float text_x, float text_y, CG_DrawFPS ================== */ -//TA: personally i think this should be longer - it should really be a cvar #define FPS_FRAMES 20 #define FPS_STRING "fps" static void CG_DrawFPS( rectDef_t *rect, float text_x, float text_y, - float scale, vec4_t color, int align, int textStyle, + float scale, vec4_t color, + int textalign, int textvalign, int textStyle, qboolean scalableText ) { - char *s; - int tx, w, totalWidth, strLength; + const char *s; + float tx, ty; + float w, h, totalWidth; + int strLength; static int previousTimes[ FPS_FRAMES ]; static int index; int i, total; @@ -1741,27 +1546,12 @@ static void CG_DrawFPS( rectDef_t *rect, float text_x, float text_y, fps = 1000 * FPS_FRAMES / total; s = va( "%d", fps ); - w = CG_Text_Width( "0", scale, 0 ); + w = UI_Text_Width( "0", scale ); + h = UI_Text_Height( "0", scale ); strLength = CG_DrawStrlen( s ); - totalWidth = CG_Text_Width( FPS_STRING, scale, 0 ) + w * strLength; - - switch( align ) - { - case ITEM_ALIGN_LEFT: - tx = rect->x; - break; - - case ITEM_ALIGN_RIGHT: - tx = rect->x + rect->w - totalWidth; - break; - - case ITEM_ALIGN_CENTER: - tx = rect->x + ( rect->w / 2.0f ) - ( totalWidth / 2.0f ); - break; + totalWidth = UI_Text_Width( FPS_STRING, scale ) + w * strLength; - default: - tx = 0.0f; - } + CG_AlignText( rect, s, 0.0f, totalWidth, h, textalign, textvalign, &tx, &ty ); if( scalableText ) { @@ -1772,8 +1562,10 @@ static void CG_DrawFPS( rectDef_t *rect, float text_x, float text_y, c[ 0 ] = s[ i ]; c[ 1 ] = '\0'; - CG_Text_Paint( text_x + tx + i * w, rect->y + text_y, scale, color, c, 0, 0, textStyle ); + UI_Text_Paint( text_x + tx + i * w, text_y + ty, scale, color, c, 0, 0, textStyle ); } + + UI_Text_Paint( text_x + tx + i * w, text_y + ty, scale, color, FPS_STRING, 0, 0, textStyle ); } else { @@ -1781,9 +1573,6 @@ static void CG_DrawFPS( rectDef_t *rect, float text_x, float text_y, CG_DrawField( rect->x, rect->y, 3, rect->w / 3, rect->h, fps ); trap_R_SetColor( NULL ); } - - if( scalableText ) - CG_Text_Paint( text_x + tx + i * w, rect->y + text_y, scale, color, FPS_STRING, 0, 0, textStyle ); } } @@ -1844,10 +1633,13 @@ CG_DrawTimer ================= */ static void CG_DrawTimer( rectDef_t *rect, float text_x, float text_y, - float scale, vec4_t color, int align, int textStyle ) + float scale, vec4_t color, + int textalign, int textvalign, int textStyle ) { - char *s; - int i, tx, w, totalWidth, strLength; + const char *s; + float tx, ty; + int i, strLength; + float w, h, totalWidth; int mins, seconds, tens; int msec; @@ -1863,36 +1655,255 @@ static void CG_DrawTimer( rectDef_t *rect, float text_x, float text_y, seconds -= tens * 10; s = va( "%d:%d%d", mins, tens, seconds ); - w = CG_Text_Width( "0", scale, 0 ); + w = UI_Text_Width( "0", scale ); + h = UI_Text_Height( "0", scale ); strLength = CG_DrawStrlen( s ); totalWidth = w * strLength; - switch( align ) + CG_AlignText( rect, s, 0.0f, totalWidth, h, textalign, textvalign, &tx, &ty ); + + for( i = 0; i < strLength; i++ ) { - case ITEM_ALIGN_LEFT: - tx = rect->x; - break; + char c[ 2 ]; - case ITEM_ALIGN_RIGHT: - tx = rect->x + rect->w - totalWidth; - break; + c[ 0 ] = s[ i ]; + c[ 1 ] = '\0'; - case ITEM_ALIGN_CENTER: - tx = rect->x + ( rect->w / 2.0f ) - ( totalWidth / 2.0f ); - break; + UI_Text_Paint( text_x + tx + i * w, text_y + ty, scale, color, c, 0, 0, textStyle ); + } +} - default: - tx = 0.0f; +/* +================= +CG_DrawTeamOverlay +================= +*/ + +typedef enum +{ + TEAMOVERLAY_OFF, + TEAMOVERLAY_ALL, + TEAMOVERLAY_SUPPORT, + TEAMOVERLAY_NEARBY, +} teamOverlayMode_t; + +typedef enum +{ + TEAMOVERLAY_SORT_NONE, + TEAMOVERLAY_SORT_SCORE, + TEAMOVERLAY_SORT_WEAPONCLASS, +} teamOverlaySort_t; + +static int QDECL SortScore( const void *a, const void *b ) +{ + int na = *(int *)a; + int nb = *(int *)b; + + return( cgs.clientinfo[ nb ].score - cgs.clientinfo[ na ].score ); +} + +static int QDECL SortWeaponClass( const void *a, const void *b ) +{ + int out; + clientInfo_t *ca = cgs.clientinfo + *(int *)a; + clientInfo_t *cb = cgs.clientinfo + *(int *)b; + + out = cb->curWeaponClass - ca->curWeaponClass; + + // We want grangers on top. ckits are already on top without the special case. + if( ca->team == TEAM_ALIENS ) + { + if( ca->curWeaponClass == PCL_ALIEN_BUILDER0_UPG || + cb->curWeaponClass == PCL_ALIEN_BUILDER0_UPG || + ca->curWeaponClass == PCL_ALIEN_BUILDER0 || + cb->curWeaponClass == PCL_ALIEN_BUILDER0 ) + { + out = -out; + } } - for( i = 0; i < strLength; i++ ) + return( out ); +} + +static void CG_DrawTeamOverlay( rectDef_t *rect, float scale, vec4_t color ) +{ + const char *s; + int i; + float x = rect->x; + float y; + clientInfo_t *ci, *pci; + vec4_t tcolor; + float iconSize = rect->h / 8.0f; + float leftMargin = 4.0f; + float iconTopMargin = 2.0f; + float midSep = 2.0f; + float backgroundWidth = rect->w; + float fontScale = 0.30f; + float vPad = 0.0f; + float nameWidth = 0.5f * rect->w; + char name[ MAX_NAME_LENGTH + 2 ]; + int maxDisplayCount = 0; + int displayCount = 0; + float nameMaxX, nameMaxXCp; + float maxX = rect->x + rect->w; + float maxXCp = maxX; + weapon_t curWeapon = WP_NONE; + teamOverlayMode_t mode = cg_drawTeamOverlay.integer; + teamOverlaySort_t sort = cg_teamOverlaySortMode.integer; + int displayClients[ MAX_CLIENTS ]; + + if( cg.predictedPlayerState.pm_type == PM_SPECTATOR ) + return; + + if( mode == TEAMOVERLAY_OFF || !cg_teamOverlayMaxPlayers.integer ) + return; + + if( !cgs.teaminfoReceievedTime ) + return; + + if( cg.showScores || + cg.predictedPlayerState.pm_type == PM_INTERMISSION ) + return; + + pci = cgs.clientinfo + cg.snap->ps.clientNum; + + if( mode == TEAMOVERLAY_ALL || mode == TEAMOVERLAY_SUPPORT ) { - char c[ 2 ]; + for( i = 0; i < MAX_CLIENTS; i++ ) + { + ci = cgs.clientinfo + i; + if( ci->infoValid && pci != ci && ci->team == pci->team ) + { + if( mode == TEAMOVERLAY_ALL ) + displayClients[ maxDisplayCount++ ] = i; + else + { + if( ci->curWeaponClass == PCL_ALIEN_BUILDER0 || + ci->curWeaponClass == PCL_ALIEN_BUILDER0_UPG || + ci->curWeaponClass == PCL_ALIEN_LEVEL1 || + ci->curWeaponClass == PCL_ALIEN_LEVEL1_UPG || + ci->curWeaponClass == WP_HBUILD ) + { + displayClients[ maxDisplayCount++ ] = i; + } + } + } + } + } + else // find nearby + { + for( i = 0; i < cg.snap->numEntities; i++ ) + { + centity_t *cent = &cg_entities[ cg.snap->entities[ i ].number ]; + vec3_t relOrigin = { 0.0f, 0.0f, 0.0f }; + int team = cent->currentState.misc & 0x00FF; - c[ 0 ] = s[ i ]; - c[ 1 ] = '\0'; + if( cent->currentState.eType != ET_PLAYER || + team != pci->team || + cent->currentState.eFlags & EF_DEAD ) + { + continue; + } + + VectorSubtract( cent->lerpOrigin, cg.predictedPlayerState.origin, relOrigin ); + + if( VectorLength( relOrigin ) < HELMET_RANGE ) + displayClients[ maxDisplayCount++ ] = cg.snap->entities[ i ].number; + } + } + + // Sort + if( sort == TEAMOVERLAY_SORT_SCORE ) + { + qsort( displayClients, maxDisplayCount, + sizeof( displayClients[ 0 ] ), SortScore ); + } + else if( sort == TEAMOVERLAY_SORT_WEAPONCLASS ) + { + qsort( displayClients, maxDisplayCount, + sizeof( displayClients[ 0 ] ), SortWeaponClass ); + } + + if( maxDisplayCount > cg_teamOverlayMaxPlayers.integer ) + maxDisplayCount = cg_teamOverlayMaxPlayers.integer; + + iconSize *= scale; + leftMargin *= scale; + iconTopMargin *= scale; + midSep *= scale; + backgroundWidth *= scale; + fontScale *= scale; + nameWidth *= scale; + + vPad = ( rect->h - ( (float) maxDisplayCount * iconSize ) ) / 2.0f; + y = rect->y + vPad; + + tcolor[ 0 ] = 1.0f; + tcolor[ 1 ] = 1.0f; + tcolor[ 2 ] = 1.0f; + tcolor[ 3 ] = color[ 3 ]; - CG_Text_Paint( text_x + tx + i * w, rect->y + text_y, scale, color, c, 0, 0, textStyle ); + for( i = 0; i < MAX_CLIENTS && displayCount < maxDisplayCount; i++ ) + { + ci = cgs.clientinfo + displayClients[ i ]; + + if( !ci->infoValid || pci == ci || ci->team != pci->team ) + continue; + + Com_sprintf( name, sizeof( name ), "%s^7", ci->name ); + + trap_R_SetColor( color ); + CG_DrawPic( x, y, backgroundWidth, + iconSize, cgs.media.teamOverlayShader ); + trap_R_SetColor( tcolor ); + if( ci->health <= 0 || !ci->curWeaponClass ) + s = ""; + else + { + if( ci->team == TEAM_HUMANS ) + curWeapon = ci->curWeaponClass; + else if( ci->team == TEAM_ALIENS ) + curWeapon = BG_Class( ci->curWeaponClass )->startWeapon; + + CG_DrawPic( x + leftMargin, y, iconSize, iconSize, + cg_weapons[ curWeapon ].weaponIcon ); + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_HUMANS ) + { + if( ci->upgrade != UP_NONE ) + { + CG_DrawPic( x + iconSize + leftMargin, y, iconSize, + iconSize, cg_upgrades[ ci->upgrade ].upgradeIcon ); + } + } + else + { + if( curWeapon == WP_ABUILD2 || curWeapon == WP_ALEVEL1_UPG || + curWeapon == WP_ALEVEL2_UPG || curWeapon == WP_ALEVEL3_UPG ) + { + CG_DrawPic( x + iconSize + leftMargin, y, iconSize, + iconSize, cgs.media.upgradeClassIconShader ); + } + } + + s = va( " [^%c%3d^7] ^7%s", + CG_GetColorCharForHealth( displayClients[ i ] ), + ci->health, + CG_ConfigString( CS_LOCATIONS + ci->location ) ); + } + + trap_R_SetColor( NULL ); + nameMaxX = nameMaxXCp = x + 2.0f * iconSize + + leftMargin + midSep + nameWidth; + UI_Text_Paint_Limit( &nameMaxXCp, x + 2.0f * iconSize + leftMargin + midSep, + y + iconSize - iconTopMargin, fontScale, tcolor, name, + 0, 0 ); + + maxXCp = maxX; + + UI_Text_Paint_Limit( &maxXCp, nameMaxX, y + iconSize - iconTopMargin, + fontScale, tcolor, s, 0, 0 ); + y += iconSize; + displayCount++; } } @@ -1902,17 +1913,19 @@ CG_DrawClock ================= */ static void CG_DrawClock( rectDef_t *rect, float text_x, float text_y, - float scale, vec4_t color, int align, int textStyle ) + float scale, vec4_t color, + int textalign, int textvalign, int textStyle ) { - char *s; - int i, tx, w, totalWidth, strLength; + const char *s; + float tx, ty; + int i, strLength; + float w, h, totalWidth; qtime_t qt; - int t; if( !cg_drawClock.integer ) return; - t = trap_RealTime( &qt ); + trap_RealTime( &qt ); if( cg_drawClock.integer == 2 ) { @@ -1936,27 +1949,12 @@ static void CG_DrawClock( rectDef_t *rect, float text_x, float text_y, s = va( "%d%s%02d%s", h, ( qt.tm_sec % 2 ) ? ":" : " ", qt.tm_min, pm ); } - w = CG_Text_Width( "0", scale, 0 ); + w = UI_Text_Width( "0", scale ); + h = UI_Text_Height( "0", scale ); strLength = CG_DrawStrlen( s ); totalWidth = w * strLength; - switch( align ) - { - case ITEM_ALIGN_LEFT: - tx = rect->x; - break; - - case ITEM_ALIGN_RIGHT: - tx = rect->x + rect->w - totalWidth; - break; - - case ITEM_ALIGN_CENTER: - tx = rect->x + ( rect->w / 2.0f ) - ( totalWidth / 2.0f ); - break; - - default: - tx = 0.0f; - } + CG_AlignText( rect, s, 0.0f, totalWidth, h, textalign, textvalign, &tx, &ty ); for( i = 0; i < strLength; i++ ) { @@ -1965,7 +1963,7 @@ static void CG_DrawClock( rectDef_t *rect, float text_x, float text_y, c[ 0 ] = s[ i ]; c[ 1 ] = '\0'; - CG_Text_Paint( text_x + tx + i * w, rect->y + text_y, scale, color, c, 0, 0, textStyle ); + UI_Text_Paint( text_x + tx + i * w, text_y + ty, scale, color, c, 0, 0, textStyle ); } } @@ -1975,39 +1973,90 @@ CG_DrawSnapshot ================== */ static void CG_DrawSnapshot( rectDef_t *rect, float text_x, float text_y, - float scale, vec4_t color, int align, int textStyle ) + float scale, vec4_t color, + int textalign, int textvalign, int textStyle ) { - char *s; - int w, tx; + const char *s; + float tx, ty; if( !cg_drawSnapshot.integer ) return; s = va( "time:%d snap:%d cmd:%d", cg.snap->serverTime, cg.latestSnapshotNum, cgs.serverCommandSequence ); - w = CG_Text_Width( s, scale, 0 ); - switch( align ) - { - case ITEM_ALIGN_LEFT: - tx = rect->x; - break; + CG_AlignText( rect, s, scale, 0.0f, 0.0f, textalign, textvalign, &tx, &ty ); - case ITEM_ALIGN_RIGHT: - tx = rect->x + rect->w - w; - break; + UI_Text_Paint( text_x + tx, text_y + ty, scale, color, s, 0, 0, textStyle ); +} - case ITEM_ALIGN_CENTER: - tx = rect->x + ( rect->w / 2.0f ) - ( w / 2.0f ); - break; +/* +=============================================================================== - default: - tx = 0.0f; - } +KILLL MESSAGE + +=============================================================================== +*/ - CG_Text_Paint( text_x + tx, rect->y + text_y, scale, color, s, 0, 0, textStyle ); +/* +================== +CG_DrawKillMsg +================== +*/ +static void CG_DrawKillMsg( rectDef_t *rect, float text_x, float text_y, + float scale, vec4_t color, + int textalign, int textvalign, int textStyle ) +{ + int i; + vec4_t hcolor; + int chatHeight; + + if (cg_killMsgHeight.integer < TEAMCHAT_HEIGHT) + chatHeight = cg_killMsgHeight.integer; + else + chatHeight = TEAMCHAT_HEIGHT; + + if (chatHeight <= 0) + return; // disabled + + if (cgs.killMsgLastPos != cgs.killMsgPos) + { + if (cg.time - cgs.killMsgMsgTimes[cgs.killMsgLastPos % chatHeight] > cg_killMsgTime.integer) + cgs.killMsgLastPos++; + + hcolor[0] = hcolor[1] = hcolor[2] = hcolor[3] = 1.0f; + + for ( i = cgs.killMsgPos - 1; i >= cgs.killMsgLastPos; i-- ) + { + int x = 0, w; + int j = i % chatHeight; + + w = UI_Text_Width( cgs.killMsgKillers[j], scale ); + UI_Text_Paint( rect->x + TINYCHAR_WIDTH, + rect->y - (cgs.killMsgPos - i)*20, + scale, color, cgs.killMsgKillers[j], + 0, 0, textStyle ); + x += w + 3; + + if ( cg_weapons[cgs.killMsgWeapons[j]].weaponIcon != WP_NONE ) + { + CG_DrawPic( rect->x + TINYCHAR_WIDTH + x, + rect->y - (cgs.killMsgPos - i)*20-15, + 16, 16, + cg_weapons[cgs.killMsgWeapons[j]].weaponIcon ); + x += 16 + 2; + + w = UI_Text_Width( cgs.killMsgVictims[j], scale ); + UI_Text_Paint( rect->x + TINYCHAR_WIDTH + x, + rect->y - (cgs.killMsgPos - i)*20, + scale, color, cgs.killMsgVictims[j], + 0, 0, textStyle ); + } + } + } } + /* =============================================================================== @@ -2080,7 +2129,7 @@ void CG_AddLagometerSnapshotInfo( snapshot_t *snap ) { previousPings[ index++ ] = cg.snap->ping; index = index % PING_FRAMES; - + for( i = 0; i < PING_FRAMES; i++ ) { cg.ping += previousPings[ i ]; @@ -2116,8 +2165,8 @@ static void CG_DrawDisconnect( void ) // also add text in center of screen s = "Connection Interrupted"; - w = CG_Text_Width( s, 0.7f, 0 ); - CG_Text_Paint( 320 - w / 2, 100, 0.7f, color, s, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); + w = UI_Text_Width( s, 0.7f ); + UI_Text_Paint( 320 - w / 2, 100, 0.7f, color, s, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); // blink the icon if( ( cg.time >> 9 ) & 1 ) @@ -2147,7 +2196,7 @@ static void CG_DrawLagometer( rectDef_t *rect, float text_x, float text_y, int color; vec4_t adjustedColor; float vscale; - vec4_t white = { 1.0f, 1.0f, 1.0f, 1.0f }; + const char *ping; if( cg.snap->ps.pm_type == PM_INTERMISSION ) return; @@ -2248,103 +2297,216 @@ static void CG_DrawLagometer( rectDef_t *rect, float text_x, float text_y, } } - v = v * vscale; + v = v * vscale; + + if( v > range ) + v = range; + + trap_R_DrawStretchPic( ax + aw - a, ay + ah - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); + } + else if( v < 0 ) + { + if( color != 4 ) + { + color = 4; // RED for dropped snapshots + trap_R_SetColor( g_color_table[ ColorIndex( COLOR_RED ) ] ); + } + + trap_R_DrawStretchPic( ax + aw - a, ay + ah - range, 1, range, 0, 0, 0, 0, cgs.media.whiteShader ); + } + } + + trap_R_SetColor( NULL ); + + if( cg_nopredict.integer || cg_synchronousClients.integer ) + ping = "snc"; + else + ping = va( "%d", cg.ping ); + ax = rect->x + ( rect->w / 2.0f ) - + ( UI_Text_Width( ping, scale ) / 2.0f ) + text_x; + ay = rect->y + ( rect->h / 2.0f ) + + ( UI_Text_Height( ping, scale ) / 2.0f ) + text_y; + + Vector4Copy( textColor, adjustedColor ); + adjustedColor[ 3 ] = 0.5f; + UI_Text_Paint( ax, ay, scale, adjustedColor, ping, 0, 0, + ITEM_TEXTSTYLE_NORMAL ); + + CG_DrawDisconnect( ); +} + +#define SPEEDOMETER_NUM_SAMPLES 4096 +#define SPEEDOMETER_NUM_DISPLAYED_SAMPLES 160 +#define SPEEDOMETER_DRAW_TEXT 0x1 +#define SPEEDOMETER_DRAW_GRAPH 0x2 +#define SPEEDOMETER_IGNORE_Z 0x4 +float speedSamples[ SPEEDOMETER_NUM_SAMPLES ]; +int speedSampleTimes[ SPEEDOMETER_NUM_SAMPLES ]; +// array indices +int oldestSpeedSample = 0; +int maxSpeedSample = 0; +int maxSpeedSampleInWindow = 0; + +/* +=================== +CG_AddSpeed + +append a speed to the sample history +=================== +*/ +void CG_AddSpeed( void ) +{ + float speed; + vec3_t vel; + int windowTime; + qboolean newSpeedGteMaxSpeed, newSpeedGteMaxSpeedInWindow; + + VectorCopy( cg.snap->ps.velocity, vel ); + + if( cg_drawSpeed.integer & SPEEDOMETER_IGNORE_Z ) + vel[ 2 ] = 0; + + speed = VectorLength( vel ); + + windowTime = cg_maxSpeedTimeWindow.integer; + if( windowTime < 0 ) + windowTime = 0; + else if( windowTime > SPEEDOMETER_NUM_SAMPLES * 1000 ) + windowTime = SPEEDOMETER_NUM_SAMPLES * 1000; + + if( ( newSpeedGteMaxSpeed = ( speed >= speedSamples[ maxSpeedSample ] ) ) ) + maxSpeedSample = oldestSpeedSample; - if( v > range ) - v = range; + if( ( newSpeedGteMaxSpeedInWindow = ( speed >= speedSamples[ maxSpeedSampleInWindow ] ) ) ) + maxSpeedSampleInWindow = oldestSpeedSample; - trap_R_DrawStretchPic( ax + aw - a, ay + ah - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); - } - else if( v < 0 ) - { - if( color != 4 ) - { - color = 4; // RED for dropped snapshots - trap_R_SetColor( g_color_table[ ColorIndex( COLOR_RED ) ] ); - } + speedSamples[ oldestSpeedSample ] = speed; + speedSampleTimes[ oldestSpeedSample ] = cg.time; - trap_R_DrawStretchPic( ax + aw - a, ay + ah - range, 1, range, 0, 0, 0, 0, cgs.media.whiteShader ); + if( !newSpeedGteMaxSpeed && maxSpeedSample == oldestSpeedSample ) + { + // if old max was overwritten find a new one + int i; + for( maxSpeedSample = 0, i = 1; i < SPEEDOMETER_NUM_SAMPLES; i++ ) + { + if( speedSamples[ i ] > speedSamples[ maxSpeedSample ] ) + maxSpeedSample = i; } } - trap_R_SetColor( NULL ); - - if( cg_nopredict.integer || cg_synchronousClients.integer ) - CG_Text_Paint( ax, ay, 0.5, white, "snc", 0, 0, ITEM_TEXTSTYLE_NORMAL ); - else + if( !newSpeedGteMaxSpeedInWindow && ( maxSpeedSampleInWindow == oldestSpeedSample || + cg.time - speedSampleTimes[ maxSpeedSampleInWindow ] > windowTime ) ) { - char *s; - - s = va( "%d", cg.ping ); - ax = rect->x + ( rect->w / 2.0f ) - ( CG_Text_Width( s, scale, 0 ) / 2.0f ) + text_x; - ay = rect->y + ( rect->h / 2.0f ) + ( CG_Text_Height( s, scale, 0 ) / 2.0f ) + text_y; - - Vector4Copy( textColor, adjustedColor ); - adjustedColor[ 3 ] = 0.5f; - CG_Text_Paint( ax, ay, scale, adjustedColor, s, 0, 0, ITEM_TEXTSTYLE_NORMAL ); + int i; + do { + maxSpeedSampleInWindow = ( maxSpeedSampleInWindow + 1 ) % SPEEDOMETER_NUM_SAMPLES; + } while( cg.time - speedSampleTimes[ maxSpeedSampleInWindow ] > windowTime ); + for( i = maxSpeedSampleInWindow; ; i = ( i + 1 ) % SPEEDOMETER_NUM_SAMPLES ) + { + if( speedSamples[ i ] > speedSamples[ maxSpeedSampleInWindow ] ) + maxSpeedSampleInWindow = i; + if( i == oldestSpeedSample ) + break; + } } - CG_DrawDisconnect( ); + oldestSpeedSample = ( oldestSpeedSample + 1 ) % SPEEDOMETER_NUM_SAMPLES; } +#define SPEEDOMETER_MIN_RANGE 900 +#define SPEED_MED 1000.f +#define SPEED_FAST 1600.f + /* -============== -CG_DrawTextBlock -============== +=================== +CG_DrawSpeedGraph +=================== */ -static void CG_DrawTextBlock( rectDef_t *rect, float text_x, float text_y, vec4_t color, - float scale, int align, int textStyle, const char *text, - menuDef_t *parent, itemDef_t *textItem ) +static void CG_DrawSpeedGraph( rectDef_t *rect, vec4_t foreColor, + vec4_t backColor ) { - float x, y, w, h; - - //offset the text - x = rect->x; - y = rect->y; - w = rect->w - ( 16 + ( 2 * text_x ) ); //16 to ensure text within frame - h = rect->h; - - textItem->text = text; + int i; + float val, max, top; + // colour of graph is interpolated between these values + const vec3_t slow = { 0.0, 0.0, 1.0 }; + const vec3_t medium = { 0.0, 1.0, 0.0 }; + const vec3_t fast = { 1.0, 0.0, 0.0 }; + vec4_t color; + + max = speedSamples[ maxSpeedSample ]; + if( max < SPEEDOMETER_MIN_RANGE ) + max = SPEEDOMETER_MIN_RANGE; + + trap_R_SetColor( backColor ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.whiteShader ); - textItem->parent = parent; - memcpy( textItem->window.foreColor, color, sizeof( vec4_t ) ); - textItem->window.flags = 0; + Vector4Copy( foreColor, color ); - switch( align ) + for( i = 1; i < SPEEDOMETER_NUM_DISPLAYED_SAMPLES; i++ ) { - case ITEM_ALIGN_LEFT: - textItem->window.rect.x = x; - break; - - case ITEM_ALIGN_RIGHT: - textItem->window.rect.x = x + w; - break; + val = speedSamples[ ( oldestSpeedSample + i + SPEEDOMETER_NUM_SAMPLES - + SPEEDOMETER_NUM_DISPLAYED_SAMPLES ) % SPEEDOMETER_NUM_SAMPLES ]; + if( val < SPEED_MED ) + VectorLerp2( val / SPEED_MED, slow, medium, color ); + else if( val < SPEED_FAST ) + VectorLerp2( ( val - SPEED_MED ) / ( SPEED_FAST - SPEED_MED ), + medium, fast, color ); + else + VectorCopy( fast, color ); + trap_R_SetColor( color ); + top = rect->y + ( 1 - val / max ) * rect->h; + CG_DrawPic( rect->x + ( i / (float)SPEEDOMETER_NUM_DISPLAYED_SAMPLES ) * rect->w, top, + rect->w / (float)SPEEDOMETER_NUM_DISPLAYED_SAMPLES, val * rect->h / max, + cgs.media.whiteShader ); + } + trap_R_SetColor( NULL ); +} - case ITEM_ALIGN_CENTER: - textItem->window.rect.x = x + ( w / 2 ); - break; +/* +=================== +CG_DrawSpeedText +=================== +*/ +static void CG_DrawSpeedText( rectDef_t *rect, float text_x, float text_y, + float scale, vec4_t foreColor ) +{ + char speedstr[ 16 ]; + float val; + vec4_t color; - default: - textItem->window.rect.x = x; - break; + VectorCopy( foreColor, color ); + color[ 3 ] = 1; + if( cg.predictedPlayerState.clientNum == cg.clientNum ) + { + vec3_t vel; + VectorCopy( cg.predictedPlayerState.velocity, vel ); + if( cg_drawSpeed.integer & SPEEDOMETER_IGNORE_Z ) + vel[ 2 ] = 0; + val = VectorLength( vel ); } + else + val = speedSamples[ ( oldestSpeedSample - 1 + SPEEDOMETER_NUM_SAMPLES ) % SPEEDOMETER_NUM_SAMPLES ]; - textItem->window.rect.y = y; - textItem->window.rect.w = w; - textItem->window.rect.h = h; - textItem->window.borderSize = 0; - textItem->textRect.x = 0; - textItem->textRect.y = 0; - textItem->textRect.w = 0; - textItem->textRect.h = 0; - textItem->textalignment = align; - textItem->textalignx = text_x; - textItem->textaligny = text_y; - textItem->textscale = scale; - textItem->textStyle = textStyle; + Com_sprintf( speedstr, sizeof( speedstr ), "%d / %d", (int)val, (int)speedSamples[ maxSpeedSampleInWindow ] ); - //hack to utilise existing autowrap code - Item_Text_AutoWrapped_Paint( textItem ); + UI_Text_Paint( + rect->x + ( rect->w - UI_Text_Width( speedstr, scale ) ) / 2.0f, + rect->y + ( rect->h + UI_Text_Height( speedstr, scale ) ) / 2.0f, + scale, color, speedstr, 0, 0, ITEM_TEXTSTYLE_NORMAL ); +} + +/* +=================== +CG_DrawSpeed +=================== +*/ +static void CG_DrawSpeed( rectDef_t *rect, float text_x, float text_y, + float scale, vec4_t foreColor, vec4_t backColor ) +{ + if( cg_drawSpeed.integer & SPEEDOMETER_DRAW_GRAPH ) + CG_DrawSpeedGraph( rect, foreColor, backColor ); + if( cg_drawSpeed.integer & SPEEDOMETER_DRAW_TEXT ) + CG_DrawSpeedText( rect, text_x, text_y, scale, foreColor ); } /* @@ -2353,13 +2515,9 @@ CG_DrawConsole =================== */ static void CG_DrawConsole( rectDef_t *rect, float text_x, float text_y, vec4_t color, - float scale, int align, int textStyle ) + float scale, int textalign, int textvalign, int textStyle ) { - static menuDef_t dummyParent; - static itemDef_t textItem; - - CG_DrawTextBlock( rect, text_x, text_y, color, scale, align, textStyle, - cg.consoleText, &dummyParent, &textItem ); + UI_DrawTextBlock( rect, text_x, text_y, color, scale, textalign, textvalign, textStyle, cg.consoleText ); } /* @@ -2368,16 +2526,12 @@ CG_DrawTutorial =================== */ static void CG_DrawTutorial( rectDef_t *rect, float text_x, float text_y, vec4_t color, - float scale, int align, int textStyle ) + float scale, int textalign, int textvalign, int textStyle ) { - static menuDef_t dummyParent; - static itemDef_t textItem; - if( !cg_tutorial.integer ) return; - CG_DrawTextBlock( rect, text_x, text_y, color, scale, align, textStyle, - CG_TutorialText( ), &dummyParent, &textItem ); + UI_DrawTextBlock( rect, text_x, text_y, color, scale, textalign, textvalign, textStyle, CG_TutorialText( ) ); } /* @@ -2387,29 +2541,34 @@ CG_DrawWeaponIcon */ void CG_DrawWeaponIcon( rectDef_t *rect, vec4_t color ) { - int ammo, clips, maxAmmo; - centity_t *cent; + int maxAmmo; playerState_t *ps; + weapon_t weapon; - cent = &cg_entities[ cg.snap->ps.clientNum ]; ps = &cg.snap->ps; + weapon = BG_GetPlayerWeapon( ps ); - ammo = ps->ammo; - clips = ps->clips; - BG_FindAmmoForWeapon( cent->currentState.weapon, &maxAmmo, NULL ); + maxAmmo = BG_Weapon( weapon )->maxAmmo; // don't display if dead if( cg.predictedPlayerState.stats[ STAT_HEALTH ] <= 0 ) return; - if( cent->currentState.weapon == 0 ) + if( weapon <= WP_NONE || weapon >= WP_NUM_WEAPONS ) + { return; + } - CG_RegisterWeapon( cent->currentState.weapon ); + if( !cg_weapons[ weapon ].registered ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: CG_DrawWeaponIcon: weapon %d (%s) " + "is not registered\n", weapon, BG_Weapon( weapon )->name ); + return; + } - if( clips == 0 && !BG_FindInfinteAmmoForWeapon( cent->currentState.weapon ) ) + if( ps->clips == 0 && !BG_Weapon( weapon )->infiniteAmmo ) { - float ammoPercent = (float)ammo / (float)maxAmmo; + float ammoPercent = (float)ps->ammo / (float)maxAmmo; if( ammoPercent < 0.33f ) { @@ -2418,7 +2577,9 @@ void CG_DrawWeaponIcon( rectDef_t *rect, vec4_t color ) } } - if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS && CG_AtHighestClass( ) ) + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_ALIENS && + !BG_AlienCanEvolve( cg.predictedPlayerState.stats[ STAT_CLASS ], + ps->persistant[ PERS_CREDIT ], cgs.alienStage ) ) { if( cg.time - cg.lastEvolveAttempt <= NO_CREDITS_TIME ) { @@ -2428,7 +2589,8 @@ void CG_DrawWeaponIcon( rectDef_t *rect, vec4_t color ) } trap_R_SetColor( color ); - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_weapons[ cent->currentState.weapon ].weaponIcon ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, + cg_weapons[ weapon ].weaponIcon ); trap_R_SetColor( NULL ); } @@ -2443,50 +2605,65 @@ CROSSHAIR */ + /* ================= CG_DrawCrosshair ================= */ -static void CG_DrawCrosshair( void ) +static void CG_DrawCrosshair( rectDef_t *rect, vec4_t color ) { float w, h; qhandle_t hShader; float x, y; weaponInfo_t *wi; + weapon_t weapon; + + weapon = BG_GetPlayerWeapon( &cg.snap->ps ); if( cg_drawCrosshair.integer == CROSSHAIR_ALWAYSOFF ) return; if( cg_drawCrosshair.integer == CROSSHAIR_RANGEDONLY && - !BG_FindLongRangedForWeapon( cg.snap->ps.weapon ) ) - { + !BG_Weapon( weapon )->longRanged ) return; - } - if( ( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) || - ( cg.snap->ps.stats[ STAT_STATE ] & SS_INFESTING ) || - ( cg.snap->ps.stats[ STAT_STATE ] & SS_HOVELING ) ) + if( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) return; if( cg.renderingThirdPerson ) return; - wi = &cg_weapons[ cg.snap->ps.weapon ]; + if( cg.snap->ps.pm_type == PM_INTERMISSION ) + return; + + wi = &cg_weapons[ weapon ]; - w = h = wi->crossHairSize; + w = h = wi->crossHairSize * cg_crosshairSize.value; + w *= cgDC.aspectScale; - x = cg_crosshairX.integer; - y = cg_crosshairY.integer; - CG_AdjustFrom640( &x, &y, &w, &h ); + //FIXME: this still ignores the width/height of the rect, but at least it's + //neater than cg_crosshairX/cg_crosshairY + x = rect->x + ( rect->w / 2 ) - ( w / 2 ); + y = rect->y + ( rect->h / 2 ) - ( h / 2 ); hShader = wi->crossHair; + //aiming at a friendly player/buildable, dim the crosshair + if( cg.time == cg.crosshairClientTime || cg.crosshairBuildable >= 0 ) + { + int i; + for( i = 0; i < 3; i++ ) + color[i] *= .5f; + + } + if( hShader != 0 ) { - trap_R_DrawStretchPic( x + cg.refdef.x + 0.5 * ( cg.refdef.width - w ), - y + cg.refdef.y + 0.5 * ( cg.refdef.height - h ), - w, h, 0, 0, 1, 1, hShader ); + + trap_R_SetColor( color ); + CG_DrawPic( x, y, w, h, hShader ); + trap_R_SetColor( NULL ); } } @@ -2502,7 +2679,7 @@ static void CG_ScanForCrosshairEntity( void ) trace_t trace; vec3_t start, end; int content; - pTeam_t team; + team_t team; VectorCopy( cg.refdef.vieworg, start ); VectorMA( start, 131072, cg.refdef.viewaxis[ 0 ], end ); @@ -2510,20 +2687,29 @@ static void CG_ScanForCrosshairEntity( void ) CG_Trace( &trace, start, vec3_origin, vec3_origin, end, cg.snap->ps.clientNum, CONTENTS_SOLID|CONTENTS_BODY ); - if( trace.entityNum >= MAX_CLIENTS ) - return; - // if the player is in fog, don't show it content = trap_CM_PointContents( trace.endpos, 0 ); if( content & CONTENTS_FOG ) return; + if( trace.entityNum >= MAX_CLIENTS ) + { + entityState_t *s = &cg_entities[ trace.entityNum ].currentState; + if( s->eType == ET_BUILDABLE && BG_Buildable( s->modelindex )->team == + cg.snap->ps.stats[ STAT_TEAM ] ) + cg.crosshairBuildable = trace.entityNum; + else + cg.crosshairBuildable = -1; + + return; + } + team = cgs.clientinfo[ trace.entityNum ].team; - if( cg.snap->ps.persistant[ PERS_TEAM ] != TEAM_SPECTATOR ) + if( cg.snap->ps.stats[ STAT_TEAM ] != TEAM_NONE ) { //only display team names of those on the same team as this player - if( team != cg.snap->ps.stats[ STAT_PTEAM ] ) + if( team != cg.snap->ps.stats[ STAT_TEAM ] ) return; } @@ -2533,6 +2719,44 @@ static void CG_ScanForCrosshairEntity( void ) } +/* +===================== +CG_DrawLocation +===================== +*/ +static void CG_DrawLocation( rectDef_t *rect, float scale, int textalign, vec4_t color ) +{ + const char *location; + centity_t *locent; + float maxX; + float tx = rect->x, ty = rect->y; + + if( cg.intermissionStarted ) + return; + + maxX = rect->x + rect->w; + + locent = CG_GetPlayerLocation( ); + if( locent ) + location = CG_ConfigString( CS_LOCATIONS + locent->currentState.generic1 ); + else + location = CG_ConfigString( CS_LOCATIONS ); + + // need to skip horiz. align if it's too long, but valign must be run either way + if( UI_Text_Width( location, scale ) < rect->w ) + { + CG_AlignText( rect, location, scale, 0.0f, 0.0f, textalign, VALIGN_CENTER, &tx, &ty ); + UI_Text_Paint( tx, ty, scale, color, location, 0, 0, ITEM_TEXTSTYLE_NORMAL ); + } + else + { + CG_AlignText( rect, location, scale, 0.0f, 0.0f, ALIGN_NONE, VALIGN_CENTER, &tx, &ty ); + UI_Text_Paint_Limit( &maxX, tx, ty, scale, color, location, 0, 0 ); + } + + trap_R_SetColor( NULL ); +} + /* ===================== CG_DrawCrosshairNames @@ -2541,7 +2765,7 @@ CG_DrawCrosshairNames static void CG_DrawCrosshairNames( rectDef_t *rect, float scale, int textStyle ) { float *color; - char *name; + const char *name; float w, x; if( !cg_drawCrosshairNames.integer ) @@ -2554,21 +2778,31 @@ static void CG_DrawCrosshairNames( rectDef_t *rect, float scale, int textStyle ) CG_ScanForCrosshairEntity( ); // draw the name of the player being looked at - color = CG_FadeColor( cg.crosshairClientTime, 1000 ); + color = CG_FadeColor( cg.crosshairClientTime, CROSSHAIR_CLIENT_TIMEOUT ); if( !color ) { trap_R_SetColor( NULL ); return; } + // add health from overlay info to the crosshair client name name = cgs.clientinfo[ cg.crosshairClientNum ].name; - w = CG_Text_Width( name, scale, 0 ); - x = rect->x + rect->w / 2; - CG_Text_Paint( x - w / 2, rect->y + rect->h, scale, color, name, 0, 0, textStyle ); + if( cg_teamOverlayUserinfo.integer && + cg.snap->ps.stats[ STAT_TEAM ] != TEAM_NONE && + cgs.teaminfoReceievedTime && + cgs.clientinfo[ cg.crosshairClientNum ].health > 0 ) + { + name = va( "%s ^7[^%c%d^7]", name, + CG_GetColorCharForHealth( cg.crosshairClientNum ), + cgs.clientinfo[ cg.crosshairClientNum ].health ); + } + + w = UI_Text_Width( name, scale ); + x = rect->x + rect->w / 2.0f; + UI_Text_Paint( x - w / 2.0f, rect->y + rect->h, scale, color, name, 0, 0, textStyle ); trap_R_SetColor( NULL ); } - /* =============== CG_OwnerDraw @@ -2578,14 +2812,12 @@ Draw an owner drawn item */ void CG_OwnerDraw( float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, - int align, float special, float scale, vec4_t color, + int align, int textalign, int textvalign, float borderSize, + float scale, vec4_t foreColor, vec4_t backColor, qhandle_t shader, int textStyle ) { rectDef_t rect; - if( cg_drawStatus.integer == 0 ) - return; - rect.x = x; rect.y = y; rect.w = w; @@ -2594,100 +2826,107 @@ void CG_OwnerDraw( float x, float y, float w, float h, float text_x, switch( ownerDraw ) { case CG_PLAYER_CREDITS_VALUE: - CG_DrawPlayerCreditsValue( &rect, color, qtrue ); + CG_DrawPlayerCreditsValue( &rect, foreColor, qtrue ); break; - case CG_PLAYER_BANK_VALUE: - CG_DrawPlayerBankValue( &rect, color, qtrue ); + case CG_PLAYER_CREDITS_FRACTION: + CG_DrawPlayerCreditsFraction( &rect, foreColor, shader ); break; case CG_PLAYER_CREDITS_VALUE_NOPAD: - CG_DrawPlayerCreditsValue( &rect, color, qfalse ); - break; - case CG_PLAYER_BANK_VALUE_NOPAD: - CG_DrawPlayerBankValue( &rect, color, qfalse ); - break; - case CG_PLAYER_STAMINA: - CG_DrawPlayerStamina( &rect, color, scale, align, textStyle, special ); + CG_DrawPlayerCreditsValue( &rect, foreColor, qfalse ); break; case CG_PLAYER_STAMINA_1: - CG_DrawPlayerStamina1( &rect, color, shader ); - break; case CG_PLAYER_STAMINA_2: - CG_DrawPlayerStamina2( &rect, color, shader ); - break; case CG_PLAYER_STAMINA_3: - CG_DrawPlayerStamina3( &rect, color, shader ); - break; case CG_PLAYER_STAMINA_4: - CG_DrawPlayerStamina4( &rect, color, shader ); + CG_DrawPlayerStamina( ownerDraw, &rect, backColor, foreColor, shader ); break; case CG_PLAYER_STAMINA_BOLT: - CG_DrawPlayerStaminaBolt( &rect, color, shader ); + CG_DrawPlayerStaminaBolt( &rect, backColor, foreColor, shader ); break; case CG_PLAYER_AMMO_VALUE: - CG_DrawPlayerAmmoValue( &rect, color ); + CG_DrawPlayerAmmoValue( &rect, foreColor ); break; case CG_PLAYER_CLIPS_VALUE: - CG_DrawPlayerClipsValue( &rect, color ); + CG_DrawPlayerClipsValue( &rect, foreColor ); break; case CG_PLAYER_BUILD_TIMER: - CG_DrawPlayerBuildTimer( &rect, color ); + CG_DrawPlayerBuildTimer( &rect, foreColor ); break; case CG_PLAYER_HEALTH: - CG_DrawPlayerHealthValue( &rect, color ); - break; - case CG_PLAYER_HEALTH_BAR: - CG_DrawPlayerHealthBar( &rect, color, scale, align, textStyle, special ); + CG_DrawPlayerHealthValue( &rect, foreColor ); break; case CG_PLAYER_HEALTH_CROSS: - CG_DrawPlayerHealthCross( &rect, color, shader ); + CG_DrawPlayerHealthCross( &rect, foreColor ); + break; + case CG_PLAYER_CHARGE_BAR_BG: + CG_DrawPlayerChargeBarBG( &rect, foreColor, shader ); + break; + case CG_PLAYER_CHARGE_BAR: + CG_DrawPlayerChargeBar( &rect, foreColor, shader ); break; case CG_PLAYER_CLIPS_RING: - CG_DrawPlayerClipsRing( &rect, color, shader ); + CG_DrawPlayerClipsRing( &rect, backColor, foreColor, shader ); break; case CG_PLAYER_BUILD_TIMER_RING: - CG_DrawPlayerBuildTimerRing( &rect, color, shader ); + CG_DrawPlayerBuildTimerRing( &rect, backColor, foreColor, shader ); break; case CG_PLAYER_WALLCLIMBING: - CG_DrawPlayerWallclimbing( &rect, color, shader ); + CG_DrawPlayerWallclimbing( &rect, backColor, foreColor, shader ); break; case CG_PLAYER_BOOSTED: - CG_DrawPlayerBoosted( &rect, color, shader ); + CG_DrawPlayerBoosted( &rect, backColor, foreColor, shader ); break; case CG_PLAYER_BOOST_BOLT: - CG_DrawPlayerBoosterBolt( &rect, color, shader ); + CG_DrawPlayerBoosterBolt( &rect, backColor, foreColor, shader ); break; case CG_PLAYER_POISON_BARBS: - CG_DrawPlayerPoisonBarbs( &rect, color, shader ); + CG_DrawPlayerPoisonBarbs( &rect, foreColor, shader ); break; case CG_PLAYER_ALIEN_SENSE: CG_DrawAlienSense( &rect ); break; case CG_PLAYER_HUMAN_SCANNER: - CG_DrawHumanScanner( &rect, shader, color ); + CG_DrawHumanScanner( &rect, shader, foreColor ); break; case CG_PLAYER_USABLE_BUILDABLE: - CG_DrawUsableBuildable( &rect, shader, color ); + CG_DrawUsableBuildable( &rect, shader, foreColor ); break; case CG_KILLER: - CG_DrawKiller( &rect, scale, color, shader, textStyle ); + CG_DrawKiller( &rect, scale, foreColor, shader, textStyle ); break; case CG_PLAYER_SELECT: - CG_DrawItemSelect( &rect, color ); + CG_DrawItemSelect( &rect, foreColor ); break; case CG_PLAYER_WEAPONICON: - CG_DrawWeaponIcon( &rect, color ); + CG_DrawWeaponIcon( &rect, foreColor ); break; case CG_PLAYER_SELECTTEXT: CG_DrawItemSelectText( &rect, scale, textStyle ); break; case CG_SPECTATORS: - CG_DrawTeamSpectators( &rect, scale, color, shader ); + CG_DrawTeamSpectators( &rect, scale, textvalign, foreColor, shader ); + break; + case CG_PLAYER_LOCATION: + CG_DrawLocation( &rect, scale, textalign, foreColor ); + break; + case CG_FOLLOW: + CG_DrawFollow( &rect, text_x, text_y, foreColor, scale, + textalign, textvalign, textStyle ); break; case CG_PLAYER_CROSSHAIRNAMES: CG_DrawCrosshairNames( &rect, scale, textStyle ); break; + case CG_PLAYER_CROSSHAIR: + CG_DrawCrosshair( &rect, foreColor ); + break; case CG_STAGE_REPORT_TEXT: - CG_DrawStageReport( &rect, text_x, text_y, color, scale, align, textStyle ); + CG_DrawStageReport( &rect, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle ); + break; + case CG_ALIENS_SCORE_LABEL: + CG_DrawTeamLabel( &rect, TEAM_ALIENS, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle ); + break; + case CG_HUMANS_SCORE_LABEL: + CG_DrawTeamLabel( &rect, TEAM_HUMANS, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle ); break; //loading screen @@ -2695,74 +2934,92 @@ void CG_OwnerDraw( float x, float y, float w, float h, float text_x, CG_DrawLevelShot( &rect ); break; case CG_LOAD_MEDIA: - CG_DrawMediaProgress( &rect, color, scale, align, textStyle, special ); + CG_DrawMediaProgress( &rect, foreColor, scale, align, textalign, textStyle, + borderSize ); break; case CG_LOAD_MEDIA_LABEL: - CG_DrawMediaProgressLabel( &rect, text_x, text_y, color, scale, align ); + CG_DrawMediaProgressLabel( &rect, text_x, text_y, foreColor, scale, textalign, textvalign ); break; case CG_LOAD_BUILDABLES: - CG_DrawBuildablesProgress( &rect, color, scale, align, textStyle, special ); + CG_DrawBuildablesProgress( &rect, foreColor, scale, align, textalign, + textStyle, borderSize ); break; case CG_LOAD_BUILDABLES_LABEL: - CG_DrawBuildablesProgressLabel( &rect, text_x, text_y, color, scale, align ); + CG_DrawBuildablesProgressLabel( &rect, text_x, text_y, foreColor, scale, textalign, textvalign ); break; case CG_LOAD_CHARMODEL: - CG_DrawCharModelProgress( &rect, color, scale, align, textStyle, special ); + CG_DrawCharModelProgress( &rect, foreColor, scale, align, textalign, + textStyle, borderSize ); break; case CG_LOAD_CHARMODEL_LABEL: - CG_DrawCharModelProgressLabel( &rect, text_x, text_y, color, scale, align ); + CG_DrawCharModelProgressLabel( &rect, text_x, text_y, foreColor, scale, textalign, textvalign ); break; case CG_LOAD_OVERALL: - CG_DrawOverallProgress( &rect, color, scale, align, textStyle, special ); + CG_DrawOverallProgress( &rect, foreColor, scale, align, textalign, textStyle, + borderSize ); break; case CG_LOAD_LEVELNAME: - CG_DrawLevelName( &rect, text_x, text_y, color, scale, align, textStyle ); + CG_DrawLevelName( &rect, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle ); break; case CG_LOAD_MOTD: - CG_DrawMOTD( &rect, text_x, text_y, color, scale, align, textStyle ); + CG_DrawMOTD( &rect, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle ); break; case CG_LOAD_HOSTNAME: - CG_DrawHostname( &rect, text_x, text_y, color, scale, align, textStyle ); + CG_DrawHostname( &rect, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle ); break; case CG_FPS: - CG_DrawFPS( &rect, text_x, text_y, scale, color, align, textStyle, qtrue ); + CG_DrawFPS( &rect, text_x, text_y, scale, foreColor, textalign, textvalign, textStyle, qtrue ); break; case CG_FPS_FIXED: - CG_DrawFPS( &rect, text_x, text_y, scale, color, align, textStyle, qfalse ); + CG_DrawFPS( &rect, text_x, text_y, scale, foreColor, textalign, textvalign, textStyle, qfalse ); break; case CG_TIMER: - CG_DrawTimer( &rect, text_x, text_y, scale, color, align, textStyle ); + CG_DrawTimer( &rect, text_x, text_y, scale, foreColor, textalign, textvalign, textStyle ); break; case CG_CLOCK: - CG_DrawClock( &rect, text_x, text_y, scale, color, align, textStyle ); + CG_DrawClock( &rect, text_x, text_y, scale, foreColor, textalign, textvalign, textStyle ); break; case CG_TIMER_MINS: - CG_DrawTimerMins( &rect, color ); + CG_DrawTimerMins( &rect, foreColor ); break; case CG_TIMER_SECS: - CG_DrawTimerSecs( &rect, color ); + CG_DrawTimerSecs( &rect, foreColor ); break; case CG_SNAPSHOT: - CG_DrawSnapshot( &rect, text_x, text_y, scale, color, align, textStyle ); + CG_DrawSnapshot( &rect, text_x, text_y, scale, foreColor, textalign, textvalign, textStyle ); break; case CG_LAGOMETER: - CG_DrawLagometer( &rect, text_x, text_y, scale, color ); + CG_DrawLagometer( &rect, text_x, text_y, scale, foreColor ); + break; + case CG_TEAMOVERLAY: + CG_DrawTeamOverlay( &rect, scale, foreColor ); + break; + case CG_SPEEDOMETER: + CG_DrawSpeed( &rect, text_x, text_y, scale, foreColor, backColor ); break; case CG_DEMO_PLAYBACK: - CG_DrawDemoPlayback( &rect, color, shader ); + CG_DrawDemoPlayback( &rect, foreColor, shader ); break; case CG_DEMO_RECORDING: - CG_DrawDemoRecording( &rect, color, shader ); + CG_DrawDemoRecording( &rect, foreColor, shader ); break; case CG_CONSOLE: - CG_DrawConsole( &rect, text_x, text_y, color, scale, align, textStyle ); + CG_DrawConsole( &rect, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle ); break; case CG_TUTORIAL: - CG_DrawTutorial( &rect, text_x, text_y, color, scale, align, textStyle ); + CG_DrawTutorial( &rect, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle ); + break; + + case CG_KILLFEED: + CG_DrawKillMsg( &rect, text_x, text_y, scale, foreColor, textalign, textvalign, textStyle ); + break; + + case CG_PLAYER_THZ_SCANNER: + THZ_DrawScanner( &rect ); break; default: @@ -2827,17 +3084,17 @@ CG_ShowTeamMenus */ void CG_ShowTeamMenu( void ) { - Menus_OpenByName( "teamMenu" ); + Menus_ActivateByName( "teamMenu" ); } /* ================== CG_EventHandling -================== - type 0 - no event handling - 1 - team menu - 2 - hud editor +type 0 - no event handling + 1 - team menu + 2 - hud editor +================== */ void CG_EventHandling( int type ) { @@ -2891,14 +3148,6 @@ int CG_ClientNumFromName( const char *p ) void CG_RunMenuScript( char **args ) { } - - -void CG_GetTeamColor( vec4_t *color ) -{ - (*color)[ 0 ] = (*color)[ 2 ] = 0.0f; - (*color)[ 1 ] = 0.17f; - (*color)[ 3 ] = 0.25f; -} //END TA UI @@ -2910,13 +3159,9 @@ CG_DrawLighting */ static void CG_DrawLighting( void ) { - centity_t *cent; - - cent = &cg_entities[ cg.snap->ps.clientNum ]; - //fade to black if stamina is low - if( ( cg.snap->ps.stats[ STAT_STAMINA ] < -800 ) && - ( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) + if( ( cg.snap->ps.stats[ STAT_STAMINA ] < STAMINA_BLACKOUT_LEVEL ) && + ( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) ) { vec4_t black = { 0, 0, 0, 0 }; black[ 3 ] = 1.0 - ( (float)( cg.snap->ps.stats[ STAT_STAMINA ] + 1000 ) / 200.0f ); @@ -2946,8 +3191,15 @@ for a few moments void CG_CenterPrint( const char *str, int y, int charWidth ) { char *s; + char newlineParsed[ MAX_STRING_CHARS ]; + const char *wrapped; + static int maxWidth = (int)( ( 2.0f / 3.0f ) * (float)SCREEN_WIDTH ); + + Q_ParseNewlines( newlineParsed, str, sizeof( newlineParsed ) ); - Q_strncpyz( cg.centerPrint, str, sizeof( cg.centerPrint ) ); + wrapped = Item_Text_Wrap( newlineParsed, 0.5f, maxWidth ); + + Q_strncpyz( cg.centerPrint, wrapped, sizeof( cg.centerPrint ) ); cg.centerPrintTime = cg.time; cg.centerPrintY = y; @@ -2994,9 +3246,9 @@ static void CG_DrawCenterString( void ) while( 1 ) { - char linebuffer[ 1024 ]; + char linebuffer[ MAX_STRING_CHARS ]; - for( l = 0; l < 50; l++ ) + for( l = 0; l < sizeof(linebuffer) - 1; l++ ) { if( !start[ l ] || start[ l ] == '\n' ) break; @@ -3006,10 +3258,10 @@ static void CG_DrawCenterString( void ) linebuffer[ l ] = 0; - w = CG_Text_Width( linebuffer, 0.5, 0 ); - h = CG_Text_Height( linebuffer, 0.5, 0 ); + w = UI_Text_Width( linebuffer, 0.5 ); + h = UI_Text_Height( linebuffer, 0.5 ); x = ( SCREEN_WIDTH - w ) / 2; - CG_Text_Paint( x, y + h, 0.5, color, linebuffer, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE ); + UI_Text_Paint( x, y + h, 0.5, color, linebuffer, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE ); y += h + 6; while( *start && ( *start != '\n' ) ) @@ -3037,83 +3289,62 @@ static void CG_DrawCenterString( void ) CG_DrawVote ================= */ -static void CG_DrawVote( void ) +static void CG_DrawVote( team_t team ) { - char *s; + const char *s; int sec; + int offset = 0; vec4_t white = { 1.0f, 1.0f, 1.0f, 1.0f }; - char yeskey[ 32 ], nokey[ 32 ]; + char yeskey[ 32 ] = "", nokey[ 32 ] = ""; - if( !cgs.voteTime ) + if( !cgs.voteTime[ team ] ) return; // play a talk beep whenever it is modified - if( cgs.voteModified ) + if( cgs.voteModified[ team ] ) { - cgs.voteModified = qfalse; + cgs.voteModified[ team ] = qfalse; trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); } - sec = ( VOTE_TIME - ( cg.time - cgs.voteTime ) ) / 1000; + sec = ( VOTE_TIME - ( cg.time - cgs.voteTime[ team ] ) ) / 1000; if( sec < 0 ) sec = 0; - Q_strncpyz( yeskey, CG_KeyBinding( "vote yes" ), sizeof( yeskey ) ); - Q_strncpyz( nokey, CG_KeyBinding( "vote no" ), sizeof( nokey ) ); - s = va( "VOTE(%i): \"%s\" [%s]Yes:%i [%s]No:%i", sec, cgs.voteString, - yeskey, cgs.voteYes, nokey, cgs.voteNo ); - CG_Text_Paint( 8, 340, 0.3f, white, s, 0, 0, ITEM_TEXTSTYLE_NORMAL ); -} -/* -================= -CG_DrawTeamVote -================= -*/ -static void CG_DrawTeamVote( void ) -{ - char *s; - int sec, cs_offset; - vec4_t white = { 1.0f, 1.0f, 1.0f, 1.0f }; - char yeskey[ 32 ], nokey[ 32 ]; + if( cg_tutorial.integer ) + { + Com_sprintf( yeskey, sizeof( yeskey ), "[%s]", + CG_KeyBinding( va( "%svote yes", team == TEAM_NONE ? "" : "team" ) ) ); + Com_sprintf( nokey, sizeof( nokey ), "[%s]", + CG_KeyBinding( va( "%svote no", team == TEAM_NONE ? "" : "team" ) ) ); + } - if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_HUMANS ) - cs_offset = 0; - else if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS ) - cs_offset = 1; - else - return; + if( team != TEAM_NONE ) + offset = 80; - if( !cgs.teamVoteTime[ cs_offset ] ) - return; + s = va( "%sVOTE(%i): %s", + team == TEAM_NONE ? "" : "TEAM", sec, cgs.voteString[ team ] ); - // play a talk beep whenever it is modified - if ( cgs.teamVoteModified[ cs_offset ] ) - { - cgs.teamVoteModified[ cs_offset ] = qfalse; - trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); - } + UI_Text_Paint( 8, 300 + offset, 0.3f, white, s, 0, 0, + ITEM_TEXTSTYLE_NORMAL ); - sec = ( VOTE_TIME - ( cg.time - cgs.teamVoteTime[ cs_offset ] ) ) / 1000; + s = va( " Called by: \"%s\"", cgs.voteCaller[ team ] ); - if( sec < 0 ) - sec = 0; + UI_Text_Paint( 8, 320 + offset, 0.3f, white, s, 0, 0, + ITEM_TEXTSTYLE_NORMAL ); - Q_strncpyz( yeskey, CG_KeyBinding( "teamvote yes" ), sizeof( yeskey ) ); - Q_strncpyz( nokey, CG_KeyBinding( "teamvote no" ), sizeof( nokey ) ); - s = va( "TEAMVOTE(%i): \"%s\" [%s]Yes:%i [%s]No:%i", sec, - cgs.teamVoteString[ cs_offset ], - yeskey, cgs.teamVoteYes[cs_offset], - nokey, cgs.teamVoteNo[ cs_offset ] ); + s = va( " %sYes:%i %sNo:%i", + yeskey, cgs.voteYes[ team ], nokey, cgs.voteNo[ team ] ); - CG_Text_Paint( 8, 360, 0.3f, white, s, 0, 0, ITEM_TEXTSTYLE_NORMAL ); + UI_Text_Paint( 8, 340 + offset, 0.3f, white, s, 0, 0, + ITEM_TEXTSTYLE_NORMAL ); } static qboolean CG_DrawScoreboard( void ) { static qboolean firstTime = qtrue; - float fade, *fadeColor; if( menuScoreboard ) menuScoreboard->window.flags &= ~WINDOW_FORCED; @@ -3125,13 +3356,8 @@ static qboolean CG_DrawScoreboard( void ) return qfalse; } - if( cg.showScores || - cg.predictedPlayerState.pm_type == PM_INTERMISSION ) - { - fade = 1.0; - fadeColor = colorWhite; - } - else + if( !cg.showScores && + cg.predictedPlayerState.pm_type != PM_INTERMISSION ) { cg.deferredPlayerLoading = 0; cg.killerName[ 0 ] = 0; @@ -3139,6 +3365,7 @@ static qboolean CG_DrawScoreboard( void ) return qfalse; } + CG_RequestScores( ); if( menuScoreboard == NULL ) menuScoreboard = Menus_FindByName( "teamscore_menu" ); @@ -3147,10 +3374,12 @@ static qboolean CG_DrawScoreboard( void ) { if( firstTime ) { + cg.spectatorTime = trap_Milliseconds(); CG_SetScoreSelection( menuScoreboard ); firstTime = qfalse; } + Menu_Update( menuScoreboard ); Menu_Paint( menuScoreboard, qtrue ); } @@ -3164,27 +3393,28 @@ CG_DrawIntermission */ static void CG_DrawIntermission( void ) { - if( cg_drawStatus.integer ) - Menu_Paint( Menus_FindByName( "default_hud" ), qtrue ); + menuDef_t *menu = Menus_FindByName( "default_hud" ); + + Menu_Update( menu ); + Menu_Paint( menu, qtrue ); cg.scoreFadeTime = cg.time; cg.scoreBoardShowing = CG_DrawScoreboard( ); } -#define FOLLOWING_STRING "following " - /* ================= -CG_DrawFollow +CG_DrawQueue ================= */ -static qboolean CG_DrawFollow( void ) +static qboolean CG_DrawQueue( void ) { float w; vec4_t color; - char buffer[ MAX_STRING_CHARS ]; + int position; + char *ordinal, buffer[ MAX_STRING_CHARS ]; - if( !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) + if( !( cg.snap->ps.pm_flags & PMF_QUEUED ) ) return qfalse; color[ 0 ] = 1; @@ -3192,66 +3422,82 @@ static qboolean CG_DrawFollow( void ) color[ 2 ] = 1; color[ 3 ] = 1; - strcpy( buffer, FOLLOWING_STRING ); - strcat( buffer, cgs.clientinfo[ cg.snap->ps.clientNum ].name ); + position = cg.snap->ps.persistant[ PERS_QUEUEPOS ] + 1; + if( position < 1 ) + return qfalse; + + switch( position % 100 ) + { + case 11: + case 12: + case 13: + ordinal = "th"; + break; + default: + switch( position % 10 ) + { + case 1: ordinal = "st"; break; + case 2: ordinal = "nd"; break; + case 3: ordinal = "rd"; break; + default: ordinal = "th"; break; + } + break; + } + + Com_sprintf( buffer, MAX_STRING_CHARS, "You are %d%s in the spawn queue", + position, ordinal ); + + w = UI_Text_Width( buffer, 0.7f ); + UI_Text_Paint( 320 - w / 2, 360, 0.7f, color, buffer, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); + + if( cg.snap->ps.persistant[ PERS_SPAWNS ] == 0 ) + Com_sprintf( buffer, MAX_STRING_CHARS, "There are no spawns remaining" ); + else if( cg.snap->ps.persistant[ PERS_SPAWNS ] == 1 ) + Com_sprintf( buffer, MAX_STRING_CHARS, "There is 1 spawn remaining" ); + else + Com_sprintf( buffer, MAX_STRING_CHARS, "There are %d spawns remaining", + cg.snap->ps.persistant[ PERS_SPAWNS ] ); - w = CG_Text_Width( buffer, 0.7f, 0 ); - CG_Text_Paint( 320 - w / 2, 400, 0.7f, color, buffer, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); + w = UI_Text_Width( buffer, 0.7f ); + UI_Text_Paint( 320 - w / 2, 400, 0.7f, color, buffer, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); return qtrue; } + /* ================= -CG_DrawQueue +CG_DrawWarmup ================= */ -static qboolean CG_DrawQueue( void ) +static void CG_DrawWarmup( void ) { - float w; - vec4_t color; - char buffer[ MAX_STRING_CHARS ]; - - if( !( cg.snap->ps.pm_flags & PMF_QUEUED ) ) - return qfalse; + int sec = 0; + int w; + int h; + float size = 0.5f; + char text[ MAX_STRING_CHARS ] = "Warmup Time:"; - color[ 0 ] = 1; - color[ 1 ] = 1; - color[ 2 ] = 1; - color[ 3 ] = 1; + if( !cg.warmupTime ) + return; - Com_sprintf( buffer, MAX_STRING_CHARS, "You are in position %d of the spawn queue.", - cg.snap->ps.persistant[ PERS_QUEUEPOS ] + 1 ); + sec = ( cg.warmupTime - cg.time ) / 1000; - w = CG_Text_Width( buffer, 0.7f, 0 ); - CG_Text_Paint( 320 - w / 2, 360, 0.7f, color, buffer, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); + if( sec < 0 ) + return; - if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) - { - if( cgs.numAlienSpawns == 1 ) - Com_sprintf( buffer, MAX_STRING_CHARS, "There is 1 spawn remaining." ); - else - Com_sprintf( buffer, MAX_STRING_CHARS, "There are %d spawns remaining.", - cgs.numAlienSpawns ); - } - else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) - { - if( cgs.numHumanSpawns == 1 ) - Com_sprintf( buffer, MAX_STRING_CHARS, "There is 1 spawn remaining." ); - else - Com_sprintf( buffer, MAX_STRING_CHARS, "There are %d spawns remaining.", - cgs.numHumanSpawns ); - } + w = UI_Text_Width( text, size ); + h = UI_Text_Height( text, size ); + UI_Text_Paint( 320 - w / 2, 200, size, colorWhite, text, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); - w = CG_Text_Width( buffer, 0.7f, 0 ); - CG_Text_Paint( 320 - w / 2, 400, 0.7f, color, buffer, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); + Com_sprintf( text, sizeof( text ), "%s", sec ? va( "%d", sec ) : "FIGHT!" ); - return qtrue; + w = UI_Text_Width( text, size ); + UI_Text_Paint( 320 - w / 2, 200 + 1.5f * h, size, colorWhite, text, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); } //================================================================================== -#define SPECTATOR_STRING "SPECTATOR" /* ================= CG_Draw2D @@ -3259,15 +3505,11 @@ CG_Draw2D */ static void CG_Draw2D( void ) { - vec4_t color; - float w; - menuDef_t *menu = NULL, *defaultMenu; + menuDef_t *menu = NULL; - color[ 0 ] = color[ 1 ] = color[ 2 ] = color[ 3 ] = 1.0f; - - // if we are taking a levelshot for the menu, don't draw anything - if( cg.levelShot ) - return; + // fading to black if stamina runs out + // (only 2D that can't be disabled) + CG_DrawLighting( ); if( cg_draw2D.integer == 0 ) return; @@ -3278,36 +3520,30 @@ static void CG_Draw2D( void ) return; } - //TA: draw the lighting effects e.g. nvg - CG_DrawLighting( ); - - - defaultMenu = Menus_FindByName( "default_hud" ); - - if( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) + if ( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_NONE + || (cg.snap->ps.persistant[ PERS_SPECSTATE ] == SPECTATOR_NOT + && cg.snap->ps.stats[ STAT_HEALTH ] > 0 )) { - w = CG_Text_Width( SPECTATOR_STRING, 0.7f, 0 ); - CG_Text_Paint( 320 - w / 2, 440, 0.7f, color, SPECTATOR_STRING, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); + menu = Menus_FindByName( BG_ClassConfig( + cg.predictedPlayerState.stats[ STAT_CLASS ] )->hudName ); + + CG_DrawBuildableStatus( ); } - else - menu = Menus_FindByName( BG_FindHudNameForClass( cg.predictedPlayerState.stats[ STAT_PCLASS ] ) ); - if( !( cg.snap->ps.stats[ STAT_STATE ] & SS_INFESTING ) && - !( cg.snap->ps.stats[ STAT_STATE ] & SS_HOVELING ) && menu && - ( cg.snap->ps.stats[ STAT_HEALTH ] > 0 ) ) + if( !menu ) { - CG_DrawBuildableStatus( ); - if( cg_drawStatus.integer ) - Menu_Paint( menu, qtrue ); + menu = Menus_FindByName( "default_hud" ); - CG_DrawCrosshair( ); + if( !menu ) // still couldn't find it + CG_Error( "Default HUD could not be found" ); } - else if( cg_drawStatus.integer ) - Menu_Paint( defaultMenu, qtrue ); - CG_DrawVote( ); - CG_DrawTeamVote( ); - CG_DrawFollow( ); + Menu_Update( menu ); + Menu_Paint( menu, qtrue ); + + CG_DrawVote( TEAM_NONE ); + CG_DrawVote( cg.predictedPlayerState.stats[ STAT_TEAM ] ); + CG_DrawWarmup( ); CG_DrawQueue( ); // don't draw center string if scoreboard is up @@ -3356,7 +3592,7 @@ static void CG_PainBlend( void ) float x, y, w, h; float s1, t1, s2, t2; - if( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR || cg.intermissionStarted ) + if( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT || cg.intermissionStarted ) return; damage = cg.lastHealth - cg.snap->ps.stats[ STAT_HEALTH ]; @@ -3383,9 +3619,9 @@ static void CG_PainBlend( void ) return; } - if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) VectorSet( color, 0.43f, 0.8f, 0.37f ); - else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + else if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) VectorSet( color, 0.8f, 0.0f, 0.0f ); if( cg.painBlendValue > cg.painBlendTarget ) @@ -3454,6 +3690,66 @@ void CG_ResetPainBlend( void ) cg.lastHealth = cg.snap->ps.stats[ STAT_HEALTH ]; } +/* +================ +CG_DrawBinaryShadersFinalPhases +================ +*/ +static void CG_DrawBinaryShadersFinalPhases( void ) +{ + float ss, f, l, u; + polyVert_t verts[ 4 ] = { + { { 0, 0, 0 }, { 0, 0 }, { 255, 255, 255, 255 } }, + { { 0, 0, 0 }, { 1, 0 }, { 255, 255, 255, 255 } }, + { { 0, 0, 0 }, { 1, 1 }, { 255, 255, 255, 255 } }, + { { 0, 0, 0 }, { 0, 1 }, { 255, 255, 255, 255 } } + }; + int i, j, k; + + if( !cg.numBinaryShadersUsed ) + return; + + ss = cg_binaryShaderScreenScale.value; + if( ss <= 0.0f ) + { + cg.numBinaryShadersUsed = 0; + return; + } + else if( ss > 1.0f ) + ss = 1.0f; + + ss = sqrt( ss ); + + f = 1.01f; // FIXME: is this a good choice to avoid near-clipping? + l = f * tan( DEG2RAD( cg.refdef.fov_x / 2 ) ) * ss; + u = f * tan( DEG2RAD( cg.refdef.fov_y / 2 ) ) * ss; + + VectorMA( cg.refdef.vieworg, f, cg.refdef.viewaxis[ 0 ], verts[ 0 ].xyz ); + VectorMA( verts[ 0 ].xyz, l, cg.refdef.viewaxis[ 1 ], verts[ 0 ].xyz ); + VectorMA( verts[ 0 ].xyz, u, cg.refdef.viewaxis[ 2 ], verts[ 0 ].xyz ); + VectorMA( verts[ 0 ].xyz, -2*l, cg.refdef.viewaxis[ 1 ], verts[ 1 ].xyz ); + VectorMA( verts[ 1 ].xyz, -2*u, cg.refdef.viewaxis[ 2 ], verts[ 2 ].xyz ); + VectorMA( verts[ 0 ].xyz, -2*u, cg.refdef.viewaxis[ 2 ], verts[ 3 ].xyz ); + + trap_R_AddPolyToScene( cgs.media.binaryAlpha1Shader, 4, verts ); + + for( i = 0; i < cg.numBinaryShadersUsed; ++i ) + { + for( j = 0; j < 4; ++j ) + { + for( k = 0; k < 3; ++k ) + verts[ j ].modulate[ k ] = cg.binaryShaderSettings[ i ].color[ k ]; + } + + if( cg.binaryShaderSettings[ i ].drawFrontline ) + trap_R_AddPolyToScene( cgs.media.binaryShaders[ i ].f3, 4, verts ); + if( cg.binaryShaderSettings[ i ].drawIntersection ) + trap_R_AddPolyToScene( cgs.media.binaryShaders[ i ].b3, 4, verts ); + } + + cg.numBinaryShadersUsed = 0; +} + /* ===================== CG_DrawActive @@ -3496,6 +3792,8 @@ void CG_DrawActive( stereoFrame_t stereoView ) VectorMA( cg.refdef.vieworg, -separation, cg.refdef.viewaxis[ 1 ], cg.refdef.vieworg ); + CG_DrawBinaryShadersFinalPhases( ); + // draw 3D view trap_R_RenderScene( &cg.refdef ); @@ -3510,6 +3808,3 @@ void CG_DrawActive( stereoFrame_t stereoView ) // draw status bar and other floating elements CG_Draw2D( ); } - - - diff --git a/src/cgame/cg_drawtools.c b/src/cgame/cg_drawtools.c index 06ae071..cc8533a 100644 --- a/src/cgame/cg_drawtools.c +++ b/src/cgame/cg_drawtools.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,14 +17,13 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_drawtools.c -- helper functions called by cg_draw, cg_scoreboard, cg_info, etc - #include "cg_local.h" /* @@ -126,10 +126,14 @@ Coords are virtual 640x480 */ void CG_DrawSides( float x, float y, float w, float h, float size ) { + float sizeY; + CG_AdjustFrom640( &x, &y, &w, &h ); + sizeY = size * cgs.screenYScale; size *= cgs.screenXScale; - trap_R_DrawStretchPic( x, y, size, h, 0, 0, 0, 0, cgs.media.whiteShader ); - trap_R_DrawStretchPic( x + w - size, y, size, h, 0, 0, 0, 0, cgs.media.whiteShader ); + + trap_R_DrawStretchPic( x, y + sizeY, size, h - ( sizeY * 2.0f ), 0, 0, 0, 0, cgs.media.whiteShader ); + trap_R_DrawStretchPic( x + w - size, y + sizeY, size, h - ( sizeY * 2.0f ), 0, 0, 0, 0, cgs.media.whiteShader ); } void CG_DrawTopBottom( float x, float y, float w, float h, float size ) @@ -172,7 +176,34 @@ void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); } +/* +================ +CG_SetClipRegion +================= +*/ +void CG_SetClipRegion( float x, float y, float w, float h ) +{ + vec4_t clip; + + CG_AdjustFrom640( &x, &y, &w, &h ); + clip[ 0 ] = x; + clip[ 1 ] = y; + clip[ 2 ] = x + w; + clip[ 3 ] = y + h; + + trap_R_SetClipRegion( clip ); +} + +/* +================ +CG_ClearClipRegion +================= +*/ +void CG_ClearClipRegion( void ) +{ + trap_R_SetClipRegion( NULL ); +} /* ================ @@ -321,30 +352,30 @@ CG_WorldToScreen */ qboolean CG_WorldToScreen( vec3_t point, float *x, float *y ) { - vec3_t trans; - float xc, yc; - float px, py; - float z; + vec3_t trans; + float xc, yc; + float px, py; + float z; - px = tan( cg.refdef.fov_x * M_PI / 360.0 ); - py = tan( cg.refdef.fov_y * M_PI / 360.0 ); + px = tan( cg.refdef.fov_x * M_PI / 360.0f ); + py = tan( cg.refdef.fov_y * M_PI / 360.0f ); - VectorSubtract( point, cg.refdef.vieworg, trans ); + VectorSubtract( point, cg.refdef.vieworg, trans ); - xc = 640.0f / 2.0f; - yc = 480.0f / 2.0f; + xc = ( 640.0f * cg_viewsize.integer ) / 200.0f; + yc = ( 480.0f * cg_viewsize.integer ) / 200.0f; - z = DotProduct( trans, cg.refdef.viewaxis[ 0 ] ); - if( z <= 0.001f ) - return qfalse; + z = DotProduct( trans, cg.refdef.viewaxis[ 0 ] ); + if( z <= 0.001f ) + return qfalse; if( x ) - *x = xc - DotProduct( trans, cg.refdef.viewaxis[ 1 ] ) * xc / ( z * px ); + *x = 320.0f - DotProduct( trans, cg.refdef.viewaxis[ 1 ] ) * xc / ( z * px ); if( y ) - *y = yc - DotProduct( trans, cg.refdef.viewaxis[ 2 ] ) * yc / ( z * py ); + *y = 240.0f - DotProduct( trans, cg.refdef.viewaxis[ 2 ] ) * yc / ( z * py ); - return qtrue; + return qtrue; } /* @@ -359,6 +390,7 @@ char *CG_KeyBinding( const char *bind ) int i; key[ 0 ] = '\0'; + // NOTE: change K_LAST_KEY to MAX_KEYS for full key support (eventually) for( i = 0; i < K_LAST_KEY; i++ ) { @@ -367,12 +399,41 @@ char *CG_KeyBinding( const char *bind ) { trap_Key_KeynumToStringBuf( i, key, sizeof( key ) ); break; - } + } } + if( !key[ 0 ] ) { Q_strncpyz( key, "\\", sizeof( key ) ); Q_strcat( key, sizeof( key ), bind ); } + return key; } + +/* +================= +CG_GetColorCharForHealth +================= +*/ +char CG_GetColorCharForHealth( int clientnum ) +{ + char health_char = '2'; + int healthPercent; + int maxHealth; + int curWeaponClass = cgs.clientinfo[ clientnum ].curWeaponClass; + + if( cgs.clientinfo[ clientnum ].team == TEAM_ALIENS ) + maxHealth = BG_Class( curWeaponClass )->health; + else + maxHealth = BG_Class( PCL_HUMAN )->health; + + healthPercent = (int) ( 100.0f * (float) cgs.clientinfo[ clientnum ].health / + (float) maxHealth ); + + if( healthPercent < 33 ) + health_char = '1'; + else if( healthPercent < 67 ) + health_char = '3'; + return health_char; +} diff --git a/src/cgame/cg_ents.c b/src/cgame/cg_ents.c index 17f1a7d..4008e56 100644 --- a/src/cgame/cg_ents.c +++ b/src/cgame/cg_ents.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,14 +17,13 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_ents.c -- present snapshot entities, happens every single frame - #include "cg_local.h" /* @@ -251,7 +251,7 @@ static void CG_EntityEffects( centity_t *cent ) if( CG_IsTrailSystemValid( ¢->muzzleTS ) ) { - //FIXME hack to prevent tesla trails reaching too far + //FIXME hack to prevent tesla trails reaching too far if( cent->currentState.eType == ET_BUILDABLE ) { vec3_t front, back; @@ -309,6 +309,136 @@ static void CG_General( centity_t *cent ) trap_R_AddRefEntityToScene( &ent ); } +/* +================== +CG_WeaponDrop +================== +*/ +static void CG_WeaponDrop( centity_t *cent ) +{ + refEntity_t ent; + entityState_t *es; + int msec; + float frac; + float scale; + weaponInfo_t *wi; + + es = ¢->currentState; + + if (BG_Weapon(es->modelindex) == WP_NONE) + { + CG_Printf("Bad weapon index %i on entity", es->modelindex); + return; + } + + // if set to invisible, skip + if (es->eFlags & EF_NODRAW) + return; + + + // items bob up and down continuously + scale = 0.005 + cent->currentState.number * 0.00001; + cent->lerpOrigin[2] += 4 + cos( ( cg.time + 1000 ) * scale ) * 4; + + memset (&ent, 0, sizeof(ent)); + + // autorotate at one of two speeds + VectorCopy( cg.autoAnglesFast, cent->lerpAngles ); + AxisCopy( cg.autoAxisFast, ent.axis ); +// VectorCopy( cg.autoAngles, cent->lerpAngles ); +// AxisCopy( cg.autoAxis, ent.axis ); + + wi = &cg_weapons[ es->modelindex ]; + + // the weapons have their origin where they attatch to player + // models, so we need to offset them or they will rotate + // eccentricly + + cent->lerpOrigin[0] -= wi->weaponMidpoint[0] * ent.axis[0][0] + + wi->weaponMidpoint[1] * ent.axis[1][0] + + wi->weaponMidpoint[2] * ent.axis[2][0]; + cent->lerpOrigin[1] -= wi->weaponMidpoint[0] * ent.axis[0][1] + + wi->weaponMidpoint[1] * ent.axis[1][1] + + wi->weaponMidpoint[2] * ent.axis[2][1]; + cent->lerpOrigin[2] -= wi->weaponMidpoint[0] * ent.axis[0][2] + + wi->weaponMidpoint[1] * ent.axis[1][2] + + wi->weaponMidpoint[2] * ent.axis[2][2]; + cent->lerpOrigin[2] += 8; // an extra height boost + +#if 0 + if( item->giType == IT_WEAPON && item->giTag == WP_RAILGUN ) { + clientInfo_t *ci = &cgs.clientinfo[cg.snap->ps.clientNum]; + Byte4Copy( ci->c1RGBA, ent.shaderRGBA ); + } +#endif + + ent.hModel = wi->weaponModel; + + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + ent.nonNormalizedAxes = qfalse; + + // if just respawned, slowly scale up + msec = cg.time - cent->miscTime; + if ( msec >= 0 && msec < ITEM_SCALEUP_TIME ) + { + frac = (float)msec / ITEM_SCALEUP_TIME; + VectorScale( ent.axis[0], frac, ent.axis[0] ); + VectorScale( ent.axis[1], frac, ent.axis[1] ); + VectorScale( ent.axis[2], frac, ent.axis[2] ); + ent.nonNormalizedAxes = qtrue; + } + else + { + frac = 1.0; + } + + // items without glow textures need to keep a minimum light value + // so they are always visible + ent.renderfx |= RF_MINLIGHT; + + // increase the size of the weapons when they are presented as items + VectorScale( ent.axis[0], 1.5, ent.axis[0] ); + VectorScale( ent.axis[1], 1.5, ent.axis[1] ); + VectorScale( ent.axis[2], 1.5, ent.axis[2] ); + ent.nonNormalizedAxes = qtrue; +#ifdef MISSIONPACK + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.weaponHoverSound ); +#endif + + // add to refresh list + trap_R_AddRefEntityToScene(&ent); + +#if 0 + if ( item->giType == IT_WEAPON && wi && wi->barrelModel ) + { + refEntity_t barrel; + vec3_t angles; + + memset( &barrel, 0, sizeof( barrel ) ); + + barrel.hModel = wi->barrelModel; + + VectorCopy( ent.lightingOrigin, barrel.lightingOrigin ); + barrel.shadowPlane = ent.shadowPlane; + barrel.renderfx = ent.renderfx; + + angles[YAW] = 0; + angles[PITCH] = 0; + angles[ROLL] = 0; + AnglesToAxis( angles, barrel.axis ); + + CG_PositionRotatedEntityOnTag( &barrel, &ent, wi->weaponModel, "tag_barrel" ); + + barrel.nonNormalizedAxes = ent.nonNormalizedAxes; + + trap_R_AddRefEntityToScene( &barrel ); + } +#endif +} + + /* ================== CG_Speaker @@ -367,6 +497,7 @@ static void CG_LaunchMissile( centity_t *cent ) { CG_SetAttachmentCent( &ps->attachment, cent ); CG_AttachToCent( &ps->attachment ); + ps->charge = es->torsoAnim; } } @@ -407,9 +538,6 @@ static void CG_Missile( centity_t *cent ) wim = &wi->wim[ weaponMode ]; - // calculate the axis - VectorCopy( es->angles, cent->lerpAngles ); - // add dynamic light if( wim->missileDlight ) { @@ -437,7 +565,8 @@ static void CG_Missile( centity_t *cent ) if( wim->usesSpriteMissle ) { ent.reType = RT_SPRITE; - ent.radius = wim->missileSpriteSize; + ent.radius = wim->missileSpriteSize + + wim->missileSpriteCharge * es->torsoAnim; ent.rotation = 0; ent.customShader = wim->missileSprite; ent.shaderRGBA[ 0 ] = 0xFF; @@ -498,6 +627,9 @@ static void CG_Mover( centity_t *cent ) s1 = ¢->currentState; + if( !s1->modelindex ) + return; + // create the render entity memset( &ent, 0, sizeof( ent ) ); VectorCopy( cent->lerpOrigin, ent.origin ); @@ -653,7 +785,7 @@ static void CG_LightFlare( centity_t *cent ) flare.renderfx |= RF_DEPTHHACK; //bunch of geometry - AngleVectors( es->angles, forward, NULL, NULL ); + AngleVectors( cent->lerpAngles, forward, NULL, NULL ); VectorCopy( cent->lerpOrigin, flare.origin ); VectorSubtract( flare.origin, cg.refdef.vieworg, delta ); len = VectorLength( delta ); @@ -788,38 +920,28 @@ static void CG_Lev2ZapChain( centity_t *cent ) int i; entityState_t *es; centity_t *source = NULL, *target = NULL; + int entityNums[ LEVEL2_AREAZAP_MAX_TARGETS + 1 ]; + int count; es = ¢->currentState; - for( i = 0; i <= 2; i++ ) + count = BG_UnpackEntityNumbers( es, entityNums, LEVEL2_AREAZAP_MAX_TARGETS + 1 ); + + for( i = 1; i < count; i++ ) { - switch( i ) + if( i == 1 ) { - case 0: - if( es->time <= 0 ) - continue; - - source = &cg_entities[ es->misc ]; - target = &cg_entities[ es->time ]; - break; - - case 1: - if( es->time2 <= 0 ) - continue; - - source = &cg_entities[ es->time ]; - target = &cg_entities[ es->time2 ]; - break; - - case 2: - if( es->constantLight <= 0 ) - continue; - - source = &cg_entities[ es->time2 ]; - target = &cg_entities[ es->constantLight ]; - break; + // First entity is the attacker + source = &cg_entities[ entityNums[ 0 ] ]; + } + else + { + // Subsequent zaps come from the first target + source = &cg_entities[ entityNums[ 1 ] ]; } + target = &cg_entities[ entityNums[ i ] ]; + if( !CG_IsTrailSystemValid( ¢->level2ZapTS[ i ] ) ) cent->level2ZapTS[ i ] = CG_SpawnNewTrailSystem( cgs.media.level2ZapTS ); @@ -844,7 +966,7 @@ void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int { centity_t *cent; vec3_t oldOrigin, origin, deltaOrigin; - vec3_t oldAngles, angles, deltaAngles; + vec3_t oldAngles, angles; if( moverNum <= 0 || moverNum >= ENTITYNUM_MAX_NORMAL ) { @@ -867,7 +989,6 @@ void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int BG_EvaluateTrajectory( ¢->currentState.apos, toTime, angles ); VectorSubtract( origin, oldOrigin, deltaOrigin ); - VectorSubtract( angles, oldAngles, deltaAngles ); VectorAdd( in, deltaOrigin, out ); @@ -947,9 +1068,10 @@ static void CG_CalcEntityLerpPositions( centity_t *cent ) return; } - if( cg_projectileNudge.integer > 0 && - cent->currentState.eType == ET_MISSILE && - !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) + if( cg_projectileNudge.integer && + !cg.demoPlayback && + cent->currentState.eType == ET_MISSILE && + !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) { timeshift = cg.ping; } @@ -972,7 +1094,7 @@ static void CG_CalcEntityLerpPositions( centity_t *cent ) // don't let the projectile go through the floor if( tr.fraction < 1.0f ) - VectorLerp( tr.fraction, lastOrigin, cent->lerpOrigin, cent->lerpOrigin ); + VectorLerp2( tr.fraction, lastOrigin, cent->lerpOrigin, cent->lerpOrigin ); } // adjust for riding a mover if it wasn't rolled into the predicted @@ -984,7 +1106,6 @@ static void CG_CalcEntityLerpPositions( centity_t *cent ) } } - /* =============== CG_CEntityPVSEnter @@ -1003,6 +1124,10 @@ static void CG_CEntityPVSEnter( centity_t *cent ) case ET_MISSILE: CG_LaunchMissile( cent ); break; + + case ET_BUILDABLE: + cent->lastBuildableHealth = es->misc; + break; } //clear any particle systems from previous uses of this centity_t @@ -1034,11 +1159,10 @@ static void CG_CEntityPVSLeave( centity_t *cent ) if( cg_debugPVS.integer ) CG_Printf( "Entity %d left PVS\n", cent->currentState.number ); - switch( es->eType ) { case ET_LEV2_ZAP_CHAIN: - for( i = 0; i <= 2; i++ ) + for( i = 0; i <= LEVEL2_AREAZAP_MAX_TARGETS; i++ ) { if( CG_IsTrailSystemValid( ¢->level2ZapTS[ i ] ) ) CG_DestroyTrailSystem( ¢->level2ZapTS[ i ] ); @@ -1069,18 +1193,23 @@ static void CG_AddCEntity( centity_t *cent ) switch( cent->currentState.eType ) { default: - CG_Error( "Bad entity type: %i\n", cent->currentState.eType ); + CG_Error( "Bad entity type: %i", cent->currentState.eType ); break; case ET_INVISIBLE: case ET_PUSH_TRIGGER: case ET_TELEPORT_TRIGGER: + case ET_LOCATION: break; case ET_GENERAL: CG_General( cent ); break; + case ET_WEAPON_DROP: + CG_WeaponDrop( cent ); + break; + case ET_CORPSE: CG_Corpse( cent ); break; @@ -1093,6 +1222,10 @@ static void CG_AddCEntity( centity_t *cent ) CG_Buildable( cent ); break; + case ET_RANGE_MARKER: + CG_RangeMarker( cent ); + break; + case ET_MISSILE: CG_Missile( cent ); break; @@ -1253,4 +1386,3 @@ void CG_AddPacketEntities( void ) } } } - diff --git a/src/cgame/cg_event.c b/src/cgame/cg_event.c index 07bcea9..3ed0ac9 100644 --- a/src/cgame/cg_event.c +++ b/src/cgame/cg_event.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,16 +17,129 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_event.c -- handle entity events at snapshot or playerstate transitions - #include "cg_local.h" +/* +======================= +CG_AddToKillMsg + +======================= +*/ +void CG_AddToKillMsg( const char* killername, const char* victimname, int icon ) +{ + int klen, vlen, index; + char *kls, *vls; + char *k, *v; + int lastcolor; + int chatHeight; + + if( cg_killMsgHeight.integer < TEAMCHAT_HEIGHT ) + chatHeight = cg_killMsgHeight.integer; + else + chatHeight = TEAMCHAT_HEIGHT; + + if( chatHeight <= 0 || cg_killMsgTime.integer <= 0 ) { + cgs.killMsgPos = cgs.killMsgLastPos = 0; + return; + } + + index = cgs.killMsgPos % chatHeight; + klen = vlen = 0; + + k = cgs.killMsgKillers[ index ]; *k=0; + v = cgs.killMsgVictims[ index ]; *v=0; + cgs.killMsgWeapons[ index ] = icon; + + memset( k, '\0', sizeof(cgs.killMsgKillers[index])); + memset( v, '\0', sizeof(cgs.killMsgVictims[index])); + kls = vls = NULL; + + lastcolor = '7'; + + // Killers name + while( *killername ) + { + if( klen > TEAMCHAT_WIDTH-1 ) { + if( kls ) { + killername -= ( k - kls ); + killername ++; + k -= ( k - kls ); + } + *k = 0; + +// cgs.killMsgMsgTimes[index] = cg.time; + k = cgs.killMsgKillers[index]; + *k = 0; + *k++ = Q_COLOR_ESCAPE; + *k++ = lastcolor; + klen = 0; + kls = NULL; + } + + if( Q_IsColorString( killername ) ) + { + *k++ = *killername++; + lastcolor = *killername; + *k++ = *killername++; + continue; + } + + if( *killername == ' ' ) + kls = k; + + *k++ = *killername++; + klen++; + } + + // Victims name + if (victimname) + while( *victimname ) + { + if( vlen > TEAMCHAT_WIDTH-1 ) { + if( vls ) { + victimname -= ( v - vls ); + victimname ++; + v -= ( v - vls ); + } + *v = 0; + + v = cgs.killMsgVictims[index]; + *v = 0; + *v++ = Q_COLOR_ESCAPE; + *v++ = lastcolor; + vlen = 0; + vls = NULL; + } + + if( Q_IsColorString( victimname ) ) + { + *v++ = *victimname++; + lastcolor = *victimname; + *v++ = *victimname++; + continue; + } + + if( *victimname == ' ' ) + vls = v; + + *v++ = *victimname++; + vlen++; + } + + cgs.killMsgMsgTimes[ index ] = cg.time; + cgs.killMsgPos++; + + if( cgs.killMsgPos - cgs.killMsgLastPos > chatHeight ) + cgs.killMsgLastPos = cgs.killMsgPos - chatHeight; +} + /* ============= CG_Obituary @@ -39,12 +153,13 @@ static void CG_Obituary( entityState_t *ent ) char *message2; const char *targetInfo; const char *attackerInfo; - char targetName[ 32 ]; - char attackerName[ 32 ]; + char targetName[ MAX_NAME_LENGTH ]; + char attackerName[ MAX_NAME_LENGTH ]; char className[ 64 ]; gender_t gender; clientInfo_t *ci; qboolean teamKill = qfalse; + int icon = WP_NONE; target = ent->otherEntityNum; attacker = ent->otherEntityNum2; @@ -54,6 +169,7 @@ static void CG_Obituary( entityState_t *ent ) CG_Error( "CG_Obituary: target out of range" ); ci = &cgs.clientinfo[ target ]; + gender = ci->gender; if( attacker < 0 || attacker >= MAX_CLIENTS ) { @@ -72,8 +188,7 @@ static void CG_Obituary( entityState_t *ent ) if( !targetInfo ) return; - Q_strncpyz( targetName, Info_ValueForKey( targetInfo, "n" ), sizeof( targetName ) - 2 ); - strcat( targetName, S_COLOR_WHITE ); + Q_strncpyz( targetName, Info_ValueForKey( targetInfo, "n" ), sizeof( targetName )); message2 = ""; @@ -81,9 +196,6 @@ static void CG_Obituary( entityState_t *ent ) switch( mod ) { - case MOD_SUICIDE: - message = "suicides"; - break; case MOD_FALLING: message = "fell fowl to gravity"; break; @@ -97,7 +209,7 @@ static void CG_Obituary( entityState_t *ent ) message = "melted"; break; case MOD_LAVA: - message = "does a back flip into the lava"; + message = "did a back flip into the lava"; break; case MOD_TARGET_LASER: message = "saw the light"; @@ -137,9 +249,8 @@ static void CG_Obituary( entityState_t *ent ) break; } - if( attacker == target ) + if( !message && attacker == target ) { - gender = ci->gender; switch( mod ) { case MOD_FLAMER_SPLASH: @@ -169,6 +280,24 @@ static void CG_Obituary( entityState_t *ent ) message = "blew himself up"; break; + case MOD_LEVEL3_BOUNCEBALL: + if( gender == GENDER_FEMALE ) + message = "sniped herself"; + else if( gender == GENDER_NEUTER ) + message = "sniped itself"; + else + message = "sniped himself"; + break; + + case MOD_PRIFLE: + if( gender == GENDER_FEMALE ) + message = "pulse rifled herself"; + else if( gender == GENDER_NEUTER ) + message = "pulse rifled itself"; + else + message = "pulse rifled himself"; + break; + default: if( gender == GENDER_FEMALE ) message = "killed herself"; @@ -178,12 +307,12 @@ static void CG_Obituary( entityState_t *ent ) message = "killed himself"; break; } - } - if( message ) - { - CG_Printf( "%s %s.\n", targetName, message ); - return; + if ( cg_killMsg.integer == 2) + { + CG_AddToKillMsg(va("%s ^7%s", targetName, message), NULL, WP_NONE); + return; + } } // check for double client messages @@ -194,8 +323,7 @@ static void CG_Obituary( entityState_t *ent ) } else { - Q_strncpyz( attackerName, Info_ValueForKey( attackerInfo, "n" ), sizeof( attackerName ) - 2); - strcat( attackerName, S_COLOR_WHITE ); + Q_strncpyz( attackerName, Info_ValueForKey( attackerInfo, "n" ), sizeof( attackerName )); // check for kill messages about the current clientNum if( target == cg.snap->ps.clientNum ) Q_strncpyz( cg.killerName, attackerName, sizeof( cg.killerName ) ); @@ -205,119 +333,141 @@ static void CG_Obituary( entityState_t *ent ) { switch( mod ) { + // + // HUMANS + // case MOD_PAINSAW: + icon = WP_PAIN_SAW; message = "was sawn by"; break; case MOD_BLASTER: + icon = WP_BLASTER; message = "was blasted by"; break; case MOD_MACHINEGUN: + icon = WP_MACHINEGUN; message = "was machinegunned by"; break; case MOD_CHAINGUN: + icon = WP_CHAINGUN; message = "was chaingunned by"; break; case MOD_SHOTGUN: + icon = WP_SHOTGUN; message = "was gunned down by"; break; case MOD_PRIFLE: + icon = WP_PULSE_RIFLE; message = "was pulse rifled by"; break; case MOD_MDRIVER: + icon = WP_MASS_DRIVER; message = "was mass driven by"; break; case MOD_LASGUN: + icon = WP_LAS_GUN; message = "was lasgunned by"; break; case MOD_FLAMER: - message = "was grilled by"; - message2 = "'s flamer"; - break; case MOD_FLAMER_SPLASH: - message = "was toasted by"; + icon = WP_FLAMER; + message = "was grilled by"; message2 = "'s flamer"; break; case MOD_LCANNON: + icon = WP_LUCIFER_CANNON; message = "felt the full force of"; message2 = "'s lucifer cannon"; break; case MOD_LCANNON_SPLASH: + icon = WP_LUCIFER_CANNON; message = "was caught in the fallout of"; message2 = "'s lucifer cannon"; break; case MOD_GRENADE: + icon = WP_GRENADE; message = "couldn't escape"; message2 = "'s grenade"; break; + // + // ALIENS + // case MOD_ABUILDER_CLAW: + icon = WP_ABUILD; message = "should leave"; message2 = "'s buildings alone"; break; case MOD_LEVEL0_BITE: + icon = WP_ALEVEL0; message = "was bitten by"; break; case MOD_LEVEL1_CLAW: + icon = WP_ALEVEL1; message = "was swiped by"; - Com_sprintf( className, 64, "'s %s", - BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL1 ) ); + Com_sprintf( className, 64, "'s %s", BG_ClassConfig( PCL_ALIEN_LEVEL1 )->humanName ); + message2 = className; + break; + case MOD_LEVEL1_PCLOUD: + icon = WP_ALEVEL1; + message = "was gassed by"; + Com_sprintf( className, 64, "'s %s", BG_ClassConfig( PCL_ALIEN_LEVEL1 )->humanName ); message2 = className; break; case MOD_LEVEL2_CLAW: + icon = WP_ALEVEL2; message = "was clawed by"; - Com_sprintf( className, 64, "'s %s", - BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL2 ) ); + Com_sprintf( className, 64, "'s %s", BG_ClassConfig( PCL_ALIEN_LEVEL2 )->humanName ); message2 = className; break; case MOD_LEVEL2_ZAP: + icon = WP_ALEVEL2; message = "was zapped by"; - Com_sprintf( className, 64, "'s %s", - BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL2 ) ); + Com_sprintf( className, 64, "'s %s", BG_ClassConfig( PCL_ALIEN_LEVEL2 )->humanName ); message2 = className; break; case MOD_LEVEL3_CLAW: + icon = WP_ALEVEL3; message = "was chomped by"; - Com_sprintf( className, 64, "'s %s", - BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL3 ) ); + Com_sprintf( className, 64, "'s %s", BG_ClassConfig( PCL_ALIEN_LEVEL3 )->humanName ); message2 = className; break; case MOD_LEVEL3_POUNCE: + icon = WP_ALEVEL3; message = "was pounced upon by"; - Com_sprintf( className, 64, "'s %s", - BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL3 ) ); + Com_sprintf( className, 64, "'s %s", BG_ClassConfig( PCL_ALIEN_LEVEL3 )->humanName ); message2 = className; break; case MOD_LEVEL3_BOUNCEBALL: + icon = WP_ALEVEL3; message = "was sniped by"; - Com_sprintf( className, 64, "'s %s", - BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL3 ) ); + Com_sprintf( className, 64, "'s %s", BG_ClassConfig( PCL_ALIEN_LEVEL3 )->humanName ); message2 = className; break; case MOD_LEVEL4_CLAW: + icon = WP_ALEVEL4; message = "was mauled by"; - Com_sprintf( className, 64, "'s %s", - BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL4 ) ); + Com_sprintf( className, 64, "'s %s", BG_ClassConfig( PCL_ALIEN_LEVEL4 )->humanName ); message2 = className; break; - case MOD_LEVEL4_CHARGE: + case MOD_LEVEL4_TRAMPLE: + icon = WP_ALEVEL4; message = "should have gotten out of the way of"; - Com_sprintf( className, 64, "'s %s", - BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL4 ) ); + Com_sprintf( className, 64, "'s %s", BG_ClassConfig( PCL_ALIEN_LEVEL4 )->humanName ); message2 = className; break; - + case MOD_LEVEL4_CRUSH: + message = "was crushed under"; + message2 = "'s weight"; + break; case MOD_POISON: message = "should have used a medkit against"; message2 = "'s poison"; break; - case MOD_LEVEL1_PCLOUD: - message = "was gassed by"; - Com_sprintf( className, 64, "'s %s", - BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL1 ) ); - message2 = className; - break; - + // + // MISC.. + // case MOD_TELEFRAG: message = "tried to invade"; message2 = "'s personal space"; @@ -327,24 +477,55 @@ static void CG_Obituary( entityState_t *ent ) break; } - if( message ) + if ( cg_killMsg.integer == 1) { - CG_Printf( "%s %s %s%s%s\n", - targetName, message, - ( teamKill ) ? S_COLOR_RED "TEAMMATE " S_COLOR_WHITE : "", - attackerName, message2 ); - if( teamKill && attacker == cg.clientNum ) + char killMessage[80]; + if( icon > WP_NONE ) { - CG_CenterPrint( va ( "You killed " S_COLOR_RED "TEAMMATE " - S_COLOR_WHITE "%s", targetName ), - SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + Com_sprintf(killMessage, sizeof(killMessage), "%s%s", + teamKill ? S_COLOR_RED"TEAMMATE "S_COLOR_WHITE:"", + targetName); + CG_AddToKillMsg(attackerName, killMessage, icon); } - return; } + else if( message ) + { + CG_Printf( "%s" S_COLOR_WHITE " %s %s%s" S_COLOR_WHITE "%s\n", + targetName, + message, + teamKill ? S_COLOR_RED "TEAMMATE " S_COLOR_WHITE : "", + attackerName, + message2 ); + } + + if( attacker == cg.clientNum ) + { + CG_CenterPrint(va("You killed %s%s", teamKill ? S_COLOR_RED"TEAMMATE "S_COLOR_WHITE:"", + targetName), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + } + if ( cg_killMsg.integer != 1) + return; + } + +#if 0 + switch (mod) { + case MOD_SLAP: + message = "was slapped to death"; + break; + default: + break; } +#endif // we don't know what it was - CG_Printf( "%s died.\n", targetName ); + if ( cg_killMsg.integer ) + { + CG_AddToKillMsg(va("%s ^7%s", targetName, message), NULL, WP_NONE); + } + else + { + CG_Printf( "%s" S_COLOR_WHITE " %s\n", targetName, message ); + } } //========================================================================== @@ -381,6 +562,60 @@ void CG_PainEvent( centity_t *cent, int health ) cent->pe.painDirection ^= 1; } +/* +========================= +CG_Level2Zap +========================= +*/ +static void CG_Level2Zap( entityState_t *es ) +{ + int i; + centity_t *source = NULL, *target = NULL; + + if( es->misc < 0 || es->misc >= MAX_CLIENTS ) + return; + + source = &cg_entities[ es->misc ]; + for( i = 0; i <= 2; i++ ) + { + switch( i ) + { + case 0: + if( es->time <= 0 ) + continue; + + target = &cg_entities[ es->time ]; + break; + + case 1: + if( es->time2 <= 0 ) + continue; + + target = &cg_entities[ es->time2 ]; + break; + + case 2: + if( es->constantLight <= 0 ) + continue; + + target = &cg_entities[ es->constantLight ]; + break; + } + + if( !CG_IsTrailSystemValid( &source->level2ZapTS[ i ] ) ) + source->level2ZapTS[ i ] = CG_SpawnNewTrailSystem( cgs.media.level2ZapTS ); + + if( CG_IsTrailSystemValid( &source->level2ZapTS[ i ] ) ) + { + CG_SetAttachmentCent( &source->level2ZapTS[ i ]->frontAttachment, source ); + CG_SetAttachmentCent( &source->level2ZapTS[ i ]->backAttachment, target ); + CG_AttachToCent( &source->level2ZapTS[ i ]->frontAttachment ); + CG_AttachToCent( &source->level2ZapTS[ i ]->backAttachment ); + } + } + source->level2ZapTime = cg.time; +} + /* ============== CG_EntityEvent @@ -389,7 +624,6 @@ An entity has an event value also called by CG_CheckPlayerstateEvents ============== */ -#define DEBUGNAME(x) if(cg_debugEvents.integer){CG_Printf(x"\n");} void CG_EntityEvent( centity_t *cent, vec3_t position ) { entityState_t *es; @@ -400,22 +634,20 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) clientInfo_t *ci; int steptime; - if( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) + if( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) steptime = 200; else - steptime = BG_FindSteptimeForClass( cg.snap->ps.stats[ STAT_PCLASS ] ); + steptime = BG_Class( cg.snap->ps.stats[ STAT_CLASS ] )->steptime; es = ¢->currentState; event = es->event & ~EV_EVENT_BITS; if( cg_debugEvents.integer ) - CG_Printf( "ent:%3i event:%3i ", es->number, event ); + CG_Printf( "ent:%3i event:%3i %s\n", es->number, event, + BG_EventName( event ) ); if( !event ) - { - DEBUGNAME("ZEROEVENT"); return; - } clientNum = es->clientNum; if( clientNum < 0 || clientNum >= MAX_CLIENTS ) @@ -429,7 +661,6 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) // movement generated events // case EV_FOOTSTEP: - DEBUGNAME( "EV_FOOTSTEP" ); if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) { if( ci->footsteps == FOOTSTEP_CUSTOM ) @@ -442,7 +673,6 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_FOOTSTEP_METAL: - DEBUGNAME( "EV_FOOTSTEP_METAL" ); if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) { if( ci->footsteps == FOOTSTEP_CUSTOM ) @@ -455,7 +685,6 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_FOOTSTEP_SQUELCH: - DEBUGNAME( "EV_FOOTSTEP_SQUELCH" ); if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) { trap_S_StartSound( NULL, es->number, CHAN_BODY, @@ -464,7 +693,6 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_FOOTSPLASH: - DEBUGNAME( "EV_FOOTSPLASH" ); if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) { trap_S_StartSound( NULL, es->number, CHAN_BODY, @@ -473,7 +701,6 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_FOOTWADE: - DEBUGNAME( "EV_FOOTWADE" ); if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) { trap_S_StartSound( NULL, es->number, CHAN_BODY, @@ -482,7 +709,6 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_SWIM: - DEBUGNAME( "EV_SWIM" ); if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) { trap_S_StartSound( NULL, es->number, CHAN_BODY, @@ -492,45 +718,41 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) case EV_FALL_SHORT: - DEBUGNAME( "EV_FALL_SHORT" ); trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landSound ); if( clientNum == cg.predictedPlayerState.clientNum ) { // smooth landing z changes - cg.landChange = -8; + cg.landChange = -1 * BG_Class( cg.predictedPlayerState.stats[ STAT_CLASS ] )->landBob; cg.landTime = cg.time; } break; case EV_FALL_MEDIUM: - DEBUGNAME( "EV_FALL_MEDIUM" ); // use normal pain sound trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*pain100_1.wav" ) ); if( clientNum == cg.predictedPlayerState.clientNum ) { // smooth landing z changes - cg.landChange = -16; + cg.landChange = -2 * BG_Class( cg.predictedPlayerState.stats[ STAT_CLASS ] )->landBob; cg.landTime = cg.time; } break; case EV_FALL_FAR: - DEBUGNAME( "EV_FALL_FAR" ); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*fall1.wav" ) ); + trap_S_StartSound( NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*fall1.wav" ) ); cent->pe.painTime = cg.time; // don't play a pain sound right after this if( clientNum == cg.predictedPlayerState.clientNum ) { // smooth landing z changes - cg.landChange = -24; + cg.landChange = -3 * BG_Class( cg.predictedPlayerState.stats[ STAT_CLASS ] )->landBob; cg.landTime = cg.time; } break; case EV_FALLING: - DEBUGNAME( "EV_FALLING" ); trap_S_StartSound( NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*falling1.wav" ) ); break; @@ -542,7 +764,6 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) case EV_STEPDN_8: case EV_STEPDN_12: case EV_STEPDN_16: // smooth out step down transitions - DEBUGNAME( "EV_STEP" ); { float oldStep; int delta; @@ -586,10 +807,9 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) } case EV_JUMP: - DEBUGNAME( "EV_JUMP" ); trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) ); - if( BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_PCLASS ], SCA_WALLJUMPER ) ) + if( BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_CLASS ], SCA_WALLJUMPER ) ) { vec3_t surfNormal, refNormal = { 0.0f, 0.0f, 1.0f }; vec3_t rotAxis; @@ -617,44 +837,36 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_LEV1_GRAB: - DEBUGNAME( "EV_LEV1_GRAB" ); trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.alienL1Grab ); break; - case EV_LEV4_CHARGE_PREPARE: - DEBUGNAME( "EV_LEV4_CHARGE_PREPARE" ); + case EV_LEV4_TRAMPLE_PREPARE: trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.alienL4ChargePrepare ); break; - case EV_LEV4_CHARGE_START: - DEBUGNAME( "EV_LEV4_CHARGE_START" ); + case EV_LEV4_TRAMPLE_START: //FIXME: stop cgs.media.alienL4ChargePrepare playing here trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.alienL4ChargeStart ); break; case EV_TAUNT: - DEBUGNAME( "EV_TAUNT" ); if( !cg_noTaunt.integer ) trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*taunt.wav" ) ); break; case EV_WATER_TOUCH: - DEBUGNAME( "EV_WATER_TOUCH" ); trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrInSound ); break; case EV_WATER_LEAVE: - DEBUGNAME( "EV_WATER_LEAVE" ); trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrOutSound ); break; case EV_WATER_UNDER: - DEBUGNAME( "EV_WATER_UNDER" ); trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrUnSound ); break; case EV_WATER_CLEAR: - DEBUGNAME( "EV_WATER_CLEAR" ); trap_S_StartSound( NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*gasp.wav" ) ); break; @@ -662,28 +874,23 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) // weapon events // case EV_NOAMMO: - DEBUGNAME( "EV_NOAMMO" ); - { - } + trap_S_StartSound( NULL, es->number, CHAN_WEAPON, + cgs.media.weaponEmptyClick ); break; case EV_CHANGE_WEAPON: - DEBUGNAME( "EV_CHANGE_WEAPON" ); trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.selectSound ); break; case EV_FIRE_WEAPON: - DEBUGNAME( "EV_FIRE_WEAPON" ); CG_FireWeapon( cent, WPM_PRIMARY ); break; case EV_FIRE_WEAPON2: - DEBUGNAME( "EV_FIRE_WEAPON2" ); CG_FireWeapon( cent, WPM_SECONDARY ); break; case EV_FIRE_WEAPON3: - DEBUGNAME( "EV_FIRE_WEAPON3" ); CG_FireWeapon( cent, WPM_TERTIARY ); break; @@ -693,32 +900,26 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) // other events // case EV_PLAYER_TELEPORT_IN: - DEBUGNAME( "EV_PLAYER_TELEPORT_IN" ); //deprecated break; case EV_PLAYER_TELEPORT_OUT: - DEBUGNAME( "EV_PLAYER_TELEPORT_OUT" ); CG_PlayerDisconnect( position ); break; case EV_BUILD_CONSTRUCT: - DEBUGNAME( "EV_BUILD_CONSTRUCT" ); //do something useful here break; case EV_BUILD_DESTROY: - DEBUGNAME( "EV_BUILD_DESTROY" ); //do something useful here break; case EV_RPTUSE_SOUND: - DEBUGNAME( "EV_RPTUSE_SOUND" ); trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.repeaterUseSound ); break; case EV_GRENADE_BOUNCE: - DEBUGNAME( "EV_GRENADE_BOUNCE" ); if( rand( ) & 1 ) trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.hardBounceSound1 ); else @@ -729,40 +930,34 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) // missile impacts // case EV_MISSILE_HIT: - DEBUGNAME( "EV_MISSILE_HIT" ); ByteToDir( es->eventParm, dir ); - CG_MissileHitPlayer( es->weapon, es->generic1, position, dir, es->otherEntityNum ); + CG_MissileHitEntity( es->weapon, es->generic1, position, dir, es->otherEntityNum, es->torsoAnim ); break; case EV_MISSILE_MISS: - DEBUGNAME( "EV_MISSILE_MISS" ); ByteToDir( es->eventParm, dir ); - CG_MissileHitWall( es->weapon, es->generic1, 0, position, dir, IMPACTSOUND_DEFAULT ); + CG_MissileHitWall( es->weapon, es->generic1, 0, position, dir, IMPACTSOUND_DEFAULT, es->torsoAnim ); break; case EV_MISSILE_MISS_METAL: - DEBUGNAME( "EV_MISSILE_MISS_METAL" ); ByteToDir( es->eventParm, dir ); - CG_MissileHitWall( es->weapon, es->generic1, 0, position, dir, IMPACTSOUND_METAL ); + CG_MissileHitWall( es->weapon, es->generic1, 0, position, dir, IMPACTSOUND_METAL, es->torsoAnim ); break; case EV_HUMAN_BUILDABLE_EXPLOSION: - DEBUGNAME( "EV_HUMAN_BUILDABLE_EXPLOSION" ); ByteToDir( es->eventParm, dir ); CG_HumanBuildableExplosion( position, dir ); break; case EV_ALIEN_BUILDABLE_EXPLOSION: - DEBUGNAME( "EV_ALIEN_BUILDABLE_EXPLOSION" ); ByteToDir( es->eventParm, dir ); CG_AlienBuildableExplosion( position, dir ); break; case EV_TESLATRAIL: - DEBUGNAME( "EV_TESLATRAIL" ); cent->currentState.weapon = WP_TESLAGEN; { - centity_t *source = &cg_entities[ es->generic1 ]; + centity_t *source = &cg_entities[ es->misc ]; centity_t *target = &cg_entities[ es->clientNum ]; vec3_t sourceOffset = { 0.0f, 0.0f, 28.0f }; @@ -785,23 +980,19 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_BULLET_HIT_WALL: - DEBUGNAME( "EV_BULLET_HIT_WALL" ); ByteToDir( es->eventParm, dir ); CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qfalse, ENTITYNUM_WORLD ); break; case EV_BULLET_HIT_FLESH: - DEBUGNAME( "EV_BULLET_HIT_FLESH" ); CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qtrue, es->eventParm ); break; case EV_SHOTGUN: - DEBUGNAME( "EV_SHOTGUN" ); CG_ShotgunFire( es ); break; case EV_GENERAL_SOUND: - DEBUGNAME( "EV_GENERAL_SOUND" ); if( cgs.gameSounds[ es->eventParm ] ) trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.gameSounds[ es->eventParm ] ); else @@ -812,7 +1003,6 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_GLOBAL_SOUND: // play from the player's head so it never diminishes - DEBUGNAME( "EV_GLOBAL_SOUND" ); if( cgs.gameSounds[ es->eventParm ] ) trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.gameSounds[ es->eventParm ] ); else @@ -825,7 +1015,6 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) case EV_PAIN: // local player sounds are triggered in CG_CheckLocalSounds, // so ignore events on the player - DEBUGNAME( "EV_PAIN" ); if( cent->currentState.number != cg.snap->ps.clientNum ) CG_PainEvent( cent, es->eventParm ); break; @@ -833,34 +1022,28 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) case EV_DEATH1: case EV_DEATH2: case EV_DEATH3: - DEBUGNAME( "EV_DEATHx" ); trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, va( "*death%i.wav", event - EV_DEATH1 + 1 ) ) ); break; case EV_OBITUARY: - DEBUGNAME( "EV_OBITUARY" ); CG_Obituary( es ); break; case EV_GIB_PLAYER: - DEBUGNAME( "EV_GIB_PLAYER" ); // no gibbing break; case EV_STOPLOOPINGSOUND: - DEBUGNAME( "EV_STOPLOOPINGSOUND" ); trap_S_StopLoopingSound( es->number ); es->loopSound = 0; break; case EV_DEBUG_LINE: - DEBUGNAME( "EV_DEBUG_LINE" ); CG_Beam( cent ); break; case EV_BUILD_DELAY: - DEBUGNAME( "EV_BUILD_DELAY" ); if( clientNum == cg.predictedPlayerState.clientNum ) { trap_S_StartLocalSound( cgs.media.buildableRepairedSound, CHAN_LOCAL_SOUND ); @@ -869,18 +1052,15 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_BUILD_REPAIR: - DEBUGNAME( "EV_BUILD_REPAIR" ); trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.buildableRepairSound ); break; case EV_BUILD_REPAIRED: - DEBUGNAME( "EV_BUILD_REPAIRED" ); trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.buildableRepairedSound ); break; case EV_OVERMIND_ATTACK: - DEBUGNAME( "EV_OVERMIND_ATTACK" ); - if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS ) + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_ALIENS ) { trap_S_StartLocalSound( cgs.media.alienOvermindAttack, CHAN_ANNOUNCER ); CG_CenterPrint( "The Overmind is under attack!", 200, GIANTCHAR_WIDTH * 4 ); @@ -888,8 +1068,7 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_OVERMIND_DYING: - DEBUGNAME( "EV_OVERMIND_DYING" ); - if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS ) + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_ALIENS ) { trap_S_StartLocalSound( cgs.media.alienOvermindDying, CHAN_ANNOUNCER ); CG_CenterPrint( "The Overmind is dying!", 200, GIANTCHAR_WIDTH * 4 ); @@ -897,17 +1076,19 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_DCC_ATTACK: - DEBUGNAME( "EV_DCC_ATTACK" ); - if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_HUMANS ) + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_HUMANS ) { //trap_S_StartLocalSound( cgs.media.humanDCCAttack, CHAN_ANNOUNCER ); CG_CenterPrint( "Our base is under attack!", 200, GIANTCHAR_WIDTH * 4 ); } break; + case EV_MGTURRET_SPINUP: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.turretSpinupSound ); + break; + case EV_OVERMIND_SPAWNS: - DEBUGNAME( "EV_OVERMIND_SPAWNS" ); - if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS ) + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_ALIENS ) { trap_S_StartLocalSound( cgs.media.alienOvermindSpawns, CHAN_ANNOUNCER ); CG_CenterPrint( "The Overmind needs spawns!", 200, GIANTCHAR_WIDTH * 4 ); @@ -915,7 +1096,6 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_ALIEN_EVOLVE: - DEBUGNAME( "EV_ALIEN_EVOLVE" ); trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.alienEvolveSound ); { particleSystem_t *ps = CG_SpawnNewParticleSystem( cgs.media.alienEvolvePS ); @@ -935,7 +1115,6 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_ALIEN_EVOLVE_FAILED: - DEBUGNAME( "EV_ALIEN_EVOLVE_FAILED" ); if( clientNum == cg.predictedPlayerState.clientNum ) { //FIXME: change to "negative" sound @@ -945,7 +1124,6 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_ALIEN_ACIDTUBE: - DEBUGNAME( "EV_ALIEN_ACIDTUBE" ); { particleSystem_t *ps = CG_SpawnNewParticleSystem( cgs.media.alienAcidTubePS ); @@ -960,18 +1138,19 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_MEDKIT_USED: - DEBUGNAME( "EV_MEDKIT_USED" ); trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.medkitUseSound ); break; case EV_PLAYER_RESPAWN: - DEBUGNAME( "EV_PLAYER_RESPAWN" ); if( es->number == cg.clientNum ) cg.spawnTime = cg.time; break; + case EV_LEV2_ZAP: + CG_Level2Zap( es ); + break; + default: - DEBUGNAME( "UNKNOWN" ); CG_Error( "Unknown event: %i", event ); break; } @@ -1031,4 +1210,3 @@ void CG_CheckEvents( centity_t *cent ) if( oldEvent != EV_NONE ) cent->currentState.event = oldEvent; } - diff --git a/src/cgame/cg_local.h b/src/cgame/cg_local.h index ea1694b..ecdf02d 100644 --- a/src/cgame/cg_local.h +++ b/src/cgame/cg_local.h @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,17 +17,18 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ -#include "../qcommon/q_shared.h" -#include "../renderer/tr_types.h" -#include "../game/bg_public.h" +#include "qcommon/q_shared.h" +#include "renderercommon/tr_types.h" +#include "game/bg_public.h" #include "cg_public.h" -#include "../ui/ui_shared.h" +#include "binaryshader.h" +#include "ui/ui_shared.h" // The entire cgame module is unloaded and reloaded on each level change, // so there is NO persistant data between levels on the client side. @@ -70,6 +72,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define CHAR_HEIGHT 48 #define TEXT_ICON_SPACE 4 +#define TEAMCHAT_WIDTH 80 +#define TEAMCHAT_HEIGHT 8 + // very large characters #define GIANT_WIDTH 32 #define GIANT_HEIGHT 48 @@ -79,10 +84,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define TEAM_OVERLAY_MAXNAME_WIDTH 12 #define TEAM_OVERLAY_MAXLOCATION_WIDTH 16 -#define DEFAULT_MODEL "sarge" -#define DEFAULT_TEAM_MODEL "sarge" -#define DEFAULT_TEAM_HEAD "sarge" - typedef enum { FOOTSTEP_NORMAL, @@ -207,7 +208,9 @@ typedef enum PMT_STATIC_TRANSFORM, PMT_TAG, PMT_CENT_ANGLES, - PMT_NORMAL + PMT_NORMAL, + PMT_LAST_NORMAL, + PMT_OPPORTUNISTIC_NORMAL } pMoveType_t; typedef enum @@ -253,7 +256,7 @@ typedef struct pLerpValues_s typedef struct baseParticle_s { vec3_t displacement; - float randDisplacement; + vec3_t randDisplacement; float normalDisplacement; pMoveType_t velMoveType; @@ -282,6 +285,7 @@ typedef struct baseParticle_s float bounceSoundCountRandFrac; pLerpValues_t radius; + int physicsRadius; pLerpValues_t alpha; pLerpValues_t rotation; @@ -317,6 +321,8 @@ typedef struct baseParticle_s qboolean overdrawProtection; qboolean realLight; qboolean cullOnStartSolid; + + float scaleWithCharge; } baseParticle_t; @@ -358,6 +364,11 @@ typedef struct particleSystem_s //for PMT_NORMAL qboolean normalValid; vec3_t normal; + //for PMT_LAST_NORMAL and PMT_OPPORTUNISTIC_NORMAL + qboolean lastNormalIsCurrent; + vec3_t lastNormal; + + int charge; } particleSystem_t; @@ -383,6 +394,8 @@ typedef struct particle_s baseParticle_t *class; particleEjector_t *parent; + particleSystem_t *childParticleSystem; + int birthTime; int lifeTime; @@ -463,7 +476,7 @@ typedef struct baseTrailBeam_s // the time it takes for a beam to fade out (double attached only) int fadeOutTime; - + char shaderName[ MAX_QPATH ]; qhandle_t shader; @@ -492,6 +505,7 @@ typedef struct baseTrailSystem_s baseTrailBeam_t *beams[ MAX_BEAMS_PER_SYSTEM ]; int numBeams; + int lifeTime; qboolean thirdPersonOnly; qboolean registered; //whether or not the assets for this trail have been loaded } baseTrailSystem_t; @@ -503,6 +517,7 @@ typedef struct trailSystem_s attachment_t frontAttachment; attachment_t backAttachment; + int birthTime; int destroyTime; qboolean valid; } trailSystem_t; @@ -551,7 +566,7 @@ typedef struct trailBeam_s // because corpses after respawn are outside the normal // client numbering range -//TA: smoothing of view and model for WW transitions +// smoothing of view and model for WW transitions #define MAXSMOOTHS 32 typedef struct @@ -566,7 +581,7 @@ typedef struct typedef struct { - lerpFrame_t legs, torso, flag, nonseg; + lerpFrame_t legs, torso, nonseg, weapon; int painTime; int painDirection; // flip from 0 to 1 @@ -594,6 +609,16 @@ typedef struct buildableStatus_s qboolean visible; // Status is visble? } buildableStatus_t; +typedef struct buildableCache_s +{ + vec3_t cachedOrigin; // If any of the values differ from their + vec3_t cachedAngles; // cached versions, then the cache is invalid + vec3_t cachedNormal; + buildable_t cachedType; + vec3_t axis[ 3 ]; + vec3_t origin; +} buildableCache_t; + //================================================= // centity_t have a direct corespondence with gentity_t in the game, but @@ -634,12 +659,13 @@ typedef struct centity_s lerpFrame_t lerpFrame; - //TA: buildableAnimNumber_t buildableAnim; //persistant anim number buildableAnimNumber_t oldBuildableAnim; //to detect when new anims are set + qboolean buildableIdleAnim; //to check if new idle anim particleSystem_t *buildablePS; buildableStatus_t buildableStatus; - float lastBuildableHealthScale; + buildableCache_t buildableCache; // so we don't recalculate things + float lastBuildableHealth; int lastBuildableDamageSoundTime; lightFlareStatus_t lfs; @@ -656,16 +682,20 @@ typedef struct centity_s particleSystem_t *jetPackPS; jetPackState_t jetPackState; + particleSystem_t *poisonCloudedPS; + particleSystem_t *entityPS; qboolean entityPSMissing; - trailSystem_t *level2ZapTS[ 3 ]; + trailSystem_t *level2ZapTS[ LEVEL2_AREAZAP_MAX_TARGETS ]; + int level2ZapTime; trailSystem_t *muzzleTS; //used for the tesla and reactor int muzzleTSDeathTime; qboolean valid; - qboolean oldValid; + qboolean oldValid; + struct centity_s *nextLocation; } centity_t; @@ -706,41 +736,20 @@ typedef struct { qboolean infoValid; - char name[ MAX_QPATH ]; - pTeam_t team; - - int botSkill; // 0 = not bot, 1-5 = bot - - vec3_t color1; - vec3_t color2; + char name[ MAX_NAME_LENGTH ]; + team_t team; int score; // updated by score servercmds int location; // location index for team mode int health; // you only get this info about your teammates - int armor; - int curWeapon; - - int handicap; - int wins, losses; // in tourney mode - - int teamTask; // task in teamplay (offence/defence) - qboolean teamLeader; // true when this is a team leader - - int powerups; // so can display quad/flag status - - int medkitUsageTime; - int invulnerabilityStartTime; - int invulnerabilityStopTime; - - int breathPuffTime; + int upgrade; + int curWeaponClass; // sends current weapon for H, current class for A // when clientinfo is changed, the loading of models/skins/sounds // can be deferred until you are dead, to prevent hitches in // gameplay char modelName[ MAX_QPATH ]; char skinName[ MAX_QPATH ]; - char headModelName[ MAX_QPATH ]; - char headSkinName[ MAX_QPATH ]; qboolean newAnims; // true if using the new mission pack animations qboolean fixedlegs; // true if legs yaw is always the same as torso yaw @@ -771,6 +780,9 @@ typedef struct sfxHandle_t customFootsteps[ 4 ]; sfxHandle_t customMetalFootsteps[ 4 ]; + + char voice[ MAX_VOICE_NAME_LEN ]; + int voiceTime; } clientInfo_t; @@ -789,6 +801,7 @@ typedef struct weaponInfoMode_s qboolean usesSpriteMissle; qhandle_t missileSprite; int missileSpriteSize; + float missileSpriteCharge; qhandle_t missileParticleSystem; qhandle_t missileTrailSystem; qboolean missileRotates; @@ -799,7 +812,6 @@ typedef struct weaponInfoMode_s int missileAnimLooping; sfxHandle_t firingSound; - qboolean loopFireSound; qhandle_t muzzleParticleSystem; @@ -824,6 +836,13 @@ typedef struct weaponInfo_s qhandle_t barrelModel; qhandle_t flashModel; + qhandle_t weaponModel3rdPerson; + qhandle_t barrelModel3rdPerson; + qhandle_t flashModel3rdPerson; + + animation_t animations[ MAX_WEAPON_ANIMATIONS ]; + qboolean noDrift; + vec3_t weaponMidpoint; // so it will rotate centered instead of by tag qhandle_t weaponIcon; @@ -869,7 +888,6 @@ typedef struct //====================================================================== -//TA: typedef struct { vec3_t alienBuildablePos[ MAX_GENTITIES ]; @@ -906,6 +924,9 @@ typedef struct #define NUM_SAVED_STATES ( CMD_BACKUP + 2 ) +// After this many msec the crosshair name fades out completely +#define CROSSHAIR_CLIENT_TIMEOUT 1000 + typedef struct { int clientFrame; // incremented each frame @@ -1000,19 +1021,14 @@ typedef struct int scoreFadeTime; char killerName[ MAX_NAME_LENGTH ]; char spectatorList[ MAX_STRING_CHARS ]; // list of names - int spectatorLen; // length of list - float spectatorWidth; // width in device units int spectatorTime; // next time to offset - int spectatorPaintX; // current paint x - int spectatorPaintX2; // current paint x - int spectatorOffset; // current offset from start - int spectatorPaintLen; // current offset from start + float spectatorOffset; // current offset from start // centerprinting int centerPrintTime; int centerPrintCharWidth; int centerPrintY; - char centerPrint[ 1024 ]; + char centerPrint[ MAX_STRING_CHARS ]; int centerPrintLines; // low ammo warning state @@ -1022,6 +1038,7 @@ typedef struct int lastKillTime; // crosshair client ID + int crosshairBuildable; int crosshairClientNum; int crosshairClientTime; @@ -1031,7 +1048,6 @@ typedef struct // attacking player int attackerTime; - int voiceTime; // reward medals int rewardStack; @@ -1052,8 +1068,7 @@ typedef struct int voiceChatBufferOut; // warmup countdown - int warmup; - int warmupCount; + int warmupTime; //========================== @@ -1083,8 +1098,7 @@ typedef struct float v_dmg_pitch; float v_dmg_roll; - vec3_t kick_angles; // weapon kicks - vec3_t kick_origin; + qboolean chaseFollow; // temp working variables for player view float bobfracsin; @@ -1099,25 +1113,25 @@ typedef struct char testModelBarrelName[MAX_QPATH]; qboolean testGun; - int spawnTime; //TA: fovwarp - int weapon1Time; //TA: time when BUTTON_ATTACK went t->f f->t - int weapon2Time; //TA: time when BUTTON_ATTACK2 went t->f f->t - int weapon3Time; //TA: time when BUTTON_USE_HOLDABLE went t->f f->t + int spawnTime; // fovwarp + int weapon1Time; // time when BUTTON_ATTACK went t->f f->t + int weapon2Time; // time when BUTTON_ATTACK2 went t->f f->t + int weapon3Time; // time when BUTTON_USE_HOLDABLE went t->f f->t qboolean weapon1Firing; qboolean weapon2Firing; qboolean weapon3Firing; int poisonedTime; - vec3_t lastNormal; //TA: view smoothage - vec3_t lastVangles; //TA: view smoothage - smooth_t sList[ MAXSMOOTHS ]; //TA: WW smoothing + vec3_t lastNormal; // view smoothage + vec3_t lastVangles; // view smoothage + smooth_t sList[ MAXSMOOTHS ]; // WW smoothing - int forwardMoveTime; //TA: for struggling + int forwardMoveTime; // for struggling int rightMoveTime; int upMoveTime; - float charModelFraction; //TA: loading percentages + float charModelFraction; // loading percentages float mediaFraction; float buildablesFraction; @@ -1129,23 +1143,32 @@ typedef struct int numConsoleLines; particleSystem_t *poisonCloudPS; + particleSystem_t *poisonCloudedPS; float painBlendValue; float painBlendTarget; + float healBlendValue; int lastHealth; + qboolean wasDeadLastFrame; int lastPredictedCommand; int lastServerTime; playerState_t savedPmoveStates[ NUM_SAVED_STATES ]; int stateHead, stateTail; int ping; + + float chargeMeterAlpha; + float chargeMeterValue; + qhandle_t lastHealthCross; + float healthCrossFade; + int nearUsableBuildable; + + int nextWeaponClickTime; + // binary shaders - by /dev/humancontroller + int numBinaryShadersUsed; + cgBinaryShaderSetting_t binaryShaderSettings[ NUM_BINARY_SHADERS ]; } cg_t; - -// all of the model, shader, and sound references that are -// loaded at gamestate time are stored in cgMedia_t -// Other media that can be tied to clients, weapons, or items are -// stored in the clientInfo_t, itemInfo_t, weaponInfo_t, and powerupInfo_t typedef struct { qhandle_t charsetShader; @@ -1168,6 +1191,7 @@ typedef struct qhandle_t scannerBlipShader; qhandle_t scannerLineShader; + qhandle_t teamOverlayShader; qhandle_t numberShaders[ 11 ]; @@ -1179,12 +1203,22 @@ typedef struct qhandle_t redBuildShader; qhandle_t humanSpawningShader; + // binary shaders + range markers + qhandle_t sphereModel; + qhandle_t sphericalCone64Model; + qhandle_t sphericalCone240Model; + + qhandle_t plainColorShader; + qhandle_t binaryAlpha1Shader; + cgMediaBinaryShader_t binaryShaders[ NUM_BINARY_SHADERS ]; + // disconnect qhandle_t disconnectPS; qhandle_t disconnectSound; // sounds sfxHandle_t tracerSound; + sfxHandle_t weaponEmptyClick; sfxHandle_t selectSound; sfxHandle_t footsteps[ FOOTSTEP_TOTAL ][ 4 ]; sfxHandle_t talkSound; @@ -1192,6 +1226,7 @@ typedef struct sfxHandle_t humanTalkSound; sfxHandle_t landSound; sfxHandle_t fallSound; + sfxHandle_t turretSpinupSound; sfxHandle_t hardBounceSound1; sfxHandle_t hardBounceSound2; @@ -1251,6 +1286,7 @@ typedef struct sfxHandle_t buildableRepairedSound; qhandle_t poisonCloudPS; + qhandle_t poisonCloudedPS; qhandle_t alienEvolvePS; qhandle_t alienAcidTubePS; @@ -1263,13 +1299,22 @@ typedef struct qhandle_t alienBleedPS; qhandle_t humanBleedPS; + qhandle_t alienBuildableBleedPS; + qhandle_t humanBuildableBleedPS; + qhandle_t teslaZapTS; sfxHandle_t lCannonWarningSound; + sfxHandle_t lCannonWarningSound2; qhandle_t buildWeaponTimerPie[ 8 ]; qhandle_t upgradeClassIconShader; + qhandle_t healthCross; + qhandle_t healthCross2X; + qhandle_t healthCross3X; + qhandle_t healthCrossMedkit; + qhandle_t healthCrossPoisoned; } cgMedia_t; typedef struct @@ -1319,17 +1364,12 @@ typedef struct char mapname[ MAX_QPATH ]; qboolean markDeconstruct; // Whether or not buildables are marked - int voteTime; - int voteYes; - int voteNo; - qboolean voteModified; // beep whenever changed - char voteString[ MAX_STRING_TOKENS ]; - - int teamVoteTime[ 2 ]; - int teamVoteYes[ 2 ]; - int teamVoteNo[ 2 ]; - qboolean teamVoteModified[ 2 ]; // beep whenever changed - char teamVoteString[ 2 ][ MAX_STRING_TOKENS ]; + int voteTime[ NUM_TEAMS ]; + int voteYes[ NUM_TEAMS ]; + int voteNo[ NUM_TEAMS ]; + char voteCaller[ NUM_TEAMS ][ MAX_NAME_LENGTH ]; + qboolean voteModified[ NUM_TEAMS ];// beep whenever changed + char voteString[ NUM_TEAMS ][ MAX_STRING_TOKENS ]; int levelStartTime; @@ -1337,22 +1377,13 @@ typedef struct qboolean newHud; - int alienBuildPoints; - int alienBuildPointsTotal; - int humanBuildPoints; - int humanBuildPointsTotal; - int humanBuildPointsPowered; - int alienStage; int humanStage; - int alienKills; - int humanKills; + int alienCredits; + int humanCredits; int alienNextStageThreshold; int humanNextStageThreshold; - int numAlienSpawns; - int numHumanSpawns; - // // locally derived information from gamestate // @@ -1367,7 +1398,9 @@ typedef struct clientInfo_t clientinfo[ MAX_CLIENTS ]; - //TA: corpse info + int teaminfoReceievedTime; + + // corpse info clientInfo_t corpseinfo[ MAX_CLIENTS ]; int cursorX; @@ -1383,57 +1416,69 @@ typedef struct // media cgMedia_t media; + + voice_t *voices; + clientList_t ignoreList; + + // Kill Message + char killMsgKillers[ TEAMCHAT_HEIGHT ][ 33*3+1 ]; + char killMsgVictims[ TEAMCHAT_HEIGHT ][ 33*3+1 ]; + int killMsgWeapons[ TEAMCHAT_HEIGHT ]; + int killMsgMsgTimes[ TEAMCHAT_HEIGHT ]; + int killMsgPos; + int killMsgLastPos; } cgs_t; +typedef struct +{ + char *cmd; + void ( *function )( void ); +} consoleCommand_t; + +typedef struct +{ + char *cmd; + void (*function)( int argNum ); +} consoleCommandCompletions_t; + //============================================================================== extern cgs_t cgs; extern cg_t cg; extern centity_t cg_entities[ MAX_GENTITIES ]; +extern displayContextDef_t cgDC; -//TA: weapon limit expanded: -//extern weaponInfo_t cg_weapons[MAX_WEAPONS]; extern weaponInfo_t cg_weapons[ 32 ]; -//TA: upgrade infos: extern upgradeInfo_t cg_upgrades[ 32 ]; -//TA: buildable infos: extern buildableInfo_t cg_buildables[ BA_NUM_BUILDABLES ]; extern markPoly_t cg_markPolys[ MAX_MARK_POLYS ]; +extern vmCvar_t cg_teslaTrailTime; extern vmCvar_t cg_centertime; extern vmCvar_t cg_runpitch; extern vmCvar_t cg_runroll; -extern vmCvar_t cg_bobup; -extern vmCvar_t cg_bobpitch; -extern vmCvar_t cg_bobroll; extern vmCvar_t cg_swingSpeed; extern vmCvar_t cg_shadows; -extern vmCvar_t cg_gibs; extern vmCvar_t cg_drawTimer; extern vmCvar_t cg_drawClock; extern vmCvar_t cg_drawFPS; extern vmCvar_t cg_drawDemoState; extern vmCvar_t cg_drawSnapshot; -extern vmCvar_t cg_draw3dIcons; -extern vmCvar_t cg_drawIcons; -extern vmCvar_t cg_drawAmmoWarning; +extern vmCvar_t cg_drawChargeBar; extern vmCvar_t cg_drawCrosshair; extern vmCvar_t cg_drawCrosshairNames; -extern vmCvar_t cg_drawRewards; +extern vmCvar_t cg_crosshairSize; extern vmCvar_t cg_drawTeamOverlay; +extern vmCvar_t cg_teamOverlaySortMode; +extern vmCvar_t cg_teamOverlayMaxPlayers; extern vmCvar_t cg_teamOverlayUserinfo; -extern vmCvar_t cg_crosshairX; -extern vmCvar_t cg_crosshairY; -extern vmCvar_t cg_drawStatus; extern vmCvar_t cg_draw2D; extern vmCvar_t cg_animSpeed; extern vmCvar_t cg_debugAnim; extern vmCvar_t cg_debugPosition; extern vmCvar_t cg_debugEvents; -extern vmCvar_t cg_teslaTrailTime; -extern vmCvar_t cg_railTrailTime; extern vmCvar_t cg_errorDecay; extern vmCvar_t cg_nopredict; extern vmCvar_t cg_debugMove; @@ -1441,65 +1486,46 @@ extern vmCvar_t cg_noPlayerAnims; extern vmCvar_t cg_showmiss; extern vmCvar_t cg_footsteps; extern vmCvar_t cg_addMarks; -extern vmCvar_t cg_brassTime; +extern vmCvar_t cg_viewsize; +extern vmCvar_t cg_drawGun; extern vmCvar_t cg_gun_frame; extern vmCvar_t cg_gun_x; extern vmCvar_t cg_gun_y; extern vmCvar_t cg_gun_z; -extern vmCvar_t cg_drawGun; -extern vmCvar_t cg_viewsize; extern vmCvar_t cg_tracerChance; extern vmCvar_t cg_tracerWidth; extern vmCvar_t cg_tracerLength; -extern vmCvar_t cg_autoswitch; -extern vmCvar_t cg_ignore; -extern vmCvar_t cg_simpleItems; -extern vmCvar_t cg_fov; -extern vmCvar_t cg_zoomFov; -extern vmCvar_t cg_thirdPersonRange; -extern vmCvar_t cg_thirdPersonAngle; extern vmCvar_t cg_thirdPerson; +extern vmCvar_t cg_thirdPersonAngle; +extern vmCvar_t cg_thirdPersonShoulderViewMode; +extern vmCvar_t cg_staticDeathCam; +extern vmCvar_t cg_thirdPersonPitchFollow; +extern vmCvar_t cg_thirdPersonRange; extern vmCvar_t cg_stereoSeparation; extern vmCvar_t cg_lagometer; -extern vmCvar_t cg_drawAttacker; +extern vmCvar_t cg_drawSpeed; +extern vmCvar_t cg_maxSpeedTimeWindow; extern vmCvar_t cg_synchronousClients; extern vmCvar_t cg_stats; -extern vmCvar_t cg_forceModel; -extern vmCvar_t cg_buildScript; extern vmCvar_t cg_paused; extern vmCvar_t cg_blood; -extern vmCvar_t cg_predictItems; -extern vmCvar_t cg_deferPlayers; -extern vmCvar_t cg_drawFriend; +extern vmCvar_t cg_teamOverlayUserinfo; extern vmCvar_t cg_teamChatsOnly; extern vmCvar_t cg_noVoiceChats; extern vmCvar_t cg_noVoiceText; -extern vmCvar_t cg_scorePlum; +extern vmCvar_t cg_hudFiles; extern vmCvar_t cg_smoothClients; extern vmCvar_t pmove_fixed; extern vmCvar_t pmove_msec; -//extern vmCvar_t cg_pmove_fixed; -extern vmCvar_t cg_cameraOrbit; -extern vmCvar_t cg_cameraOrbitDelay; +extern vmCvar_t cg_cameraMode; extern vmCvar_t cg_timescaleFadeEnd; extern vmCvar_t cg_timescaleFadeSpeed; extern vmCvar_t cg_timescale; -extern vmCvar_t cg_cameraMode; -extern vmCvar_t cg_smallFont; -extern vmCvar_t cg_bigFont; extern vmCvar_t cg_noTaunt; -extern vmCvar_t cg_noProjectileTrail; -extern vmCvar_t cg_oldRail; -extern vmCvar_t cg_oldRocket; -extern vmCvar_t cg_oldPlasma; -extern vmCvar_t cg_trueLightning; -extern vmCvar_t cg_creepRes; extern vmCvar_t cg_drawSurfNormal; extern vmCvar_t cg_drawBBOX; -extern vmCvar_t cg_debugAlloc; extern vmCvar_t cg_wwSmoothTime; -extern vmCvar_t cg_wwFollow; -extern vmCvar_t cg_wwToggle; +extern vmCvar_t cg_disableBlueprintErrors; extern vmCvar_t cg_depthSortParticles; extern vmCvar_t cg_bounceParticles; extern vmCvar_t cg_consoleLatency; @@ -1508,21 +1534,38 @@ extern vmCvar_t cg_debugParticles; extern vmCvar_t cg_debugTrails; extern vmCvar_t cg_debugPVS; extern vmCvar_t cg_disableWarningDialogs; +extern vmCvar_t cg_disableUpgradeDialogs; +extern vmCvar_t cg_disableBuildDialogs; +extern vmCvar_t cg_disableCommandDialogs; extern vmCvar_t cg_disableScannerPlane; extern vmCvar_t cg_tutorial; +extern vmCvar_t cg_rangeMarkerDrawSurface; +extern vmCvar_t cg_rangeMarkerDrawIntersection; +extern vmCvar_t cg_rangeMarkerDrawFrontline; +extern vmCvar_t cg_rangeMarkerSurfaceOpacity; +extern vmCvar_t cg_rangeMarkerLineOpacity; +extern vmCvar_t cg_rangeMarkerLineThickness; +extern vmCvar_t cg_rangeMarkerForBlueprint; +extern vmCvar_t cg_rangeMarkerBuildableTypes; +extern vmCvar_t cg_binaryShaderScreenScale; + extern vmCvar_t cg_painBlendUpRate; extern vmCvar_t cg_painBlendDownRate; extern vmCvar_t cg_painBlendMax; extern vmCvar_t cg_painBlendScale; extern vmCvar_t cg_painBlendZoom; -//TA: hack to get class an carriage through to UI module +extern vmCvar_t cg_stickySpec; +extern vmCvar_t cg_sprintToggle; +extern vmCvar_t cg_unlagged; + +extern vmCvar_t cg_debugVoices; + extern vmCvar_t ui_currentClass; extern vmCvar_t ui_carriage; extern vmCvar_t ui_stages; extern vmCvar_t ui_dialog; -extern vmCvar_t ui_loading; extern vmCvar_t ui_voteActive; extern vmCvar_t ui_alienTeamVoteActive; extern vmCvar_t ui_humanTeamVoteActive; @@ -1531,7 +1574,22 @@ extern vmCvar_t cg_debugRandom; extern vmCvar_t cg_optimizePrediction; extern vmCvar_t cg_projectileNudge; -extern vmCvar_t cg_unlagged; + +extern vmCvar_t cg_voice; + +extern vmCvar_t cg_emoticons; + +extern vmCvar_t cg_chatTeamPrefix; + +extern vmCvar_t cg_killMsg; +extern vmCvar_t cg_killMsgTime; +extern vmCvar_t cg_killMsgHeight; + +extern vmCvar_t cg_killMsgTime; +extern vmCvar_t cg_killMsgHeight; + +extern vmCvar_t thz_radar; +extern vmCvar_t thz_radarrange; // // cg_main.c @@ -1539,8 +1597,8 @@ extern vmCvar_t cg_unlagged; const char *CG_ConfigString( int index ); const char *CG_Argv( int arg ); -void QDECL CG_Printf( const char *msg, ... ); -void QDECL CG_Error( const char *msg, ... ); +void QDECL CG_Printf( const char *msg, ... ) __attribute__ ((format (printf, 1, 2))); +void QDECL CG_Error( const char *msg, ... ) __attribute__ ((noreturn, format (printf, 1, 2))); void CG_StartMusic( void ); int CG_PlayerCount( void ); @@ -1554,17 +1612,22 @@ void CG_KeyEvent( int key, qboolean down ); void CG_MouseEvent( int x, int y ); void CG_EventHandling( int type ); void CG_SetScoreSelection( void *menu ); +qboolean CG_ClientIsReady( int clientNum ); void CG_BuildSpectatorString( void ); -qboolean CG_FileExists( char *filename ); +qboolean CG_FileExists( const char *filename ); void CG_RemoveNotifyLine( void ); void CG_AddNotifyText( void ); +qboolean CG_GetRangeMarkerPreferences( qboolean *drawSurface, qboolean *drawIntersection, + qboolean *drawFrontline, float *surfaceOpacity, + float *lineOpacity, float *lineThickness ); +void CG_UpdateBuildableRangeMarkerMask( void ); // // cg_view.c // -void CG_addSmoothOp( vec3_t rotAxis, float rotAngle, float timeMod ); //TA +void CG_addSmoothOp( vec3_t rotAxis, float rotAngle, float timeMod ); void CG_TestModel_f( void ); void CG_TestGun_f( void ); void CG_TestModelNextFrame_f( void ); @@ -1573,6 +1636,9 @@ void CG_TestModelNextSkin_f( void ); void CG_TestModelPrevSkin_f( void ); void CG_AddBufferedSound( sfxHandle_t sfx ); void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); +void CG_OffsetFirstPersonView( void ); +void CG_OffsetThirdPersonView( void ); +void CG_OffsetShoulderView( void ); // @@ -1584,37 +1650,41 @@ void CG_FillRect( float x, float y, float width, float height, const floa void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader ); void CG_DrawFadePic( float x, float y, float width, float height, vec4_t fcolor, vec4_t tcolor, float amount, qhandle_t hShader ); +void CG_SetClipRegion( float x, float y, float w, float h ); +void CG_ClearClipRegion( void ); int CG_DrawStrlen( const char *str ); float *CG_FadeColor( int startMsec, int totalMsec ); void CG_TileClear( void ); void CG_ColorForHealth( vec4_t hcolor ); -void CG_GetColorForHealth( int health, int armor, vec4_t hcolor ); - void CG_DrawRect( float x, float y, float width, float height, float size, const float *color ); void CG_DrawSides(float x, float y, float w, float h, float size); void CG_DrawTopBottom(float x, float y, float w, float h, float size); qboolean CG_WorldToScreen( vec3_t point, float *x, float *y ); char *CG_KeyBinding( const char *bind ); - +char CG_GetColorCharForHealth( int clientnum ); +void CG_DrawSphere( const vec3_t center, float radius, int customShader, const float *shaderRGBA ); +void CG_DrawSphericalCone( const vec3_t tip, const vec3_t rotation, float radius, + qboolean a240, int customShader, const float *shaderRGBA ); +void CG_DrawRangeMarker( rangeMarkerType_t rmType, const vec3_t origin, const float *angles, float range, + qboolean drawSurface, qboolean drawIntersection, qboolean drawFrontline, + const vec3_t rgb, float surfaceOpacity, float lineOpacity, float lineThickness ); // // cg_draw.c // -extern int sortedTeamPlayers[ TEAM_MAXOVERLAY ]; -extern int numSortedTeamPlayers; void CG_AddLagometerFrameInfo( void ); void CG_AddLagometerSnapshotInfo( snapshot_t *snap ); +void CG_AddSpeed( void ); void CG_CenterPrint( const char *str, int y, int charWidth ); void CG_DrawActive( stereoFrame_t stereoView ); -void CG_OwnerDraw( float x, float y, float w, float h, float text_x, float text_y, - int ownerDraw, int ownerDrawFlags, int align, float special, - float scale, vec4_t color, qhandle_t shader, int textStyle); -void CG_Text_Paint( float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style ); -int CG_Text_Width( const char *text, float scale, int limit ); -int CG_Text_Height( const char *text, float scale, int limit ); +void CG_OwnerDraw( float x, float y, float w, float h, float text_x, + float text_y, int ownerDraw, int ownerDrawFlags, + int align, int textalign, int textvalign, + float borderSize, float scale, vec4_t foreColor, + vec4_t backColor, qhandle_t shader, int textStyle ); float CG_GetValue(int ownerDraw); void CG_RunMenuScript(char **args); void CG_SetPrintString( int type, const char *p ); @@ -1633,13 +1703,12 @@ void CG_DrawField( float x, float y, int width, float cw, float ch, int v void CG_Player( centity_t *cent ); void CG_Corpse( centity_t *cent ); void CG_ResetPlayerEntity( centity_t *cent ); -void CG_AddRefEntityWithPowerups( refEntity_t *ent, int powerups, int team ); void CG_NewClientInfo( int clientNum ); -void CG_PrecacheClientInfo( pClass_t class, char *model, char *skin ); +void CG_PrecacheClientInfo( class_t class, char *model, char *skin ); sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ); void CG_PlayerDisconnect( vec3_t org ); void CG_Bleed( vec3_t origin, vec3_t normal, int entityNum ); -qboolean CG_AtHighestClass( void ); +centity_t *CG_GetPlayerLocation( void ); // // cg_buildable.c @@ -1651,11 +1720,12 @@ void CG_DrawBuildableStatus( void ); void CG_InitBuildables( void ); void CG_HumanBuildableExplosion( vec3_t origin, vec3_t dir ); void CG_AlienBuildableExplosion( vec3_t origin, vec3_t dir ); +qboolean CG_GetBuildableRangeMarkerProperties( buildable_t bType, rangeMarkerType_t *rmType, float *range, vec3_t rgb ); // // cg_animation.c // -void CG_RunLerpFrame( lerpFrame_t *lf ); +void CG_RunLerpFrame( lerpFrame_t *lf, float scale ); // // cg_animmapobj.c @@ -1687,6 +1757,7 @@ void CG_CheckEvents( centity_t *cent ); void CG_EntityEvent( centity_t *cent, vec3_t position ); void CG_PainEvent( centity_t *cent, int health ); +void CG_AddToKillMsg( const char* killername, const char* victimname, int icon ); // // cg_ents.c @@ -1701,8 +1772,7 @@ void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *pare qhandle_t parentModel, char *tagName ); void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, qhandle_t parentModel, char *tagName ); - - +void CG_RangeMarker( centity_t *cent ); // @@ -1719,8 +1789,9 @@ void CG_RegisterWeapon( int weaponNum ); void CG_FireWeapon( centity_t *cent, weaponMode_t weaponMode ); void CG_MissileHitWall( weapon_t weapon, weaponMode_t weaponMode, int clientNum, - vec3_t origin, vec3_t dir, impactSound_t soundType ); -void CG_MissileHitPlayer( weapon_t weapon, weaponMode_t weaponMode, vec3_t origin, vec3_t dir, int entityNum ); + vec3_t origin, vec3_t dir, impactSound_t soundType, int charge ); +void CG_MissileHitEntity( weapon_t weaponNum, weaponMode_t weaponMode, + vec3_t origin, vec3_t dir, int entityNum, int charge ); void CG_Bullet( vec3_t origin, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum ); void CG_ShotgunFire( entityState_t *es ); @@ -1736,6 +1807,7 @@ void CG_DrawItemSelectText( rectDef_t *rect, float scale, int textStyle ) void CG_UpdateEntityPositions( void ); void CG_Scanner( rectDef_t *rect, qhandle_t shader, vec4_t color ); void CG_AlienSense( rectDef_t *rect ); +void THZ_DrawScanner( rectDef_t *rect ); // // cg_marks.c @@ -1757,6 +1829,7 @@ void CG_ProcessSnapshots( void ); // // cg_consolecmds.c // +qboolean CG_Console_CompleteArgument( int argNum ); qboolean CG_ConsoleCommand( void ); void CG_InitConsoleCommands( void ); qboolean CG_RequestScores( void ); @@ -1768,6 +1841,8 @@ void CG_ExecuteNewServerCommands( int latestSequence ); void CG_ParseServerinfo( void ); void CG_SetConfigValues( void ); void CG_ShaderStateChanged(void); +void CG_UnregisterCommands( void ); +void CG_CenterPrint_f( void ); // // cg_playerstate.c @@ -1776,14 +1851,6 @@ void CG_Respawn( void ); void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ); void CG_CheckChangedPredictableEvents( playerState_t *ps ); -// -// cg_mem.c -// -void CG_InitMemory( void ); -void *CG_Alloc( int size ); -void CG_Free( void *ptr ); -void CG_DefragmentMemory( void ); - // // cg_attachment.c // @@ -1820,6 +1887,7 @@ qboolean CG_IsParticleSystemInfinite( particleSystem_t *ps ); qboolean CG_IsParticleSystemValid( particleSystem_t **ps ); void CG_SetParticleSystemNormal( particleSystem_t *ps, vec3_t normal ); +void CG_SetParticleSystemLastNormal( particleSystem_t *ps, const float *normal ); void CG_AddParticles( void ); @@ -1855,6 +1923,20 @@ void CG_WritePTRCode( int code ); // const char *CG_TutorialText( void ); +// cg_main.c +qboolean CG_GetRangeMarkerPreferences( qboolean *drawSurface, qboolean *drawIntersection, + qboolean *drawFrontline, float *surfaceOpacity, + float *lineOpacity, float *lineThickness ); +// cg_drawtools.c +void CG_DrawRangeMarker( rangeMarkerType_t rmType, const vec3_t origin, const float *angles, float range, + qboolean drawSurface, qboolean drawIntersection, qboolean drawFrontline, + const vec3_t rgb, float surfaceOpacity, float lineOpacity, float lineThickness ); +// cg_buildable.c +qboolean CG_GetBuildableRangeMarkerProperties( buildable_t bType, rangeMarkerType_t *rmType, float *range, vec3_t rgb ); +void CG_GhostBuildableRangeMarker( buildable_t buildable, const vec3_t origin, const vec3_t normal ); +// cg_ents.c +void CG_RangeMarker( centity_t *cent ); + // //=============================================== @@ -1868,7 +1950,7 @@ const char *CG_TutorialText( void ); void trap_Print( const char *fmt ); // abort the game -void trap_Error( const char *fmt ); +void trap_Error( const char *fmt ) __attribute__((noreturn)); // milliseconds should only be used for performance tuning, never // for anything game related. Get time from the CG_DrawActiveFrame parameter @@ -1888,11 +1970,11 @@ void trap_LiteralArgs( char *buffer, int bufferLength ); // filesystem access // returns length of file -int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, enum FS_Mode mode ); void trap_FS_Read( void *buffer, int len, fileHandle_t f ); void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); void trap_FS_FCloseFile( fileHandle_t f ); -void trap_FS_Seek( fileHandle_t f, long offset, fsOrigin_t origin ); // fsOrigin_t +void trap_FS_Seek( fileHandle_t f, long offset, enum FS_Origin origin ); // fsOrigin_t int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); @@ -1905,6 +1987,7 @@ void trap_SendConsoleCommand( const char *text ); // register a command name so the console can perform command completion. // FIXME: replace this with a normal console command "defineCommand"? void trap_AddCommand( const char *cmdName ); +void trap_RemoveCommand( const char *cmdName ); // send a string to the server over the network void trap_SendClientCommand( const char *s ); @@ -1985,11 +2068,13 @@ void trap_R_AddRefEntityToScene( const refEntity_t *re ); // significant construction void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ); void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int numPolys ); +qboolean trap_R_inPVS( const vec3_t p1, const vec3_t p2 ); void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ); void trap_R_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ); int trap_R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ); void trap_R_RenderScene( const refdef_t *fd ); void trap_R_SetColor( const float *rgba ); // NULL = 1,1,1,1 +void trap_R_SetClipRegion( const float *region ); void trap_R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ); void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ); @@ -2180,6 +2265,8 @@ int trap_Key_GetKey( const char *binding ); void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen ); void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen ); void trap_Key_SetBinding( int keynum, const char *binding ); +void trap_Key_SetOverstrikeMode( qboolean state ); +qboolean trap_Key_GetOverstrikeMode( void ); int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits ); e_status trap_CIN_StopCinematic( int handle ); @@ -2199,9 +2286,19 @@ qboolean trap_GetEntityToken( char *buffer, int bufferSize ); int trap_GetDemoState( void ); int trap_GetDemoPos( void ); void trap_GetDemoName( char *buffer, int size ); +void trap_Field_CompleteList( char *listJson ); // cg_drawCrosshair settings #define CROSSHAIR_ALWAYSOFF 0 #define CROSSHAIR_RANGEDONLY 1 #define CROSSHAIR_ALWAYSON 2 +// menu types for cg_disable*Dialogs +typedef enum +{ + DT_INTERACTIVE, // team, class, armoury + DT_ARMOURYEVOLVE, // Insufficient funds et al + DT_BUILD, // build errors + DT_COMMAND, // You must be living/human/spec etc. + +} dialogType_t; diff --git a/src/cgame/cg_main.c b/src/cgame/cg_main.c index eb19dcb..7ec4ae9 100644 --- a/src/cgame/cg_main.c +++ b/src/cgame/cg_main.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,25 +17,22 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_main.c -- initialization and primary entry point for cgame - #include "cg_local.h" +#include "ui/ui_shared.h" -#include "../ui/ui_shared.h" // display context for new ui stuff displayContextDef_t cgDC; -int forceModelModificationCount = -1; - void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ); void CG_Shutdown( void ); -char *CG_VoIPString( void ); +static char *CG_VoIPString( void ); /* ================ @@ -44,9 +42,7 @@ This is the only way control passes into the module. This must be the very first function compiled into the .q3vm file ================ */ -Q_EXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3, - int arg4, int arg5, int arg6, int arg7, - int arg8, int arg9, int arg10, int arg11 ) +Q_EXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2 ) { switch( command ) { @@ -80,9 +76,7 @@ Q_EXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3, return 0; case CG_MOUSE_EVENT: - cgDC.cursorx = cgs.cursorX; - cgDC.cursory = cgs.cursorY; - CG_MouseEvent( arg0, arg1 ); + // cgame doesn't care where the cursor is return 0; case CG_EVENT_HANDLING: @@ -92,6 +86,9 @@ Q_EXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3, #ifndef MODULE_INTERFACE_11 case CG_VOIP_STRING: return (intptr_t)CG_VoIPString( ); + + case CG_CONSOLE_COMPLETARGUMENT: + return CG_Console_CompleteArgument( arg0 ); #endif default: @@ -107,39 +104,27 @@ cg_t cg; cgs_t cgs; centity_t cg_entities[ MAX_GENTITIES ]; -//TA: weapons limit expanded: -//weaponInfo_t cg_weapons[MAX_WEAPONS]; weaponInfo_t cg_weapons[ 32 ]; upgradeInfo_t cg_upgrades[ 32 ]; buildableInfo_t cg_buildables[ BA_NUM_BUILDABLES ]; vmCvar_t cg_teslaTrailTime; -vmCvar_t cg_railTrailTime; vmCvar_t cg_centertime; vmCvar_t cg_runpitch; vmCvar_t cg_runroll; -vmCvar_t cg_bobup; -vmCvar_t cg_bobpitch; -vmCvar_t cg_bobroll; vmCvar_t cg_swingSpeed; vmCvar_t cg_shadows; -vmCvar_t cg_gibs; vmCvar_t cg_drawTimer; vmCvar_t cg_drawClock; vmCvar_t cg_drawFPS; vmCvar_t cg_drawDemoState; vmCvar_t cg_drawSnapshot; -vmCvar_t cg_draw3dIcons; -vmCvar_t cg_drawIcons; -vmCvar_t cg_drawAmmoWarning; +vmCvar_t cg_drawChargeBar; vmCvar_t cg_drawCrosshair; vmCvar_t cg_drawCrosshairNames; -vmCvar_t cg_drawRewards; -vmCvar_t cg_crosshairX; -vmCvar_t cg_crosshairY; +vmCvar_t cg_crosshairSize; vmCvar_t cg_draw2D; -vmCvar_t cg_drawStatus; vmCvar_t cg_animSpeed; vmCvar_t cg_debugAnim; vmCvar_t cg_debugPosition; @@ -151,7 +136,6 @@ vmCvar_t cg_noPlayerAnims; vmCvar_t cg_showmiss; vmCvar_t cg_footsteps; vmCvar_t cg_addMarks; -vmCvar_t cg_brassTime; vmCvar_t cg_viewsize; vmCvar_t cg_drawGun; vmCvar_t cg_gun_frame; @@ -161,59 +145,41 @@ vmCvar_t cg_gun_z; vmCvar_t cg_tracerChance; vmCvar_t cg_tracerWidth; vmCvar_t cg_tracerLength; -vmCvar_t cg_autoswitch; -vmCvar_t cg_ignore; -vmCvar_t cg_simpleItems; -vmCvar_t cg_fov; -vmCvar_t cg_zoomFov; vmCvar_t cg_thirdPerson; -vmCvar_t cg_thirdPersonRange; vmCvar_t cg_thirdPersonAngle; +vmCvar_t cg_thirdPersonShoulderViewMode; +vmCvar_t cg_staticDeathCam; +vmCvar_t cg_thirdPersonPitchFollow; +vmCvar_t cg_thirdPersonRange; vmCvar_t cg_stereoSeparation; vmCvar_t cg_lagometer; -vmCvar_t cg_drawAttacker; +vmCvar_t cg_drawSpeed; +vmCvar_t cg_maxSpeedTimeWindow; vmCvar_t cg_synchronousClients; vmCvar_t cg_stats; -vmCvar_t cg_buildScript; -vmCvar_t cg_forceModel; vmCvar_t cg_paused; vmCvar_t cg_blood; -vmCvar_t cg_predictItems; -vmCvar_t cg_deferPlayers; +vmCvar_t cg_teamChatsOnly; vmCvar_t cg_drawTeamOverlay; +vmCvar_t cg_teamOverlaySortMode; +vmCvar_t cg_teamOverlayMaxPlayers; vmCvar_t cg_teamOverlayUserinfo; -vmCvar_t cg_drawFriend; -vmCvar_t cg_teamChatsOnly; +vmCvar_t cg_noPrintDuplicate; vmCvar_t cg_noVoiceChats; vmCvar_t cg_noVoiceText; vmCvar_t cg_hudFiles; -vmCvar_t cg_scorePlum; vmCvar_t cg_smoothClients; vmCvar_t pmove_fixed; -//vmCvar_t cg_pmove_fixed; vmCvar_t pmove_msec; -vmCvar_t cg_pmove_msec; vmCvar_t cg_cameraMode; -vmCvar_t cg_cameraOrbit; -vmCvar_t cg_cameraOrbitDelay; vmCvar_t cg_timescaleFadeEnd; vmCvar_t cg_timescaleFadeSpeed; vmCvar_t cg_timescale; -vmCvar_t cg_smallFont; -vmCvar_t cg_bigFont; vmCvar_t cg_noTaunt; -vmCvar_t cg_noProjectileTrail; -vmCvar_t cg_oldRail; -vmCvar_t cg_oldRocket; -vmCvar_t cg_oldPlasma; -vmCvar_t cg_trueLightning; -vmCvar_t cg_creepRes; vmCvar_t cg_drawSurfNormal; vmCvar_t cg_drawBBOX; -vmCvar_t cg_debugAlloc; vmCvar_t cg_wwSmoothTime; -vmCvar_t cg_wwFollow; -vmCvar_t cg_wwToggle; +vmCvar_t cg_disableBlueprintErrors; vmCvar_t cg_depthSortParticles; vmCvar_t cg_bounceParticles; vmCvar_t cg_consoleLatency; @@ -222,21 +188,38 @@ vmCvar_t cg_debugParticles; vmCvar_t cg_debugTrails; vmCvar_t cg_debugPVS; vmCvar_t cg_disableWarningDialogs; +vmCvar_t cg_disableUpgradeDialogs; +vmCvar_t cg_disableBuildDialogs; +vmCvar_t cg_disableCommandDialogs; vmCvar_t cg_disableScannerPlane; vmCvar_t cg_tutorial; +vmCvar_t cg_rangeMarkerDrawSurface; +vmCvar_t cg_rangeMarkerDrawIntersection; +vmCvar_t cg_rangeMarkerDrawFrontline; +vmCvar_t cg_rangeMarkerSurfaceOpacity; +vmCvar_t cg_rangeMarkerLineOpacity; +vmCvar_t cg_rangeMarkerLineThickness; +vmCvar_t cg_rangeMarkerForBlueprint; +vmCvar_t cg_rangeMarkerBuildableTypes; +vmCvar_t cg_binaryShaderScreenScale; + vmCvar_t cg_painBlendUpRate; vmCvar_t cg_painBlendDownRate; vmCvar_t cg_painBlendMax; vmCvar_t cg_painBlendScale; vmCvar_t cg_painBlendZoom; -//TA: hack to get class and carriage through to UI module +vmCvar_t cg_stickySpec; +vmCvar_t cg_sprintToggle; +vmCvar_t cg_unlagged; + +vmCvar_t cg_debugVoices; + vmCvar_t ui_currentClass; vmCvar_t ui_carriage; vmCvar_t ui_stages; vmCvar_t ui_dialog; -vmCvar_t ui_loading; vmCvar_t ui_voteActive; vmCvar_t ui_alienTeamVoteActive; vmCvar_t ui_humanTeamVoteActive; @@ -245,8 +228,19 @@ vmCvar_t cg_debugRandom; vmCvar_t cg_optimizePrediction; vmCvar_t cg_projectileNudge; -vmCvar_t cg_unlagged; +vmCvar_t cg_voice; + +vmCvar_t cg_emoticons; + +vmCvar_t cg_chatTeamPrefix; + +vmCvar_t cg_killMsg; +vmCvar_t cg_killMsgTime; +vmCvar_t cg_killMsgHeight; + +vmCvar_t thz_radar; +vmCvar_t thz_radarrange; typedef struct { @@ -258,46 +252,31 @@ typedef struct static cvarTable_t cvarTable[ ] = { - { &cg_ignore, "cg_ignore", "0", 0 }, // used for debugging - { &cg_autoswitch, "cg_autoswitch", "1", CVAR_ARCHIVE }, { &cg_drawGun, "cg_drawGun", "1", CVAR_ARCHIVE }, - { &cg_zoomFov, "cg_zoomfov", "22.5", CVAR_ARCHIVE }, - { &cg_fov, "cg_fov", "90", CVAR_ARCHIVE }, { &cg_viewsize, "cg_viewsize", "100", CVAR_ARCHIVE }, { &cg_stereoSeparation, "cg_stereoSeparation", "0.4", CVAR_ARCHIVE }, { &cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE }, - { &cg_gibs, "cg_gibs", "1", CVAR_ARCHIVE }, { &cg_draw2D, "cg_draw2D", "1", CVAR_ARCHIVE }, - { &cg_drawStatus, "cg_drawStatus", "1", CVAR_ARCHIVE }, { &cg_drawTimer, "cg_drawTimer", "1", CVAR_ARCHIVE }, { &cg_drawClock, "cg_drawClock", "0", CVAR_ARCHIVE }, { &cg_drawFPS, "cg_drawFPS", "1", CVAR_ARCHIVE }, { &cg_drawDemoState, "cg_drawDemoState", "1", CVAR_ARCHIVE }, { &cg_drawSnapshot, "cg_drawSnapshot", "0", CVAR_ARCHIVE }, - { &cg_draw3dIcons, "cg_draw3dIcons", "1", CVAR_ARCHIVE }, - { &cg_drawIcons, "cg_drawIcons", "1", CVAR_ARCHIVE }, - { &cg_drawAmmoWarning, "cg_drawAmmoWarning", "1", CVAR_ARCHIVE }, - { &cg_drawAttacker, "cg_drawAttacker", "1", CVAR_ARCHIVE }, - { &cg_drawCrosshair, "cg_drawCrosshair", "1", CVAR_ARCHIVE }, + { &cg_drawChargeBar, "cg_drawChargeBar", "1", CVAR_ARCHIVE }, + { &cg_drawCrosshair, "cg_drawCrosshair", "2", CVAR_ARCHIVE }, { &cg_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE }, - { &cg_drawRewards, "cg_drawRewards", "1", CVAR_ARCHIVE }, - { &cg_crosshairX, "cg_crosshairX", "0", CVAR_ARCHIVE }, - { &cg_crosshairY, "cg_crosshairY", "0", CVAR_ARCHIVE }, - { &cg_brassTime, "cg_brassTime", "2500", CVAR_ARCHIVE }, - { &cg_simpleItems, "cg_simpleItems", "0", CVAR_ARCHIVE }, + { &cg_crosshairSize, "cg_crosshairSize", "1", CVAR_ARCHIVE }, { &cg_addMarks, "cg_marks", "1", CVAR_ARCHIVE }, { &cg_lagometer, "cg_lagometer", "0", CVAR_ARCHIVE }, + { &cg_drawSpeed, "cg_drawSpeed", "0", CVAR_ARCHIVE }, + { &cg_maxSpeedTimeWindow, "cg_maxSpeedTimeWindow", "2000", CVAR_ARCHIVE }, { &cg_teslaTrailTime, "cg_teslaTrailTime", "250", CVAR_ARCHIVE }, - { &cg_railTrailTime, "cg_railTrailTime", "400", CVAR_ARCHIVE }, { &cg_gun_x, "cg_gunX", "0", CVAR_CHEAT }, { &cg_gun_y, "cg_gunY", "0", CVAR_CHEAT }, { &cg_gun_z, "cg_gunZ", "0", CVAR_CHEAT }, { &cg_centertime, "cg_centertime", "3", CVAR_CHEAT }, { &cg_runpitch, "cg_runpitch", "0.002", CVAR_ARCHIVE}, { &cg_runroll, "cg_runroll", "0.005", CVAR_ARCHIVE }, - { &cg_bobup , "cg_bobup", "0.005", CVAR_CHEAT }, - { &cg_bobpitch, "cg_bobpitch", "0.002", CVAR_ARCHIVE }, - { &cg_bobroll, "cg_bobroll", "0.002", CVAR_ARCHIVE }, { &cg_swingSpeed, "cg_swingSpeed", "0.3", CVAR_CHEAT }, { &cg_animSpeed, "cg_animspeed", "1", CVAR_CHEAT }, { &cg_debugAnim, "cg_debuganim", "0", CVAR_CHEAT }, @@ -312,27 +291,31 @@ static cvarTable_t cvarTable[ ] = { &cg_tracerChance, "cg_tracerchance", "0.4", CVAR_CHEAT }, { &cg_tracerWidth, "cg_tracerwidth", "1", CVAR_CHEAT }, { &cg_tracerLength, "cg_tracerlength", "100", CVAR_CHEAT }, - { &cg_thirdPersonRange, "cg_thirdPersonRange", "40", CVAR_CHEAT }, - { &cg_thirdPersonAngle, "cg_thirdPersonAngle", "0", CVAR_CHEAT }, + { &cg_thirdPersonRange, "cg_thirdPersonRange", "75", CVAR_ARCHIVE }, { &cg_thirdPerson, "cg_thirdPerson", "0", CVAR_CHEAT }, - { &cg_forceModel, "cg_forceModel", "0", CVAR_ARCHIVE }, - { &cg_predictItems, "cg_predictItems", "1", CVAR_ARCHIVE }, - { &cg_deferPlayers, "cg_deferPlayers", "1", CVAR_ARCHIVE }, - { &cg_drawTeamOverlay, "cg_drawTeamOverlay", "0", CVAR_ARCHIVE }, - { &cg_teamOverlayUserinfo, "teamoverlay", "0", CVAR_ROM | CVAR_USERINFO }, + { &cg_thirdPersonAngle, "cg_thirdPersonAngle", "0", CVAR_CHEAT }, + { &cg_thirdPersonPitchFollow, "cg_thirdPersonPitchFollow", "0", 0 }, + { &cg_thirdPersonShoulderViewMode, "cg_thirdPersonShoulderViewMode", "1", CVAR_ARCHIVE }, + { &cg_staticDeathCam, "cg_staticDeathCam", "0", CVAR_ARCHIVE }, { &cg_stats, "cg_stats", "0", 0 }, - { &cg_drawFriend, "cg_drawFriend", "1", CVAR_ARCHIVE }, + { &cg_drawTeamOverlay, "cg_drawTeamOverlay", "1", CVAR_ARCHIVE }, + { &cg_teamOverlaySortMode, "cg_teamOverlaySortMode", "1", CVAR_ARCHIVE }, + { &cg_teamOverlayMaxPlayers, "cg_teamOverlayMaxPlayers", "8", CVAR_ARCHIVE }, + { &cg_teamOverlayUserinfo, "teamoverlay", "1", CVAR_ARCHIVE|CVAR_USERINFO }, { &cg_teamChatsOnly, "cg_teamChatsOnly", "0", CVAR_ARCHIVE }, + { &cg_noPrintDuplicate, "cg_noPrintDuplicate", "0", CVAR_ARCHIVE }, { &cg_noVoiceChats, "cg_noVoiceChats", "0", CVAR_ARCHIVE }, { &cg_noVoiceText, "cg_noVoiceText", "0", CVAR_ARCHIVE }, - { &cg_creepRes, "cg_creepRes", "16", CVAR_ARCHIVE }, { &cg_drawSurfNormal, "cg_drawSurfNormal", "0", CVAR_CHEAT }, { &cg_drawBBOX, "cg_drawBBOX", "0", CVAR_CHEAT }, - { &cg_debugAlloc, "cg_debugAlloc", "0", 0 }, { &cg_wwSmoothTime, "cg_wwSmoothTime", "300", CVAR_ARCHIVE }, - { &cg_wwFollow, "cg_wwFollow", "1", CVAR_ARCHIVE|CVAR_USERINFO }, - { &cg_wwToggle, "cg_wwToggle", "1", CVAR_ARCHIVE|CVAR_USERINFO }, + { NULL, "cg_wwFollow", "1", CVAR_ARCHIVE|CVAR_USERINFO }, + { NULL, "cg_wwToggle", "1", CVAR_ARCHIVE|CVAR_USERINFO }, + { NULL, "cg_disableBlueprintErrors", "0", CVAR_ARCHIVE|CVAR_USERINFO }, + { &cg_stickySpec, "cg_stickySpec", "1", CVAR_ARCHIVE|CVAR_USERINFO }, + { &cg_sprintToggle, "cg_sprintToggle", "0", CVAR_ARCHIVE|CVAR_USERINFO }, { &cg_unlagged, "cg_unlagged", "1", CVAR_ARCHIVE|CVAR_USERINFO }, + { NULL, "cg_flySpeed", "600", CVAR_ARCHIVE|CVAR_USERINFO }, { &cg_depthSortParticles, "cg_depthSortParticles", "1", CVAR_ARCHIVE }, { &cg_bounceParticles, "cg_bounceParticles", "0", CVAR_ARCHIVE }, { &cg_consoleLatency, "cg_consoleLatency", "3000", CVAR_ARCHIVE }, @@ -341,24 +324,44 @@ static cvarTable_t cvarTable[ ] = { &cg_debugTrails, "cg_debugTrails", "0", CVAR_CHEAT }, { &cg_debugPVS, "cg_debugPVS", "0", CVAR_CHEAT }, { &cg_disableWarningDialogs, "cg_disableWarningDialogs", "0", CVAR_ARCHIVE }, - { &cg_disableScannerPlane, "cg_disableScannerPlane", "0", CVAR_ARCHIVE }, + { &cg_disableUpgradeDialogs, "cg_disableUpgradeDialogs", "0", CVAR_ARCHIVE }, + { &cg_disableBuildDialogs, "cg_disableBuildDialogs", "0", CVAR_ARCHIVE }, + { &cg_disableCommandDialogs, "cg_disableCommandDialogs", "0", CVAR_ARCHIVE }, + { &cg_disableScannerPlane, "cg_disableScannerPlane", "1", CVAR_ARCHIVE }, { &cg_tutorial, "cg_tutorial", "1", CVAR_ARCHIVE }, + + { &cg_rangeMarkerDrawSurface, "cg_rangeMarkerDrawSurface", "1", CVAR_ARCHIVE }, + { &cg_rangeMarkerDrawIntersection, "cg_rangeMarkerDrawIntersection", "1", CVAR_ARCHIVE }, + { &cg_rangeMarkerDrawFrontline, "cg_rangeMarkerDrawFrontline", "1", CVAR_ARCHIVE }, + { &cg_rangeMarkerSurfaceOpacity, "cg_rangeMarkerSurfaceOpacity", "0.08", CVAR_ARCHIVE }, + { &cg_rangeMarkerLineOpacity, "cg_rangeMarkerLineOpacity", "0.4", CVAR_ARCHIVE }, + { &cg_rangeMarkerLineThickness, "cg_rangeMarkerLineThickness", "4.0", CVAR_ARCHIVE }, + { &cg_rangeMarkerForBlueprint, "cg_rangeMarkerForBlueprint", "1", CVAR_ARCHIVE }, + { &cg_rangeMarkerBuildableTypes, "cg_rangeMarkerBuildableTypes", "support", CVAR_ARCHIVE }, + { NULL, "cg_buildableRangeMarkerMask", "", CVAR_USERINFO }, + { &cg_binaryShaderScreenScale, "cg_binaryShaderScreenScale", "1.0", CVAR_ARCHIVE }, + { &cg_hudFiles, "cg_hudFiles", "ui/hud.txt", CVAR_ARCHIVE}, + { NULL, "cg_alienConfig", "", CVAR_ARCHIVE }, + { NULL, "cg_humanConfig", "", CVAR_ARCHIVE }, + { NULL, "cg_spectatorConfig", "", CVAR_ARCHIVE }, { &cg_painBlendUpRate, "cg_painBlendUpRate", "10.0", 0 }, { &cg_painBlendDownRate, "cg_painBlendDownRate", "0.5", 0 }, { &cg_painBlendMax, "cg_painBlendMax", "0.7", 0 }, { &cg_painBlendScale, "cg_painBlendScale", "7.0", 0 }, { &cg_painBlendZoom, "cg_painBlendZoom", "0.65", 0 }, + + { &cg_debugVoices, "cg_debugVoices", "0", 0 }, - { &ui_currentClass, "ui_currentClass", "0", 0 }, - { &ui_carriage, "ui_carriage", "", 0 }, - { &ui_stages, "ui_stages", "0 0", 0 }, - { &ui_dialog, "ui_dialog", "Text not set", 0 }, - { &ui_loading, "ui_loading", "0", 0 }, - { &ui_voteActive, "ui_voteActive", "0", 0 }, - { &ui_humanTeamVoteActive, "ui_humanTeamVoteActive", "0", 0 }, - { &ui_alienTeamVoteActive, "ui_alienTeamVoteActive", "0", 0 }, + // communication cvars set by the cgame to be read by ui + { &ui_currentClass, "ui_currentClass", "0", CVAR_ROM }, + { &ui_carriage, "ui_carriage", "", CVAR_ROM }, + { &ui_stages, "ui_stages", "0 0", CVAR_ROM }, + { &ui_dialog, "ui_dialog", "Text not set", CVAR_ROM }, + { &ui_voteActive, "ui_voteActive", "0", CVAR_ROM }, + { &ui_humanTeamVoteActive, "ui_humanTeamVoteActive", "0", CVAR_ROM }, + { &ui_alienTeamVoteActive, "ui_alienTeamVoteActive", "0", CVAR_ROM }, { &cg_debugRandom, "cg_debugRandom", "0", 0 }, @@ -368,33 +371,36 @@ static cvarTable_t cvarTable[ ] = // the following variables are created in other parts of the system, // but we also reference them here - { &cg_buildScript, "com_buildScript", "0", 0 }, // force loading of all possible data amd error on failures { &cg_paused, "cl_paused", "0", CVAR_ROM }, - { &cg_blood, "com_blood", "1", CVAR_ARCHIVE }, + { &cg_blood, "cg_blood", "1", CVAR_ARCHIVE }, { &cg_synchronousClients, "g_synchronousClients", "0", 0 }, // communicated by systeminfo - { &cg_cameraOrbit, "cg_cameraOrbit", "0", CVAR_CHEAT}, - { &cg_cameraOrbitDelay, "cg_cameraOrbitDelay", "50", CVAR_ARCHIVE}, - { &cg_timescaleFadeEnd, "cg_timescaleFadeEnd", "1", 0}, - { &cg_timescaleFadeSpeed, "cg_timescaleFadeSpeed", "0", 0}, + { &cg_timescaleFadeEnd, "cg_timescaleFadeEnd", "1", CVAR_CHEAT }, + { &cg_timescaleFadeSpeed, "cg_timescaleFadeSpeed", "0", CVAR_CHEAT }, { &cg_timescale, "timescale", "1", 0}, - { &cg_scorePlum, "cg_scorePlums", "1", CVAR_USERINFO | CVAR_ARCHIVE}, { &cg_smoothClients, "cg_smoothClients", "0", CVAR_USERINFO | CVAR_ARCHIVE}, { &cg_cameraMode, "com_cameraMode", "0", CVAR_CHEAT}, { &pmove_fixed, "pmove_fixed", "0", 0}, { &pmove_msec, "pmove_msec", "8", 0}, { &cg_noTaunt, "cg_noTaunt", "0", CVAR_ARCHIVE}, - { &cg_noProjectileTrail, "cg_noProjectileTrail", "0", CVAR_ARCHIVE}, - { &cg_smallFont, "ui_smallFont", "0.2", CVAR_ARCHIVE}, - { &cg_bigFont, "ui_bigFont", "0.5", CVAR_ARCHIVE}, - { &cg_oldRail, "cg_oldRail", "1", CVAR_ARCHIVE}, - { &cg_oldRocket, "cg_oldRocket", "1", CVAR_ARCHIVE}, - { &cg_oldPlasma, "cg_oldPlasma", "1", CVAR_ARCHIVE}, - { &cg_trueLightning, "cg_trueLightning", "0.0", CVAR_ARCHIVE} -// { &cg_pmove_fixed, "cg_pmove_fixed", "0", CVAR_USERINFO | CVAR_ARCHIVE } + + { &cg_voice, "voice", "default", CVAR_USERINFO|CVAR_ARCHIVE}, + + { &cg_emoticons, "cg_emoticons", "1", CVAR_LATCH|CVAR_ARCHIVE}, + + { &cg_chatTeamPrefix, "cg_chatTeamPrefix", "1", CVAR_ARCHIVE}, + + { &cg_killMsg, "cg_killMsg", "1", CVAR_ARCHIVE }, + { &cg_killMsgTime, "cg_killMsgTime", "4000", CVAR_ARCHIVE }, + { &cg_killMsgHeight, "cg_killMsgHeight", "7", CVAR_ARCHIVE }, + + // Old school thz stuff + { &thz_radar, "thz_radar", "0", CVAR_CHEAT}, + { &thz_radarrange, "thz_radarrange", "600", CVAR_ARCHIVE}, + }; -static int cvarTableSize = sizeof( cvarTable ) / sizeof( cvarTable[0] ); +static size_t cvarTableSize = ARRAY_LEN( cvarTable ); /* ================= @@ -413,263 +419,98 @@ void CG_RegisterCvars( void ) cv->defaultString, cv->cvarFlags ); } - //repress standard Q3 console - trap_Cvar_Set( "con_notifytime", "-2" ); - // see if we are also running the server on this machine trap_Cvar_VariableStringBuffer( "sv_running", var, sizeof( var ) ); cgs.localServer = atoi( var ); - forceModelModificationCount = cg_forceModel.modificationCount; - - trap_Cvar_Register( NULL, "model", DEFAULT_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); - trap_Cvar_Register( NULL, "headmodel", DEFAULT_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); - trap_Cvar_Register( NULL, "team_model", DEFAULT_TEAM_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); - trap_Cvar_Register( NULL, "team_headmodel", DEFAULT_TEAM_HEAD, CVAR_USERINFO | CVAR_ARCHIVE ); } -/* -=================== -CG_ForceModelChange -=================== -*/ -static void CG_ForceModelChange( void ) -{ - int i; - - for( i = 0; i < MAX_CLIENTS; i++ ) - { - const char *clientInfo; - - clientInfo = CG_ConfigString( CS_PLAYERS + i ); - - if( !clientInfo[ 0 ] ) - continue; - - CG_NewClientInfo( i ); - } -} - /* =============== -CG_SetPVars +CG_SetUIVars -Set the p_* cvars +Set some cvars used by the UI =============== */ -static void CG_SetPVars( void ) +static void CG_SetUIVars( void ) { - playerState_t *ps; + int i; + char carriageCvar[ MAX_TOKEN_CHARS ]; if( !cg.snap ) return; - ps = &cg.snap->ps; - - trap_Cvar_Set( "player_hp", va( "%d", ps->stats[ STAT_HEALTH ] ) ); - trap_Cvar_Set( "player_maxhp", va( "%d", ps->stats[ STAT_MAX_HEALTH ] ) ); - switch( ps->stats[ STAT_PTEAM ] ) - { - case PTE_NONE: - trap_Cvar_Set( "player_team", "spectator" ); - trap_Cvar_Set( "player_stage", "0" ); - trap_Cvar_Set( "player_spawns","0" ); - trap_Cvar_Set( "player_kns", "0" ); - trap_Cvar_Set( "player_bp", "0" ); - trap_Cvar_Set( "player_maxbp", "0" ); - break; - - case PTE_ALIENS: - trap_Cvar_Set( "player_team", "alien" ); - trap_Cvar_Set( "player_stage", va( "%d", cgs.alienStage+1 ) ); - trap_Cvar_Set( "player_spawns",va( "%d", cgs.numAlienSpawns )); - trap_Cvar_Set( "player_kns", va( "%d",((cgs.alienStage==2)?0:abs(cgs.alienNextStageThreshold-cgs.alienKills)))); - trap_Cvar_Set( "player_stage", va( "%d", cgs.alienStage+1 ) ); - trap_Cvar_Set( "player_bp", va( "%d", cgs.alienBuildPoints )); - trap_Cvar_Set( "player_maxbp", va( "%d", cgs.alienBuildPointsTotal )); - break; - - case PTE_HUMANS: - trap_Cvar_Set( "player_team", "human" ); - trap_Cvar_Set( "player_stage", va( "%d", cgs.humanStage+1 ) ); - trap_Cvar_Set( "player_spawns",va( "%d", cgs.numHumanSpawns )); - trap_Cvar_Set( "player_kns", va( "%d",((cgs.humanStage==2)?0:abs(cgs.humanNextStageThreshold-cgs.humanKills)))); - trap_Cvar_Set( "player_bp", va( "%d", cgs.humanBuildPoints )); - trap_Cvar_Set( "player_maxbp", va( "%d", cgs.humanBuildPointsTotal )); - break; - } + *carriageCvar = 0; - trap_Cvar_Set( "player_credits", va( "%d", ps->persistant[ PERS_CREDIT ] ) ); - trap_Cvar_Set( "player_score", va( "%d", ps->persistant[ PERS_SCORE ] ) ); - trap_Cvar_Set( "player_deaths", va( "%d", ps->persistant[ PERS_KILLED ] ) ); + //determine what the player is carrying + if( BG_Weapon( cg.snap->ps.stats[ STAT_WEAPON ] )->purchasable ) + strcat( carriageCvar, va( "W%d ", cg.snap->ps.stats[ STAT_WEAPON ] ) ); - if ( CG_LastAttacker( ) != -1 ) + for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) { - trap_Cvar_Set( "player_attacker", cgs.clientinfo[ CG_LastAttacker( ) ].name ); - trap_Cvar_Set( "player_attacker_hp", va( "%d", cgs.clientinfo[ CG_LastAttacker( ) ].health)); + if( BG_InventoryContainsUpgrade( i, cg.snap->ps.stats ) && + BG_Upgrade( i )->purchasable ) + strcat( carriageCvar, va( "U%d ", i ) ); } - else - { - trap_Cvar_Set( "player_attacker", "" ); - trap_Cvar_Set( "player_attacker_hp", "" ); - } - + strcat( carriageCvar, "$" ); - if ( CG_CrosshairPlayer( ) != -1 ) - { - trap_Cvar_Set( "player_crosshair", cgs.clientinfo[ CG_CrosshairPlayer( ) ].name ); - //XXX hax required - //trap_Cvar_Set( "player_crosshair_credits", va("%d",cgs.clientinfo[CG_CrosshairPlayer( )].credits)); - } - else - { - trap_Cvar_Set( "player_crosshair", "" ); - //trap_Cvar_Set( "player_crosshair_credits", "" ); - } - - // stages - trap_Cvar_Set( "alien_stage", va( "%d", cgs.alienStage+1 ) ); - trap_Cvar_Set( "human_stage", va( "%d", cgs.humanStage+1 ) ); - - // alien kills to next stage - if( cgs.alienStage == 2 ) - trap_Cvar_Set( "alien_kns", va( "%d", 0 ) ); - else - trap_Cvar_Set( "alien_kns", va( "%d", abs(cgs.alienNextStageThreshold - cgs.alienKills)) ); - - // human kills to next stage - if( cgs.humanStage == 2 ) - trap_Cvar_Set( "human_kns", va( "%d", 0 ) ); - else - trap_Cvar_Set( "human_kns", va( "%d", abs(cgs.humanNextStageThreshold - cgs.humanKills)) ); - - // General score information - trap_Cvar_Set( "alien_score", va( "%d", cgs.alienKills ) ); - trap_Cvar_Set( "human_score", va( "%d", cgs.humanKills ) ); - - // class type - switch ( ps->stats[ STAT_PCLASS ] ) - { - case PCL_ALIEN_BUILDER0: - trap_Cvar_Set( "player_class", "Granger" ); - trap_Cvar_Set( "player_weapon", "Granger" ); - break; - - case PCL_ALIEN_BUILDER0_UPG: - trap_Cvar_Set( "player_class", "Advanced Granger" ); - trap_Cvar_Set( "player_weapon", "Advanced Granger" ); - break; - - case PCL_ALIEN_LEVEL0: - trap_Cvar_Set( "player_class", "Dretch" ); - trap_Cvar_Set( "player_weapon", "Dretch" ); - break; - - case PCL_ALIEN_LEVEL1: - trap_Cvar_Set( "player_class", "Basilisk" ); - trap_Cvar_Set( "player_weapon", "Basilisk" ); - break; - - case PCL_ALIEN_LEVEL1_UPG: - trap_Cvar_Set( "player_class", "Advanced Basilisk" ); - trap_Cvar_Set( "player_weapon", "Advanced Basilisk" ); - break; - - case PCL_ALIEN_LEVEL2: - trap_Cvar_Set( "player_class", "Marauder" ); - trap_Cvar_Set( "player_weapon", "Marauder" ); - break; - - case PCL_ALIEN_LEVEL2_UPG: - trap_Cvar_Set( "player_class", "Advanced Marauder" ); - trap_Cvar_Set( "player_weapon", "Advanced Maruder" ); - break; - - case PCL_ALIEN_LEVEL3: - trap_Cvar_Set( "player_class", "Dragoon" ); - trap_Cvar_Set( "player_weapon", "Dragoon" ); - break; - - case PCL_ALIEN_LEVEL3_UPG: - trap_Cvar_Set( "player_class", "Advanced Dragoon" ); - trap_Cvar_Set( "player_weapon", "Advanced Dragoon" ); - break; - - case PCL_ALIEN_LEVEL4: - trap_Cvar_Set( "player_class", "Tyrant" ); - trap_Cvar_Set( "player_weapon", "Tyrant" ); - break; - - case PCL_HUMAN: - trap_Cvar_Set( "player_class", "Human" ); - break; - - case PCL_HUMAN_BSUIT: - trap_Cvar_Set( "player_class", "Battlesuit" ); - break; - - default: - trap_Cvar_Set( "player_class", "Unknown" ); - } - - // weapons - switch ( ps->weapon ) - { - case WP_HBUILD: - trap_Cvar_Set( "player_weapon", "Construction Kit" ); - break; + trap_Cvar_Set( "ui_carriage", carriageCvar ); - case WP_HBUILD2: - trap_Cvar_Set( "player_weapon", "Advanced Construction Kit" ); - break; - - case WP_BLASTER: - trap_Cvar_Set( "player_weapon", "Blaster" ); - break; - - case WP_MACHINEGUN: - trap_Cvar_Set( "player_weapon", "Machine Gun" ); - break; - - case WP_PAIN_SAW: - trap_Cvar_Set( "player_weapon", "Painsaw" ); - break; + trap_Cvar_Set( "ui_stages", va( "%d %d", cgs.alienStage, cgs.humanStage ) ); +} - case WP_SHOTGUN: - trap_Cvar_Set( "player_weapon", "Shotgun" ); - break; +/* +================= +CG_SetPVars +================= +*/ +static void CG_SetPVars( void ) +{ + playerState_t *ps; - case WP_LAS_GUN: - trap_Cvar_Set( "player_weapon", "Laser Gun" ); - break; + if( !cg.snap ) return; + ps = &cg.snap->ps; - case WP_MASS_DRIVER: - trap_Cvar_Set( "player_weapon", "Mass Driver" ); - break; + trap_Cvar_Set( "player_hp", va( "%d", ps->stats[ STAT_HEALTH ] )); + trap_Cvar_Set( "player_maxhp",va( "%d", ps->stats[ STAT_MAX_HEALTH ] )); - case WP_CHAINGUN: - trap_Cvar_Set( "player_weapon", "Chain Gun" ); + switch( ps->stats[ STAT_TEAM ] ) + { + case TEAM_NONE: + trap_Cvar_Set( "team_bp", "0" ); + trap_Cvar_Set( "team_kns", "0" ); + trap_Cvar_Set( "team_teamname", "spectator" ); + trap_Cvar_Set( "team_stage", "0" ); break; - case WP_PULSE_RIFLE: - trap_Cvar_Set( "player_weapon", "Pulse Rifle" ); + case TEAM_ALIENS: + //trap_Cvar_Set( "team_bp", va( "%d", cgs.alienBuildPoints )); + trap_Cvar_Set( "team_kns", va("%d", cgs.alienNextStageThreshold) ); + trap_Cvar_Set( "team_teamname", "aliens" ); + trap_Cvar_Set( "team_stage", va( "%d", cgs.alienStage+1 ) ); break; - case WP_FLAMER: - trap_Cvar_Set( "player_weapon", "Flame Thrower" ); + case TEAM_HUMANS: + //trap_Cvar_Set( "team_bp", va("%d",cgs.humanBuildPoints) ); + trap_Cvar_Set( "team_kns", va("%d",cgs.humanNextStageThreshold) ); + trap_Cvar_Set( "team_teamname", "humans" ); + trap_Cvar_Set( "team_stage", va( "%d", cgs.humanStage+1 ) ); break; + } + + trap_Cvar_Set( "player_credits", va( "%d", cg.snap->ps.persistant[ PERS_CREDIT ] ) ); + trap_Cvar_Set( "player_score", va( "%d", cg.snap->ps.persistant[ PERS_SCORE ] ) ); - case WP_LUCIFER_CANNON: - trap_Cvar_Set( "player_weapon", "Lucifier cannon" ); - break; + if ( CG_LastAttacker( ) != -1 ) + trap_Cvar_Set( "player_attackername", cgs.clientinfo[ CG_LastAttacker( ) ].name ); + else + trap_Cvar_Set( "player_attackername", "" ); - case WP_GRENADE: - trap_Cvar_Set( "player_weapon", "Grenade" ); - break; + if ( CG_CrosshairPlayer( ) != -1 ) + trap_Cvar_Set( "player_crosshairname", cgs.clientinfo[ CG_CrosshairPlayer( ) ].name ); + else + trap_Cvar_Set( "player_crosshairname", "" ); - default: - trap_Cvar_Set( "player_weapon", "Unknown" ); - } } /* @@ -682,19 +523,16 @@ void CG_UpdateCvars( void ) int i; cvarTable_t *cv; - CG_SetPVars(); + CG_SetPVars( ); for( i = 0, cv = cvarTable; i < cvarTableSize; i++, cv++ ) - trap_Cvar_Update( cv->vmCvar ); + if( cv->vmCvar ) + trap_Cvar_Update( cv->vmCvar ); // check for modications here - // if force model changed - if( forceModelModificationCount != cg_forceModel.modificationCount ) - { - forceModelModificationCount = cg_forceModel.modificationCount; - CG_ForceModelChange( ); - } + CG_SetUIVars( ); + CG_UpdateBuildableRangeMarkerMask(); } @@ -715,6 +553,7 @@ int CG_LastAttacker( void ) return cg.snap->ps.persistant[ PERS_ATTACKER ]; } + /* ================= CG_RemoveNotifyLine @@ -735,10 +574,9 @@ void CG_RemoveNotifyLine( void ) cg.consoleText[ i ] = cg.consoleText[ i + offset ]; //pop up the first consoleLine + cg.numConsoleLines--; for( i = 0; i < cg.numConsoleLines; i++ ) cg.consoleLines[ i ] = cg.consoleLines[ i + 1 ]; - - cg.numConsoleLines--; } /* @@ -749,6 +587,7 @@ CG_AddNotifyText void CG_AddNotifyText( void ) { char buffer[ BIG_INFO_STRING ]; + int bufferLen, textLen; trap_LiteralArgs( buffer, BIG_INFO_STRING ); @@ -759,12 +598,24 @@ void CG_AddNotifyText( void ) return; } + bufferLen = strlen( buffer ); + textLen = strlen( cg.consoleText ); + + // Ignore console messages that were just printed + if( cg_noPrintDuplicate.integer && textLen >= bufferLen && + !strcmp( cg.consoleText + textLen - bufferLen, buffer ) ) + return; + if( cg.numConsoleLines == MAX_CONSOLE_LINES ) + { CG_RemoveNotifyLine( ); + textLen = strlen( cg.consoleText ); + } - Q_strcat( cg.consoleText, MAX_CONSOLE_TEXT, buffer ); + Q_strncpyz( cg.consoleText + textLen, buffer, MAX_CONSOLE_TEXT - textLen ); cg.consoleLines[ cg.numConsoleLines ].time = cg.time; - cg.consoleLines[ cg.numConsoleLines ].length = strlen( buffer ); + cg.consoleLines[ cg.numConsoleLines ].length = + MIN( bufferLen, MAX_CONSOLE_TEXT - textLen - 1 ); cg.numConsoleLines++; } @@ -774,7 +625,7 @@ void QDECL CG_Printf( const char *msg, ... ) char text[ 1024 ]; va_start( argptr, msg ); - vsprintf( text, msg, argptr ); + Q_vsnprintf( text, sizeof( text ), msg, argptr ); va_end( argptr ); trap_Print( text ); @@ -786,7 +637,7 @@ void QDECL CG_Error( const char *msg, ... ) char text[ 1024 ]; va_start( argptr, msg ); - vsprintf( text, msg, argptr ); + Q_vsnprintf( text, sizeof( text ), msg, argptr ); va_end( argptr ); trap_Error( text ); @@ -798,7 +649,7 @@ void QDECL Com_Error( int level, const char *error, ... ) char text[1024]; va_start( argptr, error ); - vsprintf( text, error, argptr ); + Q_vsnprintf( text, sizeof( text ), error, argptr ); va_end( argptr ); CG_Error( "%s", text ); @@ -808,9 +659,9 @@ void QDECL Com_Printf( const char *msg, ... ) { va_list argptr; char text[1024]; - va_start (argptr, msg); - vsprintf (text, msg, argptr); - va_end (argptr); + va_start( argptr, msg ); + Q_vsnprintf( text, sizeof( text ), msg, argptr ); + va_end( argptr ); CG_Printf ("%s", text); } @@ -841,19 +692,9 @@ CG_FileExists Test if a specific file exists or not ================= */ -qboolean CG_FileExists( char *filename ) +qboolean CG_FileExists( const char *filename ) { - fileHandle_t f; - - if( trap_FS_FOpenFile( filename, &f, FS_READ ) > 0 ) - { - //file exists so close it - trap_FS_FCloseFile( f ); - - return qtrue; - } - else - return qfalse; + return trap_FS_FOpenFile( filename, NULL, FS_READ ); } /* @@ -882,6 +723,8 @@ static void CG_RegisterSounds( void ) cgs.media.tracerSound = trap_S_RegisterSound( "sound/weapons/tracer.wav", qfalse ); cgs.media.selectSound = trap_S_RegisterSound( "sound/weapons/change.wav", qfalse ); + cgs.media.turretSpinupSound = trap_S_RegisterSound( "sound/buildables/mgturret/spinup.wav", qfalse ); + cgs.media.weaponEmptyClick = trap_S_RegisterSound( "sound/weapons/click.wav", qfalse ); cgs.media.talkSound = trap_S_RegisterSound( "sound/misc/talk.wav", qfalse ); cgs.media.alienTalkSound = trap_S_RegisterSound( "sound/misc/alien_talk.wav", qfalse ); @@ -950,6 +793,7 @@ static void CG_RegisterSounds( void ) cgs.media.buildableRepairedSound = trap_S_RegisterSound( "sound/buildables/human/repaired.wav", qfalse ); cgs.media.lCannonWarningSound = trap_S_RegisterSound( "models/weapons/lcannon/warning.wav", qfalse ); + cgs.media.lCannonWarningSound2 = trap_S_RegisterSound( "models/weapons/lcannon/warning2.wav", qfalse ); } @@ -1011,12 +855,14 @@ static void CG_RegisterGraphics( void ) cgs.media.scannerBlipShader = trap_R_RegisterShader( "gfx/2d/blip" ); cgs.media.scannerLineShader = trap_R_RegisterShader( "gfx/2d/stalk" ); + cgs.media.teamOverlayShader = trap_R_RegisterShader( "gfx/2d/teamoverlay" ); + cgs.media.tracerShader = trap_R_RegisterShader( "gfx/misc/tracer" ); cgs.media.backTileShader = trap_R_RegisterShader( "console" ); - //TA: building shaders + // building shaders cgs.media.greenBuildShader = trap_R_RegisterShader("gfx/misc/greenbuild" ); cgs.media.redBuildShader = trap_R_RegisterShader("gfx/misc/redbuild" ); cgs.media.humanSpawningShader = trap_R_RegisterShader("models/buildables/telenode/rep_cyl" ); @@ -1024,8 +870,15 @@ static void CG_RegisterGraphics( void ) for( i = 0; i < 8; i++ ) cgs.media.buildWeaponTimerPie[ i ] = trap_R_RegisterShader( buildWeaponTimerPieShaders[ i ] ); + // player health cross shaders + cgs.media.healthCross = trap_R_RegisterShader( "ui/assets/neutral/cross.tga" ); + cgs.media.healthCross2X = trap_R_RegisterShader( "ui/assets/neutral/cross2.tga" ); + cgs.media.healthCross3X = trap_R_RegisterShader( "ui/assets/neutral/cross3.tga" ); + cgs.media.healthCrossMedkit = trap_R_RegisterShader( "ui/assets/neutral/cross_medkit.tga" ); + cgs.media.healthCrossPoisoned = trap_R_RegisterShader( "ui/assets/neutral/cross_poison.tga" ); + cgs.media.upgradeClassIconShader = trap_R_RegisterShader( "icons/icona_upgrade.tga" ); - + cgs.media.balloonShader = trap_R_RegisterShader( "gfx/sprites/chatballoon" ); cgs.media.disconnectPS = CG_RegisterParticleSystem( "disconnectPS" ); @@ -1039,6 +892,7 @@ static void CG_RegisterGraphics( void ) cgs.media.wakeMarkShader = trap_R_RegisterShader( "gfx/marks/wake" ); cgs.media.poisonCloudPS = CG_RegisterParticleSystem( "firstPersonPoisonCloudPS" ); + cgs.media.poisonCloudedPS = CG_RegisterParticleSystem( "poisonCloudedPS" ); cgs.media.alienEvolvePS = CG_RegisterParticleSystem( "alienEvolvePS" ); cgs.media.alienAcidTubePS = CG_RegisterParticleSystem( "alienAcidTubePS" ); @@ -1051,9 +905,29 @@ static void CG_RegisterGraphics( void ) cgs.media.humanBuildableDestroyedPS = CG_RegisterParticleSystem( "humanBuildableDestroyedPS" ); cgs.media.alienBuildableDestroyedPS = CG_RegisterParticleSystem( "alienBuildableDestroyedPS" ); + cgs.media.humanBuildableBleedPS = CG_RegisterParticleSystem( "humanBuildableBleedPS"); + cgs.media.alienBuildableBleedPS = CG_RegisterParticleSystem( "alienBuildableBleedPS" ); + cgs.media.alienBleedPS = CG_RegisterParticleSystem( "alienBleedPS" ); cgs.media.humanBleedPS = CG_RegisterParticleSystem( "humanBleedPS" ); + cgs.media.sphereModel = trap_R_RegisterModel( "models/generic/sphere" ); + cgs.media.sphericalCone64Model = trap_R_RegisterModel( "models/generic/sphericalCone64" ); + cgs.media.sphericalCone240Model = trap_R_RegisterModel( "models/generic/sphericalCone240" ); + + cgs.media.plainColorShader = trap_R_RegisterShader( "gfx/plainColor" ); + cgs.media.binaryAlpha1Shader = trap_R_RegisterShader( "gfx/binary/alpha1" ); + + for( i = 0; i < NUM_BINARY_SHADERS; ++i ) + { + cgs.media.binaryShaders[ i ].f1 = trap_R_RegisterShader( va( "gfx/binary/%03i_F1", i ) ); + cgs.media.binaryShaders[ i ].f2 = trap_R_RegisterShader( va( "gfx/binary/%03i_F2", i ) ); + cgs.media.binaryShaders[ i ].f3 = trap_R_RegisterShader( va( "gfx/binary/%03i_F3", i ) ); + cgs.media.binaryShaders[ i ].b1 = trap_R_RegisterShader( va( "gfx/binary/%03i_B1", i ) ); + cgs.media.binaryShaders[ i ].b2 = trap_R_RegisterShader( va( "gfx/binary/%03i_B2", i ) ); + cgs.media.binaryShaders[ i ].b3 = trap_R_RegisterShader( va( "gfx/binary/%03i_B3", i ) ); + } + CG_BuildableStatusParse( "ui/assets/human/buildstat.cfg", &cgs.humanBuildStat ); CG_BuildableStatusParse( "ui/assets/alien/buildstat.cfg", &cgs.alienBuildStat ); @@ -1134,16 +1008,11 @@ void CG_BuildSpectatorString( void ) for( i = 0; i < MAX_CLIENTS; i++ ) { - if( cgs.clientinfo[ i ].infoValid && cgs.clientinfo[ i ].team == PTE_NONE ) - Q_strcat( cg.spectatorList, sizeof( cg.spectatorList ), va( "%s " S_COLOR_WHITE, cgs.clientinfo[ i ].name ) ); - } - - i = strlen( cg.spectatorList ); - - if( i != cg.spectatorLen ) - { - cg.spectatorLen = i; - cg.spectatorWidth = -1; + if( cgs.clientinfo[ i ].infoValid && cgs.clientinfo[ i ].team == TEAM_NONE ) + { + Q_strcat( cg.spectatorList, sizeof( cg.spectatorList ), + va( S_COLOR_WHITE "%s ", cgs.clientinfo[ i ].name ) ); + } } } @@ -1164,8 +1033,8 @@ static void CG_RegisterClients( void ) //precache all the models/sounds/etc for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ ) { - CG_PrecacheClientInfo( i, BG_FindModelNameForClass( i ), - BG_FindSkinNameForClass( i ) ); + CG_PrecacheClientInfo( i, BG_ClassConfig( i )->modelName, + BG_ClassConfig( i )->skinName ); cg.charModelFraction = (float)i / (float)PCL_NUM_CLASSES; trap_UpdateScreen( ); @@ -1246,8 +1115,8 @@ int CG_PlayerCount( void ) for( i = 0; i < cg.numScores; i++ ) { - if( cg.scores[ i ].team == PTE_ALIENS || - cg.scores[ i ].team == PTE_HUMANS ) + if( cg.scores[ i ].team == TEAM_ALIENS || + cg.scores[ i ].team == TEAM_HUMANS ) count++; } @@ -1452,7 +1321,7 @@ qboolean CG_Asset_Parse( int handle ) } } - return qfalse; // bk001204 - why not? + return qfalse; } void CG_ParseMenu( const char *menuFile ) @@ -1545,11 +1414,11 @@ void CG_LoadMenus( const char *menuFile ) if( !f ) { - trap_Error( va( S_COLOR_YELLOW "menu file not found: %s, using default\n", menuFile ) ); + trap_Error( va( S_COLOR_YELLOW "menu file not found: %s, using default", menuFile ) ); len = trap_FS_FOpenFile( "ui/hud.txt", &f, FS_READ ); if( !f ) - trap_Error( va( S_COLOR_RED "default menu file not found: ui/hud.txt, unable to continue!\n" ) ); + trap_Error( va( S_COLOR_RED "default menu file not found: ui/hud.txt, unable to continue!" ) ); } if( len >= MAX_MENUDEFFILE ) @@ -1594,13 +1463,13 @@ void CG_LoadMenus( const char *menuFile ) -static qboolean CG_OwnerDrawHandleKey( int ownerDraw, int flags, float *special, int key ) +static qboolean CG_OwnerDrawHandleKey( int ownerDraw, int key ) { return qfalse; } -static int CG_FeederCount( float feederID ) +static int CG_FeederCount( int feederID ) { int i, count = 0; @@ -1608,7 +1477,7 @@ static int CG_FeederCount( float feederID ) { for( i = 0; i < cg.numScores; i++ ) { - if( cg.scores[ i ].team == PTE_ALIENS ) + if( cg.scores[ i ].team == TEAM_ALIENS ) count++; } } @@ -1616,7 +1485,7 @@ static int CG_FeederCount( float feederID ) { for( i = 0; i < cg.numScores; i++ ) { - if( cg.scores[ i ].team == PTE_HUMANS ) + if( cg.scores[ i ].team == TEAM_HUMANS ) count++; } } @@ -1636,9 +1505,9 @@ void CG_SetScoreSelection( void *p ) for( i = 0; i < cg.numScores; i++ ) { - if( cg.scores[ i ].team == PTE_ALIENS ) + if( cg.scores[ i ].team == TEAM_ALIENS ) alien++; - else if( cg.scores[ i ].team == PTE_HUMANS ) + else if( cg.scores[ i ].team == TEAM_HUMANS ) human++; if( ps->clientNum == cg.scores[ i ].client ) @@ -1652,7 +1521,7 @@ void CG_SetScoreSelection( void *p ) feeder = FEEDER_ALIENTEAM_LIST; i = alien; - if( cg.scores[ cg.selectedScore ].team == PTE_HUMANS ) + if( cg.scores[ cg.selectedScore ].team == TEAM_HUMANS ) { feeder = FEEDER_HUMANTEAM_LIST; i = human; @@ -1684,7 +1553,16 @@ static clientInfo_t * CG_InfoFromScoreIndex( int index, int team, int *scoreInde return &cgs.clientinfo[ cg.scores[ index ].client ]; } -static const char *CG_FeederItemText( float feederID, int index, int column, qhandle_t *handle ) +qboolean CG_ClientIsReady( int clientNum ) +{ + clientList_t ready; + + Com_ClientListParse( &ready, CG_ConfigString( CS_CLIENTS_READY ) ); + + return Com_ClientListContains( &ready, clientNum ); +} + +static const char *CG_FeederItemText( int feederID, int index, int column, qhandle_t *handle ) { int scoreIndex = 0; clientInfo_t *info = NULL; @@ -1695,19 +1573,23 @@ static const char *CG_FeederItemText( float feederID, int index, int column, qha *handle = -1; if( feederID == FEEDER_ALIENTEAM_LIST ) - team = PTE_ALIENS; + team = TEAM_ALIENS; else if( feederID == FEEDER_HUMANTEAM_LIST ) - team = PTE_HUMANS; + team = TEAM_HUMANS; info = CG_InfoFromScoreIndex( index, team, &scoreIndex ); sp = &cg.scores[ scoreIndex ]; - if( ( atoi( CG_ConfigString( CS_CLIENTS_READY ) ) & ( 1 << sp->client ) ) && - cg.intermissionStarted ) + if( cg.intermissionStarted && CG_ClientIsReady( sp->client ) ) showIcons = qfalse; - else if( cg.snap->ps.pm_type == PM_SPECTATOR || cg.snap->ps.pm_flags & PMF_FOLLOW || - team == cg.snap->ps.stats[ STAT_PTEAM ] || cg.intermissionStarted ) + else if( cg.snap->ps.pm_type == PM_SPECTATOR || + cg.snap->ps.pm_type == PM_NOCLIP || + cg.snap->ps.pm_flags & PMF_FOLLOW || + team == cg.snap->ps.stats[ STAT_TEAM ] || + cg.intermissionStarted ) + { showIcons = qtrue; + } if( info && info->infoValid ) { @@ -1724,9 +1606,9 @@ static const char *CG_FeederItemText( float feederID, int index, int column, qha case 1: if( showIcons ) { - if( sp->team == PTE_HUMANS && sp->upgrade != UP_NONE ) + if( sp->team == TEAM_HUMANS && sp->upgrade != UP_NONE ) *handle = cg_upgrades[ sp->upgrade ].upgradeIcon; - else if( sp->team == PTE_ALIENS ) + else if( sp->team == TEAM_ALIENS ) { switch( sp->weapon ) { @@ -1745,17 +1627,16 @@ static const char *CG_FeederItemText( float feederID, int index, int column, qha break; case 2: - if( ( atoi( CG_ConfigString( CS_CLIENTS_READY ) ) & ( 1 << sp->client ) ) && - cg.intermissionStarted ) + if( cg.intermissionStarted && CG_ClientIsReady( sp->client ) ) return "Ready"; break; case 3: - return info->name; + return va( S_COLOR_WHITE "%s", info->name ); break; case 4: - return va( "%d", info->score ); + return va( "%d", sp->score ); break; case 5: @@ -1764,7 +1645,7 @@ static const char *CG_FeederItemText( float feederID, int index, int column, qha case 6: if( sp->ping == -1 ) - return "connecting"; + return ""; return va( "%4d", sp->ping ); break; @@ -1774,15 +1655,15 @@ static const char *CG_FeederItemText( float feederID, int index, int column, qha return ""; } -static qhandle_t CG_FeederItemImage( float feederID, int index ) +static qhandle_t CG_FeederItemImage( int feederID, int index ) { return 0; } -static void CG_FeederSelection( float feederID, int index ) +static void CG_FeederSelection( int feederID, int index ) { int i, count; - int team = ( feederID == FEEDER_ALIENTEAM_LIST ) ? PTE_ALIENS : PTE_HUMANS; + int team = ( feederID == FEEDER_ALIENTEAM_LIST ) ? TEAM_ALIENS : TEAM_HUMANS; count = 0; for( i = 0; i < cg.numScores; i++ ) @@ -1809,7 +1690,7 @@ static float CG_Cvar_Get( const char *cvar ) void CG_Text_PaintWithCursor( float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style ) { - CG_Text_Paint( x, y, scale, color, text, 0, limit, style ); + UI_Text_Paint( x, y, scale, color, text, 0, limit, style ); } static int CG_OwnerDrawWidth( int ownerDraw, float scale ) @@ -1817,7 +1698,7 @@ static int CG_OwnerDrawWidth( int ownerDraw, float scale ) switch( ownerDraw ) { case CG_KILLER: - return CG_Text_Width( CG_GetKillerText( ), scale, 0 ); + return UI_Text_Width( CG_GetKillerText( ), scale ); break; } @@ -1845,7 +1726,7 @@ static void CG_RunCinematicFrame( int handle ) trap_CIN_RunCinematic( handle ); } -//TA: hack to prevent warning +// hack to prevent warning static qboolean CG_OwnerDrawVisible( int parameter ) { return qfalse; @@ -1861,13 +1742,18 @@ void CG_LoadHudMenu( void ) char buff[ 1024 ]; const char *hudSet; + cgDC.aspectScale = ( ( 640.0f * cgs.glconfig.vidHeight ) / + ( 480.0f * cgs.glconfig.vidWidth ) ); + cgDC.xscale = cgs.glconfig.vidWidth / 640.0f; + cgDC.yscale = cgs.glconfig.vidHeight / 480.0f; + + cgDC.smallFontScale = CG_Cvar_Get( "ui_smallFont" ); + cgDC.bigFontScale = CG_Cvar_Get( "ui_bigFont" ); + cgDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip; cgDC.setColor = &trap_R_SetColor; cgDC.drawHandlePic = &CG_DrawPic; cgDC.drawStretchPic = &trap_R_DrawStretchPic; - cgDC.drawText = &CG_Text_Paint; - cgDC.textWidth = &CG_Text_Width; - cgDC.textHeight = &CG_Text_Height; cgDC.registerModel = &trap_R_RegisterModel; cgDC.modelBounds = &trap_R_ModelBounds; cgDC.fillRect = &CG_FillRect; @@ -1882,13 +1768,11 @@ void CG_LoadHudMenu( void ) cgDC.getValue = &CG_GetValue; cgDC.ownerDrawVisible = &CG_OwnerDrawVisible; cgDC.runScript = &CG_RunMenuScript; - cgDC.getTeamColor = &CG_GetTeamColor; cgDC.setCVar = trap_Cvar_Set; cgDC.getCVarString = trap_Cvar_VariableStringBuffer; cgDC.getCVarValue = CG_Cvar_Get; - cgDC.drawTextWithCursor = &CG_Text_PaintWithCursor; - //cgDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode; - //cgDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode; + cgDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode; + cgDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode; cgDC.startLocalSound = &trap_S_StartLocalSound; cgDC.ownerDrawHandleKey = &CG_OwnerDrawHandleKey; cgDC.feederCount = &CG_FeederCount; @@ -1902,6 +1786,7 @@ void CG_LoadHudMenu( void ) cgDC.Error = &Com_Error; cgDC.Print = &Com_Printf; cgDC.ownerDrawWidth = &CG_OwnerDrawWidth; + //cgDC.ownerDrawText = &CG_OwnerDrawText; //cgDC.Pause = &CG_Pause; cgDC.registerSound = &trap_S_RegisterSound; cgDC.startBackgroundTrack = &trap_S_StartBackgroundTrack; @@ -1926,6 +1811,8 @@ void CG_LoadHudMenu( void ) void CG_AssetCache( void ) { + int i; + cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( ASSET_GRADIENTBAR ); cgDC.Assets.scrollBar = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR ); cgDC.Assets.scrollBarArrowDown = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWDOWN ); @@ -1935,6 +1822,21 @@ void CG_AssetCache( void ) cgDC.Assets.scrollBarThumb = trap_R_RegisterShaderNoMip( ASSET_SCROLL_THUMB ); cgDC.Assets.sliderBar = trap_R_RegisterShaderNoMip( ASSET_SLIDER_BAR ); cgDC.Assets.sliderThumb = trap_R_RegisterShaderNoMip( ASSET_SLIDER_THUMB ); + + if( cg_emoticons.integer ) + { + cgDC.Assets.emoticonCount = BG_LoadEmoticons( cgDC.Assets.emoticons, + MAX_EMOTICONS ); + } + else + cgDC.Assets.emoticonCount = 0; + + for( i = 0; i < cgDC.Assets.emoticonCount; i++ ) + { + cgDC.Assets.emoticons[ i ].shader = trap_R_RegisterShaderNoMip( + va( "emoticons/%s_%dx1.tga", cgDC.Assets.emoticons[ i ].name, + cgDC.Assets.emoticons[ i ].width ) ); + } } /* @@ -1952,7 +1854,6 @@ void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) // clear everything memset( &cgs, 0, sizeof( cgs ) ); memset( &cg, 0, sizeof( cg ) ); - memset( &cg.pmext, 0, sizeof( cg.pmext ) ); memset( cg_entities, 0, sizeof( cg_entities ) ); cg.clientNum = clientNum; @@ -1960,45 +1861,52 @@ void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) cgs.processedSnapshotNum = serverMessageNum; cgs.serverCommandSequence = serverCommandSequence; + // get the rendering configuration from the client system + trap_GetGlconfig( &cgs.glconfig ); + cgs.screenXScale = cgs.glconfig.vidWidth / 640.0f; + cgs.screenYScale = cgs.glconfig.vidHeight / 480.0f; + // load a few needed things before we do any screen updates cgs.media.whiteShader = trap_R_RegisterShader( "white" ); cgs.media.charsetShader = trap_R_RegisterShader( "gfx/2d/bigchars" ); cgs.media.outlineShader = trap_R_RegisterShader( "outline" ); - //inform UI to repress cursor whilst loading - trap_Cvar_Set( "ui_loading", "1" ); - - //TA: load overrides - BG_InitClassOverrides( ); - BG_InitBuildableOverrides( ); + // load overrides + BG_InitClassConfigs( ); + BG_InitBuildableConfigs( ); BG_InitAllowedGameElements( ); - //TA: dyn memory - CG_InitMemory( ); + // Dynamic memory + BG_InitMemory( ); CG_RegisterCvars( ); CG_InitConsoleCommands( ); - //TA: moved up for LoadHudMenu String_Init( ); - //TA: TA UI CG_AssetCache( ); - CG_LoadHudMenu( ); // load new hud stuff + CG_LoadHudMenu( ); cg.weaponSelect = WP_NONE; // old servers - // get the rendering configuration from the client system - trap_GetGlconfig( &cgs.glconfig ); - cgs.screenXScale = cgs.glconfig.vidWidth / 640.0; - cgs.screenYScale = cgs.glconfig.vidHeight / 480.0; - // get the gamestate from the client system trap_GetGameState( &cgs.gameState ); + // copy vote display strings so they don't show up blank if we see + // the same one directly after connecting + Q_strncpyz( cgs.voteString[ TEAM_NONE ], + CG_ConfigString( CS_VOTE_STRING + TEAM_NONE ), + sizeof( cgs.voteString ) ); + Q_strncpyz( cgs.voteString[ TEAM_ALIENS ], + CG_ConfigString( CS_VOTE_STRING + TEAM_ALIENS ), + sizeof( cgs.voteString[ TEAM_ALIENS ] ) ); + Q_strncpyz( cgs.voteString[ TEAM_HUMANS ], + CG_ConfigString( CS_VOTE_STRING + TEAM_ALIENS ), + sizeof( cgs.voteString[ TEAM_HUMANS ] ) ); + // check version s = CG_ConfigString( CS_GAME_VERSION ); @@ -2033,8 +1941,10 @@ void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) CG_InitUpgrades( ); CG_UpdateMediaFraction( 1.0f ); - //TA: CG_InitBuildables( ); + + cgs.voices = BG_VoiceInit( ); + BG_PrintVoices( cgs.voices, cg_debugVoices.integer ); CG_RegisterClients( ); // if low on memory, some clients will be deferred @@ -2053,8 +1963,6 @@ void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) CG_ShaderStateChanged( ); trap_S_ClearLoopingSounds( qtrue ); - - trap_Cvar_Set( "ui_loading", "0" ); } /* @@ -2066,8 +1974,7 @@ Called before every level change or subsystem restart */ void CG_Shutdown( void ) { - // some mods may need to do cleanup work here, - // like closing files or archiving session data + CG_UnregisterCommands( ); } /* @@ -2075,7 +1982,7 @@ void CG_Shutdown( void ) CG_VoIPString ================ */ -char *CG_VoIPString( void ) +static char *CG_VoIPString( void ) { // a generous overestimate of the space needed for 0,1,2...61,62,63 static char voipString[ MAX_CLIENTS * 4 ]; @@ -2086,7 +1993,7 @@ char *CG_VoIPString( void ) if( Q_stricmp( voipSendTarget, "team" ) == 0 ) { - int i, slen; + int i, slen, nlen; for( slen = i = 0; i < cgs.maxclients; i++ ) { if( !cgs.clientinfo[ i ].infoValid || i == cg.clientNum ) @@ -2094,14 +2001,15 @@ char *CG_VoIPString( void ) if( cgs.clientinfo[ i ].team != cgs.clientinfo[ cg.clientNum ].team ) continue; - Com_sprintf( &voipString[ slen ], sizeof( voipString ) - slen, - "%s%d", ( slen > 0 ) ? "," : "", i ); - slen = strlen( voipString ); - if( slen + 1 >= sizeof( voipString ) ) + nlen = Q_snprintf( &voipString[ slen ], sizeof( voipString ) - slen, + "%s%d", ( slen > 0 ) ? "," : "", i ); + if( slen + nlen + 1 >= sizeof( voipString ) ) { CG_Printf( S_COLOR_YELLOW "WARNING: voipString overflowed\n" ); break; } + + slen += nlen; } // Notice that if the snprintf was truncated, slen was not updated @@ -2119,3 +2027,26 @@ char *CG_VoIPString( void ) return voipString; } + +#ifdef MODULE_INTERFACE_11 +int trap_S_SoundDuration( sfxHandle_t handle ) +{ + return 1; +} + +void trap_R_SetClipRegion( const float *region ) +{ +} + +static qboolean keyOverstrikeMode = qfalse; + +void trap_Key_SetOverstrikeMode( qboolean state ) +{ + keyOverstrikeMode = state; +} + +qboolean trap_Key_GetOverstrikeMode( void ) +{ + return keyOverstrikeMode; +} +#endif diff --git a/src/cgame/cg_marks.c b/src/cgame/cg_marks.c index 380f1f0..a5e2351 100644 --- a/src/cgame/cg_marks.c +++ b/src/cgame/cg_marks.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,14 +17,13 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_marks.c -- wall marks - #include "cg_local.h" /* @@ -286,4 +286,3 @@ void CG_AddMarks( void ) trap_R_AddPolyToScene( mp->markShader, mp->poly.numVerts, mp->verts ); } } - diff --git a/src/cgame/cg_mem.c b/src/cgame/cg_mem.c deleted file mode 100644 index 6cf5ddd..0000000 --- a/src/cgame/cg_mem.c +++ /dev/null @@ -1,202 +0,0 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus - -This file is part of Tremulous. - -Tremulous is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Tremulous is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "cg_local.h" - -#define POOLSIZE (256 * 1024) -#define FREEMEMCOOKIE ((int)0xDEADBE3F) // Any unlikely to be used value -#define ROUNDBITS 31 // Round to 32 bytes - -struct freememnode -{ - // Size of ROUNDBITS - int cookie, size; // Size includes node (obviously) - struct freememnode *prev, *next; -}; - -static char memoryPool[ POOLSIZE ]; -static struct freememnode *freehead; -static int freemem; - -void *CG_Alloc( int size ) -{ - // Find a free block and allocate. - // Does two passes, attempts to fill same-sized free slot first. - - struct freememnode *fmn, *prev, *next, *smallest; - int allocsize, smallestsize; - char *endptr; - int *ptr; - - allocsize = ( size + sizeof(int) + ROUNDBITS ) & ~ROUNDBITS; // Round to 32-byte boundary - ptr = NULL; - - smallest = NULL; - smallestsize = POOLSIZE + 1; // Guaranteed not to miss any slots :) - for( fmn = freehead; fmn; fmn = fmn->next ) - { - if( fmn->cookie != FREEMEMCOOKIE ) - CG_Error( "CG_Alloc: Memory corruption detected!\n" ); - - if( fmn->size >= allocsize ) - { - // We've got a block - if( fmn->size == allocsize ) - { - // Same size, just remove - - prev = fmn->prev; - next = fmn->next; - if( prev ) - prev->next = next; // Point previous node to next - if( next ) - next->prev = prev; // Point next node to previous - if( fmn == freehead ) - freehead = next; // Set head pointer to next - ptr = (int *) fmn; - break; // Stop the loop, this is fine - } - else - { - // Keep track of the smallest free slot - if( fmn->size < smallestsize ) - { - smallest = fmn; - smallestsize = fmn->size; - } - } - } - } - - if( !ptr && smallest ) - { - // We found a slot big enough - smallest->size -= allocsize; - endptr = (char *) smallest + smallest->size; - ptr = (int *) endptr; - } - - if( ptr ) - { - freemem -= allocsize; - if( cg_debugAlloc.integer ) - CG_Printf( "CG_Alloc of %i bytes (%i left)\n", allocsize, freemem ); - memset( ptr, 0, allocsize ); - *ptr++ = allocsize; // Store a copy of size for deallocation - return( (void *) ptr ); - } - - CG_Error( "CG_Alloc: failed on allocation of %i bytes\n", size ); - return( NULL ); -} - -void CG_Free( void *ptr ) -{ - // Release allocated memory, add it to the free list. - - struct freememnode *fmn; - char *freeend; - int *freeptr; - - freeptr = ptr; - freeptr--; - - freemem += *freeptr; - if( cg_debugAlloc.integer ) - CG_Printf( "CG_Free of %i bytes (%i left)\n", *freeptr, freemem ); - - for( fmn = freehead; fmn; fmn = fmn->next ) - { - freeend = ((char *) fmn) + fmn->size; - if( freeend == (char *) freeptr ) - { - // Released block can be merged to an existing node - - fmn->size += *freeptr; // Add size of node. - return; - } - } - // No merging, add to head of list - - fmn = (struct freememnode *) freeptr; - fmn->size = *freeptr; // Set this first to avoid corrupting *freeptr - fmn->cookie = FREEMEMCOOKIE; - fmn->prev = NULL; - fmn->next = freehead; - freehead->prev = fmn; - freehead = fmn; -} - -void CG_InitMemory( void ) -{ - // Set up the initial node - - freehead = (struct freememnode *) memoryPool; - freehead->cookie = FREEMEMCOOKIE; - freehead->size = POOLSIZE; - freehead->next = NULL; - freehead->prev = NULL; - freemem = sizeof(memoryPool); -} - -void CG_DefragmentMemory( void ) -{ - // If there's a frenzy of deallocation and we want to - // allocate something big, this is useful. Otherwise... - // not much use. - - struct freememnode *startfmn, *endfmn, *fmn; - - for( startfmn = freehead; startfmn; ) - { - endfmn = (struct freememnode *)(((char *) startfmn) + startfmn->size); - for( fmn = freehead; fmn; ) - { - if( fmn->cookie != FREEMEMCOOKIE ) - CG_Error( "CG_DefragmentMemory: Memory corruption detected!\n" ); - - if( fmn == endfmn ) - { - // We can add fmn onto startfmn. - - if( fmn->prev ) - fmn->prev->next = fmn->next; - if( fmn->next ) - { - if( !(fmn->next->prev = fmn->prev) ) - freehead = fmn->next; // We're removing the head node - } - startfmn->size += fmn->size; - memset( fmn, 0, sizeof(struct freememnode) ); // A redundant call, really. - - startfmn = freehead; - endfmn = fmn = NULL; // Break out of current loop - } - else - fmn = fmn->next; - } - - if( endfmn ) - startfmn = startfmn->next; // endfmn acts as a 'restart' flag here - } -} diff --git a/src/cgame/cg_particles.c b/src/cgame/cg_particles.c index 80c4b23..eca9de2 100644 --- a/src/cgame/cg_particles.c +++ b/src/cgame/cg_particles.c @@ -1,12 +1,13 @@ /* =========================================================================== -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -15,14 +16,13 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_particles.c -- the particle system - #include "cg_local.h" static baseParticleSystem_t baseParticleSystems[ MAX_BASEPARTICLE_SYSTEMS ]; @@ -157,6 +157,8 @@ static particle_t *CG_SpawnNewParticle( baseParticle_t *bp, particleEjector_t *p p->radius.delay = (int)CG_RandomiseValue( (float)bp->radius.delay, bp->radius.delayRandFrac ); p->radius.initial = CG_RandomiseValue( bp->radius.initial, bp->radius.initialRandFrac ); p->radius.final = CG_RandomiseValue( bp->radius.final, bp->radius.finalRandFrac ); + + p->radius.initial += bp->scaleWithCharge * pe->parent->charge; p->alpha.delay = (int)CG_RandomiseValue( (float)bp->alpha.delay, bp->alpha.delayRandFrac ); p->alpha.initial = CG_RandomiseValue( bp->alpha.initial, bp->alpha.initialRandFrac ); @@ -208,7 +210,7 @@ static particle_t *CG_SpawnNewParticle( baseParticle_t *bp, particleEjector_t *p VectorAdd( p->origin, bp->displacement, p->origin ); for( j = 0; j <= 2; j++ ) - p->origin[ j ] += ( crandom( ) * bp->randDisplacement ); + p->origin[ j ] += ( crandom( ) * bp->randDisplacement[ j ] ); switch( bp->velMoveType ) { @@ -259,6 +261,21 @@ static particle_t *CG_SpawnNewParticle( baseParticle_t *bp, particleEjector_t *p VectorNormalize( p->velocity ); VectorMA( p->origin, bp->normalDisplacement, p->velocity, p->origin ); break; + + case PMT_LAST_NORMAL: + VectorCopy( ps->lastNormal, p->velocity ); + VectorNormalize( p->velocity ); + VectorMA( p->origin, bp->normalDisplacement, p->velocity, p->origin ); + break; + + case PMT_OPPORTUNISTIC_NORMAL: + if( ps->lastNormalIsCurrent ) + { + VectorCopy( ps->lastNormal, p->velocity ); + VectorNormalize( p->velocity ); + VectorMA( p->origin, bp->normalDisplacement, p->velocity, p->origin ); + } + break; } VectorNormalize( p->velocity ); @@ -281,12 +298,18 @@ static particle_t *CG_SpawnNewParticle( baseParticle_t *bp, particleEjector_t *p //this particle has a child particle system attached if( bp->childSystemName[ 0 ] != '\0' ) { - particleSystem_t *ps = CG_SpawnNewParticleSystem( bp->childSystemHandle ); + particleSystem_t *chps = CG_SpawnNewParticleSystem( bp->childSystemHandle ); - if( CG_IsParticleSystemValid( &ps ) ) + if( CG_IsParticleSystemValid( &chps ) ) { - CG_SetAttachmentParticle( &ps->attachment, p ); - CG_AttachToParticle( &ps->attachment ); + CG_SetAttachmentParticle( &chps->attachment, p ); + CG_AttachToParticle( &chps->attachment ); + p->childParticleSystem = chps; + + if( ps->lastNormalIsCurrent ) + CG_SetParticleSystemLastNormal( chps, ps->lastNormal ); + else + VectorCopy( ps->lastNormal, chps->lastNormal ); } } @@ -465,6 +488,9 @@ particleSystem_t *CG_SpawnNewParticleSystem( qhandle_t psHandle ) ps->valid = qtrue; ps->lazyRemove = qfalse; + // use "up" as an arbitrary (non-null) "last" normal + VectorSet( ps->lastNormal, 0, 0, 1 ); + for( j = 0; j < bps->numEjectors; j++ ) CG_SpawnNewParticleEjector( bps->ejectors[ j ], ps ); @@ -567,13 +593,11 @@ Parse a value and its random variance static void CG_ParseValueAndVariance( char *token, float *value, float *variance, qboolean allowNegative ) { char valueBuffer[ 16 ]; - char varianceBuffer[ 16 ]; char *variancePtr = NULL, *varEndPointer = NULL; float localValue = 0.0f; float localVariance = 0.0f; Q_strncpyz( valueBuffer, token, sizeof( valueBuffer ) ); - Q_strncpyz( varianceBuffer, token, sizeof( varianceBuffer ) ); variancePtr = strchr( valueBuffer, '~' ); @@ -623,13 +647,96 @@ static qboolean CG_ParseColor( byte *c, char **text_p ) for( i = 0; i <= 2; i++ ) { token = COM_Parse( text_p ); - - if( !Q_stricmp( token, "" ) ) + if( !*token ) return qfalse; c[ i ] = (int)( (float)0xFF * atof_neg( token, qfalse ) ); } + token = COM_Parse( text_p ); + if( strcmp( token, "}" ) ) + { + CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" ); + return qfalse; + } + + return qtrue; +} + +/* +CG_ParseParticle helpers +*/ +static void CG_CopyLine( int *i, char *toks, size_t num, size_t size, char **text_p ) +{ + char *token; + + while( *i < num ) + { + token = COM_ParseExt( text_p, qfalse ); + if( !*token ) + break; + + Q_strncpyz( toks, token, size ); + ( *i )++; + + toks += size; + } +} + +static qboolean CG_ParseType( pMoveType_t *pmt, char **text_p ) +{ + char *token = COM_Parse( text_p ); + if( !*token ) + return qfalse; + + if( !Q_stricmp( token, "static" ) ) + *pmt = PMT_STATIC; + else if( !Q_stricmp( token, "static_transform" ) ) + *pmt = PMT_STATIC_TRANSFORM; + else if( !Q_stricmp( token, "tag" ) ) + *pmt = PMT_TAG; + else if( !Q_stricmp( token, "cent" ) ) + *pmt = PMT_CENT_ANGLES; + else if( !Q_stricmp( token, "normal" ) ) + *pmt = PMT_NORMAL; + else if( !Q_stricmp( token, "last_normal" ) ) + *pmt = PMT_LAST_NORMAL; + else if( !Q_stricmp( token, "opportunistic_normal" ) ) + *pmt = PMT_OPPORTUNISTIC_NORMAL; + + return qtrue; +} + +static qboolean CG_ParseDir( pMoveValues_t *pmv, char **text_p ) +{ + char *token = COM_Parse( text_p ); + if( !*token ) + return qfalse; + + if( !Q_stricmp( token, "linear" ) ) + pmv->dirType = PMD_LINEAR; + else if( !Q_stricmp( token, "point" ) ) + pmv->dirType = PMD_POINT; + + return qtrue; +} + +static qboolean CG_ParseFinal( pLerpValues_t *plv, char **text_p ) +{ + char *token = COM_Parse( text_p ); + if( !*token ) + return qfalse; + + if( !Q_stricmp( token, "-" ) ) + { + plv->final = PARTICLES_SAME_AS_INITIAL; + plv->finalRandFrac = 0.0f; + } + else + { + CG_ParseValueAndVariance( token, &plv->final, &plv->finalRandFrac, qfalse ); + } + return qtrue; } @@ -650,17 +757,13 @@ static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) while( 1 ) { token = COM_Parse( text_p ); - - if( !token ) - break; - - if( !Q_stricmp( token, "" ) ) + if( !*token ) return qfalse; if( !Q_stricmp( token, "bounce" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; if( !Q_stricmp( token, "cull" ) ) @@ -672,13 +775,9 @@ static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) } else { - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - - bp->bounceFrac = number; - bp->bounceFracRandFrac = randFrac; + CG_ParseValueAndVariance( token, &bp->bounceFrac, + &bp->bounceFracRandFrac, qfalse ); } - - continue; } else if( !Q_stricmp( token, "bounceMark" ) ) { @@ -686,27 +785,21 @@ static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - - bp->bounceMarkCount = number; - bp->bounceMarkCountRandFrac = randFrac; + CG_ParseValueAndVariance( token, &bp->bounceMarkCount, + &bp->bounceMarkCountRandFrac, qfalse ); token = COM_Parse( text_p ); if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - - bp->bounceMarkRadius = number; - bp->bounceMarkRadiusRandFrac = randFrac; + CG_ParseValueAndVariance( token, &bp->bounceMarkRadius, + &bp->bounceMarkRadiusRandFrac, qfalse ); token = COM_ParseExt( text_p, qfalse ); if( !*token ) break; Q_strncpyz( bp->bounceMarkName, token, MAX_QPATH ); - - continue; } else if( !Q_stricmp( token, "bounceSound" ) ) { @@ -714,30 +807,26 @@ static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - - bp->bounceSoundCount = number; - bp->bounceSoundCountRandFrac = randFrac; + CG_ParseValueAndVariance( token, &bp->bounceSoundCount, + &bp->bounceSoundCountRandFrac, qfalse ); token = COM_Parse( text_p ); if( !*token ) break; Q_strncpyz( bp->bounceSoundName, token, MAX_QPATH ); - - continue; } else if( !Q_stricmp( token, "shader" ) ) { if( bp->numModels > 0 ) { CG_Printf( S_COLOR_RED "ERROR: 'shader' not allowed in " - "conjunction with 'model'\n", token ); + "conjunction with 'model'\n" ); break; } token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; if( !Q_stricmp( token, "sync" ) ) @@ -745,38 +834,20 @@ static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) else bp->framerate = atof_neg( token, qfalse ); - token = COM_ParseExt( text_p, qfalse ); - if( !*token ) - break; - - while( *token && bp->numFrames < MAX_PS_SHADER_FRAMES ) - { - Q_strncpyz( bp->shaderNames[ bp->numFrames++ ], token, MAX_QPATH ); - token = COM_ParseExt( text_p, qfalse ); - } - - continue; + CG_CopyLine( &bp->numFrames, bp->shaderNames[ 0 ], + ARRAY_LEN( bp->shaderNames ), MAX_QPATH, text_p ); } else if( !Q_stricmp( token, "model" ) ) { if( bp->numFrames > 0 ) { CG_Printf( S_COLOR_RED "ERROR: 'model' not allowed in " - "conjunction with 'shader'\n", token ); + "conjunction with 'shader'\n" ); break; } - token = COM_ParseExt( text_p, qfalse ); - if( !*token ) - break; - - while( *token && bp->numModels < MAX_PS_MODELS ) - { - Q_strncpyz( bp->modelNames[ bp->numModels++ ], token, MAX_QPATH ); - token = COM_ParseExt( text_p, qfalse ); - } - - continue; + CG_CopyLine( &bp->numModels, bp->modelNames[ 0 ], + ARRAY_LEN( bp->modelNames ), MAX_QPATH, text_p ); } else if( !Q_stricmp( token, "modelAnimation" ) ) { @@ -823,200 +894,130 @@ static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) if( fps == 0.0f ) fps = 1.0f; - bp->modelAnimation.frameLerp = 1000 / fps; - bp->modelAnimation.initialLerp = 1000 / fps; + bp->modelAnimation.frameLerp = bp->modelAnimation.initialLerp = + 1000 / fps; } - - continue; } /// else if( !Q_stricmp( token, "velocityType" ) ) { - token = COM_Parse( text_p ); - if( !token ) + if( !CG_ParseType( &bp->velMoveType, text_p ) ) break; - - if( !Q_stricmp( token, "static" ) ) - bp->velMoveType = PMT_STATIC; - else if( !Q_stricmp( token, "static_transform" ) ) - bp->velMoveType = PMT_STATIC_TRANSFORM; - else if( !Q_stricmp( token, "tag" ) ) - bp->velMoveType = PMT_TAG; - else if( !Q_stricmp( token, "cent" ) ) - bp->velMoveType = PMT_CENT_ANGLES; - else if( !Q_stricmp( token, "normal" ) ) - bp->velMoveType = PMT_NORMAL; - - continue; } else if( !Q_stricmp( token, "velocityDir" ) ) { - token = COM_Parse( text_p ); - if( !token ) + if( !CG_ParseDir( &bp->velMoveValues, text_p ) ) break; - - if( !Q_stricmp( token, "linear" ) ) - bp->velMoveValues.dirType = PMD_LINEAR; - else if( !Q_stricmp( token, "point" ) ) - bp->velMoveValues.dirType = PMD_POINT; - - continue; } else if( !Q_stricmp( token, "velocityMagnitude" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - - bp->velMoveValues.mag = number; - bp->velMoveValues.magRandFrac = randFrac; - - continue; + CG_ParseValueAndVariance( token, &bp->velMoveValues.mag, + &bp->velMoveValues.magRandFrac, qtrue ); } else if( !Q_stricmp( token, "parentVelocityFraction" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - - bp->velMoveValues.parentVelFrac = number; - bp->velMoveValues.parentVelFracRandFrac = randFrac; - - continue; + CG_ParseValueAndVariance( token, &bp->velMoveValues.parentVelFrac, + &bp->velMoveValues.parentVelFracRandFrac, qfalse ); } else if( !Q_stricmp( token, "velocity" ) ) { for( i = 0; i <= 2; i++ ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; bp->velMoveValues.dir[ i ] = atof_neg( token, qtrue ); } token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse ); - - bp->velMoveValues.dirRandAngle = randFrac; - - continue; + CG_ParseValueAndVariance( token, NULL, &bp->velMoveValues.dirRandAngle, + qfalse ); } else if( !Q_stricmp( token, "velocityPoint" ) ) { for( i = 0; i <= 2; i++ ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; bp->velMoveValues.point[ i ] = atof_neg( token, qtrue ); } token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse ); - - bp->velMoveValues.pointRandAngle = randFrac; - - continue; + CG_ParseValueAndVariance( token, NULL, &bp->velMoveValues.pointRandAngle, + qfalse ); } /// else if( !Q_stricmp( token, "accelerationType" ) ) { - token = COM_Parse( text_p ); - if( !token ) + if( !CG_ParseType( &bp->accMoveType, text_p ) ) break; - - if( !Q_stricmp( token, "static" ) ) - bp->accMoveType = PMT_STATIC; - else if( !Q_stricmp( token, "static_transform" ) ) - bp->accMoveType = PMT_STATIC_TRANSFORM; - else if( !Q_stricmp( token, "tag" ) ) - bp->accMoveType = PMT_TAG; - else if( !Q_stricmp( token, "cent" ) ) - bp->accMoveType = PMT_CENT_ANGLES; - else if( !Q_stricmp( token, "normal" ) ) - bp->accMoveType = PMT_NORMAL; - - continue; } else if( !Q_stricmp( token, "accelerationDir" ) ) { - token = COM_Parse( text_p ); - if( !token ) + if( !CG_ParseDir( &bp->accMoveValues, text_p ) ) break; - - if( !Q_stricmp( token, "linear" ) ) - bp->accMoveValues.dirType = PMD_LINEAR; - else if( !Q_stricmp( token, "point" ) ) - bp->accMoveValues.dirType = PMD_POINT; - - continue; } else if( !Q_stricmp( token, "accelerationMagnitude" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - - bp->accMoveValues.mag = number; - bp->accMoveValues.magRandFrac = randFrac; - - continue; + CG_ParseValueAndVariance( token, &bp->accMoveValues.mag, + &bp->accMoveValues.magRandFrac, qtrue ); } else if( !Q_stricmp( token, "acceleration" ) ) { for( i = 0; i <= 2; i++ ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; bp->accMoveValues.dir[ i ] = atof_neg( token, qtrue ); } token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse ); - - bp->accMoveValues.dirRandAngle = randFrac; - - continue; + CG_ParseValueAndVariance( token, NULL, &bp->accMoveValues.dirRandAngle, + qfalse ); } else if( !Q_stricmp( token, "accelerationPoint" ) ) { for( i = 0; i <= 2; i++ ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; bp->accMoveValues.point[ i ] = atof_neg( token, qtrue ); } token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse ); - - bp->accMoveValues.pointRandAngle = randFrac; - - continue; + CG_ParseValueAndVariance( token, NULL, &bp->accMoveValues.pointRandAngle, + qfalse ); } /// else if( !Q_stricmp( token, "displacement" ) ) @@ -1024,43 +1025,45 @@ static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) for( i = 0; i <= 2; i++ ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - bp->displacement[ i ] = atof_neg( token, qtrue ); + CG_ParseValueAndVariance( token, &bp->displacement[ i ], + &bp->randDisplacement[ i ], qtrue ); } - token = COM_Parse( text_p ); - if( !token ) - break; - - CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse ); + // if there is another token on the same line interpret it as an + // additional displacement in all three directions, for compatibility + // with the old scripts where this was the only option + randFrac = 0; + token = COM_ParseExt( text_p, qfalse ); + if( token ) + CG_ParseValueAndVariance( token, NULL, &randFrac, qtrue ); - bp->randDisplacement = randFrac; + for( i = 0; i < 3; i++ ) + { + // convert randDisplacement from proportions to absolute values + if( bp->displacement[ i ] != 0 ) + bp->randDisplacement[ i ] *= bp->displacement[ i ]; - continue; + bp->randDisplacement[ i ] += randFrac; + } } else if( !Q_stricmp( token, "normalDisplacement" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; bp->normalDisplacement = atof_neg( token, qtrue ); - - continue; } else if( !Q_stricmp( token, "overdrawProtection" ) ) { bp->overdrawProtection = qtrue; - - continue; } else if( !Q_stricmp( token, "realLight" ) ) { bp->realLight = qtrue; - - continue; } else if( !Q_stricmp( token, "dynamicLight" ) ) { @@ -1070,37 +1073,20 @@ static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - + CG_ParseValueAndVariance( token, &number, &bp->dLightRadius.delayRandFrac, + qfalse ); bp->dLightRadius.delay = (int)number; - bp->dLightRadius.delayRandFrac = randFrac; token = COM_Parse( text_p ); if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + CG_ParseValueAndVariance( token, &bp->dLightRadius.initial, + &bp->dLightRadius.initialRandFrac, qfalse ); - bp->dLightRadius.initial = number; - bp->dLightRadius.initialRandFrac = randFrac; - - token = COM_Parse( text_p ); - if( !*token ) + if( !CG_ParseFinal( &bp->dLightRadius, text_p ) ) break; - if( !Q_stricmp( token, "-" ) ) - { - bp->dLightRadius.final = PARTICLES_SAME_AS_INITIAL; - bp->dLightRadius.finalRandFrac = 0.0f; - } - else - { - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - - bp->dLightRadius.final = number; - bp->dLightRadius.finalRandFrac = randFrac; - } - token = COM_Parse( text_p ); if( !*token ) break; @@ -1109,100 +1095,59 @@ static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) { if( !CG_ParseColor( bp->dLightColor, text_p ) ) break; - - token = COM_Parse( text_p ); - if( Q_stricmp( token, "}" ) ) - { - CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" ); - break; - } } - - continue; } else if( !Q_stricmp( token, "cullOnStartSolid" ) ) { bp->cullOnStartSolid = qtrue; - - continue; } else if( !Q_stricmp( token, "radius" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - + CG_ParseValueAndVariance( token, &number, &bp->radius.delayRandFrac, + qfalse ); bp->radius.delay = (int)number; - bp->radius.delayRandFrac = randFrac; token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - - bp->radius.initial = number; - bp->radius.initialRandFrac = randFrac; + CG_ParseValueAndVariance( token, &bp->radius.initial, + &bp->radius.initialRandFrac, qfalse ); + if( !CG_ParseFinal( &bp->radius, text_p ) ) + break; + } + else if( !Q_stricmp( token, "physicsRadius" ) ) + { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - - if( !Q_stricmp( token, "-" ) ) - { - bp->radius.final = PARTICLES_SAME_AS_INITIAL; - bp->radius.finalRandFrac = 0.0f; - } - else - { - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - - bp->radius.final = number; - bp->radius.finalRandFrac = randFrac; - } - - continue; + + bp->physicsRadius = atoi( token ); } else if( !Q_stricmp( token, "alpha" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - + CG_ParseValueAndVariance( token, &number, &bp->alpha.delayRandFrac, + qfalse ); bp->alpha.delay = (int)number; - bp->alpha.delayRandFrac = randFrac; token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - - bp->alpha.initial = number; - bp->alpha.initialRandFrac = randFrac; + CG_ParseValueAndVariance( token, &bp->alpha.initial, + &bp->alpha.initialRandFrac, qfalse ); - token = COM_Parse( text_p ); - if( !token ) + if( !CG_ParseFinal( &bp->alpha, text_p ) ) break; - - if( !Q_stricmp( token, "-" ) ) - { - bp->alpha.final = PARTICLES_SAME_AS_INITIAL; - bp->alpha.finalRandFrac = 0.0f; - } - else - { - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - - bp->alpha.final = number; - bp->alpha.finalRandFrac = randFrac; - } - - continue; } else if( !Q_stricmp( token, "color" ) ) { @@ -1210,10 +1155,9 @@ static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - + CG_ParseValueAndVariance( token, &number, &bp->colorDelayRandFrac, + qfalse ); bp->colorDelay = (int)number; - bp->colorDelayRandFrac = randFrac; token = COM_Parse( text_p ); if( !*token ) @@ -1224,34 +1168,18 @@ static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) if( !CG_ParseColor( bp->initialColor, text_p ) ) break; - token = COM_Parse( text_p ); - if( Q_stricmp( token, "}" ) ) - { - CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" ); - break; - } - token = COM_Parse( text_p ); if( !*token ) break; if( !Q_stricmp( token, "-" ) ) { - bp->finalColor[ 0 ] = bp->initialColor[ 0 ]; - bp->finalColor[ 1 ] = bp->initialColor[ 1 ]; - bp->finalColor[ 2 ] = bp->initialColor[ 2 ]; + memcpy( bp->finalColor, bp->initialColor, sizeof( bp->finalColor ) ); } else if( !Q_stricmp( token, "{" ) ) { if( !CG_ParseColor( bp->finalColor, text_p ) ) break; - - token = COM_Parse( text_p ); - if( Q_stricmp( token, "}" ) ) - { - CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" ); - break; - } } else { @@ -1264,90 +1192,69 @@ static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) CG_Printf( S_COLOR_RED "ERROR: missing '{'\n" ); break; } - - continue; } else if( !Q_stricmp( token, "rotation" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - + CG_ParseValueAndVariance( token, &number, &bp->rotation.delayRandFrac, + qfalse ); bp->rotation.delay = (int)number; - bp->rotation.delayRandFrac = randFrac; token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qtrue ); - - bp->rotation.initial = number; - bp->rotation.initialRandFrac = randFrac; + CG_ParseValueAndVariance( token, &bp->rotation.initial, + &bp->rotation.initialRandFrac, qtrue ); - token = COM_Parse( text_p ); - if( !token ) + if( !CG_ParseFinal( &bp->rotation, text_p ) ) break; - - if( !Q_stricmp( token, "-" ) ) - { - bp->rotation.final = PARTICLES_SAME_AS_INITIAL; - bp->rotation.finalRandFrac = 0.0f; - } - else - { - CG_ParseValueAndVariance( token, &number, &randFrac, qtrue ); - - bp->rotation.final = number; - bp->rotation.finalRandFrac = randFrac; - } - - continue; } else if( !Q_stricmp( token, "lifeTime" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - + CG_ParseValueAndVariance( token, &number, &bp->lifeTimeRandFrac, qfalse ); bp->lifeTime = (int)number; - bp->lifeTimeRandFrac = randFrac; continue; } else if( !Q_stricmp( token, "childSystem" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; Q_strncpyz( bp->childSystemName, token, MAX_QPATH ); - - continue; } else if( !Q_stricmp( token, "onDeathSystem" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; Q_strncpyz( bp->onDeathSystemName, token, MAX_QPATH ); - - continue; } else if( !Q_stricmp( token, "childTrailSystem" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; Q_strncpyz( bp->childTrailSystemName, token, MAX_QPATH ); + } + else if( !Q_stricmp( token, "scaleWithCharge" ) ) + { + token = COM_Parse( text_p ); + if( !*token ) + break; - continue; + bp->scaleWithCharge = atof( token ); } else if( !Q_stricmp( token, "}" ) ) return qtrue; //reached the end of this particle @@ -1384,17 +1291,14 @@ Parse a particle ejector section static qboolean CG_ParseParticleEjector( baseParticleEjector_t *bpe, char **text_p ) { char *token; - float number, randFrac; + float number; // read optional parameters while( 1 ) { token = COM_Parse( text_p ); - if( !token ) - break; - - if( !Q_stricmp( token, "" ) ) + if( !*token ) return qfalse; if( !Q_stricmp( token, "{" ) ) @@ -1412,43 +1316,38 @@ static qboolean CG_ParseParticleEjector( baseParticleEjector_t *bpe, char **text CG_Printf( S_COLOR_RED "ERROR: ejector has > %d particles\n", MAX_PARTICLES_PER_EJECTOR ); return qfalse; } - else if( numBaseParticles == MAX_BASEPARTICLES ) + + if( numBaseParticles == MAX_BASEPARTICLES ) { CG_Printf( S_COLOR_RED "ERROR: maximum number of particles (%d) reached\n", MAX_BASEPARTICLES ); return qfalse; } - else - { - //start parsing particles again - bpe->particles[ bpe->numParticles ] = &baseParticles[ numBaseParticles ]; - bpe->numParticles++; - numBaseParticles++; - } - continue; + + //start parsing particles again + bpe->particles[ bpe->numParticles ] = &baseParticles[ numBaseParticles ]; + bpe->numParticles++; + numBaseParticles++; } else if( !Q_stricmp( token, "delay" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - + CG_ParseValueAndVariance( token, &number, &bpe->eject.delayRandFrac, + qfalse ); bpe->eject.delay = (int)number; - bpe->eject.delayRandFrac = randFrac; - - continue; } else if( !Q_stricmp( token, "period" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; bpe->eject.initial = atoi_neg( token, qfalse ); token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; if( !Q_stricmp( token, "-" ) ) @@ -1457,17 +1356,15 @@ static qboolean CG_ParseParticleEjector( baseParticleEjector_t *bpe, char **text bpe->eject.final = atoi_neg( token, qfalse ); token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; CG_ParseValueAndVariance( token, NULL, &bpe->eject.randFrac, qfalse ); - - continue; } else if( !Q_stricmp( token, "count" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; if( !Q_stricmp( token, "infinite" ) ) @@ -1477,13 +1374,10 @@ static qboolean CG_ParseParticleEjector( baseParticleEjector_t *bpe, char **text } else { - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - + CG_ParseValueAndVariance( token, &number, &bpe->totalParticlesRandFrac, + qfalse ); bpe->totalParticles = (int)number; - bpe->totalParticlesRandFrac = randFrac; } - - continue; } else if( !Q_stricmp( token, "particle" ) ) //acceptable text continue; @@ -1517,10 +1411,7 @@ static qboolean CG_ParseParticleSystem( baseParticleSystem_t *bps, char **text_p { token = COM_Parse( text_p ); - if( !token ) - break; - - if( !Q_stricmp( token, "" ) ) + if( !*token ) return qfalse; if( !Q_stricmp( token, "{" ) ) @@ -1546,20 +1437,18 @@ static qboolean CG_ParseParticleSystem( baseParticleSystem_t *bps, char **text_p CG_Printf( S_COLOR_RED "ERROR: particle system has > %d ejectors\n", MAX_EJECTORS_PER_SYSTEM ); return qfalse; } - else if( numBaseParticleEjectors == MAX_BASEPARTICLE_EJECTORS ) + + if( numBaseParticleEjectors == MAX_BASEPARTICLE_EJECTORS ) { CG_Printf( S_COLOR_RED "ERROR: maximum number of particle ejectors (%d) reached\n", MAX_BASEPARTICLE_EJECTORS ); return qfalse; } - else - { - //start parsing ejectors again - bps->ejectors[ bps->numEjectors ] = &baseParticleEjectors[ numBaseParticleEjectors ]; - bps->numEjectors++; - numBaseParticleEjectors++; - } - continue; + + //start parsing ejectors again + bps->ejectors[ bps->numEjectors ] = &baseParticleEjectors[ numBaseParticleEjectors ]; + bps->numEjectors++; + numBaseParticleEjectors++; } else if( !Q_stricmp( token, "thirdPersonOnly" ) ) bps->thirdPersonOnly = qtrue; @@ -1602,12 +1491,14 @@ static qboolean CG_ParseParticleFile( const char *fileName ) // load the file len = trap_FS_FOpenFile( fileName, &f, FS_READ ); - if( len <= 0 ) + if( len < 0 ) return qfalse; - if( len >= sizeof( text ) - 1 ) + if( len == 0 || len >= sizeof( text ) - 1 ) { - CG_Printf( S_COLOR_RED "ERROR: particle file %s too long\n", fileName ); + trap_FS_FCloseFile( f ); + CG_Printf( S_COLOR_RED "ERROR: particle file %s is %s\n", fileName, + len == 0 ? "empty" : "too long" ); return qfalse; } @@ -1623,23 +1514,13 @@ static qboolean CG_ParseParticleFile( const char *fileName ) { token = COM_Parse( &text_p ); - if( !Q_stricmp( token, "" ) ) + if( !*token ) break; if( !Q_stricmp( token, "{" ) ) { if( psNameSet ) { - //check for name space clashes - for( i = 0; i < numBaseParticleSystems; i++ ) - { - if( !Q_stricmp( baseParticleSystems[ i ].name, psName ) ) - { - CG_Printf( S_COLOR_RED "ERROR: a particle system is already named %s\n", psName ); - return qfalse; - } - } - Q_strncpyz( baseParticleSystems[ numBaseParticleSystems ].name, psName, MAX_QPATH ); if( !CG_ParseParticleSystem( &baseParticleSystems[ numBaseParticleSystems ], &text_p, psName ) ) @@ -1657,10 +1538,8 @@ static qboolean CG_ParseParticleFile( const char *fileName ) MAX_BASEPARTICLE_SYSTEMS ); return qfalse; } - else - numBaseParticleSystems++; - continue; + numBaseParticleSystems++; } else { @@ -1668,10 +1547,25 @@ static qboolean CG_ParseParticleFile( const char *fileName ) return qfalse; } } - - if( !psNameSet ) + else if( !psNameSet ) { Q_strncpyz( psName, token, sizeof( psName ) ); + + //check for name space clashes + for( i = 0; i < numBaseParticleSystems; i++ ) + { + if( !Q_stricmp( baseParticleSystems[ i ].name, psName ) ) + { + CG_Printf( S_COLOR_RED "ERROR: a particle system is already named %s\n", psName ); + break; + } + } + if( i < numBaseParticleSystems ) + { + SkipBracedSection( &text_p, 0 ); + continue; + } + psNameSet = qtrue; } else @@ -1810,6 +1704,31 @@ void CG_SetParticleSystemNormal( particleSystem_t *ps, vec3_t normal ) ps->normalValid = qtrue; VectorCopy( normal, ps->normal ); VectorNormalize( ps->normal ); + + CG_SetParticleSystemLastNormal( ps, normal ); +} + +/* +=============== +CG_SetParticleSystemLastNormal +=============== +*/ +void CG_SetParticleSystemLastNormal( particleSystem_t *ps, const float *normal ) +{ + if( ps == NULL || !ps->valid ) + { + CG_Printf( S_COLOR_YELLOW "WARNING: tried to modify a NULL particle system\n" ); + return; + } + + if( normal ) + { + ps->lastNormalIsCurrent = qtrue; + VectorCopy( normal, ps->lastNormal ); + VectorNormalize( ps->lastNormal ); + } + else + ps->lastNormalIsCurrent = qfalse; } @@ -2056,6 +1975,17 @@ static void CG_EvaluateParticlePhysics( particle_t *p ) VectorCopy( ps->normal, acceleration ); break; + + case PMT_LAST_NORMAL: + VectorCopy( ps->lastNormal, acceleration ); + break; + + case PMT_OPPORTUNISTIC_NORMAL: + if( ps->lastNormalIsCurrent ) + VectorCopy( ps->lastNormal, acceleration ); + else + VectorClear( acceleration ); + break; } #define MAX_ACC_RADIUS 1000.0f @@ -2086,11 +2016,13 @@ static void CG_EvaluateParticlePhysics( particle_t *p ) acceleration ); } - radius = CG_LerpValues( p->radius.initial, - p->radius.final, - CG_CalculateTimeFrac( p->birthTime, - p->lifeTime, - p->radius.delay ) ); + // Some particles have a visual radius that differs from their collision radius + if( bp->physicsRadius ) + radius = bp->physicsRadius; + else + radius = CG_LerpValues( p->radius.initial, p->radius.final, + CG_CalculateTimeFrac( p->birthTime, p->lifeTime, + p->radius.delay ) ); VectorSet( mins, -radius, -radius, -radius ); VectorSet( maxs, radius, radius, radius ); @@ -2121,6 +2053,8 @@ static void CG_EvaluateParticlePhysics( particle_t *p ) if( trace.fraction == 1.0f || bounce == 0.0f ) { VectorCopy( newOrigin, p->origin ); + if( CG_IsParticleSystemValid( &p->childParticleSystem ) ) + CG_SetParticleSystemLastNormal( p->childParticleSystem, NULL ); return; } @@ -2162,6 +2096,12 @@ static void CG_EvaluateParticlePhysics( particle_t *p ) } VectorCopy( trace.endpos, p->origin ); + + if( !trace.allsolid ) + { + if( CG_IsParticleSystemValid( &p->childParticleSystem ) ) + CG_SetParticleSystemLastNormal( p->childParticleSystem, trace.plane.normal ); + } } @@ -2394,7 +2334,7 @@ static void CG_RenderParticle( particle_t *p ) p->lf.animation = &bp->modelAnimation; //run animation - CG_RunLerpFrame( &p->lf ); + CG_RunLerpFrame( &p->lf, 1.0f ); re.oldframe = p->lf.oldFrame; re.frame = p->lf.frame; @@ -2505,9 +2445,8 @@ void CG_ParticleSystemEntity( centity_t *cent ) if( CG_IsParticleSystemValid( ¢->entityPS ) ) { - CG_SetAttachmentPoint( ¢->entityPS->attachment, cent->lerpOrigin ); CG_SetAttachmentCent( ¢->entityPS->attachment, cent ); - CG_AttachToPoint( ¢->entityPS->attachment ); + CG_AttachToCent( ¢->entityPS->attachment ); } else cent->entityPSMissing = qtrue; diff --git a/src/cgame/cg_players.c b/src/cgame/cg_players.c index 3bd0e65..16779cb 100644 --- a/src/cgame/cg_players.c +++ b/src/cgame/cg_players.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,14 +17,13 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_players.c -- handle the media and animation for player entities - #include "cg_local.h" char *cg_customSoundNames[ MAX_CUSTOM_SOUNDS ] = @@ -107,12 +107,12 @@ static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) // load the file len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if( len <= 0 ) + if( len < 0 ) return qfalse; - if( len >= sizeof( text ) - 1 ) + if( len == 0 || len >= sizeof( text ) - 1 ) { - CG_Printf( "File %s too long\n", filename ); + CG_Printf( "File %s is %s\n", filename, len == 0 ? "empty" : "too long" ); trap_FS_FCloseFile( f ); return qfalse; } @@ -480,51 +480,17 @@ static qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *modelN // if any skins failed to load, return failure if( !CG_RegisterClientSkin( ci, modelName, skinName ) ) { - Com_Printf( "Failed to load skin file: %s : %s\n", modelName, skinName ); - return qfalse; + Com_Printf( "Failed to load skin file: %s : %s. Loading default\n", modelName, skinName ); + if( !CG_RegisterClientSkin( ci, modelName, "default" ) ) + { + Com_Printf( S_COLOR_RED "Failed to load default skin file!\n" ); + return qfalse; + } } - //FIXME: skins do not load without icon present. do we want icons anyway? -/* Com_sprintf( filename, sizeof( filename ), "models/players/%s/icon_%s.tga", modelName, skinName ); - ci->modelIcon = trap_R_RegisterShaderNoMip( filename ); - if( !ci->modelIcon ) - { - Com_Printf( "Failed to load icon file: %s\n", filename ); - return qfalse; - }*/ - return qtrue; } -/* -==================== -CG_ColorFromString -==================== -*/ -static void CG_ColorFromString( const char *v, vec3_t color ) -{ - int val; - - VectorClear( color ); - - val = atoi( v ); - - if( val < 1 || val > 7 ) - { - VectorSet( color, 1, 1, 1 ); - return; - } - - if( val & 1 ) - color[ 2 ] = 1.0f; - - if( val & 2 ) - color[ 1 ] = 1.0f; - - if( val & 4 ) - color[ 0 ] = 1.0f; -} - /* =================== @@ -535,24 +501,16 @@ Load it now, taking the disk hits */ static void CG_LoadClientInfo( clientInfo_t *ci ) { - const char *dir, *fallback; + const char *dir; int i; const char *s; int clientNum; if( !CG_RegisterClientModelname( ci, ci->modelName, ci->skinName ) ) - { - if( cg_buildScript.integer ) - CG_Error( "CG_RegisterClientModelname( %s, %s ) failed", ci->modelName, ci->skinName ); - - // fall back - if( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, "default" ) ) - CG_Error( "DEFAULT_MODEL (%s) failed to register", DEFAULT_MODEL ); - } + CG_Error( "CG_RegisterClientModelname( %s, %s ) failed", ci->modelName, ci->skinName ); // sounds dir = ci->modelName; - fallback = DEFAULT_MODEL; for( i = 0; i < MAX_CUSTOM_SOUNDS; i++ ) { @@ -578,15 +536,11 @@ static void CG_LoadClientInfo( clientInfo_t *ci ) s = cg_customSoundNames[ 0 ]; //death1 ci->sounds[ i ] = trap_S_RegisterSound( va( "sound/player/%s/%s", dir, s + 1 ), qfalse ); - if( !ci->sounds[ i ] ) - ci->sounds[ i ] = trap_S_RegisterSound( va( "sound/player/%s/%s", fallback, s + 1 ), qfalse ); } } else { ci->sounds[ i ] = trap_S_RegisterSound( va( "sound/player/%s/%s", dir, s + 1 ), qfalse ); - if( !ci->sounds[ i ] ) - ci->sounds[ i ] = trap_S_RegisterSound( va( "sound/player/%s/%s", fallback, s + 1 ), qfalse ); } } @@ -649,15 +603,15 @@ static void CG_CopyClientInfoModel( clientInfo_t *from, clientInfo_t *to ) CG_GetCorpseNum ====================== */ -static int CG_GetCorpseNum( pClass_t class ) +static int CG_GetCorpseNum( class_t class ) { int i; clientInfo_t *match; char *modelName; char *skinName; - modelName = BG_FindModelNameForClass( class ); - skinName = BG_FindSkinNameForClass( class ); + modelName = BG_ClassConfig( class )->modelName; + skinName = BG_ClassConfig( class )->skinName; for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ ) { @@ -666,10 +620,10 @@ static int CG_GetCorpseNum( pClass_t class ) if( !match->infoValid ) continue; - if( !Q_stricmp( modelName, match->modelName ) - && !Q_stricmp( skinName, match->skinName ) ) + if( !Q_stricmp( modelName, match->modelName ) && + !Q_stricmp( skinName, match->skinName ) ) { - // this clientinfo is identical, so use it's handles + // this clientinfo is identical, so use its handles return i; } } @@ -716,7 +670,7 @@ static qboolean CG_ScanForExistingClientInfo( clientInfo_t *ci ) CG_PrecacheClientInfo ====================== */ -void CG_PrecacheClientInfo( pClass_t class, char *model, char *skin ) +void CG_PrecacheClientInfo( class_t class, char *model, char *skin ) { clientInfo_t *ci; clientInfo_t newInfo; @@ -728,19 +682,12 @@ void CG_PrecacheClientInfo( pClass_t class, char *model, char *skin ) // model Q_strncpyz( newInfo.modelName, model, sizeof( newInfo.modelName ) ); - Q_strncpyz( newInfo.headModelName, model, sizeof( newInfo.headModelName ) ); - // modelName didn not include a skin name + // modelName did not include a skin name if( !skin ) - { Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); - Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) ); - } else - { Q_strncpyz( newInfo.skinName, skin, sizeof( newInfo.skinName ) ); - Q_strncpyz( newInfo.headSkinName, skin, sizeof( newInfo.headSkinName ) ); - } newInfo.infoValid = qtrue; @@ -749,6 +696,34 @@ void CG_PrecacheClientInfo( pClass_t class, char *model, char *skin ) CG_LoadClientInfo( ci ); } +/* +============= +CG_StatusMessages + +Print messages for player status changes +============= +*/ +static void CG_StatusMessages( clientInfo_t *new, clientInfo_t *old ) +{ + if( !old->infoValid ) + return; + + if( strcmp( new->name, old->name ) ) + CG_Printf( "%s" S_COLOR_WHITE " renamed to %s\n", old->name, new->name ); + + if( old->team != new->team ) + { + if( new->team == TEAM_NONE ) + CG_Printf( "%s" S_COLOR_WHITE " left the %ss\n", new->name, + BG_TeamName( old->team ) ); + else if( old->team == TEAM_NONE ) + CG_Printf( "%s" S_COLOR_WHITE " joined the %ss\n", new->name, + BG_TeamName( new->team ) ); + else + CG_Printf( "%s" S_COLOR_WHITE " left the %ss and joined the %ss\n", + new->name, BG_TeamName( old->team ), BG_TeamName( new->team ) ); + } +} /* ====================== @@ -774,45 +749,35 @@ void CG_NewClientInfo( int clientNum ) // the old value memset( &newInfo, 0, sizeof( newInfo ) ); - + // isolate the player's name v = Info_ValueForKey( configstring, "n" ); Q_strncpyz( newInfo.name, v, sizeof( newInfo.name ) ); - // colors - v = Info_ValueForKey( configstring, "c1" ); - CG_ColorFromString( v, newInfo.color1 ); - - v = Info_ValueForKey( configstring, "c2" ); - CG_ColorFromString( v, newInfo.color2 ); - - // bot skill - v = Info_ValueForKey( configstring, "skill" ); - newInfo.botSkill = atoi( v ); - - // handicap - v = Info_ValueForKey( configstring, "hc" ); - newInfo.handicap = atoi( v ); - - // wins - v = Info_ValueForKey( configstring, "w" ); - newInfo.wins = atoi( v ); - - // losses - v = Info_ValueForKey( configstring, "l" ); - newInfo.losses = atoi( v ); - // team v = Info_ValueForKey( configstring, "t" ); newInfo.team = atoi( v ); - // team task - v = Info_ValueForKey( configstring, "tt" ); - newInfo.teamTask = atoi( v ); + // if this is us, execute team-specific config files + // the spectator config is a little unreliable because it's easy to get on + // to the spectator team without joining it - e.g. when a new game starts. + // It's not a big deal because the spec config is the least important + // slash used anyway. + // I guess it's possible for someone to change teams during a restart and + // for that to then be missed here. But that's rare enough that people can + // just exec the configs manually, I think. + if( clientNum == cg.clientNum && ci->infoValid && + ci->team != newInfo.team ) + { + char config[ MAX_CVAR_VALUE_STRING ]; - // team leader - v = Info_ValueForKey( configstring, "tl" ); - newInfo.teamLeader = atoi( v ); + trap_Cvar_VariableStringBuffer( + va( "cg_%sConfig", BG_TeamName( newInfo.team ) ), + config, sizeof( config ) ); + + if( config[ 0 ] ) + trap_SendConsoleCommand( va( "exec \"%s\"\n", config ) ); + } // model v = Info_ValueForKey( configstring, "model" ); @@ -832,25 +797,11 @@ void CG_NewClientInfo( int clientNum ) *slash = 0; } - //CG_Printf( "NCI: %s\n", v ); - - // head model - v = Info_ValueForKey( configstring, "hmodel" ); - Q_strncpyz( newInfo.headModelName, v, sizeof( newInfo.headModelName ) ); + // voice + v = Info_ValueForKey( configstring, "v" ); + Q_strncpyz( newInfo.voice, v, sizeof( newInfo.voice ) ); - slash = strchr( newInfo.headModelName, '/' ); - - if( !slash ) - { - // modelName didn not include a skin name - Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) ); - } - else - { - Q_strncpyz( newInfo.headSkinName, slash + 1, sizeof( newInfo.headSkinName ) ); - // truncate modelName - *slash = 0; - } + CG_StatusMessages( &newInfo, ci ); // replace whatever was there with the new one newInfo.infoValid = qtrue; @@ -909,90 +860,11 @@ cg.time should be between oldFrameTime and frameTime after exit */ static void CG_RunPlayerLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float speedScale ) { - int f, numFrames; - animation_t *anim; - - // debugging tool to get no animations - if( cg_animSpeed.integer == 0 ) - { - lf->oldFrame = lf->frame = lf->backlerp = 0; - return; - } - // see if the animation sequence is switching if( newAnimation != lf->animationNumber || !lf->animation ) - { CG_SetLerpFrameAnimation( ci, lf, newAnimation ); - } - - // if we have passed the current frame, move it to - // oldFrame and calculate a new frame - if( cg.time >= lf->frameTime ) - { - lf->oldFrame = lf->frame; - lf->oldFrameTime = lf->frameTime; - - // get the next frame based on the animation - anim = lf->animation; - if( !anim->frameLerp ) - return; // shouldn't happen - - if( cg.time < lf->animationTime ) - lf->frameTime = lf->animationTime; // initial lerp - else - lf->frameTime = lf->oldFrameTime + anim->frameLerp; - f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp; - f *= speedScale; // adjust for haste, etc - numFrames = anim->numFrames; - - if( anim->flipflop ) - numFrames *= 2; - - if( f >= numFrames ) - { - f -= numFrames; - if( anim->loopFrames ) - { - f %= anim->loopFrames; - f += anim->numFrames - anim->loopFrames; - } - else - { - f = numFrames - 1; - // the animation is stuck at the end, so it - // can immediately transition to another sequence - lf->frameTime = cg.time; - } - } - - if( anim->reversed ) - lf->frame = anim->firstFrame + anim->numFrames - 1 - f; - else if( anim->flipflop && f>=anim->numFrames ) - lf->frame = anim->firstFrame + anim->numFrames - 1 - ( f % anim->numFrames ); - else - lf->frame = anim->firstFrame + f; - - if( cg.time > lf->frameTime ) - { - lf->frameTime = cg.time; - - if( cg_debugAnim.integer ) - CG_Printf( "Clamp lf->frameTime\n" ); - } - } - - if( lf->frameTime > cg.time + 200 ) - lf->frameTime = cg.time; - - if( lf->oldFrameTime > cg.time ) - lf->oldFrameTime = cg.time; - - // calculate current lerp value - if( lf->frameTime == lf->oldFrameTime ) - lf->backlerp = 0; - else - lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime ); + CG_RunLerpFrame( lf, speedScale ); } @@ -1735,13 +1607,6 @@ static void CG_PlayerSprites( centity_t *cent ) CG_PlayerFloatSprite( cent, cgs.media.connectionShader ); return; } - - if( cent->currentState.eFlags & EF_TALK ) - { - // the masses have decreed this to be wrong -/* CG_PlayerFloatSprite( cent, cgs.media.balloonShader ); - return;*/ - } } /* @@ -1754,7 +1619,7 @@ Returns the Z component of the surface being shadowed =============== */ #define SHADOW_DISTANCE 128 -static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane, pClass_t class ) +static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane, class_t class ) { vec3_t end, mins, maxs; trace_t trace; @@ -1762,7 +1627,7 @@ static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane, pClass_t c entityState_t *es = ¢->currentState; vec3_t surfNormal = { 0.0f, 0.0f, 1.0f }; - BG_FindBBoxForClass( class, mins, maxs, NULL, NULL, NULL ); + BG_ClassBoundingBox( class, mins, maxs, NULL, NULL, NULL ); mins[ 2 ] = 0.0f; maxs[ 2 ] = 2.0f; @@ -1808,7 +1673,7 @@ static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane, pClass_t c // without taking a spot in the cg_marks array CG_ImpactMark( cgs.media.shadowMarkShader, trace.endpos, trace.plane.normal, cent->pe.legs.yawAngle, 0.0f, 0.0f, 0.0f, alpha, qfalse, - 24.0f * BG_FindShadowScaleForClass( class ), qtrue ); + 24.0f * BG_ClassConfig( class )->shadowScale, qtrue ); return qtrue; } @@ -1821,7 +1686,7 @@ CG_PlayerSplash Draw a mark at the water surface =============== */ -static void CG_PlayerSplash( centity_t *cent, pClass_t class ) +static void CG_PlayerSplash( centity_t *cent, class_t class ) { vec3_t start, end; vec3_t mins, maxs; @@ -1831,7 +1696,7 @@ static void CG_PlayerSplash( centity_t *cent, pClass_t class ) if( !cg_shadows.integer ) return; - BG_FindBBoxForClass( class, mins, maxs, NULL, NULL, NULL ); + BG_ClassBoundingBox( class, mins, maxs, NULL, NULL, NULL ); VectorCopy( cent->lerpOrigin, end ); end[ 2 ] += mins[ 2 ]; @@ -1861,7 +1726,7 @@ static void CG_PlayerSplash( centity_t *cent, pClass_t class ) CG_ImpactMark( cgs.media.wakeMarkShader, trace.endpos, trace.plane.normal, cent->pe.legs.yawAngle, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, - 32.0f * BG_FindShadowScaleForClass( class ), qtrue ); + 32.0f * BG_ClassConfig( class )->shadowScale, qtrue ); } @@ -2012,7 +1877,7 @@ void CG_Player( centity_t *cent ) qboolean shadow = qfalse; float shadowPlane = 0.0f; entityState_t *es = ¢->currentState; - pClass_t class = ( es->misc >> 8 ) & 0xFF; + class_t class = ( es->misc >> 8 ) & 0xFF; float scale; vec3_t tempAxis[ 3 ], tempAxis2[ 3 ]; vec3_t angles; @@ -2051,7 +1916,7 @@ void CG_Player( centity_t *cent ) { vec3_t mins, maxs; - BG_FindBBoxForClass( class, mins, maxs, NULL, NULL, NULL ); + BG_ClassBoundingBox( class, mins, maxs, NULL, NULL, NULL ); CG_DrawBoundingBox( cent->lerpOrigin, mins, maxs ); } @@ -2150,7 +2015,7 @@ void CG_Player( centity_t *cent ) else VectorCopy( es->angles2, surfNormal ); - BG_FindBBoxForClass( class, mins, maxs, NULL, NULL, NULL ); + BG_ClassBoundingBox( class, mins, maxs, NULL, NULL, NULL ); VectorMA( legs.origin, -TRACE_DEPTH, surfNormal, end ); VectorMA( legs.origin, 1.0f, surfNormal, start ); @@ -2166,7 +2031,7 @@ void CG_Player( centity_t *cent ) } //rescale the model - scale = BG_FindModelScaleForClass( class ); + scale = BG_ClassConfig( class )->modelScale; if( scale != 1.0f ) { @@ -2178,7 +2043,7 @@ void CG_Player( centity_t *cent ) } //offset on the Z axis if required - VectorMA( legs.origin, BG_FindZOffsetForClass( class ), surfNormal, legs.origin ); + VectorMA( legs.origin, BG_ClassConfig( class )->zOffset, surfNormal, legs.origin ); VectorCopy( legs.origin, legs.lightingOrigin ); VectorCopy( legs.origin, legs.oldorigin ); // don't positionally lerp at all @@ -2233,6 +2098,21 @@ void CG_Player( centity_t *cent ) head.renderfx = renderfx; trap_R_AddRefEntityToScene( &head ); + + // if this player has been hit with poison cloud, add an effect PS + if( ( es->eFlags & EF_POISONCLOUDED ) && + ( es->number != cg.snap->ps.clientNum || cg.renderingThirdPerson ) ) + { + if( !CG_IsParticleSystemValid( ¢->poisonCloudedPS ) ) + cent->poisonCloudedPS = CG_SpawnNewParticleSystem( cgs.media.poisonCloudedPS ); + + CG_SetAttachmentTag( ¢->poisonCloudedPS->attachment, + head, head.hModel, "tag_head" ); + CG_SetAttachmentCent( ¢->poisonCloudedPS->attachment, cent ); + CG_AttachToTag( ¢->poisonCloudedPS->attachment ); + } + else if( CG_IsParticleSystemValid( ¢->poisonCloudedPS ) ) + CG_DestroyParticleSystem( ¢->poisonCloudedPS ); } // @@ -2247,7 +2127,7 @@ void CG_Player( centity_t *cent ) } CG_PlayerUpgrades( cent, &torso ); - + //sanity check that particle systems are stopped when dead if( es->eFlags & EF_DEAD ) { @@ -2277,7 +2157,7 @@ void CG_Corpse( centity_t *cent ) int renderfx; qboolean shadow = qfalse; float shadowPlane; - vec3_t origin, liveZ, deadZ; + vec3_t origin, aliveZ, deadZ; float scale; corpseNum = CG_GetCorpseNum( es->clientNum ); @@ -2297,10 +2177,8 @@ void CG_Corpse( centity_t *cent ) memset( &head, 0, sizeof( head ) ); VectorCopy( cent->lerpOrigin, origin ); - BG_FindBBoxForClass( es->clientNum, liveZ, NULL, NULL, deadZ, NULL ); - origin[ 2 ] -= ( liveZ[ 2 ] - deadZ[ 2 ] ); - - VectorCopy( es->angles, cent->lerpAngles ); + BG_ClassBoundingBox( es->clientNum, aliveZ, NULL, NULL, deadZ, NULL ); + origin[ 2 ] -= ( aliveZ[ 2 ] - deadZ[ 2 ] ); // get the rotation information if( !ci->nonsegmented ) @@ -2364,11 +2242,11 @@ void CG_Corpse( centity_t *cent ) VectorCopy( origin, legs.lightingOrigin ); legs.shadowPlane = shadowPlane; legs.renderfx = renderfx; - legs.origin[ 2 ] += BG_FindZOffsetForClass( es->clientNum ); + legs.origin[ 2 ] += BG_ClassConfig( es->clientNum )->zOffset; VectorCopy( legs.origin, legs.oldorigin ); // don't positionally lerp at all //rescale the model - scale = BG_FindModelScaleForClass( es->clientNum ); + scale = BG_ClassConfig( es->clientNum )->modelScale; if( scale != 1.0f ) { @@ -2379,7 +2257,6 @@ void CG_Corpse( centity_t *cent ) legs.nonNormalizedAxes = qtrue; } - //CG_AddRefEntityWithPowerups( &legs, es->misc, ci->team ); trap_R_AddRefEntityToScene( &legs ); // if the model failed, allow the default nullmodel to be displayed @@ -2404,7 +2281,6 @@ void CG_Corpse( centity_t *cent ) torso.shadowPlane = shadowPlane; torso.renderfx = renderfx; - //CG_AddRefEntityWithPowerups( &torso, es->misc, ci->team ); trap_R_AddRefEntityToScene( &torso ); // @@ -2423,7 +2299,6 @@ void CG_Corpse( centity_t *cent ) head.shadowPlane = shadowPlane; head.renderfx = renderfx; - //CG_AddRefEntityWithPowerups( &head, es->misc, ci->team ); trap_R_AddRefEntityToScene( &head ); } } @@ -2475,7 +2350,7 @@ void CG_ResetPlayerEntity( centity_t *cent ) cent->pe.nonseg.pitching = qfalse; if( cg_debugPosition.integer ) - CG_Printf( "%i ResetPlayerEntity yaw=%i\n", cent->currentState.number, cent->pe.torso.yawAngle ); + CG_Printf( "%i ResetPlayerEntity yaw=%f\n", cent->currentState.number, cent->pe.torso.yawAngle ); } /* @@ -2500,66 +2375,35 @@ void CG_PlayerDisconnect( vec3_t org ) } } -/* -================= -CG_Bleed - -This is the spurt of blood when a character gets hit -================= -*/ -void CG_Bleed( vec3_t origin, vec3_t normal, int entityNum ) +centity_t *CG_GetPlayerLocation( void ) { - pTeam_t team = cgs.clientinfo[ entityNum ].team; - qhandle_t bleedPS; - particleSystem_t *ps; - - if( !cg_blood.integer ) - return; + int i; + centity_t *eloc, *best; + float bestlen, len; + vec3_t origin; - if( team == PTE_ALIENS ) - bleedPS = cgs.media.alienBleedPS; - else if( team == PTE_HUMANS ) - bleedPS = cgs.media.humanBleedPS; - else - return; + best = NULL; + bestlen = 0.0f; - ps = CG_SpawnNewParticleSystem( bleedPS ); + VectorCopy( cg.predictedPlayerState.origin, origin ); - if( CG_IsParticleSystemValid( &ps ) ) + for( i = MAX_CLIENTS; i < MAX_GENTITIES; i++ ) { - CG_SetAttachmentPoint( &ps->attachment, origin ); - CG_SetAttachmentCent( &ps->attachment, &cg_entities[ entityNum ] ); - CG_AttachToPoint( &ps->attachment ); + eloc = &cg_entities[ i ]; + if( !eloc->valid || eloc->currentState.eType != ET_LOCATION ) + continue; - CG_SetParticleSystemNormal( ps, normal ); - } -} + len = DistanceSquared(origin, eloc->lerpOrigin); -/* -=============== -CG_AtHighestClass + if( best != NULL && len > bestlen ) + continue; -Is the local client at the highest class possible? -=============== -*/ -qboolean CG_AtHighestClass( void ) -{ - int i; - qboolean superiorClasses = qfalse; + if( !trap_R_inPVS( origin, eloc->lerpOrigin ) ) + continue; - for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ ) - { - if( BG_ClassCanEvolveFromTo( - cg.predictedPlayerState.stats[ STAT_PCLASS ], i, - ALIEN_MAX_KILLS, 0 ) >= 0 && - BG_FindStagesForClass( i, cgs.alienStage ) && - BG_ClassIsAllowed( i ) ) - { - superiorClasses = qtrue; - break; - } + bestlen = len; + best = eloc; } - return !superiorClasses; + return best; } - diff --git a/src/cgame/cg_playerstate.c b/src/cgame/cg_playerstate.c index e1bcb09..8ff9be4 100644 --- a/src/cgame/cg_playerstate.c +++ b/src/cgame/cg_playerstate.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -26,7 +27,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // following another player or playing back a demo, it will be checked // when the snapshot transitions like all the other entities - #include "cg_local.h" /* @@ -245,10 +245,8 @@ CG_CheckLocalSounds */ void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) { - int reward; - - // don't play the sounds if the player just changed teams - if( ps->persistant[ PERS_TEAM ] != ops->persistant[ PERS_TEAM ] ) + // don't play the sounds if the player just spawned + if( ps->persistant[ PERS_SPECSTATE ] != ops->persistant[ PERS_SPECSTATE ] ) return; // health changes of more than -1 should make pain sounds @@ -257,14 +255,6 @@ void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) if( ps->stats[ STAT_HEALTH ] > 0 ) CG_PainEvent( &cg.predictedPlayerEntity, ps->stats[ STAT_HEALTH ] ); } - - - // if we are going into the intermission, don't start any voices - if( cg.intermissionStarted ) - return; - - // reward sounds - reward = qfalse; } @@ -301,7 +291,7 @@ void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ) } if( cg.snap->ps.pm_type != PM_INTERMISSION && - ps->persistant[ PERS_TEAM ] != TEAM_SPECTATOR ) + ps->persistant[ PERS_SPECSTATE ] == SPECTATOR_NOT ) CG_CheckLocalSounds( ps, ops ); // run events @@ -313,5 +303,11 @@ void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ) cg.duckChange = ps->viewheight - ops->viewheight; cg.duckTime = cg.time; } + + // changed team + if( ps->stats[ STAT_TEAM ] != ops->stats[ STAT_TEAM ] ) + { + cg.lastHealthCross = 0; + cg.chargeMeterAlpha = 0.0f; + } } - diff --git a/src/cgame/cg_predict.c b/src/cgame/cg_predict.c index 03442ed..a791303 100644 --- a/src/cgame/cg_predict.c +++ b/src/cgame/cg_predict.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -26,7 +27,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // ahead the client's movement. // It also handles local physics interaction, like fragments bouncing off walls - #include "cg_local.h" static pmove_t cg_pmove; @@ -138,7 +138,7 @@ static void CG_ClipMoveToEntities ( const vec3_t start, const vec3_t mins, bmaxs[ 2 ] = zu; if( i == cg_numSolidEntities ) - BG_FindBBoxForClass( ( ent->misc >> 8 ) & 0xFF, bmins, bmaxs, NULL, NULL, NULL ); + BG_ClassBoundingBox( ( ent->misc >> 8 ) & 0xFF, bmins, bmaxs, NULL, NULL, NULL ); cmodel = trap_CM_TempBoxModel( bmins, bmaxs ); VectorCopy( vec3_origin, angles ); @@ -273,7 +273,7 @@ int CG_PointContents( const vec3_t point, int passEntityNum ) if( !cmodel ) continue; - contents |= trap_CM_TransformedPointContents( point, cmodel, ent->origin, ent->angles ); + contents |= trap_CM_TransformedPointContents( point, cmodel, cent->lerpOrigin, cent->lerpAngles ); } return contents; @@ -476,13 +476,13 @@ static int CG_IsUnacceptableError( playerState_t *ps, playerState_t *pps ) if( fabs( AngleDelta( ps->viewangles[ 0 ], pps->viewangles[ 0 ] ) ) > 1.0f || fabs( AngleDelta( ps->viewangles[ 1 ], pps->viewangles[ 1 ] ) ) > 1.0f || - fabs( AngleDelta( ps->viewangles[ 2 ], pps->viewangles[ 2 ] ) ) > 1.0f ) + fabs( AngleDelta( ps->viewangles[ 2 ], pps->viewangles[ 2 ] ) ) > 1.0f ) { return 12; } if( pps->viewheight != ps->viewheight ) - return 13; + return 13; if( pps->damageEvent != ps->damageEvent || pps->damageYaw != ps->damageYaw || @@ -504,13 +504,6 @@ static int CG_IsUnacceptableError( playerState_t *ps, playerState_t *pps ) return 16; } - for( i = 0; i < MAX_WEAPONS; i++ ) - { - // GH FIXME - if( pps->ammo != ps->ammo || pps->clips != ps->clips ) - return 18; - } - if( pps->generic1 != ps->generic1 || pps->loopSound != ps->loopSound ) { @@ -551,7 +544,6 @@ void CG_PredictPlayerState( void ) { int cmdNum, current, i; playerState_t oldPlayerState; - qboolean moved; usercmd_t oldestCmd; usercmd_t latestCmd; int stateIndex = 0, predictCmd = 0; @@ -590,12 +582,12 @@ void CG_PredictPlayerState( void ) cg_pmove.debugLevel = cg_debugMove.integer; if( cg_pmove.ps->pm_type == PM_DEAD ) - cg_pmove.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; + cg_pmove.tracemask = MASK_DEADSOLID; else cg_pmove.tracemask = MASK_PLAYERSOLID; - if( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) - cg_pmove.tracemask &= ~CONTENTS_BODY; // spectators can fly through bodies + if( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) + cg_pmove.tracemask = MASK_DEADSOLID; // spectators can fly through bodies cg_pmove.noFootsteps = 0; @@ -638,9 +630,15 @@ void CG_PredictPlayerState( void ) } if( pmove_msec.integer < 8 ) + { trap_Cvar_Set( "pmove_msec", "8" ); + trap_Cvar_Update(&pmove_msec); + } else if( pmove_msec.integer > 33 ) + { trap_Cvar_Set( "pmove_msec", "33" ); + trap_Cvar_Update(&pmove_msec); + } cg_pmove.pmove_fixed = pmove_fixed.integer;// | cg_pmove_fixed.integer; cg_pmove.pmove_msec = pmove_msec.integer; @@ -700,22 +698,22 @@ void CG_PredictPlayerState( void ) // make sure the state differences are acceptable errorcode = CG_IsUnacceptableError( &cg.predictedPlayerState, &cg.savedPmoveStates[ i ] ); - + if( errorcode ) { if( cg_showmiss.integer ) CG_Printf("errorcode %d at %d\n", errorcode, cg.time); break; } - + // this one is almost exact, so we'll copy it in as the starting point *cg_pmove.ps = cg.savedPmoveStates[ i ]; // advance the head cg.stateHead = ( i + 1 ) % NUM_SAVED_STATES; - + // set the next command to predict predictCmd = cg.lastPredictedCommand + 1; - + // a saved state matched, so flag it error = qfalse; break; @@ -737,9 +735,6 @@ void CG_PredictPlayerState( void ) stateIndex = cg.stateHead; } - // run cmds - moved = qfalse; - for( cmdNum = current - CMD_BACKUP + 1; cmdNum <= current; cmdNum++ ) { // get the command @@ -857,8 +852,6 @@ void CG_PredictPlayerState( void ) stateIndex = ( stateIndex + 1 ) % NUM_SAVED_STATES; } - moved = qtrue; - // add push trigger movement effects CG_TouchTriggerPrediction( ); diff --git a/src/cgame/cg_ptr.c b/src/cgame/cg_ptr.c deleted file mode 100644 index 1881087..0000000 --- a/src/cgame/cg_ptr.c +++ /dev/null @@ -1,81 +0,0 @@ -/* -=========================================================================== -Copyright (C) 2000-2006 Tim Angus - -This file is part of Tremulous. - -Tremulous is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Tremulous is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -// cg_ptr.c -- post timeout restoration handling - - -#include "cg_local.h" - -#define PTRC_FILE "ptrc.cfg" - -/* -=============== -CG_ReadPTRCode - -Read a PTR code from disk -=============== -*/ -int CG_ReadPTRCode( void ) -{ - int len; - char text[ 16 ]; - fileHandle_t f; - - // load the file - len = trap_FS_FOpenFile( PTRC_FILE, &f, FS_READ ); - if( len <= 0 ) - return 0; - - // should never happen - malformed write - if( len >= sizeof( text ) - 1 ) - return 0; - - trap_FS_Read( text, len, f ); - text[ len ] = 0; - trap_FS_FCloseFile( f ); - - return atoi( text ); -} - -/* -=============== -CG_WritePTRCode - -Write a PTR code to disk -=============== -*/ -void CG_WritePTRCode( int code ) -{ - char text[ 16 ]; - fileHandle_t f; - - Com_sprintf( text, 16, "%d", code ); - - // open file - if( trap_FS_FOpenFile( PTRC_FILE, &f, FS_WRITE ) < 0 ) - return; - - // write the code - trap_FS_Write( text, strlen( text ), f ); - - trap_FS_FCloseFile( f ); -} diff --git a/src/cgame/cg_public.h b/src/cgame/cg_public.h index 543a222..eef8d40 100644 --- a/src/cgame/cg_public.h +++ b/src/cgame/cg_public.h @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,51 +17,47 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ +#ifndef CGAME_PUBLIC_H +#define CGAME_PUBLIC_H + +#include "qcommon/q_shared.h" -#define CMD_BACKUP 64 -#define CMD_MASK (CMD_BACKUP - 1) +#define CMD_BACKUP 64 +#define CMD_MASK (CMD_BACKUP - 1) // allow a lot of command backups for very fast systems // multiple commands may be combined into a single packet, so this // needs to be larger than PACKET_BACKUP - -#define MAX_ENTITIES_IN_SNAPSHOT 256 +#define MAX_ENTITIES_IN_SNAPSHOT 256 // snapshots are a view of the server at a given time // Snapshots are generated at regular time intervals by the server, // but they may not be sent if a client's rate level is exceeded, or // they may be dropped by the network. -typedef struct -{ - int snapFlags; // SNAPFLAG_RATE_DELAYED, etc - int ping; +typedef struct { + int snapFlags; // SNAPFLAG_RATE_DELAYED, etc + int ping; - int serverTime; // server time the message is valid for (in msec) + int serverTime; // server time the message is valid for (in msec) - byte areamask[ MAX_MAP_AREA_BYTES ]; // portalarea visibility bits + byte areamask[MAX_MAP_AREA_BYTES]; // portalarea visibility bits - playerState_t ps; // complete information about the current player at this time + playerState_t ps; // complete information about the current player at this time - int numEntities; // all of the entities that need to be presented - entityState_t entities[ MAX_ENTITIES_IN_SNAPSHOT ]; // at the time of this snapshot + int numEntities; // all of the entities that need to be presented + entityState_t entities[MAX_ENTITIES_IN_SNAPSHOT]; // at the time of this snapshot - int numServerCommands; // text based server commands to execute when this - int serverCommandSequence; // snapshot becomes current + int numServerCommands; // text based server commands to execute when this + int serverCommandSequence; // snapshot becomes current } snapshot_t; -enum -{ - CGAME_EVENT_NONE, - CGAME_EVENT_TEAMMENU, - CGAME_EVENT_SCOREBOARD, - CGAME_EVENT_EDITHUD -}; +enum { CGAME_EVENT_NONE, CGAME_EVENT_TEAMMENU, CGAME_EVENT_SCOREBOARD, CGAME_EVENT_EDITHUD }; /* ================================================================== @@ -70,147 +67,146 @@ functions imported from the main executable ================================================================== */ -#define CGAME_IMPORT_API_VERSION 4 - -typedef enum -{ - CG_PRINT, - CG_ERROR, - CG_MILLISECONDS, - CG_CVAR_REGISTER, - CG_CVAR_UPDATE, - CG_CVAR_SET, - CG_CVAR_VARIABLESTRINGBUFFER, - CG_ARGC, - CG_ARGV, - CG_ARGS, - CG_FS_FOPENFILE, - CG_FS_READ, - CG_FS_WRITE, - CG_FS_FCLOSEFILE, - CG_SENDCONSOLECOMMAND, - CG_ADDCOMMAND, - CG_SENDCLIENTCOMMAND, - CG_UPDATESCREEN, - CG_CM_LOADMAP, - CG_CM_NUMINLINEMODELS, - CG_CM_INLINEMODEL, - CG_CM_LOADMODEL, - CG_CM_TEMPBOXMODEL, - CG_CM_POINTCONTENTS, - CG_CM_TRANSFORMEDPOINTCONTENTS, - CG_CM_BOXTRACE, - CG_CM_TRANSFORMEDBOXTRACE, - CG_CM_MARKFRAGMENTS, - CG_S_STARTSOUND, - CG_S_STARTLOCALSOUND, - CG_S_CLEARLOOPINGSOUNDS, - CG_S_ADDLOOPINGSOUND, - CG_S_UPDATEENTITYPOSITION, - CG_S_RESPATIALIZE, - CG_S_REGISTERSOUND, - CG_S_STARTBACKGROUNDTRACK, - CG_R_LOADWORLDMAP, - CG_R_REGISTERMODEL, - CG_R_REGISTERSKIN, - CG_R_REGISTERSHADER, - CG_R_CLEARSCENE, - CG_R_ADDREFENTITYTOSCENE, - CG_R_ADDPOLYTOSCENE, - CG_R_ADDLIGHTTOSCENE, - CG_R_RENDERSCENE, - CG_R_SETCOLOR, +#define CGAME_IMPORT_API_VERSION 4 + +typedef enum { + CG_PRINT, + CG_ERROR, + CG_MILLISECONDS, + CG_CVAR_REGISTER, + CG_CVAR_UPDATE, + CG_CVAR_SET, + CG_CVAR_VARIABLESTRINGBUFFER, + CG_ARGC, + CG_ARGV, + CG_ARGS, + CG_FS_FOPENFILE, + CG_FS_READ, + CG_FS_WRITE, + CG_FS_FCLOSEFILE, + CG_SENDCONSOLECOMMAND, + CG_ADDCOMMAND, + CG_SENDCLIENTCOMMAND, + CG_UPDATESCREEN, + CG_CM_LOADMAP, + CG_CM_NUMINLINEMODELS, + CG_CM_INLINEMODEL, + CG_CM_LOADMODEL, + CG_CM_TEMPBOXMODEL, + CG_CM_POINTCONTENTS, + CG_CM_TRANSFORMEDPOINTCONTENTS, + CG_CM_BOXTRACE, + CG_CM_TRANSFORMEDBOXTRACE, + CG_CM_MARKFRAGMENTS, + CG_S_STARTSOUND, + CG_S_STARTLOCALSOUND, + CG_S_CLEARLOOPINGSOUNDS, + CG_S_ADDLOOPINGSOUND, + CG_S_UPDATEENTITYPOSITION, + CG_S_RESPATIALIZE, + CG_S_REGISTERSOUND, + CG_S_STARTBACKGROUNDTRACK, + CG_R_LOADWORLDMAP, + CG_R_REGISTERMODEL, + CG_R_REGISTERSKIN, + CG_R_REGISTERSHADER, + CG_R_CLEARSCENE, + CG_R_ADDREFENTITYTOSCENE, + CG_R_ADDPOLYTOSCENE, + CG_R_ADDLIGHTTOSCENE, + CG_R_RENDERSCENE, + CG_R_SETCOLOR, #ifndef MODULE_INTERFACE_11 - CG_R_SETCLIPREGION, + CG_R_SETCLIPREGION, #endif - CG_R_DRAWSTRETCHPIC, - CG_R_MODELBOUNDS, - CG_R_LERPTAG, - CG_GETGLCONFIG, - CG_GETGAMESTATE, - CG_GETCURRENTSNAPSHOTNUMBER, - CG_GETSNAPSHOT, - CG_GETSERVERCOMMAND, - CG_GETCURRENTCMDNUMBER, - CG_GETUSERCMD, - CG_SETUSERCMDVALUE, - CG_R_REGISTERSHADERNOMIP, - CG_MEMORY_REMAINING, - CG_R_REGISTERFONT, - CG_KEY_ISDOWN, - CG_KEY_GETCATCHER, - CG_KEY_SETCATCHER, - CG_KEY_GETKEY, + CG_R_DRAWSTRETCHPIC, + CG_R_MODELBOUNDS, + CG_R_LERPTAG, + CG_GETGLCONFIG, + CG_GETGAMESTATE, + CG_GETCURRENTSNAPSHOTNUMBER, + CG_GETSNAPSHOT, + CG_GETSERVERCOMMAND, + CG_GETCURRENTCMDNUMBER, + CG_GETUSERCMD, + CG_SETUSERCMDVALUE, + CG_R_REGISTERSHADERNOMIP, + CG_MEMORY_REMAINING, + CG_R_REGISTERFONT, + CG_KEY_ISDOWN, + CG_KEY_GETCATCHER, + CG_KEY_SETCATCHER, + CG_KEY_GETKEY, #ifdef MODULE_INTERFACE_11 - CG_PARSE_ADD_GLOBAL_DEFINE, - CG_PARSE_LOAD_SOURCE, - CG_PARSE_FREE_SOURCE, - CG_PARSE_READ_TOKEN, - CG_PARSE_SOURCE_FILE_AND_LINE, + CG_PARSE_ADD_GLOBAL_DEFINE, + CG_PARSE_LOAD_SOURCE, + CG_PARSE_FREE_SOURCE, + CG_PARSE_READ_TOKEN, + CG_PARSE_SOURCE_FILE_AND_LINE, #endif - CG_S_STOPBACKGROUNDTRACK, - CG_REAL_TIME, - CG_SNAPVECTOR, - CG_REMOVECOMMAND, - CG_R_LIGHTFORPOINT, - CG_CIN_PLAYCINEMATIC, - CG_CIN_STOPCINEMATIC, - CG_CIN_RUNCINEMATIC, - CG_CIN_DRAWCINEMATIC, - CG_CIN_SETEXTENTS, - CG_R_REMAP_SHADER, - CG_S_ADDREALLOOPINGSOUND, - CG_S_STOPLOOPINGSOUND, - - CG_CM_TEMPCAPSULEMODEL, - CG_CM_CAPSULETRACE, - CG_CM_TRANSFORMEDCAPSULETRACE, - CG_R_ADDADDITIVELIGHTTOSCENE, - CG_GET_ENTITY_TOKEN, - CG_R_ADDPOLYSTOSCENE, - CG_R_INPVS, - CG_FS_SEEK, - CG_FS_GETFILELIST, - CG_LITERAL_ARGS, - CG_CM_BISPHERETRACE, - CG_CM_TRANSFORMEDBISPHERETRACE, - CG_GETDEMOSTATE, - CG_GETDEMOPOS, - CG_GETDEMONAME, - - CG_KEY_KEYNUMTOSTRINGBUF, - CG_KEY_GETBINDINGBUF, - CG_KEY_SETBINDING, + CG_S_STOPBACKGROUNDTRACK, + CG_REAL_TIME, + CG_SNAPVECTOR, + CG_REMOVECOMMAND, + CG_R_LIGHTFORPOINT, + CG_CIN_PLAYCINEMATIC, + CG_CIN_STOPCINEMATIC, + CG_CIN_RUNCINEMATIC, + CG_CIN_DRAWCINEMATIC, + CG_CIN_SETEXTENTS, + CG_R_REMAP_SHADER, + CG_S_ADDREALLOOPINGSOUND, + CG_S_STOPLOOPINGSOUND, + + CG_CM_TEMPCAPSULEMODEL, + CG_CM_CAPSULETRACE, + CG_CM_TRANSFORMEDCAPSULETRACE, + CG_R_ADDADDITIVELIGHTTOSCENE, + CG_GET_ENTITY_TOKEN, + CG_R_ADDPOLYSTOSCENE, + CG_R_INPVS, + CG_FS_SEEK, + CG_FS_GETFILELIST, + CG_LITERAL_ARGS, + CG_CM_BISPHERETRACE, + CG_CM_TRANSFORMEDBISPHERETRACE, + CG_GETDEMOSTATE, + CG_GETDEMOPOS, + CG_GETDEMONAME, + + CG_KEY_KEYNUMTOSTRINGBUF, + CG_KEY_GETBINDINGBUF, + CG_KEY_SETBINDING, #ifndef MODULE_INTERFACE_11 - CG_PARSE_ADD_GLOBAL_DEFINE, - CG_PARSE_LOAD_SOURCE, - CG_PARSE_FREE_SOURCE, - CG_PARSE_READ_TOKEN, - CG_PARSE_SOURCE_FILE_AND_LINE, + CG_PARSE_ADD_GLOBAL_DEFINE, + CG_PARSE_LOAD_SOURCE, + CG_PARSE_FREE_SOURCE, + CG_PARSE_READ_TOKEN, + CG_PARSE_SOURCE_FILE_AND_LINE, - CG_KEY_SETOVERSTRIKEMODE, - CG_KEY_GETOVERSTRIKEMODE, + CG_KEY_SETOVERSTRIKEMODE, + CG_KEY_GETOVERSTRIKEMODE, - CG_S_SOUNDDURATION, + CG_S_SOUNDDURATION, + CG_FIELD_COMPLETELIST, #endif - CG_MEMSET = 200, - CG_MEMCPY, - CG_STRNCPY, - CG_SIN, - CG_COS, - CG_ATAN2, - CG_SQRT, - CG_FLOOR, - CG_CEIL, - - CG_TESTPRINTINT, - CG_TESTPRINTFLOAT, - CG_ACOS + CG_MEMSET = 200, + CG_MEMCPY, + CG_STRNCPY, + CG_SIN, + CG_COS, + CG_ATAN2, + CG_SQRT, + CG_FLOOR, + CG_CEIL, + + CG_TESTPRINTINT, + CG_TESTPRINTFLOAT, + CG_ACOS } cgameImport_t; - /* ================================================================== @@ -219,55 +215,62 @@ functions exported to the main executable ================================================================== */ -typedef enum -{ - CG_INIT, - // void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) - // called when the level loads or when the renderer is restarted - // all media should be registered at this time - // cgame will display loading status by calling SCR_Update, which - // will call CG_DrawInformation during the loading process - // reliableCommandSequence will be 0 on fresh loads, but higher for - // demos, tourney restarts, or vid_restarts - - CG_SHUTDOWN, - // void (*CG_Shutdown)( void ); - // oportunity to flush and close any open files - - CG_CONSOLE_COMMAND, - // qboolean (*CG_ConsoleCommand)( void ); - // a console command has been issued locally that is not recognized by the - // main game system. - // use Cmd_Argc() / Cmd_Argv() to read the command, return qfalse if the - // command is not known to the game - - CG_DRAW_ACTIVE_FRAME, - // void (*CG_DrawActiveFrame)( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); - // Generates and draws a game scene and status information at the given time. - // If demoPlayback is set, local movement prediction will not be enabled - - CG_CROSSHAIR_PLAYER, - // int (*CG_CrosshairPlayer)( void ); - - CG_LAST_ATTACKER, - // int (*CG_LastAttacker)( void ); - - CG_KEY_EVENT, - // void (*CG_KeyEvent)( int key, qboolean down ); - - CG_MOUSE_EVENT, - // void (*CG_MouseEvent)( int dx, int dy ); - CG_EVENT_HANDLING, - // void (*CG_EventHandling)(int type); - - CG_CONSOLE_TEXT, - // void (*CG_ConsoleText)( void ); - // pass text that has been printed to the console to cgame - // use Cmd_Argc() / Cmd_Argv() to read it - - CG_VOIP_STRING - // char *(*CG_VoIPString)( void ); - // returns a string of comma-delimited clientnums based on cl_voipSendTarget +typedef enum { + CG_INIT, + // void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) + // called when the level loads or when the renderer is restarted + // all media should be registered at this time + // cgame will display loading status by calling SCR_Update, which + // will call CG_DrawInformation during the loading process + // reliableCommandSequence will be 0 on fresh loads, but higher for + // demos, tourney restarts, or vid_restarts + + CG_SHUTDOWN, + // void (*CG_Shutdown)( void ); + // oportunity to flush and close any open files + + CG_CONSOLE_COMMAND, + // qboolean (*CG_ConsoleCommand)( void ); + // a console command has been issued locally that is not recognized by the + // main game system. + // use Cmd_Argc() / Cmd_Argv() to read the command, return qfalse if the + // command is not known to the game + + CG_DRAW_ACTIVE_FRAME, + // void (*CG_DrawActiveFrame)( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); + // Generates and draws a game scene and status information at the given time. + // If demoPlayback is set, local movement prediction will not be enabled + + CG_CROSSHAIR_PLAYER, + // int (*CG_CrosshairPlayer)( void ); + + CG_LAST_ATTACKER, + // int (*CG_LastAttacker)( void ); + + CG_KEY_EVENT, + // void (*CG_KeyEvent)( int key, qboolean down ); + + CG_MOUSE_EVENT, + // void (*CG_MouseEvent)( int dx, int dy ); + CG_EVENT_HANDLING, + // void (*CG_EventHandling)(int type); + + CG_CONSOLE_TEXT, + // void (*CG_ConsoleText)( void ); + // pass text that has been printed to the console to cgame + // use Cmd_Argc() / Cmd_Argv() to read it + + CG_VOIP_STRING, + // char *(*CG_VoIPString)( void ); + // returns a string of comma-delimited clientnums based on cl_voipSendTarget + + CG_CONSOLE_COMPLETARGUMENT + // qboolean (*CG_Console_CompleteArgument)( int argNum ) + // Requests CGAME to try to complete the command line + // argument. argNum indicates which argument we're completing. CGAME + // uses trap_Argv and trap_Argc to read the command line + // contents. Returns true if a completion function is found in + // CGAME, otherwise client tries another completion method. } cgameExport_t; -//---------------------------------------------- +#endif diff --git a/src/cgame/cg_rangemarker.c b/src/cgame/cg_rangemarker.c new file mode 100644 index 0000000..2810b90 --- /dev/null +++ b/src/cgame/cg_rangemarker.c @@ -0,0 +1,399 @@ +#include "cg_local.h" + +const vec3_t cg_shaderColors[ SHC_NUM_SHADER_COLORS ] = +{ + { 0.0f, 0.0f, 0.75f }, // dark blue + { 0.3f, 0.35f, 0.625f }, // light blue + { 0.0f, 0.625f, 0.563f }, // green-cyan + { 0.313f, 0.0f, 0.625f }, // violet + { 0.625f, 0.625f, 0.0f }, // yellow + { 0.875f, 0.313f, 0.0f }, // orange + { 0.375f, 0.625f, 0.375f }, // light green + { 0.0f, 0.438f, 0.0f }, // dark green + { 1.0f, 0.0f, 0.0f }, // red + { 0.625f, 0.375f, 0.4f }, // pink + { 0.313f, 0.313f, 0.313f } // grey +}; + +/* +================ +CG_RangeMarkerPreferences +================ +*/ +qboolean CG_GetRangeMarkerPreferences( qboolean *drawSurface, qboolean *drawIntersection, + qboolean *drawFrontline, float *surfaceOpacity, + float *lineOpacity, float *lineThickness ) +{ + *drawSurface = !!cg_rangeMarkerDrawSurface.integer; + *drawIntersection = !!cg_rangeMarkerDrawIntersection.integer; + *drawFrontline = !!cg_rangeMarkerDrawFrontline.integer; + *surfaceOpacity = cg_rangeMarkerSurfaceOpacity.value; + *lineOpacity = cg_rangeMarkerLineOpacity.value; + *lineThickness = cg_rangeMarkerLineThickness.value; + + if( ( *drawSurface && *surfaceOpacity > 0.0f ) || + ( ( *drawIntersection || *drawFrontline ) && *lineOpacity > 0.0f && + *lineThickness > 0.0f && cg_binaryShaderScreenScale.value > 0.0f ) ) + { + if( *surfaceOpacity > 1.0f ) + *surfaceOpacity = 1.0f; + if( *lineOpacity > 1.0f ) + *lineOpacity = 1.0f; + return qtrue; + } + + return qfalse; +} + +/* +================ +CG_UpdateBuildableRangeMarkerMask +================ +*/ +void CG_UpdateBuildableRangeMarkerMask( void ) +{ + static int mc = 0; + + if( cg_rangeMarkerBuildableTypes.modificationCount != mc ) + { + int brmMask; + char buffer[ MAX_CVAR_VALUE_STRING ]; + char *p, *q; + buildable_t buildable; + + brmMask = 0; + + if( !cg_rangeMarkerBuildableTypes.string[ 0 ] ) + goto empty; + + Q_strncpyz( buffer, cg_rangeMarkerBuildableTypes.string, sizeof( buffer ) ); + p = &buffer[ 0 ]; + + for(;;) + { + q = strchr( p, ',' ); + if( q ) + *q = '\0'; + + while( *p == ' ' ) + ++p; + + buildable = BG_BuildableByName( p )->number; + + if( buildable != BA_NONE ) + { + brmMask |= 1 << buildable; + } + else if( !Q_stricmp( p, "all" ) ) + { + brmMask |= ( 1 << BA_A_OVERMIND ) | ( 1 << BA_A_SPAWN ) | + ( 1 << BA_A_ACIDTUBE ) | ( 1 << BA_A_TRAPPER ) | ( 1 << BA_A_HIVE ) | + ( 1 << BA_H_REACTOR ) | ( 1 << BA_H_REPEATER ) | ( 1 << BA_H_DCC ) | + ( 1 << BA_H_MGTURRET ) | ( 1 << BA_H_TESLAGEN ); + } + else + { + char *pp; + int only; + + if( !Q_stricmpn( p, "alien", 5 ) ) + { + pp = p + 5; + only = ( 1 << BA_A_OVERMIND ) | ( 1 << BA_A_SPAWN ) | + ( 1 << BA_A_ACIDTUBE ) | ( 1 << BA_A_TRAPPER ) | ( 1 << BA_A_HIVE ); + } + else if( !Q_stricmpn( p, "human", 5 ) ) + { + pp = p + 5; + only = ( 1 << BA_H_REACTOR ) | ( 1 << BA_H_REPEATER ) | ( 1 << BA_H_DCC ) | + ( 1 << BA_H_MGTURRET ) | ( 1 << BA_H_TESLAGEN ); + } + else + { + pp = p; + only = ~0; + } + + if( pp != p && !*pp ) + { + brmMask |= only; + } + else if( !Q_stricmp( pp, "support" ) ) + { + brmMask |= only & ( ( 1 << BA_A_OVERMIND ) | ( 1 << BA_A_SPAWN ) | + ( 1 << BA_H_REACTOR ) | ( 1 << BA_H_REPEATER ) | ( 1 << BA_H_DCC ) ); + } + else if( !Q_stricmp( pp, "offensive" ) ) + { + brmMask |= only & ( ( 1 << BA_A_ACIDTUBE ) | ( 1 << BA_A_TRAPPER ) | ( 1 << BA_A_HIVE ) | + ( 1 << BA_H_MGTURRET ) | ( 1 << BA_H_TESLAGEN ) ); + } + else + Com_Printf( S_COLOR_YELLOW "WARNING: unknown buildable or group: %s\n", p ); + } + + if( q ) + p = q + 1; + else + break; + } + + empty: + trap_Cvar_Set( "cg_buildableRangeMarkerMask", va( "%i", brmMask ) ); + + mc = cg_rangeMarkerBuildableTypes.modificationCount; + } +} + +// cg_drawtools.c +// +/* +================ +CG_DrawSphere +================ +*/ +void CG_DrawSphere( const vec3_t center, float radius, int customShader, const float *shaderRGBA ) +{ + refEntity_t re; + memset( &re, 0, sizeof( re ) ); + + re.reType = RT_MODEL; + re.hModel = cgs.media.sphereModel; + re.customShader = customShader; + re.renderfx = RF_NOSHADOW; + if( shaderRGBA != NULL ) + { + int i; + for( i = 0; i < 4; ++i ) + re.shaderRGBA[ i ] = 255 * shaderRGBA[ i ]; + } + + VectorCopy( center, re.origin ); + + radius *= 0.01f; + VectorSet( re.axis[ 0 ], radius, 0, 0 ); + VectorSet( re.axis[ 1 ], 0, radius, 0 ); + VectorSet( re.axis[ 2 ], 0, 0, radius ); + re.nonNormalizedAxes = qtrue; + + trap_R_AddRefEntityToScene( &re ); +} + +/* +================ +CG_DrawSphericalCone +================ +*/ +void CG_DrawSphericalCone( const vec3_t tip, const vec3_t rotation, float radius, + qboolean a240, int customShader, const float *shaderRGBA ) +{ + refEntity_t re; + memset( &re, 0, sizeof( re ) ); + + re.reType = RT_MODEL; + re.hModel = a240 ? cgs.media.sphericalCone240Model : cgs.media.sphericalCone64Model; + re.customShader = customShader; + re.renderfx = RF_NOSHADOW; + if( shaderRGBA != NULL ) + { + int i; + for( i = 0; i < 4; ++i ) + re.shaderRGBA[ i ] = 255 * shaderRGBA[ i ]; + } + + VectorCopy( tip, re.origin ); + + radius *= 0.01f; + AnglesToAxis( rotation, re.axis ); + VectorScale( re.axis[ 0 ], radius, re.axis[ 0 ] ); + VectorScale( re.axis[ 1 ], radius, re.axis[ 1 ] ); + VectorScale( re.axis[ 2 ], radius, re.axis[ 2 ] ); + re.nonNormalizedAxes = qtrue; + + trap_R_AddRefEntityToScene( &re ); +} + + +/* +================ +CG_DrawRangeMarker +================ +*/ +void CG_DrawRangeMarker( rangeMarkerType_t rmType, const vec3_t origin, const float *angles, float range, + qboolean drawSurface, qboolean drawIntersection, qboolean drawFrontline, + const vec3_t rgb, float surfaceOpacity, float lineOpacity, float lineThickness ) +{ + if( drawSurface ) + { + qhandle_t pcsh; + vec4_t rgba; + + pcsh = cgs.media.plainColorShader; + VectorCopy( rgb, rgba ); + rgba[ 3 ] = surfaceOpacity; + + switch( rmType ) + { + case RMT_SPHERE: + CG_DrawSphere( origin, range, pcsh, rgba ); + break; + case RMT_SPHERICAL_CONE_64: + CG_DrawSphericalCone( origin, angles, range, qfalse, pcsh, rgba ); + break; + case RMT_SPHERICAL_CONE_240: + CG_DrawSphericalCone( origin, angles, range, qtrue, pcsh, rgba ); + break; + } + } + + if( drawIntersection || drawFrontline ) + { + const cgMediaBinaryShader_t *mbsh; + cgBinaryShaderSetting_t *bshs; + int i; + + if( cg.numBinaryShadersUsed >= NUM_BINARY_SHADERS ) + return; + mbsh = &cgs.media.binaryShaders[ cg.numBinaryShadersUsed ]; + + if( rmType == RMT_SPHERE ) + { + if( range > lineThickness / 2 ) + { + if( drawIntersection ) + CG_DrawSphere( origin, range - lineThickness / 2, mbsh->b1, NULL ); + CG_DrawSphere( origin, range - lineThickness / 2, mbsh->f2, NULL ); + } + + if( drawIntersection ) + CG_DrawSphere( origin, range + lineThickness / 2, mbsh->b2, NULL ); + CG_DrawSphere( origin, range + lineThickness / 2, mbsh->f1, NULL ); + } + else if( rmType == RMT_SPHERICAL_CONE_64 || rmType == RMT_SPHERICAL_CONE_240 ) + { + qboolean a240; + float f, r; + vec3_t forward, tip; + + a240 = ( rmType == RMT_SPHERICAL_CONE_240 ); + f = lineThickness * ( a240 ? 0.26f : 0.8f ); + r = f + lineThickness * ( a240 ? 0.23f : 0.43f ); + AngleVectors( angles, forward, NULL, NULL ); + + if( range > r ) + { + VectorMA( origin, f, forward, tip ); + if( drawIntersection ) + CG_DrawSphericalCone( tip, angles, range - r, a240, mbsh->b1, NULL ); + CG_DrawSphericalCone( tip, angles, range - r, a240, mbsh->f2, NULL ); + } + + VectorMA( origin, -f, forward, tip ); + if( drawIntersection ) + CG_DrawSphericalCone( tip, angles, range + r, a240, mbsh->b2, NULL ); + CG_DrawSphericalCone( tip, angles, range + r, a240, mbsh->f1, NULL ); + } + + bshs = &cg.binaryShaderSettings[ cg.numBinaryShadersUsed ]; + + for( i = 0; i < 3; ++i ) + bshs->color[ i ] = 255 * lineOpacity * rgb[ i ]; + bshs->drawIntersection = drawIntersection; + bshs->drawFrontline = drawFrontline; + + ++cg.numBinaryShadersUsed; + } +} + +// cg_buildable.c + +/* +================ +CG_BuildableRangeMarkerProperties +================ +*/ +qboolean CG_GetBuildableRangeMarkerProperties( buildable_t bType, rangeMarkerType_t *rmType, float *range, vec3_t rgb ) +{ + shaderColorEnum_t shc; + + switch( bType ) + { + case BA_A_SPAWN: *range = CREEP_BASESIZE; shc = SHC_LIGHT_GREEN; break; + case BA_A_OVERMIND: *range = CREEP_BASESIZE; shc = SHC_DARK_GREEN; break; + case BA_A_ACIDTUBE: *range = ACIDTUBE_RANGE; shc = SHC_RED; break; + case BA_A_TRAPPER: *range = TRAPPER_RANGE; shc = SHC_PINK; break; + case BA_A_HIVE: *range = HIVE_SENSE_RANGE; shc = SHC_YELLOW; break; + case BA_H_MGTURRET: *range = MGTURRET_RANGE; shc = SHC_ORANGE; break; + case BA_H_TESLAGEN: *range = TESLAGEN_RANGE; shc = SHC_VIOLET; break; + case BA_H_DCC: *range = DC_RANGE; shc = SHC_GREEN_CYAN; break; + case BA_H_REACTOR: *range = REACTOR_BASESIZE; shc = SHC_DARK_BLUE; break; + case BA_H_REPEATER: *range = REPEATER_BASESIZE; shc = SHC_LIGHT_BLUE; break; + default: return qfalse; + } + + if( bType == BA_A_TRAPPER ) + *rmType = RMT_SPHERICAL_CONE_64; + else if( bType == BA_H_MGTURRET ) + *rmType = RMT_SPHERICAL_CONE_240; + else + *rmType = RMT_SPHERE; + + VectorCopy( cg_shaderColors[ shc ], rgb ); + + return qtrue; +} + +/* +================ +CG_GhostBuildableRangeMarker +================ +*/ +void CG_GhostBuildableRangeMarker( buildable_t buildable, const vec3_t origin, const vec3_t normal ) +{ + qboolean drawS, drawI, drawF; + float so, lo, th; + rangeMarkerType_t rmType; + float range; + vec3_t rgb; + + if( CG_GetRangeMarkerPreferences( &drawS, &drawI, &drawF, &so, &lo, &th ) && + CG_GetBuildableRangeMarkerProperties( buildable, &rmType, &range, rgb ) ) + { + vec3_t localOrigin, angles; + + if( buildable == BA_A_HIVE || buildable == BA_H_TESLAGEN ) + VectorMA( origin, BG_BuildableConfig( buildable )->maxs[ 2 ], normal, localOrigin ); + else + VectorCopy( origin, localOrigin ); + + if( rmType != RMT_SPHERE ) + vectoangles( normal, angles ); + + CG_DrawRangeMarker( rmType, localOrigin, ( rmType != RMT_SPHERE ? angles : NULL ), + range, drawS, drawI, drawF, rgb, so, lo, th ); + } +} + +// cg_ents.c + +/* +================ +CG_RangeMarker +================ +*/ +void CG_RangeMarker( centity_t *cent ) +{ + qboolean drawS, drawI, drawF; + float so, lo, th; + rangeMarkerType_t rmType; + float range; + vec3_t rgb; + + if( CG_GetRangeMarkerPreferences( &drawS, &drawI, &drawF, &so, &lo, &th ) && + CG_GetBuildableRangeMarkerProperties( cent->currentState.modelindex, &rmType, &range, rgb ) ) + { + CG_DrawRangeMarker( rmType, cent->lerpOrigin, ( rmType > 0 ? cent->lerpAngles : NULL ), + range, drawS, drawI, drawF, rgb, so, lo, th ); + } +} + diff --git a/src/cgame/cg_scanner.c b/src/cgame/cg_scanner.c index 033960e..a7df030 100644 --- a/src/cgame/cg_scanner.c +++ b/src/cgame/cg_scanner.c @@ -1,12 +1,13 @@ /* =========================================================================== -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -15,15 +16,14 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ - #include "cg_local.h" -static entityPos_t entityPositions; +static entityPos_t entityPositions; #define HUMAN_SCANNER_UPDATE_PERIOD 700 @@ -39,7 +39,7 @@ void CG_UpdateEntityPositions( void ) centity_t *cent = NULL; int i; - if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_HUMANS ) + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_HUMANS ) { if( entityPositions.lastUpdateTime + HUMAN_SCANNER_UPDATE_PERIOD > cg.time ) return; @@ -58,10 +58,11 @@ void CG_UpdateEntityPositions( void ) { cent = &cg_entities[ cg.snap->entities[ i ].number ]; - if( cent->currentState.eType == ET_BUILDABLE ) + if( cent->currentState.eType == ET_BUILDABLE && + !( cent->currentState.eFlags & EF_DEAD ) ) { - //TA: add to list of item positions (for creep) - if( cent->currentState.modelindex2 == BIT_ALIENS ) + // add to list of item positions (for creep) + if( cent->currentState.modelindex2 == TEAM_ALIENS ) { VectorCopy( cent->lerpOrigin, entityPositions.alienBuildablePos[ entityPositions.numAlienBuildables ] ); @@ -71,7 +72,7 @@ void CG_UpdateEntityPositions( void ) if( entityPositions.numAlienBuildables < MAX_GENTITIES ) entityPositions.numAlienBuildables++; } - else if( cent->currentState.modelindex2 == BIT_HUMANS ) + else if( cent->currentState.modelindex2 == TEAM_HUMANS ) { VectorCopy( cent->lerpOrigin, entityPositions.humanBuildablePos[ entityPositions.numHumanBuildables ] ); @@ -84,7 +85,7 @@ void CG_UpdateEntityPositions( void ) { int team = cent->currentState.misc & 0x00FF; - if( team == PTE_ALIENS ) + if( team == TEAM_ALIENS ) { VectorCopy( cent->lerpOrigin, entityPositions.alienClientPos[ entityPositions.numAlienClients ] ); @@ -92,7 +93,7 @@ void CG_UpdateEntityPositions( void ) if( entityPositions.numAlienClients < MAX_CLIENTS ) entityPositions.numAlienClients++; } - else if( team == PTE_HUMANS ) + else if( team == TEAM_HUMANS ) { VectorCopy( cent->lerpOrigin, entityPositions.humanClientPos[ entityPositions.numHumanClients ] ); @@ -104,8 +105,8 @@ void CG_UpdateEntityPositions( void ) } } -#define STALKWIDTH 2.0f -#define BLIPX 16.0f +#define STALKWIDTH (2.0f * cgDC.aspectScale) +#define BLIPX (16.0f * cgDC.aspectScale) #define BLIPY 8.0f #define FAR_ALPHA 0.8f #define NEAR_ALPHA 1.2f @@ -162,7 +163,7 @@ static void CG_DrawBlips( rectDef_t *rect, vec3_t origin, vec4_t colour ) trap_R_SetColor( NULL ); } -#define BLIPX2 24.0f +#define BLIPX2 (24.0f * cgDC.aspectScale) #define BLIPY2 24.0f /* @@ -183,15 +184,7 @@ static void CG_DrawDir( rectDef_t *rect, vec3_t origin, vec4_t colour ) float angle; playerState_t *ps = &cg.snap->ps; - if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) - { - if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) - VectorSet( normal, 0.0f, 0.0f, -1.0f ); - else - VectorCopy( ps->grapplePoint, normal ); - } - else - VectorSet( normal, 0.0f, 0.0f, 1.0f ); + BG_GetClientNormal( ps, normal ); AngleVectors( entityPositions.vangles, view, NULL, NULL ); @@ -363,3 +356,125 @@ void CG_Scanner( rectDef_t *rect, qhandle_t shader, vec4_t color ) CG_DrawBlips( rect, relOrigin, aIabove ); } } + +void THZ_DrawScanner( rectDef_t *rect ) +{ + vec4_t colorB = { 0.0f, 0.0f, 0.0f, 0.5f }; + vec4_t color = { 1.0f, 1.0f, 1.0f, 0.2f }; + + vec4_t aliencolor = { 1.0f, 0.0f, 0.0f, 0.8f }; + vec4_t humancolor = { 0.0f, 0.0f, 1.0f, 0.8f }; + vec4_t buildcolor = { 0.0f, 1.0f, 1.0f, 0.8f }; + vec4_t buildcolor2 = { 1.0f, 1.0f, 0.0f, 0.8f }; + + vec3_t drawOrigin = { 0.0f, 0.0f, 0.0f }; + vec3_t origin = { 0.0f, 0.0f, 0.0f }; + vec3_t relOrigin = { 0.0f, 0.0f, 0.0f }; + + static vec3_t up = { 0.0f, 0.0f, 1.0f }; + + int i; + + if( !thz_radar.integer ) + return; + + //CG_FillRect( rect->x, rect->y, rect->w, rect->h, colorB ); + + // Draw cross + CG_FillRect( rect->x + (rect->w/2), + rect->y, + 1, + rect->h, + color ); + CG_FillRect( rect->x, + rect->y+(rect->h/2), + rect->w, + 1, + color ); + + // update the player positions + CG_UpdateEntityPositions( ); + + // blips + VectorCopy( entityPositions.origin, origin ); + + // human buildables + for( i = 0; i < entityPositions.numHumanBuildables; i++ ) + { + VectorClear( relOrigin ); + VectorSubtract( entityPositions.humanBuildablePos[ i ], origin, relOrigin ); + + if( VectorLength( relOrigin ) < thz_radarrange.integer ) + { + RotatePointAroundVector( drawOrigin, up, relOrigin, -entityPositions.vangles[ 1 ] - 90 ); + + drawOrigin[ 0 ] /= ((float)(1.25f * (float)thz_radarrange.integer) / (float)rect->w); + drawOrigin[ 1 ] /= ((float)(1.25f * (float)thz_radarrange.integer) / (float)rect->h); + drawOrigin[ 2 ] /= ((float)(1.25f * (float)thz_radarrange.integer) / (float)rect->w); + + CG_FillRect( rect->x + (rect->w / 2) + -drawOrigin[0] - 5, + rect->y + (rect->h / 2) + drawOrigin[1] - 5, + 10, 10, buildcolor ); + } + } + + // humans + for( i = 0; i < entityPositions.numHumanClients; i++ ) + { + VectorClear( relOrigin ); + VectorSubtract( entityPositions.humanClientPos[ i ], origin, relOrigin ); + + if( VectorLength( relOrigin ) < thz_radarrange.integer ) + { + RotatePointAroundVector( drawOrigin, up, relOrigin, -entityPositions.vangles[ 1 ] - 90 ); + + drawOrigin[ 0 ] /= ((float)(1.25f * (float)thz_radarrange.integer) / (float)rect->w); + drawOrigin[ 1 ] /= ((float)(1.25f * (float)thz_radarrange.integer) / (float)rect->h); + drawOrigin[ 2 ] /= ((float)(1.25f * (float)thz_radarrange.integer) / (float)rect->w); + + CG_FillRect( rect->x + (rect->w / 2) + -drawOrigin[0] -3, + rect->y + (rect->h / 2) + drawOrigin[1] -3, + 6, 6, humancolor ); + } + } + + // alien structures + for( i = 0; i < entityPositions.numAlienBuildables; i++ ) + { + VectorClear( relOrigin ); + VectorSubtract( entityPositions.alienBuildablePos[ i ], origin, relOrigin ); + + if( VectorLength( relOrigin ) < thz_radarrange.integer ) + { + RotatePointAroundVector( drawOrigin, up, relOrigin, -entityPositions.vangles[ 1 ] - 90 ); + + drawOrigin[ 0 ] /= ((float)(1.25f * (float)thz_radarrange.integer) / (float)rect->w); + drawOrigin[ 1 ] /= ((float)(1.25f * (float)thz_radarrange.integer) / (float)rect->h); + drawOrigin[ 2 ] /= ((float)(1.25f * (float)thz_radarrange.integer) / (float)rect->w); + + CG_FillRect( rect->x + (rect->w / 2) + -drawOrigin[0] - 5, + rect->y + (rect->h / 2) + drawOrigin[1] - 5, + 10, 10, buildcolor2 ); + } + } + + // aliens + for( i = 0; i < entityPositions.numAlienClients; i++ ) + { + VectorClear( relOrigin ); + VectorSubtract( entityPositions.alienClientPos[ i ], origin, relOrigin ); + + if( VectorLength( relOrigin ) < thz_radarrange.integer ) + { + RotatePointAroundVector( drawOrigin, up, relOrigin, -entityPositions.vangles[ 1 ] - 90 ); + + drawOrigin[ 0 ] /= ((float)(1.25f * (float)thz_radarrange.integer) / (float)rect->w); + drawOrigin[ 1 ] /= ((float)(1.25f * (float)thz_radarrange.integer) / (float)rect->h); + drawOrigin[ 2 ] /= ((float)(1.25f * (float)thz_radarrange.integer) / (float)rect->w); + + CG_FillRect( rect->x + (rect->w / 2) + -drawOrigin[0] -3, + rect->y + (rect->h / 2) + drawOrigin[1] -3, + 6, 6, aliencolor ); + } + } +} diff --git a/src/cgame/cg_servercmds.c b/src/cgame/cg_servercmds.c index 7fb3e06..4de7586 100644 --- a/src/cgame/cg_servercmds.c +++ b/src/cgame/cg_servercmds.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -25,7 +26,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // these are processed at snapshot transition time, so there will definately // be a valid snapshot this frame - #include "cg_local.h" /* @@ -38,13 +38,13 @@ static void CG_ParseScores( void ) { int i; - cg.numScores = atoi( CG_Argv( 1 ) ); + cg.numScores = ( trap_Argc( ) - 3 ) / 6; if( cg.numScores > MAX_CLIENTS ) cg.numScores = MAX_CLIENTS; - cg.teamScores[ 0 ] = atoi( CG_Argv( 2 ) ); - cg.teamScores[ 1 ] = atoi( CG_Argv( 3 ) ); + cg.teamScores[ 0 ] = atoi( CG_Argv( 1 ) ); + cg.teamScores[ 1 ] = atoi( CG_Argv( 2 ) ); memset( cg.scores, 0, sizeof( cg.scores ) ); @@ -54,18 +54,17 @@ static void CG_ParseScores( void ) for( i = 0; i < cg.numScores; i++ ) { // - cg.scores[ i ].client = atoi( CG_Argv( i * 6 + 4 ) ); - cg.scores[ i ].score = atoi( CG_Argv( i * 6 + 5 ) ); - cg.scores[ i ].ping = atoi( CG_Argv( i * 6 + 6 ) ); - cg.scores[ i ].time = atoi( CG_Argv( i * 6 + 7 ) ); - cg.scores[ i ].weapon = atoi( CG_Argv( i * 6 + 8 ) ); - cg.scores[ i ].upgrade = atoi( CG_Argv( i * 6 + 9 ) ); + cg.scores[ i ].client = atoi( CG_Argv( i * 6 + 3 ) ); + cg.scores[ i ].score = atoi( CG_Argv( i * 6 + 4 ) ); + cg.scores[ i ].ping = atoi( CG_Argv( i * 6 + 5 ) ); + cg.scores[ i ].time = atoi( CG_Argv( i * 6 + 6 ) ); + cg.scores[ i ].weapon = atoi( CG_Argv( i * 6 + 7 ) ); + cg.scores[ i ].upgrade = atoi( CG_Argv( i * 6 + 8 ) ); if( cg.scores[ i ].client < 0 || cg.scores[ i ].client >= MAX_CLIENTS ) cg.scores[ i ].client = 0; cgs.clientinfo[ cg.scores[ i ].client ].score = cg.scores[ i ].score; - cgs.clientinfo[ cg.scores[ i ].client ].powerups = 0; cg.scores[ i ].team = cgs.clientinfo[ cg.scores[ i ].client ].team; } @@ -80,22 +79,33 @@ CG_ParseTeamInfo static void CG_ParseTeamInfo( void ) { int i; + int count; int client; - numSortedTeamPlayers = atoi( CG_Argv( 1 ) ); + count = trap_Argc( ); - for( i = 0; i < numSortedTeamPlayers; i++ ) + for( i = 1; i < count; i++ ) // i is also incremented when writing into cgs.clientinfo { - client = atoi( CG_Argv( i * 6 + 2 ) ); + client = atoi( CG_Argv( i ) ); + + // wrong team? drop the remaining info + if( cgs.clientinfo[ client ].team != cg.snap->ps.stats[ STAT_TEAM ] ) + return; - sortedTeamPlayers[ i ] = client; + if( client < 0 || client >= MAX_CLIENTS ) + { + CG_Printf( "[skipnotify]CG_ParseTeamInfo: bad client number: %d\n", client ); + return; + } - cgs.clientinfo[ client ].location = atoi( CG_Argv( i * 6 + 3 ) ); - cgs.clientinfo[ client ].health = atoi( CG_Argv( i * 6 + 4 ) ); - cgs.clientinfo[ client ].armor = atoi( CG_Argv( i * 6 + 5 ) ); - cgs.clientinfo[ client ].curWeapon = atoi( CG_Argv( i * 6 + 6 ) ); - cgs.clientinfo[ client ].powerups = atoi( CG_Argv( i * 6 + 7 ) ); + cgs.clientinfo[ client ].location = atoi( CG_Argv( ++i ) ); + cgs.clientinfo[ client ].health = atoi( CG_Argv( ++i ) ); + cgs.clientinfo[ client ].curWeaponClass = atoi( CG_Argv( ++i ) ); + if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + cgs.clientinfo[ client ].upgrade = atoi( CG_Argv( ++i ) ); } + + cgs.teaminfoReceievedTime = cg.time; } @@ -133,13 +143,7 @@ static void CG_ParseWarmup( void ) info = CG_ConfigString( CS_WARMUP ); warmup = atoi( info ); - cg.warmupCount = -1; - - if( warmup == 0 && cg.warmup ) - { - } - - cg.warmup = warmup; + cg.warmupTime = warmup; } /* @@ -151,19 +155,28 @@ Called on load to set the initial values from configure strings */ void CG_SetConfigValues( void ) { - sscanf( CG_ConfigString( CS_BUILDPOINTS ), - "%d %d %d %d %d", &cgs.alienBuildPoints, - &cgs.alienBuildPointsTotal, - &cgs.humanBuildPoints, - &cgs.humanBuildPointsTotal, - &cgs.humanBuildPointsPowered ); + const char *alienStages = CG_ConfigString( CS_ALIEN_STAGES ); + const char *humanStages = CG_ConfigString( CS_HUMAN_STAGES ); - sscanf( CG_ConfigString( CS_STAGES ), "%d %d %d %d %d %d", &cgs.alienStage, &cgs.humanStage, - &cgs.alienKills, &cgs.humanKills, &cgs.alienNextStageThreshold, &cgs.humanNextStageThreshold ); - sscanf( CG_ConfigString( CS_SPAWNS ), "%d %d", &cgs.numAlienSpawns, &cgs.numHumanSpawns ); + if( alienStages[0] ) + { + sscanf( alienStages, "%d %d %d", &cgs.alienStage, &cgs.alienCredits, + &cgs.alienNextStageThreshold ); + } + else + cgs.alienStage = cgs.alienCredits = cgs.alienNextStageThreshold = 0; + + + if( humanStages[0] ) + { + sscanf( humanStages, "%d %d %d", &cgs.humanStage, &cgs.humanCredits, + &cgs.humanNextStageThreshold ); + } + else + cgs.humanStage = cgs.humanCredits = cgs.humanNextStageThreshold = 0; cgs.levelStartTime = atoi( CG_ConfigString( CS_LEVEL_START_TIME ) ); - cg.warmup = atoi( CG_ConfigString( CS_WARMUP ) ); + cg.warmupTime = atoi( CG_ConfigString( CS_WARMUP ) ); } @@ -224,7 +237,7 @@ CG_AnnounceAlienStageTransistion */ static void CG_AnnounceAlienStageTransistion( stage_t from, stage_t to ) { - if( cg.predictedPlayerState.stats[ STAT_PTEAM ] != PTE_ALIENS ) + if( cg.predictedPlayerState.stats[ STAT_TEAM ] != TEAM_ALIENS ) return; trap_S_StartLocalSound( cgs.media.alienStageTransition, CHAN_ANNOUNCER ); @@ -238,7 +251,7 @@ CG_AnnounceHumanStageTransistion */ static void CG_AnnounceHumanStageTransistion( stage_t from, stage_t to ) { - if( cg.predictedPlayerState.stats[ STAT_PTEAM ] != PTE_HUMANS ) + if( cg.predictedPlayerState.stats[ STAT_TEAM ] != TEAM_HUMANS ) return; trap_S_StartLocalSound( cgs.media.humanStageTransition, CHAN_ANNOUNCER ); @@ -272,91 +285,72 @@ static void CG_ConfigStringModified( void ) CG_ParseServerinfo( ); else if( num == CS_WARMUP ) CG_ParseWarmup( ); - else if( num == CS_BUILDPOINTS ) - sscanf( str, "%d %d %d %d %d", &cgs.alienBuildPoints, - &cgs.alienBuildPointsTotal, - &cgs.humanBuildPoints, - &cgs.humanBuildPointsTotal, - &cgs.humanBuildPointsPowered ); - else if( num == CS_STAGES ) + else if( num == CS_ALIEN_STAGES ) { stage_t oldAlienStage = cgs.alienStage; - stage_t oldHumanStage = cgs.humanStage; - - sscanf( str, "%d %d %d %d %d %d", - &cgs.alienStage, &cgs.humanStage, - &cgs.alienKills, &cgs.humanKills, - &cgs.alienNextStageThreshold, &cgs.humanNextStageThreshold ); - - if( cgs.alienStage != oldAlienStage ) - CG_AnnounceAlienStageTransistion( oldAlienStage, cgs.alienStage ); - if( cgs.humanStage != oldHumanStage ) - CG_AnnounceHumanStageTransistion( oldHumanStage, cgs.humanStage ); - } - else if( num == CS_SPAWNS ) - sscanf( str, "%d %d", &cgs.numAlienSpawns, &cgs.numHumanSpawns ); - else if( num == CS_LEVEL_START_TIME ) - cgs.levelStartTime = atoi( str ); - else if( num == CS_VOTE_TIME ) - { - cgs.voteTime = atoi( str ); - cgs.voteModified = qtrue; + if( str[0] ) + { + sscanf( str, "%d %d %d", &cgs.alienStage, &cgs.alienCredits, + &cgs.alienNextStageThreshold ); - if( cgs.voteTime ) - trap_Cvar_Set( "ui_voteActive", "1" ); + if( cgs.alienStage != oldAlienStage ) + CG_AnnounceAlienStageTransistion( oldAlienStage, cgs.alienStage ); + } else - trap_Cvar_Set( "ui_voteActive", "0" ); - } - else if( num == CS_VOTE_YES ) - { - cgs.voteYes = atoi( str ); - cgs.voteModified = qtrue; - } - else if( num == CS_VOTE_NO ) - { - cgs.voteNo = atoi( str ); - cgs.voteModified = qtrue; + { + cgs.alienStage = cgs.alienCredits = cgs.alienNextStageThreshold = 0; + } } - else if( num == CS_VOTE_STRING ) - Q_strncpyz( cgs.voteString, str, sizeof( cgs.voteString ) ); - else if( num >= CS_TEAMVOTE_TIME && num <= CS_TEAMVOTE_TIME + 1 ) + else if( num == CS_HUMAN_STAGES ) { - int cs_offset = num - CS_TEAMVOTE_TIME; - - cgs.teamVoteTime[ cs_offset ] = atoi( str ); - cgs.teamVoteModified[ cs_offset ] = qtrue; + stage_t oldHumanStage = cgs.humanStage; - if( cs_offset == 0 ) + if( str[0] ) { - if( cgs.teamVoteTime[ cs_offset ] ) - trap_Cvar_Set( "ui_humanTeamVoteActive", "1" ); - else - trap_Cvar_Set( "ui_humanTeamVoteActive", "0" ); + sscanf( str, "%d %d %d", &cgs.humanStage, &cgs.humanCredits, + &cgs.humanNextStageThreshold ); + + if( cgs.humanStage != oldHumanStage ) + CG_AnnounceHumanStageTransistion( oldHumanStage, cgs.humanStage ); } - else if( cs_offset == 1 ) + else { - if( cgs.teamVoteTime[ cs_offset ] ) - trap_Cvar_Set( "ui_alienTeamVoteActive", "1" ); - else - trap_Cvar_Set( "ui_alienTeamVoteActive", "0" ); + cgs.humanStage = cgs.humanCredits = cgs.humanNextStageThreshold = 0; } } - else if( num >= CS_TEAMVOTE_YES && num <= CS_TEAMVOTE_YES + 1 ) + else if( num == CS_LEVEL_START_TIME ) + cgs.levelStartTime = atoi( str ); + else if( num >= CS_VOTE_TIME && num < CS_VOTE_TIME + NUM_TEAMS ) { - cgs.teamVoteYes[ num - CS_TEAMVOTE_YES ] = atoi( str ); - cgs.teamVoteModified[ num - CS_TEAMVOTE_YES ] = qtrue; + cgs.voteTime[ num - CS_VOTE_TIME ] = atoi( str ); + cgs.voteModified[ num - CS_VOTE_TIME ] = qtrue; + + if( num - CS_VOTE_TIME == TEAM_NONE ) + trap_Cvar_Set( "ui_voteActive", cgs.voteTime[ TEAM_NONE ] ? "1" : "0" ); + else if( num - CS_VOTE_TIME == TEAM_ALIENS ) + trap_Cvar_Set( "ui_alienTeamVoteActive", + cgs.voteTime[ TEAM_ALIENS ] ? "1" : "0" ); + else if( num - CS_VOTE_TIME == TEAM_HUMANS ) + trap_Cvar_Set( "ui_humanTeamVoteActive", + cgs.voteTime[ TEAM_HUMANS ] ? "1" : "0" ); } - else if( num >= CS_TEAMVOTE_NO && num <= CS_TEAMVOTE_NO + 1 ) + else if( num >= CS_VOTE_YES && num < CS_VOTE_YES + NUM_TEAMS ) { - cgs.teamVoteNo[ num - CS_TEAMVOTE_NO ] = atoi( str ); - cgs.teamVoteModified[ num - CS_TEAMVOTE_NO ] = qtrue; + cgs.voteYes[ num - CS_VOTE_YES ] = atoi( str ); + cgs.voteModified[ num - CS_VOTE_YES ] = qtrue; } - else if( num >= CS_TEAMVOTE_STRING && num <= CS_TEAMVOTE_STRING + 1 ) + else if( num >= CS_VOTE_NO && num < CS_VOTE_NO + NUM_TEAMS ) { - Q_strncpyz( cgs.teamVoteString[ num - CS_TEAMVOTE_STRING ], str, - sizeof( cgs.teamVoteString[ num - CS_TEAMVOTE_STRING ] ) ); + cgs.voteNo[ num - CS_VOTE_NO ] = atoi( str ); + cgs.voteModified[ num - CS_VOTE_NO ] = qtrue; } + else if( num >= CS_VOTE_STRING && num < CS_VOTE_STRING + NUM_TEAMS ) + Q_strncpyz( cgs.voteString[ num - CS_VOTE_STRING ], str, + sizeof( cgs.voteString[ num - CS_VOTE_STRING ] ) ); + else if( num >= CS_VOTE_CALLER && num < CS_VOTE_CALLER + NUM_TEAMS ) + Q_strncpyz( cgs.voteCaller[ num - CS_VOTE_CALLER ], str, + sizeof( cgs.voteCaller[ num - CS_VOTE_CALLER ] ) ); else if( num == CS_INTERMISSION ) cg.intermissionStarted = atoi( str ); else if( num >= CS_MODELS && num < CS_MODELS+MAX_MODELS ) @@ -413,7 +407,7 @@ static void CG_MapRestart( void ) cg.intermissionStarted = qfalse; - cgs.voteTime = 0; + cgs.voteTime[ TEAM_NONE ] = 0; cg.mapRestart = qtrue; @@ -423,574 +417,903 @@ static void CG_MapRestart( void ) // we really should clear more parts of cg here and stop sounds - // play the "fight" sound if this is a restart without warmup - if( cg.warmup == 0 ) - CG_CenterPrint( "FIGHT!", 120, GIANTCHAR_WIDTH * 2 ); - trap_Cvar_Set( "cg_thirdPerson", "0" ); } -/* -================= -CG_RemoveChatEscapeChar -================= -*/ -static void CG_RemoveChatEscapeChar( char *text ) -{ - int i, l; - - l = 0; - for( i = 0; text[ i ]; i++ ) - { - if( text[ i ] == '\x19' ) - continue; - - text[ l++ ] = text[ i ]; - } - - text[ l ] = '\0'; -} - -/* -=============== -CG_SetUIVars - -Set some cvars used by the UI -=============== -*/ -static void CG_SetUIVars( void ) -{ - int i; - char carriageCvar[ MAX_TOKEN_CHARS ]; - - *carriageCvar = 0; - - //determine what the player is carrying - for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) - { - if( BG_InventoryContainsWeapon( i, cg.snap->ps.stats ) && - BG_FindPurchasableForWeapon( i ) ) - strcat( carriageCvar, va( "W%d ", i ) ); - } - for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) - { - if( BG_InventoryContainsUpgrade( i, cg.snap->ps.stats ) && - BG_FindPurchasableForUpgrade( i ) ) - strcat( carriageCvar, va( "U%d ", i ) ); - } - strcat( carriageCvar, "$" ); - - trap_Cvar_Set( "ui_carriage", carriageCvar ); - - trap_Cvar_Set( "ui_stages", va( "%d %d", cgs.alienStage, cgs.humanStage ) ); -} - - /* ============== CG_Menu ============== */ -void CG_Menu( int menu ) +void CG_Menu( int menu, int arg ) { - const char *cmd = NULL; // command to send - const char *longMsg = NULL; // command parameter - const char *shortMsg = NULL; // non-modal version of message - CG_SetUIVars( ); + const char *cmd; // command to send + const char *longMsg = NULL; // command parameter + const char *shortMsg = NULL; // non-modal version of message + const char *dialog; + dialogType_t type = 0; // controls which cg_disable var will switch it off + + switch( cg.snap->ps.stats[ STAT_TEAM ] ) + { + case TEAM_ALIENS: + dialog = "menu tremulous_alien_dialog\n"; + break; + case TEAM_HUMANS: + dialog = "menu tremulous_human_dialog\n"; + break; + default: + dialog = "menu tremulous_default_dialog\n"; + } + cmd = dialog; - // string literals have static storage duration, this is safe, - // cleaner and much more readable. switch( menu ) { case MN_TEAM: cmd = "menu tremulous_teamselect\n"; + type = DT_INTERACTIVE; break; case MN_A_CLASS: cmd = "menu tremulous_alienclass\n"; + type = DT_INTERACTIVE; break; case MN_H_SPAWN: cmd = "menu tremulous_humanitem\n"; + type = DT_INTERACTIVE; break; case MN_A_BUILD: cmd = "menu tremulous_alienbuild\n"; + type = DT_INTERACTIVE; break; case MN_H_BUILD: cmd = "menu tremulous_humanbuild\n"; + type = DT_INTERACTIVE; break; case MN_H_ARMOURY: cmd = "menu tremulous_humanarmoury\n"; + type = DT_INTERACTIVE; + break; + + case MN_H_UNKNOWNITEM: + shortMsg = "Unknown item"; + type = DT_ARMOURYEVOLVE; break; case MN_A_TEAMFULL: longMsg = "The alien team has too many players. Please wait until slots " "become available or join the human team."; - shortMsg = "The alien team has too many players\n"; - cmd = "menu tremulous_alien_dialog\n"; + shortMsg = "The alien team has too many players"; + type = DT_COMMAND; break; case MN_H_TEAMFULL: longMsg = "The human team has too many players. Please wait until slots " "become available or join the alien team."; - shortMsg = "The human team has too many players\n"; - cmd = "menu tremulous_human_dialog\n"; + shortMsg = "The human team has too many players"; + type = DT_COMMAND; break; - case MN_A_TEAMCHANGEBUILDTIMER: - longMsg = "You cannot leave the Alien team until your build timer " - "has expired."; - shortMsg = "You cannot change teams until your build timer expires.\n"; - cmd = "menu tremulous_alien_dialog\n"; + case MN_A_TEAMLOCKED: + longMsg = "The alien team is locked. You cannot join the aliens " + "at this time."; + shortMsg = "The alien team is locked"; + type = DT_COMMAND; break; - case MN_H_TEAMCHANGEBUILDTIMER: - longMsg = "You cannot leave the Human team until your build timer " - "has expired."; - shortMsg = "You cannot change teams until your build timer expires.\n"; - cmd = "menu tremulous_human_dialog\n"; + case MN_H_TEAMLOCKED: + longMsg = "The human team is locked. You cannot join the humans " + "at this time."; + shortMsg = "The human team is locked"; + type = DT_COMMAND; + break; + + case MN_PLAYERLIMIT: + longMsg = "The maximum number of playing clients has been reached. " + "Please wait until slots become available."; + shortMsg = "No free player slots"; + type = DT_COMMAND; break; //=============================== - case MN_H_NOROOM: - longMsg = "There is no room to build here. Move until the buildable turns " - "translucent green indicating a valid build location."; - shortMsg = "There is no room to build here\n"; - cmd = "menu tremulous_human_dialog\n"; + // Since cheating commands have no default binds, they will often be done + // via console. In light of this, perhaps opening a menu is + // counterintuitive + case MN_CMD_CHEAT: + //longMsg = "This action is considered cheating. It can only be used " + // "in cheat mode, which is not enabled on this server."; + shortMsg = "Cheats are not enabled on this server"; + type = DT_COMMAND; break; - case MN_H_NOPOWER: - longMsg = "There is no power remaining. Free up power by destroying " - "existing buildable objects."; - shortMsg = "There is no power remaining\n"; - cmd = "menu tremulous_human_dialog\n"; + case MN_CMD_CHEAT_TEAM: + shortMsg = "Cheats are not enabled on this server, so " + "you may not use this command while on a team"; + type = DT_COMMAND; break; - case MN_H_NOTPOWERED: - longMsg = "This buildable is not powered. Build a Reactor and/or Repeater " - "in order to power it."; - shortMsg = "This buildable is not powered\n"; - cmd = "menu tremulous_human_dialog\n"; + case MN_CMD_TEAM: + //longMsg = "You must be on a team to perform this action. Join the alien" + // "or human team and try again."; + shortMsg = "Join a team first"; + type = DT_COMMAND; break; - case MN_H_NORMAL: + case MN_CMD_SPEC: + //longMsg = "You may not perform this action while on a team. Become a " + // "spectator before trying again."; + shortMsg = "You can only use this command when spectating"; + type = DT_COMMAND; + break; + + case MN_CMD_ALIEN: + //longMsg = "You must be on the alien team to perform this action."; + shortMsg = "Must be alien to use this command"; + type = DT_COMMAND; + break; + + case MN_CMD_HUMAN: + //longMsg = "You must be on the human team to perform this action."; + shortMsg = "Must be human to use this command"; + type = DT_COMMAND; + break; + + case MN_CMD_ALIVE: + //longMsg = "You must be alive to perform this action."; + shortMsg = "Must be alive to use this command"; + type = DT_COMMAND; + break; + + + //=============================== + + case MN_B_NOROOM: + longMsg = "There is no room to build here. Move until the structure turns " + "translucent green, indicating a valid build location."; + shortMsg = "There is no room to build here"; + type = DT_BUILD; + break; + + case MN_B_NORMAL: longMsg = "Cannot build on this surface. The surface is too steep or " - "unsuitable to build on. Please choose another site for this " - "structure."; - shortMsg = "Cannot build on this surface\n"; - cmd = "menu tremulous_human_dialog\n"; + "unsuitable for building. Please choose another site for this " + "structure."; + shortMsg = "Cannot build on this surface"; + type = DT_BUILD; + break; + + case MN_B_CANNOT: + longMsg = NULL; + shortMsg = "You cannot build that structure"; + type = DT_BUILD; + break; + + // FIXME: MN_H_ and MN_A_? + case MN_B_LASTSPAWN: + longMsg = "This action would remove your team's last spawn point, " + "which often quickly results in a loss. Try building more " + "spawns."; + shortMsg = "You may not deconstruct the last spawn"; + break; + + case MN_B_SUDDENDEATH: + longMsg = "Neither team has prevailed after a certain time and the " + "game has entered Sudden Death. During Sudden Death " + "building is not allowed."; + shortMsg = "Cannot build during Sudden Death"; + type = DT_BUILD; + break; + + case MN_B_REVOKED: + longMsg = "Your teammates have lost faith in your ability to build " + "for the team. You will not be allowed to build until your " + "team votes to reinstate your building rights."; + shortMsg = "Your building rights have been revoked"; + type = DT_BUILD; break; - case MN_H_REACTOR: - longMsg = "There can only be one Reactor. Destroy the existing one if you " + case MN_B_SURRENDER: + longMsg = "Your team has decided to admit defeat and concede the game:" + "traitors and cowards are not allowed to build."; + // too harsh? + shortMsg = "Building is denied to traitorous cowards"; + break; + + //=============================== + + case MN_H_NOBP: + if( cgs.markDeconstruct ) + longMsg = "There is no power remaining. Free up power by marking " + "existing buildable objects."; + else + longMsg = "There is no power remaining. Free up power by deconstructing " + "existing buildable objects."; + shortMsg = "There is no power remaining"; + type = DT_BUILD; + break; + + case MN_H_NOTPOWERED: + longMsg = "This buildable is not powered. Build a Reactor and/or Repeater " + "in order to power it."; + shortMsg = "This buildable is not powered"; + type = DT_BUILD; + break; + + case MN_H_ONEREACTOR: + longMsg = "There can only be one Reactor. Deconstruct the existing one if you " "wish to move it."; - shortMsg = "There can only be one Reactor\n"; - cmd = "menu tremulous_human_dialog\n"; + shortMsg = "There can only be one Reactor"; + type = DT_BUILD; break; - case MN_H_REPEATER: + case MN_H_NOPOWERHERE: longMsg = "There is no power here. If available, a Repeater may be used to " "transmit power to this location."; - shortMsg = "There is no power here\n"; - cmd = "menu tremulous_human_dialog\n"; + shortMsg = "There is no power here"; + type = DT_BUILD; break; case MN_H_NODCC: longMsg = "There is no Defense Computer. A Defense Computer is needed to " "build this."; - shortMsg = "There is no Defense Computer\n"; - cmd = "menu tremulous_human_dialog\n"; - break; - - case MN_H_TNODEWARN: - longMsg = "WARNING: This Telenode will not be powered. Build near a power " - "structure to prevent seeing this message again."; - shortMsg = "This Telenode will not be powered\n"; - cmd = "menu tremulous_human_dialog\n"; - break; - - case MN_H_RPTWARN: - longMsg = "WARNING: This Repeater will not be powered as there is no parent " - "Reactor providing power. Build a Reactor."; - shortMsg = "This Repeater will not be powered\n"; - cmd = "menu tremulous_human_dialog\n"; + shortMsg = "There is no Defense Computer"; + type = DT_BUILD; break; - case MN_H_RPTWARN2: + case MN_H_RPTPOWERHERE: longMsg = "This area already has power. A Repeater is not required here."; - shortMsg = "This area already has power\n"; - cmd = "menu tremulous_human_dialog\n"; + shortMsg = "This area already has power"; + type = DT_BUILD; break; case MN_H_NOSLOTS: longMsg = "You have no room to carry this. Please sell any conflicting " "upgrades before purchasing this item."; - shortMsg = "You have no room to carry this\n"; - cmd = "menu tremulous_human_dialog\n"; + shortMsg = "You have no room to carry this"; + type = DT_ARMOURYEVOLVE; break; case MN_H_NOFUNDS: longMsg = "Insufficient funds. You do not have enough credits to perform " "this action."; - shortMsg = "Insufficient funds\n"; - cmd = "menu tremulous_human_dialog\n"; + shortMsg = "Insufficient funds"; + type = DT_ARMOURYEVOLVE; break; case MN_H_ITEMHELD: longMsg = "You already hold this item. It is not possible to carry multiple " "items of the same type."; - shortMsg = "You already hold this item\n"; - cmd = "menu tremulous_human_dialog\n"; + shortMsg = "You already hold this item"; + type = DT_ARMOURYEVOLVE; break; case MN_H_NOARMOURYHERE: longMsg = "You must be near a powered Armoury in order to purchase " - "weapons, upgrades or non-energy ammunition."; - shortMsg = "You must be near a powered Armoury\n"; - cmd = "menu tremulous_human_dialog\n"; + "weapons, upgrades or ammunition."; + shortMsg = "You must be near a powered Armoury"; + type = DT_ARMOURYEVOLVE; break; case MN_H_NOENERGYAMMOHERE: - longMsg = "You must be near an Armoury, Reactor or Repeater in order " - "to purchase energy ammunition."; - shortMsg = "You must be near an Armoury, Reactor or Repeater\n"; - cmd = "menu tremulous_human_dialog\n"; + longMsg = "You must be near a Reactor or a powered Armoury or Repeater " + "in order to purchase energy ammunition."; + shortMsg = "You must be near a Reactor or a powered Armoury or Repeater"; + type = DT_ARMOURYEVOLVE; break; case MN_H_NOROOMBSUITON: longMsg = "There is not enough room here to put on a Battle Suit. " "Make sure you have enough head room to climb in."; - shortMsg = "Not enough room here to put on a Battle Suit\n"; - cmd = "menu tremulous_human_dialog\n"; + shortMsg = "Not enough room here to put on a Battle Suit"; + type = DT_ARMOURYEVOLVE; break; case MN_H_NOROOMBSUITOFF: longMsg = "There is not enough room here to take off your Battle Suit. " "Make sure you have enough head room to climb out."; - shortMsg = "Not enough room here to take off your Battle Suit\n"; - cmd = "menu tremulous_human_dialog\n"; + shortMsg = "Not enough room here to take off your Battle Suit"; + type = DT_ARMOURYEVOLVE; break; case MN_H_ARMOURYBUILDTIMER: longMsg = "You are not allowed to buy or sell weapons until your " "build timer has expired."; - shortMsg = "You can not buy or sell weapos until your build timer " - "expires\n"; - cmd = "menu tremulous_human_dialog\n"; + shortMsg = "You can not buy or sell weapons until your build timer " + "expires"; + type = DT_ARMOURYEVOLVE; break; + case MN_H_DEADTOCLASS: + shortMsg = "You must be dead to use the class command"; + type = DT_COMMAND; + break; - //=============================== - - case MN_A_NOROOM: - longMsg = "There is no room to build here. Move until the structure turns " - "translucent green indicating a valid build location."; - shortMsg = "There is no room to build here\n"; - cmd = "menu tremulous_alien_dialog\n"; + case MN_H_UNKNOWNSPAWNITEM: + shortMsg = "Unknown starting item"; + type = DT_COMMAND; break; + //=============================== + case MN_A_NOCREEP: longMsg = "There is no creep here. You must build near existing Eggs or " "the Overmind. Alien structures will not support themselves."; - shortMsg = "There is no creep here\n"; - cmd = "menu tremulous_alien_dialog\n"; + shortMsg = "There is no creep here"; + type = DT_BUILD; break; case MN_A_NOOVMND: longMsg = "There is no Overmind. An Overmind must be built to control " - "the structure you tried to place"; - shortMsg = "There is no Overmind\n"; - cmd = "menu tremulous_alien_dialog\n"; - break; - - case MN_A_OVERMIND: - longMsg = "There can only be one Overmind. Destroy the existing one if you " - "wish to move it."; - shortMsg = "There can only be one Overmind\n"; - cmd = "menu tremulous_alien_dialog\n"; + "the structure you tried to place."; + shortMsg = "There is no Overmind"; + type = DT_BUILD; break; - case MN_A_HOVEL: - longMsg = "There can only be one Hovel. Destroy the existing one if you " + case MN_A_ONEOVERMIND: + longMsg = "There can only be one Overmind. Deconstruct the existing one if you " "wish to move it."; - shortMsg = "There can only be one Hovel\n"; - cmd = "menu tremulous_alien_dialog\n"; + shortMsg = "There can only be one Overmind"; + type = DT_BUILD; break; - case MN_A_NOASSERT: - longMsg = "The Overmind cannot control any more structures. Destroy existing " + case MN_A_NOBP: + longMsg = "The Overmind cannot control any more structures. Deconstruct existing " "structures to build more."; - shortMsg = "The Overmind cannot control any more structures\n"; - cmd = "menu tremulous_alien_dialog\n"; - break; - - case MN_A_SPWNWARN: - longMsg = "WARNING: This spawn will not be controlled by an Overmind. " - "Build an Overmind to prevent seeing this message again."; - shortMsg = "This spawn will not be controlled by an Overmind\n"; - cmd = "menu tremulous_alien_dialog\n"; - break; - - case MN_A_NORMAL: - longMsg = "Cannot build on this surface. This surface is too steep or " - "unsuitable to build on. Please choose another site for this " - "structure."; - shortMsg = "Cannot build on this surface\n"; - cmd = "menu tremulous_alien_dialog\n"; + shortMsg = "The Overmind cannot control any more structures"; + type = DT_BUILD; break; case MN_A_NOEROOM: longMsg = "There is no room to evolve here. Move away from walls or other " - "nearby objects and try again."; - cmd = "menu tremulous_alien_dialog\n"; - shortMsg = "There is no room to evolve here\n"; + "nearby objects and try again."; + shortMsg = "There is no room to evolve here"; + type = DT_ARMOURYEVOLVE; break; case MN_A_TOOCLOSE: longMsg = "This location is too close to the enemy to evolve. Move away " - "until you are no longer aware of the enemy's presence and try " - "again."; - shortMsg = "This location is too close to the enemy to evolve\n"; - cmd = "menu tremulous_alien_dialog\n"; + "from the enemy's presence and try again."; + shortMsg = "This location is too close to the enemy to evolve"; + type = DT_ARMOURYEVOLVE; break; case MN_A_NOOVMND_EVOLVE: longMsg = "There is no Overmind. An Overmind must be built to allow " "you to upgrade."; - shortMsg = "There is no Overmind\n"; - cmd = "menu tremulous_alien_dialog\n"; + shortMsg = "There is no Overmind"; + type = DT_ARMOURYEVOLVE; break; case MN_A_EVOLVEBUILDTIMER: - longMsg = "You cannot Evolve until your build timer has expired."; - shortMsg = "You cannot Evolve until your build timer expires\n"; - cmd = "menu tremulous_alien_dialog\n"; + longMsg = "You cannot evolve until your build timer has expired."; + shortMsg = "You cannot evolve until your build timer expires"; + type = DT_ARMOURYEVOLVE; break; - case MN_A_HOVEL_OCCUPIED: - longMsg = "This Hovel is already occupied by another builder."; - shortMsg = "This Hovel is already occupied by another builder\n"; - cmd = "menu tremulous_alien_dialog\n"; + case MN_A_INFEST: + trap_Cvar_Set( "ui_currentClass", + va( "%d %d", cg.snap->ps.stats[ STAT_CLASS ], + cg.snap->ps.persistant[ PERS_CREDIT ] ) ); + + cmd = "menu tremulous_alienupgrade\n"; + type = DT_INTERACTIVE; break; - case MN_A_HOVEL_BLOCKED: - longMsg = "The exit to this Hovel is currently blocked. Please wait until it " - "becomes clear then try again."; - shortMsg = "The exit to this Hovel is currently blocked\n"; - cmd = "menu tremulous_alien_dialog\n"; + case MN_A_CANTEVOLVE: + shortMsg = va( "You cannot evolve into a %s", + BG_ClassConfig( arg )->humanName ); + type = DT_ARMOURYEVOLVE; break; - case MN_A_HOVEL_EXIT: - longMsg = "The exit to this Hovel would always be blocked. Please choose " - "a more suitable location."; - shortMsg = "The exit to this Hovel would always be blocked\n"; - cmd = "menu tremulous_alien_dialog\n"; + case MN_A_EVOLVEWALLWALK: + shortMsg = "You cannot evolve while wallwalking"; + type = DT_ARMOURYEVOLVE; break; - case MN_A_INFEST: - trap_Cvar_Set( "ui_currentClass", va( "%d %d", cg.snap->ps.stats[ STAT_PCLASS ], - cg.snap->ps.persistant[ PERS_CREDIT ] ) ); - cmd = "menu tremulous_alienupgrade\n"; + case MN_A_UNKNOWNCLASS: + shortMsg = "Unknown class"; + type = DT_ARMOURYEVOLVE; + break; + + case MN_A_CLASSNOTSPAWN: + shortMsg = va( "You cannot spawn as a %s", + BG_ClassConfig( arg )->humanName ); + type = DT_ARMOURYEVOLVE; + break; + + case MN_A_CLASSNOTALLOWED: + shortMsg = va( "The %s is not allowed", + BG_ClassConfig( arg )->humanName ); + type = DT_ARMOURYEVOLVE; + break; + + case MN_A_CLASSNOTATSTAGE: + shortMsg = va( "The %s is not allowed at Stage %d", + BG_ClassConfig( arg )->humanName, + cgs.alienStage + 1 ); + type = DT_ARMOURYEVOLVE; break; default: Com_Printf( "cgame: debug: no such menu %d\n", menu ); } + + if( type == DT_ARMOURYEVOLVE && cg_disableUpgradeDialogs.integer ) + return; - if( !cg_disableWarningDialogs.integer || !shortMsg ) - { - // Player either wants dialog window or there's no short message - if( cmd ) - { - if( longMsg ) - trap_Cvar_Set( "ui_dialog", longMsg ); + if( type == DT_BUILD && cg_disableBuildDialogs.integer ) + return; - trap_SendConsoleCommand( cmd ); - } - } - else + if( type == DT_COMMAND && cg_disableCommandDialogs.integer ) + return; + + if( cmd != dialog ) + { + trap_SendConsoleCommand( cmd ); + } + else if( longMsg && cg_disableWarningDialogs.integer == 0 ) { - // There is short message and player wants it - CG_Printf( shortMsg ); - } + trap_Cvar_Set( "ui_dialog", longMsg ); + trap_SendConsoleCommand( cmd ); + } + else if( shortMsg && cg_disableWarningDialogs.integer < 2 ) + { + CG_Printf( "%s\n", shortMsg ); + } } /* ================= -CG_ServerCommand - -The string has been tokenized and can be retrieved with -Cmd_Argc() / Cmd_Argv() +CG_Say ================= */ -static void CG_ServerCommand( void ) +static void CG_Say( int clientNum, saymode_t mode, const char *text ) { - const char *cmd; - char text[ MAX_SAY_TEXT ]; + char *name; + char prefix[ 11 ] = ""; + char *ignore = ""; + const char *location = ""; + char *color; + char *maybeColon; + + if( clientNum >= 0 && clientNum < MAX_CLIENTS ) + { + clientInfo_t *ci = &cgs.clientinfo[ clientNum ]; + char *tcolor = S_COLOR_WHITE; - cmd = CG_Argv( 0 ); + name = ci->name; - if( !cmd[ 0 ] ) - { - // server claimed the command - return; - } + if( ci->team == TEAM_ALIENS ) + tcolor = S_COLOR_RED; + else if( ci->team == TEAM_HUMANS ) + tcolor = S_COLOR_CYAN; - if( !strcmp( cmd, "cp" ) ) - { - CG_CenterPrint( CG_Argv( 1 ), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); - return; - } + if( cg_chatTeamPrefix.integer ) + Com_sprintf( prefix, sizeof( prefix ), "[%s%c" S_COLOR_WHITE "] ", + tcolor, toupper( *( BG_TeamName( ci->team ) ) ) ); - if( !strcmp( cmd, "cs" ) ) - { - CG_ConfigStringModified( ); - return; + if( ( mode == SAY_TEAM || mode == SAY_AREA ) && + cg.snap->ps.pm_type != PM_INTERMISSION ) + { + int locationNum; + + if( clientNum == cg.snap->ps.clientNum ) + { + centity_t *locent; + + locent = CG_GetPlayerLocation( ); + if( locent ) + locationNum = locent->currentState.generic1; + else + locationNum = -1; + } + else + locationNum = ci->location; + + if( locationNum >= 0 && locationNum < MAX_LOCATIONS ) + { + const char *s = CG_ConfigString( CS_LOCATIONS + locationNum ); + + if( *s ) + location = va( " (%s" S_COLOR_WHITE ")", s ); + } + } } + else + name = "console"; - if( !strcmp( cmd, "print" ) ) + // IRC-like /me parsing + if( mode != SAY_RAW && Q_stricmpn( text, "/me ", 4 ) == 0 ) { - CG_Printf( "%s", CG_Argv( 1 ) ); - return; + text += 4; + Q_strcat( prefix, sizeof( prefix ), "* " ); + maybeColon = ""; } + else + maybeColon = ":"; - if( !strcmp( cmd, "chat" ) ) + switch( mode ) { - if( !cg_teamChatsOnly.integer ) - { - Q_strncpyz( text, CG_Argv( 1 ), MAX_SAY_TEXT ); - if( Q_stricmpn( text, "[skipnotify]", 12 ) ) - trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); - CG_RemoveChatEscapeChar( text ); + case SAY_ALL: + // might already be ignored but in that case no harm is done + if( cg_teamChatsOnly.integer ) + ignore = "[skipnotify]"; + +#ifdef MODULE_INTERFACE_11 + CG_Printf( "%s%s%s" S_COLOR_WHITE "%s " S_COLOR_GREEN "%s\n", + ignore, prefix, name, maybeColon, text ); +#else + CG_Printf( "%s%s%s" S_COLOR_WHITE "%s %c" S_COLOR_GREEN "%s\n", + ignore, prefix, name, maybeColon, INDENT_MARKER, text ); +#endif + break; + case SAY_TEAM: +#ifdef MODULE_INTERFACE_11 + CG_Printf( "%s%s(%s" S_COLOR_WHITE ")%s%s " S_COLOR_CYAN "%s\n", + ignore, prefix, name, location, maybeColon, text ); +#else + CG_Printf( "%s%s(%s" S_COLOR_WHITE ")%s%s %c" S_COLOR_CYAN "%s\n", + ignore, prefix, name, location, maybeColon, INDENT_MARKER, text ); +#endif + break; + case SAY_ADMINS: + case SAY_ADMINS_PUBLIC: +#ifdef MODULE_INTERFACE_11 + CG_Printf( "%s%s%s%s" S_COLOR_WHITE "%s " S_COLOR_MAGENTA "%s\n", + ignore, prefix, + ( mode == SAY_ADMINS ) ? "[ADMIN]" : "[PLAYER]", + name, maybeColon, text ); +#else + CG_Printf( "%s%s%s%s" S_COLOR_WHITE "%s %c" S_COLOR_MAGENTA "%s\n", + ignore, prefix, + ( mode == SAY_ADMINS ) ? "[ADMIN]" : "[PLAYER]", + name, maybeColon, INDENT_MARKER, text ); +#endif + break; + case SAY_AREA: +#ifdef MODULE_INTERFACE_11 + CG_Printf( "%s%s<%s" S_COLOR_WHITE ">%s%s " S_COLOR_BLUE "%s\n", + ignore, prefix, name, location, maybeColon, text ); +#else + CG_Printf( "%s%s<%s" S_COLOR_WHITE ">%s%s %c" S_COLOR_BLUE "%s\n", + ignore, prefix, name, location, maybeColon, INDENT_MARKER, text ); +#endif + break; + case SAY_PRIVMSG: + case SAY_TPRIVMSG: + color = ( mode == SAY_TPRIVMSG ) ? S_COLOR_CYAN : S_COLOR_GREEN; +#ifdef MODULE_INTERFACE_11 + CG_Printf( "%s%s[%s" S_COLOR_WHITE " -> %s" S_COLOR_WHITE "]%s %s%s\n", + ignore, prefix, name, cgs.clientinfo[ cg.clientNum ].name, + maybeColon, color, text ); +#else + CG_Printf( "%s%s[%s" S_COLOR_WHITE " -> %s" S_COLOR_WHITE "]%s %c%s%s\n", + ignore, prefix, name, cgs.clientinfo[ cg.clientNum ].name, + maybeColon, INDENT_MARKER, color, text ); +#endif + if( !ignore[0] ) + { + CG_CenterPrint( va( "%sPrivate message from: " S_COLOR_WHITE "%s", + color, name ), 200, GIANTCHAR_WIDTH * 4 ); + if( clientNum < 0 || clientNum >= MAX_CLIENTS ) + clientNum = cg.clientNum; + CG_Printf( ">> to reply, say: /m %d [your message] <<\n", clientNum ); + } + break; + case SAY_RAW: CG_Printf( "%s\n", text ); - } - - return; + break; } - if( !strcmp( cmd, "tchat" ) ) + switch( mode ) { - Q_strncpyz( text, CG_Argv( 1 ), MAX_SAY_TEXT ); - if( Q_stricmpn( text, "[skipnotify]", 12 ) ) - { - if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + case SAY_TEAM: + case SAY_AREA: + case SAY_TPRIVMSG: + if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + { trap_S_StartLocalSound( cgs.media.alienTalkSound, CHAN_LOCAL_SOUND ); - else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + break; + } + else if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + { trap_S_StartLocalSound( cgs.media.humanTalkSound, CHAN_LOCAL_SOUND ); - else - trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); - } - CG_RemoveChatEscapeChar( text ); - CG_Printf( "%s\n", text ); - return; + break; + } + default: + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); } +} - if( !strcmp( cmd, "scores" ) ) - { - CG_ParseScores( ); - return; - } +/* +================= +CG_VoiceTrack - if( !strcmp( cmd, "tinfo" ) ) +return the voice indexed voice track or print errors quietly to console +in case someone is on an unpure server and wants to know which voice pak +is missing or incomplete +================= +*/ +static voiceTrack_t *CG_VoiceTrack( char *voice, int cmd, int track ) +{ + voice_t *v; + voiceCmd_t *c; + voiceTrack_t *t; + + v = BG_VoiceByName( cgs.voices, voice ); + if( !v ) { - CG_ParseTeamInfo( ); - return; + CG_Printf( "[skipnotify]WARNING: could not find voice \"%s\"\n", voice ); + return NULL; } - - if( !strcmp( cmd, "map_restart" ) ) + c = BG_VoiceCmdByNum( v->cmds, cmd ); + if( !c ) { - CG_MapRestart( ); - return; + CG_Printf( "[skipnotify]WARNING: could not find command %d " + "in voice \"%s\"\n", cmd, voice ); + return NULL; } - - if( Q_stricmp( cmd, "remapShader" ) == 0 ) + t = BG_VoiceTrackByNum( c->tracks, track ); + if( !t ) { - if( trap_Argc( ) == 4 ) - trap_R_RemapShader( CG_Argv( 1 ), CG_Argv( 2 ), CG_Argv( 3 ) ); + CG_Printf( "[skipnotify]WARNING: could not find track %d for command %d in " + "voice \"%s\"\n", track, cmd, voice ); + return NULL; } + return t; +} - // clientLevelShot is sent before taking a special screenshot for - // the menu system during development - if( !strcmp( cmd, "clientLevelShot" ) ) - { - cg.levelShot = qtrue; +/* +================= +CG_ParseVoice + +voice clientNum vChan cmdNum trackNum [sayText] +================= +*/ +static void CG_ParseVoice( void ) +{ + int clientNum; + int vChan; + char sayText[ MAX_SAY_TEXT] = {""}; + voiceTrack_t *track; + clientInfo_t *ci; + + if( trap_Argc() < 5 || trap_Argc() > 6 ) return; - } - //the server has triggered a menu - if( !strcmp( cmd, "servermenu" ) ) - { - if( trap_Argc( ) == 2 && !cg.demoPlayback ) - CG_Menu( atoi( CG_Argv( 1 ) ) ); + if( trap_Argc() == 6 ) + Q_strncpyz( sayText, CG_Argv( 5 ), sizeof( sayText ) ); + clientNum = atoi( CG_Argv( 1 ) ); + if( clientNum < 0 || clientNum >= MAX_CLIENTS ) return; - } - //the server thinks this client should close all menus - if( !strcmp( cmd, "serverclosemenus" ) ) - { - trap_SendConsoleCommand( "closemenus\n" ); + vChan = atoi( CG_Argv( 2 ) ); + if( vChan < 0 || vChan >= VOICE_CHAN_NUM_CHANS ) return; - } - //poison cloud effect needs to be reliable - if( !strcmp( cmd, "poisoncloud" ) ) - { - cg.poisonedTime = cg.time; + if( cg_teamChatsOnly.integer && vChan != VOICE_CHAN_TEAM ) + return; - if( CG_IsParticleSystemValid( &cg.poisonCloudPS ) ) - { - cg.poisonCloudPS = CG_SpawnNewParticleSystem( cgs.media.poisonCloudPS ); - CG_SetAttachmentCent( &cg.poisonCloudPS->attachment, &cg.predictedPlayerEntity ); - CG_AttachToCent( &cg.poisonCloudPS->attachment ); - } + ci = &cgs.clientinfo[ clientNum ]; + // this joker is still talking + if( ci->voiceTime > cg.time ) return; - } - if( !strcmp( cmd, "weaponswitch" ) ) + track = CG_VoiceTrack( ci->voice, atoi( CG_Argv( 3 ) ), atoi( CG_Argv( 4 ) ) ); + + // keep track of how long the player will be speaking + // assume it takes 3s to say "*unintelligible gibberish*" + if( track ) + ci->voiceTime = cg.time + track->duration; + else + ci->voiceTime = cg.time + 3000; + + if( !sayText[ 0 ] ) { - CG_Printf( "client weaponswitch\n" ); - if( trap_Argc( ) == 2 ) + if( track ) + Q_strncpyz( sayText, track->text, sizeof( sayText ) ); + else + Q_strncpyz( sayText, "*unintelligible gibberish*", sizeof( sayText ) ); + } + + if( !cg_noVoiceText.integer ) + { + switch( vChan ) { - cg.weaponSelect = atoi( CG_Argv( 1 ) ); - cg.weaponSelectTime = cg.time; + case VOICE_CHAN_ALL: + CG_Say( clientNum, SAY_ALL, sayText ); + break; + case VOICE_CHAN_TEAM: + CG_Say( clientNum, SAY_TEAM, sayText ); + break; + default: + break; } + } + // playing voice audio tracks disabled + if( cg_noVoiceChats.integer ) return; - } - // server requests a ptrc - if( !strcmp( cmd, "ptrcrequest" ) ) + // no audio track to play + if( !track ) + return; + + switch( vChan ) { - int code = CG_ReadPTRCode( ); + case VOICE_CHAN_ALL: + trap_S_StartLocalSound( track->track, CHAN_VOICE ); + break; + case VOICE_CHAN_TEAM: + trap_S_StartLocalSound( track->track, CHAN_VOICE ); + break; + case VOICE_CHAN_LOCAL: + trap_S_StartSound( NULL, clientNum, CHAN_VOICE, track->track ); + break; + default: + break; + } +} - trap_SendClientCommand( va( "ptrcverify %d", code ) ); - return; - } +/* +================= +CG_CenterPrint_f +================= +*/ +void CG_CenterPrint_f( void ) +{ + CG_CenterPrint( CG_Argv( 1 ), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); +} + +/* +================= +CG_Print_f +================= +*/ +static void CG_Print_f( void ) +{ + CG_Printf( "%s", CG_Argv( 1 ) ); +} + +/* +================= +CG_Chat_f +================= +*/ +static void CG_Chat_f( void ) +{ + char id[ 3 ]; + char mode[ 3 ]; + + trap_Argv( 1, id, sizeof( id ) ); + trap_Argv( 2, mode, sizeof( mode ) ); + + CG_Say( atoi( id ), atoi( mode ), CG_Argv( 3 ) ); +} - // server issues a ptrc - if( !strcmp( cmd, "ptrcissue" ) ) +/* +================= +CG_ServerMenu_f +================= +*/ +static void CG_ServerMenu_f( void ) +{ + if( !cg.demoPlayback ) { if( trap_Argc( ) == 2 ) - { - int code = atoi( CG_Argv( 1 ) ); + CG_Menu( atoi( CG_Argv( 1 ) ), 0 ); + else if( trap_Argc( ) == 3 ) + CG_Menu( atoi( CG_Argv( 1 ) ), atoi( CG_Argv( 2 ) ) ); + } +} - CG_WritePTRCode( code ); - } +/* +================= +CG_ServerCloseMenus_f +================= +*/ +static void CG_ServerCloseMenus_f( void ) +{ + trap_SendConsoleCommand( "closemenus\n" ); +} - return; +/* +================= +CG_PoisonCloud_f +================= +*/ +static void CG_PoisonCloud_f( void ) +{ + cg.poisonedTime = cg.time; + + if( CG_IsParticleSystemValid( &cg.poisonCloudPS ) ) + { + cg.poisonCloudPS = CG_SpawnNewParticleSystem( cgs.media.poisonCloudPS ); + CG_SetAttachmentCent( &cg.poisonCloudPS->attachment, &cg.predictedPlayerEntity ); + CG_AttachToCent( &cg.poisonCloudPS->attachment ); } +} + +static char registeredCmds[ 8192 ]; // cmd1\0cmd2\0cmdn\0\0 +static size_t gcmdsOffset; +static void CG_GameCmds_f( void ) +{ + int i; + int c = trap_Argc( ); + const char *cmd; + size_t len; - // reply to ptrcverify - if( !strcmp( cmd, "ptrcconfirm" ) ) + for( i = 1; i < c; i++ ) { - trap_SendConsoleCommand( "menu ptrc_popmenu\n" ); + cmd = CG_Argv( i ); + len = strlen( cmd ) + 1; + if( len + gcmdsOffset >= sizeof( registeredCmds ) - 1 ) + { + CG_Printf( "AddCommand: too many commands (%d >= %d)\n", + (int)( len + gcmdsOffset ), (int)( sizeof( registeredCmds ) - 1 ) ); + return; + } + trap_AddCommand( cmd ); + strcpy( registeredCmds + gcmdsOffset, cmd ); + gcmdsOffset += len; + } +} +void CG_UnregisterCommands( void ) +{ + size_t len, offset = 0; + while( registeredCmds[ offset ] ) + { + len = strlen( registeredCmds + offset ); + trap_RemoveCommand( registeredCmds + offset ); + offset += len + 1; + } + memset( registeredCmds, 0, 2 ); + gcmdsOffset = 0; +} + +static consoleCommand_t svcommands[ ] = +{ + { "chat", CG_Chat_f }, + { "cmds", CG_GameCmds_f }, + { "cp", CG_CenterPrint_f }, + { "cs", CG_ConfigStringModified }, + { "map_restart", CG_MapRestart }, + { "poisoncloud", CG_PoisonCloud_f }, + { "print", CG_Print_f }, + { "scores", CG_ParseScores }, + { "serverclosemenus", CG_ServerCloseMenus_f }, + { "servermenu", CG_ServerMenu_f }, + { "tinfo", CG_ParseTeamInfo }, + { "voice", CG_ParseVoice } +}; + +/* +================= +CG_ServerCommand + +The string has been tokenized and can be retrieved with +Cmd_Argc() / Cmd_Argv() +================= +*/ +static void CG_ServerCommand( void ) +{ + const char *cmd; + consoleCommand_t *command; + + cmd = CG_Argv( 0 ); + command = bsearch( cmd, svcommands, ARRAY_LEN( svcommands ), + sizeof( svcommands[ 0 ] ), cmdcmp ); + + if( command ) + { + command->function( ); return; } diff --git a/src/cgame/cg_snapshot.c b/src/cgame/cg_snapshot.c index 84b419f..3316abb 100644 --- a/src/cgame/cg_snapshot.c +++ b/src/cgame/cg_snapshot.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,15 +17,14 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_snapshot.c -- things that happen on snapshot transition, // not necessarily every single rendered frame - #include "cg_local.h" /* @@ -41,8 +41,8 @@ static void CG_ResetEntity( centity_t *cent ) cent->trailTime = cg.snap->serverTime; - VectorCopy( cent->currentState.origin, cent->lerpOrigin ); - VectorCopy( cent->currentState.angles, cent->lerpAngles ); + VectorCopy( cent->currentState.pos.trBase, cent->lerpOrigin ); + VectorCopy( cent->currentState.apos.trBase, cent->lerpAngles ); if( cent->currentState.eType == ET_PLAYER ) CG_ResetPlayerEntity( cent ); @@ -142,9 +142,6 @@ static void CG_TransitionSnapshot( void ) // execute any server string commands before transitioning entities CG_ExecuteNewServerCommands( cg.nextSnap->serverCommandSequence ); - // if we had a map_restart, set everthing with initial - if( !cg.snap ) { } //TA: ? - // clear the currentValid flag for all entities in the existing snapshot for( i = 0; i < cg.snap->numEntities; i++ ) { @@ -311,7 +308,7 @@ static snapshot_t *CG_ReadNextSnapshot( void ) if( cg.latestSnapshotNum > cgs.processedSnapshotNum + 1000 ) { - CG_Printf( "WARNING: CG_ReadNextSnapshot: way out of range, %i > %i", + CG_Printf( "WARNING: CG_ReadNextSnapshot: way out of range, %i > %i\n", cg.latestSnapshotNum, cgs.processedSnapshotNum ); } @@ -461,4 +458,3 @@ void CG_ProcessSnapshots( void ) if( cg.nextSnap != NULL && cg.nextSnap->serverTime <= cg.time ) CG_Error( "CG_ProcessSnapshots: cg.nextSnap->serverTime <= cg.time" ); } - diff --git a/src/cgame/cg_syscalls.asm b/src/cgame/cg_syscalls.asm index 2537c91..3b8ed01 100644 --- a/src/cgame/cg_syscalls.asm +++ b/src/cgame/cg_syscalls.asm @@ -12,7 +12,7 @@ equ trap_Argv -9 equ trap_Args -10 equ trap_FS_FOpenFile -11 equ trap_FS_Read -12 -equ trap_FS_Write -13 +equ trap_FS_Write -13 equ trap_FS_FCloseFile -14 equ trap_SendConsoleCommand -15 equ trap_AddCommand -16 @@ -106,6 +106,7 @@ equ trap_Key_SetOverstrikeMode -102 equ trap_Key_GetOverstrikeMode -103 equ trap_S_SoundDuration -104 +equ trap_Field_CompleteList -105 equ memset -201 equ memcpy -202 @@ -118,4 +119,3 @@ equ floor -208 equ ceil -209 equ testPrintInt -210 equ testPrintFloat -211 - diff --git a/src/cgame/cg_syscalls.c b/src/cgame/cg_syscalls.c index aa42019..6b81d64 100644 --- a/src/cgame/cg_syscalls.c +++ b/src/cgame/cg_syscalls.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,15 +17,14 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_syscalls.c -- this file is only included when building a dll // cg_syscalls.asm is included instead when building a qvm - #include "cg_local.h" static intptr_t (QDECL *syscall)( intptr_t arg, ... ) = (intptr_t (QDECL *)( intptr_t, ...))-1; @@ -38,9 +38,9 @@ Q_EXPORT void dllEntry( intptr_t (QDECL *syscallptr)( intptr_t arg,... ) ) int PASSFLOAT( float x ) { - float floatTemp; - floatTemp = x; - return *(int *)&floatTemp; + floatint_t fi; + fi.f = x; + return fi.i; } void trap_Print( const char *fmt ) @@ -51,6 +51,8 @@ void trap_Print( const char *fmt ) void trap_Error( const char *fmt ) { syscall( CG_ERROR, fmt ); + // shut up GCC warning about returning functions, because we know better + exit(1); } int trap_Milliseconds( void ) @@ -98,7 +100,7 @@ void trap_LiteralArgs( char *buffer, int bufferLength ) syscall( CG_LITERAL_ARGS, buffer, bufferLength ); } -int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ) +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, enum FS_Mode mode ) { return syscall( CG_FS_FOPENFILE, qpath, f, mode ); } @@ -118,7 +120,7 @@ void trap_FS_FCloseFile( fileHandle_t f ) syscall( CG_FS_FCLOSEFILE, f ); } -void trap_FS_Seek( fileHandle_t f, long offset, fsOrigin_t origin ) +void trap_FS_Seek( fileHandle_t f, long offset, enum FS_Origin origin ) { syscall( CG_FS_SEEK, f, offset, origin ); } @@ -290,6 +292,13 @@ sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ) return syscall( CG_S_REGISTERSOUND, sample, compressed ); } +#ifndef MODULE_INTERFACE_11 +int trap_S_SoundDuration( sfxHandle_t handle ) +{ + return syscall( CG_S_SOUNDDURATION, handle ); +} +#endif + void trap_S_StartBackgroundTrack( const char *intro, const char *loop ) { syscall( CG_S_STARTBACKGROUNDTRACK, intro, loop ); @@ -340,6 +349,11 @@ void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t * syscall( CG_R_ADDPOLYTOSCENE, hShader, numVerts, verts ); } +qboolean trap_R_inPVS( const vec3_t p1, const vec3_t p2 ) +{ + return syscall( CG_R_INPVS, p1, p2 ); +} + void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num ) { syscall( CG_R_ADDPOLYSTOSCENE, hShader, numVerts, verts, num ); @@ -370,6 +384,13 @@ void trap_R_SetColor( const float *rgba ) syscall( CG_R_SETCOLOR, rgba ); } +#ifndef MODULE_INTERFACE_11 +void trap_R_SetClipRegion( const float *region ) +{ + syscall( CG_R_SETCLIPREGION, region ); +} +#endif + void trap_R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ) { @@ -572,3 +593,16 @@ void trap_Key_SetBinding( int keynum, const char *binding ) { syscall( CG_KEY_SETBINDING, keynum, binding ); } +#ifndef MODULE_INTERFACE_11 +void trap_Key_SetOverstrikeMode( qboolean state ) { + syscall( CG_KEY_SETOVERSTRIKEMODE, state ); +} + +qboolean trap_Key_GetOverstrikeMode( void ) { + return syscall( CG_KEY_GETOVERSTRIKEMODE ); +} + +void trap_Field_CompleteList( char *listJson ) { + syscall( CG_FIELD_COMPLETELIST, listJson ); +} +#endif diff --git a/src/cgame/cg_syscalls_11.asm b/src/cgame/cg_syscalls_11.asm index 0893ebc..13c262c 100644 --- a/src/cgame/cg_syscalls_11.asm +++ b/src/cgame/cg_syscalls_11.asm @@ -12,7 +12,7 @@ equ trap_Argv -9 equ trap_Args -10 equ trap_FS_FOpenFile -11 equ trap_FS_Read -12 -equ trap_FS_Write -13 +equ trap_FS_Write -13 equ trap_FS_FCloseFile -14 equ trap_SendConsoleCommand -15 equ trap_AddCommand -16 @@ -112,4 +112,3 @@ equ floor -208 equ ceil -209 equ testPrintInt -210 equ testPrintFloat -211 - diff --git a/src/cgame/cg_trails.c b/src/cgame/cg_trails.c index c8943ed..c7d2662 100644 --- a/src/cgame/cg_trails.c +++ b/src/cgame/cg_trails.c @@ -1,12 +1,13 @@ /* =========================================================================== -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -15,14 +16,13 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_trails.c -- the trail system - #include "cg_local.h" static baseTrailSystem_t baseTrailSystems[ MAX_BASETRAIL_SYSTEMS ]; @@ -60,7 +60,7 @@ static void CG_CalculateBeamNodeProperties( trailBeam_t *tb ) if( ts->destroyTime > 0 && btb->fadeOutTime ) { - fadeAlpha -= ( cg.time - ts->destroyTime ) / btb->fadeOutTime; + fadeAlpha -= (float)( cg.time - ts->destroyTime ) / btb->fadeOutTime; if( fadeAlpha < 0.0f ) fadeAlpha = 0.0f; @@ -559,7 +559,7 @@ static void CG_UpdateBeam( trailBeam_t *tb ) tb->lastEvalTime = cg.time; // first make sure this beam has enough nodes - if( ts->destroyTime <= 0 ) + if( ts->destroyTime <= 0 || btb->fadeOutTime > 0 ) { nodesToAdd = btb->numSegments - CG_CountBeamNodes( tb ) + 1; @@ -898,7 +898,7 @@ static qboolean CG_ParseTrailBeam( baseTrailBeam_t *btb, char **text_p ) { if( btb->numJitters == MAX_TRAIL_BEAM_JITTERS ) { - CG_Printf( S_COLOR_RED "ERROR: too many jitters\n", token ); + CG_Printf( S_COLOR_RED "ERROR: too many jitters\n" ); break; } @@ -1009,6 +1009,15 @@ static qboolean CG_ParseTrailSystem( baseTrailSystem_t *bts, char **text_p, cons } else if( !Q_stricmp( token, "thirdPersonOnly" ) ) bts->thirdPersonOnly = qtrue; + else if( !Q_stricmp( token, "lifeTime" ) ) + { + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + bts->lifeTime = atoi_neg( token, qfalse ); + continue; + } else if( !Q_stricmp( token, "beam" ) ) //acceptable text continue; else if( !Q_stricmp( token, "}" ) ) @@ -1051,9 +1060,11 @@ static qboolean CG_ParseTrailFile( const char *fileName ) if( len <= 0 ) return qfalse; - if( len >= sizeof( text ) - 1 ) + if( len == 0 || len >= sizeof( text ) - 1 ) { - CG_Printf( S_COLOR_RED "ERROR: trail file %s too long\n", fileName ); + trap_FS_FCloseFile( f ); + CG_Printf( S_COLOR_RED "ERROR: trail file %s is %s\n", fileName, + len == 0 ? "empty" : "too long" ); return qfalse; } @@ -1251,11 +1262,14 @@ static trailBeam_t *CG_SpawnNewTrailBeam( baseTrailBeam_t *btb, if( cg_debugTrails.integer >= 1 ) CG_Printf( "TB %s created\n", ts->class->name ); - break; + return tb; } } - return tb; + if( cg_debugTrails.integer >= 1 ) + CG_Printf( "MAX_TRAIL_BEAMS\n" ); + + return NULL; } @@ -1291,18 +1305,22 @@ trailSystem_t *CG_SpawnNewTrailSystem( qhandle_t psHandle ) ts->valid = qtrue; ts->destroyTime = -1; - + ts->birthTime = cg.time; + for( j = 0; j < bts->numBeams; j++ ) CG_SpawnNewTrailBeam( bts->beams[ j ], ts ); if( cg_debugTrails.integer >= 1 ) CG_Printf( "TS %s created\n", bts->name ); - break; + return ts; } } - return ts; + if( cg_debugTrails.integer >= 1 ) + CG_Printf( "MAX_TRAIL_SYSTEMS\n" ); + + return NULL; } /* @@ -1406,6 +1424,19 @@ static void CG_GarbageCollectTrailSystems( void ) CG_DestroyTrailSystem( &tempTS ); } + // lifetime expired + if( ts->destroyTime <= 0 && ts->class->lifeTime && + ts->birthTime + ts->class->lifeTime < cg.time ) + { + trailSystem_t *tempTS = ts; + + CG_DestroyTrailSystem( &tempTS ); + if( cg_debugTrails.integer >= 1 ) + CG_Printf( "TS %s expired (born %d, lives %d, now %d)\n", + ts->class->name, ts->birthTime, ts->class->lifeTime, + cg.time ); + } + if( cg_debugTrails.integer >= 1 && !ts->valid ) CG_Printf( "TS %s garbage collected\n", ts->class->name ); } diff --git a/src/cgame/cg_tutorial.c b/src/cgame/cg_tutorial.c index c418726..e9dbcd6 100644 --- a/src/cgame/cg_tutorial.c +++ b/src/cgame/cg_tutorial.c @@ -1,12 +1,13 @@ /* =========================================================================== -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -15,8 +16,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -35,7 +36,8 @@ static bind_t bindings[ ] = { { "+button2", "Activate Upgrade", { -1, -1 } }, { "+speed", "Run/Walk", { -1, -1 } }, - { "boost", "Sprint", { -1, -1 } }, + { "+button6", "Dodge", { -1, -1 } }, + { "+button8", "Sprint", { -1, -1 } }, { "+moveup", "Jump", { -1, -1 } }, { "+movedown", "Crouch", { -1, -1 } }, { "+attack", "Primary Attack", { -1, -1 } }, @@ -49,7 +51,7 @@ static bind_t bindings[ ] = { "weapnext", "Next Upgrade", { -1, -1 } } }; -static const int numBindings = sizeof( bindings ) / sizeof( bind_t ); +static const size_t numBindings = ARRAY_LEN( bindings ); /* ================= @@ -122,9 +124,8 @@ static const char *CG_KeyNameForCommand( const char *command ) } else { - Q_strncpyz( buffer, va( "\"%s\"", bindings[ i ].humanName ), - MAX_STRING_CHARS ); - Q_strcat( buffer, MAX_STRING_CHARS, " (unbound)" ); + Com_sprintf( buffer, MAX_STRING_CHARS, "\"%s\" (unbound)", + bindings[ i ].humanName ); } return buffer; @@ -157,12 +158,12 @@ static entityState_t *CG_BuildableInRange( playerState_t *ps, float *healthFract if( healthFraction ) { - health = es->generic1 & B_HEALTH_MASK; - *healthFraction = (float)health / B_HEALTH_MASK; + health = es->misc; + *healthFraction = (float)health / BG_Buildable( es->modelindex )->health; } if( es->eType == ET_BUILDABLE && - ps->stats[ STAT_PTEAM ] == BG_FindTeamForBuildable( es->modelindex ) ) + ps->stats[ STAT_TEAM ] == BG_Buildable( es->modelindex )->team ) return es; else return NULL; @@ -183,61 +184,61 @@ static void CG_AlienBuilderText( char *text, playerState_t *ps ) Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to place the %s\n", CG_KeyNameForCommand( "+attack" ), - BG_FindHumanNameForBuildable( buildable ) ) ); + BG_Buildable( buildable )->humanName ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to cancel placing the %s\n", CG_KeyNameForCommand( "+button5" ), - BG_FindHumanNameForBuildable( buildable ) ) ); + BG_Buildable( buildable )->humanName ) ); } else { Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to build a structure\n", CG_KeyNameForCommand( "+attack" ) ) ); + } - if( ( es = CG_BuildableInRange( ps, NULL ) ) ) + if( ( es = CG_BuildableInRange( ps, NULL ) ) ) + { + if( cgs.markDeconstruct ) { - if( cgs.markDeconstruct ) + if( es->eFlags & EF_B_MARKED ) { - if( es->generic1 & B_MARKED_TOGGLEBIT ) - { - Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s to unmark this structure\n", - CG_KeyNameForCommand( "deconstruct" ) ) ); - } - else - { - Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s to mark this structure\n", - CG_KeyNameForCommand( "deconstruct" ) ) ); - } + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to unmark this structure for replacement\n", + CG_KeyNameForCommand( "deconstruct" ) ) ); } else { Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s to destroy this structure\n", + va( "Press %s to mark this structure for replacement\n", CG_KeyNameForCommand( "deconstruct" ) ) ); } } - } - - if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG ) - { - if( ( ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) == BA_NONE ) + else { Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s to swipe\n", - CG_KeyNameForCommand( "+button5" ) ) ); + va( "Press %s to destroy this structure\n", + CG_KeyNameForCommand( "deconstruct" ) ) ); } + } + if( ( ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) == BA_NONE ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to swipe\n", + CG_KeyNameForCommand( "+button5" ) ) ); + } + + if( ps->stats[ STAT_CLASS ] == PCL_ALIEN_BUILDER0_UPG ) + { Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to launch a projectile\n", - CG_KeyNameForCommand( "+button2" ) ) ); + CG_KeyNameForCommand( "+button2" ) ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to walk on walls\n", - CG_KeyNameForCommand( "+movedown" ) ) ); + CG_KeyNameForCommand( "+movedown" ) ) ); } } @@ -249,7 +250,7 @@ CG_AlienLevel0Text static void CG_AlienLevel0Text( char *text, playerState_t *ps ) { Q_strcat( text, MAX_TUTORIAL_TEXT, - "Touch a human to damage it\n" ); + "Touch humans to damage them\n" ); Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to walk on walls\n", @@ -264,13 +265,13 @@ CG_AlienLevel1Text static void CG_AlienLevel1Text( char *text, playerState_t *ps ) { Q_strcat( text, MAX_TUTORIAL_TEXT, - "Touch a human to grab it\n" ); + "Touch humans to grab them\n" ); Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to swipe\n", CG_KeyNameForCommand( "+attack" ) ) ); - if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL1_UPG ) + if( ps->stats[ STAT_CLASS ] == PCL_ALIEN_LEVEL1_UPG ) { Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to spray poisonous gas\n", @@ -293,7 +294,7 @@ static void CG_AlienLevel2Text( char *text, playerState_t *ps ) va( "Press %s to bite\n", CG_KeyNameForCommand( "+attack" ) ) ); - if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL2_UPG ) + if( ps->stats[ STAT_CLASS ] == PCL_ALIEN_LEVEL2_UPG ) { Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to invoke an electrical attack\n", @@ -316,7 +317,7 @@ static void CG_AlienLevel3Text( char *text, playerState_t *ps ) va( "Press %s to bite\n", CG_KeyNameForCommand( "+attack" ) ) ); - if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL3_UPG ) + if( ps->stats[ STAT_CLASS ] == PCL_ALIEN_LEVEL3_UPG ) { Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to launch a projectile\n", @@ -340,7 +341,7 @@ static void CG_AlienLevel4Text( char *text, playerState_t *ps ) CG_KeyNameForCommand( "+attack" ) ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Hold down and release %s to charge\n", + va( "Hold down and release %s to trample\n", CG_KeyNameForCommand( "+button5" ) ) ); } @@ -351,36 +352,47 @@ CG_HumanCkitText */ static void CG_HumanCkitText( char *text, playerState_t *ps ) { - buildable_t buildable = ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT; - float health; + buildable_t buildable = ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT; + entityState_t *es; if( buildable > BA_NONE ) { Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to place the %s\n", CG_KeyNameForCommand( "+attack" ), - BG_FindHumanNameForBuildable( buildable ) ) ); + BG_Buildable( buildable )->humanName ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to cancel placing the %s\n", CG_KeyNameForCommand( "+button5" ), - BG_FindHumanNameForBuildable( buildable ) ) ); + BG_Buildable( buildable )->humanName ) ); } else { Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to build a structure\n", CG_KeyNameForCommand( "+attack" ) ) ); + } - if( CG_BuildableInRange( ps, &health ) ) + if( ( es = CG_BuildableInRange( ps, NULL ) ) ) + { + if( cgs.markDeconstruct ) { - if( health < 1.0f ) + if( es->eFlags & EF_B_MARKED ) { Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Hold %s to repair this structure\n", - CG_KeyNameForCommand( "+button5" ) ) ); + va( "Press %s to unmark this structure\n", + CG_KeyNameForCommand( "deconstruct" ) ) ); } - + else + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to mark this structure\n", + CG_KeyNameForCommand( "deconstruct" ) ) ); + } + } + else + { Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to destroy this structure\n", CG_KeyNameForCommand( "deconstruct" ) ) ); @@ -396,21 +408,17 @@ CG_HumanText static void CG_HumanText( char *text, playerState_t *ps ) { char *name; - int ammo, clips; upgrade_t upgrade = UP_NONE; - if( cg.weaponSelect <= 32 ) + if( cg.weaponSelect < 32 ) name = cg_weapons[ cg.weaponSelect ].humanName; - else if( cg.weaponSelect > 32 ) + else { name = cg_upgrades[ cg.weaponSelect - 32 ].humanName; upgrade = cg.weaponSelect - 32; } - ammo = ps->ammo; - clips = ps->clips; - - if( !ammo && !clips && !BG_FindInfinteAmmoForWeapon( ps->weapon ) ) + if( !ps->ammo && !ps->clips && !BG_Weapon( ps->weapon )->infiniteAmmo ) { //no ammo switch( ps->weapon ) @@ -429,7 +437,7 @@ static void CG_HumanText( char *text, playerState_t *ps ) case WP_MASS_DRIVER: case WP_LUCIFER_CANNON: Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Find a Reactor or Repeater and press %s for more ammo\n", + va( "Find an Armoury, Reactor, or Repeater and press %s for more ammo\n", CG_KeyNameForCommand( "buy ammo" ) ) ); break; @@ -451,14 +459,14 @@ static void CG_HumanText( char *text, playerState_t *ps ) Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to fire the %s\n", CG_KeyNameForCommand( "+attack" ), - BG_FindHumanNameForWeapon( ps->weapon ) ) ); + BG_Weapon( ps->weapon )->humanName ) ); break; case WP_MASS_DRIVER: Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to fire the %s\n", CG_KeyNameForCommand( "+attack" ), - BG_FindHumanNameForWeapon( ps->weapon ) ) ); + BG_Weapon( ps->weapon )->humanName ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Hold %s to zoom\n", @@ -469,7 +477,7 @@ static void CG_HumanText( char *text, playerState_t *ps ) Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Hold %s to activate the %s\n", CG_KeyNameForCommand( "+attack" ), - BG_FindHumanNameForWeapon( ps->weapon ) ) ); + BG_Weapon( ps->weapon )->humanName ) ); break; case WP_LUCIFER_CANNON: @@ -480,11 +488,10 @@ static void CG_HumanText( char *text, playerState_t *ps ) Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to fire the %s\n", CG_KeyNameForCommand( "+button5" ), - BG_FindHumanNameForWeapon( ps->weapon ) ) ); + BG_Weapon( ps->weapon )->humanName ) ); break; case WP_HBUILD: - case WP_HBUILD2: CG_HumanCkitText( text, ps ); break; @@ -501,7 +508,7 @@ static void CG_HumanText( char *text, playerState_t *ps ) CG_KeyNameForCommand( "weapnext" ) ) ); if( upgrade == UP_NONE || - ( upgrade > UP_NONE && BG_FindUsableForUpgrade( upgrade ) ) ) + ( upgrade > UP_NONE && BG_Upgrade( upgrade )->usable ) ) { Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to use the %s\n", @@ -515,16 +522,52 @@ static void CG_HumanText( char *text, playerState_t *ps ) Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to use your %s\n", CG_KeyNameForCommand( "itemact medkit" ), - BG_FindHumanNameForUpgrade( UP_MEDKIT ) ) ); + BG_Upgrade( UP_MEDKIT )->humanName ) ); + } + + if( ps->stats[ STAT_STAMINA ] <= STAMINA_BLACKOUT_LEVEL ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + "You are blacking out. Stop sprinting to recover stamina\n" ); + } + else if( ps->stats[ STAT_STAMINA ] <= STAMINA_SLOW_LEVEL ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + "Your stamina is low. Stop sprinting to recover\n" ); + } + + switch( cg.nearUsableBuildable ) + { + case BA_H_ARMOURY: + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to buy equipment upgrades at the %s. Sell your old weapon first!\n", + CG_KeyNameForCommand( "+button7" ), + BG_Buildable( cg.nearUsableBuildable )->humanName ) ); + break; + case BA_H_REPEATER: + case BA_H_REACTOR: + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to refill your energy weapon's ammo at the %s\n", + CG_KeyNameForCommand( "+button7" ), + BG_Buildable( cg.nearUsableBuildable )->humanName ) ); + break; + case BA_NONE: + break; + default: + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to use the %s\n", + CG_KeyNameForCommand( "+button7" ), + BG_Buildable( cg.nearUsableBuildable )->humanName ) ); + break; } Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s to use a structure\n", - CG_KeyNameForCommand( "+button7" ) ) ); + va( "Press %s and any direction to sprint\n", + CG_KeyNameForCommand( "+button8" ) ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s to sprint\n", - CG_KeyNameForCommand( "boost" ) ) ); + va( "Press %s and back or strafe to dodge\n", + CG_KeyNameForCommand( "+button6" ) ) ); } /* @@ -534,37 +577,56 @@ CG_SpectatorText */ static void CG_SpectatorText( char *text, playerState_t *ps ) { - if( cgs.clientinfo[ cg.clientNum ].team != PTE_NONE ) + if( cgs.clientinfo[ cg.clientNum ].team != TEAM_NONE ) { - Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s to spawn\n", CG_KeyNameForCommand( "+attack" ) ) ); + if( ps->pm_flags & PMF_QUEUED ) + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to leave spawn queue\n", + CG_KeyNameForCommand( "+attack" ) ) ); + else + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to spawn\n", + CG_KeyNameForCommand( "+attack" ) ) ); } else { Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s to join a team\n", CG_KeyNameForCommand( "+attack" ) ) ); + va( "Press %s to join a team\n", + CG_KeyNameForCommand( "+attack" ) ) ); } if( ps->pm_flags & PMF_FOLLOW ) { - Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s to stop following\n", - CG_KeyNameForCommand( "+button2" ) ) ); + if( !cg.chaseFollow ) + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to switch to chase-cam spectator mode\n", + CG_KeyNameForCommand( "+button2" ) ) ); + else if( cgs.clientinfo[ cg.clientNum ].team == TEAM_NONE ) + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to return to free spectator mode\n", + CG_KeyNameForCommand( "+button2" ) ) ); + else + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to stop following\n", + CG_KeyNameForCommand( "+button2" ) ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s or ", CG_KeyNameForCommand( "weapprev" ) ) ); + va( "Press %s or ", + CG_KeyNameForCommand( "weapprev" ) ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "%s to change player\n", CG_KeyNameForCommand( "weapnext" ) ) ); + va( "%s to change player\n", + CG_KeyNameForCommand( "weapnext" ) ) ); } else { Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s to follow a %s\n", CG_KeyNameForCommand( "+button2" ), - ( cgs.clientinfo[ cg.clientNum ].team == PTE_NONE ) - ? "player" : "teammate" ) ); + va( "Press %s to follow a player\n", + CG_KeyNameForCommand( "+button2" ) ) ); } } +#define BINDING_REFRESH_INTERVAL 30 + /* =============== CG_TutorialText @@ -576,22 +638,26 @@ const char *CG_TutorialText( void ) { playerState_t *ps; static char text[ MAX_TUTORIAL_TEXT ]; + static int refreshBindings = 0; + + if( refreshBindings == 0 ) + CG_GetBindings( ); - CG_GetBindings( ); + refreshBindings = ( refreshBindings + 1 ) % BINDING_REFRESH_INTERVAL; text[ 0 ] = '\0'; ps = &cg.snap->ps; if( !cg.intermissionStarted && !cg.demoPlayback ) { - if( ps->persistant[ PERS_TEAM ] == TEAM_SPECTATOR || + if( ps->persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT || ps->pm_flags & PMF_FOLLOW ) { CG_SpectatorText( text, ps ); } else if( ps->stats[ STAT_HEALTH ] > 0 ) { - switch( ps->stats[ STAT_PCLASS ] ) + switch( ps->stats[ STAT_CLASS ] ) { case PCL_ALIEN_BUILDER0: case PCL_ALIEN_BUILDER0_UPG: @@ -622,6 +688,7 @@ const char *CG_TutorialText( void ) break; case PCL_HUMAN: + case PCL_HUMAN_BSUIT: CG_HumanText( text, ps ); break; @@ -629,26 +696,11 @@ const char *CG_TutorialText( void ) break; } - if( ps->stats[ STAT_PTEAM ] == PTE_ALIENS ) + if( ps->stats[ STAT_TEAM ] == TEAM_ALIENS ) { - entityState_t *es = CG_BuildableInRange( ps, NULL ); - - if( ps->stats[ STAT_STATE ] & SS_HOVELING ) - { - Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s to exit the hovel\n", - CG_KeyNameForCommand( "+button7" ) ) ); - } - else if( es && es->modelindex == BA_A_HOVEL && - es->generic1 & B_SPAWNED_TOGGLEBIT && - ( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0 || - ps->stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG ) ) - { - Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s to enter the hovel\n", - CG_KeyNameForCommand( "+button7" ) ) ); - } - else if( BG_UpgradeClassAvailable( ps ) ) + if( BG_AlienCanEvolve( ps->stats[ STAT_CLASS ], + ps->persistant[ PERS_CREDIT ], + cgs.alienStage ) ) { Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to evolve\n", @@ -656,7 +708,23 @@ const char *CG_TutorialText( void ) } } } + } + else if( !cg.demoPlayback ) + { + if( !CG_ClientIsReady( ps->clientNum ) ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s when ready to continue\n", + CG_KeyNameForCommand( "+attack" ) ) ); + } + else + { + Q_strcat( text, MAX_TUTORIAL_TEXT, "Waiting for other players to be ready\n" ); + } + } + if( !cg.demoPlayback ) + { Q_strcat( text, MAX_TUTORIAL_TEXT, "Press ESC for the menu" ); } diff --git a/src/cgame/cg_view.c b/src/cgame/cg_view.c index 428f299..871b095 100644 --- a/src/cgame/cg_view.c +++ b/src/cgame/cg_view.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,18 +17,16 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_view.c -- setup all the parameters (position, angle, etc) // for a 3D rendering - #include "cg_local.h" - /* ============================================================================= @@ -181,7 +180,7 @@ static void CG_AddTestModel( void ) return; } - // if testing a gun, set the origin reletive to the view origin + // if testing a gun, set the origin relative to the view origin if( cg.testGun ) { VectorCopy( cg.refdef.vieworg, cg.testModelEntity.origin ); @@ -229,21 +228,7 @@ static void CG_CalcVrect( void ) if( cg.snap->ps.pm_type == PM_INTERMISSION ) size = 100; else - { - // bound normal viewsize - if( cg_viewsize.integer < 30 ) - { - trap_Cvar_Set( "cg_viewsize", "30" ); - size = 30; - } - else if( cg_viewsize.integer > 100 ) - { - trap_Cvar_Set( "cg_viewsize","100" ); - size = 100; - } - else - size = cg_viewsize.integer; - } + size = cg_viewsize.integer; cg.refdef.width = cgs.glconfig.vidWidth * size / 100; cg.refdef.width &= ~1; @@ -257,119 +242,282 @@ static void CG_CalcVrect( void ) //============================================================================== - /* =============== CG_OffsetThirdPersonView =============== */ -#define FOCUS_DISTANCE 512 -static void CG_OffsetThirdPersonView( void ) +void CG_OffsetThirdPersonView( void ) { + int i; vec3_t forward, right, up; vec3_t view; - vec3_t focusAngles; trace_t trace; static vec3_t mins = { -8, -8, -8 }; static vec3_t maxs = { 8, 8, 8 }; vec3_t focusPoint; - float focusDist; - float forwardScale, sideScale; vec3_t surfNormal; - - if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_WALLCLIMBING ) + int cmdNum; + usercmd_t cmd, oldCmd; + float range; + vec3_t mouseInputAngles; + vec3_t rotationAngles; + vec3_t axis[ 3 ], rotaxis[ 3 ]; + float deltaPitch; + static float pitch; + static vec3_t killerPos = { 0, 0, 0 }; + + // If cg_thirdpersonShoulderViewMode == 2, do shoulder view instead + // If cg_thirdpersonShoulderViewMode == 1, do shoulder view when chasing + // a wallwalker because it's really erratic to watch + if( ( cg_thirdPersonShoulderViewMode.integer == 2 ) || + ( ( cg_thirdPersonShoulderViewMode.integer == 1 ) && + ( cg.snap->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING ) && + ( cg.snap->ps.stats[ STAT_HEALTH ] > 0 ) ) ) { - if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) - VectorSet( surfNormal, 0.0f, 0.0f, -1.0f ); - else - VectorCopy( cg.predictedPlayerState.grapplePoint, surfNormal ); + CG_OffsetShoulderView( ); + return; } - else - VectorSet( surfNormal, 0.0f, 0.0f, 1.0f ); + BG_GetClientNormal( &cg.predictedPlayerState, surfNormal ); + // Set the view origin to the class's view height VectorMA( cg.refdef.vieworg, cg.predictedPlayerState.viewheight, surfNormal, cg.refdef.vieworg ); - VectorCopy( cg.refdefViewAngles, focusAngles ); + // Set the focus point where the camera will look (at the player's vieworg) + VectorCopy( cg.refdef.vieworg, focusPoint ); - // if dead, look at killer + // If player is dead, we want the player to be between us and the killer + // so pretend that the player was looking at the killer, then place cam behind them. if( cg.predictedPlayerState.stats[ STAT_HEALTH ] <= 0 ) { - focusAngles[ YAW ] = cg.predictedPlayerState.stats[ STAT_VIEWLOCK ]; - cg.refdefViewAngles[ YAW ] = cg.predictedPlayerState.stats[ STAT_VIEWLOCK ]; + int killerEntNum = cg.predictedPlayerState.stats[ STAT_VIEWLOCK ]; + + // already looking at ourself + if( killerEntNum != cg.snap->ps.clientNum ) + { + vec3_t lookDirection; + if( cg.wasDeadLastFrame == qfalse || !cg_staticDeathCam.integer ) + { + VectorCopy( cg_entities[ killerEntNum ].lerpOrigin, killerPos ); + cg.wasDeadLastFrame = qtrue; + } + VectorSubtract( killerPos, cg.refdef.vieworg, lookDirection ); + vectoangles( lookDirection, cg.refdefViewAngles ); + } } - //if ( focusAngles[PITCH] > 45 ) { - // focusAngles[PITCH] = 45; // don't go too far overhead - //} - AngleVectors( focusAngles, forward, NULL, NULL ); + // get and rangecheck cg_thirdPersonRange + range = cg_thirdPersonRange.value; + if( range > 150.0f ) range = 150.0f; + if( range < 30.0f ) range = 30.0f; + + // Calculate the angle of the camera's position around the player. + // Unless in demo, PLAYING in third person, or in dead-third-person cam, allow the player + // to control camera position offsets using the mouse position. + if( cg.demoPlayback || + ( ( cg.snap->ps.pm_flags & PMF_FOLLOW ) && + ( cg.predictedPlayerState.stats[ STAT_HEALTH ] > 0 ) ) ) + { + // Collect our input values from the mouse. + cmdNum = trap_GetCurrentCmdNumber(); + trap_GetUserCmd( cmdNum, &cmd ); + trap_GetUserCmd( cmdNum - 1, &oldCmd ); - VectorMA( cg.refdef.vieworg, FOCUS_DISTANCE, forward, focusPoint ); + // Prevent pitch from wrapping and clamp it within a [-75, 90] range. + // Cgame has no access to ps.delta_angles[] here, so we need to reproduce + // it ourselves. + deltaPitch = SHORT2ANGLE( cmd.angles[ PITCH ] - oldCmd.angles[ PITCH ] ); + if( fabs(deltaPitch) < 200.0f ) + { + pitch += deltaPitch; + } - VectorCopy( cg.refdef.vieworg, view ); + mouseInputAngles[ PITCH ] = pitch; + mouseInputAngles[ YAW ] = -1.0f * SHORT2ANGLE( cmd.angles[ YAW ] ); // yaw is inverted + mouseInputAngles[ ROLL ] = 0.0f; + + for( i = 0; i < 3; i++ ) + mouseInputAngles[ i ] = AngleNormalize180( mouseInputAngles[ i ] ); - VectorMA( view, 12, surfNormal, view ); + // Set the rotation angles to be the view angles offset by the mouse input + // Ignore the original pitch though; it's too jerky otherwise + if( !cg_thirdPersonPitchFollow.integer ) + cg.refdefViewAngles[ PITCH ] = 0.0f; - //cg.refdefViewAngles[PITCH] *= 0.5; + for( i = 0; i < 3; i++ ) + { + rotationAngles[ i ] = AngleNormalize180(cg.refdefViewAngles[ i ]) + mouseInputAngles[ i ]; + AngleNormalize180( rotationAngles[ i ] ); + } - AngleVectors( cg.refdefViewAngles, forward, right, up ); + // Don't let pitch go too high/too low or the camera flips around and + // that's really annoying. + // However, when we're not on the floor or ceiling (wallwalk) pitch + // may not be pitch, so just let it go. + if( surfNormal[ 2 ] > 0.5f || surfNormal[ 2 ] < -0.5f ) + { + if( rotationAngles[ PITCH ] > 85.0f ) + rotationAngles[ PITCH ] = 85.0f; + else if( rotationAngles[ PITCH ] < -85.0f ) + rotationAngles[ PITCH ] = -85.0f; + } + + // Perform the rotations specified by rotationAngles. + AnglesToAxis( rotationAngles, axis ); + if( !( cg.snap->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING ) || + !BG_RotateAxis( cg.snap->ps.grapplePoint, axis, rotaxis, qfalse, + cg.snap->ps.eFlags & EF_WALLCLIMBCEILING ) ) + AxisCopy( axis, rotaxis ); + + // Convert the new axis back to angles. + AxisToAngles( rotaxis, rotationAngles ); + } + else + { + if( cg.predictedPlayerState.stats[ STAT_HEALTH ] > 0 ) + { + // If we're playing the game in third person, the viewangles already + // take care of our mouselook, so just use them. + for( i = 0; i < 3; i++ ) + rotationAngles[ i ] = cg.refdefViewAngles[ i ]; + } + else // dead + { + rotationAngles[ PITCH ] = 20.0f; + rotationAngles[ YAW ] = cg.refdefViewAngles[ YAW ]; + } + } - forwardScale = cos( cg_thirdPersonAngle.value / 180 * M_PI ); - sideScale = sin( cg_thirdPersonAngle.value / 180 * M_PI ); - VectorMA( view, -cg_thirdPersonRange.value * forwardScale, forward, view ); - VectorMA( view, -cg_thirdPersonRange.value * sideScale, right, view ); + rotationAngles[ YAW ] -= cg_thirdPersonAngle.value; - // trace a ray from the origin to the viewpoint to make sure the view isn't - // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything + // Move the camera range distance back. + AngleVectors( rotationAngles, forward, right, up ); + VectorCopy( cg.refdef.vieworg, view ); + VectorMA( view, -range, forward, view ); + // Ensure that the current camera position isn't out of bounds and that there + // is nothing between the camera and the player. if( !cg_cameraMode.integer ) { + // Trace a ray from the origin to the viewpoint to make sure the view isn't + // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); - if( trace.fraction != 1.0 ) + if( trace.fraction != 1.0f ) { VectorCopy( trace.endpos, view ); - view[ 2 ] += ( 1.0 - trace.fraction ) * 32; - // try another trace to this position, because a tunnel may have the ceiling - // close enogh that this is poking out + view[ 2 ] += ( 1.0f - trace.fraction ) * 32; + // Try another trace to this position, because a tunnel may have the ceiling + // close enogh that this is poking out. CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); VectorCopy( trace.endpos, view ); } } + // Set the camera position to what we calculated. VectorCopy( view, cg.refdef.vieworg ); - // select pitch to look at focus point from vieword - VectorSubtract( focusPoint, cg.refdef.vieworg, focusPoint ); - focusDist = sqrt( focusPoint[ 0 ] * focusPoint[ 0 ] + focusPoint[ 1 ] * focusPoint[ 1 ] ); - if ( focusDist < 1 ) { - focusDist = 1; // should never happen + // The above checks may have moved the camera such that the existing viewangles + // may not still face the player. Recalculate them to do so. + // but if we're dead, don't bother because we'd rather see what killed us + if( cg.predictedPlayerState.stats[ STAT_HEALTH ] > 0 ) + { + VectorSubtract( focusPoint, cg.refdef.vieworg, focusPoint ); + vectoangles( focusPoint, cg.refdefViewAngles ); } - cg.refdefViewAngles[ PITCH ] = -180 / M_PI * atan2( focusPoint[ 2 ], focusDist ); - cg.refdefViewAngles[ YAW ] -= cg_thirdPersonAngle.value; } +/* +=============== +CG_OffsetShoulderView + +=============== +*/ +void CG_OffsetShoulderView( void ) +{ + int i; + int cmdNum; + usercmd_t cmd, oldCmd; + vec3_t rotationAngles; + vec3_t axis[ 3 ], rotaxis[ 3 ]; + float deltaMousePitch; + static float mousePitch; + vec3_t forward, right, up; + classConfig_t* classConfig; + + // Ignore following pitch; it's too jerky otherwise. + if( !cg_thirdPersonPitchFollow.integer ) + cg.refdefViewAngles[ PITCH ] = 0.0f; + + AngleVectors( cg.refdefViewAngles, forward, right, up ); + + classConfig = BG_ClassConfig( cg.snap->ps.stats[ STAT_CLASS ] ); + VectorMA( cg.refdef.vieworg, classConfig->shoulderOffsets[ 0 ], forward, cg.refdef.vieworg ); + VectorMA( cg.refdef.vieworg, classConfig->shoulderOffsets[ 1 ], right, cg.refdef.vieworg ); + VectorMA( cg.refdef.vieworg, classConfig->shoulderOffsets[ 2 ], up, cg.refdef.vieworg ); + + // If someone is playing like this, the rest is already taken care of + // so just get the firstperson effects and leave. + if( !cg.demoPlayback && !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) + { + CG_OffsetFirstPersonView(); + return; + } + + // Get mouse input for camera rotation. + cmdNum = trap_GetCurrentCmdNumber(); + trap_GetUserCmd( cmdNum, &cmd ); + trap_GetUserCmd( cmdNum - 1, &oldCmd ); + + // Prevent pitch from wrapping and clamp it within a [30, -50] range. + // Cgame has no access to ps.delta_angles[] here, so we need to reproduce + // it ourselves here. + deltaMousePitch = SHORT2ANGLE( cmd.angles[ PITCH ] - oldCmd.angles[ PITCH ] ); + if( fabs(deltaMousePitch) < 200.0f ) + mousePitch += deltaMousePitch; + + // Handle pitch. + rotationAngles[ PITCH ] = mousePitch; + + rotationAngles[ PITCH ] = AngleNormalize180( rotationAngles[ PITCH ] + AngleNormalize180( cg.refdefViewAngles[ PITCH ] ) ); + if( rotationAngles [ PITCH ] < -90.0f ) rotationAngles [ PITCH ] = -90.0f; + if( rotationAngles [ PITCH ] > 90.0f ) rotationAngles [ PITCH ] = 90.0f; + + // Yaw and Roll are much easier. + rotationAngles[ YAW ] = SHORT2ANGLE( cmd.angles[ YAW ] ) + cg.refdefViewAngles[ YAW ]; + rotationAngles[ ROLL ] = 0.0f; + + // Perform the rotations. + AnglesToAxis( rotationAngles, axis ); + if( !( cg.snap->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING ) || + !BG_RotateAxis( cg.snap->ps.grapplePoint, axis, rotaxis, qfalse, + cg.snap->ps.eFlags & EF_WALLCLIMBCEILING ) ) + AxisCopy( axis, rotaxis ); + + AxisToAngles( rotaxis, rotationAngles ); + + // Actually set the viewangles. + for( i = 0; i < 3; i++ ) + cg.refdefViewAngles[ i ] = rotationAngles[ i ]; + + // Now run the first person stuff so we get various effects added. + CG_OffsetFirstPersonView( ); +} + // this causes a compiler bug on mac MrC compiler static void CG_StepOffset( void ) { float steptime; - int timeDelta; + int timeDelta; vec3_t normal; playerState_t *ps = &cg.predictedPlayerState; - if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) - { - if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) - VectorSet( normal, 0.0f, 0.0f, -1.0f ); - else - VectorCopy( ps->grapplePoint, normal ); - } - else - VectorSet( normal, 0.0f, 0.0f, 1.0f ); + BG_GetClientNormal( ps, normal ); - steptime = BG_FindSteptimeForClass( ps->stats[ STAT_PCLASS ] ); + steptime = BG_Class( ps->stats[ STAT_CLASS ] )->steptime; // smooth out stair climbing timeDelta = cg.time - cg.stepTime; @@ -378,17 +526,15 @@ static void CG_StepOffset( void ) float stepChange = cg.stepChange * (steptime - timeDelta) / steptime; - if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) - VectorMA( cg.refdef.vieworg, -stepChange, normal, cg.refdef.vieworg ); - else - cg.refdef.vieworg[ 2 ] -= stepChange; + VectorMA( cg.refdef.vieworg, -stepChange, normal, cg.refdef.vieworg ); } } -#define PCLOUD_ROLL_AMPLITUDE 25.0f -#define PCLOUD_ROLL_FREQUENCY 0.4f -#define PCLOUD_ZOOM_AMPLITUDE 15 -#define PCLOUD_ZOOM_FREQUENCY 0.7f +#define PCLOUD_ROLL_AMPLITUDE 25.0f +#define PCLOUD_ROLL_FREQUENCY 0.4f +#define PCLOUD_ZOOM_AMPLITUDE 15 +#define PCLOUD_ZOOM_FREQUENCY 0.625f // 2.5s / 4 +#define PCLOUD_DISORIENT_DURATION 2500 /* @@ -397,7 +543,7 @@ CG_OffsetFirstPersonView =============== */ -static void CG_OffsetFirstPersonView( void ) +void CG_OffsetFirstPersonView( void ) { float *origin; float *angles; @@ -409,19 +555,10 @@ static void CG_OffsetFirstPersonView( void ) vec3_t predictedVelocity; int timeDelta; float bob2; - vec3_t normal, baseOrigin; + vec3_t normal; playerState_t *ps = &cg.predictedPlayerState; - if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) - { - if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) - VectorSet( normal, 0.0f, 0.0f, -1.0f ); - else - VectorCopy( ps->grapplePoint, normal ); - } - else - VectorSet( normal, 0.0f, 0.0f, 1.0f ); - + BG_GetClientNormal( ps, normal ); if( cg.snap->ps.pm_type == PM_INTERMISSION ) return; @@ -429,8 +566,6 @@ static void CG_OffsetFirstPersonView( void ) origin = cg.refdef.vieworg; angles = cg.refdefViewAngles; - VectorCopy( origin, baseOrigin ); - // if dead, fix the angle and don't add any kick if( cg.snap->ps.stats[ STAT_HEALTH ] <= 0 ) { @@ -441,9 +576,6 @@ static void CG_OffsetFirstPersonView( void ) return; } - // add angles based on weapon kick - VectorAdd( angles, cg.kick_angles, angles ); - // add angles based on damage kick if( cg.damageTime ) { @@ -485,10 +617,10 @@ static void CG_OffsetFirstPersonView( void ) // add angles based on bob // bob amount is class dependant - if( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) + if( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) bob2 = 0.0f; else - bob2 = BG_FindBobForClass( cg.predictedPlayerState.stats[ STAT_PCLASS ] ); + bob2 = BG_Class( cg.predictedPlayerState.stats[ STAT_CLASS ] )->bob; #define LEVEL4_FEEDBACK 10.0f @@ -498,7 +630,8 @@ static void CG_OffsetFirstPersonView( void ) { if( ps->stats[ STAT_MISC ] > 0 ) { - float fraction = (float)ps->stats[ STAT_MISC ] / (float)LEVEL4_CHARGE_TIME; + float fraction = (float)ps->stats[ STAT_MISC ] / + LEVEL4_TRAMPLE_CHARGE_MAX; if( fraction > 1.0f ) fraction = 1.0f; @@ -530,26 +663,24 @@ static void CG_OffsetFirstPersonView( void ) #define LEVEL3_FEEDBACK 20.0f //provide some feedback for pouncing - if( cg.predictedPlayerState.weapon == WP_ALEVEL3 || - cg.predictedPlayerState.weapon == WP_ALEVEL3_UPG ) + if( ( cg.predictedPlayerState.weapon == WP_ALEVEL3 || + cg.predictedPlayerState.weapon == WP_ALEVEL3_UPG ) && + cg.predictedPlayerState.stats[ STAT_MISC ] > 0 ) { - if( cg.predictedPlayerState.stats[ STAT_MISC ] > 0 ) - { - float fraction1, fraction2; - vec3_t forward; - - AngleVectors( angles, forward, NULL, NULL ); - VectorNormalize( forward ); + float fraction1, fraction2; + vec3_t forward; - fraction1 = (float)( cg.time - cg.weapon2Time ) / (float)LEVEL3_POUNCE_CHARGE_TIME; + AngleVectors( angles, forward, NULL, NULL ); + VectorNormalize( forward ); - if( fraction1 > 1.0f ) - fraction1 = 1.0f; + fraction1 = (float)cg.predictedPlayerState.stats[ STAT_MISC ] / + LEVEL3_POUNCE_TIME_UPG; + if( fraction1 > 1.0f ) + fraction1 = 1.0f; - fraction2 = -sin( fraction1 * M_PI / 2 ); + fraction2 = -sin( fraction1 * M_PI / 2 ); - VectorMA( origin, LEVEL3_FEEDBACK * fraction2, forward, origin ); - } + VectorMA( origin, LEVEL3_FEEDBACK * fraction2, forward, origin ); } #define STRUGGLE_DIST 5.0f @@ -562,7 +693,6 @@ static void CG_OffsetFirstPersonView( void ) usercmd_t cmd; int cmdNum; float fFraction, rFraction, uFraction; - float fFraction2, rFraction2, uFraction2; cmdNum = trap_GetCurrentCmdNumber(); trap_GetUserCmd( cmdNum, &cmd ); @@ -580,10 +710,6 @@ static void CG_OffsetFirstPersonView( void ) if( uFraction > 1.0f ) uFraction = 1.0f; - fFraction2 = -sin( fFraction * M_PI / 2 ); - rFraction2 = -sin( rFraction * M_PI / 2 ); - uFraction2 = -sin( uFraction * M_PI / 2 ); - if( cmd.forwardmove > 0 ) VectorMA( origin, STRUGGLE_DIST * fFraction, forward, origin ); else if( cmd.forwardmove < 0 ) @@ -606,14 +732,21 @@ static void CG_OffsetFirstPersonView( void ) cg.upMoveTime = cg.time; } - if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_POISONCLOUDED && + if( ( cg.predictedPlayerEntity.currentState.eFlags & EF_POISONCLOUDED ) && + ( cg.time - cg.poisonedTime < PCLOUD_DISORIENT_DURATION) && !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) { - float fraction = sin( ( (float)cg.time / 1000.0f ) * M_PI * 2 * PCLOUD_ROLL_FREQUENCY ); - float pitchFraction = sin( ( (float)cg.time / 1000.0f ) * M_PI * 5 * PCLOUD_ROLL_FREQUENCY ); - - fraction *= 1.0f - ( ( cg.time - cg.poisonedTime ) / (float)LEVEL1_PCLOUD_TIME ); - pitchFraction *= 1.0f - ( ( cg.time - cg.poisonedTime ) / (float)LEVEL1_PCLOUD_TIME ); + float scale, fraction, pitchFraction; + + scale = 1.0f - (float)( cg.time - cg.poisonedTime ) / + BG_PlayerPoisonCloudTime( &cg.predictedPlayerState ); + if( scale < 0.0f ) + scale = 0.0f; + + fraction = sin( ( cg.time - cg.poisonedTime ) / 500.0f * M_PI * PCLOUD_ROLL_FREQUENCY ) * + scale; + pitchFraction = sin( ( cg.time - cg.poisonedTime ) / 200.0f * M_PI * PCLOUD_ROLL_FREQUENCY ) * + scale; angles[ ROLL ] += fraction * PCLOUD_ROLL_AMPLITUDE; angles[ YAW ] += fraction * PCLOUD_ROLL_AMPLITUDE; @@ -621,17 +754,17 @@ static void CG_OffsetFirstPersonView( void ) } // this *feels* more realisitic for humans - if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_HUMANS ) + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_HUMANS && + ( cg.predictedPlayerState.pm_type == PM_NORMAL || + cg.predictedPlayerState.pm_type == PM_JETPACK ) ) { angles[PITCH] += cg.bobfracsin * bob2 * 0.5; // heavy breathing effects //FIXME: sound - if( cg.predictedPlayerState.stats[ STAT_STAMINA ] < 0 ) + if( cg.predictedPlayerState.stats[ STAT_STAMINA ] < STAMINA_BREATHING_LEVEL ) { - float deltaBreath = (float)( - cg.predictedPlayerState.stats[ STAT_STAMINA ] < 0 ? - -cg.predictedPlayerState.stats[ STAT_STAMINA ] : - cg.predictedPlayerState.stats[ STAT_STAMINA ] ) / 200.0; + float deltaBreath = ( cg.predictedPlayerState.stats[ STAT_STAMINA ] - + STAMINA_BREATHING_LEVEL ) / -250.0; float deltaAngle = cos( (float)cg.time/150.0 ) * deltaBreath; deltaAngle += ( deltaAngle < 0 ? -deltaAngle : deltaAngle ) * 0.5; @@ -643,11 +776,7 @@ static void CG_OffsetFirstPersonView( void ) //=================================== // add view height - // when wall climbing the viewheight is not straight up - if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_WALLCLIMBING ) - VectorMA( origin, ps->viewheight, normal, origin ); - else - origin[ 2 ] += cg.predictedPlayerState.viewheight; + VectorMA( origin, ps->viewheight, normal, origin ); // smooth out duck height changes timeDelta = cg.time - cg.duckTime; @@ -663,12 +792,7 @@ static void CG_OffsetFirstPersonView( void ) if( bob > 6 ) bob = 6; - // likewise for bob - if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_WALLCLIMBING ) - VectorMA( origin, bob, normal, origin ); - else - origin[ 2 ] += bob; - + VectorMA( origin, bob, normal, origin ); // add fall height delta = cg.time - cg.landTime; @@ -687,10 +811,6 @@ static void CG_OffsetFirstPersonView( void ) // add step offset CG_StepOffset( ); - - // add kick offset - - VectorAdd (origin, cg.kick_origin, origin); } //====================================================================== @@ -702,14 +822,17 @@ CG_CalcFov Fixed fov at intermissions, otherwise account for fov variable and zooms. ==================== */ -#define WAVE_AMPLITUDE 1 -#define WAVE_FREQUENCY 0.4 +#define WAVE_AMPLITUDE 1.0f +#define WAVE_FREQUENCY 0.4f -#define FOVWARPTIME 400.0 +#define FOVWARPTIME 400.0f +#define BASE_FOV_Y 73.739792f // atan2( 3, 4 / tan( 90 ) ) +#define MAX_FOV_Y 120.0f +#define MAX_FOV_WARP_Y 127.5f static int CG_CalcFov( void ) { - float x; + float y; float phase; float v; int contents; @@ -719,95 +842,114 @@ static int CG_CalcFov( void ) int inwater; int attribFov; usercmd_t cmd; + usercmd_t oldcmd; int cmdNum; cmdNum = trap_GetCurrentCmdNumber( ); trap_GetUserCmd( cmdNum, &cmd ); + trap_GetUserCmd( cmdNum - 1, &oldcmd ); + + // switch follow modes if necessary: cycle between free -> follow -> third-person follow + if( cmd.buttons & BUTTON_USE_HOLDABLE && !( oldcmd.buttons & BUTTON_USE_HOLDABLE ) ) + { + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) + { + if( !cg.chaseFollow ) + cg.chaseFollow = qtrue; + else + { + cg.chaseFollow = qfalse; + trap_SendClientCommand( "follow\n" ); + } + } + else if ( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) + trap_SendClientCommand( "follow\n" ); + } if( cg.predictedPlayerState.pm_type == PM_INTERMISSION || - ( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) ) + ( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) || + ( cg.renderingThirdPerson ) ) { - // if in intermission, use a fixed value - fov_x = 90; + // if in intermission or third person, use a fixed value + fov_y = BASE_FOV_Y; } else { // don't lock the fov globally - we need to be able to change it - attribFov = BG_FindFovForClass( cg.predictedPlayerState.stats[ STAT_PCLASS ] ); - fov_x = attribFov; + attribFov = BG_Class( cg.predictedPlayerState.stats[ STAT_CLASS ] )->fov * 0.75f; + fov_y = attribFov; - if ( fov_x < 1 ) - fov_x = 1; - else if ( fov_x > 160 ) - fov_x = 160; + if ( fov_y < 1.0f ) + fov_y = 1.0f; + else if ( fov_y > MAX_FOV_Y ) + fov_y = MAX_FOV_Y; if( cg.spawnTime > ( cg.time - FOVWARPTIME ) && - BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_PCLASS ], SCA_FOVWARPS ) ) + BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_CLASS ], SCA_FOVWARPS ) ) { - float temp, temp2; - - temp = (float)( cg.time - cg.spawnTime ) / FOVWARPTIME; - temp2 = ( 170 - fov_x ) * temp; - - //Com_Printf( "%f %f\n", temp*100, temp2*100 ); + float fraction = (float)( cg.time - cg.spawnTime ) / FOVWARPTIME; - fov_x = 170 - temp2; + fov_y = MAX_FOV_WARP_Y - ( ( MAX_FOV_WARP_Y - fov_y ) * fraction ); } // account for zooms - zoomFov = BG_FindZoomFovForWeapon( cg.predictedPlayerState.weapon ); - if ( zoomFov < 1 ) - zoomFov = 1; + zoomFov = BG_Weapon( cg.predictedPlayerState.weapon )->zoomFov * 0.75f; + if ( zoomFov < 1.0f ) + zoomFov = 1.0f; else if ( zoomFov > attribFov ) zoomFov = attribFov; // only do all the zoom stuff if the client CAN zoom // FIXME: zoom control is currently hard coded to BUTTON_ATTACK2 - if( BG_WeaponCanZoom( cg.predictedPlayerState.weapon ) ) + if( BG_Weapon( cg.predictedPlayerState.weapon )->canZoom ) { if ( cg.zoomed ) { f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; - if ( f > 1.0 ) - fov_x = zoomFov; + if ( f > 1.0f ) + fov_y = zoomFov; else - fov_x = fov_x + f * ( zoomFov - fov_x ); + fov_y = fov_y + f * ( zoomFov - fov_y ); // BUTTON_ATTACK2 isn't held so unzoom next time if( !( cmd.buttons & BUTTON_ATTACK2 ) ) { cg.zoomed = qfalse; - cg.zoomTime = cg.time; + cg.zoomTime = MIN( cg.time, + cg.time + cg.time - cg.zoomTime - ZOOM_TIME ); } } else { f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; - if ( f <= 1.0 ) - fov_x = zoomFov + f * ( fov_x - zoomFov ); + if ( f > 1.0f ) + fov_y = fov_y; + else + fov_y = zoomFov + f * ( fov_y - zoomFov ); // BUTTON_ATTACK2 is held so zoom next time if( cmd.buttons & BUTTON_ATTACK2 ) { cg.zoomed = qtrue; - cg.zoomTime = cg.time; + cg.zoomTime = MIN( cg.time, + cg.time + cg.time - cg.zoomTime - ZOOM_TIME ); } } } } - x = cg.refdef.width / tan( fov_x / 360 * M_PI ); - fov_y = atan2( cg.refdef.height, x ); - fov_y = fov_y * 360 / M_PI; + y = cg.refdef.height / tan( 0.5f * DEG2RAD( fov_y ) ); + fov_x = atan2( cg.refdef.width, y ); + fov_x = 2.0f * RAD2DEG( fov_x ); // warp if underwater contents = CG_PointContents( cg.refdef.vieworg, -1 ); if( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { - phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2; + phase = cg.time / 1000.0f * WAVE_FREQUENCY * M_PI * 2.0f; v = WAVE_AMPLITUDE * sin( phase ); fov_x += v; fov_y -= v; @@ -816,13 +958,16 @@ static int CG_CalcFov( void ) else inwater = qfalse; - if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_POISONCLOUDED && + if( ( cg.predictedPlayerEntity.currentState.eFlags & EF_POISONCLOUDED ) && + ( cg.time - cg.poisonedTime < PCLOUD_DISORIENT_DURATION) && cg.predictedPlayerState.stats[ STAT_HEALTH ] > 0 && !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) { - phase = cg.time / 1000.0 * PCLOUD_ZOOM_FREQUENCY * M_PI * 2; - v = PCLOUD_ZOOM_AMPLITUDE * sin( phase ); - v *= 1.0f - ( ( cg.time - cg.poisonedTime ) / (float)LEVEL1_PCLOUD_TIME ); + float scale = 1.0f - (float)( cg.time - cg.poisonedTime ) / + BG_PlayerPoisonCloudTime( &cg.predictedPlayerState ); + + phase = ( cg.time - cg.poisonedTime ) / 1000.0f * PCLOUD_ZOOM_FREQUENCY * M_PI * 2.0f; + v = PCLOUD_ZOOM_AMPLITUDE * sin( phase ) * scale; fov_x += v; fov_y += v; } @@ -833,9 +978,9 @@ static int CG_CalcFov( void ) cg.refdef.fov_y = fov_y; if( !cg.zoomed ) - cg.zoomSensitivity = 1; + cg.zoomSensitivity = 1.0f; else - cg.zoomSensitivity = cg.refdef.fov_y / 75.0; + cg.zoomSensitivity = cg.refdef.fov_y / 75.0f; return inwater; } @@ -941,10 +1086,7 @@ static void CG_smoothWWTransitions( playerState_t *ps, const vec3_t in, vec3_t o } //set surfNormal - if( !( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) ) - VectorCopy( ps->grapplePoint, surfNormal ); - else - VectorCopy( ceilingNormal, surfNormal ); + BG_GetClientNormal( ps, surfNormal ); AnglesToAxis( in, inAxis ); @@ -952,7 +1094,6 @@ static void CG_smoothWWTransitions( playerState_t *ps, const vec3_t in, vec3_t o if( !VectorCompare( surfNormal, cg.lastNormal ) ) { //if we moving from the ceiling to the floor special case - //( x product of colinear vectors is undefined) if( VectorCompare( ceilingNormal, cg.lastNormal ) && VectorCompare( refNormal, surfNormal ) ) { @@ -1091,7 +1232,8 @@ static int CG_CalcViewValues( void ) ps = &cg.predictedPlayerState; // intermission view - if( ps->pm_type == PM_INTERMISSION ) + if( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_FREEZE || + ps->pm_type == PM_SPECTATOR ) { VectorCopy( ps->origin, cg.refdef.vieworg ); VectorCopy( ps->viewangles, cg.refdefViewAngles ); @@ -1105,18 +1247,22 @@ static int CG_CalcViewValues( void ) cg.xyspeed = sqrt( ps->velocity[ 0 ] * ps->velocity[ 0 ] + ps->velocity[ 1 ] * ps->velocity[ 1 ] ); + // the bob velocity should't get too fast to avoid jerking + if( cg.xyspeed > 300.0f ) + cg.xyspeed = 300.0f; + VectorCopy( ps->origin, cg.refdef.vieworg ); - if( BG_ClassHasAbility( ps->stats[ STAT_PCLASS ], SCA_WALLCLIMBER ) ) + if( BG_ClassHasAbility( ps->stats[ STAT_CLASS ], SCA_WALLCLIMBER ) ) CG_smoothWWTransitions( ps, ps->viewangles, cg.refdefViewAngles ); - else if( BG_ClassHasAbility( ps->stats[ STAT_PCLASS ], SCA_WALLJUMPER ) ) + else if( BG_ClassHasAbility( ps->stats[ STAT_CLASS ], SCA_WALLJUMPER ) ) CG_smoothWJTransitions( ps, ps->viewangles, cg.refdefViewAngles ); else VectorCopy( ps->viewangles, cg.refdefViewAngles ); //clumsy logic, but it needs to be this way round because the CS propogation //delay screws things up otherwise - if( !BG_ClassHasAbility( ps->stats[ STAT_PCLASS ], SCA_WALLJUMPER ) ) + if( !BG_ClassHasAbility( ps->stats[ STAT_CLASS ], SCA_WALLJUMPER ) ) { if( !( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) ) VectorSet( cg.lastNormal, 0.0f, 0.0f, 1.0f ); @@ -1143,6 +1289,8 @@ static int CG_CalcViewValues( void ) if( CG_IsParticleSystemValid( &cg.poisonCloudPS ) ) CG_DestroyParticleSystem( &cg.poisonCloudPS ); } + else + cg.wasDeadLastFrame = qfalse; if( cg.renderingThirdPerson ) { @@ -1155,7 +1303,7 @@ static int CG_CalcViewValues( void ) CG_OffsetFirstPersonView( ); } - // position eye reletive to origin + // position eye relative to origin AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); if( cg.hyperspace ) @@ -1260,7 +1408,11 @@ void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demo CG_PredictPlayerState( ); // decide on third person view - cg.renderingThirdPerson = cg_thirdPerson.integer || ( cg.snap->ps.stats[ STAT_HEALTH ] <= 0 ); + cg.renderingThirdPerson = ( cg_thirdPerson.integer || ( cg.snap->ps.stats[ STAT_HEALTH ] <= 0 ) || + ( cg.chaseFollow && cg.snap->ps.pm_flags & PMF_FOLLOW) ); + + // update speedometer + CG_AddSpeed( ); // build cg.refdef inwater = CG_CalcViewValues( ); @@ -1335,4 +1487,3 @@ void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demo if( cg_stats.integer ) CG_Printf( "cg.clientFrame:%i\n", cg.clientFrame ); } - diff --git a/src/cgame/cg_weapons.c b/src/cgame/cg_weapons.c index 9aa9f9d..fc88606 100644 --- a/src/cgame/cg_weapons.c +++ b/src/cgame/cg_weapons.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,14 +17,13 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_weapons.c -- events and effects dealing with weapons - #include "cg_local.h" /* @@ -38,26 +38,32 @@ void CG_RegisterUpgrade( int upgradeNum ) upgradeInfo_t *upgradeInfo; char *icon; - upgradeInfo = &cg_upgrades[ upgradeNum ]; - - if( upgradeNum == 0 ) + if( upgradeNum <= UP_NONE || upgradeNum >= UP_NUM_UPGRADES ) + { + CG_Error( "CG_RegisterUpgrade: out of range: %d", upgradeNum ); return; + } + + upgradeInfo = &cg_upgrades[ upgradeNum ]; if( upgradeInfo->registered ) + { + CG_Printf( "CG_RegisterUpgrade: already registered: (%d) %s\n", upgradeNum, + BG_Upgrade( upgradeNum )->name ); return; + } - memset( upgradeInfo, 0, sizeof( *upgradeInfo ) ); upgradeInfo->registered = qtrue; - if( !BG_FindNameForUpgrade( upgradeNum ) ) + if( !BG_Upgrade( upgradeNum )->name[ 0 ] ) CG_Error( "Couldn't find upgrade %i", upgradeNum ); - upgradeInfo->humanName = BG_FindHumanNameForUpgrade( upgradeNum ); + upgradeInfo->humanName = BG_Upgrade( upgradeNum )->humanName; //la la la la la, i'm not listening! if( upgradeNum == UP_GRENADE ) upgradeInfo->upgradeIcon = cg_weapons[ WP_GRENADE ].weaponIcon; - else if( ( icon = BG_FindIconForUpgrade( upgradeNum ) ) ) + else if( ( icon = BG_Upgrade( upgradeNum )->icon ) ) upgradeInfo->upgradeIcon = trap_R_RegisterShader( icon ); } @@ -72,13 +78,112 @@ void CG_InitUpgrades( void ) { int i; - memset( cg_upgrades, 0, sizeof( cg_upgrades ) ); + Com_Memset( cg_upgrades, 0, sizeof( cg_upgrades ) ); for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) CG_RegisterUpgrade( i ); } +/* +====================== +CG_ParseWeaponAnimationFile + +Read a configuration file containing animation counts and rates +models/weapons/rifle/animation.cfg, etc +====================== +*/ +static qboolean CG_ParseWeaponAnimationFile( const char *filename, weaponInfo_t *weapon ) +{ + char *text_p; + int len; + int i; + char *token; + float fps; + char text[ 20000 ]; + fileHandle_t f; + animation_t *animations; + + animations = weapon->animations; + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if( len < 0 ) + return qfalse; + + if( len == 0 || len >= sizeof( text ) - 1 ) + { + trap_FS_FCloseFile( f ); + CG_Printf( "File %s is %s\n", filename, len == 0 ? "empty" : "too long" ); + return qfalse; + } + + trap_FS_Read( text, len, f ); + text[ len ] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + + // read information for each frame + for( i = WANIM_NONE + 1; i < MAX_WEAPON_ANIMATIONS; i++ ) + { + + token = COM_Parse( &text_p ); + if( !*token ) + break; + + if( !Q_stricmp( token, "noDrift" ) ) + { + weapon->noDrift = qtrue; + continue; + } + + animations[ i ].firstFrame = atoi( token ); + + token = COM_Parse( &text_p ); + if( !*token ) + break; + + animations[ i ].numFrames = atoi( token ); + animations[ i ].reversed = qfalse; + animations[ i ].flipflop = qfalse; + + // if numFrames is negative the animation is reversed + if( animations[ i ].numFrames < 0 ) + { + animations[ i ].numFrames = -animations[ i ].numFrames; + animations[ i ].reversed = qtrue; + } + + token = COM_Parse( &text_p ); + if ( !*token ) + break; + + animations[i].loopFrames = atoi( token ); + + token = COM_Parse( &text_p ); + if( !*token ) + break; + + fps = atof( token ); + if( fps == 0 ) + fps = 1; + + animations[ i ].frameLerp = 1000 / fps; + animations[ i ].initialLerp = 1000 / fps; + } + + if( i != MAX_WEAPON_ANIMATIONS ) + { + CG_Printf( "Error parsing animation file: %s\n", filename ); + return qfalse; + } + + return qtrue; +} + + /* =============== CG_ParseWeaponModeSection @@ -141,6 +246,16 @@ static qboolean CG_ParseWeaponModeSection( weaponInfoMode_t *wim, char **text_p continue; } + else if( !Q_stricmp( token, "missileSpriteCharge" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + wim->missileSpriteCharge = atof( token ); + + continue; + } else if( !Q_stricmp( token, "missileRotates" ) ) { wim->missileRotates = qtrue; @@ -429,12 +544,13 @@ static qboolean CG_ParseWeaponFile( const char *filename, weaponInfo_t *wi ) // load the file len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if( len <= 0 ) + if( len < 0 ) return qfalse; - if( len >= sizeof( text ) - 1 ) + if( len == 0 || len >= sizeof( text ) - 1 ) { - CG_Printf( "File %s too long\n", filename ); + trap_FS_FCloseFile( f ); + CG_Printf( "File %s is %s\n", filename, len == 0 ? "empty" : "too long" ); return qfalse; } @@ -517,8 +633,33 @@ static qboolean CG_ParseWeaponFile( const char *filename, weaponInfo_t *wi ) strcat( path, "_hand.md3" ); wi->handsModel = trap_R_RegisterModel( path ); - if( !wi->handsModel ) - wi->handsModel = trap_R_RegisterModel( "models/weapons2/shotgun/shotgun_hand.md3" ); + continue; + } + else if( !Q_stricmp( token, "weaponModel3rdPerson" ) ) + { + char path[ MAX_QPATH ]; + + token = COM_Parse( &text_p ); + if( !token ) + break; + + wi->weaponModel3rdPerson = trap_R_RegisterModel( token ); + + if( !wi->weaponModel3rdPerson ) + { + CG_Printf( S_COLOR_RED "ERROR: 3rd person weapon " + "model not found %s\n", token ); + } + + strcpy( path, token ); + COM_StripExtension( path, path, MAX_QPATH ); + strcat( path, "_flash.md3" ); + wi->flashModel3rdPerson = trap_R_RegisterModel( path ); + + strcpy( path, token ); + COM_StripExtension( path, path, MAX_QPATH ); + strcat( path, "_barrel.md3" ); + wi->barrelModel3rdPerson = trap_R_RegisterModel( path ); continue; } @@ -596,35 +737,42 @@ void CG_RegisterWeapon( int weaponNum ) vec3_t mins, maxs; int i; - weaponInfo = &cg_weapons[ weaponNum ]; - - if( weaponNum == 0 ) + if( weaponNum <= WP_NONE || weaponNum >= WP_NUM_WEAPONS ) + { + CG_Error( "CG_RegisterWeapon: out of range: %d", weaponNum ); return; + } + + weaponInfo = &cg_weapons[ weaponNum ]; if( weaponInfo->registered ) + { + CG_Printf( "CG_RegisterWeapon: already registered: (%d) %s\n", weaponNum, + BG_Weapon( weaponNum )->name ); return; + } - memset( weaponInfo, 0, sizeof( *weaponInfo ) ); weaponInfo->registered = qtrue; - if( !BG_FindNameForWeapon( weaponNum ) ) + if( !BG_Weapon( weaponNum )->name[ 0 ] ) CG_Error( "Couldn't find weapon %i", weaponNum ); - Com_sprintf( path, MAX_QPATH, "models/weapons/%s/weapon.cfg", BG_FindNameForWeapon( weaponNum ) ); + Com_sprintf( path, MAX_QPATH, "models/weapons/%s/weapon.cfg", BG_Weapon( weaponNum )->name ); - weaponInfo->humanName = BG_FindHumanNameForWeapon( weaponNum ); + weaponInfo->humanName = BG_Weapon( weaponNum )->humanName; if( !CG_ParseWeaponFile( path, weaponInfo ) ) Com_Printf( S_COLOR_RED "ERROR: failed to parse %s\n", path ); + Com_sprintf( path, MAX_QPATH, "models/weapons/%s/animation.cfg", BG_Weapon( weaponNum )->name ); + + if( !CG_ParseWeaponAnimationFile( path, weaponInfo ) ) + Com_Printf( S_COLOR_RED "ERROR: failed to parse %s\n", path ); + // calc midpoint for rotation trap_R_ModelBounds( weaponInfo->weaponModel, mins, maxs ); for( i = 0 ; i < 3 ; i++ ) weaponInfo->weaponMidpoint[ i ] = mins[ i ] + 0.5 * ( maxs[ i ] - mins[ i ] ); - - //FIXME: - for( i = WPM_NONE + 1; i < WPM_NUM_WEAPONMODES; i++ ) - weaponInfo->wim[ i ].loopFireSound = qfalse; } /* @@ -638,7 +786,7 @@ void CG_InitWeapons( void ) { int i; - memset( cg_weapons, 0, sizeof( cg_weapons ) ); + Com_Memset( cg_weapons, 0, sizeof( cg_weapons ) ); for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) CG_RegisterWeapon( i ); @@ -655,6 +803,53 @@ VIEW WEAPON ======================================================================================== */ +/* +=============== +CG_SetWeaponLerpFrameAnimation + +may include ANIM_TOGGLEBIT +=============== +*/ +static void CG_SetWeaponLerpFrameAnimation( weapon_t weapon, lerpFrame_t *lf, int newAnimation ) +{ + animation_t *anim; + + lf->animationNumber = newAnimation; + newAnimation &= ~ANIM_TOGGLEBIT; + + if( newAnimation < 0 || newAnimation >= MAX_WEAPON_ANIMATIONS ) + CG_Error( "Bad animation number: %i", newAnimation ); + + anim = &cg_weapons[ weapon ].animations[ newAnimation ]; + + lf->animation = anim; + lf->animationTime = lf->frameTime + anim->initialLerp; + + if( cg_debugAnim.integer ) + CG_Printf( "Anim: %i\n", newAnimation ); +} + +/* +=============== +CG_WeaponAnimation +=============== +*/ +static void CG_WeaponAnimation( centity_t *cent, int *old, int *now, float *backLerp ) +{ + lerpFrame_t *lf = ¢->pe.weapon; + entityState_t *es = ¢->currentState; + + // see if the animation sequence is switching + if( es->weaponAnim != lf->animationNumber || !lf->animation ) + CG_SetWeaponLerpFrameAnimation( es->weapon, lf, es->weaponAnim ); + + CG_RunLerpFrame( lf, 1.0f ); + + *old = lf->oldFrame; + *now = lf->frame; + *backLerp = lf->backlerp; +} + /* ================= CG_MapTorsoToWeaponFrame @@ -690,10 +885,13 @@ CG_CalculateWeaponPosition */ static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles ) { - float scale; - int delta; - float fracsin; - float bob; + float scale; + int delta; + float fracsin; + float bob; + weaponInfo_t *weapon; + + weapon = &cg_weapons[ cg.predictedPlayerState.weapon ]; VectorCopy( cg.refdef.vieworg, origin ); VectorCopy( cg.refdefViewAngles, angles ); @@ -706,7 +904,7 @@ static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles ) // gun angles from bobbing // bob amount is class dependant - bob = BG_FindBobForClass( cg.predictedPlayerState.stats[ STAT_PCLASS ] ); + bob = BG_Class( cg.predictedPlayerState.stats[ STAT_CLASS ] )->bob; if( bob != 0 ) { @@ -716,7 +914,7 @@ static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles ) } // drop the weapon when landing - if( !BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_PCLASS ], SCA_NOWEAPONDRIFT ) ) + if( !weapon->noDrift ) { delta = cg.time - cg.landTime; if( delta < LAND_DEFLECT_TIME ) @@ -795,6 +993,13 @@ void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent weaponNum = cent->currentState.weapon; weaponMode = cent->currentState.generic1; + if( weaponNum <= WP_NONE || weaponNum >= WP_NUM_WEAPONS ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: CG_AddPlayerWeapon: weapon " + "number %i is out of bounds", weaponNum ); + return; + } + if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES ) weaponMode = WPM_PRIMARY; @@ -805,16 +1010,23 @@ void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent else firing = qfalse; - CG_RegisterWeapon( weaponNum ); weapon = &cg_weapons[ weaponNum ]; + if( !weapon->registered ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: CG_AddPlayerWeapon: weapon %d (%s) " + "is not registered\n", weaponNum, BG_Weapon( weaponNum )->name ); + return; + } // add the weapon - memset( &gun, 0, sizeof( gun ) ); + Com_Memset( &gun, 0, sizeof( gun ) ); + Com_Memset( &barrel, 0, sizeof( barrel ) ); + Com_Memset( &flash, 0, sizeof( flash ) ); + VectorCopy( parent->lightingOrigin, gun.lightingOrigin ); gun.shadowPlane = parent->shadowPlane; gun.renderfx = parent->renderfx; - // set custom shading for railgun refire rate if( ps ) { gun.shaderRGBA[ 0 ] = 255; @@ -842,7 +1054,15 @@ void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent } } - gun.hModel = weapon->weaponModel; + if( !ps ) + { + gun.hModel = weapon->weaponModel3rdPerson; + + if( !gun.hModel ) + gun.hModel = weapon->weaponModel; + } + else + gun.hModel = weapon->weaponModel; noGunModel = ( ( !ps || cg.renderingThirdPerson ) && weapon->disableIn3rdPerson ) || !gun.hModel; @@ -858,27 +1078,46 @@ void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->readySound ); } + // Lucifer cannon charge warning beep + if( weaponNum == WP_LUCIFER_CANNON && + ( cent->currentState.eFlags & EF_WARN_CHARGE ) && + cg.snap->ps.stats[ STAT_TEAM ] != TEAM_ALIENS ) + { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, + vec3_origin, ps ? cgs.media.lCannonWarningSound : + cgs.media.lCannonWarningSound2 ); + } + if( !noGunModel ) { CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_weapon" ); + CG_WeaponAnimation( cent, &gun.oldframe, &gun.frame, &gun.backlerp ); trap_R_AddRefEntityToScene( &gun ); + if( !ps ) + { + barrel.hModel = weapon->barrelModel3rdPerson; + + if( !barrel.hModel ) + barrel.hModel = weapon->barrelModel; + } + else + barrel.hModel = weapon->barrelModel; + // add the spinning barrel - if( weapon->barrelModel ) + if( barrel.hModel ) { - memset( &barrel, 0, sizeof( barrel ) ); VectorCopy( parent->lightingOrigin, barrel.lightingOrigin ); barrel.shadowPlane = parent->shadowPlane; barrel.renderfx = parent->renderfx; - barrel.hModel = weapon->barrelModel; angles[ YAW ] = 0; angles[ PITCH ] = 0; angles[ ROLL ] = CG_MachinegunSpinAngle( cent, firing ); AnglesToAxis( angles, barrel.axis ); - CG_PositionRotatedEntityOnTag( &barrel, &gun, weapon->weaponModel, "tag_barrel" ); + CG_PositionRotatedEntityOnTag( &barrel, &gun, gun.hModel, "tag_barrel" ); trap_R_AddRefEntityToScene( &barrel ); } @@ -892,7 +1131,7 @@ void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent if( noGunModel ) CG_SetAttachmentTag( ¢->muzzlePS->attachment, *parent, parent->hModel, "tag_weapon" ); else - CG_SetAttachmentTag( ¢->muzzlePS->attachment, gun, weapon->weaponModel, "tag_flash" ); + CG_SetAttachmentTag( ¢->muzzlePS->attachment, gun, gun.hModel, "tag_flash" ); } //if the PS is infinite disable it when not firing @@ -908,12 +1147,20 @@ void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent return; } - memset( &flash, 0, sizeof( flash ) ); VectorCopy( parent->lightingOrigin, flash.lightingOrigin ); flash.shadowPlane = parent->shadowPlane; flash.renderfx = parent->renderfx; - flash.hModel = weapon->flashModel; + if( !ps ) + { + flash.hModel = weapon->flashModel3rdPerson; + + if( !flash.hModel ) + flash.hModel = weapon->flashModel; + } + else + flash.hModel = weapon->flashModel; + if( flash.hModel ) { angles[ YAW ] = 0; @@ -924,7 +1171,7 @@ void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent if( noGunModel ) CG_PositionRotatedEntityOnTag( &flash, parent, parent->hModel, "tag_weapon" ); else - CG_PositionRotatedEntityOnTag( &flash, &gun, weapon->weaponModel, "tag_flash" ); + CG_PositionRotatedEntityOnTag( &flash, &gun, gun.hModel, "tag_flash" ); trap_R_AddRefEntityToScene( &flash ); } @@ -941,7 +1188,7 @@ void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent if( noGunModel ) CG_SetAttachmentTag( ¢->muzzlePS->attachment, *parent, parent->hModel, "tag_weapon" ); else - CG_SetAttachmentTag( ¢->muzzlePS->attachment, gun, weapon->weaponModel, "tag_flash" ); + CG_SetAttachmentTag( ¢->muzzlePS->attachment, gun, gun.hModel, "tag_flash" ); CG_SetAttachmentCent( ¢->muzzlePS->attachment, cent ); CG_AttachToTag( ¢->muzzlePS->attachment ); @@ -970,6 +1217,9 @@ CG_AddViewWeapon Add the weapon, and flash for the player's view ============== */ + +#define WEAPON_CLICK_REPEAT 500 + void CG_AddViewWeapon( playerState_t *ps ) { refEntity_t hand; @@ -981,20 +1231,23 @@ void CG_AddViewWeapon( playerState_t *ps ) weapon_t weapon = ps->weapon; weaponMode_t weaponMode = ps->generic1; + // no weapon carried - can't draw it + if( weapon == WP_NONE ) + return; + if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES ) weaponMode = WPM_PRIMARY; - CG_RegisterWeapon( weapon ); wi = &cg_weapons[ weapon ]; - cent = &cg.predictedPlayerEntity; // &cg_entities[cg.snap->ps.clientNum]; - - if( ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) || - ( ps->stats[ STAT_STATE ] & SS_INFESTING ) || - ( ps->stats[ STAT_STATE ] & SS_HOVELING ) ) + if( !wi->registered ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: CG_AddViewWeapon: weapon %d (%s) " + "is not registered\n", weapon, BG_Weapon( weapon )->name ); return; + } + cent = &cg.predictedPlayerEntity; // &cg_entities[cg.snap->ps.clientNum]; - // no weapon carried - can't draw it - if( weapon == WP_NONE ) + if( ps->persistant[PERS_SPECSTATE] != SPECTATOR_NOT ) return; if( ps->pm_type == PM_INTERMISSION ) @@ -1004,12 +1257,6 @@ void CG_AddViewWeapon( playerState_t *ps ) if( ( ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) > BA_NONE ) CG_GhostBuildable( ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ); - if( weapon == WP_LUCIFER_CANNON && ps->stats[ STAT_MISC ] > 0 ) - { - if( ps->stats[ STAT_MISC ] > ( LCANNON_TOTAL_CHARGE - ( LCANNON_TOTAL_CHARGE / 3 ) ) ) - trap_S_AddLoopingSound( ps->clientNum, ps->origin, vec3_origin, cgs.media.lCannonWarningSound ); - } - // no gun if in third person view if( cg.renderingThirdPerson ) return; @@ -1052,7 +1299,7 @@ void CG_AddViewWeapon( playerState_t *ps ) else fovOffset = 0; - memset( &hand, 0, sizeof( hand ) ); + Com_Memset( &hand, 0, sizeof( hand ) ); // set up gun position CG_CalculateWeaponPosition( hand.origin, angles ); @@ -1061,12 +1308,16 @@ void CG_AddViewWeapon( playerState_t *ps ) VectorMA( hand.origin, cg_gun_y.value, cg.refdef.viewaxis[ 1 ], hand.origin ); VectorMA( hand.origin, ( cg_gun_z.value + fovOffset ), cg.refdef.viewaxis[ 2 ], hand.origin ); + // Lucifer Cannon vibration effect if( weapon == WP_LUCIFER_CANNON && ps->stats[ STAT_MISC ] > 0 ) { - float fraction = (float)ps->stats[ STAT_MISC ] / (float)LCANNON_TOTAL_CHARGE; + float fraction; - VectorMA( hand.origin, random( ) * fraction, cg.refdef.viewaxis[ 0 ], hand.origin ); - VectorMA( hand.origin, random( ) * fraction, cg.refdef.viewaxis[ 1 ], hand.origin ); + fraction = (float)ps->stats[ STAT_MISC ] / LCANNON_CHARGE_TIME_MAX; + VectorMA( hand.origin, random( ) * fraction, cg.refdef.viewaxis[ 0 ], + hand.origin ); + VectorMA( hand.origin, random( ) * fraction, cg.refdef.viewaxis[ 1 ], + hand.origin ); } AnglesToAxis( angles, hand.axis ); @@ -1109,19 +1360,7 @@ CG_WeaponSelectable */ static qboolean CG_WeaponSelectable( weapon_t weapon ) { - //int ammo, clips; - // - //ammo = cg.snap->ps.ammo; - //clips = cg.snap->ps.clips - // - // this is a pain in the ass - //if( !ammo && !clips && !BG_FindInfinteAmmoForWeapon( i ) ) - // return qfalse; - - if( !BG_InventoryContainsWeapon( weapon, cg.snap->ps.stats ) ) - return qfalse; - - return qtrue; + return BG_InventoryContainsWeapon( weapon, cg.snap->ps.stats ); } @@ -1135,7 +1374,7 @@ static qboolean CG_UpgradeSelectable( upgrade_t upgrade ) if( !BG_InventoryContainsUpgrade( upgrade, cg.snap->ps.stats ) ) return qfalse; - return BG_FindUsableForUpgrade( upgrade ); + return BG_Upgrade( upgrade )->usable; } @@ -1149,22 +1388,19 @@ CG_DrawItemSelect void CG_DrawItemSelect( rectDef_t *rect, vec4_t color ) { int i; - int x = rect->x; - int y = rect->y; - int width = rect->w; - int height = rect->h; - int iconsize; + float x = rect->x; + float y = rect->y; + float width = rect->w; + float height = rect->h; + float iconWidth; + float iconHeight; int items[ 64 ]; + int colinfo[ 64 ]; int numItems = 0, selectedItem = 0; int length; - int selectWindow; qboolean vertical; - centity_t *cent; playerState_t *ps; - - int colinfo[ 64 ]; - cent = &cg_entities[ cg.snap->ps.clientNum ]; ps = &cg.snap->ps; // don't display if dead @@ -1174,109 +1410,121 @@ void CG_DrawItemSelect( rectDef_t *rect, vec4_t color ) if( !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) { // first make sure that whatever it selected is actually selectable - if( cg.weaponSelect <= 32 && !CG_WeaponSelectable( cg.weaponSelect ) ) - CG_NextWeapon_f( ); - else if( cg.weaponSelect > 32 && !CG_UpgradeSelectable( cg.weaponSelect - 32 ) ) - CG_NextWeapon_f( ); + if( cg.weaponSelect < 32 ) + { + if( !CG_WeaponSelectable( cg.weaponSelect ) ) + CG_NextWeapon_f( ); + } + else + { + if( !CG_UpgradeSelectable( cg.weaponSelect - 32 ) ) + CG_NextWeapon_f( ); + } } // showing weapon select clears pickup item display, but not the blend blob cg.itemPickupTime = 0; - if( height > width ) - { - vertical = qtrue; - iconsize = width; - length = height / width; - } - else - { - vertical = qfalse; - iconsize = height; - length = width / height; - } - - selectWindow = length / 2; - + // put all weapons in the items list for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) { if( !BG_InventoryContainsWeapon( i, cg.snap->ps.stats ) ) continue; - - { - int ammo, clips; - - ammo = cg.snap->ps.ammo; - clips = cg.snap->ps.clips; - - if( !ammo && !clips && !BG_FindInfinteAmmoForWeapon( i ) ) - colinfo[ numItems ] = 1; - else - colinfo[ numItems ] = 0; - - } + + if( !ps->ammo && !ps->clips && !BG_Weapon( i )->infiniteAmmo ) + colinfo[ numItems ] = 1; + else + colinfo[ numItems ] = 0; if( i == cg.weaponSelect ) selectedItem = numItems; - CG_RegisterWeapon( i ); + if( !cg_weapons[ i ].registered ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: CG_DrawItemSelect: weapon %d (%s) " + "is not registered\n", i, BG_Weapon( i )->name ); + continue; + } items[ numItems ] = i; numItems++; } + // put all upgrades in the weapons list for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) { if( !BG_InventoryContainsUpgrade( i, cg.snap->ps.stats ) ) continue; colinfo[ numItems ] = 0; - if( !BG_FindUsableForUpgrade ( i ) ) + if( !BG_Upgrade( i )->usable ) colinfo[ numItems ] = 2; - if( i == cg.weaponSelect - 32 ) selectedItem = numItems; - CG_RegisterUpgrade( i ); + if( !cg_upgrades[ i ].registered ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: CG_DrawItemSelect: upgrade %d (%s) " + "is not registered\n", i, BG_Upgrade( i )->name ); + continue; + } items[ numItems ] = i + 32; numItems++; } + // compute the length of the display window and determine orientation + vertical = height > width; + if( vertical ) + { + iconWidth = width * cgDC.aspectScale; + iconHeight = width; + length = height / ( width * cgDC.aspectScale ); + } + else + { + iconWidth = height * cgDC.aspectScale; + iconHeight = height; + length = width / ( height * cgDC.aspectScale ); + } + + // render icon ring for( i = 0; i < length; i++ ) { - int displacement = i - selectWindow; - int item = displacement + selectedItem; + int item = i - length / 2 + selectedItem; - if( ( item >= 0 ) && ( item < numItems ) ) + if( item < 0 ) + item += length; + else if( item >= length ) + item -= length; + if( item >= 0 && item < numItems ) { switch( colinfo[ item ] ) { - case 0: - color = colorCyan; - break; - case 1: - color = colorRed; - break; - case 2: - color = colorGray; - break; + case 0: + color = colorCyan; + break; + case 1: + color = colorRed; + break; + case 2: + color = colorMdGrey; + break; } color[3] = 0.5; - trap_R_SetColor( color ); - if( items[ item ] <= 32 ) - CG_DrawPic( x, y, iconsize, iconsize, cg_weapons[ items[ item ] ].weaponIcon ); - else if( items[ item ] > 32 ) - CG_DrawPic( x, y, iconsize, iconsize, cg_upgrades[ items[ item ] - 32 ].upgradeIcon ); - - trap_R_SetColor( NULL ); + if( items[ item ] < 32 ) + CG_DrawPic( x, y, iconWidth, iconHeight, + cg_weapons[ items[ item ] ].weaponIcon ); + else + CG_DrawPic( x, y, iconWidth, iconHeight, + cg_upgrades[ items[ item ] - 32 ].upgradeIcon ); } - if( vertical ) - y += iconsize; + y += iconHeight; else - x += iconsize; + x += iconWidth; } + trap_R_SetColor( NULL ); } @@ -1298,29 +1546,29 @@ void CG_DrawItemSelectText( rectDef_t *rect, float scale, int textStyle ) trap_R_SetColor( color ); // draw the selected name - if( cg.weaponSelect <= 32 ) + if( cg.weaponSelect < 32 ) { if( cg_weapons[ cg.weaponSelect ].registered && BG_InventoryContainsWeapon( cg.weaponSelect, cg.snap->ps.stats ) ) { if( ( name = cg_weapons[ cg.weaponSelect ].humanName ) ) { - w = CG_Text_Width( name, scale, 0 ); + w = UI_Text_Width( name, scale ); x = rect->x + rect->w / 2; - CG_Text_Paint( x - w / 2, rect->y + rect->h, scale, color, name, 0, 0, textStyle ); + UI_Text_Paint( x - w / 2, rect->y + rect->h, scale, color, name, 0, 0, textStyle ); } } } - else if( cg.weaponSelect > 32 ) + else { if( cg_upgrades[ cg.weaponSelect - 32 ].registered && BG_InventoryContainsUpgrade( cg.weaponSelect - 32, cg.snap->ps.stats ) ) { if( ( name = cg_upgrades[ cg.weaponSelect - 32 ].humanName ) ) { - w = CG_Text_Width( name, scale, 0 ); + w = UI_Text_Width( name, scale ); x = rect->x + rect->w / 2; - CG_Text_Paint( x - w / 2, rect->y + rect->h, scale, color, name, 0, 0, textStyle ); + UI_Text_Paint( x - w / 2, rect->y + rect->h, scale, color, name, 0, 0, textStyle ); } } } @@ -1357,12 +1605,12 @@ void CG_NextWeapon_f( void ) if( cg.weaponSelect == 64 ) cg.weaponSelect = 0; - if( cg.weaponSelect <= 32 ) + if( cg.weaponSelect < 32 ) { if( CG_WeaponSelectable( cg.weaponSelect ) ) break; } - else if( cg.weaponSelect > 32 ) + else { if( CG_UpgradeSelectable( cg.weaponSelect - 32 ) ) break; @@ -1401,12 +1649,12 @@ void CG_PrevWeapon_f( void ) if( cg.weaponSelect == -1 ) cg.weaponSelect = 63; - if( cg.weaponSelect <= 32 ) + if( cg.weaponSelect < 32 ) { if( CG_WeaponSelectable( cg.weaponSelect ) ) break; } - else if( cg.weaponSelect > 32 ) + else { if( CG_UpgradeSelectable( cg.weaponSelect - 32 ) ) break; @@ -1521,7 +1769,7 @@ Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing ================= */ void CG_MissileHitWall( weapon_t weaponNum, weaponMode_t weaponMode, int clientNum, - vec3_t origin, vec3_t dir, impactSound_t soundType ) + vec3_t origin, vec3_t dir, impactSound_t soundType, int charge ) { qhandle_t mark = 0; qhandle_t ps = 0; @@ -1579,6 +1827,7 @@ void CG_MissileHitWall( weapon_t weaponNum, weaponMode_t weaponMode, int clientN CG_SetAttachmentPoint( &partSystem->attachment, origin ); CG_SetParticleSystemNormal( partSystem, dir ); CG_AttachToPoint( &partSystem->attachment ); + partSystem->charge = charge; } } @@ -1592,11 +1841,11 @@ void CG_MissileHitWall( weapon_t weaponNum, weaponMode_t weaponMode, int clientN /* ================= -CG_MissileHitPlayer +CG_MissileHitEntity ================= */ -void CG_MissileHitPlayer( weapon_t weaponNum, weaponMode_t weaponMode, - vec3_t origin, vec3_t dir, int entityNum ) +void CG_MissileHitEntity( weapon_t weaponNum, weaponMode_t weaponMode, + vec3_t origin, vec3_t dir, int entityNum, int charge ) { vec3_t normal; weaponInfo_t *weapon = &cg_weapons[ weaponNum ]; @@ -1610,7 +1859,25 @@ void CG_MissileHitPlayer( weapon_t weaponNum, weaponMode_t weaponMode, weaponMode = WPM_PRIMARY; if( weapon->wim[ weaponMode ].alwaysImpact ) - CG_MissileHitWall( weaponNum, weaponMode, 0, origin, dir, IMPACTSOUND_FLESH ); + { + int sound; + + if( cg_entities[ entityNum ].currentState.eType == ET_PLAYER ) + { + // Players + sound = IMPACTSOUND_FLESH; + } + else if( cg_entities[ entityNum ].currentState.eType == ET_BUILDABLE && + BG_Buildable( cg_entities[ entityNum ].currentState.modelindex )->team == TEAM_ALIENS ) + { + // Alien buildables + sound = IMPACTSOUND_FLESH; + } + else + sound = IMPACTSOUND_DEFAULT; + + CG_MissileHitWall( weaponNum, weaponMode, 0, origin, dir, sound, charge ); + } } @@ -1772,7 +2039,7 @@ void CG_Bullet( vec3_t end, int sourceEntityNum, vec3_t normal, qboolean flesh, if( flesh ) CG_Bleed( end, normal, fleshEntityNum ); else - CG_MissileHitWall( WP_MACHINEGUN, WPM_PRIMARY, 0, end, normal, IMPACTSOUND_DEFAULT ); + CG_MissileHitWall( WP_MACHINEGUN, WPM_PRIMARY, 0, end, normal, IMPACTSOUND_DEFAULT, 0 ); } /* @@ -1818,12 +2085,13 @@ static void CG_ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, int othe if( !( tr.surfaceFlags & SURF_NOIMPACT ) ) { - if( cg_entities[ tr.entityNum ].currentState.eType == ET_PLAYER ) - CG_MissileHitPlayer( WP_SHOTGUN, WPM_PRIMARY, tr.endpos, tr.plane.normal, tr.entityNum ); + if( cg_entities[ tr.entityNum ].currentState.eType == ET_PLAYER || + cg_entities[ tr.entityNum ].currentState.eType == ET_BUILDABLE ) + CG_MissileHitEntity( WP_SHOTGUN, WPM_PRIMARY, tr.endpos, tr.plane.normal, tr.entityNum, 0 ); else if( tr.surfaceFlags & SURF_METALSTEPS ) - CG_MissileHitWall( WP_SHOTGUN, WPM_PRIMARY, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_METAL ); + CG_MissileHitWall( WP_SHOTGUN, WPM_PRIMARY, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_METAL, 0 ); else - CG_MissileHitWall( WP_SHOTGUN, WPM_PRIMARY, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_DEFAULT ); + CG_MissileHitWall( WP_SHOTGUN, WPM_PRIMARY, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_DEFAULT, 0 ); } } } @@ -1845,3 +2113,54 @@ void CG_ShotgunFire( entityState_t *es ) CG_ShotgunPattern( es->pos.trBase, es->origin2, es->eventParm, es->otherEntityNum ); } +/* +================= +CG_Bleed + +This is the spurt of blood when a character gets hit +================= +*/ +void CG_Bleed( vec3_t origin, vec3_t normal, int entityNum ) +{ + team_t team; + qhandle_t bleedPS; + particleSystem_t *ps; + + if( !cg_blood.integer ) + return; + + if( cg_entities[ entityNum ].currentState.eType == ET_PLAYER ) + { + team = cgs.clientinfo[ entityNum ].team; + if( team == TEAM_ALIENS ) + bleedPS = cgs.media.alienBleedPS; + else if( team == TEAM_HUMANS ) + bleedPS = cgs.media.humanBleedPS; + else + return; + } + else if( cg_entities[ entityNum ].currentState.eType == ET_BUILDABLE ) + { + //ew + team = BG_Buildable( cg_entities[ entityNum ].currentState.modelindex )->team; + if( team == TEAM_ALIENS ) + bleedPS = cgs.media.alienBuildableBleedPS; + else if( team == TEAM_HUMANS ) + bleedPS = cgs.media.humanBuildableBleedPS; + else + return; + } + else + return; + + ps = CG_SpawnNewParticleSystem( bleedPS ); + + if( CG_IsParticleSystemValid( &ps ) ) + { + CG_SetAttachmentPoint( &ps->attachment, origin ); + CG_SetAttachmentCent( &ps->attachment, &cg_entities[ entityNum ] ); + CG_AttachToPoint( &ps->attachment ); + + CG_SetParticleSystemNormal( ps, normal ); + } +} -- cgit