diff options
Diffstat (limited to 'src')
39 files changed, 45141 insertions, 0 deletions
diff --git a/src/cgame/cg_animation.c b/src/cgame/cg_animation.c new file mode 100644 index 0000000..c370c53 --- /dev/null +++ b/src/cgame/cg_animation.c @@ -0,0 +1,111 @@ +/* +=========================================================================== +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" + +/* +=============== +CG_RunLerpFrame + +Sets cg.snap, cg.oldFrame, and cg.backlerp +cg.time should be between oldFrameTime and frameTime after exit +=============== +*/ +void CG_RunLerpFrame( lerpFrame_t *lf ) +{ +  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; +  } + +  // 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; +    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 ); +} diff --git a/src/cgame/cg_animmapobj.c b/src/cgame/cg_animmapobj.c new file mode 100644 index 0000000..2e80b7e --- /dev/null +++ b/src/cgame/cg_animmapobj.c @@ -0,0 +1,227 @@ +/* +=========================================================================== +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" + + +/* +=============== +CG_DoorAnimation +=============== +*/ +static void CG_DoorAnimation( centity_t *cent, int *old, int *now, float *backLerp ) +{ +  CG_RunLerpFrame( ¢->lerpFrame ); + +  *old      = cent->lerpFrame.oldFrame; +  *now      = cent->lerpFrame.frame; +  *backLerp = cent->lerpFrame.backlerp; +} + + +/* +=============== +CG_ModelDoor +=============== +*/ +void CG_ModelDoor( centity_t *cent ) +{ +  refEntity_t     ent; +  entityState_t   *es; +  animation_t     anim; +  lerpFrame_t     *lf = ¢->lerpFrame; + +  es = ¢->currentState; + +  if( !es->modelindex ) +    return; + +  //create the render entity +  memset( &ent, 0, sizeof( ent ) ); +  VectorCopy( cent->lerpOrigin, ent.origin ); +  VectorCopy( cent->lerpOrigin, ent.oldorigin ); +  AnglesToAxis( cent->lerpAngles, ent.axis ); + +  ent.renderfx = RF_NOSHADOW; + +  //add the door model +  ent.skinNum = 0; +  ent.hModel = cgs.gameModels[ es->modelindex ]; + +  //scale the door +  VectorScale( ent.axis[ 0 ], es->origin2[ 0 ], ent.axis[ 0 ] ); +  VectorScale( ent.axis[ 1 ], es->origin2[ 1 ], ent.axis[ 1 ] ); +  VectorScale( ent.axis[ 2 ], es->origin2[ 2 ], ent.axis[ 2 ] ); +  ent.nonNormalizedAxes = qtrue; + +  //setup animation +  anim.firstFrame   = es->powerups; +  anim.numFrames    = es->weapon; +  anim.reversed     = !es->legsAnim; +  anim.flipflop     = qfalse; +  anim.loopFrames   = 0; +  anim.frameLerp    = 1000 / es->torsoAnim; +  anim.initialLerp  = 1000 / es->torsoAnim; + +  //door changed state +  if( es->legsAnim != cent->doorState ) +  { +    lf->animationTime = lf->frameTime + anim.initialLerp; +    cent->doorState = es->legsAnim; +  } + +  lf->animation = &anim; + +  //run animation +  CG_DoorAnimation( cent, &ent.oldframe, &ent.frame, &ent.backlerp ); + +  trap_R_AddRefEntityToScene( &ent ); +} + + +/* +=============== +CG_AMOAnimation +=============== +*/ +static void CG_AMOAnimation( centity_t *cent, int *old, int *now, float *backLerp ) +{ +  if( !( cent->currentState.eFlags & EF_MOVER_STOP ) || cent->animPlaying ) +  { +    int delta = cg.time - cent->miscTime; + +    //hack to prevent "pausing" mucking up the lerping +    if( delta > 900 ) +    { +      cent->lerpFrame.oldFrameTime  += delta; +      cent->lerpFrame.frameTime     += delta; +    } + +    CG_RunLerpFrame( ¢->lerpFrame ); +    cent->miscTime = cg.time; +  } + +  *old      = cent->lerpFrame.oldFrame; +  *now      = cent->lerpFrame.frame; +  *backLerp = cent->lerpFrame.backlerp; +} + + +/* +================== +CG_animMapObj +================== +*/ +void CG_AnimMapObj( centity_t *cent ) +{ +  refEntity_t     ent; +  entityState_t   *es; +  float           scale; +  animation_t     anim; + +  es = ¢->currentState; + +  // if set to invisible, skip +  if( !es->modelindex || ( es->eFlags & EF_NODRAW ) ) +    return; + +  memset( &ent, 0, sizeof( ent ) ); + +  VectorCopy( es->angles, cent->lerpAngles ); +  AnglesToAxis( cent->lerpAngles, ent.axis ); + +  ent.hModel = cgs.gameModels[ es->modelindex ]; + +  VectorCopy( cent->lerpOrigin, ent.origin); +  VectorCopy( cent->lerpOrigin, ent.oldorigin); + +  ent.nonNormalizedAxes = qfalse; + +  //scale the model +  if( es->angles2[ 0 ] ) +  { +    scale = es->angles2[ 0 ]; +    VectorScale( ent.axis[ 0 ], scale, ent.axis[ 0 ] ); +    VectorScale( ent.axis[ 1 ], scale, ent.axis[ 1 ] ); +    VectorScale( ent.axis[ 2 ], scale, ent.axis[ 2 ] ); +    ent.nonNormalizedAxes = qtrue; +  } + +  //setup animation +  anim.firstFrame = es->powerups; +  anim.numFrames = es->weapon; +  anim.reversed = qfalse; +  anim.flipflop = qfalse; + +  // if numFrames is negative the animation is reversed +  if( anim.numFrames < 0 ) +  { +    anim.numFrames = -anim.numFrames; +    anim.reversed = qtrue; +  } + +  anim.loopFrames = es->torsoAnim; + +  if( !es->legsAnim ) +  { +    anim.frameLerp = 1000; +    anim.initialLerp = 1000; +  } +  else +  { +    anim.frameLerp = 1000 / es->legsAnim; +    anim.initialLerp = 1000 / es->legsAnim; +  } + +  cent->lerpFrame.animation = &anim; +  +  if( !anim.loopFrames ) +  { +    // add one frame to allow the animation to play the last frame +    // add another to get it to stop playing at the first frame +    anim.numFrames += 2; + +    if( !cent->animInit ) +    { +      cent->animInit = qtrue; +      cent->animPlaying = !( cent->currentState.eFlags & EF_MOVER_STOP ); +    } +    else +    { +      if( cent->animLastState != +          !( cent->currentState.eFlags & EF_MOVER_STOP ) ) +      { +        cent->animPlaying = qtrue; +        cent->lerpFrame.animationTime = cg.time; +        cent->lerpFrame.frameTime = cg.time; +      } +    } +    cent->animLastState = !( cent->currentState.eFlags & EF_MOVER_STOP ); +  } + +  //run animation +  CG_AMOAnimation( cent, &ent.oldframe, &ent.frame, &ent.backlerp ); + +  // add to refresh list +  trap_R_AddRefEntityToScene(&ent); +} diff --git a/src/cgame/cg_attachment.c b/src/cgame/cg_attachment.c new file mode 100644 index 0000000..0d3c8ea --- /dev/null +++ b/src/cgame/cg_attachment.c @@ -0,0 +1,404 @@ +/* +=========================================================================== +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_attachment.c -- an abstract attachment system + +#include "cg_local.h" + +/* +=============== +CG_AttachmentPoint + +Return the attachment point +=============== +*/ +qboolean CG_AttachmentPoint( attachment_t *a, vec3_t v ) +{ +  centity_t   *cent; + +  if( !a ) +    return qfalse; + +  // if it all breaks, then use the last point we know was correct +  VectorCopy( a->lastValidAttachmentPoint, v ); + +  switch( a->type ) +  { +    case AT_STATIC: +      if( !a->staticValid ) +        return qfalse; + +      VectorCopy( a->origin, v ); +      break; + +    case AT_TAG: +      if( !a->tagValid ) +        return qfalse; + +      AxisCopy( axisDefault, a->re.axis ); +      CG_PositionRotatedEntityOnTag( &a->re, &a->parent, +                                     a->model, a->tagName ); +      VectorCopy( a->re.origin, v ); +      break; + +    case AT_CENT: +      if( !a->centValid ) +        return qfalse; + +      if( a->centNum == cg.predictedPlayerState.clientNum ) +      { +        // this is smoother if it's the local client +        VectorCopy( cg.predictedPlayerState.origin, v ); +      } +      else +      { +        cent = &cg_entities[ a->centNum ]; +        VectorCopy( cent->lerpOrigin, v ); +      } +      break; + +    case AT_PARTICLE: +      if( !a->particleValid ) +        return qfalse; + +      if( !a->particle->valid ) +      { +        a->particleValid = qfalse; +        return qfalse; +      } +      else +        VectorCopy( a->particle->origin, v ); +      break; + +    default: +      CG_Printf( S_COLOR_RED "ERROR: Invalid attachmentType_t in attachment\n" ); +      break; +  } + +  if( a->hasOffset ) +    VectorAdd( v, a->offset, v ); + +  VectorCopy( v, a->lastValidAttachmentPoint ); + +  return qtrue; +} + +/* +=============== +CG_AttachmentDir + +Return the attachment direction +=============== +*/ +qboolean CG_AttachmentDir( attachment_t *a, vec3_t v ) +{ +  vec3_t      forward; +  centity_t   *cent; + +  if( !a ) +    return qfalse; + +  switch( a->type ) +  { +    case AT_STATIC: +      return qfalse; +      break; + +    case AT_TAG: +      if( !a->tagValid ) +        return qfalse; + +      VectorCopy( a->re.axis[ 0 ], v ); +      break; + +    case AT_CENT: +      if( !a->centValid ) +        return qfalse; + +      cent = &cg_entities[ a->centNum ]; +      AngleVectors( cent->lerpAngles, forward, NULL, NULL ); +      VectorCopy( forward, v ); +      break; + +    case AT_PARTICLE: +      if( !a->particleValid ) +        return qfalse; + +      if( !a->particle->valid ) +      { +        a->particleValid = qfalse; +        return qfalse; +      } +      else +        VectorCopy( a->particle->velocity, v ); +      break; + +    default: +      CG_Printf( S_COLOR_RED "ERROR: Invalid attachmentType_t in attachment\n" ); +      break; +  } + +  VectorNormalize( v ); +  return qtrue; +} + +/* +=============== +CG_AttachmentAxis + +Return the attachment axis +=============== +*/ +qboolean CG_AttachmentAxis( attachment_t *a, vec3_t axis[ 3 ] ) +{ +  centity_t   *cent; + +  if( !a ) +    return qfalse; + +  switch( a->type ) +  { +    case AT_STATIC: +      return qfalse; +      break; + +    case AT_TAG: +      if( !a->tagValid ) +        return qfalse; + +      AxisCopy( a->re.axis, axis ); +      break; + +    case AT_CENT: +      if( !a->centValid ) +        return qfalse; + +      cent = &cg_entities[ a->centNum ]; +      AnglesToAxis( cent->lerpAngles, axis ); +      break; + +    case AT_PARTICLE: +      return qfalse; +      break; + +    default: +      CG_Printf( S_COLOR_RED "ERROR: Invalid attachmentType_t in attachment\n" ); +      break; +  } + +  return qtrue; +} + +/* +=============== +CG_AttachmentVelocity + +If the attachment can have velocity, return it +=============== +*/ +qboolean CG_AttachmentVelocity( attachment_t *a, vec3_t v ) +{ +  if( !a ) +    return qfalse; + +  if( a->particleValid && a->particle->valid ) +  { +    VectorCopy( a->particle->velocity, v ); +    return qtrue; +  } +  else if( a->centValid ) +  { +    centity_t *cent = &cg_entities[ a->centNum ]; + +    VectorCopy( cent->currentState.pos.trDelta, v ); +    return qtrue; +  } + +  return qfalse; +} + +/* +=============== +CG_AttachmentCentNum + +If the attachment has a centNum, return it +=============== +*/ +int CG_AttachmentCentNum( attachment_t *a ) +{ +  if( !a || !a->centValid ) +    return -1; + +  return a->centNum; +} + +/* +=============== +CG_Attached + +If the attachment is valid, return qtrue +=============== +*/ +qboolean CG_Attached( attachment_t *a ) +{ +  if( !a ) +    return qfalse; + +  return a->attached; +} + +/* +=============== +CG_AttachToPoint + +Attach to a point in space +=============== +*/ +void CG_AttachToPoint( attachment_t *a ) +{ +  if( !a || !a->staticValid ) +    return; + +  a->type = AT_STATIC; +  a->attached = qtrue; +} + +/* +=============== +CG_AttachToCent + +Attach to a centity_t +=============== +*/ +void CG_AttachToCent( attachment_t *a ) +{ +  if( !a || !a->centValid ) +    return; + +  a->type = AT_CENT; +  a->attached = qtrue; +} + +/* +=============== +CG_AttachToTag + +Attach to a model tag +=============== +*/ +void CG_AttachToTag( attachment_t *a ) +{ +  if( !a || !a->tagValid ) +    return; + +  a->type = AT_TAG; +  a->attached = qtrue; +} + +/* +=============== +CG_AttachToParticle + +Attach to a particle +=============== +*/ +void CG_AttachToParticle( attachment_t *a ) +{ +  if( !a || !a->particleValid ) +    return; + +  a->type = AT_PARTICLE; +  a->attached = qtrue; +} + +/* +=============== +CG_SetAttachmentPoint +=============== +*/ +void CG_SetAttachmentPoint( attachment_t *a, vec3_t v ) +{ +  if( !a ) +    return; + +  VectorCopy( v, a->origin ); +  a->staticValid = qtrue; +} + +/* +=============== +CG_SetAttachmentCent +=============== +*/ +void CG_SetAttachmentCent( attachment_t *a, centity_t *cent ) +{ +  if( !a || !cent ) +    return; + +  a->centNum = cent->currentState.number; +  a->centValid = qtrue; +} + +/* +=============== +CG_SetAttachmentTag +=============== +*/ +void CG_SetAttachmentTag( attachment_t *a, refEntity_t parent, +    qhandle_t model, char *tagName ) +{ +  if( !a ) +    return; + +  a->parent = parent; +  a->model = model; +  strncpy( a->tagName, tagName, MAX_STRING_CHARS ); +  a->tagValid = qtrue; +} + +/* +=============== +CG_SetAttachmentParticle +=============== +*/ +void CG_SetAttachmentParticle( attachment_t *a, particle_t *p ) +{ +  if( !a ) +    return; + +  a->particle = p; +  a->particleValid = qtrue; +} + +/* +=============== +CG_SetAttachmentOffset +=============== +*/ +void CG_SetAttachmentOffset( attachment_t *a, vec3_t v ) +{ +  if( !a ) +    return; + +  VectorCopy( v, a->offset ); +  a->hasOffset = qtrue; +} diff --git a/src/cgame/cg_buildable.c b/src/cgame/cg_buildable.c new file mode 100644 index 0000000..e7b4d85 --- /dev/null +++ b/src/cgame/cg_buildable.c @@ -0,0 +1,1423 @@ +/* +=========================================================================== +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" + +char *cg_buildableSoundNames[ MAX_BUILDABLE_ANIMATIONS ] = +{ +  "construct1.wav", +  "construct2.wav", +  "idle1.wav", +  "idle2.wav", +  "idle3.wav", +  "attack1.wav", +  "attack2.wav", +  "spawn1.wav", +  "spawn2.wav", +  "pain1.wav", +  "pain2.wav", +  "destroy1.wav", +  "destroy2.wav", +  "destroyed.wav" +}; + +static sfxHandle_t defaultAlienSounds[ MAX_BUILDABLE_ANIMATIONS ]; +static sfxHandle_t defaultHumanSounds[ MAX_BUILDABLE_ANIMATIONS ]; + +/* +=================== +CG_AlienBuildableExplosion + +Generated a bunch of gibs launching out from a location +=================== +*/ +void CG_AlienBuildableExplosion( vec3_t origin, vec3_t dir ) +{ +  particleSystem_t  *ps; + +  trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.alienBuildableExplosion ); + +  //particle system +  ps = CG_SpawnNewParticleSystem( cgs.media.alienBuildableDestroyedPS ); + +  if( CG_IsParticleSystemValid( &ps ) ) +  { +    CG_SetAttachmentPoint( &ps->attachment, origin ); +    CG_SetParticleSystemNormal( ps, dir ); +    CG_AttachToPoint( &ps->attachment ); +  } +} + +/* +================= +CG_HumanBuildableExplosion + +Called for human buildables as they are destroyed +================= +*/ +void CG_HumanBuildableExplosion( vec3_t origin, vec3_t dir ) +{ +  particleSystem_t  *ps; + +  trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.humanBuildableExplosion ); + +  //particle system +  ps = CG_SpawnNewParticleSystem( cgs.media.humanBuildableDestroyedPS ); + +  if( CG_IsParticleSystemValid( &ps ) ) +  { +    CG_SetAttachmentPoint( &ps->attachment, origin ); +    CG_SetParticleSystemNormal( ps, dir ); +    CG_AttachToPoint( &ps->attachment ); +  } +} + + +#define CREEP_SIZE            64.0f + +/* +================== +CG_Creep +================== +*/ +static void CG_Creep( centity_t *cent ) +{ +  int           msec; +  float         size, frac; +  trace_t       tr; +  vec3_t        temp, origin; +  int           scaleUpTime = BG_FindBuildTimeForBuildable( cent->currentState.modelindex ); +  int           time; + +  time = cent->currentState.time; + +  //should the creep be growing or receding? +  if( time >= 0 ) +  { +    msec = cg.time - time; +    if( msec >= 0 && msec < scaleUpTime ) +      frac = (float)msec / scaleUpTime; +    else +      frac = 1.0f; +  } +  else if( time < 0 ) +  { +    msec = cg.time + time; +    if( msec >= 0 && msec < CREEP_SCALEDOWN_TIME ) +      frac = 1.0f - ( (float)msec / CREEP_SCALEDOWN_TIME ); +    else +      frac = 0.0f; +  } + +  VectorCopy( cent->currentState.origin2, temp ); +  VectorScale( temp, -4096, temp ); +  VectorAdd( temp, cent->lerpOrigin, temp ); + +  CG_Trace( &tr, cent->lerpOrigin, NULL, NULL, temp, cent->currentState.number, MASK_PLAYERSOLID ); + +  VectorCopy( tr.endpos, origin ); + +  size = CREEP_SIZE * frac; + +  if( size > 0.0f ) +    CG_ImpactMark( cgs.media.creepShader, origin, cent->currentState.origin2, +                   0.0f, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, size, qtrue ); +} + +/* +====================== +CG_ParseBuildableAnimationFile + +Read a configuration file containing animation counts and rates +models/buildables/hivemind/animation.cfg, etc +====================== +*/ +static qboolean CG_ParseBuildableAnimationFile( const char *filename, buildable_t buildable ) +{ +  char          *text_p; +  int           len; +  int           i; +  char          *token; +  float         fps; +  char          text[ 20000 ]; +  fileHandle_t  f; +  animation_t   *animations; + +  animations = cg_buildables[ buildable ].animations; + +  // load the file +  len = trap_FS_FOpenFile( filename, &f, FS_READ ); +  if( len <= 0 ) +    return qfalse; + +  if( len >= sizeof( text ) - 1 ) +  { +    CG_Printf( "File %s too long\n", filename ); +    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 = BANIM_NONE + 1; i < MAX_BUILDABLE_ANIMATIONS; i++ ) +  { + +    token = COM_Parse( &text_p ); +    if( !*token ) +      break; + +    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_BUILDABLE_ANIMATIONS ) +  { +    CG_Printf( "Error parsing animation file: %s\n", filename ); +    return qfalse; +  } + +  return qtrue; +} + +/* +====================== +CG_ParseBuildableSoundFile + +Read a configuration file containing sound properties +sound/buildables/hivemind/sound.cfg, etc +====================== +*/ +static qboolean CG_ParseBuildableSoundFile( const char *filename, buildable_t buildable ) +{ +  char          *text_p; +  int           len; +  int           i; +  char          *token; +  char          text[ 20000 ]; +  fileHandle_t  f; +  sound_t       *sounds; + +  sounds = cg_buildables[ buildable ].sounds; + +  // load the file +  len = trap_FS_FOpenFile( filename, &f, FS_READ ); +  if ( len <= 0 ) +    return qfalse; + +  if ( len >= sizeof( text ) - 1 ) +  { +    CG_Printf( "File %s too long\n", filename ); +    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 = BANIM_NONE + 1; i < MAX_BUILDABLE_ANIMATIONS; i++ ) +  { + +    token = COM_Parse( &text_p ); +    if ( !*token ) +      break; + +    sounds[ i ].enabled = atoi( token ); + +    token = COM_Parse( &text_p ); +    if ( !*token ) +      break; + +    sounds[ i ].looped = atoi( token ); + +  } + +  if( i != MAX_BUILDABLE_ANIMATIONS ) +  { +    CG_Printf( "Error parsing sound file: %s\n", filename ); +    return qfalse; +  } + +  return qtrue; +} +/* +=============== +CG_InitBuildables + +Initialises the animation db +=============== +*/ +void CG_InitBuildables( void ) +{ +  char          filename[ MAX_QPATH ]; +  char          soundfile[ MAX_QPATH ]; +  char          *buildableName; +  char          *modelFile; +  int           i; +  int           j; +  fileHandle_t  f; + +  memset( cg_buildables, 0, sizeof( cg_buildables ) ); + +  //default sounds +  for( j = BANIM_NONE + 1; j < MAX_BUILDABLE_ANIMATIONS; j++ ) +  { +    strcpy( soundfile, cg_buildableSoundNames[ j - 1 ] ); + +    Com_sprintf( filename, sizeof( filename ), "sound/buildables/alien/%s", soundfile ); +    defaultAlienSounds[ j ] = trap_S_RegisterSound( filename, qfalse ); + +    Com_sprintf( filename, sizeof( filename ), "sound/buildables/human/%s", soundfile ); +    defaultHumanSounds[ j ] = trap_S_RegisterSound( filename, qfalse ); +  } + +  cg.buildablesFraction = 0.0f; + +  for( i = BA_NONE + 1; i < BA_NUM_BUILDABLES; i++ ) +  { +    buildableName = BG_FindNameForBuildable( i ); + +    //animation.cfg +    Com_sprintf( filename, sizeof( filename ), "models/buildables/%s/animation.cfg", buildableName ); +    if ( !CG_ParseBuildableAnimationFile( filename, i ) ) +      Com_Printf( S_COLOR_YELLOW "WARNING: failed to load animation file %s\n", filename ); + +    //sound.cfg +    Com_sprintf( filename, sizeof( filename ), "sound/buildables/%s/sound.cfg", buildableName ); +    if ( !CG_ParseBuildableSoundFile( filename, i ) ) +      Com_Printf( S_COLOR_YELLOW "WARNING: failed to load sound file %s\n", filename ); + +    //models +    for( j = 0; j <= 3; j++ ) +    { +      if( ( modelFile = BG_FindModelsForBuildable( i, j ) ) ) +        cg_buildables[ i ].models[ j ] = trap_R_RegisterModel( modelFile ); +    } + +    //sounds +    for( j = BANIM_NONE + 1; j < MAX_BUILDABLE_ANIMATIONS; j++ ) +    { +      strcpy( soundfile, cg_buildableSoundNames[ j - 1 ] ); +      Com_sprintf( filename, sizeof( filename ), "sound/buildables/%s/%s", buildableName, soundfile ); + +      if( cg_buildables[ i ].sounds[ j ].enabled ) +      { +        if( trap_FS_FOpenFile( filename, &f, FS_READ ) > 0 ) +        { +          //file exists so close it +          trap_FS_FCloseFile( f ); + +          cg_buildables[ i ].sounds[ j ].sound = trap_S_RegisterSound( filename, qfalse ); +        } +        else +        { +          //file doesn't exist - use default +          if( BG_FindTeamForBuildable( i ) == BIT_ALIENS ) +            cg_buildables[ i ].sounds[ j ].sound = defaultAlienSounds[ j ]; +          else +            cg_buildables[ i ].sounds[ j ].sound = defaultHumanSounds[ j ]; +        } +      } +    } + +    cg.buildablesFraction = (float)i / (float)( BA_NUM_BUILDABLES - 1 ); +    trap_UpdateScreen( ); +  } + +  cgs.media.teslaZapTS = CG_RegisterTrailSystem( "models/buildables/tesla/zap" ); +} + +/* +=============== +CG_SetBuildableLerpFrameAnimation + +may include ANIM_TOGGLEBIT +=============== +*/ +static void CG_SetBuildableLerpFrameAnimation( buildable_t buildable, lerpFrame_t *lf, int newAnimation ) +{ +  animation_t *anim; + +  lf->animationNumber = newAnimation; + +  if( newAnimation < 0 || newAnimation >= MAX_BUILDABLE_ANIMATIONS ) +    CG_Error( "Bad animation number: %i", newAnimation ); + +  anim = &cg_buildables[ buildable ].animations[ newAnimation ]; + +  //this item has just spawned so lf->frameTime will be zero +  if( !lf->animation ) +    lf->frameTime = cg.time + 1000; //1 sec delay before starting the spawn anim + +  lf->animation = anim; +  lf->animationTime = lf->frameTime + anim->initialLerp; + +  if( cg_debugAnim.integer ) +    CG_Printf( "Anim: %i\n", newAnimation ); +} + +/* +=============== +CG_RunBuildableLerpFrame + +Sets cg.snap, cg.oldFrame, and cg.backlerp +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", +                 newAnimation, lf->animationNumber, lf->animation ); + +    CG_SetBuildableLerpFrameAnimation( buildable, lf, newAnimation ); + +    if( !cg_buildables[ buildable ].sounds[ newAnimation ].looped && +        cg_buildables[ buildable ].sounds[ newAnimation ].enabled ) +    { +      if( cg_debugRandom.integer ) +        CG_Printf( "Sound for animation %d for a %s\n", +            newAnimation, BG_FindHumanNameForBuildable( buildable ) ); + +      trap_S_StartSound( cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, +        cg_buildables[ buildable ].sounds[ newAnimation ].sound ); +    } +  } + +  if( cg_buildables[ buildable ].sounds[ lf->animationNumber ].looped && +      cg_buildables[ buildable ].sounds[ lf->animationNumber ].enabled ) +    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; + +    // 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"); +    } +  } + +  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_BuildableAnimation +=============== +*/ +static void CG_BuildableAnimation( centity_t *cent, int *old, int *now, float *backLerp ) +{ +  entityState_t *es = ¢->currentState; + +  //if no animation is set default to idle anim +  if( cent->buildableAnim == BANIM_NONE ) +    cent->buildableAnim = es->torsoAnim; + +  //display the first frame of the construction anim if not yet spawned +  if( !( es->generic1 & B_SPAWNED_TOGGLEBIT ) ) +  { +    animation_t *anim = &cg_buildables[ es->modelindex ].animations[ BANIM_CONSTRUCT1 ]; + +    //so that when animation starts for real it has sensible numbers +    cent->lerpFrame.oldFrameTime = +      cent->lerpFrame.frameTime = +      cent->lerpFrame.animationTime = +      cg.time; + +    *old      = cent->lerpFrame.oldFrame = anim->firstFrame; +    *now      = cent->lerpFrame.frame    = anim->firstFrame; +    *backLerp = cent->lerpFrame.backlerp = 0.0f; + +    //ensure that an animation is triggered once the buildable has spawned +    cent->oldBuildableAnim = BANIM_NONE; +  } +  else +  { +    if( ( cent->oldBuildableAnim ^ es->legsAnim ) & ANIM_TOGGLEBIT ) +    { +      if( cg_debugAnim.integer ) +        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 ); + +      if( cent->buildableAnim == es->torsoAnim || es->legsAnim & ANIM_FORCEBIT ) +        cent->buildableAnim = cent->oldBuildableAnim = es->legsAnim; +      else +        cent->buildableAnim = cent->oldBuildableAnim = es->torsoAnim; +    } + +    CG_RunBuildableLerpFrame( cent ); + +    *old      = cent->lerpFrame.oldFrame; +    *now      = cent->lerpFrame.frame; +    *backLerp = cent->lerpFrame.backlerp; +  } +} + +#define TRACE_DEPTH 64.0f + +/* +=============== +CG_PositionAndOrientateBuildable +=============== +*/ +static void CG_PositionAndOrientateBuildable( const vec3_t angles, const vec3_t inOrigin, +                                              const vec3_t normal, const int skipNumber, +                                              const vec3_t mins, const vec3_t maxs, +                                              vec3_t outAxis[ 3 ], vec3_t outOrigin ) +{ +  vec3_t  forward, start, end; +  trace_t tr; + +  AngleVectors( angles, forward, NULL, NULL ); +  VectorCopy( normal, outAxis[ 2 ] ); +  ProjectPointOnPlane( outAxis[ 0 ], forward, outAxis[ 2 ] ); + +  if( !VectorNormalize( outAxis[ 0 ] ) ) +  { +    AngleVectors( angles, NULL, NULL, forward ); +    ProjectPointOnPlane( outAxis[ 0 ], forward, outAxis[ 2 ] ); +    VectorNormalize( outAxis[ 0 ] ); +  } + +  CrossProduct( outAxis[ 0 ], outAxis[ 2 ], outAxis[ 1 ] ); +  outAxis[ 1 ][ 0 ] = -outAxis[ 1 ][ 0 ]; +  outAxis[ 1 ][ 1 ] = -outAxis[ 1 ][ 1 ]; +  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 ) +  { +    //erm we missed completely - try again with a box trace +    CG_Trace( &tr, start, mins, maxs, end, skipNumber, MASK_PLAYERSOLID ); +  } + +  VectorMA( inOrigin, tr.fraction * -TRACE_DEPTH, normal, outOrigin ); +} + +/* +================== +CG_GhostBuildable +================== +*/ +void CG_GhostBuildable( buildable_t buildable ) +{ +  refEntity_t     ent; +  playerState_t   *ps; +  vec3_t          angles, entity_origin; +  vec3_t          mins, maxs; +  trace_t         tr; +  float           scale; + +  ps = &cg.predictedPlayerState; + +  memset( &ent, 0, sizeof( ent ) ); + +  BG_FindBBoxForBuildable( buildable, mins, maxs ); + +  BG_PositionBuildableRelativeToPlayer( ps, mins, maxs, CG_Trace, entity_origin, angles, &tr ); + +  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 ); + +  VectorCopy( ent.origin, ent.lightingOrigin ); +  VectorCopy( ent.origin, ent.oldorigin ); // don't positionally lerp at all + +  ent.hModel = cg_buildables[ buildable ].models[ 0 ]; + +  if( ps->stats[ STAT_BUILDABLE ] & SB_VALID_TOGGLEBIT ) +    ent.customShader = cgs.media.greenBuildShader; +  else +    ent.customShader = cgs.media.redBuildShader; + +  //rescale the model +  scale = BG_FindModelScaleForBuildable( buildable ); + +  if( scale != 1.0f ) +  { +    VectorScale( ent.axis[ 0 ], scale, ent.axis[ 0 ] ); +    VectorScale( ent.axis[ 1 ], scale, ent.axis[ 1 ] ); +    VectorScale( ent.axis[ 2 ], scale, ent.axis[ 2 ] ); + +    ent.nonNormalizedAxes = qtrue; +  } +  else +    ent.nonNormalizedAxes = qfalse; + +  // add to refresh list +  trap_R_AddRefEntityToScene( &ent ); +} + +/* +================== +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; + +  if( !( es->generic1 & B_SPAWNED_TOGGLEBIT ) ) +    return; + +  if( team == BIT_HUMANS ) +  { +    if( healthFrac < 0.33f && !CG_IsParticleSystemValid( ¢->buildablePS ) ) +    { +      cent->buildablePS = CG_SpawnNewParticleSystem( cgs.media.humanBuildableDamagedPS ); + +      if( CG_IsParticleSystemValid( ¢->buildablePS ) ) +      { +        CG_SetAttachmentCent( ¢->buildablePS->attachment, cent ); +        CG_AttachToCent( ¢->buildablePS->attachment ); +      } +    } +    else if( healthFrac >= 0.33f && CG_IsParticleSystemValid( ¢->buildablePS ) ) +      CG_DestroyParticleSystem( ¢->buildablePS ); +  } +  else if( team == BIT_ALIENS ) +  { +    if( healthFrac < 0.33f && !CG_IsParticleSystemValid( ¢->buildablePS ) ) +    { +      cent->buildablePS = CG_SpawnNewParticleSystem( cgs.media.alienBuildableDamagedPS ); + +      if( CG_IsParticleSystemValid( ¢->buildablePS ) ) +      { +        CG_SetAttachmentCent( ¢->buildablePS->attachment, cent ); +        CG_SetParticleSystemNormal( cent->buildablePS, es->origin2 ); +        CG_AttachToCent( ¢->buildablePS->attachment ); +      } +    } +    else if( healthFrac >= 0.33f && CG_IsParticleSystemValid( ¢->buildablePS ) ) +      CG_DestroyParticleSystem( ¢->buildablePS ); +  } +} + + +void CG_BuildableStatusParse( const char *filename, buildStat_t *bs ) +{ +  pc_token_t token; +  int        handle; +  const char *s; +  int        i; +  float      f; +  vec4_t     c; + +  handle = trap_Parse_LoadSource( filename ); +  if( !handle ) +    return; +  while( 1 ) +  { +    if( !trap_Parse_ReadToken( handle, &token ) ) +      break; +    if( !Q_stricmp( token.string, "frameShader" ) ) +    { +      if( PC_String_Parse( handle, &s ) ) +        bs->frameShader = trap_R_RegisterShader( s ); +      continue; +    } +    else if( !Q_stricmp( token.string, "overlayShader" ) ) +    { +      if( PC_String_Parse( handle, &s ) ) +        bs->overlayShader = trap_R_RegisterShader( s ); +      continue; +    } +    else if( !Q_stricmp( token.string, "noPowerShader" ) ) +    { +      if( PC_String_Parse( handle, &s ) ) +        bs->noPowerShader = trap_R_RegisterShader( s ); +      continue; +    } +    else if( !Q_stricmp( token.string, "markedShader" ) ) +    { +      if( PC_String_Parse( handle, &s ) ) +        bs->markedShader = trap_R_RegisterShader( s ); +      continue; +    } +    else if( !Q_stricmp( token.string, "healthSevereColor" ) ) +    { +      if( PC_Color_Parse( handle, &c ) ) +        Vector4Copy( c, bs->healthSevereColor ); +      continue; +    } +    else if( !Q_stricmp( token.string, "healthHighColor" ) ) +    { +      if( PC_Color_Parse( handle, &c ) ) +        Vector4Copy( c, bs->healthHighColor ); +      continue; +    } +    else if( !Q_stricmp( token.string, "healthElevatedColor" ) ) +    { +      if( PC_Color_Parse( handle, &c ) ) +        Vector4Copy( c, bs->healthElevatedColor ); +      continue; +    } +    else if( !Q_stricmp( token.string, "healthGuardedColor" ) ) +    { +      if( PC_Color_Parse( handle, &c ) ) +        Vector4Copy( c, bs->healthGuardedColor ); +      continue; +    } +    else if( !Q_stricmp( token.string, "healthLowColor" ) ) +    { +      if( PC_Color_Parse( handle, &c ) ) +        Vector4Copy( c, bs->healthLowColor ); +      continue; +    } +    else if( !Q_stricmp( token.string, "foreColor" ) ) +    { +      if( PC_Color_Parse( handle, &c ) ) +        Vector4Copy( c, bs->foreColor ); +      continue; +    } +    else if( !Q_stricmp( token.string, "backColor" ) ) +    { +      if( PC_Color_Parse( handle, &c ) ) +        Vector4Copy( c, bs->backColor ); +      continue; +    } +    else if( !Q_stricmp( token.string, "frameHeight" ) ) +    { +      if( PC_Int_Parse( handle, &i ) ) +        bs->frameHeight = i; +      continue; +    } +    else if( !Q_stricmp( token.string, "frameWidth" ) ) +    { +      if( PC_Int_Parse( handle, &i ) ) +        bs->frameWidth = i; +      continue; +    } +    else if( !Q_stricmp( token.string, "healthPadding" ) ) +    { +      if( PC_Int_Parse( handle, &i ) ) +        bs->healthPadding = i; +      continue; +    } +    else if( !Q_stricmp( token.string, "overlayHeight" ) ) +    { +      if( PC_Int_Parse( handle, &i ) ) +        bs->overlayHeight = i; +      continue; +    } +    else if( !Q_stricmp( token.string, "overlayWidth" ) ) +    { +      if( PC_Int_Parse( handle, &i ) ) +        bs->overlayWidth = i; +      continue; +    } +    else if( !Q_stricmp( token.string, "verticalMargin" ) ) +    { +      if( PC_Float_Parse( handle, &f ) ) +        bs->verticalMargin = f; +      continue; +    } +    else if( !Q_stricmp( token.string, "horizontalMargin" ) ) +    { +      if( PC_Float_Parse( handle, &f ) ) +        bs->horizontalMargin = f; +      continue; +    } +    else +    { +      Com_Printf("CG_BuildableStatusParse: unknown token %s in %s\n", +        token.string, filename ); +      bs->loaded = qfalse; +      return; +    } +  } +  bs->loaded = qtrue; +} + +#define STATUS_FADE_TIME      200 +#define STATUS_MAX_VIEW_DIST  900.0f +#define STATUS_PEEK_DIST      20 +/* +================== +CG_BuildableStatusDisplay +================== +*/ +static void CG_BuildableStatusDisplay( centity_t *cent ) +{ +  entityState_t   *es = ¢->currentState; +  vec3_t          origin; +  float           healthScale; +  int             health; +  float           x, y; +  vec4_t          color; +  qboolean        powered, marked; +  trace_t         tr; +  float           d; +  buildStat_t     *bs; +  int             i, j; +  int             entNum; +  vec3_t          trOrigin; +  vec3_t          right; +  qboolean        visible = qfalse; +  vec3_t          mins, maxs; +  entityState_t   *hit; + +  if( BG_FindTeamForBuildable( es->modelindex ) == BIT_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 ); + +  VectorCopy( cent->lerpOrigin, origin ); + +  // center point +  origin[ 2 ] += mins[ 2 ]; +  origin[ 2 ] += ( abs( mins[ 2 ] ) + abs( maxs[ 2 ] ) ) / 2; + +  entNum = cg.predictedPlayerState.clientNum; + +  // if first try fails, step left, step right +  for( j = 0; j < 3; j++ ) +  { +    VectorCopy( cg.refdef.vieworg, trOrigin ); +    switch( j ) +    { +      case 1: +        // step right +        AngleVectors( cg.refdefViewAngles, NULL, right, NULL ); +        VectorMA( trOrigin, STATUS_PEEK_DIST, right, trOrigin ); +        break; +      case 2: +        // step left +        AngleVectors( cg.refdefViewAngles, NULL, right, NULL ); +        VectorMA( trOrigin, -STATUS_PEEK_DIST, right, trOrigin ); +        break; +      default: +        break; +    } +    // look through up to 3 players and/or transparent buildables +    for( i = 0; i < 3; i++ ) +    { +      CG_Trace( &tr, trOrigin, NULL, NULL, origin, entNum, MASK_SHOT ); +      if( tr.entityNum == cent->currentState.number ) +      { +        visible = qtrue; +        break; +      } + +      if( tr.entityNum == ENTITYNUM_WORLD ) +        break; + +      hit  = &cg_entities[ tr.entityNum ].currentState; + +      if( tr.entityNum < MAX_CLIENTS || ( hit->eType == ET_BUILDABLE && +          ( !( es->generic1 & B_SPAWNED_TOGGLEBIT ) || +            BG_FindTransparentTestForBuildable( hit->modelindex ) ) ) ) +      { +        entNum = tr.entityNum; +        VectorCopy( tr.endpos, trOrigin ); +      } +      else +        break; +    } +  } +  // hack to make the kit obscure view +  if( cg_drawGun.integer && visible && +      cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_HUMANS && +      CG_WorldToScreen( origin, &x, &y ) ) +  { +    if( x > 450 && y > 290 ) +      visible = qfalse; +  } + +  if( !visible && cent->buildableStatus.visible ) +  { +    cent->buildableStatus.visible   = qfalse; +    cent->buildableStatus.lastTime  = cg.time; +  } +  else if( visible && !cent->buildableStatus.visible ) +  { +    cent->buildableStatus.visible   = qtrue; +    cent->buildableStatus.lastTime  = cg.time; +  } + +  // Fade up +  if( cent->buildableStatus.visible ) +  { +    if( cent->buildableStatus.lastTime + STATUS_FADE_TIME > cg.time ) +      color[ 3 ] = (float)( cg.time - cent->buildableStatus.lastTime ) / STATUS_FADE_TIME; +  } + +  // Fade down +  if( !cent->buildableStatus.visible ) +  { +    if( cent->buildableStatus.lastTime + STATUS_FADE_TIME > cg.time ) +      color[ 3 ] = 1.0f - (float)( cg.time - cent->buildableStatus.lastTime ) / STATUS_FADE_TIME; +    else +      return; +  } + +  health = es->generic1 & B_HEALTH_MASK; +  healthScale = (float)health / B_HEALTH_MASK; + +  if( health > 0 && healthScale < 0.01f ) +    healthScale = 0.01f; +  else if( healthScale < 0.0f ) +    healthScale = 0.0f; +  else if( healthScale > 1.0f ) +    healthScale = 1.0f; + +  if( CG_WorldToScreen( origin, &x, &y ) ) +  { +    float  picH = bs->frameHeight; +    float  picW = bs->frameWidth; +    float  picX = x; +    float  picY = y; +    float  scale; +    float  subH, subY; +    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; + +    picH *= scale; +    picW *= scale; +    picX -= ( picW * 0.5f ); +    picY -= ( picH * 0.5f ); + +    // sub-elements such as icons and number +    subH = picH - ( picH * bs->verticalMargin ); +    subY = picY + ( picH * 0.5f ) - ( subH * 0.5f ); + +    if( bs->frameShader ) +    { +      Vector4Copy( bs->backColor, frameColor ); +      frameColor[ 3 ] = color[ 3 ]; +      trap_R_SetColor( frameColor ); +      CG_DrawPic( picX, picY, picW, picH, bs->frameShader ); +      trap_R_SetColor( NULL ); +    } + +    if( health > 0 ) +    { +      float hX, hY, hW, hH; +      vec4_t healthColor; + +      hX = picX + ( bs->healthPadding * scale ); +      hY = picY + ( bs->healthPadding * scale ); +      hH = picH - ( bs->healthPadding * 2.0f * scale ); +      hW = picW * healthScale - ( bs->healthPadding * 2.0f * scale ); + +      if( healthScale == 1.0f ) +        Vector4Copy( bs->healthLowColor, healthColor ); +      else if( healthScale >= 0.75f ) +        Vector4Copy( bs->healthGuardedColor, healthColor ); +      else if( healthScale >= 0.50f ) +        Vector4Copy( bs->healthElevatedColor, healthColor ); +      else if( healthScale >= 0.25f ) +        Vector4Copy( bs->healthHighColor, healthColor ); +      else +        Vector4Copy( bs->healthSevereColor, healthColor ); + +      healthColor[ 3 ] = color[ 3 ]; +      trap_R_SetColor( healthColor ); +      +      CG_DrawPic( hX, hY, hW, hH, cgs.media.whiteShader ); +      trap_R_SetColor( NULL ); +    } + +    if( bs->overlayShader ) +    { +      float oW = bs->overlayWidth; +      float oH = bs->overlayHeight; +      float oX = x; +      float oY = y; + +      oH *= scale; +      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 ); +    } + +    trap_R_SetColor( color ); +    if( !powered ) +    { +      float pX; + +      pX = picX + ( subH * bs->horizontalMargin ); +      CG_DrawPic( pX, subY, subH, subH, bs->noPowerShader ); +    } + +    if( marked ) +    { +      float mX; + +      mX = picX + picW - ( subH * bs->horizontalMargin ) - subH; +      CG_DrawPic( mX, subY, subH, subH, bs->markedShader ); +    } + +    { +      float nX; +      int healthMax; +      int healthPoints; + +      healthMax = BG_FindHealthForBuildable( es->modelindex ); +      healthPoints = (int)( healthScale * healthMax ); +      if( health > 0 && healthPoints < 1 ) +        healthPoints = 1; +      nX = picX + ( picW * 0.5f ) - 2.0f - ( ( subH * 4 ) * 0.5f );  +        +      if( healthPoints > 999 ) +        nX -= 0.0f; +      else if( healthPoints > 99 ) +        nX -= subH * 0.5f; +      else if( healthPoints > 9 ) +        nX -= subH * 1.0f; +      else +        nX -= subH * 1.5f; +      +      CG_DrawField( nX, subY, 4, subH, subH, healthPoints ); +    } +    trap_R_SetColor( NULL ); +  } +} + +static int QDECL CG_SortDistance( const void *a, const void *b ) +{ +  centity_t    *aent, *bent; +  float        adist, bdist; + +  aent = &cg_entities[ *(int *)a ]; +  bent = &cg_entities[ *(int *)b ]; +  adist = Distance( cg.refdef.vieworg, aent->lerpOrigin ); +  bdist = Distance( cg.refdef.vieworg, bent->lerpOrigin ); +  if( adist > bdist ) +    return -1; +  else if( adist < bdist ) +    return 1; +  else +    return 0; +} + +/* +================== +CG_DrawBuildableStatus +================== +*/ +void CG_DrawBuildableStatus( void ) +{ +  int             i; +  centity_t       *cent; +  entityState_t   *es; +  int             buildableList[ MAX_ENTITIES_IN_SNAPSHOT ]; +  int             buildables = 0; + +  switch( cg.predictedPlayerState.weapon ) +  { +    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; + +    default: +      break; +  } +} + +#define BUILDABLE_SOUND_PERIOD  500 + +/* +================== +CG_Buildable +================== +*/ +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 ); +  float           scale; +  int             health; +  float           healthScale; + +  //must be before EF_NODRAW check +  if( team == BIT_ALIENS ) +    CG_Creep( cent ); + +  // if set to invisible, skip +  if( es->eFlags & EF_NODRAW ) +  { +    if( CG_IsParticleSystemValid( ¢->buildablePS ) ) +      CG_DestroyParticleSystem( ¢->buildablePS ); + +    return; +  } + +  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 ); + +  if( es->pos.trType == TR_STATIONARY ) +    CG_PositionAndOrientateBuildable( angles, ent.origin, surfNormal, es->number, +                                      mins, maxs, ent.axis, ent.origin ); + +  //offset on the Z axis if required +  VectorMA( ent.origin, BG_FindZOffsetForBuildable( es->modelindex ), 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 ) ) +  { +    sfxHandle_t prebuildSound = cgs.media.humanBuildablePrebuild; + +    if( team == BIT_HUMANS ) +    { +      ent.customShader = cgs.media.humanSpawningShader; +      prebuildSound = cgs.media.humanBuildablePrebuild; +    } +    else if( team == BIT_ALIENS ) +      prebuildSound = cgs.media.alienBuildablePrebuild; + +    trap_S_AddLoopingSound( es->number, cent->lerpOrigin, vec3_origin, prebuildSound ); +  } + +  CG_BuildableAnimation( cent, &ent.oldframe, &ent.frame, &ent.backlerp ); + +  //rescale the model +  scale = BG_FindModelScaleForBuildable( es->modelindex ); + +  if( scale != 1.0f ) +  { +    VectorScale( ent.axis[ 0 ], scale, ent.axis[ 0 ] ); +    VectorScale( ent.axis[ 1 ], scale, ent.axis[ 1 ] ); +    VectorScale( ent.axis[ 2 ], scale, ent.axis[ 2 ] ); + +    ent.nonNormalizedAxes = qtrue; +  } +  else +    ent.nonNormalizedAxes = qfalse; + + +  //add to refresh list +  trap_R_AddRefEntityToScene( &ent ); + +  CrossProduct( surfNormal, refNormal, xNormal ); +  VectorNormalize( xNormal ); +  rotAngle = RAD2DEG( acos( DotProduct( surfNormal, refNormal ) ) ); + +  //turret barrel bit +  if( cg_buildables[ es->modelindex ].models[ 1 ] ) +  { +    refEntity_t turretBarrel; +    vec3_t      flatAxis[ 3 ]; + +    memset( &turretBarrel, 0, sizeof( turretBarrel ) ); + +    turretBarrel.hModel = cg_buildables[ es->modelindex ].models[ 1 ]; + +    CG_PositionEntityOnTag( &turretBarrel, &ent, ent.hModel, "tag_turret" ); +    VectorCopy( cent->lerpOrigin, turretBarrel.lightingOrigin ); +    AnglesToAxis( es->angles2, flatAxis ); + +    RotatePointAroundVector( turretBarrel.axis[ 0 ], xNormal, flatAxis[ 0 ], -rotAngle ); +    RotatePointAroundVector( turretBarrel.axis[ 1 ], xNormal, flatAxis[ 1 ], -rotAngle ); +    RotatePointAroundVector( turretBarrel.axis[ 2 ], xNormal, flatAxis[ 2 ], -rotAngle ); + +    turretBarrel.oldframe = ent.oldframe; +    turretBarrel.frame    = ent.frame; +    turretBarrel.backlerp = ent.backlerp; + +    turretBarrel.customShader = ent.customShader; + +    if( scale != 1.0f ) +    { +      VectorScale( turretBarrel.axis[ 0 ], scale, turretBarrel.axis[ 0 ] ); +      VectorScale( turretBarrel.axis[ 1 ], scale, turretBarrel.axis[ 1 ] ); +      VectorScale( turretBarrel.axis[ 2 ], scale, turretBarrel.axis[ 2 ] ); + +      turretBarrel.nonNormalizedAxes = qtrue; +    } +    else +      turretBarrel.nonNormalizedAxes = qfalse; + +    trap_R_AddRefEntityToScene( &turretBarrel ); +  } + +  //turret barrel bit +  if( cg_buildables[ es->modelindex ].models[ 2 ] ) +  { +    refEntity_t turretTop; +    vec3_t      flatAxis[ 3 ]; +    vec3_t      swivelAngles; + +    memset( &turretTop, 0, sizeof( turretTop ) ); + +    VectorCopy( es->angles2, swivelAngles ); +    swivelAngles[ PITCH ] = 0.0f; + +    turretTop.hModel = cg_buildables[ es->modelindex ].models[ 2 ]; + +    CG_PositionRotatedEntityOnTag( &turretTop, &ent, ent.hModel, "tag_turret" ); +    VectorCopy( cent->lerpOrigin, turretTop.lightingOrigin ); +    AnglesToAxis( swivelAngles, flatAxis ); + +    RotatePointAroundVector( turretTop.axis[ 0 ], xNormal, flatAxis[ 0 ], -rotAngle ); +    RotatePointAroundVector( turretTop.axis[ 1 ], xNormal, flatAxis[ 1 ], -rotAngle ); +    RotatePointAroundVector( turretTop.axis[ 2 ], xNormal, flatAxis[ 2 ], -rotAngle ); + +    turretTop.oldframe = ent.oldframe; +    turretTop.frame    = ent.frame; +    turretTop.backlerp = ent.backlerp; + +    turretTop.customShader = ent.customShader; + +    if( scale != 1.0f ) +    { +      VectorScale( turretTop.axis[ 0 ], scale, turretTop.axis[ 0 ] ); +      VectorScale( turretTop.axis[ 1 ], scale, turretTop.axis[ 1 ] ); +      VectorScale( turretTop.axis[ 2 ], scale, turretTop.axis[ 2 ] ); + +      turretTop.nonNormalizedAxes = qtrue; +    } +    else +      turretTop.nonNormalizedAxes = qfalse; + +    trap_R_AddRefEntityToScene( &turretTop ); +  } + +  //weapon effects for turrets +  if( es->eFlags & EF_FIRING ) +  { +    weaponInfo_t  *weapon = &cg_weapons[ es->weapon ]; + +    if( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME || +        BG_FindProjTypeForBuildable( es->modelindex ) == WP_TESLAGEN ) +    { +      if( weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 0 ] || +          weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 1 ] || +          weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 2 ] ) +      { +        trap_R_AddLightToScene( cent->lerpOrigin, 300 + ( rand( ) & 31 ), +            weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 0 ], +            weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 1 ], +            weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 2 ] ); +      } +    } + +    if( weapon->wim[ WPM_PRIMARY ].firingSound ) +    { +      trap_S_AddLoopingSound( es->number, cent->lerpOrigin, vec3_origin, +          weapon->wim[ WPM_PRIMARY ].firingSound ); +    } +    else if( weapon->readySound ) +      trap_S_AddLoopingSound( es->number, cent->lerpOrigin, vec3_origin, weapon->readySound ); +  } + +  health = es->generic1 & B_HEALTH_MASK; +  healthScale = (float)health / B_HEALTH_MASK; + +  if( healthScale < cent->lastBuildableHealthScale && ( es->generic1 & B_SPAWNED_TOGGLEBIT ) ) +  { +    if( cent->lastBuildableDamageSoundTime + BUILDABLE_SOUND_PERIOD < cg.time ) +    { +      if( team == BIT_HUMANS ) +      { +        int i = rand( ) % 4; +        trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.humanBuildableDamage[ i ] ); +      } +      else if( team == BIT_ALIENS ) +        trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.alienBuildableDamage ); + +      cent->lastBuildableDamageSoundTime = cg.time; +    } +  } + +  cent->lastBuildableHealthScale = healthScale; + +  //smoke etc for damaged buildables +  CG_BuildableParticleEffects( cent ); +} diff --git a/src/cgame/cg_consolecmds.c b/src/cgame/cg_consolecmds.c new file mode 100644 index 0000000..68aab6c --- /dev/null +++ b/src/cgame/cg_consolecmds.c @@ -0,0 +1,325 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +// 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 + +Keybinding command +================= +*/ +static void CG_SizeUp_f( void ) +{ +  trap_Cvar_Set( "cg_viewsize", va( "%i", (int)( cg_viewsize.integer + 10 ) ) ); +} + + +/* +================= +CG_SizeDown_f + +Keybinding command +================= +*/ +static void CG_SizeDown_f( void ) +{ +  trap_Cvar_Set( "cg_viewsize", va( "%i", (int)( cg_viewsize.integer - 10 ) ) ); +} + + +/* +============= +CG_Viewpos_f + +Debugging command to print the current position +============= +*/ +static void CG_Viewpos_f( void ) +{ +  CG_Printf( "(%i %i %i) : %i\n", (int)cg.refdef.vieworg[ 0 ], +    (int)cg.refdef.vieworg[ 1 ], (int)cg.refdef.vieworg[ 2 ], +    (int)cg.refdefViewAngles[ YAW ] ); +} + +qboolean CG_RequestScores( void ) +{ +  if( cg.scoresRequestTime + 2000 < cg.time ) +  { +    // 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; +  } +  else +    return qfalse; +} + +extern menuDef_t *menuScoreboard; + +static void CG_scrollScoresDown_f( void ) +{ +  if( menuScoreboard && cg.scoreBoardShowing ) +  { +    Menu_ScrollFeeder( menuScoreboard, FEEDER_ALIENTEAM_LIST, qtrue ); +    Menu_ScrollFeeder( menuScoreboard, FEEDER_HUMANTEAM_LIST, qtrue ); +  } +} + + +static void CG_scrollScoresUp_f( void ) +{ +  if( menuScoreboard && cg.scoreBoardShowing ) +  { +    Menu_ScrollFeeder( menuScoreboard, FEEDER_ALIENTEAM_LIST, qfalse ); +    Menu_ScrollFeeder( menuScoreboard, FEEDER_HUMANTEAM_LIST, qfalse ); +  } +} + +static void CG_ScoresDown_f( void ) +{ +  if( !cg.showScores ) +  { +    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; +    } +  } +  else +  { +    // show the cached contents even if they just pressed if it +    // is within two seconds +    cg.showScores = qtrue; +  } +} + +static void CG_ScoresUp_f( void ) +{ +  if( cg.showScores ) +  { +    cg.showScores = qfalse; +    cg.scoreFadeTime = cg.time; +  } +} + +static void CG_TellTarget_f( void ) +{ +  int   clientNum; +  char  command[ 128 ]; +  char  message[ 128 ]; + +  clientNum = CG_CrosshairPlayer( ); +  if( clientNum == -1 ) +    return; + +  trap_Args( message, 128 ); +  Com_sprintf( command, 128, "tell %i %s", clientNum, message ); +  trap_SendClientCommand( command ); +} + +static void CG_TellAttacker_f( void ) +{ +  int   clientNum; +  char  command[ 128 ]; +  char  message[ 128 ]; + +  clientNum = CG_LastAttacker( ); +  if( clientNum == -1 ) +    return; + +  trap_Args( message, 128 ); +  Com_sprintf( command, 128, "tell %i %s", clientNum, message ); +  trap_SendClientCommand( command ); +} + +typedef struct +{ +  char  *cmd; +  void  (*function)( void ); +} consoleCommand_t; + +static consoleCommand_t commands[ ] = +{ +  { "testgun", CG_TestGun_f }, +  { "testmodel", CG_TestModel_f }, +  { "nextframe", CG_TestModelNextFrame_f }, +  { "prevframe", CG_TestModelPrevFrame_f }, +  { "nextskin", CG_TestModelNextSkin_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 }, +  { "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 }, +  { "testPS", CG_TestPS_f }, +  { "destroyTestPS", CG_DestroyTestPS_f }, +  { "testTS", CG_TestTS_f }, +  { "destroyTestTS", CG_DestroyTestTS_f }, +}; + + +/* +================= +CG_ConsoleCommand + +The string has been tokenized and can be retrieved with +Cmd_Argc() / Cmd_Argv() +================= +*/ +qboolean CG_ConsoleCommand( void ) +{ +  const char  *cmd; +  const char  *arg1; +  int         i; + +  cmd = CG_Argv( 0 ); + +  //TA: ugly hacky special case +  if( !Q_stricmp( cmd, "ui_menu" ) ) +  { +    arg1 = CG_Argv( 1 ); +    trap_SendConsoleCommand( va( "menu %s\n", arg1 ) ); +    return qtrue; +  } + +  for( i = 0; i < sizeof( commands ) / sizeof( commands[ 0 ] ); i++ ) +  { +    if( !Q_stricmp( cmd, commands[ i ].cmd ) ) +    { +      commands[ i ].function( ); +      return qtrue; +    } +  } + +  return qfalse; +} + + +/* +================= +CG_InitConsoleCommands + +Let the client system know about all of our commands +so it can perform tab completion +================= +*/ +void CG_InitConsoleCommands( void ) +{ +  int   i; + +  for( i = 0 ; i < sizeof( commands ) / sizeof( commands[ 0 ] ) ; i++ ) +    trap_AddCommand( commands[ i ].cmd ); + +  // +  // the game server will interpret these commands, which will be automatically +  // forwarded to the server after they are not recognized locally +  // +  trap_AddCommand( "kill" ); +  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( "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" ); +  trap_AddCommand( "sell" ); +  trap_AddCommand( "reload" ); +  trap_AddCommand( "itemact" ); +  trap_AddCommand( "itemdeact" ); +  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" ); +} diff --git a/src/cgame/cg_draw.c b/src/cgame/cg_draw.c new file mode 100644 index 0000000..82df413 --- /dev/null +++ b/src/cgame/cg_draw.c @@ -0,0 +1,3524 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +// cg_draw.c -- draw all of the graphical elements during +// active (after loading) gameplay + + +#include "cg_local.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 ) +{ +  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; + +  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 ) +  { +    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++; +      } +    } +  } + +  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 ) +  { +    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; +} + +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 ); +} + +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; + +  if( scale <= cg_smallFont.value ) +    font = &cgDC.Assets.smallFont; +  else if( scale > cg_bigFont.value ) +    font = &cgDC.Assets.bigFont; + +  useScale = scale * font->glyphScale; +  if( text ) +  { +// 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 + +      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 ); +        } + + +        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; +        s++; +        count++; +      } +    } + +    trap_R_SetColor( NULL ); +  } +} + +/* +============== +CG_DrawFieldPadded + +Draws large numbers for status bar and powerups +============== +*/ +static void CG_DrawFieldPadded( int x, int y, int width, int cw, int ch, int value ) +{ +  char  num[ 16 ], *ptr; +  int   l, orgL; +  int   frame; +  int   charWidth, charHeight; + +  if( !( charWidth = cw ) ) +    charWidth = CHAR_WIDTH; + +  if( !( charHeight = ch ) ) +    charWidth = CHAR_HEIGHT; + +  if( width < 1 ) +    return; + +  // draw number string +  if( width > 4 ) +    width = 4; + +  switch( width ) +  { +    case 1: +      value = value > 9 ? 9 : value; +      value = value < 0 ? 0 : value; +      break; +    case 2: +      value = value > 99 ? 99 : value; +      value = value < -9 ? -9 : value; +      break; +    case 3: +      value = value > 999 ? 999 : value; +      value = value < -99 ? -99 : value; +      break; +    case 4: +      value = value > 9999 ? 9999 : value; +      value = value < -999 ? -999 : value; +      break; +  } + +  Com_sprintf( num, sizeof( num ), "%d", value ); +  l = strlen( num ); + +  if( l > width ) +    l = width; + +  orgL = l; + +  x += 2; + +  ptr = num; +  while( *ptr && l ) +  { +    if( width > orgL ) +    { +      CG_DrawPic( x,y, charWidth, charHeight, cgs.media.numberShaders[ 0 ] ); +      width--; +      x += charWidth; +      continue; +    } + +    if( *ptr == '-' ) +      frame = STAT_MINUS; +    else +      frame = *ptr - '0'; + +    CG_DrawPic( x,y, charWidth, charHeight, cgs.media.numberShaders[ frame ] ); +    x += charWidth; +    ptr++; +    l--; +  } +} + +/* +============== +CG_DrawField + +Draws large numbers for status bar and powerups +============== +*/ +void CG_DrawField( float x, float y, int width, float cw, float ch, int value ) +{ +  char  num[ 16 ], *ptr; +  int   l; +  int   frame; +  float charWidth, charHeight; + +  if( !( charWidth = cw ) ) +    charWidth = CHAR_WIDTH; + +  if( !( charHeight = ch ) ) +    charWidth = CHAR_HEIGHT; + +  if( width < 1 ) +    return; + +  // draw number string +  if( width > 4 ) +    width = 4; + +  switch( width ) +  { +    case 1: +      value = value > 9 ? 9 : value; +      value = value < 0 ? 0 : value; +      break; +    case 2: +      value = value > 99 ? 99 : value; +      value = value < -9 ? -9 : value; +      break; +    case 3: +      value = value > 999 ? 999 : value; +      value = value < -99 ? -99 : value; +      break; +    case 4: +      value = value > 9999 ? 9999 : value; +      value = value < -999 ? -999 : value; +      break; +  } + +  Com_sprintf( num, sizeof( num ), "%d", value ); +  l = strlen( num ); + +  if( l > width ) +    l = width; + +  x += 2 + charWidth * ( width - l ); + +  ptr = num; +  while( *ptr && l ) +  { +    if( *ptr == '-' ) +      frame = STAT_MINUS; +    else +      frame = *ptr -'0'; + +    CG_DrawPic( x,y, charWidth, charHeight, cgs.media.numberShaders[ frame ] ); +    x += charWidth; +    ptr++; +    l--; +  } +} + +static void CG_DrawProgressBar( rectDef_t *rect, vec4_t color, float scale, +                                int align, int textStyle, int special, float progress ) +{ +  float   rimWidth = rect->h / 20.0f; +  float   doneWidth, leftWidth; +  float   tx, ty, tw, th; +  char    textBuffer[ 8 ]; + +  if( rimWidth < 0.6f ) +    rimWidth = 0.6f; + +  if( special >= 0.0f ) +    rimWidth = special; + +  if( progress < 0.0f ) +    progress = 0.0f; +  else if( progress > 1.0f ) +    progress = 1.0f; + +  doneWidth = ( rect->w - 2 * rimWidth ) * progress; +  leftWidth = ( rect->w - 2 * rimWidth ) - doneWidth; + +  trap_R_SetColor( color ); + +  //draw rim and bar +  if( align == ITEM_ALIGN_RIGHT ) +  { +    CG_DrawPic( rect->x, rect->y, rimWidth, rect->h, cgs.media.whiteShader ); +    CG_DrawPic( rect->x + rimWidth, rect->y, +      leftWidth, rimWidth, cgs.media.whiteShader ); +    CG_DrawPic( rect->x + rimWidth, rect->y + rect->h - rimWidth, +      leftWidth, rimWidth, cgs.media.whiteShader ); +    CG_DrawPic( rect->x + rimWidth + leftWidth, rect->y, +      rimWidth + doneWidth, rect->h, cgs.media.whiteShader ); +  } +  else +  { +    CG_DrawPic( rect->x, rect->y, rimWidth + doneWidth, rect->h, cgs.media.whiteShader ); +    CG_DrawPic( rimWidth + rect->x + doneWidth, rect->y, +      leftWidth, rimWidth, cgs.media.whiteShader ); +    CG_DrawPic( rimWidth + rect->x + doneWidth, rect->y + rect->h - rimWidth, +      leftWidth, rimWidth, cgs.media.whiteShader ); +    CG_DrawPic( rect->x + rect->w - rimWidth, rect->y, rimWidth, rect->h, cgs.media.whiteShader ); +  } + +  trap_R_SetColor( NULL ); + +  //draw text +  if( scale > 0.0 ) +  { +    Com_sprintf( textBuffer, sizeof( textBuffer ), "%d%%", (int)( progress * 100 ) ); +    tw = CG_Text_Width( textBuffer, scale, 0 ); +    th = scale * 40.0f; + +    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 ); +  } +} + +//=============== TA: was cg_newdraw.c + +#define NO_CREDITS_TIME 2000 + +static void CG_DrawPlayerCreditsValue( rectDef_t *rect, vec4_t color, qboolean padding ) +{ +  int           value; +  playerState_t *ps; +  centity_t     *cent; + +  cent = &cg_entities[ cg.snap->ps.clientNum ]; +  ps = &cg.snap->ps; + +  //if the build timer pie is showing don't show this +  if( ( cent->currentState.weapon == WP_ABUILD || +      cent->currentState.weapon == WP_ABUILD2 ) && ps->stats[ STAT_MISC ] ) +    return; + +  value = ps->persistant[ PERS_CREDIT ]; +  if( value > -1 ) +  { +    if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS && +        !CG_AtHighestClass( ) ) +    { +      if( cg.time - cg.lastEvolveAttempt <= NO_CREDITS_TIME ) +      { +        if( ( ( cg.time - cg.lastEvolveAttempt ) / 300 ) % 2 ) +          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 = ps->persistant[ PERS_BANK ]; +  if( value > -1 ) +  { +    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 ); +  } +} + +#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 ) +{ +  playerState_t *ps = &cg.snap->ps; +  float         stamina = ps->stats[ STAT_STAMINA ]; +  float         maxStaminaBy3 = (float)MAX_STAMINA / 3.0f; +  float         progress; + +  stamina -= ( 2 * (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_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 +============== +*/ +static void CG_DrawPlayerStamina3( 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; + +  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_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 ); + +  trap_R_SetColor( color ); +  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); +  trap_R_SetColor( NULL ); +} + +/* +============== +CG_DrawPlayerStaminaBolt +============== +*/ +static void CG_DrawPlayerStaminaBolt( rectDef_t *rect, vec4_t color, qhandle_t shader ) +{ +  playerState_t *ps = &cg.snap->ps; +  float         stamina = ps->stats[ STAT_STAMINA ]; + +  if( stamina < 0 ) +    color[ 3 ] = HH_MIN_ALPHA; +  else +    color[ 3 ] = HH_MAX_ALPHA; + +  trap_R_SetColor( color ); +  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); +  trap_R_SetColor( NULL ); +} + +/* +============== +CG_DrawPlayerClipsRing +============== +*/ +static void CG_DrawPlayerClipsRing( rectDef_t *rect, vec4_t color, qhandle_t shader ) +{ +  playerState_t *ps = &cg.snap->ps; +  centity_t     *cent; +  float         buildTime = ps->stats[ STAT_MISC ]; +  float         progress; +  float         maxDelay; + +  cent = &cg_entities[ cg.snap->ps.clientNum ]; + +  switch( cent->currentState.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; + +      progress = ( maxDelay - buildTime ) / maxDelay; + +      color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF ); +      break; + +    default: +      if( ps->weaponstate == WEAPON_RELOADING ) +      { +        maxDelay = (float)BG_FindReloadTimeForWeapon( cent->currentState.weapon ); +        progress = ( maxDelay - (float)ps->weaponTime ) / maxDelay; + +        color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF ); +      } +      break; +  } + +  trap_R_SetColor( color ); +  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); +  trap_R_SetColor( NULL ); +} + +/* +============== +CG_DrawPlayerBuildTimerRing +============== +*/ +static void CG_DrawPlayerBuildTimerRing( rectDef_t *rect, vec4_t color, qhandle_t shader ) +{ +  playerState_t *ps = &cg.snap->ps; +  centity_t     *cent; +  float         buildTime = ps->stats[ STAT_MISC ]; +  float         progress; +  float         maxDelay; + +  cent = &cg_entities[ cg.snap->ps.clientNum ]; + +  maxDelay = (float)BG_FindBuildDelayForWeapon( cent->currentState.weapon ); + +  if( buildTime > maxDelay ) +    buildTime = maxDelay; + +  progress = ( maxDelay - buildTime ) / maxDelay; + +  color[ 3 ] = AH_MIN_ALPHA + ( progress * AH_ALPHA_DIFF ); + +  trap_R_SetColor( color ); +  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); +  trap_R_SetColor( NULL ); +} + +/* +============== +CG_DrawPlayerBoosted +============== +*/ +static void CG_DrawPlayerBoosted( rectDef_t *rect, vec4_t color, qhandle_t shader ) +{ +  playerState_t *ps = &cg.snap->ps; +  qboolean      boosted = ps->stats[ STAT_STATE ] & SS_BOOSTED; + +  if( boosted ) +    color[ 3 ] = AH_MAX_ALPHA; +  else +    color[ 3 ] = AH_MIN_ALPHA; + +  trap_R_SetColor( color ); +  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); +  trap_R_SetColor( NULL ); +} + +/* +============== +CG_DrawPlayerBoosterBolt +============== +*/ +static void CG_DrawPlayerBoosterBolt( rectDef_t *rect, vec4_t color, qhandle_t shader ) +{ +  playerState_t *ps = &cg.snap->ps; +  qboolean      boosted = ps->stats[ STAT_STATE ] & SS_BOOSTED; +  vec4_t        localColor; + +  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; +    } +  } + +  trap_R_SetColor( localColor ); +  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); +  trap_R_SetColor( NULL ); +} + +/* +============== +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; + +  BG_UnpackAmmoArray( ps->weapon, ps->ammo, ps->powerups, &numBarbs, NULL ); + +  if( height > width ) +  { +    vertical = qtrue; +    iconsize = width; +  } +  else if( height <= width ) +  { +    vertical = qfalse; +    iconsize = height; +  } + +  if( color[ 3 ] != 0.0 ) +    trap_R_SetColor( color ); + +  for( i = 0; i < numBarbs; i ++ ) +  { +    if( vertical ) +      y += iconsize; +    else +      x += iconsize; + +    CG_DrawPic( x, y, iconsize, iconsize, shader ); +  } + +  trap_R_SetColor( NULL ); +} + +/* +============== +CG_DrawPlayerWallclimbing +============== +*/ +static void CG_DrawPlayerWallclimbing( rectDef_t *rect, vec4_t color, qhandle_t shader ) +{ +  playerState_t *ps = &cg.snap->ps; +  qboolean      ww = ps->stats[ STAT_STATE ] & SS_WALLCLIMBING; + +  if( ww ) +    color[ 3 ] = AH_MAX_ALPHA; +  else +    color[ 3 ] = AH_MIN_ALPHA; + +  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; + +  if( cent->currentState.weapon ) +  { +    switch( cent->currentState.weapon ) +    { +      case WP_ABUILD: +      case WP_ABUILD2: +        //percentage of BP remaining +        value = cgs.alienBuildPoints; +        break; + +      case WP_HBUILD: +      case WP_HBUILD2: +        //percentage of BP remaining +        value = cgs.humanBuildPoints; +        break; + +      default: +        BG_UnpackAmmoArray( cent->currentState.weapon, ps->ammo, ps->powerups, &value, NULL ); +        break; +    } + +    if( value > 999 ) +      value = 999; + +    if( value > -1 ) +    { +      trap_R_SetColor( color ); +      CG_DrawField( rect->x, rect->y, 4, rect->w / 4, rect->h, value ); +      trap_R_SetColor( NULL ); +    } +  } +} + + +/* +============== +CG_DrawAlienSense +============== +*/ +static void CG_DrawAlienSense( rectDef_t *rect ) +{ +  if( BG_ClassHasAbility( cg.snap->ps.stats[ STAT_PCLASS ], SCA_ALIENSENSE ) ) +    CG_AlienSense( rect ); +} + + +/* +============== +CG_DrawHumanScanner +============== +*/ +static void CG_DrawHumanScanner( rectDef_t *rect, qhandle_t shader, vec4_t color ) +{ +  if( BG_InventoryContainsUpgrade( UP_HELMET, cg.snap->ps.stats ) ) +    CG_Scanner( rect, shader, color ); +} + + +/* +============== +CG_DrawUsableBuildable +============== +*/ +static void CG_DrawUsableBuildable( rectDef_t *rect, qhandle_t shader, vec4_t color ) +{ +  vec3_t        view, point; +  trace_t       trace; +  entityState_t *es; + +  AngleVectors( cg.refdefViewAngles, view, NULL, NULL ); +  VectorMA( cg.refdef.vieworg, 64, view, point ); +  CG_Trace( &trace, cg.refdef.vieworg, NULL, NULL, +            point, cg.predictedPlayerState.clientNum, MASK_SHOT ); + +  es = &cg_entities[ trace.entityNum ].currentState; + +  if( es->eType == ET_BUILDABLE && BG_FindUsableForBuildable( es->modelindex ) && +      cg.predictedPlayerState.stats[ STAT_PTEAM ] == BG_FindTeamForBuildable( es->modelindex ) ) +  { +    //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 ) ) ) +      return; + +    trap_R_SetColor( color ); +    CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); +    trap_R_SetColor( NULL ); +  } +} + + +#define BUILD_DELAY_TIME  2000 + +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; + +      default: +        return; +        break; +    } + +    if( !ps->stats[ STAT_MISC ] ) +      return; + +    index = (int)( progress * 8.0f ); + +    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 ); +  } +} + +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; + +  if( cent->currentState.weapon ) +  { +    switch( cent->currentState.weapon ) +    { +      case WP_ABUILD: +      case WP_ABUILD2: +      case WP_HBUILD: +      case WP_HBUILD2: +        break; + +      default: +        BG_UnpackAmmoArray( cent->currentState.weapon, ps->ammo, ps->powerups, NULL, &value ); + +        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 ); +  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 ) +{ +  playerState_t *ps = &cg.snap->ps; +  int           health = ps->stats[ STAT_HEALTH ]; + +  if( health < 10 ) +  { +    color[ 0 ] = 1.0f; +    color[ 1 ] = color[ 2 ] = 0.0f; +  } + +  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 ) +{ +  vec4_t white = { 1.0f, 1.0f, 1.0f, 1.0f }; +  float tx, tw = CG_Text_Width( s, scale, 0 ); + +  switch( align ) +  { +    case ITEM_ALIGN_LEFT: +      tx = 0.0f; +      break; + +    case ITEM_ALIGN_RIGHT: +      tx = rect->w - tw; +      break; + +    case ITEM_ALIGN_CENTER: +      tx = ( rect->w / 2.0f ) - ( tw / 2.0f ); +      break; + +    default: +      tx = 0.0f; +  } + +  if( fraction < 1.0f ) +    CG_Text_Paint( rect->x + text_x + tx, rect->y + text_y, scale, white, +      s, 0, 0, ITEM_TEXTSTYLE_NORMAL ); +  else +    CG_Text_Paint( rect->x + text_x + tx, rect->y + text_y, 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 ) +{ +  CG_DrawProgressBar( rect, color, scale, align, textStyle, special, cg.mediaFraction ); +} + +static void CG_DrawMediaProgressLabel( rectDef_t *rect, float text_x, float text_y, +                                       vec4_t color, float scale, int align ) +{ +  CG_DrawProgressLabel( rect, text_x, text_y, color, scale, align, "Map and Textures", cg.mediaFraction ); +} + +static void CG_DrawBuildablesProgress( rectDef_t *rect, vec4_t color, float scale, +                                       int align, int textStyle, int special ) +{ +  CG_DrawProgressBar( rect, color, scale, align, textStyle, special, cg.buildablesFraction ); +} + +static void CG_DrawBuildablesProgressLabel( rectDef_t *rect, float text_x, float text_y, +                                            vec4_t color, float scale, int align ) +{ +  CG_DrawProgressLabel( rect, text_x, text_y, color, scale, align, "Buildable Models", cg.buildablesFraction ); +} + +static void CG_DrawCharModelProgress( rectDef_t *rect, vec4_t color, float scale, +                                      int align, int textStyle, int special ) +{ +  CG_DrawProgressBar( rect, color, scale, align, textStyle, special, cg.charModelFraction ); +} + +static void CG_DrawCharModelProgressLabel( rectDef_t *rect, float text_x, float text_y, +                                           vec4_t color, float scale, int align ) +{ +  CG_DrawProgressLabel( rect, text_x, text_y, color, scale, align, "Character Models", cg.charModelFraction ); +} + +static void CG_DrawOverallProgress( rectDef_t *rect, vec4_t color, float scale, +                                    int align, int textStyle, int special ) +{ +  float total; + +  total = ( cg.charModelFraction + cg.buildablesFraction + cg.mediaFraction ) / 3.0f; +  CG_DrawProgressBar( rect, color, scale, align, textStyle, special, total ); +} + +static void CG_DrawLevelShot( rectDef_t *rect ) +{ +  const char  *s; +  const char  *info; +  qhandle_t   levelshot; +  qhandle_t   detail; + +  info = CG_ConfigString( CS_SERVERINFO ); +  s = Info_ValueForKey( info, "mapname" ); +  levelshot = trap_R_RegisterShaderNoMip( va( "levelshots/%s.tga", s ) ); + +  if( !levelshot ) +    levelshot = trap_R_RegisterShaderNoMip( "gfx/2d/load_screen" ); + +  trap_R_SetColor( NULL ); +  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, levelshot ); + +  // blend a detail texture over it +  detail = trap_R_RegisterShader( "gfx/misc/detail" ); +  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 ) +{ +  const char  *s; + +  s = CG_ConfigString( CS_MESSAGE ); + +  CG_DrawLoadingString( rect, text_x, text_y, color, scale, align, textStyle, s ); +} + +static void CG_DrawMOTD( rectDef_t *rect, float text_x, float text_y, +                         vec4_t color, float scale, int align, int textStyle ) +{ +  const char  *s; + +  s = CG_ConfigString( CS_MOTD ); + +  CG_DrawLoadingString( rect, text_x, text_y, color, scale, align, textStyle, s ); +} + +static void CG_DrawHostname( rectDef_t *rect, float text_x, float text_y, +                             vec4_t color, float scale, int align, int textStyle ) +{ +  char buffer[ 1024 ]; +  const char  *info; + +  info = CG_ConfigString( CS_SERVERINFO ); + +  Q_strncpyz( buffer, Info_ValueForKey( info, "sv_hostname" ), 1024 ); +  Q_CleanStr( buffer ); + +  CG_DrawLoadingString( rect, text_x, text_y, color, scale, align, textStyle, buffer ); +} + +/* +============== +CG_DrawDemoPlayback +============== +*/ +static void CG_DrawDemoPlayback( rectDef_t *rect, vec4_t color, qhandle_t shader ) +{ +  if( !cg_drawDemoState.integer ) +    return; + +  if( trap_GetDemoState( ) != DS_PLAYBACK ) +    return; + +  trap_R_SetColor( color ); +  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); +  trap_R_SetColor( NULL ); +} + +/* +============== +CG_DrawDemoRecording +============== +*/ +static void CG_DrawDemoRecording( rectDef_t *rect, vec4_t color, qhandle_t shader ) +{ +  if( !cg_drawDemoState.integer ) +    return; + +  if( trap_GetDemoState( ) != DS_RECORDING ) +    return; + +  trap_R_SetColor( color ); +  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); +  trap_R_SetColor( NULL ); +} + +/* +====================== +CG_UpdateMediaFraction + +====================== +*/ +void CG_UpdateMediaFraction( float newFract ) +{ +  cg.mediaFraction = newFract; + +  trap_UpdateScreen( ); +} + +/* +==================== +CG_DrawLoadingScreen + +Draw all the status / pacifier stuff during level loading +==================== +*/ +void CG_DrawLoadingScreen( void ) +{ +  Menu_Paint( Menus_FindByName( "Loading" ), qtrue ); +} + +float CG_GetValue( int ownerDraw ) +{ +  centity_t *cent; +  playerState_t *ps; + +  cent = &cg_entities[ cg.snap->ps.clientNum ]; +  ps = &cg.snap->ps; + +  switch( ownerDraw ) +  { +    case CG_PLAYER_AMMO_VALUE: +      if( cent->currentState.weapon ) +      { +        int value; + +        BG_UnpackAmmoArray( cent->currentState.weapon, ps->ammo, ps->powerups, +           &value, NULL ); + +        return value; +      } +      break; +    case CG_PLAYER_CLIPS_VALUE: +      if( cent->currentState.weapon ) +      { +        int value; + +        BG_UnpackAmmoArray( cent->currentState.weapon, ps->ammo, ps->powerups, +           NULL, &value ); + +        return value; +      } +      break; +    case CG_PLAYER_HEALTH: +      return ps->stats[ STAT_HEALTH ]; +      break; +    default: +      break; +  } + +  return -1; +} + +const char *CG_GetKillerText( ) +{ +  const char *s = ""; +  if( cg.killerName[ 0 ] ) +    s = va( "Fragged by %s", cg.killerName ); + +  return s; +} + + +static void CG_DrawKiller( rectDef_t *rect, float scale, vec4_t color, +                           qhandle_t shader, int textStyle ) +{ +  // fragged by ... line + if( cg.killerName[ 0 ] ) + { +   int x = rect->x + rect->w / 2; +   CG_Text_Paint( x - CG_Text_Width( CG_GetKillerText( ), scale, 0 ) / 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; + +  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; + +    useScale = scale * font->glyphScale; +    trap_R_SetColor( color ); +    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 + +      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( CG_Text_Width( s, useScale, 1 ) + x > max ) +        { +          *maxX = 0; +          break; +        } + +        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++; +      } +    } + +    trap_R_SetColor( NULL ); +  } +} + +static void CG_DrawTeamSpectators( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader ) +{ +  if( cg.spectatorLen ) +  { +    float maxX; + +    if( cg.spectatorWidth == -1 ) +    { +      cg.spectatorWidth = 0; +      cg.spectatorPaintX = rect->x + 1; +      cg.spectatorPaintX2 = -1; +    } + +    if( cg.spectatorOffset > cg.spectatorLen ) +    { +      cg.spectatorOffset = 0; +      cg.spectatorPaintX = rect->x + 1; +      cg.spectatorPaintX2 = -1; +    } + +    if( cg.time > cg.spectatorTime ) +    { +      cg.spectatorTime = cg.time + 10; + +      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; + +          if( cg.spectatorPaintX2 >= 0 ) +            cg.spectatorPaintX = cg.spectatorPaintX2; +          else +            cg.spectatorPaintX = rect->x + rect->w - 2; + +          cg.spectatorPaintX2 = -1; +        } +      } +      else +      { +        cg.spectatorPaintX--; + +        if( cg.spectatorPaintX2 >= 0 ) +          cg.spectatorPaintX2--; +      } +    } + +    maxX = rect->x + rect->w - 2; + +    CG_Text_Paint_Limit( &maxX, cg.spectatorPaintX, rect->y + rect->h - 3, scale, color, +                         &cg.spectatorList[ cg.spectatorOffset ], 0, 0 ); + +    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 ); +    } + +    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; +  } +} + +/* +================== +CG_DrawStageReport +================== +*/ +static void CG_DrawStageReport( rectDef_t *rect, float text_x, float text_y, +    vec4_t color, float scale, int align, int textStyle ) +{ +  char  s[ MAX_TOKEN_CHARS ]; +  int   tx, w, kills; + +  if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_NONE && !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 ) +  { +    kills = cgs.alienNextStageThreshold - cgs.alienKills; + +    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 ); +    else +      Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d kills for next stage", +          cgs.alienStage + 1, kills ); +  } +  else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) +  { +    kills = cgs.humanNextStageThreshold - cgs.humanKills; + +    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 +      Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d kills for next stage", +          cgs.humanStage + 1, kills ); +  } + +  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_Text_Paint( text_x + tx, rect->y + text_y, scale, color, s, 0, 0, textStyle ); +} + +/* +================== +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, +                        qboolean scalableText ) +{ +  char        *s; +  int         tx, w, totalWidth, strLength; +  static int  previousTimes[ FPS_FRAMES ]; +  static int  index; +  int         i, total; +  int         fps; +  static int  previous; +  int         t, frameTime; + +  if( !cg_drawFPS.integer ) +    return; + +  // don't use serverTime, because that will be drifting to +  // correct for internet lag changes, timescales, timedemos, etc +  t = trap_Milliseconds( ); +  frameTime = t - previous; +  previous = t; + +  previousTimes[ index % FPS_FRAMES ] = frameTime; +  index++; + +  if( index > FPS_FRAMES ) +  { +    // average multiple frames together to smooth changes out a bit +    total = 0; + +    for( i = 0 ; i < FPS_FRAMES ; i++ ) +      total += previousTimes[ i ]; + +    if( !total ) +      total = 1; + +    fps = 1000 * FPS_FRAMES / total; + +    s = va( "%d", fps ); +    w = CG_Text_Width( "0", scale, 0 ); +    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; + +      default: +        tx = 0.0f; +    } + +    if( scalableText ) +    { +      for( i = 0; i < strLength; i++ ) +      { +        char c[ 2 ]; + +        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 ); +      } +    } +    else +    { +      trap_R_SetColor( color ); +      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 ); +  } +} + + +/* +================= +CG_DrawTimerMins +================= +*/ +static void CG_DrawTimerMins( rectDef_t *rect, vec4_t color ) +{ +  int     mins, seconds; +  int     msec; + +  if( !cg_drawTimer.integer ) +    return; + +  msec = cg.time - cgs.levelStartTime; + +  seconds = msec / 1000; +  mins = seconds / 60; +  seconds -= mins * 60; + +  trap_R_SetColor( color ); +  CG_DrawField( rect->x, rect->y, 3, rect->w / 3, rect->h, mins ); +  trap_R_SetColor( NULL ); +} + + +/* +================= +CG_DrawTimerSecs +================= +*/ +static void CG_DrawTimerSecs( rectDef_t *rect, vec4_t color ) +{ +  int     mins, seconds; +  int     msec; + +  if( !cg_drawTimer.integer ) +    return; + +  msec = cg.time - cgs.levelStartTime; + +  seconds = msec / 1000; +  mins = seconds / 60; +  seconds -= mins * 60; + +  trap_R_SetColor( color ); +  CG_DrawFieldPadded( rect->x, rect->y, 2, rect->w / 2, rect->h, seconds ); +  trap_R_SetColor( NULL ); +} + + +/* +================= +CG_DrawTimer +================= +*/ +static void CG_DrawTimer( rectDef_t *rect, float text_x, float text_y, +                          float scale, vec4_t color, int align, int textStyle ) +{ +  char    *s; +  int     i, tx, w, totalWidth, strLength; +  int     mins, seconds, tens; +  int     msec; + +  if( !cg_drawTimer.integer ) +    return; + +  msec = cg.time - cgs.levelStartTime; + +  seconds = msec / 1000; +  mins = seconds / 60; +  seconds -= mins * 60; +  tens = seconds / 10; +  seconds -= tens * 10; + +  s = va( "%d:%d%d", mins, tens, seconds ); +  w = CG_Text_Width( "0", scale, 0 ); +  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; +  } + +  for( i = 0; i < strLength; i++ ) +  { +    char c[ 2 ]; + +    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 ); +  } +} + +/* +================= +CG_DrawClock +================= +*/ +static void CG_DrawClock( rectDef_t *rect, float text_x, float text_y, +                          float scale, vec4_t color, int align, int textStyle ) +{ +  char    *s; +  int     i, tx, w, totalWidth, strLength; +  qtime_t qt; +  int     t; + +  if( !cg_drawClock.integer ) +    return; + +  t = trap_RealTime( &qt ); + +  if( cg_drawClock.integer == 2 ) +  { +    s = va( "%02d%s%02d", qt.tm_hour, ( qt.tm_sec % 2 ) ? ":" : " ", +      qt.tm_min ); +  } +  else +  { +    char *pm = "am"; +    int h = qt.tm_hour; + +    if( h == 0 ) +      h = 12; +    else if( h == 12 ) +      pm = "pm"; +    else if( h > 12 ) +    { +      h -= 12; +      pm = "pm"; +    } + +    s = va( "%d%s%02d%s", h, ( qt.tm_sec % 2 ) ? ":" : " ", qt.tm_min, pm ); +  } +  w = CG_Text_Width( "0", scale, 0 ); +  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; +  } + +  for( i = 0; i < strLength; i++ ) +  { +    char c[ 2 ]; + +    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 ); +  } +} + +/* +================== +CG_DrawSnapshot +================== +*/ +static void CG_DrawSnapshot( rectDef_t *rect, float text_x, float text_y, +                             float scale, vec4_t color, int align, int textStyle ) +{ +  char    *s; +  int     w, tx; + +  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; + +    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_Text_Paint( text_x + tx, rect->y + text_y, scale, color, s, 0, 0, textStyle ); +} + +/* +=============================================================================== + +LAGOMETER + +=============================================================================== +*/ + +#define LAG_SAMPLES   128 + +typedef struct +{ +  int frameSamples[ LAG_SAMPLES ]; +  int frameCount; +  int snapshotFlags[ LAG_SAMPLES ]; +  int snapshotSamples[ LAG_SAMPLES ]; +  int snapshotCount; +} lagometer_t; + +lagometer_t   lagometer; + +/* +============== +CG_AddLagometerFrameInfo + +Adds the current interpolate / extrapolate bar for this frame +============== +*/ +void CG_AddLagometerFrameInfo( void ) +{ +  int     offset; + +  offset = cg.time - cg.latestSnapshotTime; +  lagometer.frameSamples[ lagometer.frameCount & ( LAG_SAMPLES - 1 ) ] = offset; +  lagometer.frameCount++; +} + +/* +============== +CG_AddLagometerSnapshotInfo + +Each time a snapshot is received, log its ping time and +the number of snapshots that were dropped before it. + +Pass NULL for a dropped packet. +============== +*/ +#define PING_FRAMES 40 +void CG_AddLagometerSnapshotInfo( snapshot_t *snap ) +{ +  static int  previousPings[ PING_FRAMES ]; +  static int  index; +  int         i; + +  // dropped packet +  if( !snap ) +  { +    lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1 ) ] = -1; +    lagometer.snapshotCount++; +    return; +  } + +  // add this snapshot's info +  lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1 ) ] = snap->ping; +  lagometer.snapshotFlags[ lagometer.snapshotCount & ( LAG_SAMPLES - 1 ) ] = snap->snapFlags; +  lagometer.snapshotCount++; + +  cg.ping = 0; +  if( cg.snap ) +  { +    previousPings[ index++ ] = cg.snap->ping; +    index = index % PING_FRAMES; +   +    for( i = 0; i < PING_FRAMES; i++ ) +    { +      cg.ping += previousPings[ i ]; +    } + +    cg.ping /= PING_FRAMES; +  } +} + +/* +============== +CG_DrawDisconnect + +Should we draw something differnet for long lag vs no packets? +============== +*/ +static void CG_DrawDisconnect( void ) +{ +  float       x, y; +  int         cmdNum; +  usercmd_t   cmd; +  const char  *s; +  int         w; +  vec4_t      color = { 1.0f, 1.0f, 1.0f, 1.0f }; + +  // draw the phone jack if we are completely past our buffers +  cmdNum = trap_GetCurrentCmdNumber( ) - CMD_BACKUP + 1; +  trap_GetUserCmd( cmdNum, &cmd ); + +  // special check for map_restart +  if( cmd.serverTime <= cg.snap->ps.commandTime || cmd.serverTime > cg.time ) +    return; + +  // 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 ); + +  // blink the icon +  if( ( cg.time >> 9 ) & 1 ) +    return; + +  x = 640 - 48; +  y = 480 - 48; + +  CG_DrawPic( x, y, 48, 48, trap_R_RegisterShader( "gfx/2d/net.tga" ) ); +} + +#define MAX_LAGOMETER_PING  900 +#define MAX_LAGOMETER_RANGE 300 + + +/* +============== +CG_DrawLagometer +============== +*/ +static void CG_DrawLagometer( rectDef_t *rect, float text_x, float text_y, +    float scale, vec4_t textColor ) +{ +  int     a, x, y, i; +  float   v; +  float   ax, ay, aw, ah, mid, range; +  int     color; +  vec4_t  adjustedColor; +  float   vscale; +  vec4_t  white = { 1.0f, 1.0f, 1.0f, 1.0f }; + +  if( cg.snap->ps.pm_type == PM_INTERMISSION ) +    return; + +  if( !cg_lagometer.integer ) +    return; + +  if( cg.demoPlayback ) +    return; + +  Vector4Copy( textColor, adjustedColor ); +  adjustedColor[ 3 ] = 0.25f; + +  trap_R_SetColor( adjustedColor ); +  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.whiteShader ); +  trap_R_SetColor( NULL ); + +  // +  // draw the graph +  // +  ax = x = rect->x; +  ay = y = rect->y; +  aw = rect->w; +  ah = rect->h; + +  trap_R_SetColor( NULL ); + +  CG_AdjustFrom640( &ax, &ay, &aw, &ah ); + +  color = -1; +  range = ah / 3; +  mid = ay + range; + +  vscale = range / MAX_LAGOMETER_RANGE; + +  // draw the frame interpoalte / extrapolate graph +  for( a = 0 ; a < aw ; a++ ) +  { +    i = ( lagometer.frameCount - 1 - a ) & ( LAG_SAMPLES - 1 ); +    v = lagometer.frameSamples[ i ]; +    v *= vscale; + +    if( v > 0 ) +    { +      if( color != 1 ) +      { +        color = 1; +        trap_R_SetColor( g_color_table[ ColorIndex( COLOR_YELLOW ) ] ); +      } + +      if( v > range ) +        v = range; + +      trap_R_DrawStretchPic( ax + aw - a, mid - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); +    } +    else if( v < 0 ) +    { +      if( color != 2 ) +      { +        color = 2; +        trap_R_SetColor( g_color_table[ ColorIndex( COLOR_BLUE ) ] ); +      } + +      v = -v; +      if( v > range ) +        v = range; + +      trap_R_DrawStretchPic( ax + aw - a, mid, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); +    } +  } + +  // draw the snapshot latency / drop graph +  range = ah / 2; +  vscale = range / MAX_LAGOMETER_PING; + +  for( a = 0 ; a < aw ; a++ ) +  { +    i = ( lagometer.snapshotCount - 1 - a ) & ( LAG_SAMPLES - 1 ); +    v = lagometer.snapshotSamples[ i ]; + +    if( v > 0 ) +    { +      if( lagometer.snapshotFlags[ i ] & SNAPFLAG_RATE_DELAYED ) +      { +        if( color != 5 ) +        { +          color = 5;  // YELLOW for rate delay +          trap_R_SetColor( g_color_table[ ColorIndex( COLOR_YELLOW ) ] ); +        } +      } +      else +      { +        if( color != 3 ) +        { +          color = 3; + +          trap_R_SetColor( g_color_table[ ColorIndex( COLOR_GREEN ) ] ); +        } +      } + +      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 ) +    CG_Text_Paint( ax, ay, 0.5, white, "snc", 0, 0, ITEM_TEXTSTYLE_NORMAL ); +  else +  { +    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 ); +  } + +  CG_DrawDisconnect( ); +} + +/* +============== +CG_DrawTextBlock +============== +*/ +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 ) +{ +  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; + +  textItem->parent = parent; +  memcpy( textItem->window.foreColor, color, sizeof( vec4_t ) ); +  textItem->window.flags = 0; + +  switch( align ) +  { +    case ITEM_ALIGN_LEFT: +      textItem->window.rect.x = x; +      break; + +    case ITEM_ALIGN_RIGHT: +      textItem->window.rect.x = x + w; +      break; + +    case ITEM_ALIGN_CENTER: +      textItem->window.rect.x = x + ( w / 2 ); +      break; + +    default: +      textItem->window.rect.x = x; +      break; +  } + +  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; + +  //hack to utilise existing autowrap code +  Item_Text_AutoWrapped_Paint( textItem ); +} + +/* +=================== +CG_DrawConsole +=================== +*/ +static void CG_DrawConsole( rectDef_t *rect, float text_x, float text_y, vec4_t color, +                            float scale, int align, 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 ); +} + +/* +=================== +CG_DrawTutorial +=================== +*/ +static void CG_DrawTutorial( rectDef_t *rect, float text_x, float text_y, vec4_t color, +                            float scale, int align, 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 ); +} + +/* +=================== +CG_DrawWeaponIcon +=================== +*/ +void CG_DrawWeaponIcon( rectDef_t *rect, vec4_t color ) +{ +  int           ammo, clips, maxAmmo; +  centity_t     *cent; +  playerState_t *ps; + +  cent = &cg_entities[ cg.snap->ps.clientNum ]; +  ps = &cg.snap->ps; + +  BG_UnpackAmmoArray( cent->currentState.weapon, ps->ammo, ps->powerups, &ammo, &clips ); +  BG_FindAmmoForWeapon( cent->currentState.weapon, &maxAmmo, NULL ); + +  // don't display if dead +  if( cg.predictedPlayerState.stats[ STAT_HEALTH ] <= 0 ) +    return; + +  if( cent->currentState.weapon == 0 ) +    return; + +  CG_RegisterWeapon( cent->currentState.weapon ); + +  if( clips == 0 && !BG_FindInfinteAmmoForWeapon( cent->currentState.weapon ) ) +  { +    float ammoPercent = (float)ammo / (float)maxAmmo; + +    if( ammoPercent < 0.33f ) +    { +      color[ 0 ] = 1.0f; +      color[ 1 ] = color[ 2 ] = 0.0f; +    } +  } + +  if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS && CG_AtHighestClass( ) ) +  { +    if( cg.time - cg.lastEvolveAttempt <= NO_CREDITS_TIME ) +    { +      if( ( ( cg.time - cg.lastEvolveAttempt ) / 300 ) % 2 ) +        color[ 3 ] = 0.0f; +    } +  } + +  trap_R_SetColor( color ); +  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_weapons[ cent->currentState.weapon ].weaponIcon ); +  trap_R_SetColor( NULL ); +} + + + +/* +================================================================================ + +CROSSHAIR + +================================================================================ +*/ + + +/* +================= +CG_DrawCrosshair +================= +*/ +static void CG_DrawCrosshair( void ) +{ +  float         w, h; +  qhandle_t     hShader; +  float         x, y; +  weaponInfo_t  *wi; + +  if( cg_drawCrosshair.integer == CROSSHAIR_ALWAYSOFF ) +    return; + +  if( cg_drawCrosshair.integer == CROSSHAIR_RANGEDONLY && +      !BG_FindLongRangedForWeapon( cg.snap->ps.weapon ) ) +  { +    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 ) ) +    return; + +  if( cg.renderingThirdPerson ) +    return; + +  wi = &cg_weapons[ cg.snap->ps.weapon ]; + +  w = h = wi->crossHairSize; + +  x = cg_crosshairX.integer; +  y = cg_crosshairY.integer; +  CG_AdjustFrom640( &x, &y, &w, &h ); + +  hShader = wi->crossHair; + +  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 ); +  } +} + + + +/* +================= +CG_ScanForCrosshairEntity +================= +*/ +static void CG_ScanForCrosshairEntity( void ) +{ +  trace_t   trace; +  vec3_t    start, end; +  int       content; +  pTeam_t   team; + +  VectorCopy( cg.refdef.vieworg, start ); +  VectorMA( start, 131072, cg.refdef.viewaxis[ 0 ], end ); + +  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; + +  team = cgs.clientinfo[ trace.entityNum ].team; + +  if( cg.snap->ps.persistant[ PERS_TEAM ] != TEAM_SPECTATOR ) +  { +    //only display team names of those on the same team as this player +    if( team != cg.snap->ps.stats[ STAT_PTEAM ] ) +      return; +  } + +  // update the fade timer +  cg.crosshairClientNum = trace.entityNum; +  cg.crosshairClientTime = cg.time; +} + + +/* +===================== +CG_DrawCrosshairNames +===================== +*/ +static void CG_DrawCrosshairNames( rectDef_t *rect, float scale, int textStyle ) +{ +  float   *color; +  char    *name; +  float   w, x; + +  if( !cg_drawCrosshairNames.integer ) +    return; + +  if( cg.renderingThirdPerson ) +    return; + +  // scan the known entities to see if the crosshair is sighted on one +  CG_ScanForCrosshairEntity( ); + +  // draw the name of the player being looked at +  color = CG_FadeColor( cg.crosshairClientTime, 1000 ); +  if( !color ) +  { +    trap_R_SetColor( NULL ); +    return; +  } + +  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 ); +  trap_R_SetColor( NULL ); +} + + +/* +=============== +CG_OwnerDraw + +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, +                   qhandle_t shader, int textStyle ) +{ +  rectDef_t rect; + +  if( cg_drawStatus.integer == 0 ) +    return; + +  rect.x = x; +  rect.y = y; +  rect.w = w; +  rect.h = h; + +  switch( ownerDraw ) +  { +    case CG_PLAYER_CREDITS_VALUE: +      CG_DrawPlayerCreditsValue( &rect, color, qtrue ); +      break; +    case CG_PLAYER_BANK_VALUE: +      CG_DrawPlayerBankValue( &rect, color, qtrue ); +      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 ); +      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 ); +      break; +    case CG_PLAYER_STAMINA_BOLT: +      CG_DrawPlayerStaminaBolt( &rect, color, shader ); +      break; +    case CG_PLAYER_AMMO_VALUE: +      CG_DrawPlayerAmmoValue( &rect, color ); +      break; +    case CG_PLAYER_CLIPS_VALUE: +      CG_DrawPlayerClipsValue( &rect, color ); +      break; +    case CG_PLAYER_BUILD_TIMER: +      CG_DrawPlayerBuildTimer( &rect, color ); +      break; +    case CG_PLAYER_HEALTH: +      CG_DrawPlayerHealthValue( &rect, color ); +      break; +    case CG_PLAYER_HEALTH_BAR: +      CG_DrawPlayerHealthBar( &rect, color, scale, align, textStyle, special ); +      break; +    case CG_PLAYER_HEALTH_CROSS: +      CG_DrawPlayerHealthCross( &rect, color, shader ); +      break; +    case CG_PLAYER_CLIPS_RING: +      CG_DrawPlayerClipsRing( &rect, color, shader ); +      break; +    case CG_PLAYER_BUILD_TIMER_RING: +      CG_DrawPlayerBuildTimerRing( &rect, color, shader ); +      break; +    case CG_PLAYER_WALLCLIMBING: +      CG_DrawPlayerWallclimbing( &rect, color, shader ); +      break; +    case CG_PLAYER_BOOSTED: +      CG_DrawPlayerBoosted( &rect, color, shader ); +      break; +    case CG_PLAYER_BOOST_BOLT: +      CG_DrawPlayerBoosterBolt( &rect, color, shader ); +      break; +    case CG_PLAYER_POISON_BARBS: +      CG_DrawPlayerPoisonBarbs( &rect, color, shader ); +      break; +    case CG_PLAYER_ALIEN_SENSE: +      CG_DrawAlienSense( &rect ); +      break; +    case CG_PLAYER_HUMAN_SCANNER: +      CG_DrawHumanScanner( &rect, shader, color ); +      break; +    case CG_PLAYER_USABLE_BUILDABLE: +      CG_DrawUsableBuildable( &rect, shader, color ); +      break; +    case CG_KILLER: +      CG_DrawKiller( &rect, scale, color, shader, textStyle ); +      break; +    case CG_PLAYER_SELECT: +      CG_DrawItemSelect( &rect, color ); +      break; +    case CG_PLAYER_WEAPONICON: +      CG_DrawWeaponIcon( &rect, color ); +      break; +    case CG_PLAYER_SELECTTEXT: +      CG_DrawItemSelectText( &rect, scale, textStyle ); +      break; +    case CG_SPECTATORS: +      CG_DrawTeamSpectators( &rect, scale, color, shader ); +      break; +    case CG_PLAYER_CROSSHAIRNAMES: +      CG_DrawCrosshairNames( &rect, scale, textStyle ); +      break; +    case CG_STAGE_REPORT_TEXT: +      CG_DrawStageReport( &rect, text_x, text_y, color, scale, align, textStyle ); +      break; + +    //loading screen +    case CG_LOAD_LEVELSHOT: +      CG_DrawLevelShot( &rect ); +      break; +    case CG_LOAD_MEDIA: +      CG_DrawMediaProgress( &rect, color, scale, align, textStyle, special ); +      break; +    case CG_LOAD_MEDIA_LABEL: +      CG_DrawMediaProgressLabel( &rect, text_x, text_y, color, scale, align ); +      break; +    case CG_LOAD_BUILDABLES: +      CG_DrawBuildablesProgress( &rect, color, scale, align, textStyle, special ); +      break; +    case CG_LOAD_BUILDABLES_LABEL: +      CG_DrawBuildablesProgressLabel( &rect, text_x, text_y, color, scale, align ); +      break; +    case CG_LOAD_CHARMODEL: +      CG_DrawCharModelProgress( &rect, color, scale, align, textStyle, special ); +      break; +    case CG_LOAD_CHARMODEL_LABEL: +      CG_DrawCharModelProgressLabel( &rect, text_x, text_y, color, scale, align ); +      break; +    case CG_LOAD_OVERALL: +      CG_DrawOverallProgress( &rect, color, scale, align, textStyle, special ); +      break; +    case CG_LOAD_LEVELNAME: +      CG_DrawLevelName( &rect, text_x, text_y, color, scale, align, textStyle ); +      break; +    case CG_LOAD_MOTD: +      CG_DrawMOTD( &rect, text_x, text_y, color, scale, align, textStyle ); +      break; +    case CG_LOAD_HOSTNAME: +      CG_DrawHostname( &rect, text_x, text_y, color, scale, align, textStyle ); +      break; + +    case CG_FPS: +      CG_DrawFPS( &rect, text_x, text_y, scale, color, align, textStyle, qtrue ); +      break; +    case CG_FPS_FIXED: +      CG_DrawFPS( &rect, text_x, text_y, scale, color, align, textStyle, qfalse ); +      break; +    case CG_TIMER: +      CG_DrawTimer( &rect, text_x, text_y, scale, color, align, textStyle ); +      break; +    case CG_CLOCK: +      CG_DrawClock( &rect, text_x, text_y, scale, color, align, textStyle ); +      break; +    case CG_TIMER_MINS: +      CG_DrawTimerMins( &rect, color ); +      break; +    case CG_TIMER_SECS: +      CG_DrawTimerSecs( &rect, color ); +      break; +    case CG_SNAPSHOT: +      CG_DrawSnapshot( &rect, text_x, text_y, scale, color, align, textStyle ); +      break; +    case CG_LAGOMETER: +      CG_DrawLagometer( &rect, text_x, text_y, scale, color ); +      break; + +    case CG_DEMO_PLAYBACK: +      CG_DrawDemoPlayback( &rect, color, shader ); +      break; +    case CG_DEMO_RECORDING: +      CG_DrawDemoRecording( &rect, color, shader ); +      break; + +    case CG_CONSOLE: +      CG_DrawConsole( &rect, text_x, text_y, color, scale, align, textStyle ); +      break; + +    case CG_TUTORIAL: +      CG_DrawTutorial( &rect, text_x, text_y, color, scale, align, textStyle ); +      break; + +    default: +      break; +  } +} + +void CG_MouseEvent( int x, int y ) +{ +  int n; + +  if( ( cg.predictedPlayerState.pm_type == PM_NORMAL || +        cg.predictedPlayerState.pm_type == PM_SPECTATOR ) && +        cg.showScores == qfalse ) +  { +    trap_Key_SetCatcher( 0 ); +    return; +  } + +  cgs.cursorX += x; +  if( cgs.cursorX < 0 ) +    cgs.cursorX = 0; +  else if( cgs.cursorX > 640 ) +    cgs.cursorX = 640; + +  cgs.cursorY += y; +  if( cgs.cursorY < 0 ) +    cgs.cursorY = 0; +  else if( cgs.cursorY > 480 ) +    cgs.cursorY = 480; + +  n = Display_CursorType( cgs.cursorX, cgs.cursorY ); +  cgs.activeCursor = 0; +  if( n == CURSOR_ARROW ) +    cgs.activeCursor = cgs.media.selectCursor; +  else if( n == CURSOR_SIZER ) +    cgs.activeCursor = cgs.media.sizeCursor; + +  if( cgs.capturedItem ) +    Display_MouseMove( cgs.capturedItem, x, y ); +  else +    Display_MouseMove( NULL, cgs.cursorX, cgs.cursorY ); +} + +/* +================== +CG_HideTeamMenus +================== + +*/ +void CG_HideTeamMenu( void ) +{ +  Menus_CloseByName( "teamMenu" ); +  Menus_CloseByName( "getMenu" ); +} + +/* +================== +CG_ShowTeamMenus +================== + +*/ +void CG_ShowTeamMenu( void ) +{ +  Menus_OpenByName( "teamMenu" ); +} + +/* +================== +CG_EventHandling +================== + type 0 - no event handling +      1 - team menu +      2 - hud editor + +*/ +void CG_EventHandling( int type ) +{ +  cgs.eventHandling = type; + +  if( type == CGAME_EVENT_NONE ) +    CG_HideTeamMenu( ); +} + + + +void CG_KeyEvent( int key, qboolean down ) +{ +  if( !down ) +    return; + +  if( cg.predictedPlayerState.pm_type == PM_NORMAL || +      ( cg.predictedPlayerState.pm_type == PM_SPECTATOR && +        cg.showScores == qfalse ) ) +  { +    CG_EventHandling( CGAME_EVENT_NONE ); +    trap_Key_SetCatcher( 0 ); +    return; +  } + +  Display_HandleKey( key, down, cgs.cursorX, cgs.cursorY ); + +  if( cgs.capturedItem ) +    cgs.capturedItem = NULL; +  else +  { +    if( key == K_MOUSE2 && down ) +      cgs.capturedItem = Display_CaptureItem( cgs.cursorX, cgs.cursorY ); +  } +} + +int CG_ClientNumFromName( const char *p ) +{ +  int i; + +  for( i = 0; i < cgs.maxclients; i++ ) +  { +    if( cgs.clientinfo[ i ].infoValid && +        Q_stricmp( cgs.clientinfo[ i ].name, p ) == 0 ) +      return i; +  } + +  return -1; +} + +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 + + +/* +================ +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 ) ) +  { +    vec4_t black = { 0, 0, 0, 0 }; +    black[ 3 ] = 1.0 - ( (float)( cg.snap->ps.stats[ STAT_STAMINA ] + 1000 ) / 200.0f ); +    trap_R_SetColor( black ); +    CG_DrawPic( 0, 0, 640, 480, cgs.media.whiteShader ); +    trap_R_SetColor( NULL ); +  } +} + +/* +=============================================================================== + +CENTER PRINTING + +=============================================================================== +*/ + + +/* +============== +CG_CenterPrint + +Called for important messages that should stay in the center of the screen +for a few moments +============== +*/ +void CG_CenterPrint( const char *str, int y, int charWidth ) +{ +  char  *s; + +  Q_strncpyz( cg.centerPrint, str, sizeof( cg.centerPrint ) ); + +  cg.centerPrintTime = cg.time; +  cg.centerPrintY = y; +  cg.centerPrintCharWidth = charWidth; + +  // count the number of lines for centering +  cg.centerPrintLines = 1; +  s = cg.centerPrint; +  while( *s ) +  { +    if( *s == '\n' ) +      cg.centerPrintLines++; + +    s++; +  } +} + + +/* +=================== +CG_DrawCenterString +=================== +*/ +static void CG_DrawCenterString( void ) +{ +  char  *start; +  int   l; +  int   x, y, w; +  int h; +  float *color; + +  if( !cg.centerPrintTime ) +    return; + +  color = CG_FadeColor( cg.centerPrintTime, 1000 * cg_centertime.value ); +  if( !color ) +    return; + +  trap_R_SetColor( color ); + +  start = cg.centerPrint; + +  y = cg.centerPrintY - cg.centerPrintLines * BIGCHAR_HEIGHT / 2; + +  while( 1 ) +  { +    char linebuffer[ 1024 ]; + +    for( l = 0; l < 50; l++ ) +    { +      if( !start[ l ] || start[ l ] == '\n' ) +        break; + +      linebuffer[ l ] = start[ l ]; +    } + +    linebuffer[ l ] = 0; + +    w = CG_Text_Width( linebuffer, 0.5, 0 ); +    h = CG_Text_Height( linebuffer, 0.5, 0 ); +    x = ( SCREEN_WIDTH - w ) / 2; +    CG_Text_Paint( x, y + h, 0.5, color, linebuffer, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE ); +    y += h + 6; + +    while( *start && ( *start != '\n' ) ) +      start++; + +    if( !*start ) +      break; + +    start++; +  } + +  trap_R_SetColor( NULL ); +} + + + + + +//============================================================================== + +//FIXME: both vote notes are hardcoded, change to ownerdrawn? + +/* +================= +CG_DrawVote +================= +*/ +static void CG_DrawVote( void ) +{ +  char    *s; +  int     sec; +  vec4_t  white = { 1.0f, 1.0f, 1.0f, 1.0f }; +  char    yeskey[ 32 ], nokey[ 32 ]; + +  if( !cgs.voteTime ) +    return; + +  // play a talk beep whenever it is modified +  if( cgs.voteModified ) +  { +    cgs.voteModified = qfalse; +    trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); +  } + +  sec = ( VOTE_TIME - ( cg.time - cgs.voteTime ) ) / 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.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_HUMANS ) +    cs_offset = 0; +  else if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS ) +    cs_offset = 1; +  else +    return; + +  if( !cgs.teamVoteTime[ cs_offset ] ) +    return; + +  // 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 ); +  } + +  sec = ( VOTE_TIME - ( cg.time - cgs.teamVoteTime[ cs_offset ] ) ) / 1000; + +  if( sec < 0 ) +    sec = 0; + +  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 ] ); + +  CG_Text_Paint( 8, 360, 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; + +  if( cg_paused.integer ) +  { +    cg.deferredPlayerLoading = 0; +    firstTime = qtrue; +    return qfalse; +  } + +  if( cg.showScores || +      cg.predictedPlayerState.pm_type == PM_INTERMISSION ) +  { +    fade = 1.0; +    fadeColor = colorWhite; +  } +  else +  { +    cg.deferredPlayerLoading = 0; +    cg.killerName[ 0 ] = 0; +    firstTime = qtrue; +    return qfalse; +  } + + +  if( menuScoreboard == NULL ) +    menuScoreboard = Menus_FindByName( "teamscore_menu" ); + +  if( menuScoreboard ) +  { +    if( firstTime ) +    { +      CG_SetScoreSelection( menuScoreboard ); +      firstTime = qfalse; +    } + +    Menu_Paint( menuScoreboard, qtrue ); +  } + +  return qtrue; +} + +/* +================= +CG_DrawIntermission +================= +*/ +static void CG_DrawIntermission( void ) +{ +  if( cg_drawStatus.integer ) +    Menu_Paint( Menus_FindByName( "default_hud" ), qtrue ); + +  cg.scoreFadeTime = cg.time; +  cg.scoreBoardShowing = CG_DrawScoreboard( ); +} + +#define FOLLOWING_STRING "following " + +/* +================= +CG_DrawFollow +================= +*/ +static qboolean CG_DrawFollow( void ) +{ +  float       w; +  vec4_t      color; +  char        buffer[ MAX_STRING_CHARS ]; + +  if( !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) +    return qfalse; + +  color[ 0 ] = 1; +  color[ 1 ] = 1; +  color[ 2 ] = 1; +  color[ 3 ] = 1; + +  strcpy( buffer, FOLLOWING_STRING ); +  strcat( buffer, cgs.clientinfo[ cg.snap->ps.clientNum ].name ); + +  w = CG_Text_Width( buffer, 0.7f, 0 ); +  CG_Text_Paint( 320 - w / 2, 400, 0.7f, color, buffer, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); + +  return qtrue; +} + +/* +================= +CG_DrawQueue +================= +*/ +static qboolean CG_DrawQueue( void ) +{ +  float       w; +  vec4_t      color; +  char        buffer[ MAX_STRING_CHARS ]; + +  if( !( cg.snap->ps.pm_flags & PMF_QUEUED ) ) +    return qfalse; + +  color[ 0 ] = 1; +  color[ 1 ] = 1; +  color[ 2 ] = 1; +  color[ 3 ] = 1; + +  Com_sprintf( buffer, MAX_STRING_CHARS, "You are in position %d of the spawn queue.", +               cg.snap->ps.persistant[ PERS_QUEUEPOS ] + 1 ); + +  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( 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 = CG_Text_Width( buffer, 0.7f, 0 ); +  CG_Text_Paint( 320 - w / 2, 400, 0.7f, color, buffer, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); + +  return qtrue; +} + +//================================================================================== + +#define SPECTATOR_STRING "SPECTATOR" +/* +================= +CG_Draw2D +================= +*/ +static void CG_Draw2D( void ) +{ +  vec4_t    color; +  float     w; +  menuDef_t *menu = NULL, *defaultMenu; + +  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; + +  if( cg_draw2D.integer == 0 ) +    return; + +  if( cg.snap->ps.pm_type == PM_INTERMISSION ) +  { +    CG_DrawIntermission( ); +    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 ) +  { +    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 ); +  } +  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 ) ) +  { +    CG_DrawBuildableStatus( ); +    if( cg_drawStatus.integer ) +      Menu_Paint( menu, qtrue ); + +    CG_DrawCrosshair( ); +  } +  else if( cg_drawStatus.integer ) +    Menu_Paint( defaultMenu, qtrue ); + +  CG_DrawVote( ); +  CG_DrawTeamVote( ); +  CG_DrawFollow( ); +  CG_DrawQueue( ); + +  // don't draw center string if scoreboard is up +  cg.scoreBoardShowing = CG_DrawScoreboard( ); + +  if( !cg.scoreBoardShowing ) +    CG_DrawCenterString( ); +} + +/* +=============== +CG_ScalePainBlendTCs +=============== +*/ +static void CG_ScalePainBlendTCs( float* s1, float *t1, float *s2, float *t2 ) +{ +  *s1 -= 0.5f; +  *t1 -= 0.5f; +  *s2 -= 0.5f; +  *t2 -= 0.5f; + +  *s1 *= cg_painBlendZoom.value; +  *t1 *= cg_painBlendZoom.value; +  *s2 *= cg_painBlendZoom.value; +  *t2 *= cg_painBlendZoom.value; + +  *s1 += 0.5f; +  *t1 += 0.5f; +  *s2 += 0.5f; +  *t2 += 0.5f; +} + +#define PAINBLEND_BORDER    0.15f + +/* +=============== +CG_PainBlend +=============== +*/ +static void CG_PainBlend( void ) +{ +  vec4_t      color; +  int         damage; +  float       damageAsFracOfMax; +  qhandle_t   shader = cgs.media.viewBloodShader; +  float       x, y, w, h; +  float       s1, t1, s2, t2; + +  if( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR || cg.intermissionStarted ) +    return; + +  damage = cg.lastHealth - cg.snap->ps.stats[ STAT_HEALTH ]; + +  if( damage < 0 ) +    damage = 0; + +  damageAsFracOfMax = (float)damage / cg.snap->ps.stats[ STAT_MAX_HEALTH ]; +  cg.lastHealth = cg.snap->ps.stats[ STAT_HEALTH ]; + +  cg.painBlendValue += damageAsFracOfMax * cg_painBlendScale.value; + +  if( cg.painBlendValue > 0.0f ) +  { +    cg.painBlendValue -= ( cg.frametime / 1000.0f ) * +      cg_painBlendDownRate.value; +  } + +  if( cg.painBlendValue > 1.0f ) +    cg.painBlendValue = 1.0f; +  else if( cg.painBlendValue <= 0.0f ) +  { +    cg.painBlendValue = 0.0f; +    return; +  } + +  if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) +    VectorSet( color, 0.43f, 0.8f, 0.37f ); +  else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) +    VectorSet( color, 0.8f, 0.0f, 0.0f ); + +  if( cg.painBlendValue > cg.painBlendTarget ) +  { +    cg.painBlendTarget += ( cg.frametime / 1000.0f ) * +      cg_painBlendUpRate.value; +  } +  else if( cg.painBlendValue < cg.painBlendTarget ) +    cg.painBlendTarget = cg.painBlendValue; + +  if( cg.painBlendTarget > cg_painBlendMax.value ) +    cg.painBlendTarget = cg_painBlendMax.value; + +  color[ 3 ] = cg.painBlendTarget; + +  trap_R_SetColor( color ); + +  //left +  x = 0.0f; y = 0.0f; +  w = PAINBLEND_BORDER * 640.0f; h = 480.0f; +  CG_AdjustFrom640( &x, &y, &w, &h ); +  s1 = 0.0f; t1 = 0.0f; +  s2 = PAINBLEND_BORDER; t2 = 1.0f; +  CG_ScalePainBlendTCs( &s1, &t1, &s2, &t2 ); +  trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, shader ); + +  //right +  x = 640.0f - ( PAINBLEND_BORDER * 640.0f ); y = 0.0f; +  w = PAINBLEND_BORDER * 640.0f; h = 480.0f; +  CG_AdjustFrom640( &x, &y, &w, &h ); +  s1 = 1.0f - PAINBLEND_BORDER; t1 = 0.0f; +  s2 = 1.0f; t2 =1.0f; +  CG_ScalePainBlendTCs( &s1, &t1, &s2, &t2 ); +  trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, shader ); + +  //top +  x = PAINBLEND_BORDER * 640.0f; y = 0.0f; +  w = 640.0f - ( 2 * PAINBLEND_BORDER * 640.0f ); h = PAINBLEND_BORDER * 480.0f; +  CG_AdjustFrom640( &x, &y, &w, &h ); +  s1 = PAINBLEND_BORDER; t1 = 0.0f; +  s2 = 1.0f - PAINBLEND_BORDER; t2 = PAINBLEND_BORDER; +  CG_ScalePainBlendTCs( &s1, &t1, &s2, &t2 ); +  trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, shader ); + +  //bottom +  x = PAINBLEND_BORDER * 640.0f; y = 480.0f - ( PAINBLEND_BORDER * 480.0f ); +  w = 640.0f - ( 2 * PAINBLEND_BORDER * 640.0f ); h = PAINBLEND_BORDER * 480.0f; +  CG_AdjustFrom640( &x, &y, &w, &h ); +  s1 = PAINBLEND_BORDER; t1 = 1.0f - PAINBLEND_BORDER; +  s2 = 1.0f - PAINBLEND_BORDER; t2 = 1.0f; +  CG_ScalePainBlendTCs( &s1, &t1, &s2, &t2 ); +  trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, shader ); + +  trap_R_SetColor( NULL ); +} + +/* +===================== +CG_ResetPainBlend +===================== +*/ +void CG_ResetPainBlend( void ) +{ +  cg.painBlendValue = 0.0f; +  cg.painBlendTarget = 0.0f; +  cg.lastHealth = cg.snap->ps.stats[ STAT_HEALTH ]; +} + +/* +===================== +CG_DrawActive + +Perform all drawing needed to completely fill the screen +===================== +*/ +void CG_DrawActive( stereoFrame_t stereoView ) +{ +  float   separation; +  vec3_t    baseOrg; + +  // optionally draw the info screen instead +  if( !cg.snap ) +    return; + +  switch ( stereoView ) +  { +    case STEREO_CENTER: +      separation = 0; +      break; +    case STEREO_LEFT: +      separation = -cg_stereoSeparation.value / 2; +      break; +    case STEREO_RIGHT: +      separation = cg_stereoSeparation.value / 2; +      break; +    default: +      separation = 0; +      CG_Error( "CG_DrawActive: Undefined stereoView" ); +  } + +  // clear around the rendered view if sized down +  CG_TileClear( ); + +  // offset vieworg appropriately if we're doing stereo separation +  VectorCopy( cg.refdef.vieworg, baseOrg ); + +  if( separation != 0 ) +    VectorMA( cg.refdef.vieworg, -separation, cg.refdef.viewaxis[ 1 ], +              cg.refdef.vieworg ); + +  // draw 3D view +  trap_R_RenderScene( &cg.refdef ); + +  // restore original viewpoint if running stereo +  if( separation != 0 ) +    VectorCopy( baseOrg, cg.refdef.vieworg ); + +  // first person blend blobs, done after AnglesToAxis +  if( !cg.renderingThirdPerson ) +    CG_PainBlend( ); + +  // draw status bar and other floating elements +  CG_Draw2D( ); +} + + + diff --git a/src/cgame/cg_drawtools.c b/src/cgame/cg_drawtools.c new file mode 100644 index 0000000..06ae071 --- /dev/null +++ b/src/cgame/cg_drawtools.c @@ -0,0 +1,378 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +// cg_drawtools.c -- helper functions called by cg_draw, cg_scoreboard, cg_info, etc + + +#include "cg_local.h" + +/* +=============== +CG_DrawPlane + +Draw a quad in 3 space - basically CG_DrawPic in 3 space +=============== +*/ +void CG_DrawPlane( vec3_t origin, vec3_t down, vec3_t right, qhandle_t shader ) +{ +  polyVert_t  verts[ 4 ]; +  vec3_t      temp; + +  VectorCopy( origin, verts[ 0 ].xyz ); +  verts[ 0 ].st[ 0 ] = 0; +  verts[ 0 ].st[ 1 ] = 0; +  verts[ 0 ].modulate[ 0 ] = 255; +  verts[ 0 ].modulate[ 1 ] = 255; +  verts[ 0 ].modulate[ 2 ] = 255; +  verts[ 0 ].modulate[ 3 ] = 255; + +  VectorAdd( origin, right, temp ); +  VectorCopy( temp, verts[ 1 ].xyz ); +  verts[ 1 ].st[ 0 ] = 1; +  verts[ 1 ].st[ 1 ] = 0; +  verts[ 1 ].modulate[ 0 ] = 255; +  verts[ 1 ].modulate[ 1 ] = 255; +  verts[ 1 ].modulate[ 2 ] = 255; +  verts[ 1 ].modulate[ 3 ] = 255; + +  VectorAdd( origin, right, temp ); +  VectorAdd( temp, down, temp ); +  VectorCopy( temp, verts[ 2 ].xyz ); +  verts[ 2 ].st[ 0 ] = 1; +  verts[ 2 ].st[ 1 ] = 1; +  verts[ 2 ].modulate[ 0 ] = 255; +  verts[ 2 ].modulate[ 1 ] = 255; +  verts[ 2 ].modulate[ 2 ] = 255; +  verts[ 2 ].modulate[ 3 ] = 255; + +  VectorAdd( origin, down, temp ); +  VectorCopy( temp, verts[ 3 ].xyz ); +  verts[ 3 ].st[ 0 ] = 0; +  verts[ 3 ].st[ 1 ] = 1; +  verts[ 3 ].modulate[ 0 ] = 255; +  verts[ 3 ].modulate[ 1 ] = 255; +  verts[ 3 ].modulate[ 2 ] = 255; +  verts[ 3 ].modulate[ 3 ] = 255; + +  trap_R_AddPolyToScene( shader, 4, verts ); +} + +/* +================ +CG_AdjustFrom640 + +Adjusted for resolution and screen aspect ratio +================ +*/ +void CG_AdjustFrom640( float *x, float *y, float *w, float *h ) +{ +#if 0 +  // adjust for wide screens +  if ( cgs.glconfig.vidWidth * 480 > cgs.glconfig.vidHeight * 640 ) { +    *x += 0.5 * ( cgs.glconfig.vidWidth - ( cgs.glconfig.vidHeight * 640 / 480 ) ); +  } +#endif +  // scale for screen sizes +  *x *= cgs.screenXScale; +  *y *= cgs.screenYScale; +  *w *= cgs.screenXScale; +  *h *= cgs.screenYScale; +} + +/* +================ +CG_FillRect + +Coordinates are 640*480 virtual values +================= +*/ +void CG_FillRect( float x, float y, float width, float height, const float *color ) +{ +  trap_R_SetColor( color ); + +  CG_AdjustFrom640( &x, &y, &width, &height ); +  trap_R_DrawStretchPic( x, y, width, height, 0, 0, 0, 0, cgs.media.whiteShader ); + +  trap_R_SetColor( NULL ); +} + + +/* +================ +CG_DrawSides + +Coords are virtual 640x480 +================ +*/ +void CG_DrawSides( float x, float y, float w, float h, float size ) +{ +  CG_AdjustFrom640( &x, &y, &w, &h ); +  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 ); +} + +void CG_DrawTopBottom( float x, float y, float w, float h, float size ) +{ +  CG_AdjustFrom640( &x, &y, &w, &h ); +  size *= cgs.screenYScale; +  trap_R_DrawStretchPic( x, y, w, size, 0, 0, 0, 0, cgs.media.whiteShader ); +  trap_R_DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, cgs.media.whiteShader ); +} + + +/* +================ +CG_DrawRect + +Coordinates are 640*480 virtual values +================= +*/ +void CG_DrawRect( float x, float y, float width, float height, float size, const float *color ) +{ +  trap_R_SetColor( color ); + +  CG_DrawTopBottom( x, y, width, height, size ); +  CG_DrawSides( x, y, width, height, size ); + +  trap_R_SetColor( NULL ); +} + + +/* +================ +CG_DrawPic + +Coordinates are 640*480 virtual values +================= +*/ +void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader ) +{ +  CG_AdjustFrom640( &x, &y, &width, &height ); +  trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); +} + + + +/* +================ +CG_DrawFadePic + +Coordinates are 640*480 virtual values +================= +*/ +void CG_DrawFadePic( float x, float y, float width, float height, vec4_t fcolor, +                     vec4_t tcolor, float amount, qhandle_t hShader ) +{ +  vec4_t  finalcolor; +  float   inverse; + +  inverse = 100 - amount; + +  CG_AdjustFrom640( &x, &y, &width, &height ); + +  finalcolor[ 0 ] = ( ( inverse * fcolor[ 0 ] ) + ( amount * tcolor[ 0 ] ) ) / 100; +  finalcolor[ 1 ] = ( ( inverse * fcolor[ 1 ] ) + ( amount * tcolor[ 1 ] ) ) / 100; +  finalcolor[ 2 ] = ( ( inverse * fcolor[ 2 ] ) + ( amount * tcolor[ 2 ] ) ) / 100; +  finalcolor[ 3 ] = ( ( inverse * fcolor[ 3 ] ) + ( amount * tcolor[ 3 ] ) ) / 100; + +  trap_R_SetColor( finalcolor ); +  trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); +  trap_R_SetColor( NULL ); +} + +/* +================= +CG_DrawStrlen + +Returns character count, skiping color escape codes +================= +*/ +int CG_DrawStrlen( const char *str ) +{ +  const char  *s = str; +  int         count = 0; + +  while( *s ) +  { +    if( Q_IsColorString( s ) ) +      s += 2; +    else +    { +      count++; +      s++; +    } +  } + +  return count; +} + +/* +============= +CG_TileClearBox + +This repeats a 64*64 tile graphic to fill the screen around a sized down +refresh window. +============= +*/ +static void CG_TileClearBox( int x, int y, int w, int h, qhandle_t hShader ) +{ +  float s1, t1, s2, t2; + +  s1 = x / 64.0; +  t1 = y / 64.0; +  s2 = ( x + w ) / 64.0; +  t2 = ( y + h ) / 64.0; +  trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, hShader ); +} + + + +/* +============== +CG_TileClear + +Clear around a sized down screen +============== +*/ +void CG_TileClear( void ) +{ +  int   top, bottom, left, right; +  int   w, h; + +  w = cgs.glconfig.vidWidth; +  h = cgs.glconfig.vidHeight; + +  if( cg.refdef.x == 0 && cg.refdef.y == 0 && +      cg.refdef.width == w && cg.refdef.height == h ) +    return;   // full screen rendering + +  top = cg.refdef.y; +  bottom = top + cg.refdef.height - 1; +  left = cg.refdef.x; +  right = left + cg.refdef.width - 1; + +  // clear above view screen +  CG_TileClearBox( 0, 0, w, top, cgs.media.backTileShader ); + +  // clear below view screen +  CG_TileClearBox( 0, bottom, w, h - bottom, cgs.media.backTileShader ); + +  // clear left of view screen +  CG_TileClearBox( 0, top, left, bottom - top + 1, cgs.media.backTileShader ); + +  // clear right of view screen +  CG_TileClearBox( right, top, w - right, bottom - top + 1, cgs.media.backTileShader ); +} + +/* +================ +CG_FadeColor +================ +*/ +float *CG_FadeColor( int startMsec, int totalMsec ) +{ +  static vec4_t   color; +  int     t; + +  if( startMsec == 0 ) +    return NULL; + +  t = cg.time - startMsec; + +  if( t >= totalMsec ) +    return NULL; + +  // fade out +  if( totalMsec - t < FADE_TIME ) +    color[ 3 ] = ( totalMsec - t ) * 1.0 / FADE_TIME; +  else +    color[ 3 ] = 1.0; + +  color[ 0 ] = color[ 1 ] = color[ 2 ] = 1; + +  return color; +} + +/* +================ +CG_WorldToScreen +================ +*/ +qboolean CG_WorldToScreen( vec3_t point, float *x, float *y ) +{ +	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 ); + +	VectorSubtract( point, cg.refdef.vieworg, trans ); + +	xc = 640.0f / 2.0f; +	yc = 480.0f / 2.0f; + +	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 ); + +  if( y ) +	  *y = yc - DotProduct( trans, cg.refdef.viewaxis[ 2 ] ) * yc / ( z * py ); + +	return qtrue; +} + +/* +================ +CG_KeyBinding +================ +*/ +char *CG_KeyBinding( const char *bind ) +{ +  static char key[ 32 ]; +  char bindbuff[ MAX_CVAR_VALUE_STRING ]; +  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++ ) +  { +    trap_Key_GetBindingBuf( i, bindbuff, sizeof( bindbuff ) ); +    if( !Q_stricmp( bindbuff, 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; +} diff --git a/src/cgame/cg_ents.c b/src/cgame/cg_ents.c new file mode 100644 index 0000000..2d2e808 --- /dev/null +++ b/src/cgame/cg_ents.c @@ -0,0 +1,1256 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +// cg_ents.c -- present snapshot entities, happens every single frame + + +#include "cg_local.h" + +/* +====================== +CG_DrawBoxFace + +Draws a bounding box face +====================== +*/ +static void CG_DrawBoxFace( vec3_t a, vec3_t b, vec3_t c, vec3_t d ) +{ +  polyVert_t  verts[ 4 ]; +  vec4_t      color = { 255.0f, 0.0f, 0.0f, 128.0f }; + +  VectorCopy( d, verts[ 0 ].xyz ); +  verts[ 0 ].st[ 0 ] = 1; +  verts[ 0 ].st[ 1 ] = 1; +  Vector4Copy( color, verts[ 0 ].modulate ); + +  VectorCopy( c, verts[ 1 ].xyz ); +  verts[ 1 ].st[ 0 ] = 1; +  verts[ 1 ].st[ 1 ] = 0; +  Vector4Copy( color, verts[ 1 ].modulate ); + +  VectorCopy( b, verts[ 2 ].xyz ); +  verts[ 2 ].st[ 0 ] = 0; +  verts[ 2 ].st[ 1 ] = 0; +  Vector4Copy( color, verts[ 2 ].modulate ); + +  VectorCopy( a, verts[ 3 ].xyz ); +  verts[ 3 ].st[ 0 ] = 0; +  verts[ 3 ].st[ 1 ] = 1; +  Vector4Copy( color, verts[ 3 ].modulate ); + +  trap_R_AddPolyToScene( cgs.media.outlineShader, 4, verts ); +} + +/* +====================== +CG_DrawBoundingBox + +Draws a bounding box +====================== +*/ +void CG_DrawBoundingBox( vec3_t origin, vec3_t mins, vec3_t maxs ) +{ +  vec3_t  ppp, mpp, mmp, pmp; +  vec3_t  mmm, pmm, ppm, mpm; + +  ppp[ 0 ] = origin[ 0 ] + maxs[ 0 ]; +  ppp[ 1 ] = origin[ 1 ] + maxs[ 1 ]; +  ppp[ 2 ] = origin[ 2 ] + maxs[ 2 ]; + +  mpp[ 0 ] = origin[ 0 ] + mins[ 0 ]; +  mpp[ 1 ] = origin[ 1 ] + maxs[ 1 ]; +  mpp[ 2 ] = origin[ 2 ] + maxs[ 2 ]; + +  mmp[ 0 ] = origin[ 0 ] + mins[ 0 ]; +  mmp[ 1 ] = origin[ 1 ] + mins[ 1 ]; +  mmp[ 2 ] = origin[ 2 ] + maxs[ 2 ]; + +  pmp[ 0 ] = origin[ 0 ] + maxs[ 0 ]; +  pmp[ 1 ] = origin[ 1 ] + mins[ 1 ]; +  pmp[ 2 ] = origin[ 2 ] + maxs[ 2 ]; + +  ppm[ 0 ] = origin[ 0 ] + maxs[ 0 ]; +  ppm[ 1 ] = origin[ 1 ] + maxs[ 1 ]; +  ppm[ 2 ] = origin[ 2 ] + mins[ 2 ]; + +  mpm[ 0 ] = origin[ 0 ] + mins[ 0 ]; +  mpm[ 1 ] = origin[ 1 ] + maxs[ 1 ]; +  mpm[ 2 ] = origin[ 2 ] + mins[ 2 ]; + +  mmm[ 0 ] = origin[ 0 ] + mins[ 0 ]; +  mmm[ 1 ] = origin[ 1 ] + mins[ 1 ]; +  mmm[ 2 ] = origin[ 2 ] + mins[ 2 ]; + +  pmm[ 0 ] = origin[ 0 ] + maxs[ 0 ]; +  pmm[ 1 ] = origin[ 1 ] + mins[ 1 ]; +  pmm[ 2 ] = origin[ 2 ] + mins[ 2 ]; + +  //phew! + +  CG_DrawBoxFace( ppp, mpp, mmp, pmp ); +  CG_DrawBoxFace( ppp, pmp, pmm, ppm ); +  CG_DrawBoxFace( mpp, ppp, ppm, mpm ); +  CG_DrawBoxFace( mmp, mpp, mpm, mmm ); +  CG_DrawBoxFace( pmp, mmp, mmm, pmm ); +  CG_DrawBoxFace( mmm, mpm, ppm, pmm ); +} + + +/* +====================== +CG_PositionEntityOnTag + +Modifies the entities position and axis by the given +tag location +====================== +*/ +void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, +                             qhandle_t parentModel, char *tagName ) +{ +  int           i; +  orientation_t lerped; + +  // lerp the tag +  trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, +                  1.0 - parent->backlerp, tagName ); + +  // FIXME: allow origin offsets along tag? +  VectorCopy( parent->origin, entity->origin ); +  for( i = 0; i < 3; i++ ) +    VectorMA( entity->origin, lerped.origin[ i ], parent->axis[ i ], entity->origin ); + +  // had to cast away the const to avoid compiler problems... +  MatrixMultiply( lerped.axis, ( (refEntity_t *)parent )->axis, entity->axis ); +  entity->backlerp = parent->backlerp; +} + + +/* +====================== +CG_PositionRotatedEntityOnTag + +Modifies the entities position and axis by the given +tag location +====================== +*/ +void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, +                                    qhandle_t parentModel, char *tagName ) +{ +  int           i; +  orientation_t lerped; +  vec3_t        tempAxis[ 3 ]; + +//AxisClear( entity->axis ); +  // lerp the tag +  trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, +                  1.0 - parent->backlerp, tagName ); + +  // FIXME: allow origin offsets along tag? +  VectorCopy( parent->origin, entity->origin ); +  for( i = 0; i < 3; i++ ) +    VectorMA( entity->origin, lerped.origin[ i ], parent->axis[ i ], entity->origin ); + +  // had to cast away the const to avoid compiler problems... +  MatrixMultiply( entity->axis, lerped.axis, tempAxis ); +  MatrixMultiply( tempAxis, ( (refEntity_t *)parent )->axis, entity->axis ); +} + + + +/* +========================================================================== + +FUNCTIONS CALLED EACH FRAME + +========================================================================== +*/ + +/* +====================== +CG_SetEntitySoundPosition + +Also called by event processing code +====================== +*/ +void CG_SetEntitySoundPosition( centity_t *cent ) +{ +  if( cent->currentState.solid == SOLID_BMODEL ) +  { +    vec3_t  origin; +    float   *v; + +    v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ]; +    VectorAdd( cent->lerpOrigin, v, origin ); +    trap_S_UpdateEntityPosition( cent->currentState.number, origin ); +  } +  else +    trap_S_UpdateEntityPosition( cent->currentState.number, cent->lerpOrigin ); +} + +/* +================== +CG_EntityEffects + +Add continuous entity effects, like local entity emission and lighting +================== +*/ +static void CG_EntityEffects( centity_t *cent ) +{ +  // update sound origins +  CG_SetEntitySoundPosition( cent ); + +  // add loop sound +  if( cent->currentState.loopSound ) +  { +    if( cent->currentState.eType != ET_SPEAKER ) +    { +      trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, +                              cgs.gameSounds[ cent->currentState.loopSound ] ); +    } +    else +    { +      trap_S_AddRealLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, +                                  cgs.gameSounds[ cent->currentState.loopSound ] ); +    } +  } + + +  // constant light glow +  if ( cent->currentState.constantLight ) +  { +    int   cl; +    int   i, r, g, b; + +    cl = cent->currentState.constantLight; +    r = cl & 255; +    g = ( cl >> 8 ) & 255; +    b = ( cl >> 16 ) & 255; +    i = ( ( cl >> 24 ) & 255 ) * 4; +    trap_R_AddLightToScene( cent->lerpOrigin, i, r, g, b ); +  } + +  if( CG_IsTrailSystemValid( ¢->muzzleTS ) ) +  { +		//FIXME hack to prevent tesla trails reaching too far +    if( cent->currentState.eType == ET_BUILDABLE ) +    { +      vec3_t  front, back; + +      CG_AttachmentPoint( ¢->muzzleTS->frontAttachment, front ); +      CG_AttachmentPoint( ¢->muzzleTS->backAttachment, back ); + +      if( Distance( front, back ) > ( TESLAGEN_RANGE * M_ROOT3 ) ) +        CG_DestroyTrailSystem( ¢->muzzleTS ); +    } + +    if( cg.time > cent->muzzleTSDeathTime && CG_IsTrailSystemValid( ¢->muzzleTS ) ) +      CG_DestroyTrailSystem( ¢->muzzleTS ); +  } +} + + +/* +================== +CG_General +================== +*/ +static void CG_General( centity_t *cent ) +{ +  refEntity_t     ent; +  entityState_t   *s1; + +  s1 = ¢->currentState; + +  // if set to invisible, skip +  if( !s1->modelindex ) +    return; + +  memset( &ent, 0, sizeof( ent ) ); + +  // set frame + +  ent.frame = s1->frame; +  ent.oldframe = ent.frame; +  ent.backlerp = 0; + +  VectorCopy( cent->lerpOrigin, ent.origin); +  VectorCopy( cent->lerpOrigin, ent.oldorigin); + +  ent.hModel = cgs.gameModels[ s1->modelindex ]; + +  // player model +  if( s1->number == cg.snap->ps.clientNum ) +    ent.renderfx |= RF_THIRD_PERSON;  // only draw from mirrors + +  // convert angles to axis +  AnglesToAxis( cent->lerpAngles, ent.axis ); + +  // add to refresh list +  trap_R_AddRefEntityToScene( &ent ); +} + +/* +================== +CG_Speaker + +Speaker entities can automatically play sounds +================== +*/ +static void CG_Speaker( centity_t *cent ) +{ +  if( ! cent->currentState.clientNum ) +  { // FIXME: use something other than clientNum... +    return;   // not auto triggering +  } + +  if( cg.time < cent->miscTime ) +    return; + +  trap_S_StartSound( NULL, cent->currentState.number, CHAN_ITEM, cgs.gameSounds[ cent->currentState.eventParm ] ); + +  //  ent->s.frame = ent->wait * 10; +  //  ent->s.clientNum = ent->random * 10; +  cent->miscTime = cg.time + cent->currentState.frame * 100 + cent->currentState.clientNum * 100 * crandom( ); +} + + +//============================================================================ + +/* +=============== +CG_LaunchMissile +=============== +*/ +static void CG_LaunchMissile( centity_t *cent ) +{ +  entityState_t       *es; +  const weaponInfo_t  *wi; +  particleSystem_t    *ps; +  trailSystem_t       *ts; +  weapon_t            weapon; +  weaponMode_t        weaponMode; + +  es = ¢->currentState; + +  weapon = es->weapon; +  if( weapon > WP_NUM_WEAPONS ) +    weapon = WP_NONE; + +  wi = &cg_weapons[ weapon ]; +  weaponMode = es->generic1; + +  if( wi->wim[ weaponMode ].missileParticleSystem ) +  { +    ps = CG_SpawnNewParticleSystem( wi->wim[ weaponMode ].missileParticleSystem ); + +    if( CG_IsParticleSystemValid( &ps ) ) +    { +      CG_SetAttachmentCent( &ps->attachment, cent ); +      CG_AttachToCent( &ps->attachment ); +    } +  } + +  if( wi->wim[ weaponMode ].missileTrailSystem ) +  { +    ts = CG_SpawnNewTrailSystem( wi->wim[ weaponMode ].missileTrailSystem ); + +    if( CG_IsTrailSystemValid( &ts ) ) +    { +      CG_SetAttachmentCent( &ts->frontAttachment, cent ); +      CG_AttachToCent( &ts->frontAttachment ); +    } +  } +} + +/* +=============== +CG_Missile +=============== +*/ +static void CG_Missile( centity_t *cent ) +{ +  refEntity_t             ent; +  entityState_t           *es; +  const weaponInfo_t      *wi; +  weapon_t                weapon; +  weaponMode_t            weaponMode; +  const weaponInfoMode_t  *wim; + +  es = ¢->currentState; + +  weapon = es->weapon; +  if( weapon > WP_NUM_WEAPONS ) +    weapon = WP_NONE; + +  wi = &cg_weapons[ weapon ]; +  weaponMode = es->generic1; + +  wim = &wi->wim[ weaponMode ]; + +  // calculate the axis +  VectorCopy( es->angles, cent->lerpAngles ); + +  // add dynamic light +  if( wim->missileDlight ) +  { +    trap_R_AddLightToScene( cent->lerpOrigin, wim->missileDlight, +      wim->missileDlightColor[ 0 ], +      wim->missileDlightColor[ 1 ], +      wim->missileDlightColor[ 2 ] ); +  } + +  // add missile sound +  if( wim->missileSound ) +  { +    vec3_t  velocity; + +    BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity ); + +    trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, wim->missileSound ); +  } + +  // create the render entity +  memset( &ent, 0, sizeof( ent ) ); +  VectorCopy( cent->lerpOrigin, ent.origin ); +  VectorCopy( cent->lerpOrigin, ent.oldorigin ); + +  if( wim->usesSpriteMissle ) +  { +    ent.reType = RT_SPRITE; +    ent.radius = wim->missileSpriteSize; +    ent.rotation = 0; +    ent.customShader = wim->missileSprite; +    ent.shaderRGBA[ 0 ] = 0xFF; +    ent.shaderRGBA[ 1 ] = 0xFF; +    ent.shaderRGBA[ 2 ] = 0xFF; +    ent.shaderRGBA[ 3 ] = 0xFF; +  } +  else +  { +    ent.hModel = wim->missileModel; +    ent.renderfx = wim->missileRenderfx | RF_NOSHADOW; + +    // convert direction of travel into axis +    if( VectorNormalize2( es->pos.trDelta, ent.axis[ 0 ] ) == 0 ) +      ent.axis[ 0 ][ 2 ] = 1; + +    // spin as it moves +    if( es->pos.trType != TR_STATIONARY && wim->missileRotates ) +      RotateAroundDirection( ent.axis, cg.time / 4 ); +    else +      RotateAroundDirection( ent.axis, es->time ); + +    if( wim->missileAnimates ) +    { +      int timeSinceStart = cg.time - es->time; + +      if( wim->missileAnimLooping ) +      { +        ent.frame = wim->missileAnimStartFrame + +          (int)( ( timeSinceStart / 1000.0f ) * wim->missileAnimFrameRate ) % +          wim->missileAnimNumFrames; +      } +      else +      { +        ent.frame = wim->missileAnimStartFrame + +          (int)( ( timeSinceStart / 1000.0f ) * wim->missileAnimFrameRate ); + +        if( ent.frame > ( wim->missileAnimStartFrame + wim->missileAnimNumFrames ) ) +          ent.frame = wim->missileAnimStartFrame + wim->missileAnimNumFrames; +      } +    } +  } + +  //only refresh if there is something to display +  if( wim->missileSprite || wim->missileModel ) +    trap_R_AddRefEntityToScene( &ent ); +} + +/* +=============== +CG_Mover +=============== +*/ +static void CG_Mover( centity_t *cent ) +{ +  refEntity_t     ent; +  entityState_t   *s1; + +  s1 = ¢->currentState; + +  // create the render entity +  memset( &ent, 0, sizeof( ent ) ); +  VectorCopy( cent->lerpOrigin, ent.origin ); +  VectorCopy( cent->lerpOrigin, ent.oldorigin ); +  AnglesToAxis( cent->lerpAngles, ent.axis ); + +  ent.renderfx = RF_NOSHADOW; + +  // flicker between two skins (FIXME?) +  ent.skinNum = ( cg.time >> 6 ) & 1; + +  // get the model, either as a bmodel or a modelindex +  if( s1->solid == SOLID_BMODEL ) +    ent.hModel = cgs.inlineDrawModel[ s1->modelindex ]; +  else +    ent.hModel = cgs.gameModels[ s1->modelindex ]; + +  // add to refresh list +  trap_R_AddRefEntityToScene( &ent ); + +  // add the secondary model +  if( s1->modelindex2 ) +  { +    ent.skinNum = 0; +    ent.hModel = cgs.gameModels[ s1->modelindex2 ]; +    trap_R_AddRefEntityToScene( &ent ); +  } + +} + +/* +=============== +CG_Beam + +Also called as an event +=============== +*/ +void CG_Beam( centity_t *cent ) +{ +  refEntity_t     ent; +  entityState_t   *s1; + +  s1 = ¢->currentState; + +  // create the render entity +  memset( &ent, 0, sizeof( ent ) ); +  VectorCopy( s1->pos.trBase, ent.origin ); +  VectorCopy( s1->origin2, ent.oldorigin ); +  AxisClear( ent.axis ); +  ent.reType = RT_BEAM; + +  ent.renderfx = RF_NOSHADOW; + +  // add to refresh list +  trap_R_AddRefEntityToScene( &ent ); +} + + +/* +=============== +CG_Portal +=============== +*/ +static void CG_Portal( centity_t *cent ) +{ +  refEntity_t     ent; +  entityState_t   *s1; + +  s1 = ¢->currentState; + +  // create the render entity +  memset( &ent, 0, sizeof( ent ) ); +  VectorCopy( cent->lerpOrigin, ent.origin ); +  VectorCopy( s1->origin2, ent.oldorigin ); +  ByteToDir( s1->eventParm, ent.axis[ 0 ] ); +  PerpendicularVector( ent.axis[ 1 ], ent.axis[ 0 ] ); + +  // negating this tends to get the directions like they want +  // we really should have a camera roll value +  VectorSubtract( vec3_origin, ent.axis[ 1 ], ent.axis[ 1 ] ); + +  CrossProduct( ent.axis[ 0 ], ent.axis[ 1 ], ent.axis[ 2 ] ); +  ent.reType = RT_PORTALSURFACE; +  ent.oldframe = s1->powerups; +  ent.frame = s1->frame;    // rotation speed +  ent.skinNum = s1->clientNum / 256.0 * 360;  // roll offset + +  // add to refresh list +  trap_R_AddRefEntityToScene( &ent ); +} + +//============================================================================ + +#define SETBOUNDS(v1,v2,r)  ((v1)[0]=(-r/2),(v1)[1]=(-r/2),(v1)[2]=(-r/2),\ +                             (v2)[0]=(r/2),(v2)[1]=(r/2),(v2)[2]=(r/2)) +#define RADIUSSTEP          0.5f + +#define FLARE_OFF       0 +#define FLARE_NOFADE    1 +#define FLARE_TIMEFADE  2 +#define FLARE_REALFADE  3 + +/* +========================= +CG_LightFlare +========================= +*/ +static void CG_LightFlare( centity_t *cent ) +{ +  refEntity_t   flare; +  entityState_t *es; +  vec3_t        forward, delta; +  float         len; +  trace_t       tr; +  float         maxAngle; +  vec3_t        mins, maxs, start, end; +  float         srcRadius, srLocal, ratio = 1.0f; +  int           entityNum; + +  es = ¢->currentState; + +  if( cg.renderingThirdPerson ) +    entityNum = MAGIC_TRACE_HACK; +  else +    entityNum = cg.predictedPlayerState.clientNum; + +  //don't draw light flares +  if( cg_lightFlare.integer == FLARE_OFF ) +    return; + +  //flare is "off" +  if( es->eFlags & EF_NODRAW ) +    return; + +  CG_Trace( &tr, cg.refdef.vieworg, NULL, NULL, es->angles2, +            entityNum, MASK_SHOT ); + +  //if there is no los between the view and the flare source +  //it definately cannot be seen +  if( tr.fraction < 1.0f || tr.allsolid ) +    return; + +  memset( &flare, 0, sizeof( flare ) ); + +  flare.reType = RT_SPRITE; +  flare.customShader = cgs.gameShaders[ es->modelindex ]; +  flare.shaderRGBA[ 0 ] = 0xFF; +  flare.shaderRGBA[ 1 ] = 0xFF; +  flare.shaderRGBA[ 2 ] = 0xFF; +  flare.shaderRGBA[ 3 ] = 0xFF; + +  //flares always drawn before the rest of the scene +  flare.renderfx |= RF_DEPTHHACK; + +  //bunch of geometry +  AngleVectors( es->angles, forward, NULL, NULL ); +  VectorCopy( cent->lerpOrigin, flare.origin ); +  VectorSubtract( flare.origin, cg.refdef.vieworg, delta ); +  len = VectorLength( delta ); +  VectorNormalize( delta ); + +  //flare is too close to camera to be drawn +  if( len < es->generic1 ) +    return; + +  //don't bother for flares behind the view plane +  if( DotProduct( delta, cg.refdef.viewaxis[ 0 ] ) < 0.0 ) +    return; + +  //only recalculate radius and ratio every three frames +  if( !( cg.clientFrame % 2 ) ) +  { +    //can only see the flare when in front of it +    flare.radius = len / es->origin2[ 0 ]; + +    if( es->origin2[ 2 ] == 0 ) +      srcRadius = srLocal = flare.radius / 2.0f; +    else +      srcRadius = srLocal = len / es->origin2[ 2 ]; + +    maxAngle = es->origin2[ 1 ]; + +    if( maxAngle > 0.0f ) +    { +      float radiusMod = 1.0f - ( 180.0f - RAD2DEG( +            acos( DotProduct( delta, forward ) ) ) ) / maxAngle; + +      if( radiusMod < 0.0f ) +        radiusMod = 0.0f; + +      flare.radius *= radiusMod; +    } + +    if( flare.radius < 0.0f ) +      flare.radius = 0.0f; + +    VectorMA( flare.origin, -flare.radius, delta, end ); +    VectorMA( cg.refdef.vieworg, flare.radius, delta, start ); + +    if( cg_lightFlare.integer == FLARE_REALFADE ) +    { +      //"correct" flares +      CG_BiSphereTrace( &tr, cg.refdef.vieworg, end, +          1.0f, srcRadius, entityNum, MASK_SHOT ); + +      if( tr.fraction < 1.0f ) +        ratio = tr.lateralFraction; +      else +        ratio = 1.0f; +    } +    else if( cg_lightFlare.integer == FLARE_TIMEFADE ) +    { +      //draw timed flares +      SETBOUNDS( mins, maxs, srcRadius ); +      CG_Trace( &tr, start, mins, maxs, end, +                entityNum, MASK_SHOT ); + +      if( ( tr.fraction < 1.0f || tr.startsolid ) && cent->lfs.status ) +      { +        cent->lfs.status = qfalse; +        cent->lfs.lastTime = cg.time; +      } +      else if( ( tr.fraction == 1.0f && !tr.startsolid ) && !cent->lfs.status ) +      { +        cent->lfs.status = qtrue; +        cent->lfs.lastTime = cg.time; +      } + +      //fade flare up +      if( cent->lfs.status ) +      { +        if( cent->lfs.lastTime + es->time > cg.time ) +          ratio = (float)( cg.time - cent->lfs.lastTime ) / es->time; +      } + +      //fade flare down +      if( !cent->lfs.status ) +      { +        if( cent->lfs.lastTime + es->time > cg.time ) +        { +          ratio = (float)( cg.time - cent->lfs.lastTime ) / es->time; +          ratio = 1.0f - ratio; +        } +        else +          ratio = 0.0f; +      } +    } +    else if( cg_lightFlare.integer == FLARE_NOFADE ) +    { +      //draw nofade flares +      SETBOUNDS( mins, maxs, srcRadius ); +      CG_Trace( &tr, start, mins, maxs, end, +                entityNum, MASK_SHOT ); + +      //flare source occluded +      if( ( tr.fraction < 1.0f || tr.startsolid ) ) +        ratio = 0.0f; +    } +  } +  else +  { +    ratio        = cent->lfs.lastRatio; +    flare.radius = cent->lfs.lastRadius; +  } + +  cent->lfs.lastRatio  = ratio; +  cent->lfs.lastRadius = flare.radius; + +  if( ratio < 1.0f ) +  { +    flare.radius *= ratio; +    flare.shaderRGBA[ 3 ] = (byte)( (float)flare.shaderRGBA[ 3 ] * ratio ); +  } + +  if( flare.radius <= 0.0f ) +    return; + +  trap_R_AddRefEntityToScene( &flare ); +} + +/* +========================= +CG_Lev2ZapChain +========================= +*/ +static void CG_Lev2ZapChain( centity_t *cent ) +{ +  int           i; +  entityState_t *es; +  centity_t     *source = NULL, *target = NULL; + +  es = ¢->currentState; + +  for( i = 0; i <= 2; i++ ) +  { +    switch( i ) +    { +      case 0: +        if( es->time <= 0 ) +          continue; + +        source = &cg_entities[ es->powerups ]; +        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; +    } + +    if( !CG_IsTrailSystemValid( ¢->level2ZapTS[ i ] ) ) +      cent->level2ZapTS[ i ] = CG_SpawnNewTrailSystem( cgs.media.level2ZapTS ); + +    if( CG_IsTrailSystemValid( ¢->level2ZapTS[ i ] ) ) +    { +      CG_SetAttachmentCent( ¢->level2ZapTS[ i ]->frontAttachment, source ); +      CG_SetAttachmentCent( ¢->level2ZapTS[ i ]->backAttachment, target ); +      CG_AttachToCent( ¢->level2ZapTS[ i ]->frontAttachment ); +      CG_AttachToCent( ¢->level2ZapTS[ i ]->backAttachment ); +    } +  } +} + +/* +========================= +CG_AdjustPositionForMover + +Also called by client movement prediction code +========================= +*/ +void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ) +{ +  centity_t *cent; +  vec3_t    oldOrigin, origin, deltaOrigin; +  vec3_t    oldAngles, angles, deltaAngles; + +  if( moverNum <= 0 || moverNum >= ENTITYNUM_MAX_NORMAL ) +  { +    VectorCopy( in, out ); +    return; +  } + +  cent = &cg_entities[ moverNum ]; + +  if( cent->currentState.eType != ET_MOVER ) +  { +    VectorCopy( in, out ); +    return; +  } + +  BG_EvaluateTrajectory( ¢->currentState.pos, fromTime, oldOrigin ); +  BG_EvaluateTrajectory( ¢->currentState.apos, fromTime, oldAngles ); + +  BG_EvaluateTrajectory( ¢->currentState.pos, toTime, origin ); +  BG_EvaluateTrajectory( ¢->currentState.apos, toTime, angles ); + +  VectorSubtract( origin, oldOrigin, deltaOrigin ); +  VectorSubtract( angles, oldAngles, deltaAngles ); + +  VectorAdd( in, deltaOrigin, out ); + +  // FIXME: origin change when on a rotating object +} + + +/* +============================= +CG_InterpolateEntityPosition +============================= +*/ +static void CG_InterpolateEntityPosition( centity_t *cent ) +{ +  vec3_t    current, next; +  float     f; + +  // it would be an internal error to find an entity that interpolates without +  // a snapshot ahead of the current one +  if( cg.nextSnap == NULL ) +    CG_Error( "CG_InterpoateEntityPosition: cg.nextSnap == NULL" ); + +  f = cg.frameInterpolation; + +  // this will linearize a sine or parabolic curve, but it is important +  // to not extrapolate player positions if more recent data is available +  BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, current ); +  BG_EvaluateTrajectory( ¢->nextState.pos, cg.nextSnap->serverTime, next ); + +  cent->lerpOrigin[ 0 ] = current[ 0 ] + f * ( next[ 0 ] - current[ 0 ] ); +  cent->lerpOrigin[ 1 ] = current[ 1 ] + f * ( next[ 1 ] - current[ 1 ] ); +  cent->lerpOrigin[ 2 ] = current[ 2 ] + f * ( next[ 2 ] - current[ 2 ] ); + +  BG_EvaluateTrajectory( ¢->currentState.apos, cg.snap->serverTime, current ); +  BG_EvaluateTrajectory( ¢->nextState.apos, cg.nextSnap->serverTime, next ); + +  cent->lerpAngles[ 0 ] = LerpAngle( current[ 0 ], next[ 0 ], f ); +  cent->lerpAngles[ 1 ] = LerpAngle( current[ 1 ], next[ 1 ], f ); +  cent->lerpAngles[ 2 ] = LerpAngle( current[ 2 ], next[ 2 ], f ); + +} + +/* +=============== +CG_CalcEntityLerpPositions + +=============== +*/ +static void CG_CalcEntityLerpPositions( centity_t *cent ) +{ +  // this will be set to how far forward projectiles will be extrapolated +  int timeshift = 0; + +  // if this player does not want to see extrapolated players +  if( !cg_smoothClients.integer ) +  { +    // make sure the clients use TR_INTERPOLATE +    if( cent->currentState.number < MAX_CLIENTS ) +    { +      cent->currentState.pos.trType = TR_INTERPOLATE; +      cent->nextState.pos.trType = TR_INTERPOLATE; +    } +  } + +  if( cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE ) +  { +    CG_InterpolateEntityPosition( cent ); +    return; +  } + +  // first see if we can interpolate between two snaps for +  // linear extrapolated clients +  if( cent->interpolate && cent->currentState.pos.trType == TR_LINEAR_STOP && +      cent->currentState.number < MAX_CLIENTS ) +  { +    CG_InterpolateEntityPosition( cent ); +    return; +  } + +  if( cg_projectileNudge.integer > 0 && +    cent->currentState.eType == ET_MISSILE && +    !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) +  { +    timeshift = cg.ping; +  } + +  // just use the current frame and evaluate as best we can +  BG_EvaluateTrajectory( ¢->currentState.pos, +    ( cg.time + timeshift ), cent->lerpOrigin ); +  BG_EvaluateTrajectory( ¢->currentState.apos, +    ( cg.time + timeshift ), cent->lerpAngles ); + +  if( timeshift ) +  { +    trace_t tr; +    vec3_t lastOrigin; +	 +    BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, lastOrigin ); +	 +    CG_Trace( &tr, lastOrigin, vec3_origin, vec3_origin, cent->lerpOrigin, +      cent->currentState.number, MASK_SHOT ); +	 +    // don't let the projectile go through the floor +    if( tr.fraction < 1.0f ) +      VectorLerp( tr.fraction, lastOrigin, cent->lerpOrigin, cent->lerpOrigin ); +  } + +  // adjust for riding a mover if it wasn't rolled into the predicted +  // player state +  if( cent != &cg.predictedPlayerEntity ) +  { +    CG_AdjustPositionForMover( cent->lerpOrigin, cent->currentState.groundEntityNum, +                               cg.snap->serverTime, cg.time, cent->lerpOrigin ); +  } +} + + +/* +=============== +CG_CEntityPVSEnter + +=============== +*/ +static void CG_CEntityPVSEnter( centity_t *cent ) +{ +  entityState_t *es = ¢->currentState; + +  if( cg_debugPVS.integer ) +    CG_Printf( "Entity %d entered PVS\n", cent->currentState.number ); + +  switch( es->eType ) +  { +    case ET_MISSILE: +      CG_LaunchMissile( cent ); +      break; +  } + +  //clear any particle systems from previous uses of this centity_t +  cent->muzzlePS = NULL; +  cent->muzzlePsTrigger = qfalse; +  cent->jetPackPS = NULL; +  cent->jetPackState = JPS_OFF; +  cent->buildablePS = NULL; +  cent->entityPS = NULL; +  cent->entityPSMissing = qfalse; + +  //make sure that the buildable animations are in a consistent state +  //when a buildable enters the PVS +  cent->buildableAnim = cent->lerpFrame.animationNumber = BANIM_NONE; +  cent->oldBuildableAnim = es->legsAnim; +} + + +/* +=============== +CG_CEntityPVSLeave + +=============== +*/ +static void CG_CEntityPVSLeave( centity_t *cent ) +{ +  int           i; +  entityState_t *es = ¢->currentState; + +  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++ ) +      { +        if( CG_IsTrailSystemValid( ¢->level2ZapTS[ i ] ) ) +          CG_DestroyTrailSystem( ¢->level2ZapTS[ i ] ); +      } +      break; +  } +} + + +/* +=============== +CG_AddCEntity + +=============== +*/ +static void CG_AddCEntity( centity_t *cent ) +{ +  // event-only entities will have been dealt with already +  if( cent->currentState.eType >= ET_EVENTS ) +    return; + +  // calculate the current origin +  CG_CalcEntityLerpPositions( cent ); + +  // add automatic effects +  CG_EntityEffects( cent ); + +  switch( cent->currentState.eType ) +  { +    default: +      CG_Error( "Bad entity type: %i\n", cent->currentState.eType ); +      break; + +    case ET_INVISIBLE: +    case ET_PUSH_TRIGGER: +    case ET_TELEPORT_TRIGGER: +      break; + +    case ET_GENERAL: +      CG_General( cent ); +      break; + +    case ET_CORPSE: +      CG_Corpse( cent ); +      break; + +    case ET_PLAYER: +      CG_Player( cent ); +      break; + +    case ET_BUILDABLE: +      CG_Buildable( cent ); +      break; + +    case ET_MISSILE: +      CG_Missile( cent ); +      break; + +    case ET_MOVER: +      CG_Mover( cent ); +      break; + +    case ET_BEAM: +      CG_Beam( cent ); +      break; + +    case ET_PORTAL: +      CG_Portal( cent ); +      break; + +    case ET_SPEAKER: +      CG_Speaker( cent ); +      break; + +    case ET_PARTICLE_SYSTEM: +      CG_ParticleSystemEntity( cent ); +      break; + +    case ET_ANIMMAPOBJ: +      CG_AnimMapObj( cent ); +      break; + +    case ET_MODELDOOR: +      CG_ModelDoor( cent ); +      break; + +    case ET_LIGHTFLARE: +      CG_LightFlare( cent ); +      break; + +    case ET_LEV2_ZAP_CHAIN: +      CG_Lev2ZapChain( cent ); +      break; +  } +} + +/* +=============== +CG_AddPacketEntities + +=============== +*/ +void CG_AddPacketEntities( void ) +{ +  int             num; +  centity_t       *cent; +  playerState_t   *ps; + +  // set cg.frameInterpolation +  if( cg.nextSnap ) +  { +    int   delta; + +    delta = ( cg.nextSnap->serverTime - cg.snap->serverTime ); + +    if( delta == 0 ) +      cg.frameInterpolation = 0; +    else +      cg.frameInterpolation = (float)( cg.time - cg.snap->serverTime ) / delta; +  } +  else +  { +    cg.frameInterpolation = 0;  // actually, it should never be used, because +                  // no entities should be marked as interpolating +  } + +  // the auto-rotating items will all have the same axis +  cg.autoAngles[ 0 ] = 0; +  cg.autoAngles[ 1 ] = ( cg.time & 2047 ) * 360 / 2048.0; +  cg.autoAngles[ 2 ] = 0; + +  cg.autoAnglesFast[ 0 ] = 0; +  cg.autoAnglesFast[ 1 ] = ( cg.time & 1023 ) * 360 / 1024.0f; +  cg.autoAnglesFast[ 2 ] = 0; + +  AnglesToAxis( cg.autoAngles, cg.autoAxis ); +  AnglesToAxis( cg.autoAnglesFast, cg.autoAxisFast ); + +  // generate and add the entity from the playerstate +  ps = &cg.predictedPlayerState; +  BG_PlayerStateToEntityState( ps, &cg.predictedPlayerEntity.currentState, qfalse ); +  cg.predictedPlayerEntity.valid = qtrue; +  CG_AddCEntity( &cg.predictedPlayerEntity ); + +  // lerp the non-predicted value for lightning gun origins +  CG_CalcEntityLerpPositions( &cg_entities[ cg.snap->ps.clientNum ] ); + +  // scanner +  CG_UpdateEntityPositions( ); + +  for( num = 0; num < MAX_GENTITIES; num++ ) +    cg_entities[ num ].valid = qfalse; + +  // add each entity sent over by the server +  for( num = 0; num < cg.snap->numEntities; num++ ) +  { +    cent = &cg_entities[ cg.snap->entities[ num ].number ]; +    cent->valid = qtrue; +  } + +  for( num = 0; num < MAX_GENTITIES; num++ ) +  { +    cent = &cg_entities[ num ]; + +    if( cent->valid && !cent->oldValid ) +      CG_CEntityPVSEnter( cent ); +    else if( !cent->valid && cent->oldValid ) +      CG_CEntityPVSLeave( cent ); + +    cent->oldValid = cent->valid; +  } + +  // add each entity sent over by the server +  for( num = 0; num < cg.snap->numEntities; num++ ) +  { +    cent = &cg_entities[ cg.snap->entities[ num ].number ]; +    CG_AddCEntity( cent ); +  } + +  //make an attempt at drawing bounding boxes of selected entity types +  if( cg_drawBBOX.integer ) +  { +    for( num = 0; num < cg.snap->numEntities; num++ ) +    { +      float         x, zd, zu; +      vec3_t        mins, maxs; +      entityState_t *es; + +      cent = &cg_entities[ cg.snap->entities[ num ].number ]; +      es = ¢->currentState; + +      switch( es->eType ) +      { +        case ET_BUILDABLE: +        case ET_MISSILE: +        case ET_CORPSE: +          x = ( es->solid & 255 ); +          zd = ( ( es->solid >> 8 ) & 255 ); +          zu = ( ( es->solid >> 16 ) & 255 ) - 32; + +          mins[ 0 ] = mins[ 1 ] = -x; +          maxs[ 0 ] = maxs[ 1 ] = x; +          mins[ 2 ] = -zd; +          maxs[ 2 ] = zu; + +          CG_DrawBoundingBox( cent->lerpOrigin, mins, maxs ); +          break; + +        default: +          break; +      } +    } +  } +} + diff --git a/src/cgame/cg_event.c b/src/cgame/cg_event.c new file mode 100644 index 0000000..07bcea9 --- /dev/null +++ b/src/cgame/cg_event.c @@ -0,0 +1,1034 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +// cg_event.c -- handle entity events at snapshot or playerstate transitions + + +#include "cg_local.h" + +/* +============= +CG_Obituary +============= +*/ +static void CG_Obituary( entityState_t *ent ) +{ +  int           mod; +  int           target, attacker; +  char          *message; +  char          *message2; +  const char    *targetInfo; +  const char    *attackerInfo; +  char          targetName[ 32 ]; +  char          attackerName[ 32 ]; +  char          className[ 64 ]; +  gender_t      gender; +  clientInfo_t  *ci; +  qboolean      teamKill = qfalse; + +  target = ent->otherEntityNum; +  attacker = ent->otherEntityNum2; +  mod = ent->eventParm; + +  if( target < 0 || target >= MAX_CLIENTS ) +    CG_Error( "CG_Obituary: target out of range" ); + +  ci = &cgs.clientinfo[ target ]; + +  if( attacker < 0 || attacker >= MAX_CLIENTS ) +  { +    attacker = ENTITYNUM_WORLD; +    attackerInfo = NULL; +  } +  else +  { +    attackerInfo = CG_ConfigString( CS_PLAYERS + attacker ); +    if( ci && cgs.clientinfo[ attacker ].team == ci->team ) +      teamKill = qtrue; +  } + +  targetInfo = CG_ConfigString( CS_PLAYERS + target ); + +  if( !targetInfo ) +    return; + +  Q_strncpyz( targetName, Info_ValueForKey( targetInfo, "n" ), sizeof( targetName ) - 2 ); +  strcat( targetName, S_COLOR_WHITE ); + +  message2 = ""; + +  // check for single client messages + +  switch( mod ) +  { +    case MOD_SUICIDE: +      message = "suicides"; +      break; +    case MOD_FALLING: +      message = "fell fowl to gravity"; +      break; +    case MOD_CRUSH: +      message = "was squished"; +      break; +    case MOD_WATER: +      message = "forgot to pack a snorkel"; +      break; +    case MOD_SLIME: +      message = "melted"; +      break; +    case MOD_LAVA: +      message = "does a back flip into the lava"; +      break; +    case MOD_TARGET_LASER: +      message = "saw the light"; +      break; +    case MOD_TRIGGER_HURT: +      message = "was in the wrong place"; +      break; +    case MOD_HSPAWN: +      message = "should have run further"; +      break; +    case MOD_ASPAWN: +      message = "shouldn't have trod in the acid"; +      break; +    case MOD_MGTURRET: +      message = "was gunned down by a turret"; +      break; +    case MOD_TESLAGEN: +      message = "was zapped by a tesla generator"; +      break; +    case MOD_ATUBE: +      message = "was melted by an acid tube"; +      break; +    case MOD_OVERMIND: +      message = "got too close to the overmind"; +      break; +    case MOD_REACTOR: +      message = "got too close to the reactor"; +      break; +    case MOD_SLOWBLOB: +      message = "should have visited a medical station"; +      break; +    case MOD_SWARM: +      message = "was hunted down by the swarm"; +      break; +    default: +      message = NULL; +      break; +  } + +  if( attacker == target ) +  { +    gender = ci->gender; +    switch( mod ) +    { +      case MOD_FLAMER_SPLASH: +        if( gender == GENDER_FEMALE ) +          message = "toasted herself"; +        else if( gender == GENDER_NEUTER ) +          message = "toasted itself"; +        else +          message = "toasted himself"; +        break; + +      case MOD_LCANNON_SPLASH: +        if( gender == GENDER_FEMALE ) +          message = "irradiated herself"; +        else if( gender == GENDER_NEUTER ) +          message = "irradiated itself"; +        else +          message = "irradiated himself"; +        break; + +      case MOD_GRENADE: +        if( gender == GENDER_FEMALE ) +          message = "blew herself up"; +        else if( gender == GENDER_NEUTER ) +          message = "blew itself up"; +        else +          message = "blew himself up"; +        break; + +      default: +        if( gender == GENDER_FEMALE ) +          message = "killed herself"; +        else if( gender == GENDER_NEUTER ) +          message = "killed itself"; +        else +          message = "killed himself"; +        break; +    } +  } + +  if( message ) +  { +    CG_Printf( "%s %s.\n", targetName, message ); +    return; +  } + +  // check for double client messages +  if( !attackerInfo ) +  { +    attacker = ENTITYNUM_WORLD; +    strcpy( attackerName, "noname" ); +  } +  else +  { +    Q_strncpyz( attackerName, Info_ValueForKey( attackerInfo, "n" ), sizeof( attackerName ) - 2); +    strcat( attackerName, S_COLOR_WHITE ); +    // check for kill messages about the current clientNum +    if( target == cg.snap->ps.clientNum ) +      Q_strncpyz( cg.killerName, attackerName, sizeof( cg.killerName ) ); +  } + +  if( attacker != ENTITYNUM_WORLD ) +  { +    switch( mod ) +    { +      case MOD_PAINSAW: +        message = "was sawn by"; +        break; +      case MOD_BLASTER: +        message = "was blasted by"; +        break; +      case MOD_MACHINEGUN: +        message = "was machinegunned by"; +        break; +      case MOD_CHAINGUN: +        message = "was chaingunned by"; +        break; +      case MOD_SHOTGUN: +        message = "was gunned down by"; +        break; +      case MOD_PRIFLE: +        message = "was pulse rifled by"; +        break; +      case MOD_MDRIVER: +        message = "was mass driven by"; +        break; +      case MOD_LASGUN: +        message = "was lasgunned by"; +        break; +      case MOD_FLAMER: +        message = "was grilled by"; +        message2 = "'s flamer"; +        break; +      case MOD_FLAMER_SPLASH: +        message = "was toasted by"; +        message2 = "'s flamer"; +        break; +      case MOD_LCANNON: +        message = "felt the full force of"; +        message2 = "'s lucifer cannon"; +        break; +      case MOD_LCANNON_SPLASH: +        message = "was caught in the fallout of"; +        message2 = "'s lucifer cannon"; +        break; +      case MOD_GRENADE: +        message = "couldn't escape"; +        message2 = "'s grenade"; +        break; + +      case MOD_ABUILDER_CLAW: +        message = "should leave"; +        message2 = "'s buildings alone"; +        break; +      case MOD_LEVEL0_BITE: +        message = "was bitten by"; +        break; +      case MOD_LEVEL1_CLAW: +        message = "was swiped by"; +        Com_sprintf( className, 64, "'s %s", +            BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL1 ) ); +        message2 = className; +        break; +      case MOD_LEVEL2_CLAW: +        message = "was clawed by"; +        Com_sprintf( className, 64, "'s %s", +            BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL2 ) ); +        message2 = className; +        break; +      case MOD_LEVEL2_ZAP: +        message = "was zapped by"; +        Com_sprintf( className, 64, "'s %s", +            BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL2 ) ); +        message2 = className; +        break; +      case MOD_LEVEL3_CLAW: +        message = "was chomped by"; +        Com_sprintf( className, 64, "'s %s", +            BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL3 ) ); +        message2 = className; +        break; +      case MOD_LEVEL3_POUNCE: +        message = "was pounced upon by"; +        Com_sprintf( className, 64, "'s %s", +            BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL3 ) ); +        message2 = className; +        break; +      case MOD_LEVEL3_BOUNCEBALL: +        message = "was sniped by"; +        Com_sprintf( className, 64, "'s %s", +            BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL3 ) ); +        message2 = className; +        break; +      case MOD_LEVEL4_CLAW: +        message = "was mauled by"; +        Com_sprintf( className, 64, "'s %s", +            BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL4 ) ); +        message2 = className; +        break; +      case MOD_LEVEL4_CHARGE: +        message = "should have gotten out of the way of"; +        Com_sprintf( className, 64, "'s %s", +            BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL4 ) ); +        message2 = className; +        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; + + +      case MOD_TELEFRAG: +        message = "tried to invade"; +        message2 = "'s personal space"; +        break; +      default: +        message = "was killed by"; +        break; +    } + +    if( message ) +    { +      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 ) +      { +        CG_CenterPrint( va ( "You killed " S_COLOR_RED "TEAMMATE " +          S_COLOR_WHITE "%s", targetName ), +          SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); +      } +      return; +    } +  } + +  // we don't know what it was +  CG_Printf( "%s died.\n", targetName ); +} + +//========================================================================== + +/* +================ +CG_PainEvent + +Also called by playerstate transition +================ +*/ +void CG_PainEvent( centity_t *cent, int health ) +{ +  char  *snd; + +  // don't do more than two pain sounds a second +  if( cg.time - cent->pe.painTime < 500 ) +    return; + +  if( health < 25 ) +    snd = "*pain25_1.wav"; +  else if( health < 50 ) +    snd = "*pain50_1.wav"; +  else if( health < 75 ) +    snd = "*pain75_1.wav"; +  else +    snd = "*pain100_1.wav"; + +  trap_S_StartSound( NULL, cent->currentState.number, CHAN_VOICE, +    CG_CustomSound( cent->currentState.number, snd ) ); + +  // save pain time for programitic twitch animation +  cent->pe.painTime = cg.time; +  cent->pe.painDirection ^= 1; +} + +/* +============== +CG_EntityEvent + +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; +  int           event; +  vec3_t        dir; +  const char    *s; +  int           clientNum; +  clientInfo_t  *ci; +  int           steptime; + +  if( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) +    steptime = 200; +  else +    steptime = BG_FindSteptimeForClass( cg.snap->ps.stats[ STAT_PCLASS ] ); + +  es = ¢->currentState; +  event = es->event & ~EV_EVENT_BITS; + +  if( cg_debugEvents.integer ) +    CG_Printf( "ent:%3i  event:%3i ", es->number, event ); + +  if( !event ) +  { +    DEBUGNAME("ZEROEVENT"); +    return; +  } + +  clientNum = es->clientNum; +  if( clientNum < 0 || clientNum >= MAX_CLIENTS ) +    clientNum = 0; + +  ci = &cgs.clientinfo[ clientNum ]; + +  switch( event ) +  { +    // +    // movement generated events +    // +    case EV_FOOTSTEP: +      DEBUGNAME( "EV_FOOTSTEP" ); +      if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) +      { +        if( ci->footsteps == FOOTSTEP_CUSTOM ) +          trap_S_StartSound( NULL, es->number, CHAN_BODY, +            ci->customFootsteps[ rand( ) & 3 ] ); +        else +          trap_S_StartSound( NULL, es->number, CHAN_BODY, +            cgs.media.footsteps[ ci->footsteps ][ rand( ) & 3 ] ); +      } +      break; + +    case EV_FOOTSTEP_METAL: +      DEBUGNAME( "EV_FOOTSTEP_METAL" ); +      if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) +      { +        if( ci->footsteps == FOOTSTEP_CUSTOM ) +          trap_S_StartSound( NULL, es->number, CHAN_BODY, +            ci->customMetalFootsteps[ rand( ) & 3 ] ); +        else +          trap_S_StartSound( NULL, es->number, CHAN_BODY, +            cgs.media.footsteps[ FOOTSTEP_METAL ][ rand( ) & 3 ] ); +      } +      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, +          cgs.media.footsteps[ FOOTSTEP_FLESH ][ rand( ) & 3 ] ); +      } +      break; + +    case EV_FOOTSPLASH: +      DEBUGNAME( "EV_FOOTSPLASH" ); +      if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) +      { +        trap_S_StartSound( NULL, es->number, CHAN_BODY, +          cgs.media.footsteps[ FOOTSTEP_SPLASH ][ rand( ) & 3 ] ); +      } +      break; + +    case EV_FOOTWADE: +      DEBUGNAME( "EV_FOOTWADE" ); +      if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) +      { +        trap_S_StartSound( NULL, es->number, CHAN_BODY, +          cgs.media.footsteps[ FOOTSTEP_SPLASH ][ rand( ) & 3 ] ); +      } +      break; + +    case EV_SWIM: +      DEBUGNAME( "EV_SWIM" ); +      if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) +      { +        trap_S_StartSound( NULL, es->number, CHAN_BODY, +          cgs.media.footsteps[ FOOTSTEP_SPLASH ][ rand( ) & 3 ] ); +      } +      break; + + +    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.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.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" ) ); +      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.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; + +    case EV_STEP_4: +    case EV_STEP_8: +    case EV_STEP_12: +    case EV_STEP_16:    // smooth out step up transitions +    case EV_STEPDN_4: +    case EV_STEPDN_8: +    case EV_STEPDN_12: +    case EV_STEPDN_16:    // smooth out step down transitions +      DEBUGNAME( "EV_STEP" ); +      { +        float  oldStep; +        int    delta; +        int    step; + +        if( clientNum != cg.predictedPlayerState.clientNum ) +          break; + +        // if we are interpolating, we don't need to smooth steps +        if( cg.demoPlayback || ( cg.snap->ps.pm_flags & PMF_FOLLOW ) || +            cg_nopredict.integer || cg_synchronousClients.integer ) +          break; + +        // check for stepping up before a previous step is completed +        delta = cg.time - cg.stepTime; + +        if( delta < steptime ) +          oldStep = cg.stepChange * ( steptime - delta ) / steptime; +        else +          oldStep = 0; + +        // add this amount +        if( event >= EV_STEPDN_4 ) +        { +          step = 4 * ( event - EV_STEPDN_4 + 1 ); +          cg.stepChange = oldStep - step; +        } +        else +        { +          step = 4 * ( event - EV_STEP_4 + 1 ); +          cg.stepChange = oldStep + step; +        } + +        if( cg.stepChange > MAX_STEP_CHANGE ) +          cg.stepChange = MAX_STEP_CHANGE; +        else if( cg.stepChange < -MAX_STEP_CHANGE ) +          cg.stepChange = -MAX_STEP_CHANGE; + +        cg.stepTime = cg.time; +        break; +      } + +    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 ) ) +      { +        vec3_t  surfNormal, refNormal = { 0.0f, 0.0f, 1.0f }; +        vec3_t  rotAxis; + +        if( clientNum != cg.predictedPlayerState.clientNum ) +          break; + +        //set surfNormal +        VectorCopy( cg.predictedPlayerState.grapplePoint, surfNormal ); + +        //if we are moving from one surface to another smooth the transition +        if( !VectorCompare( surfNormal, cg.lastNormal ) && surfNormal[ 2 ] != 1.0f ) +        { +          CrossProduct( refNormal, surfNormal, rotAxis ); +          VectorNormalize( rotAxis ); + +          //add the op +          CG_addSmoothOp( rotAxis, 15.0f, 1.0f ); +        } + +        //copy the current normal to the lastNormal +        VectorCopy( surfNormal, cg.lastNormal ); +      } + +      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" ); +      trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.alienL4ChargePrepare ); +      break; + +    case EV_LEV4_CHARGE_START: +      DEBUGNAME( "EV_LEV4_CHARGE_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; + +    // +    // weapon events +    // +    case EV_NOAMMO: +      DEBUGNAME( "EV_NOAMMO" ); +      { +      } +      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; + +    //================================================================= + +    // +    // 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 +        trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.hardBounceSound2 ); +      break; + +    // +    // missile impacts +    // +    case EV_MISSILE_HIT: +      DEBUGNAME( "EV_MISSILE_HIT" ); +      ByteToDir( es->eventParm, dir ); +      CG_MissileHitPlayer( es->weapon, es->generic1, position, dir, es->otherEntityNum ); +      break; + +    case EV_MISSILE_MISS: +      DEBUGNAME( "EV_MISSILE_MISS" ); +      ByteToDir( es->eventParm, dir ); +      CG_MissileHitWall( es->weapon, es->generic1, 0, position, dir, IMPACTSOUND_DEFAULT ); +      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 ); +      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 *target = &cg_entities[ es->clientNum ]; +        vec3_t    sourceOffset = { 0.0f, 0.0f, 28.0f }; + +        if( !CG_IsTrailSystemValid( &source->muzzleTS ) ) +        { +          source->muzzleTS = CG_SpawnNewTrailSystem( cgs.media.teslaZapTS ); + +          if( CG_IsTrailSystemValid( &source->muzzleTS ) ) +          { +            CG_SetAttachmentCent( &source->muzzleTS->frontAttachment, source ); +            CG_SetAttachmentCent( &source->muzzleTS->backAttachment, target ); +            CG_AttachToCent( &source->muzzleTS->frontAttachment ); +            CG_AttachToCent( &source->muzzleTS->backAttachment ); +            CG_SetAttachmentOffset( &source->muzzleTS->frontAttachment, sourceOffset ); + +            source->muzzleTSDeathTime = cg.time + cg_teslaTrailTime.integer; +          } +        } +      } +      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 +      { +        s = CG_ConfigString( CS_SOUNDS + es->eventParm ); +        trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, s ) ); +      } +      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 +      { +        s = CG_ConfigString( CS_SOUNDS + es->eventParm ); +        trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, CG_CustomSound( es->number, s ) ); +      } +      break; + +    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; + +    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 ); +        cg.lastBuildAttempt = cg.time; +      } +      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 ) +      { +        trap_S_StartLocalSound( cgs.media.alienOvermindAttack, CHAN_ANNOUNCER ); +        CG_CenterPrint( "The Overmind is under attack!", 200, GIANTCHAR_WIDTH * 4 ); +      } +      break; + +    case EV_OVERMIND_DYING: +      DEBUGNAME( "EV_OVERMIND_DYING" ); +      if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS ) +      { +        trap_S_StartLocalSound( cgs.media.alienOvermindDying, CHAN_ANNOUNCER ); +        CG_CenterPrint( "The Overmind is dying!", 200, GIANTCHAR_WIDTH * 4 ); +      } +      break; + +    case EV_DCC_ATTACK: +      DEBUGNAME( "EV_DCC_ATTACK" ); +      if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_HUMANS ) +      { +        //trap_S_StartLocalSound( cgs.media.humanDCCAttack, CHAN_ANNOUNCER ); +        CG_CenterPrint( "Our base is under attack!", 200, GIANTCHAR_WIDTH * 4 ); +      } +      break; + +    case EV_OVERMIND_SPAWNS: +      DEBUGNAME( "EV_OVERMIND_SPAWNS" ); +      if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS ) +      { +        trap_S_StartLocalSound( cgs.media.alienOvermindSpawns, CHAN_ANNOUNCER ); +        CG_CenterPrint( "The Overmind needs spawns!", 200, GIANTCHAR_WIDTH * 4 ); +      } +      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 ); + +        if( CG_IsParticleSystemValid( &ps ) ) +        { +          CG_SetAttachmentCent( &ps->attachment, cent ); +          CG_AttachToCent( &ps->attachment ); +        } +      } + +      if( es->number == cg.clientNum ) +      { +        CG_ResetPainBlend( ); +        cg.spawnTime = cg.time; +      } +      break; + +    case EV_ALIEN_EVOLVE_FAILED: +      DEBUGNAME( "EV_ALIEN_EVOLVE_FAILED" ); +      if( clientNum == cg.predictedPlayerState.clientNum ) +      { +        //FIXME: change to "negative" sound +        trap_S_StartLocalSound( cgs.media.buildableRepairedSound, CHAN_LOCAL_SOUND ); +        cg.lastEvolveAttempt = cg.time; +      } +      break; + +    case EV_ALIEN_ACIDTUBE: +      DEBUGNAME( "EV_ALIEN_ACIDTUBE" ); +      { +        particleSystem_t *ps = CG_SpawnNewParticleSystem( cgs.media.alienAcidTubePS ); + +        if( CG_IsParticleSystemValid( &ps ) ) +        { +          CG_SetAttachmentCent( &ps->attachment, cent ); +          ByteToDir( es->eventParm, dir ); +          CG_SetParticleSystemNormal( ps, dir ); +          CG_AttachToCent( &ps->attachment ); +        } +      } +      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; + +    default: +      DEBUGNAME( "UNKNOWN" ); +      CG_Error( "Unknown event: %i", event ); +      break; +  } +} + + +/* +============== +CG_CheckEvents + +============== +*/ +void CG_CheckEvents( centity_t *cent ) +{ +  entity_event_t event; +  entity_event_t oldEvent = EV_NONE; + +  // check for event-only entities +  if( cent->currentState.eType > ET_EVENTS ) +  { +    event = cent->currentState.eType - ET_EVENTS; + +    if( cent->previousEvent ) +      return; // already fired + +    cent->previousEvent = 1; + +    cent->currentState.event = cent->currentState.eType - ET_EVENTS; +     +    // Move the pointer to the entity that the +    // event was originally attached to +    if( cent->currentState.eFlags & EF_PLAYER_EVENT ) +    { +      cent = &cg_entities[ cent->currentState.otherEntityNum ]; +      oldEvent = cent->currentState.event; +      cent->currentState.event = event; +    } +  } +  else +  { +    // check for events riding with another entity +    if( cent->currentState.event == cent->previousEvent ) +      return; + +    cent->previousEvent = cent->currentState.event; +    if( ( cent->currentState.event & ~EV_EVENT_BITS ) == 0 ) +      return; +  } + +  // calculate the position at exactly the frame time +  BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, cent->lerpOrigin ); +  CG_SetEntitySoundPosition( cent ); + +  CG_EntityEvent( cent, cent->lerpOrigin ); +   +  // If this was a reattached spilled event, restore the original event +  if( oldEvent != EV_NONE ) +    cent->currentState.event = oldEvent; +} + diff --git a/src/cgame/cg_local.h b/src/cgame/cg_local.h new file mode 100644 index 0000000..74b7a4d --- /dev/null +++ b/src/cgame/cg_local.h @@ -0,0 +1,2072 @@ +/* +=========================================================================== +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 "../qcommon/q_shared.h" +#include "../renderer/tr_types.h" +#include "../game/bg_public.h" +#include "cg_public.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. +// If you absolutely need something stored, it can either be kept +// by the server in the server stored userinfos, or stashed in a cvar. + +#define CG_FONT_THRESHOLD 0.1 + +#define POWERUP_BLINKS      5 + +#define POWERUP_BLINK_TIME  1000 +#define FADE_TIME           200 +#define PULSE_TIME          200 +#define DAMAGE_DEFLECT_TIME 100 +#define DAMAGE_RETURN_TIME  400 +#define DAMAGE_TIME         500 +#define LAND_DEFLECT_TIME   150 +#define LAND_RETURN_TIME    300 +#define DUCK_TIME           100 +#define PAIN_TWITCH_TIME    200 +#define WEAPON_SELECT_TIME  1400 +#define ITEM_SCALEUP_TIME   1000 +#define ZOOM_TIME           150 +#define ITEM_BLOB_TIME      200 +#define MUZZLE_FLASH_TIME   20 +#define SINK_TIME           1000    // time for fragments to sink into ground before going away +#define ATTACKER_HEAD_TIME  10000 +#define REWARD_TIME         3000 + +#define PULSE_SCALE         1.5     // amount to scale up the icons when activating + +#define MAX_STEP_CHANGE     32 + +#define MAX_VERTS_ON_POLY   10 +#define MAX_MARK_POLYS      256 + +#define STAT_MINUS          10  // num frame for '-' stats digit + +#define ICON_SIZE           48 +#define CHAR_WIDTH          32 +#define CHAR_HEIGHT         48 +#define TEXT_ICON_SPACE     4 + +// very large characters +#define GIANT_WIDTH         32 +#define GIANT_HEIGHT        48 + +#define NUM_CROSSHAIRS      10 + +#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, +  FOOTSTEP_FLESH, +  FOOTSTEP_METAL, +  FOOTSTEP_SPLASH, +  FOOTSTEP_CUSTOM, +  FOOTSTEP_NONE, + +  FOOTSTEP_TOTAL +} footstep_t; + +typedef enum +{ +  IMPACTSOUND_DEFAULT, +  IMPACTSOUND_METAL, +  IMPACTSOUND_FLESH +} impactSound_t; + +typedef enum +{ +  JPS_OFF, +  JPS_DESCENDING, +  JPS_HOVERING, +  JPS_ASCENDING +} jetPackState_t; + +//====================================================================== + +// when changing animation, set animationTime to frameTime + lerping time +// The current lerp will finish out, then it will lerp to the new animation +typedef struct +{ +  int         oldFrame; +  int         oldFrameTime;     // time when ->oldFrame was exactly on + +  int         frame; +  int         frameTime;        // time when ->frame will be exactly on + +  float       backlerp; + +  float       yawAngle; +  qboolean    yawing; +  float       pitchAngle; +  qboolean    pitching; + +  int         animationNumber;  // may include ANIM_TOGGLEBIT +  animation_t *animation; +  int         animationTime;    // time when the first frame of the animation will be exact +} lerpFrame_t; + +//====================================================================== + +//attachment system +typedef enum +{ +  AT_STATIC, +  AT_TAG, +  AT_CENT, +  AT_PARTICLE +} attachmentType_t; + +//forward declaration for particle_t +struct particle_s; + +typedef struct attachment_s +{ +  attachmentType_t  type; +  qboolean          attached; + +  qboolean          staticValid; +  qboolean          tagValid; +  qboolean          centValid; +  qboolean          particleValid; + +  qboolean          hasOffset; +  vec3_t            offset; + +  vec3_t            lastValidAttachmentPoint; + +  //AT_STATIC +  vec3_t            origin; + +  //AT_TAG +  refEntity_t       re;     //FIXME: should be pointers? +  refEntity_t       parent; // +  qhandle_t         model; +  char              tagName[ MAX_STRING_CHARS ]; + +  //AT_CENT +  int               centNum; + +  //AT_PARTICLE +  struct particle_s *particle; +} attachment_t; + +//====================================================================== + +//particle system stuff +#define MAX_PARTICLE_FILES        128 + +#define MAX_PS_SHADER_FRAMES      32 +#define MAX_PS_MODELS             8 +#define MAX_EJECTORS_PER_SYSTEM   4 +#define MAX_PARTICLES_PER_EJECTOR 4 + +#define MAX_BASEPARTICLE_SYSTEMS  192 +#define MAX_BASEPARTICLE_EJECTORS MAX_BASEPARTICLE_SYSTEMS*MAX_EJECTORS_PER_SYSTEM +#define MAX_BASEPARTICLES         MAX_BASEPARTICLE_EJECTORS*MAX_PARTICLES_PER_EJECTOR + +#define MAX_PARTICLE_SYSTEMS      48 +#define MAX_PARTICLE_EJECTORS     MAX_PARTICLE_SYSTEMS*MAX_EJECTORS_PER_SYSTEM +#define MAX_PARTICLES             MAX_PARTICLE_EJECTORS*5 + +#define PARTICLES_INFINITE        -1 +#define PARTICLES_SAME_AS_INITIAL -2 + +//COMPILE TIME STRUCTURES +typedef enum +{ +  PMT_STATIC, +  PMT_STATIC_TRANSFORM, +  PMT_TAG, +  PMT_CENT_ANGLES, +  PMT_NORMAL +} pMoveType_t; + +typedef enum +{ +  PMD_LINEAR, +  PMD_POINT +} pDirType_t; + +typedef struct pMoveValues_u +{ +  pDirType_t  dirType; + +  //PMD_LINEAR +  vec3_t      dir; +  float       dirRandAngle; + +  //PMD_POINT +  vec3_t      point; +  float       pointRandAngle; + +  float       mag; +  float       magRandFrac; + +  float       parentVelFrac; +  float       parentVelFracRandFrac; +} pMoveValues_t; + +typedef struct pLerpValues_s +{ +  int   delay; +  float delayRandFrac; + +  float initial; +  float initialRandFrac; + +  float final; +  float finalRandFrac; + +  float randFrac; +} pLerpValues_t; + +//particle template +typedef struct baseParticle_s +{ +  vec3_t          displacement; +  float           randDisplacement; +  float           normalDisplacement; + +  pMoveType_t     velMoveType; +  pMoveValues_t   velMoveValues; + +  pMoveType_t     accMoveType; +  pMoveValues_t   accMoveValues; + +  int             lifeTime; +  float           lifeTimeRandFrac; + +  float           bounceFrac; +  float           bounceFracRandFrac; +  qboolean        bounceCull; + +  char            bounceMarkName[ MAX_QPATH ]; +  qhandle_t       bounceMark; +  float           bounceMarkRadius; +  float           bounceMarkRadiusRandFrac; +  float           bounceMarkCount; +  float           bounceMarkCountRandFrac; + +  char            bounceSoundName[ MAX_QPATH ]; +  qhandle_t       bounceSound; +  float           bounceSoundCount; +  float           bounceSoundCountRandFrac; + +  pLerpValues_t   radius; +  pLerpValues_t   alpha; +  pLerpValues_t   rotation; + +  qboolean        dynamicLight; +  pLerpValues_t   dLightRadius; +  byte            dLightColor[ 3 ]; + +  int             colorDelay; +  float           colorDelayRandFrac; +  byte            initialColor[ 3 ]; +  byte            finalColor[ 3 ]; + +  char            childSystemName[ MAX_QPATH ]; +  qhandle_t       childSystemHandle; + +  char            onDeathSystemName[ MAX_QPATH ]; +  qhandle_t       onDeathSystemHandle; + +  char            childTrailSystemName[ MAX_QPATH ]; +  qhandle_t       childTrailSystemHandle; + +  //particle invariant stuff +  char            shaderNames[ MAX_PS_SHADER_FRAMES ][ MAX_QPATH ]; +  qhandle_t       shaders[ MAX_PS_SHADER_FRAMES ]; +  int             numFrames; +  float           framerate; + +  char            modelNames[ MAX_PS_MODELS ][ MAX_QPATH ]; +  qhandle_t       models[ MAX_PS_MODELS ]; +  int             numModels; +  animation_t     modelAnimation; + +  qboolean        overdrawProtection; +  qboolean        realLight; +  qboolean        cullOnStartSolid; +} baseParticle_t; + + +//ejector template +typedef struct baseParticleEjector_s +{ +  baseParticle_t  *particles[ MAX_PARTICLES_PER_EJECTOR ]; +  int             numParticles; + +  pLerpValues_t   eject;          //zero period indicates creation of all particles at once + +  int             totalParticles;         //can be infinite +  float           totalParticlesRandFrac; +} baseParticleEjector_t; + + +//particle system template +typedef struct baseParticleSystem_s +{ +  char                  name[ MAX_QPATH ]; +  baseParticleEjector_t *ejectors[ MAX_EJECTORS_PER_SYSTEM ]; +  int                   numEjectors; + +  qboolean              thirdPersonOnly; +  qboolean              registered; //whether or not the assets for this particle have been loaded +} baseParticleSystem_t; + + +//RUN TIME STRUCTURES +typedef struct particleSystem_s +{ +  baseParticleSystem_t  *class; + +  attachment_t          attachment; + +  qboolean              valid; +  qboolean              lazyRemove; //mark this system for later removal + +  //for PMT_NORMAL +  qboolean              normalValid; +  vec3_t                normal; +} particleSystem_t; + + +typedef struct particleEjector_s +{ +  baseParticleEjector_t *class; +  particleSystem_t      *parent; + +  pLerpValues_t         ejectPeriod; + +  int                   count; +  int                   totalParticles; + +  int                   nextEjectionTime; + +  qboolean              valid; +} particleEjector_t; + + +//used for actual particle evaluation +typedef struct particle_s +{ +  baseParticle_t    *class; +  particleEjector_t *parent; + +  int               birthTime; +  int               lifeTime; + +  float             bounceMarkRadius; +  int               bounceMarkCount; +  int               bounceSoundCount; +  qboolean          atRest; + +  vec3_t            origin; +  vec3_t            velocity; + +  pMoveType_t       accMoveType; +  pMoveValues_t     accMoveValues; + +  int               lastEvalTime; + +  int               nextChildTime; + +  pLerpValues_t     radius; +  pLerpValues_t     alpha; +  pLerpValues_t     rotation; + +  pLerpValues_t     dLightRadius; + +  int               colorDelay; + +  qhandle_t         model; +  lerpFrame_t       lf; +  vec3_t            lastAxis[ 3 ]; + +  qboolean          valid; +  int               frameWhenInvalidated; + +  int               sortKey; +} particle_t; + +//====================================================================== + +//trail system stuff +#define MAX_TRAIL_FILES           128 + +#define MAX_BEAMS_PER_SYSTEM      4 + +#define MAX_BASETRAIL_SYSTEMS     64 +#define MAX_BASETRAIL_BEAMS       MAX_BASETRAIL_SYSTEMS*MAX_BEAMS_PER_SYSTEM + +#define MAX_TRAIL_SYSTEMS         32 +#define MAX_TRAIL_BEAMS           MAX_TRAIL_SYSTEMS*MAX_BEAMS_PER_SYSTEM +#define MAX_TRAIL_BEAM_NODES      128 + +#define MAX_TRAIL_BEAM_JITTERS    4 + +typedef enum +{ +  TBTT_STRETCH, +  TBTT_REPEAT +} trailBeamTextureType_t; + +typedef struct baseTrailJitter_s +{ +  float   magnitude; +  int     period; +} baseTrailJitter_t; + +//beam template +typedef struct baseTrailBeam_s +{ +  int                     numSegments; +  float                   frontWidth; +  float                   backWidth; +  float                   frontAlpha; +  float                   backAlpha; +  byte                    frontColor[ 3 ]; +  byte                    backColor[ 3 ]; + +  // the time it takes for a segment to vanish (single attached only) +  int                     segmentTime; + +  // the time it takes for a beam to fade out (double attached only) +  int                     fadeOutTime; + +  char                    shaderName[ MAX_QPATH ]; +  qhandle_t               shader; + +  trailBeamTextureType_t  textureType; + +  //TBTT_STRETCH +  float                   frontTextureCoord; +  float                   backTextureCoord; + +  //TBTT_REPEAT +  float                   repeatLength; +  qboolean                clampToBack; + +  qboolean                realLight; + +  int                     numJitters; +  baseTrailJitter_t       jitters[ MAX_TRAIL_BEAM_JITTERS ]; +  qboolean                jitterAttachments; +} baseTrailBeam_t; + + +//trail system template +typedef struct baseTrailSystem_s +{ +  char            name[ MAX_QPATH ]; +  baseTrailBeam_t *beams[ MAX_BEAMS_PER_SYSTEM ]; +  int             numBeams; + +  qboolean        thirdPersonOnly; +  qboolean        registered; //whether or not the assets for this trail have been loaded +} baseTrailSystem_t; + +typedef struct trailSystem_s +{ +  baseTrailSystem_t   *class; + +  attachment_t        frontAttachment; +  attachment_t        backAttachment; + +  int                 destroyTime; +  qboolean            valid; +} trailSystem_t; + +typedef struct trailBeamNode_s +{ +  vec3_t                  refPosition; +  vec3_t                  position; + +  int                     timeLeft; + +  float                   textureCoord; +  float                   halfWidth; +  byte                    alpha; +  byte                    color[ 3 ]; + +  vec2_t                  jitters[ MAX_TRAIL_BEAM_JITTERS ]; + +  struct trailBeamNode_s  *prev; +  struct trailBeamNode_s  *next; + +  qboolean                used; +} trailBeamNode_t; + +typedef struct trailBeam_s +{ +  baseTrailBeam_t   *class; +  trailSystem_t     *parent; + +  trailBeamNode_t   nodePool[ MAX_TRAIL_BEAM_NODES ]; +  trailBeamNode_t   *nodes; + +  int               lastEvalTime; + +  qboolean          valid; + +  int               nextJitterTimes[ MAX_TRAIL_BEAM_JITTERS ]; +} trailBeam_t; + +//====================================================================== + +// player entities need to track more information +// than any other type of entity. + +// note that not every player entity is a client entity, +// because corpses after respawn are outside the normal +// client numbering range + +//TA: smoothing of view and model for WW transitions +#define   MAXSMOOTHS          32 + +typedef struct +{ +  float     time; +  float     timeMod; + +  vec3_t    rotAxis; +  float     rotAngle; +} smooth_t; + + +typedef struct +{ +  lerpFrame_t legs, torso, flag, nonseg; +  int         painTime; +  int         painDirection;  // flip from 0 to 1 + +  // machinegun spinning +  float       barrelAngle; +  int         barrelTime; +  qboolean    barrelSpinning; + +  vec3_t      lastNormal; +  vec3_t      lastAxis[ 3 ]; +  smooth_t    sList[ MAXSMOOTHS ]; +} playerEntity_t; + +typedef struct lightFlareStatus_s +{ +  float     lastRadius;    //caching of likely flare radius +  float     lastRatio;     //caching of likely flare ratio +  int       lastTime;      //last time flare was visible/occluded +  qboolean  status;        //flare is visble? +} lightFlareStatus_t; + +typedef struct buildableStatus_s +{ +  int       lastTime;      // Last time status was visible +  qboolean  visible;       // Status is visble? +} buildableStatus_t; + +//================================================= + +// centity_t have a direct corespondence with gentity_t in the game, but +// only the entityState_t is directly communicated to the cgame +typedef struct centity_s +{ +  entityState_t         currentState;     // from cg.frame +  entityState_t         nextState;        // from cg.nextFrame, if available +  qboolean              interpolate;      // true if next is valid to interpolate to +  qboolean              currentValid;     // true if cg.frame holds this entity + +  int                   muzzleFlashTime;  // move to playerEntity? +  int                   muzzleFlashTime2; // move to playerEntity? +  int                   muzzleFlashTime3; // move to playerEntity? +  int                   previousEvent; +  int                   teleportFlag; + +  int                   trailTime;        // so missile trails can handle dropped initial packets +  int                   dustTrailTime; +  int                   miscTime; +  int                   snapShotTime;     // last time this entity was found in a snapshot + +  playerEntity_t        pe; + +  int                   errorTime;        // decay the error from this time +  vec3_t                errorOrigin; +  vec3_t                errorAngles; + +  qboolean              extrapolated;     // false if origin / angles is an interpolation +  vec3_t                rawOrigin; +  vec3_t                rawAngles; + +  vec3_t                beamEnd; + +  // exact interpolated position of entity on this frame +  vec3_t                lerpOrigin; +  vec3_t                lerpAngles; + +  lerpFrame_t           lerpFrame; + +  //TA: +  buildableAnimNumber_t buildableAnim;    //persistant anim number +  buildableAnimNumber_t oldBuildableAnim; //to detect when new anims are set +  particleSystem_t      *buildablePS; +  buildableStatus_t     buildableStatus; +  float                 lastBuildableHealthScale; +  int                   lastBuildableDamageSoundTime; + +  lightFlareStatus_t    lfs; + +  qboolean              doorState; + +  qboolean              animInit; +  qboolean              animPlaying; +  qboolean              animLastState; + +  particleSystem_t      *muzzlePS; +  qboolean              muzzlePsTrigger; + +  particleSystem_t      *jetPackPS; +  jetPackState_t        jetPackState; + +  particleSystem_t      *entityPS; +  qboolean              entityPSMissing; + +  trailSystem_t         *level2ZapTS[ 3 ]; + +  trailSystem_t         *muzzleTS; //used for the tesla and reactor +  int                   muzzleTSDeathTime; + +  qboolean              valid; +  qboolean              oldValid; +} centity_t; + + +//====================================================================== + +typedef struct markPoly_s +{ +  struct markPoly_s *prevMark, *nextMark; +  int               time; +  qhandle_t         markShader; +  qboolean          alphaFade;    // fade alpha instead of rgb +  float             color[ 4 ]; +  poly_t            poly; +  polyVert_t        verts[ MAX_VERTS_ON_POLY ]; +} markPoly_t; + +//====================================================================== + + +typedef struct +{ +  int       client; +  int       score; +  int       ping; +  int       time; +  int       team; +  weapon_t  weapon; +  upgrade_t upgrade; +} score_t; + +// each client has an associated clientInfo_t +// that contains media references necessary to present the +// client model and other color coded effects +// this is regenerated each time a client's configstring changes, +// usually as a result of a userinfo (name, model, etc) change +#define MAX_CUSTOM_SOUNDS 32 +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; + +  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; + +  // 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 +  qboolean    fixedtorso;                 // true if torso never changes yaw +  qboolean    nonsegmented;               // true if model is Q2 style nonsegmented + +  vec3_t      headOffset;                 // move head in icon views +  footstep_t  footsteps; +  gender_t    gender;                     // from model + +  qhandle_t   legsModel; +  qhandle_t   legsSkin; + +  qhandle_t   torsoModel; +  qhandle_t   torsoSkin; + +  qhandle_t   headModel; +  qhandle_t   headSkin; + +  qhandle_t   nonSegModel;                //non-segmented model system +  qhandle_t   nonSegSkin;                 //non-segmented model system + +  qhandle_t   modelIcon; + +  animation_t animations[ MAX_PLAYER_TOTALANIMATIONS ]; + +  sfxHandle_t sounds[ MAX_CUSTOM_SOUNDS ]; + +  sfxHandle_t customFootsteps[ 4 ]; +  sfxHandle_t customMetalFootsteps[ 4 ]; +} clientInfo_t; + + +typedef struct weaponInfoMode_s +{ +  float       flashDlight; +  vec3_t      flashDlightColor; +  sfxHandle_t flashSound[ 4 ];  // fast firing weapons randomly choose +  qboolean    continuousFlash; + +  qhandle_t   missileModel; +  sfxHandle_t missileSound; +  float       missileDlight; +  vec3_t      missileDlightColor; +  int         missileRenderfx; +  qboolean    usesSpriteMissle; +  qhandle_t   missileSprite; +  int         missileSpriteSize; +  qhandle_t   missileParticleSystem; +  qhandle_t   missileTrailSystem; +  qboolean    missileRotates; +  qboolean    missileAnimates; +  int         missileAnimStartFrame; +  int         missileAnimNumFrames; +  int         missileAnimFrameRate; +  int         missileAnimLooping; + +  sfxHandle_t firingSound; +  qboolean    loopFireSound; + +  qhandle_t   muzzleParticleSystem; + +  qboolean    alwaysImpact; +  qhandle_t   impactParticleSystem; +  qhandle_t   impactMark; +  qhandle_t   impactMarkSize; +  sfxHandle_t impactSound[ 4 ]; //random impact sound +  sfxHandle_t impactFleshSound[ 4 ]; //random impact sound +} weaponInfoMode_t; + +// each WP_* weapon enum has an associated weaponInfo_t +// that contains media references necessary to present the +// weapon and its effects +typedef struct weaponInfo_s +{ +  qboolean          registered; +  char              *humanName; + +  qhandle_t         handsModel;       // the hands don't actually draw, they just position the weapon +  qhandle_t         weaponModel; +  qhandle_t         barrelModel; +  qhandle_t         flashModel; + +  vec3_t            weaponMidpoint;   // so it will rotate centered instead of by tag + +  qhandle_t         weaponIcon; +  qhandle_t         ammoIcon; + +  qhandle_t         crossHair; +  int               crossHairSize; + +  sfxHandle_t       readySound; + +  qboolean          disableIn3rdPerson; + +  weaponInfoMode_t  wim[ WPM_NUM_WEAPONMODES ]; +} weaponInfo_t; + +typedef struct upgradeInfo_s +{ +  qboolean    registered; +  char        *humanName; + +  qhandle_t   upgradeIcon; +} upgradeInfo_t; + +typedef struct +{ +  qboolean    looped; +  qboolean    enabled; + +  sfxHandle_t sound; +} sound_t; + +typedef struct +{ +  qhandle_t   models[ MAX_BUILDABLE_MODELS ]; +  animation_t animations[ MAX_BUILDABLE_ANIMATIONS ]; + +  //same number of sounds as animations +  sound_t     sounds[ MAX_BUILDABLE_ANIMATIONS ]; +} buildableInfo_t; + +#define MAX_REWARDSTACK   10 +#define MAX_SOUNDBUFFER   20 + +//====================================================================== + +//TA: +typedef struct +{ +  vec3_t    alienBuildablePos[ MAX_GENTITIES ]; +  int       alienBuildableTimes[ MAX_GENTITIES ]; +  int       numAlienBuildables; + +  vec3_t    humanBuildablePos[ MAX_GENTITIES ]; +  int       numHumanBuildables; + +  vec3_t    alienClientPos[ MAX_CLIENTS ]; +  int       numAlienClients; + +  vec3_t    humanClientPos[ MAX_CLIENTS ]; +  int       numHumanClients; + +  int       lastUpdateTime; +  vec3_t    origin; +  vec3_t    vangles; +} entityPos_t; + +typedef struct +{ +  int time; +  int length; +} consoleLine_t; + +#define MAX_CONSOLE_TEXT  8192 +#define MAX_CONSOLE_LINES 32 + +// all cg.stepTime, cg.duckTime, cg.landTime, etc are set to cg.time when the action +// occurs, and they will have visible effects for #define STEP_TIME or whatever msec after + +#define MAX_PREDICTED_EVENTS  16 + +#define NUM_SAVED_STATES ( CMD_BACKUP + 2 ) + +typedef struct +{ +  int           clientFrame;                        // incremented each frame + +  int           clientNum; + +  qboolean      demoPlayback; +  qboolean      levelShot;                          // taking a level menu screenshot +  int           deferredPlayerLoading; +  qboolean      loading;                            // don't defer players at initial startup +  qboolean      intermissionStarted;                // don't play voice rewards, because game will end shortly + +  // there are only one or two snapshot_t that are relevent at a time +  int           latestSnapshotNum;                  // the number of snapshots the client system has received +  int           latestSnapshotTime;                 // the time from latestSnapshotNum, so we don't need to read the snapshot yet + +  snapshot_t    *snap;                              // cg.snap->serverTime <= cg.time +  snapshot_t    *nextSnap;                          // cg.nextSnap->serverTime > cg.time, or NULL +  snapshot_t    activeSnapshots[ 2 ]; + +  float         frameInterpolation;                 // (float)( cg.time - cg.frame->serverTime ) / +                                                    // (cg.nextFrame->serverTime - cg.frame->serverTime) + +  qboolean      thisFrameTeleport; +  qboolean      nextFrameTeleport; + +  int           frametime;                          // cg.time - cg.oldTime + +  int           time;                               // this is the time value that the client +                                                    // is rendering at. +  int           oldTime;                            // time at last frame, used for missile trails and prediction checking + +  int           physicsTime;                        // either cg.snap->time or cg.nextSnap->time + +  int           timelimitWarnings;                  // 5 min, 1 min, overtime +  int           fraglimitWarnings; + +  qboolean      mapRestart;                         // set on a map restart to set back the weapon + +  qboolean      renderingThirdPerson;               // during deaths, chasecams, etc + +  // prediction state +  qboolean      hyperspace;                         // true if prediction has hit a trigger_teleport +  playerState_t predictedPlayerState; +  pmoveExt_t    pmext; +  centity_t     predictedPlayerEntity; +  qboolean      validPPS;                           // clear until the first call to CG_PredictPlayerState +  int           predictedErrorTime; +  vec3_t        predictedError; + +  int           eventSequence; +  int           predictableEvents[MAX_PREDICTED_EVENTS]; + +  float         stepChange;                         // for stair up smoothing +  int           stepTime; + +  float         duckChange;                         // for duck viewheight smoothing +  int           duckTime; + +  float         landChange;                         // for landing hard +  int           landTime; + +  // input state sent to server +  int           weaponSelect; + +  // auto rotating items +  vec3_t        autoAngles; +  vec3_t        autoAxis[ 3 ]; +  vec3_t        autoAnglesFast; +  vec3_t        autoAxisFast[ 3 ]; + +  // view rendering +  refdef_t      refdef; +  vec3_t        refdefViewAngles;                   // will be converted to refdef.viewaxis + +  // zoom key +  qboolean      zoomed; +  int           zoomTime; +  float         zoomSensitivity; + +  // information screen text during loading +  char          infoScreenText[ MAX_STRING_CHARS ]; + +  // scoreboard +  int           scoresRequestTime; +  int           numScores; +  int           selectedScore; +  int           teamScores[ 2 ]; +  score_t       scores[MAX_CLIENTS]; +  qboolean      showScores; +  qboolean      scoreBoardShowing; +  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 + +  // centerprinting +  int           centerPrintTime; +  int           centerPrintCharWidth; +  int           centerPrintY; +  char          centerPrint[ 1024 ]; +  int           centerPrintLines; + +  // low ammo warning state +  int           lowAmmoWarning;   // 1 = low, 2 = empty + +  // kill timers for carnage reward +  int           lastKillTime; + +  // crosshair client ID +  int           crosshairClientNum; +  int           crosshairClientTime; + +  // powerup active flashing +  int           powerupActive; +  int           powerupTime; + +  // attacking player +  int           attackerTime; +  int           voiceTime; + +  // reward medals +  int           rewardStack; +  int           rewardTime; +  int           rewardCount[ MAX_REWARDSTACK ]; +  qhandle_t     rewardShader[ MAX_REWARDSTACK ]; +  qhandle_t     rewardSound[ MAX_REWARDSTACK ]; + +  // sound buffer mainly for announcer sounds +  int           soundBufferIn; +  int           soundBufferOut; +  int           soundTime; +  qhandle_t     soundBuffer[ MAX_SOUNDBUFFER ]; + +  // for voice chat buffer +  int           voiceChatTime; +  int           voiceChatBufferIn; +  int           voiceChatBufferOut; + +  // warmup countdown +  int           warmup; +  int           warmupCount; + +  //========================== + +  int           itemPickup; +  int           itemPickupTime; +  int           itemPickupBlendTime;                // the pulse around the crosshair is timed seperately + +  int           weaponSelectTime; +  int           weaponAnimation; +  int           weaponAnimationTime; + +  // blend blobs +  float         damageTime; +  float         damageX, damageY, damageValue; + +  // status bar head +  float         headYaw; +  float         headEndPitch; +  float         headEndYaw; +  int           headEndTime; +  float         headStartPitch; +  float         headStartYaw; +  int           headStartTime; + +  // view movement +  float         v_dmg_time; +  float         v_dmg_pitch; +  float         v_dmg_roll; + +  vec3_t        kick_angles;                        // weapon kicks +  vec3_t        kick_origin; + +  // temp working variables for player view +  float         bobfracsin; +  int           bobcycle; +  float         xyspeed; +  int           nextOrbitTime; + +  // development tool +  refEntity_t   testModelEntity; +  refEntity_t   testModelBarrelEntity; +  char          testModelName[MAX_QPATH]; +  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 +  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 + +  int           forwardMoveTime;                    //TA: for struggling +  int           rightMoveTime; +  int           upMoveTime; + +  float         charModelFraction;                  //TA: loading percentages +  float         mediaFraction; +  float         buildablesFraction; + +  int           lastBuildAttempt; +  int           lastEvolveAttempt; + +  char          consoleText[ MAX_CONSOLE_TEXT ]; +  consoleLine_t consoleLines[ MAX_CONSOLE_LINES ]; +  int           numConsoleLines; + +  particleSystem_t  *poisonCloudPS; + +  float         painBlendValue; +  float         painBlendTarget; +  int           lastHealth; + +  int           lastPredictedCommand; +  int           lastServerTime; +  playerState_t savedPmoveStates[ NUM_SAVED_STATES ]; +  int           stateHead, stateTail; +  int           ping; +} 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; +  qhandle_t   whiteShader; +  qhandle_t   outlineShader; + +  qhandle_t   level2ZapTS; + +  qhandle_t   balloonShader; +  qhandle_t   connectionShader; + +  qhandle_t   viewBloodShader; +  qhandle_t   tracerShader; +  qhandle_t   crosshairShader[ WP_NUM_WEAPONS ]; +  qhandle_t   backTileShader; + +  qhandle_t   creepShader; + +  qhandle_t   scannerShader; +  qhandle_t   scannerBlipShader; +  qhandle_t   scannerLineShader; + + +  qhandle_t   numberShaders[ 11 ]; + +  qhandle_t   shadowMarkShader; +  qhandle_t   wakeMarkShader; + +  // buildable shaders +  qhandle_t   greenBuildShader; +  qhandle_t   redBuildShader; +  qhandle_t   humanSpawningShader; + +  // disconnect +  qhandle_t   disconnectPS; +  qhandle_t   disconnectSound; + +  // sounds +  sfxHandle_t tracerSound; +  sfxHandle_t selectSound; +  sfxHandle_t footsteps[ FOOTSTEP_TOTAL ][ 4 ]; +  sfxHandle_t talkSound; +  sfxHandle_t alienTalkSound; +  sfxHandle_t humanTalkSound; +  sfxHandle_t landSound; +  sfxHandle_t fallSound; + +  sfxHandle_t hardBounceSound1; +  sfxHandle_t hardBounceSound2; + +  sfxHandle_t voteNow; +  sfxHandle_t votePassed; +  sfxHandle_t voteFailed; + +  sfxHandle_t watrInSound; +  sfxHandle_t watrOutSound; +  sfxHandle_t watrUnSound; + +  sfxHandle_t jetpackDescendSound; +  sfxHandle_t jetpackIdleSound; +  sfxHandle_t jetpackAscendSound; + +  qhandle_t   jetPackDescendPS; +  qhandle_t   jetPackHoverPS; +  qhandle_t   jetPackAscendPS; + +  sfxHandle_t medkitUseSound; + +  sfxHandle_t alienStageTransition; +  sfxHandle_t humanStageTransition; + +  sfxHandle_t alienOvermindAttack; +  sfxHandle_t alienOvermindDying; +  sfxHandle_t alienOvermindSpawns; + +  sfxHandle_t alienBuildableExplosion; +  sfxHandle_t alienBuildableDamage; +  sfxHandle_t alienBuildablePrebuild; +  sfxHandle_t humanBuildableExplosion; +  sfxHandle_t humanBuildablePrebuild; +  sfxHandle_t humanBuildableDamage[ 4 ]; + +  sfxHandle_t alienL1Grab; +  sfxHandle_t alienL4ChargePrepare; +  sfxHandle_t alienL4ChargeStart; + +  qhandle_t   cursor; +  qhandle_t   selectCursor; +  qhandle_t   sizeCursor; + +  //light armour +  qhandle_t larmourHeadSkin; +  qhandle_t larmourLegsSkin; +  qhandle_t larmourTorsoSkin; + +  qhandle_t jetpackModel; +  qhandle_t jetpackFlashModel; +  qhandle_t battpackModel; + +  sfxHandle_t repeaterUseSound; + +  sfxHandle_t buildableRepairSound; +  sfxHandle_t buildableRepairedSound; + +  qhandle_t   poisonCloudPS; +  qhandle_t   alienEvolvePS; +  qhandle_t   alienAcidTubePS; + +  sfxHandle_t alienEvolveSound; + +  qhandle_t   humanBuildableDamagedPS; +  qhandle_t   humanBuildableDestroyedPS; +  qhandle_t   alienBuildableDamagedPS; +  qhandle_t   alienBuildableDestroyedPS; + +  qhandle_t   alienBleedPS; +  qhandle_t   humanBleedPS; + +  qhandle_t   teslaZapTS; + +  sfxHandle_t lCannonWarningSound; + +  qhandle_t   buildWeaponTimerPie[ 8 ]; +  qhandle_t   upgradeClassIconShader; +} cgMedia_t; + +typedef struct +{ +  qhandle_t     frameShader; +  qhandle_t     overlayShader; +  qhandle_t     noPowerShader; +  qhandle_t     markedShader; +  vec4_t        healthSevereColor; +  vec4_t        healthHighColor; +  vec4_t        healthElevatedColor; +  vec4_t        healthGuardedColor; +  vec4_t        healthLowColor; +  int           frameHeight; +  int           frameWidth; +  int           healthPadding; +  int           overlayHeight; +  int           overlayWidth; +  float         verticalMargin; +  float         horizontalMargin; +  vec4_t        foreColor; +  vec4_t        backColor; +  qboolean      loaded; +} buildStat_t; + + +// The client game static (cgs) structure hold everything +// loaded or calculated from the gamestate.  It will NOT +// be cleared when a tournement restart is done, allowing +// all clients to begin playing instantly +typedef struct +{ +  gameState_t   gameState;              // gamestate from server +  glconfig_t    glconfig;               // rendering configuration +  float         screenXScale;           // derived from glconfig +  float         screenYScale; +  float         screenXBias; + +  int           serverCommandSequence;  // reliable command stream counter +  int           processedSnapshotNum;   // the number of snapshots cgame has requested + +  qboolean      localServer;            // detected on startup by checking sv_running + +  // parsed from serverinfo +  int           timelimit; +  int           maxclients; +  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           levelStartTime; + +  int           scores1, scores2;   // from configstrings + +  qboolean      newHud; + +  int           alienBuildPoints; +  int           alienBuildPointsTotal; +  int           humanBuildPoints; +  int           humanBuildPointsTotal; +  int           humanBuildPointsPowered; + +  int           alienStage; +  int           humanStage; +  int           alienKills; +  int           humanKills; +  int           alienNextStageThreshold; +  int           humanNextStageThreshold; + +  int           numAlienSpawns; +  int           numHumanSpawns; + +  // +  // locally derived information from gamestate +  // +  qhandle_t     gameModels[ MAX_MODELS ]; +  qhandle_t     gameShaders[ MAX_GAME_SHADERS ]; +  qhandle_t     gameParticleSystems[ MAX_GAME_PARTICLE_SYSTEMS ]; +  sfxHandle_t   gameSounds[ MAX_SOUNDS ]; + +  int           numInlineModels; +  qhandle_t     inlineDrawModel[ MAX_MODELS ]; +  vec3_t        inlineModelMidpoints[ MAX_MODELS ]; + +  clientInfo_t  clientinfo[ MAX_CLIENTS ]; + +  //TA: corpse info +  clientInfo_t  corpseinfo[ MAX_CLIENTS ]; + +  int           cursorX; +  int           cursorY; +  qboolean      eventHandling; +  qboolean      mouseCaptured; +  qboolean      sizingHud; +  void          *capturedItem; +  qhandle_t     activeCursor; + +  buildStat_t   alienBuildStat; +  buildStat_t   humanBuildStat; + +  // media +  cgMedia_t           media; +} cgs_t; + +//============================================================================== + +extern  cgs_t     cgs; +extern  cg_t      cg; +extern  centity_t cg_entities[ MAX_GENTITIES ]; + +//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_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_drawCrosshair; +extern  vmCvar_t    cg_drawCrosshairNames; +extern  vmCvar_t    cg_drawRewards; +extern  vmCvar_t    cg_drawTeamOverlay; +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; +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_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_stereoSeparation; +extern  vmCvar_t    cg_lagometer; +extern  vmCvar_t    cg_drawAttacker; +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_teamChatsOnly; +extern  vmCvar_t    cg_noVoiceChats; +extern  vmCvar_t    cg_noVoiceText; +extern  vmCvar_t    cg_scorePlum; +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_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_depthSortParticles; +extern  vmCvar_t    cg_bounceParticles; +extern  vmCvar_t    cg_consoleLatency; +extern  vmCvar_t    cg_lightFlare; +extern  vmCvar_t    cg_debugParticles; +extern  vmCvar_t    cg_debugTrails; +extern  vmCvar_t    cg_debugPVS; +extern  vmCvar_t    cg_disableWarningDialogs; +extern  vmCvar_t    cg_disableScannerPlane; +extern  vmCvar_t    cg_tutorial; + +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    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; + +extern  vmCvar_t    cg_debugRandom; + +extern  vmCvar_t    cg_optimizePrediction; +extern  vmCvar_t    cg_projectileNudge; +extern  vmCvar_t    cg_unlagged; + +// +// cg_main.c +// +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        CG_StartMusic( void ); +int         CG_PlayerCount( void ); + +void        CG_UpdateCvars( void ); + +int         CG_CrosshairPlayer( void ); +int         CG_LastAttacker( void ); +void        CG_LoadMenus( const char *menuFile ); +void        CG_KeyEvent( int key, qboolean down ); +void        CG_MouseEvent( int x, int y ); +void        CG_EventHandling( int type ); +void        CG_SetScoreSelection( void *menu ); +void        CG_BuildSpectatorString( void ); + +qboolean    CG_FileExists( char *filename ); +void        CG_RemoveNotifyLine( void ); +void        CG_AddNotifyText( void ); + + +// +// cg_view.c +// +void        CG_addSmoothOp( vec3_t rotAxis, float rotAngle, float timeMod ); //TA +void        CG_TestModel_f( void ); +void        CG_TestGun_f( void ); +void        CG_TestModelNextFrame_f( void ); +void        CG_TestModelPrevFrame_f( void ); +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 ); + + +// +// cg_drawtools.c +// +void        CG_DrawPlane( vec3_t origin, vec3_t down, vec3_t right, qhandle_t shader ); +void        CG_AdjustFrom640( float *x, float *y, float *w, float *h ); +void        CG_FillRect( float x, float y, float width, float height, const float *color ); +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 ); + +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 ); + + +// +// cg_draw.c +// +extern  int sortedTeamPlayers[ TEAM_MAXOVERLAY ]; +extern  int numSortedTeamPlayers; + +void        CG_AddLagometerFrameInfo( void ); +void        CG_AddLagometerSnapshotInfo( snapshot_t *snap ); +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 ); +float       CG_GetValue(int ownerDraw); +void        CG_RunMenuScript(char **args); +void        CG_SetPrintString( int type, const char *p ); +void        CG_GetTeamColor( vec4_t *color ); +const char  *CG_GetKillerText( void ); +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 ); +void        CG_DrawLoadingScreen( void ); +void        CG_UpdateMediaFraction( float newFract ); +void        CG_ResetPainBlend( void ); +void        CG_DrawField( float x, float y, int width, float cw, float ch, int value ); + +// +// cg_players.c +// +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 ); +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 ); + +// +// cg_buildable.c +// +void        CG_GhostBuildable( buildable_t buildable ); +void        CG_Buildable( centity_t *cent ); +void        CG_BuildableStatusParse( const char *filename, buildStat_t *bs ); +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 ); + +// +// cg_animation.c +// +void        CG_RunLerpFrame( lerpFrame_t *lf ); + +// +// cg_animmapobj.c +// +void        CG_AnimMapObj( centity_t *cent ); +void        CG_ModelDoor( centity_t *cent ); + +// +// cg_predict.c +// + +#define MAGIC_TRACE_HACK -2 + +void        CG_BuildSolidList( void ); +int         CG_PointContents( const vec3_t point, int passEntityNum ); +void        CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, +                const vec3_t end, int skipNumber, int mask ); +void        CG_CapTrace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, +                const vec3_t end, int skipNumber, int mask ); +void        CG_BiSphereTrace( trace_t *result, const vec3_t start, const vec3_t end, +                const float startRadius, const float endRadius, int skipNumber, int mask ); +void        CG_PredictPlayerState( void ); + + +// +// cg_events.c +// +void        CG_CheckEvents( centity_t *cent ); +void        CG_EntityEvent( centity_t *cent, vec3_t position ); +void        CG_PainEvent( centity_t *cent, int health ); + + +// +// cg_ents.c +// +void        CG_DrawBoundingBox( vec3_t origin, vec3_t mins, vec3_t maxs ); +void        CG_SetEntitySoundPosition( centity_t *cent ); +void        CG_AddPacketEntities( void ); +void        CG_Beam( centity_t *cent ); +void        CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ); + +void        CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, +                                    qhandle_t parentModel, char *tagName ); +void        CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, +                                           qhandle_t parentModel, char *tagName ); + + + + +// +// cg_weapons.c +// +void        CG_NextWeapon_f( void ); +void        CG_PrevWeapon_f( void ); +void        CG_Weapon_f( void ); + +void        CG_InitUpgrades( void ); +void        CG_RegisterUpgrade( int upgradeNum ); +void        CG_InitWeapons( void ); +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 ); +void        CG_Bullet( vec3_t origin, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum ); +void        CG_ShotgunFire( entityState_t *es ); + +void        CG_AddViewWeapon (playerState_t *ps); +void        CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent ); +void        CG_DrawItemSelect( rectDef_t *rect, vec4_t color ); +void        CG_DrawItemSelectText( rectDef_t *rect, float scale, int textStyle ); + + +// +// cg_scanner.c +// +void        CG_UpdateEntityPositions( void ); +void        CG_Scanner( rectDef_t *rect, qhandle_t shader, vec4_t color ); +void        CG_AlienSense( rectDef_t *rect ); + +// +// cg_marks.c +// +void        CG_InitMarkPolys( void ); +void        CG_AddMarks( void ); +void        CG_ImpactMark( qhandle_t markShader, +                           const vec3_t origin, const vec3_t dir, +                           float orientation, +                           float r, float g, float b, float a, +                           qboolean alphaFade, +                           float radius, qboolean temporary ); + +// +// cg_snapshot.c +// +void          CG_ProcessSnapshots( void ); + +// +// cg_consolecmds.c +// +qboolean      CG_ConsoleCommand( void ); +void          CG_InitConsoleCommands( void ); +qboolean      CG_RequestScores( void ); + +// +// cg_servercmds.c +// +void          CG_ExecuteNewServerCommands( int latestSequence ); +void          CG_ParseServerinfo( void ); +void          CG_SetConfigValues( void ); +void          CG_ShaderStateChanged(void); + +// +// cg_playerstate.c +// +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 +// +qboolean    CG_AttachmentPoint( attachment_t *a, vec3_t v ); +qboolean    CG_AttachmentDir( attachment_t *a, vec3_t v ); +qboolean    CG_AttachmentAxis( attachment_t *a, vec3_t axis[ 3 ] ); +qboolean    CG_AttachmentVelocity( attachment_t *a, vec3_t v ); +int         CG_AttachmentCentNum( attachment_t *a ); + +qboolean    CG_Attached( attachment_t *a ); + +void        CG_AttachToPoint( attachment_t *a ); +void        CG_AttachToCent( attachment_t *a ); +void        CG_AttachToTag( attachment_t *a ); +void        CG_AttachToParticle( attachment_t *a ); +void        CG_SetAttachmentPoint( attachment_t *a, vec3_t v ); +void        CG_SetAttachmentCent( attachment_t *a, centity_t *cent ); +void        CG_SetAttachmentTag( attachment_t *a, refEntity_t parent, +                qhandle_t model, char *tagName ); +void        CG_SetAttachmentParticle( attachment_t *a, particle_t *p ); + +void        CG_SetAttachmentOffset( attachment_t *a, vec3_t v ); + +// +// cg_particles.c +// +void                CG_LoadParticleSystems( void ); +qhandle_t           CG_RegisterParticleSystem( char *name ); + +particleSystem_t    *CG_SpawnNewParticleSystem( qhandle_t psHandle ); +void                CG_DestroyParticleSystem( particleSystem_t **ps ); + +qboolean            CG_IsParticleSystemInfinite( particleSystem_t *ps ); +qboolean            CG_IsParticleSystemValid( particleSystem_t **ps ); + +void                CG_SetParticleSystemNormal( particleSystem_t *ps, vec3_t normal ); + +void                CG_AddParticles( void ); + +void                CG_ParticleSystemEntity( centity_t *cent ); + +void                CG_TestPS_f( void ); +void                CG_DestroyTestPS_f( void ); + +// +// cg_trails.c +// +void                CG_LoadTrailSystems( void ); +qhandle_t           CG_RegisterTrailSystem( char *name ); + +trailSystem_t       *CG_SpawnNewTrailSystem( qhandle_t psHandle ); +void                CG_DestroyTrailSystem( trailSystem_t **ts ); + +qboolean            CG_IsTrailSystemValid( trailSystem_t **ts ); + +void                CG_AddTrails( void ); + +void                CG_TestTS_f( void ); +void                CG_DestroyTestTS_f( void ); + +// +// cg_ptr.c +// +int   CG_ReadPTRCode( void ); +void  CG_WritePTRCode( int code ); + +// +// cg_tutorial.c +// +const char *CG_TutorialText( void ); + +// +//=============================================== + +// +// system traps +// These functions are how the cgame communicates with the main game system +// + + +// print message on the local console +void          trap_Print( const char *fmt ); + +// abort the game +void          trap_Error( const char *fmt ); + +// milliseconds should only be used for performance tuning, never +// for anything game related.  Get time from the CG_DrawActiveFrame parameter +int           trap_Milliseconds( void ); + +// console variable interaction +void          trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); +void          trap_Cvar_Update( vmCvar_t *vmCvar ); +void          trap_Cvar_Set( const char *var_name, const char *value ); +void          trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); + +// ServerCommand and ConsoleCommand parameter access +int           trap_Argc( void ); +void          trap_Argv( int n, char *buffer, int bufferLength ); +void          trap_Args( char *buffer, int bufferLength ); +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 ); +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 +int           trap_FS_GetFileList( const char *path, const char *extension, +                                   char *listbuf, int bufsize ); + +// add commands to the local console as if they were typed in +// for map changing, etc.  The command is not executed immediately, +// but will be executed in order the next time console commands +// are processed +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 ); + +// send a string to the server over the network +void          trap_SendClientCommand( const char *s ); + +// force a screen update, only used during gamestate load +void          trap_UpdateScreen( void ); + +// model collision +void          trap_CM_LoadMap( const char *mapname ); +int           trap_CM_NumInlineModels( void ); +clipHandle_t  trap_CM_InlineModel( int index );    // 0 = world, 1+ = bmodels +clipHandle_t  trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs ); +int           trap_CM_PointContents( const vec3_t p, clipHandle_t model ); +int           trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ); +void          trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, +                                const vec3_t mins, const vec3_t maxs, +                                clipHandle_t model, int brushmask ); +void          trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, +                                           const vec3_t mins, const vec3_t maxs, +                                           clipHandle_t model, int brushmask, +                                           const vec3_t origin, const vec3_t angles ); +void          trap_CM_CapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, +                const vec3_t mins, const vec3_t maxs, +                clipHandle_t model, int brushmask ); +void          trap_CM_TransformedCapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, +                const vec3_t mins, const vec3_t maxs, +                clipHandle_t model, int brushmask, +                const vec3_t origin, const vec3_t angles ); +void          trap_CM_BiSphereTrace( trace_t *results, const vec3_t start, +                const vec3_t end, float startRad, float endRad, +                clipHandle_t model, int mask ); +void          trap_CM_TransformedBiSphereTrace( trace_t *results, const vec3_t start, +                const vec3_t end, float startRad, float endRad, +                clipHandle_t model, int mask, +                const vec3_t origin ); + +// Returns the projection of a polygon onto the solid brushes in the world +int           trap_CM_MarkFragments( int numPoints, const vec3_t *points, +                                     const vec3_t projection, +                                     int maxPoints, vec3_t pointBuffer, +                                     int maxFragments, markFragment_t *fragmentBuffer ); + +// normal sounds will have their volume dynamically changed as their entity +// moves and the listener moves +void          trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ); +void          trap_S_StopLoopingSound( int entnum ); + +// a local sound is always played full volume +void          trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ); +void          trap_S_ClearLoopingSounds( qboolean killall ); +void          trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); +void          trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); +void          trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin ); + +// respatialize recalculates the volumes of sound as they should be heard by the +// given entityNum and position +void          trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ); +sfxHandle_t   trap_S_RegisterSound( const char *sample, qboolean compressed );    // returns buzz if not found +void          trap_S_StartBackgroundTrack( const char *intro, const char *loop ); // empty name stops music +void          trap_S_StopBackgroundTrack( void ); + + +void          trap_R_LoadWorldMap( const char *mapname ); + +// all media should be registered during level startup to prevent +// hitches during gameplay +qhandle_t     trap_R_RegisterModel( const char *name );     // returns rgb axis if not found +qhandle_t     trap_R_RegisterSkin( const char *name );      // returns all white if not found +qhandle_t     trap_R_RegisterShader( const char *name );      // returns all white if not found +qhandle_t     trap_R_RegisterShaderNoMip( const char *name );     // returns all white if not found + +// a scene is built up by calls to R_ClearScene and the various R_Add functions. +// Nothing is drawn until R_RenderScene is called. +void          trap_R_ClearScene( void ); +void          trap_R_AddRefEntityToScene( const refEntity_t *re ); + +// polys are intended for simple wall marks, not really for doing +// 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 ); +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_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 ); +int           trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, +                              float frac, const char *tagName ); +void          trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ); + +// The glconfig_t will not change during the life of a cgame. +// If it needs to change, the entire cgame will be restarted, because +// all the qhandle_t are then invalid. +void          trap_GetGlconfig( glconfig_t *glconfig ); + +// the gamestate should be grabbed at startup, and whenever a +// configstring changes +void          trap_GetGameState( gameState_t *gamestate ); + +// cgame will poll each frame to see if a newer snapshot has arrived +// that it is interested in.  The time is returned seperately so that +// snapshot latency can be calculated. +void          trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ); + +// a snapshot get can fail if the snapshot (or the entties it holds) is so +// old that it has fallen out of the client system queue +qboolean      trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ); + +// retrieve a text command from the server stream +// the current snapshot will hold the number of the most recent command +// qfalse can be returned if the client system handled the command +// argc() / argv() can be used to examine the parameters of the command +qboolean      trap_GetServerCommand( int serverCommandNumber ); + +// returns the most recent command number that can be passed to GetUserCmd +// this will always be at least one higher than the number in the current +// snapshot, and it may be quite a few higher if it is a fast computer on +// a lagged connection +int           trap_GetCurrentCmdNumber( void ); + +qboolean      trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd ); + +// used for the weapon select and zoom +void          trap_SetUserCmdValue( int stateValue, float sensitivityScale ); + +// aids for VM testing +void          testPrintInt( char *string, int i ); +void          testPrintFloat( char *string, float f ); + +int           trap_MemoryRemaining( void ); +void          trap_R_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font); +qboolean      trap_Key_IsDown( int keynum ); +int           trap_Key_GetCatcher( void ); +void          trap_Key_SetCatcher( int catcher ); +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 ); + +int           trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits ); +e_status      trap_CIN_StopCinematic( int handle ); +e_status      trap_CIN_RunCinematic( int handle ); +void          trap_CIN_DrawCinematic( int handle ); +void          trap_CIN_SetExtents( int handle, int x, int y, int w, int h ); + +void          trap_SnapVector( float *v ); +int           trap_RealTime( qtime_t *tm ); + +qboolean      trap_loadCamera( const char *name ); +void          trap_startCamera( int time ); +qboolean      trap_getCameraInfo( int time, vec3_t *origin, vec3_t *angles ); + +qboolean      trap_GetEntityToken( char *buffer, int bufferSize ); + +int           trap_GetDemoState( void ); +int           trap_GetDemoPos( void ); +void          trap_GetDemoName( char *buffer, int size ); + +// cg_drawCrosshair settings +#define CROSSHAIR_ALWAYSOFF       0 +#define CROSSHAIR_RANGEDONLY      1 +#define CROSSHAIR_ALWAYSON        2 + diff --git a/src/cgame/cg_main.c b/src/cgame/cg_main.c new file mode 100644 index 0000000..d303b30 --- /dev/null +++ b/src/cgame/cg_main.c @@ -0,0 +1,1842 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +// cg_main.c -- initialization and primary entry point for cgame + + +#include "cg_local.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 ); + +/* +================ +vmMain + +This is the only way control passes into the module. +This must be the very first function compiled into the .q3vm file +================ +*/ +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 ) +{ +  switch( command ) +  { +    case CG_INIT: +      CG_Init( arg0, arg1, arg2 ); +      return 0; + +    case CG_SHUTDOWN: +      CG_Shutdown( ); +      return 0; + +    case CG_CONSOLE_COMMAND: +      return CG_ConsoleCommand( ); + +    case CG_CONSOLE_TEXT: +      CG_AddNotifyText( ); +      return 0; + +    case CG_DRAW_ACTIVE_FRAME: +      CG_DrawActiveFrame( arg0, arg1, arg2 ); +      return 0; + +    case CG_CROSSHAIR_PLAYER: +      return CG_CrosshairPlayer( ); + +    case CG_LAST_ATTACKER: +      return CG_LastAttacker( ); + +    case CG_KEY_EVENT: +      CG_KeyEvent( arg0, arg1 ); +      return 0; + +    case CG_MOUSE_EVENT: +      cgDC.cursorx = cgs.cursorX; +      cgDC.cursory = cgs.cursorY; +      CG_MouseEvent( arg0, arg1 ); +      return 0; + +    case CG_EVENT_HANDLING: +      CG_EventHandling( arg0 ); +      return 0; + +    default: +      CG_Error( "vmMain: unknown command %i", command ); +      break; +  } + +  return -1; +} + + +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_drawCrosshair; +vmCvar_t  cg_drawCrosshairNames; +vmCvar_t  cg_drawRewards; +vmCvar_t  cg_crosshairX; +vmCvar_t  cg_crosshairY; +vmCvar_t  cg_draw2D; +vmCvar_t  cg_drawStatus; +vmCvar_t  cg_animSpeed; +vmCvar_t  cg_debugAnim; +vmCvar_t  cg_debugPosition; +vmCvar_t  cg_debugEvents; +vmCvar_t  cg_errorDecay; +vmCvar_t  cg_nopredict; +vmCvar_t  cg_debugMove; +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; +vmCvar_t  cg_gun_x; +vmCvar_t  cg_gun_y; +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_stereoSeparation; +vmCvar_t  cg_lagometer; +vmCvar_t  cg_drawAttacker; +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_drawTeamOverlay; +vmCvar_t  cg_teamOverlayUserinfo; +vmCvar_t  cg_drawFriend; +vmCvar_t  cg_teamChatsOnly; +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_depthSortParticles; +vmCvar_t  cg_bounceParticles; +vmCvar_t  cg_consoleLatency; +vmCvar_t  cg_lightFlare; +vmCvar_t  cg_debugParticles; +vmCvar_t  cg_debugTrails; +vmCvar_t  cg_debugPVS; +vmCvar_t  cg_disableWarningDialogs; +vmCvar_t  cg_disableScannerPlane; +vmCvar_t  cg_tutorial; + +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  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; + +vmCvar_t  cg_debugRandom; + +vmCvar_t  cg_optimizePrediction; +vmCvar_t  cg_projectileNudge; +vmCvar_t  cg_unlagged; + + +typedef struct +{ +  vmCvar_t  *vmCvar; +  char      *cvarName; +  char      *defaultString; +  int       cvarFlags; +} cvarTable_t; + +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_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_addMarks, "cg_marks", "1", CVAR_ARCHIVE }, +  { &cg_lagometer, "cg_lagometer", "0", 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 }, +  { &cg_debugPosition, "cg_debugposition", "0", CVAR_CHEAT }, +  { &cg_debugEvents, "cg_debugevents", "0", CVAR_CHEAT }, +  { &cg_errorDecay, "cg_errordecay", "100", 0 }, +  { &cg_nopredict, "cg_nopredict", "0", 0 }, +  { &cg_debugMove, "cg_debugMove", "0", 0 }, +  { &cg_noPlayerAnims, "cg_noplayeranims", "0", CVAR_CHEAT }, +  { &cg_showmiss, "cg_showmiss", "0", 0 }, +  { &cg_footsteps, "cg_footsteps", "1", CVAR_CHEAT }, +  { &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_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_stats, "cg_stats", "0", 0 }, +  { &cg_drawFriend, "cg_drawFriend", "1", CVAR_ARCHIVE }, +  { &cg_teamChatsOnly, "cg_teamChatsOnly", "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 }, +  { &cg_unlagged, "cg_unlagged", "1", 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 }, +  { &cg_lightFlare, "cg_lightFlare", "3", CVAR_ARCHIVE }, +  { &cg_debugParticles, "cg_debugParticles", "0", CVAR_CHEAT }, +  { &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_tutorial, "cg_tutorial", "1", CVAR_ARCHIVE }, +  { &cg_hudFiles, "cg_hudFiles", "ui/hud.txt", 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 }, + +  { &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 }, + +  { &cg_debugRandom, "cg_debugRandom", "0", 0 }, +   +  { &cg_optimizePrediction, "cg_optimizePrediction", "1", CVAR_ARCHIVE }, +  { &cg_projectileNudge, "cg_projectileNudge", "1", CVAR_ARCHIVE }, + +  // 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_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_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 } +}; + +static int   cvarTableSize = sizeof( cvarTable ) / sizeof( cvarTable[0] ); + +/* +================= +CG_RegisterCvars +================= +*/ +void CG_RegisterCvars( void ) +{ +  int         i; +  cvarTable_t *cv; +  char        var[ MAX_TOKEN_CHARS ]; + +  for( i = 0, cv = cvarTable; i < cvarTableSize; i++, cv++ ) +  { +    trap_Cvar_Register( cv->vmCvar, cv->cvarName, +      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_UpdateCvars +================= +*/ +void CG_UpdateCvars( void ) +{ +  int         i; +  cvarTable_t *cv; + +  for( i = 0, cv = cvarTable; i < cvarTableSize; i++, cv++ ) +    trap_Cvar_Update( cv->vmCvar ); + +  // check for modications here + +  // if force model changed +  if( forceModelModificationCount != cg_forceModel.modificationCount ) +  { +    forceModelModificationCount = cg_forceModel.modificationCount; +    CG_ForceModelChange( ); +  } +} + + +int CG_CrosshairPlayer( void ) +{ +  if( cg.time > ( cg.crosshairClientTime + 1000 ) ) +    return -1; + +  return cg.crosshairClientNum; +} + + +int CG_LastAttacker( void ) +{ +  if( !cg.attackerTime ) +    return -1; + +  return cg.snap->ps.persistant[ PERS_ATTACKER ]; +} + +/* +================= +CG_RemoveNotifyLine +================= +*/ +void CG_RemoveNotifyLine( void ) +{ +  int i, offset, totalLength; + +  if( cg.numConsoleLines == 0 ) +    return; + +  offset = cg.consoleLines[ 0 ].length; +  totalLength = strlen( cg.consoleText ) - offset; + +  //slide up consoleText +  for( i = 0; i <= totalLength; i++ ) +    cg.consoleText[ i ] = cg.consoleText[ i + offset ]; + +  //pop up the first consoleLine +  for( i = 0; i < cg.numConsoleLines; i++ ) +    cg.consoleLines[ i ] = cg.consoleLines[ i + 1 ]; + +  cg.numConsoleLines--; +} + +/* +================= +CG_AddNotifyText +================= +*/ +void CG_AddNotifyText( void ) +{ +  char buffer[ BIG_INFO_STRING ]; + +  trap_LiteralArgs( buffer, BIG_INFO_STRING ); + +  if( !buffer[ 0 ] ) +  { +    cg.consoleText[ 0 ] = '\0'; +    cg.numConsoleLines = 0; +    return; +  } + +  if( cg.numConsoleLines == MAX_CONSOLE_LINES ) +    CG_RemoveNotifyLine( ); + +  Q_strcat( cg.consoleText, MAX_CONSOLE_TEXT, buffer ); +  cg.consoleLines[ cg.numConsoleLines ].time = cg.time; +  cg.consoleLines[ cg.numConsoleLines ].length = strlen( buffer ); +  cg.numConsoleLines++; +} + +void QDECL CG_Printf( const char *msg, ... ) +{ +  va_list argptr; +  char    text[ 1024 ]; + +  va_start( argptr, msg ); +  vsprintf( text, msg, argptr ); +  va_end( argptr ); + +  trap_Print( text ); +} + +void QDECL CG_Error( const char *msg, ... ) +{ +  va_list argptr; +  char    text[ 1024 ]; + +  va_start( argptr, msg ); +  vsprintf( text, msg, argptr ); +  va_end( argptr ); + +  trap_Error( text ); +} + +void QDECL Com_Error( int level, const char *error, ... ) +{ +  va_list argptr; +  char    text[1024]; + +  va_start( argptr, error ); +  vsprintf( text, error, argptr ); +  va_end( argptr ); + +  CG_Error( "%s", text ); +} + +void QDECL Com_Printf( const char *msg, ... ) { +  va_list   argptr; +  char    text[1024]; + +  va_start (argptr, msg); +  vsprintf (text, msg, argptr); +  va_end (argptr); + +  CG_Printf ("%s", text); +} + + + +/* +================ +CG_Argv +================ +*/ +const char *CG_Argv( int arg ) +{ +  static char buffer[ MAX_STRING_CHARS ]; + +  trap_Argv( arg, buffer, sizeof( buffer ) ); + +  return buffer; +} + + +//======================================================================== + +/* +================= +CG_FileExists + +Test if a specific file exists or not +================= +*/ +qboolean CG_FileExists( 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; +} + +/* +================= +CG_RegisterSounds + +called during a precache command +================= +*/ +static void CG_RegisterSounds( void ) +{ +  int         i; +  char        name[ MAX_QPATH ]; +  const char  *soundName; + +  cgs.media.alienStageTransition  = trap_S_RegisterSound( "sound/announcements/overmindevolved.wav", qtrue ); +  cgs.media.humanStageTransition  = trap_S_RegisterSound( "sound/announcements/reinforcement.wav", qtrue ); + +  cgs.media.alienOvermindAttack   = trap_S_RegisterSound( "sound/announcements/overmindattack.wav", qtrue ); +  cgs.media.alienOvermindDying    = trap_S_RegisterSound( "sound/announcements/overminddying.wav", qtrue ); +  cgs.media.alienOvermindSpawns   = trap_S_RegisterSound( "sound/announcements/overmindspawns.wav", qtrue ); + +  cgs.media.alienL1Grab           = trap_S_RegisterSound( "sound/player/level1/grab.wav", qtrue ); +  cgs.media.alienL4ChargePrepare  = trap_S_RegisterSound( "sound/player/level4/charge_prepare.wav", qtrue ); +  cgs.media.alienL4ChargeStart    = trap_S_RegisterSound( "sound/player/level4/charge_start.wav", qtrue ); + +  cgs.media.tracerSound           = trap_S_RegisterSound( "sound/weapons/tracer.wav", qfalse ); +  cgs.media.selectSound           = trap_S_RegisterSound( "sound/weapons/change.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 ); +  cgs.media.humanTalkSound        = trap_S_RegisterSound( "sound/misc/human_talk.wav", qfalse ); +  cgs.media.landSound             = trap_S_RegisterSound( "sound/player/land1.wav", qfalse ); + +  cgs.media.watrInSound           = trap_S_RegisterSound( "sound/player/watr_in.wav", qfalse ); +  cgs.media.watrOutSound          = trap_S_RegisterSound( "sound/player/watr_out.wav", qfalse ); +  cgs.media.watrUnSound           = trap_S_RegisterSound( "sound/player/watr_un.wav", qfalse ); + +  cgs.media.disconnectSound       = trap_S_RegisterSound( "sound/misc/disconnect.wav", qfalse ); + +  for( i = 0; i < 4; i++ ) +  { +    Com_sprintf( name, sizeof( name ), "sound/player/footsteps/step%i.wav", i + 1 ); +    cgs.media.footsteps[ FOOTSTEP_NORMAL ][ i ] = trap_S_RegisterSound( name, qfalse ); + +    Com_sprintf( name, sizeof( name ), "sound/player/footsteps/flesh%i.wav", i + 1 ); +    cgs.media.footsteps[ FOOTSTEP_FLESH ][ i ] = trap_S_RegisterSound( name, qfalse ); + +    Com_sprintf( name, sizeof( name ), "sound/player/footsteps/splash%i.wav", i + 1 ); +    cgs.media.footsteps[ FOOTSTEP_SPLASH ][ i ] = trap_S_RegisterSound( name, qfalse ); + +    Com_sprintf( name, sizeof( name ), "sound/player/footsteps/clank%i.wav", i + 1 ); +    cgs.media.footsteps[ FOOTSTEP_METAL ][ i ] = trap_S_RegisterSound( name, qfalse ); +  } + +  for( i = 1 ; i < MAX_SOUNDS ; i++ ) +  { +    soundName = CG_ConfigString( CS_SOUNDS + i ); + +    if( !soundName[ 0 ] ) +      break; + +    if( soundName[ 0 ] == '*' ) +      continue; // custom sound + +    cgs.gameSounds[ i ] = trap_S_RegisterSound( soundName, qfalse ); +  } + +  cgs.media.jetpackDescendSound     = trap_S_RegisterSound( "sound/upgrades/jetpack/low.wav", qfalse ); +  cgs.media.jetpackIdleSound        = trap_S_RegisterSound( "sound/upgrades/jetpack/idle.wav", qfalse ); +  cgs.media.jetpackAscendSound      = trap_S_RegisterSound( "sound/upgrades/jetpack/hi.wav", qfalse ); + +  cgs.media.medkitUseSound          = trap_S_RegisterSound( "sound/upgrades/medkit/medkit.wav", qfalse ); + +  cgs.media.alienEvolveSound        = trap_S_RegisterSound( "sound/player/alienevolve.wav", qfalse ); + +  cgs.media.alienBuildableExplosion = trap_S_RegisterSound( "sound/buildables/alien/explosion.wav", qfalse ); +  cgs.media.alienBuildableDamage    = trap_S_RegisterSound( "sound/buildables/alien/damage.wav", qfalse ); +  cgs.media.alienBuildablePrebuild  = trap_S_RegisterSound( "sound/buildables/alien/prebuild.wav", qfalse ); + +  cgs.media.humanBuildableExplosion = trap_S_RegisterSound( "sound/buildables/human/explosion.wav", qfalse ); +  cgs.media.humanBuildablePrebuild  = trap_S_RegisterSound( "sound/buildables/human/prebuild.wav", qfalse ); + +  for( i = 0; i < 4; i++ ) +    cgs.media.humanBuildableDamage[ i ] = trap_S_RegisterSound( +        va( "sound/buildables/human/damage%d.wav", i ), qfalse ); + +  cgs.media.hardBounceSound1        = trap_S_RegisterSound( "sound/misc/hard_bounce1.wav", qfalse ); +  cgs.media.hardBounceSound2        = trap_S_RegisterSound( "sound/misc/hard_bounce2.wav", qfalse ); + +  cgs.media.repeaterUseSound        = trap_S_RegisterSound( "sound/buildables/repeater/use.wav", qfalse ); + +  cgs.media.buildableRepairSound    = trap_S_RegisterSound( "sound/buildables/human/repair.wav", qfalse ); +  cgs.media.buildableRepairedSound  = trap_S_RegisterSound( "sound/buildables/human/repaired.wav", qfalse ); + +  cgs.media.lCannonWarningSound     = trap_S_RegisterSound( "models/weapons/lcannon/warning.wav", qfalse ); +} + + +//=================================================================================== + + +/* +================= +CG_RegisterGraphics + +This function may execute for a couple of minutes with a slow disk. +================= +*/ +static void CG_RegisterGraphics( void ) +{ +  int         i; +  static char *sb_nums[ 11 ] = +  { +    "gfx/2d/numbers/zero_32b", +    "gfx/2d/numbers/one_32b", +    "gfx/2d/numbers/two_32b", +    "gfx/2d/numbers/three_32b", +    "gfx/2d/numbers/four_32b", +    "gfx/2d/numbers/five_32b", +    "gfx/2d/numbers/six_32b", +    "gfx/2d/numbers/seven_32b", +    "gfx/2d/numbers/eight_32b", +    "gfx/2d/numbers/nine_32b", +    "gfx/2d/numbers/minus_32b", +  }; +  static char *buildWeaponTimerPieShaders[ 8 ] = +  { +    "ui/assets/neutral/1_5pie", +    "ui/assets/neutral/3_0pie", +    "ui/assets/neutral/4_5pie", +    "ui/assets/neutral/6_0pie", +    "ui/assets/neutral/7_5pie", +    "ui/assets/neutral/9_0pie", +    "ui/assets/neutral/10_5pie", +    "ui/assets/neutral/12_0pie", +  }; + +  // clear any references to old media +  memset( &cg.refdef, 0, sizeof( cg.refdef ) ); +  trap_R_ClearScene( ); + +  trap_R_LoadWorldMap( cgs.mapname ); +  CG_UpdateMediaFraction( 0.66f ); + +  for( i = 0; i < 11; i++ ) +    cgs.media.numberShaders[ i ] = trap_R_RegisterShader( sb_nums[ i ] ); + +  cgs.media.viewBloodShader           = trap_R_RegisterShader( "gfx/damage/fullscreen_painblend" ); + +  cgs.media.connectionShader          = trap_R_RegisterShader( "gfx/2d/net" ); + +  cgs.media.creepShader               = trap_R_RegisterShader( "creep" ); + +  cgs.media.scannerBlipShader         = trap_R_RegisterShader( "gfx/2d/blip" ); +  cgs.media.scannerLineShader         = trap_R_RegisterShader( "gfx/2d/stalk" ); + +  cgs.media.tracerShader              = trap_R_RegisterShader( "gfx/misc/tracer" ); + +  cgs.media.backTileShader            = trap_R_RegisterShader( "console" ); + + +  //TA: 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" ); + +  for( i = 0; i < 8; i++ ) +    cgs.media.buildWeaponTimerPie[ i ] = trap_R_RegisterShader( buildWeaponTimerPieShaders[ i ] ); + +  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" ); + +  CG_UpdateMediaFraction( 0.7f ); + +  memset( cg_weapons, 0, sizeof( cg_weapons ) ); +  memset( cg_upgrades, 0, sizeof( cg_upgrades ) ); + +  cgs.media.shadowMarkShader          = trap_R_RegisterShader( "gfx/marks/shadow" ); +  cgs.media.wakeMarkShader            = trap_R_RegisterShader( "gfx/marks/wake" ); + +  cgs.media.poisonCloudPS             = CG_RegisterParticleSystem( "firstPersonPoisonCloudPS" ); +  cgs.media.alienEvolvePS             = CG_RegisterParticleSystem( "alienEvolvePS" ); +  cgs.media.alienAcidTubePS           = CG_RegisterParticleSystem( "alienAcidTubePS" ); + +  cgs.media.jetPackDescendPS          = CG_RegisterParticleSystem( "jetPackDescendPS" ); +  cgs.media.jetPackHoverPS            = CG_RegisterParticleSystem( "jetPackHoverPS" ); +  cgs.media.jetPackAscendPS           = CG_RegisterParticleSystem( "jetPackAscendPS" ); + +  cgs.media.humanBuildableDamagedPS   = CG_RegisterParticleSystem( "humanBuildableDamagedPS" ); +  cgs.media.alienBuildableDamagedPS   = CG_RegisterParticleSystem( "alienBuildableDamagedPS" ); +  cgs.media.humanBuildableDestroyedPS = CG_RegisterParticleSystem( "humanBuildableDestroyedPS" ); +  cgs.media.alienBuildableDestroyedPS = CG_RegisterParticleSystem( "alienBuildableDestroyedPS" ); + +  cgs.media.alienBleedPS              = CG_RegisterParticleSystem( "alienBleedPS" ); +  cgs.media.humanBleedPS              = CG_RegisterParticleSystem( "humanBleedPS" ); + +  CG_BuildableStatusParse( "ui/assets/human/buildstat.cfg", &cgs.humanBuildStat ); +  CG_BuildableStatusParse( "ui/assets/alien/buildstat.cfg", &cgs.alienBuildStat ); +  +  // register the inline models +  cgs.numInlineModels = trap_CM_NumInlineModels( ); + +  for( i = 1; i < cgs.numInlineModels; i++ ) +  { +    char    name[ 10 ]; +    vec3_t  mins, maxs; +    int     j; + +    Com_sprintf( name, sizeof( name ), "*%i", i ); + +    cgs.inlineDrawModel[ i ] = trap_R_RegisterModel( name ); +    trap_R_ModelBounds( cgs.inlineDrawModel[ i ], mins, maxs ); + +    for( j = 0 ; j < 3 ; j++ ) +      cgs.inlineModelMidpoints[ i ][ j ] = mins[ j ] + 0.5 * ( maxs[ j ] - mins[ j ] ); +  } + +  // register all the server specified models +  for( i = 1; i < MAX_MODELS; i++ ) +  { +    const char *modelName; + +    modelName = CG_ConfigString( CS_MODELS + i ); + +    if( !modelName[ 0 ] ) +      break; + +    cgs.gameModels[ i ] = trap_R_RegisterModel( modelName ); +  } + +  CG_UpdateMediaFraction( 0.8f ); + +  // register all the server specified shaders +  for( i = 1; i < MAX_GAME_SHADERS; i++ ) +  { +    const char *shaderName; + +    shaderName = CG_ConfigString( CS_SHADERS + i ); + +    if( !shaderName[ 0 ] ) +      break; + +    cgs.gameShaders[ i ] = trap_R_RegisterShader( shaderName ); +  } + +  CG_UpdateMediaFraction( 0.9f ); + +  // register all the server specified particle systems +  for( i = 1; i < MAX_GAME_PARTICLE_SYSTEMS; i++ ) +  { +    const char *psName; + +    psName = CG_ConfigString( CS_PARTICLE_SYSTEMS + i ); + +    if( !psName[ 0 ] ) +      break; + +    cgs.gameParticleSystems[ i ] = CG_RegisterParticleSystem( (char *)psName ); +  } +} + + +/* +======================= +CG_BuildSpectatorString + +======================= +*/ +void CG_BuildSpectatorString( void ) +{ +  int i; + +  cg.spectatorList[ 0 ] = 0; + +  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; +  } +} + + + +/* +=================== +CG_RegisterClients + +=================== +*/ +static void CG_RegisterClients( void ) +{ +  int   i; + +  cg.charModelFraction = 0.0f; + +  //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.charModelFraction = (float)i / (float)PCL_NUM_CLASSES; +    trap_UpdateScreen( ); +  } + +  cgs.media.larmourHeadSkin    = trap_R_RegisterSkin( "models/players/human_base/head_light.skin" ); +  cgs.media.larmourLegsSkin    = trap_R_RegisterSkin( "models/players/human_base/lower_light.skin" ); +  cgs.media.larmourTorsoSkin   = trap_R_RegisterSkin( "models/players/human_base/upper_light.skin" ); + +  cgs.media.jetpackModel       = trap_R_RegisterModel( "models/players/human_base/jetpack.md3" ); +  cgs.media.jetpackFlashModel  = trap_R_RegisterModel( "models/players/human_base/jetpack_flash.md3" ); +  cgs.media.battpackModel      = trap_R_RegisterModel( "models/players/human_base/battpack.md3" ); + +  cg.charModelFraction = 1.0f; +  trap_UpdateScreen( ); + +  //load all the clientinfos of clients already connected to the server +  for( i = 0; i < MAX_CLIENTS; i++ ) +  { +    const char  *clientInfo; + +    clientInfo = CG_ConfigString( CS_PLAYERS + i ); +    if( !clientInfo[ 0 ] ) +      continue; + +    CG_NewClientInfo( i ); +  } + +  CG_BuildSpectatorString( ); +} + +//=========================================================================== + +/* +================= +CG_ConfigString +================= +*/ +const char *CG_ConfigString( int index ) +{ +  if( index < 0 || index >= MAX_CONFIGSTRINGS ) +    CG_Error( "CG_ConfigString: bad index: %i", index ); + +  return cgs.gameState.stringData + cgs.gameState.stringOffsets[ index ]; +} + +//================================================================== + +/* +====================== +CG_StartMusic + +====================== +*/ +void CG_StartMusic( void ) +{ +  char  *s; +  char  parm1[ MAX_QPATH ], parm2[ MAX_QPATH ]; + +  // start the background music +  s = (char *)CG_ConfigString( CS_MUSIC ); +  Q_strncpyz( parm1, COM_Parse( &s ), sizeof( parm1 ) ); +  Q_strncpyz( parm2, COM_Parse( &s ), sizeof( parm2 ) ); + +  trap_S_StartBackgroundTrack( parm1, parm2 ); +} + +/* +====================== +CG_PlayerCount +====================== +*/ +int CG_PlayerCount( void ) +{ +  int i, count = 0; + +  CG_RequestScores( ); + +  for( i = 0; i < cg.numScores; i++ ) +  { +    if( cg.scores[ i ].team == PTE_ALIENS || +        cg.scores[ i ].team == PTE_HUMANS ) +      count++; +  } + +  return count; +} + +// +// ============================== +// new hud stuff ( mission pack ) +// ============================== +// +char *CG_GetMenuBuffer( const char *filename ) +{ +  int           len; +  fileHandle_t  f; +  static char   buf[ MAX_MENUFILE ]; + +  len = trap_FS_FOpenFile( filename, &f, FS_READ ); + +  if( !f ) +  { +    trap_Print( va( S_COLOR_RED "menu file not found: %s, using default\n", filename ) ); +    return NULL; +  } + +  if( len >= MAX_MENUFILE ) +  { +    trap_Print( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", +                    filename, len, MAX_MENUFILE ) ); +    trap_FS_FCloseFile( f ); +    return NULL; +  } + +  trap_FS_Read( buf, len, f ); +  buf[len] = 0; +  trap_FS_FCloseFile( f ); + +  return buf; +} + +qboolean CG_Asset_Parse( int handle ) +{ +  pc_token_t token; +  const char *tempStr; + +  if( !trap_Parse_ReadToken( handle, &token ) ) +    return qfalse; + +  if( Q_stricmp( token.string, "{" ) != 0 ) +    return qfalse; + +  while( 1 ) +  { +    if( !trap_Parse_ReadToken( handle, &token ) ) +      return qfalse; + +    if( Q_stricmp( token.string, "}" ) == 0 ) +      return qtrue; + +    // font +    if( Q_stricmp( token.string, "font" ) == 0 ) +    { +      int pointSize; + +      if( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle, &pointSize ) ) +        return qfalse; + +      cgDC.registerFont( tempStr, pointSize, &cgDC.Assets.textFont ); +      continue; +    } + +    // smallFont +    if( Q_stricmp( token.string, "smallFont" ) == 0 ) +    { +      int pointSize; + +      if( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle, &pointSize ) ) +        return qfalse; + +      cgDC.registerFont( tempStr, pointSize, &cgDC.Assets.smallFont ); +      continue; +    } + +    // font +    if( Q_stricmp( token.string, "bigfont" ) == 0 ) +    { +      int pointSize; + +      if( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle, &pointSize ) ) +        return qfalse; + +      cgDC.registerFont( tempStr, pointSize, &cgDC.Assets.bigFont ); +      continue; +    } + +    // gradientbar +    if( Q_stricmp( token.string, "gradientbar" ) == 0 ) +    { +      if( !PC_String_Parse( handle, &tempStr ) ) +        return qfalse; + +      cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( tempStr ); +      continue; +    } + +    // enterMenuSound +    if( Q_stricmp( token.string, "menuEnterSound" ) == 0 ) +    { +      if( !PC_String_Parse( handle, &tempStr ) ) +        return qfalse; + +      cgDC.Assets.menuEnterSound = trap_S_RegisterSound( tempStr, qfalse ); +      continue; +    } + +    // exitMenuSound +    if( Q_stricmp( token.string, "menuExitSound" ) == 0 ) +    { +      if( !PC_String_Parse( handle, &tempStr ) ) +        return qfalse; + +      cgDC.Assets.menuExitSound = trap_S_RegisterSound( tempStr, qfalse ); +      continue; +    } + +    // itemFocusSound +    if( Q_stricmp( token.string, "itemFocusSound" ) == 0 ) +    { +      if( !PC_String_Parse( handle, &tempStr ) ) +        return qfalse; + +      cgDC.Assets.itemFocusSound = trap_S_RegisterSound( tempStr, qfalse ); +      continue; +    } + +    // menuBuzzSound +    if( Q_stricmp( token.string, "menuBuzzSound" ) == 0 ) +    { +      if( !PC_String_Parse( handle, &tempStr ) ) +        return qfalse; + +      cgDC.Assets.menuBuzzSound = trap_S_RegisterSound( tempStr, qfalse ); +      continue; +    } + +    if( Q_stricmp( token.string, "cursor" ) == 0 ) +    { +      if( !PC_String_Parse( handle, &cgDC.Assets.cursorStr ) ) +        return qfalse; + +      cgDC.Assets.cursor = trap_R_RegisterShaderNoMip( cgDC.Assets.cursorStr ); +      continue; +    } + +    if( Q_stricmp( token.string, "fadeClamp" ) == 0 ) +    { +      if( !PC_Float_Parse( handle, &cgDC.Assets.fadeClamp ) ) +        return qfalse; + +      continue; +    } + +    if( Q_stricmp( token.string, "fadeCycle" ) == 0 ) +    { +      if( !PC_Int_Parse( handle, &cgDC.Assets.fadeCycle ) ) +        return qfalse; + +      continue; +    } + +    if( Q_stricmp( token.string, "fadeAmount" ) == 0 ) +    { +      if( !PC_Float_Parse( handle, &cgDC.Assets.fadeAmount ) ) +        return qfalse; + +      continue; +    } + +    if( Q_stricmp( token.string, "shadowX" ) == 0 ) +    { +      if( !PC_Float_Parse( handle, &cgDC.Assets.shadowX ) ) +        return qfalse; + +      continue; +    } + +    if( Q_stricmp( token.string, "shadowY" ) == 0 ) +    { +      if( !PC_Float_Parse( handle, &cgDC.Assets.shadowY ) ) +        return qfalse; + +      continue; +    } + +    if( Q_stricmp( token.string, "shadowColor" ) == 0 ) +    { +      if( !PC_Color_Parse( handle, &cgDC.Assets.shadowColor ) ) +        return qfalse; + +      cgDC.Assets.shadowFadeClamp = cgDC.Assets.shadowColor[ 3 ]; +      continue; +    } +  } + +  return qfalse; // bk001204 - why not? +} + +void CG_ParseMenu( const char *menuFile ) +{ +  pc_token_t  token; +  int         handle; + +  handle = trap_Parse_LoadSource( menuFile ); + +  if( !handle ) +    handle = trap_Parse_LoadSource( "ui/testhud.menu" ); + +  if( !handle ) +    return; + +  while( 1 ) +  { +    if( !trap_Parse_ReadToken( handle, &token ) ) +      break; + +    //if ( Q_stricmp( token, "{" ) ) { +    //  Com_Printf( "Missing { in menu file\n" ); +    //  break; +    //} + +    //if ( menuCount == MAX_MENUS ) { +    //  Com_Printf( "Too many menus!\n" ); +    //  break; +    //} + +    if( token.string[ 0 ] == '}' ) +      break; + +    if( Q_stricmp( token.string, "assetGlobalDef" ) == 0 ) +    { +      if( CG_Asset_Parse( handle ) ) +        continue; +      else +        break; +    } + + +    if( Q_stricmp( token.string, "menudef" ) == 0 ) +    { +      // start a new menu +      Menu_New( handle ); +    } +  } + +  trap_Parse_FreeSource( handle ); +} + +qboolean CG_Load_Menu( char **p ) +{ +  char *token; + +  token = COM_ParseExt( p, qtrue ); + +  if( token[ 0 ] != '{' ) +    return qfalse; + +  while( 1 ) +  { +    token = COM_ParseExt( p, qtrue ); + +    if( Q_stricmp( token, "}" ) == 0 ) +      return qtrue; + +    if( !token || token[ 0 ] == 0 ) +      return qfalse; + +    CG_ParseMenu( token ); +  } +  return qfalse; +} + + + +void CG_LoadMenus( const char *menuFile ) +{ +  char          *token; +  char          *p; +  int           len, start; +  fileHandle_t  f; +  static char   buf[ MAX_MENUDEFFILE ]; + +  start = trap_Milliseconds( ); + +  len = trap_FS_FOpenFile( menuFile, &f, FS_READ ); + +  if( !f ) +  { +    trap_Error( va( S_COLOR_YELLOW "menu file not found: %s, using default\n", 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" ) ); +  } + +  if( len >= MAX_MENUDEFFILE ) +  { +    trap_Error( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", +                menuFile, len, MAX_MENUDEFFILE ) ); +    trap_FS_FCloseFile( f ); +    return; +  } + +  trap_FS_Read( buf, len, f ); +  buf[ len ] = 0; +  trap_FS_FCloseFile( f ); + +  COM_Compress( buf ); + +  Menu_Reset( ); + +  p = buf; + +  while( 1 ) +  { +    token = COM_ParseExt( &p, qtrue ); + +    if( !token || token[ 0 ] == 0 || token[ 0 ] == '}' ) +      break; + +    if( Q_stricmp( token, "}" ) == 0 ) +      break; + +    if( Q_stricmp( token, "loadmenu" ) == 0 ) +    { +      if( CG_Load_Menu( &p ) ) +        continue; +      else +        break; +    } +  } + +  Com_Printf( "UI menu load time = %d milli seconds\n", trap_Milliseconds( ) - start ); +} + + + +static qboolean CG_OwnerDrawHandleKey( int ownerDraw, int flags, float *special, int key ) +{ +  return qfalse; +} + + +static int CG_FeederCount( float feederID ) +{ +  int i, count = 0; + +  if( feederID == FEEDER_ALIENTEAM_LIST ) +  { +    for( i = 0; i < cg.numScores; i++ ) +    { +      if( cg.scores[ i ].team == PTE_ALIENS ) +        count++; +    } +  } +  else if( feederID == FEEDER_HUMANTEAM_LIST ) +  { +    for( i = 0; i < cg.numScores; i++ ) +    { +      if( cg.scores[ i ].team == PTE_HUMANS ) +        count++; +    } +  } + +  return count; +} + + +void CG_SetScoreSelection( void *p ) +{ +  menuDef_t     *menu = (menuDef_t*)p; +  playerState_t *ps = &cg.snap->ps; +  int           i, alien, human; +  int           feeder; + +  alien = human = 0; + +  for( i = 0; i < cg.numScores; i++ ) +  { +    if( cg.scores[ i ].team == PTE_ALIENS ) +      alien++; +    else if( cg.scores[ i ].team == PTE_HUMANS ) +      human++; + +    if( ps->clientNum == cg.scores[ i ].client ) +      cg.selectedScore = i; +  } + +  if( menu == NULL ) +    // just interested in setting the selected score +    return; + +  feeder = FEEDER_ALIENTEAM_LIST; +  i = alien; + +  if( cg.scores[ cg.selectedScore ].team == PTE_HUMANS ) +  { +    feeder = FEEDER_HUMANTEAM_LIST; +    i = human; +  } + +  Menu_SetFeederSelection(menu, feeder, i, NULL); +} + +// FIXME: might need to cache this info +static clientInfo_t * CG_InfoFromScoreIndex( int index, int team, int *scoreIndex ) +{ +  int i, count; +  count = 0; + +  for( i = 0; i < cg.numScores; i++ ) +  { +    if( cg.scores[ i ].team == team ) +    { +      if( count == index ) +      { +        *scoreIndex = i; +        return &cgs.clientinfo[ cg.scores[ i ].client ]; +      } +      count++; +    } +  } + +  *scoreIndex = index; +  return &cgs.clientinfo[ cg.scores[ index ].client ]; +} + +static const char *CG_FeederItemText( float feederID, int index, int column, qhandle_t *handle ) +{ +  int           scoreIndex = 0; +  clientInfo_t  *info = NULL; +  int           team = -1; +  score_t       *sp = NULL; +  qboolean      showIcons = qfalse; + +  *handle = -1; + +  if( feederID == FEEDER_ALIENTEAM_LIST ) +    team = PTE_ALIENS; +  else if( feederID == FEEDER_HUMANTEAM_LIST ) +    team = PTE_HUMANS; + +  info = CG_InfoFromScoreIndex( index, team, &scoreIndex ); +  sp = &cg.scores[ scoreIndex ]; + +  if( ( atoi( CG_ConfigString( CS_CLIENTS_READY ) ) & ( 1 << sp->client ) ) && +      cg.intermissionStarted ) +    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 ) +    showIcons = qtrue; + +  if( info && info->infoValid ) +  { +    switch( column ) +    { +      case 0: +        if( showIcons ) +        { +          if( sp->weapon != WP_NONE ) +            *handle = cg_weapons[ sp->weapon ].weaponIcon; +        } +        break; + +      case 1: +        if( showIcons ) +        { +          if( sp->team == PTE_HUMANS && sp->upgrade != UP_NONE ) +            *handle = cg_upgrades[ sp->upgrade ].upgradeIcon; +          else if( sp->team == PTE_ALIENS ) +          { +            switch( sp->weapon ) +            { +              case WP_ABUILD2: +              case WP_ALEVEL1_UPG: +              case WP_ALEVEL2_UPG: +              case WP_ALEVEL3_UPG: +                *handle = cgs.media.upgradeClassIconShader; +                break; + +              default: +                break; +            } +          } +        } +        break; + +      case 2: +        if( ( atoi( CG_ConfigString( CS_CLIENTS_READY ) ) & ( 1 << sp->client ) ) && +            cg.intermissionStarted ) +          return "Ready"; +        break; + +      case 3: +        return info->name; +        break; + +      case 4: +        return va( "%d", info->score ); +        break; + +      case 5: +        return va( "%4d", sp->time ); +        break; + +      case 6: +        if( sp->ping == -1 ) +          return "connecting"; + +        return va( "%4d", sp->ping ); +        break; +    } +  } + +  return ""; +} + +static qhandle_t CG_FeederItemImage( float feederID, int index ) +{ +  return 0; +} + +static void CG_FeederSelection( float feederID, int index ) +{ +  int i, count; +  int team = ( feederID == FEEDER_ALIENTEAM_LIST ) ? PTE_ALIENS : PTE_HUMANS; +  count = 0; + +  for( i = 0; i < cg.numScores; i++ ) +  { +    if( cg.scores[ i ].team == team ) +    { +      if( index == count ) +        cg.selectedScore = i; + +      count++; +    } +  } +} + +static float CG_Cvar_Get( const char *cvar ) +{ +  char buff[ 128 ]; + +  memset( buff, 0, sizeof( buff ) ); +  trap_Cvar_VariableStringBuffer( cvar, buff, sizeof( buff ) ); +  return atof( buff ); +} + +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 ); +} + +static int CG_OwnerDrawWidth( int ownerDraw, float scale ) +{ +  switch( ownerDraw ) +  { +    case CG_KILLER: +      return CG_Text_Width( CG_GetKillerText( ), scale, 0 ); +      break; +  } + +  return 0; +} + +static int CG_PlayCinematic( const char *name, float x, float y, float w, float h ) +{ +  return trap_CIN_PlayCinematic( name, x, y, w, h, CIN_loop ); +} + +static void CG_StopCinematic( int handle ) +{ +  trap_CIN_StopCinematic( handle ); +} + +static void CG_DrawCinematic( int handle, float x, float y, float w, float h ) +{ +  trap_CIN_SetExtents( handle, x, y, w, h ); +  trap_CIN_DrawCinematic( handle ); +} + +static void CG_RunCinematicFrame( int handle ) +{ +  trap_CIN_RunCinematic( handle ); +} + +//TA: hack to prevent warning +static qboolean CG_OwnerDrawVisible( int parameter ) +{ +  return qfalse; +} + +/* +================= +CG_LoadHudMenu +================= +*/ +void CG_LoadHudMenu( void ) +{ +  char        buff[ 1024 ]; +  const char  *hudSet; + +  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; +  cgDC.drawRect             = &CG_DrawRect; +  cgDC.drawSides            = &CG_DrawSides; +  cgDC.drawTopBottom        = &CG_DrawTopBottom; +  cgDC.clearScene           = &trap_R_ClearScene; +  cgDC.addRefEntityToScene  = &trap_R_AddRefEntityToScene; +  cgDC.renderScene          = &trap_R_RenderScene; +  cgDC.registerFont         = &trap_R_RegisterFont; +  cgDC.ownerDrawItem        = &CG_OwnerDraw; +  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.startLocalSound      = &trap_S_StartLocalSound; +  cgDC.ownerDrawHandleKey   = &CG_OwnerDrawHandleKey; +  cgDC.feederCount          = &CG_FeederCount; +  cgDC.feederItemImage      = &CG_FeederItemImage; +  cgDC.feederItemText       = &CG_FeederItemText; +  cgDC.feederSelection      = &CG_FeederSelection; +  //cgDC.setBinding           = &trap_Key_SetBinding; +  //cgDC.getBindingBuf        = &trap_Key_GetBindingBuf; +  //cgDC.keynumToStringBuf    = &trap_Key_KeynumToStringBuf; +  //cgDC.executeText          = &trap_Cmd_ExecuteText; +  cgDC.Error                = &Com_Error; +  cgDC.Print                = &Com_Printf; +  cgDC.ownerDrawWidth       = &CG_OwnerDrawWidth; +  //cgDC.Pause                = &CG_Pause; +  cgDC.registerSound        = &trap_S_RegisterSound; +  cgDC.startBackgroundTrack = &trap_S_StartBackgroundTrack; +  cgDC.stopBackgroundTrack  = &trap_S_StopBackgroundTrack; +  cgDC.playCinematic        = &CG_PlayCinematic; +  cgDC.stopCinematic        = &CG_StopCinematic; +  cgDC.drawCinematic        = &CG_DrawCinematic; +  cgDC.runCinematicFrame    = &CG_RunCinematicFrame; + +  Init_Display( &cgDC ); + +  Menu_Reset( ); + +  trap_Cvar_VariableStringBuffer( "cg_hudFiles", buff, sizeof( buff ) ); +  hudSet = buff; + +  if( hudSet[ 0 ] == '\0' ) +    hudSet = "ui/hud.txt"; + +  CG_LoadMenus( hudSet ); +} + +void CG_AssetCache( void ) +{ +  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 ); +  cgDC.Assets.scrollBarArrowUp    = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWUP ); +  cgDC.Assets.scrollBarArrowLeft  = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWLEFT ); +  cgDC.Assets.scrollBarArrowRight = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWRIGHT ); +  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 ); +} + +/* +================= +CG_Init + +Called after every level change or subsystem restart +Will perform callbacks to make the loading info screen update. +================= +*/ +void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) +{ +  const char  *s; + +  // 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; + +  cgs.processedSnapshotNum = serverMessageNum; +  cgs.serverCommandSequence = serverCommandSequence; + +  // 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( ); +  BG_InitAllowedGameElements( ); + +  //TA: dyn memory +  CG_InitMemory( ); + +  CG_RegisterCvars( ); + +  CG_InitConsoleCommands( ); + +  //TA: moved up for LoadHudMenu +  String_Init( ); + +  //TA: TA UI +  CG_AssetCache( ); +  CG_LoadHudMenu( );      // load new hud stuff + +  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 ); + +  // check version +  s = CG_ConfigString( CS_GAME_VERSION ); + +  if( strcmp( s, GAME_VERSION ) ) +    CG_Error( "Client/Server game mismatch: %s/%s", GAME_VERSION, s ); + +  s = CG_ConfigString( CS_LEVEL_START_TIME ); +  cgs.levelStartTime = atoi( s ); + +  CG_ParseServerinfo( ); + +  // load the new map +  trap_CM_LoadMap( cgs.mapname ); + +  cg.loading = qtrue;   // force players to load instead of defer + +  CG_LoadTrailSystems( ); +  CG_UpdateMediaFraction( 0.05f ); + +  CG_LoadParticleSystems( ); +  CG_UpdateMediaFraction( 0.05f ); + +  CG_RegisterSounds( ); +  CG_UpdateMediaFraction( 0.60f ); + +  CG_RegisterGraphics( ); +  CG_UpdateMediaFraction( 0.90f ); + +  CG_InitWeapons( ); +  CG_UpdateMediaFraction( 0.95f ); + +  CG_InitUpgrades( ); +  CG_UpdateMediaFraction( 1.0f ); + +  //TA: +  CG_InitBuildables( ); + +  CG_RegisterClients( );   // if low on memory, some clients will be deferred + +  cg.loading = qfalse;  // future players will be deferred + +  CG_InitMarkPolys( ); + +  // remove the last loading update +  cg.infoScreenText[ 0 ] = 0; + +  // Make sure we have update values (scores) +  CG_SetConfigValues( ); + +  CG_StartMusic( ); + +  CG_ShaderStateChanged( ); + +  trap_S_ClearLoopingSounds( qtrue ); + +  trap_Cvar_Set( "ui_loading", "0" ); +} + +/* +================= +CG_Shutdown + +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 +} diff --git a/src/cgame/cg_marks.c b/src/cgame/cg_marks.c new file mode 100644 index 0000000..380f1f0 --- /dev/null +++ b/src/cgame/cg_marks.c @@ -0,0 +1,289 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +// cg_marks.c -- wall marks + + +#include "cg_local.h" + +/* +=================================================================== + +MARK POLYS + +=================================================================== +*/ + + +markPoly_t  cg_activeMarkPolys;     // double linked list +markPoly_t  *cg_freeMarkPolys;      // single linked list +markPoly_t  cg_markPolys[ MAX_MARK_POLYS ]; +static int  markTotal; + +/* +=================== +CG_InitMarkPolys + +This is called at startup and for tournement restarts +=================== +*/ +void CG_InitMarkPolys( void ) +{ +  int   i; + +  memset( cg_markPolys, 0, sizeof( cg_markPolys ) ); + +  cg_activeMarkPolys.nextMark = &cg_activeMarkPolys; +  cg_activeMarkPolys.prevMark = &cg_activeMarkPolys; +  cg_freeMarkPolys = cg_markPolys; + +  for( i = 0; i < MAX_MARK_POLYS - 1; i++ ) +    cg_markPolys[ i ].nextMark = &cg_markPolys[ i + 1 ]; +} + + +/* +================== +CG_FreeMarkPoly +================== +*/ +void CG_FreeMarkPoly( markPoly_t *le ) +{ +  if( !le->prevMark ) +    CG_Error( "CG_FreeLocalEntity: not active" ); + +  // remove from the doubly linked active list +  le->prevMark->nextMark = le->nextMark; +  le->nextMark->prevMark = le->prevMark; + +  // the free list is only singly linked +  le->nextMark = cg_freeMarkPolys; +  cg_freeMarkPolys = le; +} + +/* +=================== +CG_AllocMark + +Will allways succeed, even if it requires freeing an old active mark +=================== +*/ +markPoly_t *CG_AllocMark( void ) +{ +  markPoly_t  *le; +  int         time; + +  if( !cg_freeMarkPolys ) +  { +    // no free entities, so free the one at the end of the chain +    // remove the oldest active entity +    time = cg_activeMarkPolys.prevMark->time; + +    while( cg_activeMarkPolys.prevMark && time == cg_activeMarkPolys.prevMark->time ) +      CG_FreeMarkPoly( cg_activeMarkPolys.prevMark ); +  } + +  le = cg_freeMarkPolys; +  cg_freeMarkPolys = cg_freeMarkPolys->nextMark; + +  memset( le, 0, sizeof( *le ) ); + +  // link into the active list +  le->nextMark = cg_activeMarkPolys.nextMark; +  le->prevMark = &cg_activeMarkPolys; +  cg_activeMarkPolys.nextMark->prevMark = le; +  cg_activeMarkPolys.nextMark = le; +  return le; +} + + + +/* +================= +CG_ImpactMark + +origin should be a point within a unit of the plane +dir should be the plane normal + +temporary marks will not be stored or randomly oriented, but immediately +passed to the renderer. +================= +*/ +#define MAX_MARK_FRAGMENTS  128 +#define MAX_MARK_POINTS   384 + +void CG_ImpactMark( qhandle_t markShader, const vec3_t origin, const vec3_t dir, +                    float orientation, float red, float green, float blue, float alpha, +                    qboolean alphaFade, float radius, qboolean temporary ) +{ +  vec3_t          axis[ 3 ]; +  float           texCoordScale; +  vec3_t          originalPoints[ 4 ]; +  byte            colors[ 4 ]; +  int             i, j; +  int             numFragments; +  markFragment_t  markFragments[ MAX_MARK_FRAGMENTS ], *mf; +  vec3_t          markPoints[ MAX_MARK_POINTS ]; +  vec3_t          projection; + +  if( !cg_addMarks.integer ) +    return; + +  if( radius <= 0 ) +    CG_Error( "CG_ImpactMark called with <= 0 radius" ); + +  //if ( markTotal >= MAX_MARK_POLYS ) { +  //  return; +  //} + +  // create the texture axis +  VectorNormalize2( dir, axis[ 0 ] ); +  PerpendicularVector( axis[ 1 ], axis[ 0 ] ); +  RotatePointAroundVector( axis[ 2 ], axis[ 0 ], axis[ 1 ], orientation ); +  CrossProduct( axis[ 0 ], axis[ 2 ], axis[ 1 ] ); + +  texCoordScale = 0.5 * 1.0 / radius; + +  // create the full polygon +  for( i = 0; i < 3; i++ ) +  { +    originalPoints[ 0 ][ i ] = origin[ i ] - radius * axis[ 1 ][ i ] - radius * axis[ 2 ][ i ]; +    originalPoints[ 1 ][ i ] = origin[ i ] + radius * axis[ 1 ][ i ] - radius * axis[ 2 ][ i ]; +    originalPoints[ 2 ][ i ] = origin[ i ] + radius * axis[ 1 ][ i ] + radius * axis[ 2 ][ i ]; +    originalPoints[ 3 ][ i ] = origin[ i ] - radius * axis[ 1 ][ i ] + radius * axis[ 2 ][ i ]; +  } + +  // get the fragments +  VectorScale( dir, -20, projection ); +  numFragments = trap_CM_MarkFragments( 4, (void *)originalPoints, +          projection, MAX_MARK_POINTS, markPoints[ 0 ], +          MAX_MARK_FRAGMENTS, markFragments ); + +  colors[ 0 ] = red * 255; +  colors[ 1 ] = green * 255; +  colors[ 2 ] = blue * 255; +  colors[ 3 ] = alpha * 255; + +  for( i = 0, mf = markFragments; i < numFragments; i++, mf++ ) +  { +    polyVert_t  *v; +    polyVert_t  verts[ MAX_VERTS_ON_POLY ]; +    markPoly_t  *mark; + +    // we have an upper limit on the complexity of polygons +    // that we store persistantly +    if( mf->numPoints > MAX_VERTS_ON_POLY ) +      mf->numPoints = MAX_VERTS_ON_POLY; + +    for( j = 0, v = verts; j < mf->numPoints; j++, v++ ) +    { +      vec3_t    delta; + +      VectorCopy( markPoints[ mf->firstPoint + j ], v->xyz ); + +      VectorSubtract( v->xyz, origin, delta ); +      v->st[ 0 ] = 0.5 + DotProduct( delta, axis[ 1 ] ) * texCoordScale; +      v->st[ 1 ] = 0.5 + DotProduct( delta, axis[ 2 ] ) * texCoordScale; +      *(int *)v->modulate = *(int *)colors; +    } + +    // if it is a temporary (shadow) mark, add it immediately and forget about it +    if( temporary ) +    { +      trap_R_AddPolyToScene( markShader, mf->numPoints, verts ); +      continue; +    } + +    // otherwise save it persistantly +    mark = CG_AllocMark( ); +    mark->time = cg.time; +    mark->alphaFade = alphaFade; +    mark->markShader = markShader; +    mark->poly.numVerts = mf->numPoints; +    mark->color[ 0 ] = red; +    mark->color[ 1 ] = green; +    mark->color[ 2 ] = blue; +    mark->color[ 3 ] = alpha; +    memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[ 0 ] ) ); +    markTotal++; +  } +} + + +/* +=============== +CG_AddMarks +=============== +*/ +#define MARK_TOTAL_TIME   10000 +#define MARK_FADE_TIME    1000 + +void CG_AddMarks( void ) +{ +  int         j; +  markPoly_t  *mp, *next; +  int         t; +  int         fade; + +  if( !cg_addMarks.integer ) +    return; + +  mp = cg_activeMarkPolys.nextMark; +  for ( ; mp != &cg_activeMarkPolys; mp = next ) +  { +    // grab next now, so if the local entity is freed we +    // still have it +    next = mp->nextMark; + +    // see if it is time to completely remove it +    if( cg.time > mp->time + MARK_TOTAL_TIME ) +    { +      CG_FreeMarkPoly( mp ); +      continue; +    } + +    // fade all marks out with time +    t = mp->time + MARK_TOTAL_TIME - cg.time; +    if( t < MARK_FADE_TIME ) +    { +      fade = 255 * t / MARK_FADE_TIME; +      if( mp->alphaFade ) +      { +        for( j = 0; j < mp->poly.numVerts; j++ ) +          mp->verts[ j ].modulate[ 3 ] = fade; +      } +      else +      { +        for( j = 0; j < mp->poly.numVerts; j++ ) +        { +          mp->verts[ j ].modulate[ 0 ] = mp->color[ 0 ] * fade; +          mp->verts[ j ].modulate[ 1 ] = mp->color[ 1 ] * fade; +          mp->verts[ j ].modulate[ 2 ] = mp->color[ 2 ] * fade; +        } +      } +    } + +    trap_R_AddPolyToScene( mp->markShader, mp->poly.numVerts, mp->verts ); +  } +} + diff --git a/src/cgame/cg_mem.c b/src/cgame/cg_mem.c new file mode 100644 index 0000000..6cf5ddd --- /dev/null +++ b/src/cgame/cg_mem.c @@ -0,0 +1,202 @@ +/* +=========================================================================== +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 new file mode 100644 index 0000000..80c4b23 --- /dev/null +++ b/src/cgame/cg_particles.c @@ -0,0 +1,2567 @@ +/* +=========================================================================== +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_particles.c -- the particle system + + +#include "cg_local.h" + +static baseParticleSystem_t   baseParticleSystems[ MAX_BASEPARTICLE_SYSTEMS ]; +static baseParticleEjector_t  baseParticleEjectors[ MAX_BASEPARTICLE_EJECTORS ]; +static baseParticle_t         baseParticles[ MAX_BASEPARTICLES ]; +static int                    numBaseParticleSystems = 0; +static int                    numBaseParticleEjectors = 0; +static int                    numBaseParticles = 0; + +static particleSystem_t     particleSystems[ MAX_PARTICLE_SYSTEMS ]; +static particleEjector_t    particleEjectors[ MAX_PARTICLE_EJECTORS ]; +static particle_t           particles[ MAX_PARTICLES ]; +static particle_t           *sortedParticles[ MAX_PARTICLES ]; +static particle_t           *radixBuffer[ MAX_PARTICLES ]; + +/* +=============== +CG_LerpValues + +Lerp between two values +=============== +*/ +static float CG_LerpValues( float a, float b, float f ) +{ +  if( b == PARTICLES_SAME_AS_INITIAL ) +    return a; +  else +    return ( (a) + (f) * ( (b) - (a) ) ); +} + +/* +=============== +CG_RandomiseValue + +Randomise some value by some variance +=============== +*/ +static float CG_RandomiseValue( float value, float variance ) +{ +  if( value != 0.0f ) +    return value * ( 1.0f + ( random( ) * variance ) ); +  else +    return random( ) * variance; +} + +/* +=============== +CG_SpreadVector + +Randomly spread a vector by some amount +=============== +*/ +static void CG_SpreadVector( vec3_t v, float spread ) +{ +  vec3_t  p, r1, r2; +  float   randomSpread = crandom( ) * spread; +  float   randomRotation = random( ) * 360.0f; + +  PerpendicularVector( p, v ); + +  RotatePointAroundVector( r1, p, v, randomSpread ); +  RotatePointAroundVector( r2, v, r1, randomRotation ); + +  VectorCopy( r2, v ); +} + +/* +=============== +CG_DestroyParticle + +Destroy an individual particle +=============== +*/ +static void CG_DestroyParticle( particle_t *p, vec3_t impactNormal ) +{ +  //this particle has an onDeath particle system attached +  if( p->class->onDeathSystemName[ 0 ] != '\0' ) +  { +    particleSystem_t  *ps; + +    ps = CG_SpawnNewParticleSystem( p->class->onDeathSystemHandle ); + +    if( CG_IsParticleSystemValid( &ps ) ) +    { +      if( impactNormal ) +        CG_SetParticleSystemNormal( ps, impactNormal ); + +      CG_SetAttachmentPoint( &ps->attachment, p->origin ); +      CG_AttachToPoint( &ps->attachment ); +    } +  } + +  p->valid = qfalse; + +  //this gives other systems a couple of +  //frames to realise the particle is gone +  p->frameWhenInvalidated = cg.clientFrame; +} + +/* +=============== +CG_SpawnNewParticle + +Introduce a new particle into the world +=============== +*/ +static particle_t *CG_SpawnNewParticle( baseParticle_t *bp, particleEjector_t *parent ) +{ +  int                     i, j; +  particle_t              *p = NULL; +  particleEjector_t       *pe = parent; +  particleSystem_t        *ps = parent->parent; +  vec3_t                  attachmentPoint, attachmentVelocity; +  vec3_t                  transform[ 3 ]; + +  for( i = 0; i < MAX_PARTICLES; i++ ) +  { +    p = &particles[ i ]; + +    //FIXME: the + 1 may be unnecessary +    if( !p->valid && cg.clientFrame > p->frameWhenInvalidated + 1 ) +    { +      memset( p, 0, sizeof( particle_t ) ); + +      //found a free slot +      p->class = bp; +      p->parent = pe; + +      p->birthTime = cg.time; +      p->lifeTime = (int)CG_RandomiseValue( (float)bp->lifeTime, bp->lifeTimeRandFrac ); + +      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->alpha.delay = (int)CG_RandomiseValue( (float)bp->alpha.delay, bp->alpha.delayRandFrac ); +      p->alpha.initial = CG_RandomiseValue( bp->alpha.initial, bp->alpha.initialRandFrac ); +      p->alpha.final = CG_RandomiseValue( bp->alpha.final, bp->alpha.finalRandFrac ); + +      p->rotation.delay = (int)CG_RandomiseValue( (float)bp->rotation.delay, bp->rotation.delayRandFrac ); +      p->rotation.initial = CG_RandomiseValue( bp->rotation.initial, bp->rotation.initialRandFrac ); +      p->rotation.final = CG_RandomiseValue( bp->rotation.final, bp->rotation.finalRandFrac ); + +      p->dLightRadius.delay = +        (int)CG_RandomiseValue( (float)bp->dLightRadius.delay, bp->dLightRadius.delayRandFrac ); +      p->dLightRadius.initial = +        CG_RandomiseValue( bp->dLightRadius.initial, bp->dLightRadius.initialRandFrac ); +      p->dLightRadius.final = +        CG_RandomiseValue( bp->dLightRadius.final, bp->dLightRadius.finalRandFrac ); + +      p->colorDelay = CG_RandomiseValue( bp->colorDelay, bp->colorDelayRandFrac ); + +      p->bounceMarkRadius = CG_RandomiseValue( bp->bounceMarkRadius, bp->bounceMarkRadiusRandFrac ); +      p->bounceMarkCount = +        rint( CG_RandomiseValue( (float)bp->bounceMarkCount, bp->bounceMarkCountRandFrac ) ); +      p->bounceSoundCount = +        rint( CG_RandomiseValue( (float)bp->bounceSoundCount, bp->bounceSoundCountRandFrac ) ); + +      if( bp->numModels ) +      { +        p->model = bp->models[ rand( ) % bp->numModels ]; + +        if( bp->modelAnimation.frameLerp < 0 ) +        { +          bp->modelAnimation.frameLerp = p->lifeTime / bp->modelAnimation.numFrames; +          bp->modelAnimation.initialLerp = p->lifeTime / bp->modelAnimation.numFrames; +        } +      } + +      if( !CG_AttachmentPoint( &ps->attachment, attachmentPoint ) ) +        return NULL; + +      VectorCopy( attachmentPoint, p->origin ); + +      if( CG_AttachmentAxis( &ps->attachment, transform ) ) +      { +        vec3_t  transDisplacement; + +        VectorMatrixMultiply( bp->displacement, transform, transDisplacement ); +        VectorAdd( p->origin, transDisplacement, p->origin ); +      } +      else +        VectorAdd( p->origin, bp->displacement, p->origin ); + +      for( j = 0; j <= 2; j++ ) +        p->origin[ j ] += ( crandom( ) * bp->randDisplacement ); + +      switch( bp->velMoveType ) +      { +        case PMT_STATIC: +          if( bp->velMoveValues.dirType == PMD_POINT ) +            VectorSubtract( bp->velMoveValues.point, p->origin, p->velocity ); +          else if( bp->velMoveValues.dirType == PMD_LINEAR ) +            VectorCopy( bp->velMoveValues.dir, p->velocity ); +          break; + +        case PMT_STATIC_TRANSFORM: +          if( !CG_AttachmentAxis( &ps->attachment, transform ) ) +            return NULL; + +          if( bp->velMoveValues.dirType == PMD_POINT ) +          { +            vec3_t transPoint; + +            VectorMatrixMultiply( bp->velMoveValues.point, transform, transPoint ); +            VectorSubtract( transPoint, p->origin, p->velocity ); +          } +          else if( bp->velMoveValues.dirType == PMD_LINEAR ) +            VectorMatrixMultiply( bp->velMoveValues.dir, transform, p->velocity ); +          break; + +        case PMT_TAG: +        case PMT_CENT_ANGLES: +          if( bp->velMoveValues.dirType == PMD_POINT ) +            VectorSubtract( attachmentPoint, p->origin, p->velocity ); +          else if( bp->velMoveValues.dirType == PMD_LINEAR ) +          { +            if( !CG_AttachmentDir( &ps->attachment, p->velocity ) ) +              return NULL; +          } +          break; + +        case PMT_NORMAL: +          if( !ps->normalValid ) +          { +            CG_Printf( S_COLOR_RED "ERROR: a particle with velocityType " +                "normal has no normal\n" ); +            return NULL; +          } + +          VectorCopy( ps->normal, p->velocity ); + +          //normal displacement +          VectorNormalize( p->velocity ); +          VectorMA( p->origin, bp->normalDisplacement, p->velocity, p->origin ); +          break; +      } + +      VectorNormalize( p->velocity ); +      CG_SpreadVector( p->velocity, bp->velMoveValues.dirRandAngle ); +      VectorScale( p->velocity, +                   CG_RandomiseValue( bp->velMoveValues.mag, bp->velMoveValues.magRandFrac ), +                   p->velocity ); + +      if( CG_AttachmentVelocity( &ps->attachment, attachmentVelocity ) ) +      { +        VectorMA( p->velocity, +            CG_RandomiseValue( bp->velMoveValues.parentVelFrac, +              bp->velMoveValues.parentVelFracRandFrac ), attachmentVelocity, p->velocity ); +      } + +      p->lastEvalTime = cg.time; + +      p->valid = qtrue; + +      //this particle has a child particle system attached +      if( bp->childSystemName[ 0 ] != '\0' ) +      { +        particleSystem_t  *ps = CG_SpawnNewParticleSystem( bp->childSystemHandle ); + +        if( CG_IsParticleSystemValid( &ps ) ) +        { +          CG_SetAttachmentParticle( &ps->attachment, p ); +          CG_AttachToParticle( &ps->attachment ); +        } +      } + +      //this particle has a child trail system attached +      if( bp->childTrailSystemName[ 0 ] != '\0' ) +      { +        trailSystem_t *ts = CG_SpawnNewTrailSystem( bp->childTrailSystemHandle ); + +        if( CG_IsTrailSystemValid( &ts ) ) +        { +          CG_SetAttachmentParticle( &ts->frontAttachment, p ); +          CG_AttachToParticle( &ts->frontAttachment ); +        } +      } + +      break; +    } +  } + +  return p; +} + + +/* +=============== +CG_SpawnNewParticles + +Check if there are any ejectors that should be +introducing new particles +=============== +*/ +static void CG_SpawnNewParticles( void ) +{ +  int                   i, j; +  particle_t            *p; +  particleSystem_t      *ps; +  particleEjector_t     *pe; +  baseParticleEjector_t *bpe; +  float                 lerpFrac; +  int                   count; + +  for( i = 0; i < MAX_PARTICLE_EJECTORS; i++ ) +  { +    pe = &particleEjectors[ i ]; +    ps = pe->parent; + +    if( pe->valid ) +    { +      //a non attached particle system can't make particles +      if( !CG_Attached( &ps->attachment ) ) +        continue; + +      bpe = particleEjectors[ i ].class; + +      //if this system is scheduled for removal don't make any new particles +      if( !ps->lazyRemove ) +      { +        while( pe->nextEjectionTime <= cg.time && +               ( pe->count > 0 || pe->totalParticles == PARTICLES_INFINITE ) ) +        { +          for( j = 0; j < bpe->numParticles; j++ ) +            CG_SpawnNewParticle( bpe->particles[ j ], pe ); + +          if( pe->count > 0 ) +            pe->count--; + +          //calculate next ejection time +          lerpFrac = 1.0 - ( (float)pe->count / (float)pe->totalParticles ); +          pe->nextEjectionTime = cg.time + (int)CG_RandomiseValue( +              CG_LerpValues( pe->ejectPeriod.initial, +                             pe->ejectPeriod.final, +                             lerpFrac ), +              pe->ejectPeriod.randFrac ); +        } +      } + +      if( pe->count == 0 || ps->lazyRemove ) +      { +        count = 0; + +        //wait for child particles to die before declaring this pe invalid +        for( j = 0; j < MAX_PARTICLES; j++ ) +        { +          p = &particles[ j ]; + +          if( p->valid && p->parent == pe ) +            count++; +        } + +        if( !count ) +          pe->valid = qfalse; +      } +    } +  } +} + + +/* +=============== +CG_SpawnNewParticleEjector + +Allocate a new particle ejector +=============== +*/ +static particleEjector_t *CG_SpawnNewParticleEjector( baseParticleEjector_t *bpe, +                                                      particleSystem_t *parent ) +{ +  int                     i; +  particleEjector_t       *pe = NULL; +  particleSystem_t        *ps = parent; + +  for( i = 0; i < MAX_PARTICLE_EJECTORS; i++ ) +  { +    pe = &particleEjectors[ i ]; + +    if( !pe->valid ) +    { +      memset( pe, 0, sizeof( particleEjector_t ) ); + +      //found a free slot +      pe->class = bpe; +      pe->parent = ps; + +      pe->ejectPeriod.initial = bpe->eject.initial; +      pe->ejectPeriod.final = bpe->eject.final; +      pe->ejectPeriod.randFrac = bpe->eject.randFrac; + +      pe->nextEjectionTime = cg.time + +        (int)CG_RandomiseValue( (float)bpe->eject.delay, bpe->eject.delayRandFrac ); +      pe->count = pe->totalParticles = +        (int)rint( CG_RandomiseValue( (float)bpe->totalParticles, bpe->totalParticlesRandFrac ) ); + +      pe->valid = qtrue; + +      if( cg_debugParticles.integer >= 1 ) +        CG_Printf( "PE %s created\n", ps->class->name ); + +      break; +    } +  } + +  return pe; +} + + +/* +=============== +CG_SpawnNewParticleSystem + +Allocate a new particle system +=============== +*/ +particleSystem_t *CG_SpawnNewParticleSystem( qhandle_t psHandle ) +{ +  int                   i, j; +  particleSystem_t      *ps = NULL; +  baseParticleSystem_t  *bps = &baseParticleSystems[ psHandle - 1 ]; + +  if( !bps->registered ) +  { +    CG_Printf( S_COLOR_RED "ERROR: a particle system has not been registered yet\n" ); +    return NULL; +  } + +  for( i = 0; i < MAX_PARTICLE_SYSTEMS; i++ ) +  { +    ps = &particleSystems[ i ]; + +    if( !ps->valid ) +    { +      memset( ps, 0, sizeof( particleSystem_t ) ); + +      //found a free slot +      ps->class = bps; + +      ps->valid = qtrue; +      ps->lazyRemove = qfalse; + +      for( j = 0; j < bps->numEjectors; j++ ) +        CG_SpawnNewParticleEjector( bps->ejectors[ j ], ps ); + +      if( cg_debugParticles.integer >= 1 ) +        CG_Printf( "PS %s created\n", bps->name ); + +      break; +    } +  } + +  return ps; +} + +/* +=============== +CG_RegisterParticleSystem + +Load the shaders required for a particle system +=============== +*/ +qhandle_t CG_RegisterParticleSystem( char *name ) +{ +  int                   i, j, k, l; +  baseParticleSystem_t  *bps; +  baseParticleEjector_t *bpe; +  baseParticle_t        *bp; + +  for( i = 0; i < MAX_BASEPARTICLE_SYSTEMS; i++ ) +  { +    bps = &baseParticleSystems[ i ]; + +    if( !Q_stricmpn( bps->name, name, MAX_QPATH ) ) +    { +      //already registered +      if( bps->registered ) +        return i + 1; + +      for( j = 0; j < bps->numEjectors; j++ ) +      { +        bpe = bps->ejectors[ j ]; + +        for( l = 0; l < bpe->numParticles; l++ ) +        { +          bp = bpe->particles[ l ]; + +          for( k = 0; k < bp->numFrames; k++ ) +            bp->shaders[ k ] = trap_R_RegisterShader( bp->shaderNames[ k ] ); + +          for( k = 0; k < bp->numModels; k++ ) +            bp->models[ k ] = trap_R_RegisterModel( bp->modelNames[ k ] ); + +          if( bp->bounceMarkName[ 0 ] != '\0' ) +            bp->bounceMark = trap_R_RegisterShader( bp->bounceMarkName ); + +          if( bp->bounceSoundName[ 0 ] != '\0' ) +            bp->bounceSound = trap_S_RegisterSound( bp->bounceSoundName, qfalse ); + +          //recursively register any children +          if( bp->childSystemName[ 0 ] != '\0' ) +          { +            //don't care about a handle for children since +            //the system deals with it +            CG_RegisterParticleSystem( bp->childSystemName ); +          } + +          if( bp->onDeathSystemName[ 0 ] != '\0' ) +          { +            //don't care about a handle for children since +            //the system deals with it +            CG_RegisterParticleSystem( bp->onDeathSystemName ); +          } + +          if( bp->childTrailSystemName[ 0 ] != '\0' ) +            bp->childTrailSystemHandle = CG_RegisterTrailSystem( bp->childTrailSystemName ); +        } +      } + +      if( cg_debugParticles.integer >= 1 ) +        CG_Printf( "Registered particle system %s\n", name ); + +      bps->registered = qtrue; + +      //avoid returning 0 +      return i + 1; +    } +  } + +  CG_Printf( S_COLOR_RED "ERROR: failed to register particle system %s\n", name ); +  return 0; +} + + +/* +=============== +CG_ParseValueAndVariance + +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, '~' ); + +  //variance included +  if( variancePtr ) +  { +    variancePtr[ 0 ] = '\0'; +    variancePtr++; + +    localValue = atof_neg( valueBuffer, allowNegative ); + +    varEndPointer = strchr( variancePtr, '%' ); + +    if( varEndPointer ) +    { +      varEndPointer[ 0 ] = '\0'; +      localVariance = atof_neg( variancePtr, qfalse ) / 100.0f; +    } +    else +    { +      if( localValue != 0.0f ) +        localVariance = atof_neg( variancePtr, qfalse ) / localValue; +      else +        localVariance = atof_neg( variancePtr, qfalse ); +    } +  } +  else +    localValue = atof_neg( valueBuffer, allowNegative ); + +  if( value != NULL ) +    *value = localValue; + +  if( variance != NULL ) +    *variance = localVariance; +} + +/* +=============== +CG_ParseColor +=============== +*/ +static qboolean CG_ParseColor( byte *c, char **text_p ) +{ +  char  *token; +  int   i; + +  for( i = 0; i <= 2; i++ ) +  { +    token = COM_Parse( text_p ); + +    if( !Q_stricmp( token, "" ) ) +      return qfalse; + +    c[ i ] = (int)( (float)0xFF * atof_neg( token, qfalse ) ); +  } + +  return qtrue; +} + +/* +=============== +CG_ParseParticle + +Parse a particle section +=============== +*/ +static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) +{ +  char  *token; +  float number, randFrac; +  int   i; + +  // read optional parameters +  while( 1 ) +  { +    token = COM_Parse( text_p ); + +    if( !token ) +      break; + +    if( !Q_stricmp( token, "" ) ) +      return qfalse; + +    if( !Q_stricmp( token, "bounce" ) ) +    { +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      if( !Q_stricmp( token, "cull" ) ) +      { +        bp->bounceCull = qtrue; + +        bp->bounceFrac = -1.0f; +        bp->bounceFracRandFrac = 0.0f; +      } +      else +      { +        CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + +        bp->bounceFrac = number; +        bp->bounceFracRandFrac = randFrac; +      } + +      continue; +    } +    else if( !Q_stricmp( token, "bounceMark" ) ) +    { +      token = COM_Parse( text_p ); +      if( !*token ) +        break; + +      CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + +      bp->bounceMarkCount = number; +      bp->bounceMarkCountRandFrac = randFrac; + +      token = COM_Parse( text_p ); +      if( !*token ) +        break; + +      CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + +      bp->bounceMarkRadius = number; +      bp->bounceMarkRadiusRandFrac = randFrac; + +      token = COM_ParseExt( text_p, qfalse ); +      if( !*token ) +        break; + +      Q_strncpyz( bp->bounceMarkName, token, MAX_QPATH ); + +      continue; +    } +    else if( !Q_stricmp( token, "bounceSound" ) ) +    { +      token = COM_Parse( text_p ); +      if( !*token ) +        break; + +      CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + +      bp->bounceSoundCount = number; +      bp->bounceSoundCountRandFrac = randFrac; + +      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 ); +        break; +      } + +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      if( !Q_stricmp( token, "sync" ) ) +        bp->framerate = 0.0f; +      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; +    } +    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 ); +        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; +    } +    else if( !Q_stricmp( token, "modelAnimation" ) ) +    { +      token = COM_Parse( text_p ); +      if( !*token ) +        break; + +      bp->modelAnimation.firstFrame = atoi_neg( token, qfalse ); + +      token = COM_Parse( text_p ); +      if( !*token ) +        break; + +      bp->modelAnimation.numFrames = atoi( token ); +      bp->modelAnimation.reversed = qfalse; +      bp->modelAnimation.flipflop = qfalse; + +      // if numFrames is negative the animation is reversed +      if( bp->modelAnimation.numFrames < 0 ) +      { +        bp->modelAnimation.numFrames = -bp->modelAnimation.numFrames; +        bp->modelAnimation.reversed = qtrue; +      } + +      token = COM_Parse( text_p ); +      if( !*token ) +        break; + +      bp->modelAnimation.loopFrames = atoi( token ); + +      token = COM_Parse( text_p ); +      if( !*token ) +        break; + +      if( !Q_stricmp( token, "sync" ) ) +      { +        bp->modelAnimation.frameLerp = -1; +        bp->modelAnimation.initialLerp = -1; +      } +      else +      { +        float fps = atof_neg( token, qfalse ); + +        if( fps == 0.0f ) +          fps = 1.0f; + +        bp->modelAnimation.frameLerp = 1000 / fps; +        bp->modelAnimation.initialLerp = 1000 / fps; +      } + +      continue; +    } +    /// +    else if( !Q_stricmp( token, "velocityType" ) ) +    { +      token = COM_Parse( text_p ); +      if( !token ) +        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 ) +        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 ) +        break; + +      CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + +      bp->velMoveValues.mag = number; +      bp->velMoveValues.magRandFrac = randFrac; + +      continue; +    } +    else if( !Q_stricmp( token, "parentVelocityFraction" ) ) +    { +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + +      bp->velMoveValues.parentVelFrac = number; +      bp->velMoveValues.parentVelFracRandFrac = randFrac; + +      continue; +    } +    else if( !Q_stricmp( token, "velocity" ) ) +    { +      for( i = 0; i <= 2; i++ ) +      { +        token = COM_Parse( text_p ); +        if( !token ) +          break; + +        bp->velMoveValues.dir[ i ] = atof_neg( token, qtrue ); +      } + +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse ); + +      bp->velMoveValues.dirRandAngle = randFrac; + +      continue; +    } +    else if( !Q_stricmp( token, "velocityPoint" ) ) +    { +      for( i = 0; i <= 2; i++ ) +      { +        token = COM_Parse( text_p ); +        if( !token ) +          break; + +        bp->velMoveValues.point[ i ] = atof_neg( token, qtrue ); +      } + +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse ); + +      bp->velMoveValues.pointRandAngle = randFrac; + +      continue; +    } +    /// +    else if( !Q_stricmp( token, "accelerationType" ) ) +    { +      token = COM_Parse( text_p ); +      if( !token ) +        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 ) +        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 ) +        break; + +      CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + +      bp->accMoveValues.mag = number; +      bp->accMoveValues.magRandFrac = randFrac; + +      continue; +    } +    else if( !Q_stricmp( token, "acceleration" ) ) +    { +      for( i = 0; i <= 2; i++ ) +      { +        token = COM_Parse( text_p ); +        if( !token ) +          break; + +        bp->accMoveValues.dir[ i ] = atof_neg( token, qtrue ); +      } + +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse ); + +      bp->accMoveValues.dirRandAngle = randFrac; + +      continue; +    } +    else if( !Q_stricmp( token, "accelerationPoint" ) ) +    { +      for( i = 0; i <= 2; i++ ) +      { +        token = COM_Parse( text_p ); +        if( !token ) +          break; + +        bp->accMoveValues.point[ i ] = atof_neg( token, qtrue ); +      } + +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse ); + +      bp->accMoveValues.pointRandAngle = randFrac; + +      continue; +    } +    /// +    else if( !Q_stricmp( token, "displacement" ) ) +    { +      for( i = 0; i <= 2; i++ ) +      { +        token = COM_Parse( text_p ); +        if( !token ) +          break; + +        bp->displacement[ i ] = atof_neg( token, qtrue ); +      } + +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse ); + +      bp->randDisplacement = randFrac; + +      continue; +    } +    else if( !Q_stricmp( token, "normalDisplacement" ) ) +    { +      token = COM_Parse( text_p ); +      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" ) ) +    { +      bp->dynamicLight = qtrue; + +      token = COM_Parse( text_p ); +      if( !*token ) +        break; + +      CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + +      bp->dLightRadius.delay = (int)number; +      bp->dLightRadius.delayRandFrac = randFrac; + +      token = COM_Parse( text_p ); +      if( !*token ) +        break; + +      CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + +      bp->dLightRadius.initial = number; +      bp->dLightRadius.initialRandFrac = randFrac; + +      token = COM_Parse( text_p ); +      if( !*token ) +        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; + +      if( !Q_stricmp( token, "{" ) ) +      { +        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 ) +        break; + +      CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + +      bp->radius.delay = (int)number; +      bp->radius.delayRandFrac = randFrac; + +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + +      bp->radius.initial = number; +      bp->radius.initialRandFrac = randFrac; + +      token = COM_Parse( text_p ); +      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; +    } +    else if( !Q_stricmp( token, "alpha" ) ) +    { +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + +      bp->alpha.delay = (int)number; +      bp->alpha.delayRandFrac = randFrac; + +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + +      bp->alpha.initial = number; +      bp->alpha.initialRandFrac = randFrac; + +      token = COM_Parse( text_p ); +      if( !token ) +        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" ) ) +    { +      token = COM_Parse( text_p ); +      if( !*token ) +        break; + +      CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + +      bp->colorDelay = (int)number; +      bp->colorDelayRandFrac = randFrac; + +      token = COM_Parse( text_p ); +      if( !*token ) +        break; + +      if( !Q_stricmp( token, "{" ) ) +      { +        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 ]; +        } +        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 +        { +          CG_Printf( S_COLOR_RED "ERROR: missing '{'\n" ); +          break; +        } +      } +      else +      { +        CG_Printf( S_COLOR_RED "ERROR: missing '{'\n" ); +        break; +      } + +      continue; +    } +    else if( !Q_stricmp( token, "rotation" ) ) +    { +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + +      bp->rotation.delay = (int)number; +      bp->rotation.delayRandFrac = randFrac; + +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      CG_ParseValueAndVariance( token, &number, &randFrac, qtrue ); + +      bp->rotation.initial = number; +      bp->rotation.initialRandFrac = randFrac; + +      token = COM_Parse( text_p ); +      if( !token ) +        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 ) +        break; + +      CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + +      bp->lifeTime = (int)number; +      bp->lifeTimeRandFrac = randFrac; + +      continue; +    } +    else if( !Q_stricmp( token, "childSystem" ) ) +    { +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      Q_strncpyz( bp->childSystemName, token, MAX_QPATH ); + +      continue; +    } +    else if( !Q_stricmp( token, "onDeathSystem" ) ) +    { +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      Q_strncpyz( bp->onDeathSystemName, token, MAX_QPATH ); + +      continue; +    } +    else if( !Q_stricmp( token, "childTrailSystem" ) ) +    { +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      Q_strncpyz( bp->childTrailSystemName, token, MAX_QPATH ); + +      continue; +    } +    else if( !Q_stricmp( token, "}" ) ) +      return qtrue; //reached the end of this particle +    else +    { +      CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in particle\n", token ); +      return qfalse; +    } +  } + +  return qfalse; +} + +/* +=============== +CG_InitialiseBaseParticle +=============== +*/ +static void CG_InitialiseBaseParticle( baseParticle_t *bp ) +{ +  memset( bp, 0, sizeof( baseParticle_t ) ); + +  memset( bp->initialColor, 0xFF, sizeof( bp->initialColor ) ); +  memset( bp->finalColor, 0xFF, sizeof( bp->finalColor ) ); +} + +/* +=============== +CG_ParseParticleEjector + +Parse a particle ejector section +=============== +*/ +static qboolean CG_ParseParticleEjector( baseParticleEjector_t *bpe, char **text_p ) +{ +  char  *token; +  float number, randFrac; + +  // read optional parameters +  while( 1 ) +  { +    token = COM_Parse( text_p ); + +    if( !token ) +      break; + +    if( !Q_stricmp( token, "" ) ) +      return qfalse; + +    if( !Q_stricmp( token, "{" ) ) +    { +      CG_InitialiseBaseParticle( &baseParticles[ numBaseParticles ] ); + +      if( !CG_ParseParticle( &baseParticles[ numBaseParticles ], text_p ) ) +      { +        CG_Printf( S_COLOR_RED "ERROR: failed to parse particle\n" ); +        return qfalse; +      } + +      if( bpe->numParticles == MAX_PARTICLES_PER_EJECTOR ) +      { +        CG_Printf( S_COLOR_RED "ERROR: ejector has > %d particles\n", MAX_PARTICLES_PER_EJECTOR ); +        return qfalse; +      } +      else 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; +    } +    else if( !Q_stricmp( token, "delay" ) ) +    { +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + +      bpe->eject.delay = (int)number; +      bpe->eject.delayRandFrac = randFrac; + +      continue; +    } +    else if( !Q_stricmp( token, "period" ) ) +    { +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      bpe->eject.initial = atoi_neg( token, qfalse ); + +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      if( !Q_stricmp( token, "-" ) ) +        bpe->eject.final = PARTICLES_SAME_AS_INITIAL; +      else +        bpe->eject.final = atoi_neg( token, qfalse ); + +      token = COM_Parse( text_p ); +      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 ) +        break; + +      if( !Q_stricmp( token, "infinite" ) ) +      { +        bpe->totalParticles = PARTICLES_INFINITE; +        bpe->totalParticlesRandFrac = 0.0f; +      } +      else +      { +        CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + +        bpe->totalParticles = (int)number; +        bpe->totalParticlesRandFrac = randFrac; +      } + +      continue; +    } +    else if( !Q_stricmp( token, "particle" ) ) //acceptable text +      continue; +    else if( !Q_stricmp( token, "}" ) ) +      return qtrue; //reached the end of this particle ejector +    else +    { +      CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in particle ejector\n", token ); +      return qfalse; +    } +  } + +  return qfalse; +} + + +/* +=============== +CG_ParseParticleSystem + +Parse a particle system section +=============== +*/ +static qboolean CG_ParseParticleSystem( baseParticleSystem_t *bps, char **text_p, const char *name ) +{ +  char                  *token; +  baseParticleEjector_t *bpe; + +  // read optional parameters +  while( 1 ) +  { +    token = COM_Parse( text_p ); + +    if( !token ) +      break; + +    if( !Q_stricmp( token, "" ) ) +      return qfalse; + +    if( !Q_stricmp( token, "{" ) ) +    { +      if( !CG_ParseParticleEjector( &baseParticleEjectors[ numBaseParticleEjectors ], text_p ) ) +      { +        CG_Printf( S_COLOR_RED "ERROR: failed to parse particle ejector\n" ); +        return qfalse; +      } + +      bpe = &baseParticleEjectors[ numBaseParticleEjectors ]; + +      //check for infinite count + zero period +      if( bpe->totalParticles == PARTICLES_INFINITE && +          ( bpe->eject.initial == 0.0f || bpe->eject.final == 0.0f ) ) +      { +        CG_Printf( S_COLOR_RED "ERROR: ejector with 'count infinite' potentially has zero period\n" ); +        return qfalse; +      } + +      if( bps->numEjectors == MAX_EJECTORS_PER_SYSTEM ) +      { +        CG_Printf( S_COLOR_RED "ERROR: particle system has > %d ejectors\n", MAX_EJECTORS_PER_SYSTEM ); +        return qfalse; +      } +      else 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; +    } +    else if( !Q_stricmp( token, "thirdPersonOnly" ) ) +      bps->thirdPersonOnly = qtrue; +    else if( !Q_stricmp( token, "ejector" ) ) //acceptable text +      continue; +    else if( !Q_stricmp( token, "}" ) ) +    { +      if( cg_debugParticles.integer >= 1 ) +        CG_Printf( "Parsed particle system %s\n", name ); + +      return qtrue; //reached the end of this particle system +    } +    else +    { +      CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in particle system %s\n", token, bps->name ); +      return qfalse; +    } +  } + +  return qfalse; +} + +/* +=============== +CG_ParseParticleFile + +Load the particle systems from a particle file +=============== +*/ +static qboolean CG_ParseParticleFile( const char *fileName ) +{ +  char          *text_p; +  int           i; +  int           len; +  char          *token; +  char          text[ 32000 ]; +  char          psName[ MAX_QPATH ]; +  qboolean      psNameSet = qfalse; +  fileHandle_t  f; + +  // load the file +  len = trap_FS_FOpenFile( fileName, &f, FS_READ ); +  if( len <= 0 ) +    return qfalse; + +  if( len >= sizeof( text ) - 1 ) +  { +    CG_Printf( S_COLOR_RED "ERROR: particle file %s too long\n", fileName ); +    return qfalse; +  } + +  trap_FS_Read( text, len, f ); +  text[ len ] = 0; +  trap_FS_FCloseFile( f ); + +  // parse the text +  text_p = text; + +  // read optional parameters +  while( 1 ) +  { +    token = COM_Parse( &text_p ); + +    if( !Q_stricmp( 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 ) ) +        { +          CG_Printf( S_COLOR_RED "ERROR: %s: failed to parse particle system %s\n", fileName, psName ); +          return qfalse; +        } + +        //start parsing particle systems again +        psNameSet = qfalse; + +        if( numBaseParticleSystems == MAX_BASEPARTICLE_SYSTEMS ) +        { +          CG_Printf( S_COLOR_RED "ERROR: maximum number of particle systems (%d) reached\n", +              MAX_BASEPARTICLE_SYSTEMS ); +          return qfalse; +        } +        else +          numBaseParticleSystems++; + +        continue; +      } +      else +      { +        CG_Printf( S_COLOR_RED "ERROR: unamed particle system\n" ); +        return qfalse; +      } +    } + +    if( !psNameSet ) +    { +      Q_strncpyz( psName, token, sizeof( psName ) ); +      psNameSet = qtrue; +    } +    else +    { +      CG_Printf( S_COLOR_RED "ERROR: particle system already named\n" ); +      return qfalse; +    } +  } + +  return qtrue; +} + + +/* +=============== +CG_LoadParticleSystems + +Load particle systems from .particle files +=============== +*/ +void CG_LoadParticleSystems( void ) +{ +  int   i, j, numFiles, fileLen; +  char  fileList[ MAX_PARTICLE_FILES * MAX_QPATH ]; +  char  fileName[ MAX_QPATH ]; +  char  *filePtr; + +  //clear out the old +  numBaseParticleSystems = 0; +  numBaseParticleEjectors = 0; +  numBaseParticles = 0; + +  for( i = 0; i < MAX_BASEPARTICLE_SYSTEMS; i++ ) +  { +    baseParticleSystem_t  *bps = &baseParticleSystems[ i ]; +    memset( bps, 0, sizeof( baseParticleSystem_t ) ); +  } + +  for( i = 0; i < MAX_BASEPARTICLE_EJECTORS; i++ ) +  { +    baseParticleEjector_t  *bpe = &baseParticleEjectors[ i ]; +    memset( bpe, 0, sizeof( baseParticleEjector_t ) ); +  } + +  for( i = 0; i < MAX_BASEPARTICLES; i++ ) +  { +    baseParticle_t  *bp = &baseParticles[ i ]; +    memset( bp, 0, sizeof( baseParticle_t ) ); +  } + + +  //and bring in the new +  numFiles = trap_FS_GetFileList( "scripts", ".particle", +      fileList, MAX_PARTICLE_FILES * MAX_QPATH ); +  filePtr = fileList; + +  for( i = 0; i < numFiles; i++, filePtr += fileLen + 1 ) +  { +    fileLen = strlen( filePtr ); +    strcpy( fileName, "scripts/" ); +    strcat( fileName, filePtr ); +    CG_Printf( "...loading '%s'\n", fileName ); +    CG_ParseParticleFile( fileName ); +  } + +  //connect any child systems to their psHandle +  for( i = 0; i < numBaseParticles; i++ ) +  { +    baseParticle_t  *bp = &baseParticles[ i ]; + +    if( bp->childSystemName[ 0 ] ) +    { +      //particle class has a child, resolve the name +      for( j = 0; j < numBaseParticleSystems; j++ ) +      { +        baseParticleSystem_t  *bps = &baseParticleSystems[ j ]; + +        if( !Q_stricmp( bps->name, bp->childSystemName ) ) +        { +          //FIXME: add checks for cycles and infinite children + +          bp->childSystemHandle = j + 1; + +          break; +        } +      } + +      if( j == numBaseParticleSystems ) +      { +        //couldn't find named particle system +        CG_Printf( S_COLOR_YELLOW "WARNING: failed to find child %s\n", bp->childSystemName ); +        bp->childSystemName[ 0 ] = '\0'; +      } +    } + +    if( bp->onDeathSystemName[ 0 ] ) +    { +      //particle class has a child, resolve the name +      for( j = 0; j < numBaseParticleSystems; j++ ) +      { +        baseParticleSystem_t  *bps = &baseParticleSystems[ j ]; + +        if( !Q_stricmp( bps->name, bp->onDeathSystemName ) ) +        { +          //FIXME: add checks for cycles and infinite children + +          bp->onDeathSystemHandle = j + 1; + +          break; +        } +      } + +      if( j == numBaseParticleSystems ) +      { +        //couldn't find named particle system +        CG_Printf( S_COLOR_YELLOW "WARNING: failed to find onDeath system %s\n", bp->onDeathSystemName ); +        bp->onDeathSystemName[ 0 ] = '\0'; +      } +    } +  } +} + +/* +=============== +CG_SetParticleSystemNormal +=============== +*/ +void CG_SetParticleSystemNormal( particleSystem_t *ps, vec3_t normal ) +{ +  if( ps == NULL || !ps->valid ) +  { +    CG_Printf( S_COLOR_YELLOW "WARNING: tried to modify a NULL particle system\n" ); +    return; +  } + +  ps->normalValid = qtrue; +  VectorCopy( normal, ps->normal ); +  VectorNormalize( ps->normal ); +} + + +/* +=============== +CG_DestroyParticleSystem + +Destroy a particle system + +This doesn't actually invalidate anything, it just stops +particle ejectors from producing new particles so the +garbage collector will eventually remove this system. +However is does set the pointer to NULL so the user is +unable to manipulate this particle system any longer. +=============== +*/ +void CG_DestroyParticleSystem( particleSystem_t **ps ) +{ +  int               i; +  particleEjector_t *pe; + +  if( *ps == NULL || !(*ps)->valid ) +  { +    CG_Printf( S_COLOR_YELLOW "WARNING: tried to destroy a NULL particle system\n" ); +    return; +  } + +  if( cg_debugParticles.integer >= 1 ) +    CG_Printf( "PS destroyed\n" ); + +  for( i = 0; i < MAX_PARTICLE_EJECTORS; i++ ) +  { +    pe = &particleEjectors[ i ]; + +    if( pe->valid && pe->parent == *ps ) +      pe->totalParticles = pe->count = 0; +  } + +  *ps = NULL; +} + +/* +=============== +CG_IsParticleSystemInfinite + +Test a particle system for 'count infinite' ejectors +=============== +*/ +qboolean CG_IsParticleSystemInfinite( particleSystem_t *ps ) +{ +  int               i; +  particleEjector_t *pe; + +  if( ps == NULL ) +  { +    CG_Printf( S_COLOR_YELLOW "WARNING: tried to test a NULL particle system\n" ); +    return qfalse; +  } + +  if( !ps->valid ) +  { +    CG_Printf( S_COLOR_YELLOW "WARNING: tried to test an invalid particle system\n" ); +    return qfalse; +  } + +  //don't bother checking already invalid systems +  if( !ps->valid ) +    return qfalse; + +  for( i = 0; i < MAX_PARTICLE_EJECTORS; i++ ) +  { +    pe = &particleEjectors[ i ]; + +    if( pe->valid && pe->parent == ps ) +    { +      if( pe->totalParticles == PARTICLES_INFINITE ) +        return qtrue; +    } +  } + +  return qfalse; +} + +/* +=============== +CG_IsParticleSystemValid + +Test a particle system for validity +=============== +*/ +qboolean CG_IsParticleSystemValid( particleSystem_t **ps ) +{ +  if( *ps == NULL || ( *ps && !(*ps)->valid ) ) +  { +    if( *ps && !(*ps)->valid ) +      *ps = NULL; + +    return qfalse; +  } + +  return qtrue; +} + +/* +=============== +CG_GarbageCollectParticleSystems + +Destroy inactive particle systems +=============== +*/ +static void CG_GarbageCollectParticleSystems( void ) +{ +  int               i, j, count; +  particleSystem_t  *ps; +  particleEjector_t *pe; +  int               centNum; + +  for( i = 0; i < MAX_PARTICLE_SYSTEMS; i++ ) +  { +    ps = &particleSystems[ i ]; +    count = 0; + +    //don't bother checking already invalid systems +    if( !ps->valid ) +      continue; + +    for( j = 0; j < MAX_PARTICLE_EJECTORS; j++ ) +    { +      pe = &particleEjectors[ j ]; + +      if( pe->valid && pe->parent == ps ) +        count++; +    } + +    if( !count ) +      ps->valid = qfalse; + +    //check systems where the parent cent has left the PVS +    //( local player entity is always valid ) +    if( ( centNum = CG_AttachmentCentNum( &ps->attachment ) ) >= 0 && +        centNum != cg.snap->ps.clientNum ) +    { +      if( !cg_entities[ centNum ].valid ) +        ps->lazyRemove = qtrue; +    } + +    if( cg_debugParticles.integer >= 1 && !ps->valid ) +      CG_Printf( "PS %s garbage collected\n", ps->class->name ); +  } +} + + +/* +=============== +CG_CalculateTimeFrac + +Calculate the fraction of time passed +=============== +*/ +static float CG_CalculateTimeFrac( int birth, int life, int delay ) +{ +  float frac; + +  frac = ( (float)cg.time - (float)( birth + delay ) ) / (float)( life - delay ); + +  if( frac < 0.0f ) +    frac = 0.0f; +  else if( frac > 1.0f ) +    frac = 1.0f; + +  return frac; +} + +/* +=============== +CG_EvaluateParticlePhysics + +Compute the physics on a specific particle +=============== +*/ +static void CG_EvaluateParticlePhysics( particle_t *p ) +{ +  particleSystem_t  *ps = p->parent->parent; +  baseParticle_t    *bp = p->class; +  vec3_t            acceleration, newOrigin; +  vec3_t            mins, maxs; +  float             deltaTime, bounce, radius, dot; +  trace_t           trace; +  vec3_t            transform[ 3 ]; + +  if( p->atRest ) +  { +    VectorClear( p->velocity ); +    return; +  } + +  switch( bp->accMoveType ) +  { +    case PMT_STATIC: +      if( bp->accMoveValues.dirType == PMD_POINT ) +        VectorSubtract( bp->accMoveValues.point, p->origin, acceleration ); +      else if( bp->accMoveValues.dirType == PMD_LINEAR ) +        VectorCopy( bp->accMoveValues.dir, acceleration ); + +      break; + +    case PMT_STATIC_TRANSFORM: +      if( !CG_AttachmentAxis( &ps->attachment, transform ) ) +        return; + +      if( bp->accMoveValues.dirType == PMD_POINT ) +      { +        vec3_t transPoint; + +        VectorMatrixMultiply( bp->accMoveValues.point, transform, transPoint ); +        VectorSubtract( transPoint, p->origin, acceleration ); +      } +      else if( bp->accMoveValues.dirType == PMD_LINEAR ) +        VectorMatrixMultiply( bp->accMoveValues.dir, transform, acceleration ); +      break; + +    case PMT_TAG: +    case PMT_CENT_ANGLES: +      if( bp->accMoveValues.dirType == PMD_POINT ) +      { +        vec3_t point; + +        if( !CG_AttachmentPoint( &ps->attachment, point ) ) +          return; + +        VectorSubtract( point, p->origin, acceleration ); +      } +      else if( bp->accMoveValues.dirType == PMD_LINEAR ) +      { +        if( !CG_AttachmentDir( &ps->attachment, acceleration ) ) +          return; +      } +      break; + +    case PMT_NORMAL: +      if( !ps->normalValid ) +        return; + +      VectorCopy( ps->normal, acceleration ); + +      break; +  } + +#define MAX_ACC_RADIUS 1000.0f + +  if( bp->accMoveValues.dirType == PMD_POINT ) +  { +    //FIXME: so this fall off is a bit... odd -- it works.. +    float r2 = DotProduct( acceleration, acceleration ); // = radius^2 +    float scale = ( MAX_ACC_RADIUS - r2 ) / MAX_ACC_RADIUS; + +    if( scale > 1.0f ) +      scale = 1.0f; +    else if( scale < 0.1f ) +      scale = 0.1f; + +    scale *= CG_RandomiseValue( bp->accMoveValues.mag, bp->accMoveValues.magRandFrac ); + +    VectorNormalize( acceleration ); +    CG_SpreadVector( acceleration, bp->accMoveValues.dirRandAngle ); +    VectorScale( acceleration, scale, acceleration ); +  } +  else if( bp->accMoveValues.dirType == PMD_LINEAR ) +  { +    VectorNormalize( acceleration ); +    CG_SpreadVector( acceleration, bp->accMoveValues.dirRandAngle ); +    VectorScale( acceleration, +                 CG_RandomiseValue( bp->accMoveValues.mag, bp->accMoveValues.magRandFrac ), +                 acceleration ); +  } + +  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 ); + +  bounce = CG_RandomiseValue( bp->bounceFrac, bp->bounceFracRandFrac ); + +  deltaTime = (float)( cg.time - p->lastEvalTime ) * 0.001; +  VectorMA( p->velocity, deltaTime, acceleration, p->velocity ); +  VectorMA( p->origin, deltaTime, p->velocity, newOrigin ); +  p->lastEvalTime = cg.time; + +  // we're not doing particle physics, but at least cull them in solids +  if( !cg_bounceParticles.integer ) +  { +    int contents = trap_CM_PointContents( newOrigin, 0 );  + +    if( ( contents & CONTENTS_SOLID ) || ( contents & CONTENTS_NODROP ) ) +      CG_DestroyParticle( p, NULL ); +    else  +      VectorCopy( newOrigin, p->origin ); +    return; +  } + +  CG_Trace( &trace, p->origin, mins, maxs, newOrigin, +      CG_AttachmentCentNum( &ps->attachment ), CONTENTS_SOLID ); + +  //not hit anything or not a collider +  if( trace.fraction == 1.0f || bounce == 0.0f ) +  { +    VectorCopy( newOrigin, p->origin ); +    return; +  } + +  //remove particles that get into a CONTENTS_NODROP brush +  if( ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) || +      ( bp->cullOnStartSolid && trace.startsolid ) ) +  { +    CG_DestroyParticle( p, NULL ); +    return; +  } +  else if( bp->bounceCull ) +  { +    CG_DestroyParticle( p, trace.plane.normal ); +    return; +  } + +  //reflect the velocity on the trace plane +  dot = DotProduct( p->velocity, trace.plane.normal ); +  VectorMA( p->velocity, -2.0f * dot, trace.plane.normal, p->velocity ); + +  VectorScale( p->velocity, bounce, p->velocity ); + +  if( trace.plane.normal[ 2 ] > 0.5f && +      ( p->velocity[ 2 ] < 40.0f || +        p->velocity[ 2 ] < -cg.frametime * p->velocity[ 2 ] ) ) +    p->atRest = qtrue; + +  if( bp->bounceMarkName[ 0 ] && p->bounceMarkCount > 0 ) +  { +    CG_ImpactMark( bp->bounceMark, trace.endpos, trace.plane.normal, +        random( ) * 360, 1, 1, 1, 1, qtrue, bp->bounceMarkRadius, qfalse ); +    p->bounceMarkCount--; +  } + +  if( bp->bounceSoundName[ 0 ] && p->bounceSoundCount > 0 ) +  { +    trap_S_StartSound( trace.endpos, ENTITYNUM_WORLD, CHAN_AUTO, bp->bounceSound ); +    p->bounceSoundCount--; +  } + +  VectorCopy( trace.endpos, p->origin ); +} + + +#define GETKEY(x,y) (((x)>>y)&0xFF) + +/* +=============== +CG_Radix +=============== +*/ +static void CG_Radix( int bits, int size, particle_t **source, particle_t **dest ) +{ +  int count[ 256 ]; +  int index[ 256 ]; +  int i; + +  memset( count, 0, sizeof( count ) ); + +  for( i = 0; i < size; i++ ) +    count[ GETKEY( source[ i ]->sortKey, bits ) ]++; + +  index[ 0 ] = 0; + +  for( i = 1; i < 256; i++ ) +    index[ i ] = index[ i - 1 ] + count[ i - 1 ]; + +  for( i = 0; i < size; i++ ) +    dest[ index[ GETKEY( source[ i ]->sortKey, bits ) ]++ ] = source[ i ]; +} + +/* +=============== +CG_RadixSort + +Radix sort with 4 byte size buckets +=============== +*/ +static void CG_RadixSort( particle_t **source, particle_t **temp, int size ) +{ +  CG_Radix( 0,   size, source, temp ); +  CG_Radix( 8,   size, temp, source ); +  CG_Radix( 16,  size, source, temp ); +  CG_Radix( 24,  size, temp, source ); +} + +/* +=============== +CG_CompactAndSortParticles + +Depth sort the particles +=============== +*/ +static void CG_CompactAndSortParticles( void ) +{ +  int     i, j = 0; +  int     numParticles; +  vec3_t  delta; + +  for( i = 0; i < MAX_PARTICLES; i++ ) +    sortedParticles[ i ] = &particles[ i ]; + +  if( !cg_depthSortParticles.integer ) +    return; + +  for( i = MAX_PARTICLES - 1; i >= 0; i-- ) +  { +    if( sortedParticles[ i ]->valid ) +    { +      //find the first hole +      while( j < MAX_PARTICLES && sortedParticles[ j ]->valid ) +        j++; + +      //no more holes +      if( j >= i ) +        break; + +      sortedParticles[ j ] = sortedParticles[ i ]; +    } +  } + +  numParticles = i; + +  //set sort keys +  for( i = 0; i < numParticles; i++ ) +  { +    VectorSubtract( sortedParticles[ i ]->origin, cg.refdef.vieworg, delta ); +    sortedParticles[ i ]->sortKey = (int)DotProduct( delta, delta ); +  } + +  CG_RadixSort( sortedParticles, radixBuffer, numParticles ); + +  //FIXME: wtf? +  //reverse order of particles array +  for( i = 0; i < numParticles; i++ ) +    radixBuffer[ i ] = sortedParticles[ numParticles - i - 1 ]; + +  for( i = 0; i < numParticles; i++ ) +    sortedParticles[ i ] = radixBuffer[ i ]; +} + +/* +=============== +CG_RenderParticle + +Actually render a particle +=============== +*/ +static void CG_RenderParticle( particle_t *p ) +{ +  refEntity_t           re; +  float                 timeFrac, scale; +  int                   index; +  baseParticle_t        *bp = p->class; +  particleSystem_t      *ps = p->parent->parent; +  baseParticleSystem_t  *bps = ps->class; +  vec3_t                alight, dlight, lightdir; +  int                   i; +  vec3_t                up = { 0.0f, 0.0f, 1.0f }; + +  memset( &re, 0, sizeof( refEntity_t ) ); + +  timeFrac = CG_CalculateTimeFrac( p->birthTime, p->lifeTime, 0 ); + +  scale = CG_LerpValues( p->radius.initial, +                    p->radius.final, +                    CG_CalculateTimeFrac( p->birthTime, +                                          p->lifeTime, +                                          p->radius.delay ) ); + +  re.shaderTime = p->birthTime / 1000.0f; + +  if( bp->numFrames )       //shader based +  { +    re.reType = RT_SPRITE; + +    //apply environmental lighting to the particle +    if( bp->realLight ) +    { +      trap_R_LightForPoint( p->origin, alight, dlight, lightdir ); +      for( i = 0; i <= 2; i++ ) +        re.shaderRGBA[ i ] = (byte)alight[ i ]; +    } +    else +    { +      vec3_t  colorRange; + +      VectorSubtract( bp->finalColor, +          bp->initialColor, colorRange ); + +      VectorMA( bp->initialColor, +          CG_CalculateTimeFrac( p->birthTime, +            p->lifeTime, +            p->colorDelay ), +          colorRange, re.shaderRGBA ); +    } + +    re.shaderRGBA[ 3 ] = (byte)( (float)0xFF * +                         CG_LerpValues( p->alpha.initial, +                               p->alpha.final, +                               CG_CalculateTimeFrac( p->birthTime, +                                                     p->lifeTime, +                                                     p->alpha.delay ) ) ); + +    re.radius = scale; + +    re.rotation = CG_LerpValues( p->rotation.initial, +                        p->rotation.final, +                        CG_CalculateTimeFrac( p->birthTime, +                                              p->lifeTime, +                                              p->rotation.delay ) ); + +    // if the view would be "inside" the sprite, kill the sprite +    // so it doesn't add too much overdraw +    if( Distance( p->origin, cg.refdef.vieworg ) < re.radius && bp->overdrawProtection ) +      return; + +    if( bp->framerate == 0.0f ) +    { +      //sync animation time to lifeTime of particle +      index = (int)( timeFrac * ( bp->numFrames + 1 ) ); + +      if( index >= bp->numFrames ) +        index = bp->numFrames - 1; + +      re.customShader = bp->shaders[ index ]; +    } +    else +    { +      //looping animation +      index = (int)( bp->framerate * timeFrac * p->lifeTime * 0.001 ) % bp->numFrames; +      re.customShader = bp->shaders[ index ]; +    } + +  } +  else if( bp->numModels )  //model based +  { +    re.reType = RT_MODEL; + +    re.hModel = p->model; + +    if( p->atRest ) +      AxisCopy( p->lastAxis, re.axis ); +    else +    { +      // convert direction of travel into axis +      VectorNormalize2( p->velocity, re.axis[ 0 ] ); + +      if( re.axis[ 0 ][ 0 ] == 0.0f && re.axis[ 0 ][ 1 ] == 0.0f ) +        AxisCopy( axisDefault, re.axis ); +      else +      { +        ProjectPointOnPlane( re.axis[ 2 ], up, re.axis[ 0 ] ); +        VectorNormalize( re.axis[ 2 ] ); +        CrossProduct( re.axis[ 2 ], re.axis[ 0 ], re.axis[ 1 ] ); +      } + +      AxisCopy( re.axis, p->lastAxis ); +    } + +    if( scale != 1.0f ) +    { +      VectorScale( re.axis[ 0 ], scale, re.axis[ 0 ] ); +      VectorScale( re.axis[ 1 ], scale, re.axis[ 1 ] ); +      VectorScale( re.axis[ 2 ], scale, re.axis[ 2 ] ); +      re.nonNormalizedAxes = qtrue; +    } +    else +      re.nonNormalizedAxes = qfalse; + +    p->lf.animation = &bp->modelAnimation; + +    //run animation +    CG_RunLerpFrame( &p->lf ); + +    re.oldframe = p->lf.oldFrame; +    re.frame    = p->lf.frame; +    re.backlerp = p->lf.backlerp; +  } + +  if( bps->thirdPersonOnly && +      CG_AttachmentCentNum( &ps->attachment ) == cg.snap->ps.clientNum && +      !cg.renderingThirdPerson ) +    re.renderfx |= RF_THIRD_PERSON; + +  if( bp->dynamicLight && !( re.renderfx & RF_THIRD_PERSON ) ) +  { +    trap_R_AddLightToScene( p->origin, +      CG_LerpValues( p->dLightRadius.initial, p->dLightRadius.final, +        CG_CalculateTimeFrac( p->birthTime, p->lifeTime, p->dLightRadius.delay ) ), +        (float)bp->dLightColor[ 0 ] / (float)0xFF, +        (float)bp->dLightColor[ 1 ] / (float)0xFF, +        (float)bp->dLightColor[ 2 ] / (float)0xFF ); +  } + +  VectorCopy( p->origin, re.origin ); + +  trap_R_AddRefEntityToScene( &re ); +} + +/* +=============== +CG_AddParticles + +Add particles to the scene +=============== +*/ +void CG_AddParticles( void ) +{ +  int           i; +  particle_t    *p; +  int           numPS = 0, numPE = 0, numP = 0; + +  //remove expired particle systems +  CG_GarbageCollectParticleSystems( ); + +  //check each ejector and introduce any new particles +  CG_SpawnNewParticles( ); + +  //sorting +  CG_CompactAndSortParticles( ); + +  for( i = 0; i < MAX_PARTICLES; i++ ) +  { +    p = sortedParticles[ i ]; + +    if( p->valid ) +    { +      if( p->birthTime + p->lifeTime > cg.time ) +      { +        //particle is active +        CG_EvaluateParticlePhysics( p ); +        CG_RenderParticle( p ); +      } +      else +        CG_DestroyParticle( p, NULL ); +    } +  } + +  if( cg_debugParticles.integer >= 2 ) +  { +    for( i = 0; i < MAX_PARTICLE_SYSTEMS; i++ ) +      if( particleSystems[ i ].valid ) +        numPS++; + +    for( i = 0; i < MAX_PARTICLE_EJECTORS; i++ ) +      if( particleEjectors[ i ].valid ) +        numPE++; + +    for( i = 0; i < MAX_PARTICLES; i++ ) +      if( particles[ i ].valid ) +        numP++; + +    CG_Printf( "PS: %d  PE: %d  P: %d\n", numPS, numPE, numP ); +  } +} + +/* +=============== +CG_ParticleSystemEntity + +Particle system entity client code +=============== +*/ +void CG_ParticleSystemEntity( centity_t *cent ) +{ +  entityState_t *es; + +  es = ¢->currentState; + +  if( es->eFlags & EF_NODRAW ) +  { +    if( CG_IsParticleSystemValid( ¢->entityPS ) && CG_IsParticleSystemInfinite( cent->entityPS ) ) +      CG_DestroyParticleSystem( ¢->entityPS ); + +    return; +  } + +  if( !CG_IsParticleSystemValid( ¢->entityPS ) && !cent->entityPSMissing ) +  { +    cent->entityPS = CG_SpawnNewParticleSystem( cgs.gameParticleSystems[ es->modelindex ] ); + +    if( CG_IsParticleSystemValid( ¢->entityPS ) ) +    { +      CG_SetAttachmentPoint( ¢->entityPS->attachment, cent->lerpOrigin ); +      CG_SetAttachmentCent( ¢->entityPS->attachment, cent ); +      CG_AttachToPoint( ¢->entityPS->attachment ); +    } +    else +      cent->entityPSMissing = qtrue; +  } +} + +static particleSystem_t *testPS; +static qhandle_t        testPSHandle; + +/* +=============== +CG_DestroyTestPS_f + +Destroy the test a particle system +=============== +*/ +void CG_DestroyTestPS_f( void ) +{ +  if( CG_IsParticleSystemValid( &testPS ) ) +    CG_DestroyParticleSystem( &testPS ); +} + +/* +=============== +CG_TestPS_f + +Test a particle system +=============== +*/ +void CG_TestPS_f( void ) +{ +  vec3_t  origin; +  vec3_t  up = { 0.0f, 0.0f, 1.0f }; +  char    psName[ MAX_QPATH ]; + +  if( trap_Argc( ) < 2 ) +    return; + +  Q_strncpyz( psName, CG_Argv( 1 ), MAX_QPATH ); +  testPSHandle = CG_RegisterParticleSystem( psName ); + +  if( testPSHandle ) +  { +    CG_DestroyTestPS_f( ); + +    testPS = CG_SpawnNewParticleSystem( testPSHandle ); + +    VectorMA( cg.refdef.vieworg, 100, cg.refdef.viewaxis[ 0 ], origin ); + +    if( CG_IsParticleSystemValid( &testPS ) ) +    { +      CG_SetAttachmentPoint( &testPS->attachment, origin ); +      CG_SetParticleSystemNormal( testPS, up ); +      CG_AttachToPoint( &testPS->attachment ); +    } +  } +} diff --git a/src/cgame/cg_players.c b/src/cgame/cg_players.c new file mode 100644 index 0000000..b594330 --- /dev/null +++ b/src/cgame/cg_players.c @@ -0,0 +1,2565 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +// cg_players.c -- handle the media and animation for player entities + + +#include "cg_local.h" + +char *cg_customSoundNames[ MAX_CUSTOM_SOUNDS ] = +{ +  "*death1.wav", +  "*death2.wav", +  "*death3.wav", +  "*jump1.wav", +  "*pain25_1.wav", +  "*pain50_1.wav", +  "*pain75_1.wav", +  "*pain100_1.wav", +  "*falling1.wav", +  "*gasp.wav", +  "*drown.wav", +  "*fall1.wav", +  "*taunt.wav" +}; + + +/* +================ +CG_CustomSound + +================ +*/ +sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ) +{ +  clientInfo_t  *ci; +  int           i; + +  if( soundName[ 0 ] != '*' ) +    return trap_S_RegisterSound( soundName, qfalse ); + +  if( clientNum < 0 || clientNum >= MAX_CLIENTS ) +    clientNum = 0; + +  ci = &cgs.clientinfo[ clientNum ]; + +  for( i = 0; i < MAX_CUSTOM_SOUNDS && cg_customSoundNames[ i ]; i++ ) +  { +    if( !strcmp( soundName, cg_customSoundNames[ i ] ) ) +      return ci->sounds[ i ]; +  } + +  CG_Error( "Unknown custom sound: %s", soundName ); +  return 0; +} + + + +/* +============================================================================= + +CLIENT INFO + +============================================================================= +*/ + +/* +====================== +CG_ParseAnimationFile + +Read a configuration file containing animation coutns and rates +models/players/visor/animation.cfg, etc +====================== +*/ +static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) +{ +  char          *text_p, *prev; +  int           len; +  int           i; +  char          *token; +  float         fps; +  int           skip; +  char          text[ 20000 ]; +  fileHandle_t  f; +  animation_t   *animations; + +  animations = ci->animations; + +  // load the file +  len = trap_FS_FOpenFile( filename, &f, FS_READ ); +  if( len <= 0 ) +    return qfalse; + +  if( len >= sizeof( text ) - 1 ) +  { +    CG_Printf( "File %s too long\n", filename ); +    trap_FS_FCloseFile( f ); +    return qfalse; +  } + +  trap_FS_Read( text, len, f ); +  text[ len ] = 0; +  trap_FS_FCloseFile( f ); + +  // parse the text +  text_p = text; +  skip = 0; // quite the compiler warning + +  ci->footsteps = FOOTSTEP_NORMAL; +  VectorClear( ci->headOffset ); +  ci->gender = GENDER_MALE; +  ci->fixedlegs = qfalse; +  ci->fixedtorso = qfalse; +  ci->nonsegmented = qfalse; + +  // read optional parameters +  while( 1 ) +  { +    prev = text_p;  // so we can unget +    token = COM_Parse( &text_p ); + +    if( !token ) +      break; + +    if( !Q_stricmp( token, "footsteps" ) ) +    { +      token = COM_Parse( &text_p ); +      if( !token ) +        break; + +      if( !Q_stricmp( token, "default" ) || !Q_stricmp( token, "normal" ) ) +        ci->footsteps = FOOTSTEP_NORMAL; +      else if( !Q_stricmp( token, "flesh" ) ) +        ci->footsteps = FOOTSTEP_FLESH; +      else if( !Q_stricmp( token, "none" ) ) +        ci->footsteps = FOOTSTEP_NONE; +      else if( !Q_stricmp( token, "custom" ) ) +        ci->footsteps = FOOTSTEP_CUSTOM; +      else +        CG_Printf( "Bad footsteps parm in %s: %s\n", filename, token ); + +      continue; +    } +    else if( !Q_stricmp( token, "headoffset" ) ) +    { +      for( i = 0 ; i < 3 ; i++ ) +      { +        token = COM_Parse( &text_p ); +        if( !token ) +          break; + +        ci->headOffset[ i ] = atof( token ); +      } + +      continue; +    } +    else if( !Q_stricmp( token, "sex" ) ) +    { +      token = COM_Parse( &text_p ); + +      if( !token ) +        break; + +      if( token[ 0 ] == 'f' || token[ 0 ] == 'F' ) +        ci->gender = GENDER_FEMALE; +      else if( token[ 0 ] == 'n' || token[ 0 ] == 'N' ) +        ci->gender = GENDER_NEUTER; +      else +        ci->gender = GENDER_MALE; + +      continue; +    } +    else if( !Q_stricmp( token, "fixedlegs" ) ) +    { +      ci->fixedlegs = qtrue; +      continue; +    } +    else if( !Q_stricmp( token, "fixedtorso" ) ) +    { +      ci->fixedtorso = qtrue; +      continue; +    } +    else if( !Q_stricmp( token, "nonsegmented" ) ) +    { +      ci->nonsegmented = qtrue; +      continue; +    } + +    // if it is a number, start parsing animations +    if( token[ 0 ] >= '0' && token[ 0 ] <= '9' ) +    { +      text_p = prev;  // unget the token +      break; +    } + +    Com_Printf( "unknown token '%s' is %s\n", token, filename ); +  } + +  if( !ci->nonsegmented ) +  { +    // read information for each frame +    for( i = 0; i < MAX_PLAYER_ANIMATIONS; i++ ) +    { +      token = COM_Parse( &text_p ); + +      if( !*token ) +      { +        if( i >= TORSO_GETFLAG && i <= TORSO_NEGATIVE ) +        { +          animations[ i ].firstFrame = animations[ TORSO_GESTURE ].firstFrame; +          animations[ i ].frameLerp = animations[ TORSO_GESTURE ].frameLerp; +          animations[ i ].initialLerp = animations[ TORSO_GESTURE ].initialLerp; +          animations[ i ].loopFrames = animations[ TORSO_GESTURE ].loopFrames; +          animations[ i ].numFrames = animations[ TORSO_GESTURE ].numFrames; +          animations[ i ].reversed = qfalse; +          animations[ i ].flipflop = qfalse; +          continue; +        } + +        break; +      } + +      animations[ i ].firstFrame = atoi( token ); + +      // leg only frames are adjusted to not count the upper body only frames +      if( i == LEGS_WALKCR ) +        skip = animations[ LEGS_WALKCR ].firstFrame - animations[ TORSO_GESTURE ].firstFrame; + +      if( i >= LEGS_WALKCR && i<TORSO_GETFLAG ) +        animations[ i ].firstFrame -= skip; + +      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_PLAYER_ANIMATIONS ) +    { +      CG_Printf( "Error parsing animation file: %s", filename ); +      return qfalse; +    } +    // crouch backward animation +    memcpy( &animations[ LEGS_BACKCR ], &animations[ LEGS_WALKCR ], sizeof( animation_t ) ); +    animations[ LEGS_BACKCR ].reversed = qtrue; +    // walk backward animation +    memcpy( &animations[ LEGS_BACKWALK ], &animations[ LEGS_WALK ], sizeof( animation_t ) ); +    animations[ LEGS_BACKWALK ].reversed = qtrue; +    // flag moving fast +    animations[ FLAG_RUN ].firstFrame = 0; +    animations[ FLAG_RUN ].numFrames = 16; +    animations[ FLAG_RUN ].loopFrames = 16; +    animations[ FLAG_RUN ].frameLerp = 1000 / 15; +    animations[ FLAG_RUN ].initialLerp = 1000 / 15; +    animations[ FLAG_RUN ].reversed = qfalse; +    // flag not moving or moving slowly +    animations[ FLAG_STAND ].firstFrame = 16; +    animations[ FLAG_STAND ].numFrames = 5; +    animations[ FLAG_STAND ].loopFrames = 0; +    animations[ FLAG_STAND ].frameLerp = 1000 / 20; +    animations[ FLAG_STAND ].initialLerp = 1000 / 20; +    animations[ FLAG_STAND ].reversed = qfalse; +    // flag speeding up +    animations[ FLAG_STAND2RUN ].firstFrame = 16; +    animations[ FLAG_STAND2RUN ].numFrames = 5; +    animations[ FLAG_STAND2RUN ].loopFrames = 1; +    animations[ FLAG_STAND2RUN ].frameLerp = 1000 / 15; +    animations[ FLAG_STAND2RUN ].initialLerp = 1000 / 15; +    animations[ FLAG_STAND2RUN ].reversed = qtrue; +  } +  else +  { +    // read information for each frame +    for( i = 0; i < MAX_NONSEG_PLAYER_ANIMATIONS; i++ ) +    { +      token = COM_Parse( &text_p ); + +      if( !*token ) +        break; + +      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_NONSEG_PLAYER_ANIMATIONS ) +    { +      CG_Printf( "Error parsing animation file: %s", filename ); +      return qfalse; +    } + +    // walk backward animation +    memcpy( &animations[ NSPA_WALKBACK ], &animations[ NSPA_WALK ], sizeof( animation_t ) ); +    animations[ NSPA_WALKBACK ].reversed = qtrue; +  } + +  return qtrue; +} + +/* +========================== +CG_RegisterClientSkin +========================== +*/ +static qboolean CG_RegisterClientSkin( clientInfo_t *ci, const char *modelName, const char *skinName ) +{ +  char filename[ MAX_QPATH ]; + +  if( !ci->nonsegmented ) +  { +    Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower_%s.skin", modelName, skinName ); +    ci->legsSkin = trap_R_RegisterSkin( filename ); +    if( !ci->legsSkin ) +      Com_Printf( "Leg skin load failure: %s\n", filename ); + +    Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper_%s.skin", modelName, skinName ); +    ci->torsoSkin = trap_R_RegisterSkin( filename ); +    if( !ci->torsoSkin ) +      Com_Printf( "Torso skin load failure: %s\n", filename ); + +    Com_sprintf( filename, sizeof( filename ), "models/players/%s/head_%s.skin", modelName, skinName ); +    ci->headSkin = trap_R_RegisterSkin( filename ); +    if( !ci->headSkin ) +      Com_Printf( "Head skin load failure: %s\n", filename ); + +    if( !ci->legsSkin || !ci->torsoSkin || !ci->headSkin ) +      return qfalse; +  } +  else +  { +    Com_sprintf( filename, sizeof( filename ), "models/players/%s/nonseg_%s.skin", modelName, skinName ); +    ci->nonSegSkin = trap_R_RegisterSkin( filename ); +    if( !ci->nonSegSkin ) +      Com_Printf( "Non-segmented skin load failure: %s\n", filename ); + +    if( !ci->nonSegSkin ) +      return qfalse; +  } + +  return qtrue; +} + +/* +========================== +CG_RegisterClientModelname +========================== +*/ +static qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *modelName, const char *skinName ) +{ +  char filename[ MAX_QPATH * 2 ]; + +  // do this first so the nonsegmented property is set +  // load the animations +  Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", modelName ); +  if( !CG_ParseAnimationFile( filename, ci ) ) +  { +    Com_Printf( "Failed to load animation file %s\n", filename ); +    return qfalse; +  } + +  // load cmodels before models so filecache works + +  if( !ci->nonsegmented ) +  { +    Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.md3", modelName ); +    ci->legsModel = trap_R_RegisterModel( filename ); +    if( !ci->legsModel ) +    { +      Com_Printf( "Failed to load model file %s\n", filename ); +      return qfalse; +    } + +    Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.md3", modelName ); +    ci->torsoModel = trap_R_RegisterModel( filename ); +    if( !ci->torsoModel ) +    { +      Com_Printf( "Failed to load model file %s\n", filename ); +      return qfalse; +    } + +    Com_sprintf( filename, sizeof( filename ), "models/players/%s/head.md3", modelName ); +    ci->headModel = trap_R_RegisterModel( filename ); +    if( !ci->headModel ) +    { +      Com_Printf( "Failed to load model file %s\n", filename ); +      return qfalse; +    } +  } +  else +  { +    Com_sprintf( filename, sizeof( filename ), "models/players/%s/nonseg.md3", modelName ); +    ci->nonSegModel = trap_R_RegisterModel( filename ); +    if( !ci->nonSegModel ) +    { +      Com_Printf( "Failed to load model file %s\n", filename ); +      return qfalse; +    } +  } + +  // 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; +  } + +  //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; +} + + +/* +=================== +CG_LoadClientInfo + +Load it now, taking the disk hits +=================== +*/ +static void CG_LoadClientInfo( clientInfo_t *ci ) +{ +  const char  *dir, *fallback; +  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 ); +  } + +  // sounds +  dir = ci->modelName; +  fallback = DEFAULT_MODEL; + +  for( i = 0; i < MAX_CUSTOM_SOUNDS; i++ ) +  { +    s = cg_customSoundNames[ i ]; + +    if( !s ) +      break; + +    // fanny about a bit with sounds that are missing +    if( !CG_FileExists( va( "sound/player/%s/%s", dir, s + 1 ) ) ) +    { +      //file doesn't exist + +      if( i == 11 || i == 8 ) //fall or falling +      { +        ci->sounds[ i ] = trap_S_RegisterSound( "sound/null.wav", qfalse ); +      } +      else +      { +        if( i == 9 ) //gasp +          s = cg_customSoundNames[ 7 ]; //pain100_1 +        else if( i == 10 ) //drown +          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 ); +    } +  } + +  if( ci->footsteps == FOOTSTEP_CUSTOM ) +  { +    for( i = 0; i < 4; i++ ) +    { +      ci->customFootsteps[ i ] = trap_S_RegisterSound( va( "sound/player/%s/step%d.wav", dir, i + 1 ), qfalse ); +      if( !ci->customFootsteps[ i ] ) +        ci->customFootsteps[ i ] = trap_S_RegisterSound( va( "sound/player/footsteps/step%d.wav", i + 1 ), qfalse ); + +      ci->customMetalFootsteps[ i ] = trap_S_RegisterSound( va( "sound/player/%s/clank%d.wav", dir, i + 1 ), qfalse ); +      if( !ci->customMetalFootsteps[ i ] ) +        ci->customMetalFootsteps[ i ] = trap_S_RegisterSound( va( "sound/player/footsteps/clank%d.wav", i + 1 ), qfalse ); +    } +  } + +  // reset any existing players and bodies, because they might be in bad +  // frames for this new model +  clientNum = ci - cgs.clientinfo; +  for( i = 0; i < MAX_GENTITIES; i++ ) +  { +    if( cg_entities[ i ].currentState.clientNum == clientNum && +        cg_entities[ i ].currentState.eType == ET_PLAYER ) +      CG_ResetPlayerEntity( &cg_entities[ i ] ); +  } +} + +/* +====================== +CG_CopyClientInfoModel +====================== +*/ +static void CG_CopyClientInfoModel( clientInfo_t *from, clientInfo_t *to ) +{ +  VectorCopy( from->headOffset, to->headOffset ); +  to->footsteps = from->footsteps; +  to->gender = from->gender; + +  to->legsModel = from->legsModel; +  to->legsSkin = from->legsSkin; +  to->torsoModel = from->torsoModel; +  to->torsoSkin = from->torsoSkin; +  to->headModel = from->headModel; +  to->headSkin = from->headSkin; +  to->nonSegModel = from->nonSegModel; +  to->nonSegSkin = from->nonSegSkin; +  to->nonsegmented = from->nonsegmented; +  to->modelIcon = from->modelIcon; + +  memcpy( to->animations, from->animations, sizeof( to->animations ) ); +  memcpy( to->sounds, from->sounds, sizeof( to->sounds ) ); +  memcpy( to->customFootsteps, from->customFootsteps, sizeof( to->customFootsteps ) ); +  memcpy( to->customMetalFootsteps, from->customMetalFootsteps, sizeof( to->customMetalFootsteps ) ); +} + + +/* +====================== +CG_GetCorpseNum +====================== +*/ +static int CG_GetCorpseNum( pClass_t class ) +{ +  int           i; +  clientInfo_t  *match; +  char          *modelName; +  char          *skinName; + +  modelName = BG_FindModelNameForClass( class ); +  skinName = BG_FindSkinNameForClass( class ); + +  for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ ) +  { +    match = &cgs.corpseinfo[ i ]; + +    if( !match->infoValid ) +      continue; + +    if( !Q_stricmp( modelName, match->modelName ) +      && !Q_stricmp( skinName, match->skinName ) ) +    { +      // this clientinfo is identical, so use it's handles +      return i; +    } +  } + +  //something has gone horribly wrong +  return -1; +} + + +/* +====================== +CG_ScanForExistingClientInfo +====================== +*/ +static qboolean CG_ScanForExistingClientInfo( clientInfo_t *ci ) +{ +  int   i; +  clientInfo_t  *match; + +  for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ ) +  { +    match = &cgs.corpseinfo[ i ]; + +    if( !match->infoValid ) +      continue; + +    if( !Q_stricmp( ci->modelName, match->modelName ) && +        !Q_stricmp( ci->skinName, match->skinName ) ) +    { +      // this clientinfo is identical, so use it's handles +      CG_CopyClientInfoModel( match, ci ); + +      return qtrue; +    } +  } + +  // shouldn't happen +  return qfalse; +} + + +/* +====================== +CG_PrecacheClientInfo +====================== +*/ +void CG_PrecacheClientInfo( pClass_t class, char *model, char *skin ) +{ +  clientInfo_t  *ci; +  clientInfo_t  newInfo; + +  ci = &cgs.corpseinfo[ class ]; + +  // the old value +  memset( &newInfo, 0, sizeof( newInfo ) ); + +  // model +  Q_strncpyz( newInfo.modelName, model, sizeof( newInfo.modelName ) ); +  Q_strncpyz( newInfo.headModelName, model, sizeof( newInfo.headModelName ) ); + +  // modelName didn 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; + +  // actually register the models +  *ci = newInfo; +  CG_LoadClientInfo( ci ); +} + + +/* +====================== +CG_NewClientInfo +====================== +*/ +void CG_NewClientInfo( int clientNum ) +{ +  clientInfo_t  *ci; +  clientInfo_t  newInfo; +  const char    *configstring; +  const char    *v; +  char          *slash; + +  ci = &cgs.clientinfo[ clientNum ]; + +  configstring = CG_ConfigString( clientNum + CS_PLAYERS ); +  if( !configstring[ 0 ] ) +  { +    memset( ci, 0, sizeof( *ci ) ); +    return;   // player just left +  } + +  // 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 ); + +  // team leader +  v = Info_ValueForKey( configstring, "tl" ); +  newInfo.teamLeader = atoi( v ); + +  // model +  v = Info_ValueForKey( configstring, "model" ); +  Q_strncpyz( newInfo.modelName, v, sizeof( newInfo.modelName ) ); + +  slash = strchr( newInfo.modelName, '/' ); + +  if( !slash ) +  { +    // modelName didn not include a skin name +    Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); +  } +  else +  { +    Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); +    // truncate modelName +    *slash = 0; +  } + +  //CG_Printf( "NCI: %s\n", v ); + +  // head model +  v = Info_ValueForKey( configstring, "hmodel" ); +  Q_strncpyz( newInfo.headModelName, v, sizeof( newInfo.headModelName ) ); + +  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; +  } + +  // replace whatever was there with the new one +  newInfo.infoValid = qtrue; +  *ci = newInfo; + +  // scan for an existing clientinfo that matches this modelname +  // so we can avoid loading checks if possible +  if( !CG_ScanForExistingClientInfo( ci ) ) +    CG_LoadClientInfo( ci ); +} + + + +/* +============================================================================= + +PLAYER ANIMATION + +============================================================================= +*/ + + +/* +=============== +CG_SetLerpFrameAnimation + +may include ANIM_TOGGLEBIT +=============== +*/ +static void CG_SetLerpFrameAnimation( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation ) +{ +  animation_t *anim; + +  lf->animationNumber = newAnimation; +  newAnimation &= ~ANIM_TOGGLEBIT; + +  if( newAnimation < 0 || newAnimation >= MAX_PLAYER_TOTALANIMATIONS ) +    CG_Error( "Bad animation number: %i", newAnimation ); + +  anim = &ci->animations[ newAnimation ]; + +  lf->animation = anim; +  lf->animationTime = lf->frameTime + anim->initialLerp; + +  if( cg_debugAnim.integer ) +    CG_Printf( "Anim: %i\n", newAnimation ); +} + +/* +=============== +CG_RunPlayerLerpFrame + +Sets cg.snap, cg.oldFrame, and cg.backlerp +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_ClearLerpFrame +=============== +*/ +static void CG_ClearLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int animationNumber ) +{ +  lf->frameTime = lf->oldFrameTime = cg.time; +  CG_SetLerpFrameAnimation( ci, lf, animationNumber ); +  lf->oldFrame = lf->frame = lf->animation->firstFrame; +} + + +/* +=============== +CG_PlayerAnimation +=============== +*/ +static void CG_PlayerAnimation( centity_t *cent, int *legsOld, int *legs, float *legsBackLerp, +                                int *torsoOld, int *torso, float *torsoBackLerp ) +{ +  clientInfo_t  *ci; +  int           clientNum; +  float         speedScale = 1.0f; + +  clientNum = cent->currentState.clientNum; + +  if( cg_noPlayerAnims.integer ) +  { +    *legsOld = *legs = *torsoOld = *torso = 0; +    return; +  } + +  ci = &cgs.clientinfo[ clientNum ]; + +  // do the shuffle turn frames locally +  if( cent->pe.legs.yawing && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_IDLE ) +    CG_RunPlayerLerpFrame( ci, ¢->pe.legs, LEGS_TURN, speedScale ); +  else +    CG_RunPlayerLerpFrame( ci, ¢->pe.legs, cent->currentState.legsAnim, speedScale ); + +  *legsOld = cent->pe.legs.oldFrame; +  *legs = cent->pe.legs.frame; +  *legsBackLerp = cent->pe.legs.backlerp; + +  CG_RunPlayerLerpFrame( ci, ¢->pe.torso, cent->currentState.torsoAnim, speedScale ); + +  *torsoOld = cent->pe.torso.oldFrame; +  *torso = cent->pe.torso.frame; +  *torsoBackLerp = cent->pe.torso.backlerp; +} + + +/* +=============== +CG_PlayerNonSegAnimation +=============== +*/ +static void CG_PlayerNonSegAnimation( centity_t *cent, int *nonSegOld, +                                      int *nonSeg, float *nonSegBackLerp ) +{ +  clientInfo_t  *ci; +  int           clientNum; +  float         speedScale = 1.0f; + +  clientNum = cent->currentState.clientNum; + +  if( cg_noPlayerAnims.integer ) +  { +    *nonSegOld = *nonSeg = 0; +    return; +  } + +  ci = &cgs.clientinfo[ clientNum ]; + +  // do the shuffle turn frames locally +  if( cent->pe.nonseg.yawing && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == NSPA_STAND ) +    CG_RunPlayerLerpFrame( ci, ¢->pe.nonseg, NSPA_TURN, speedScale ); +  else +    CG_RunPlayerLerpFrame( ci, ¢->pe.nonseg, cent->currentState.legsAnim, speedScale ); + +  *nonSegOld = cent->pe.nonseg.oldFrame; +  *nonSeg = cent->pe.nonseg.frame; +  *nonSegBackLerp = cent->pe.nonseg.backlerp; +} + +/* +============================================================================= + +PLAYER ANGLES + +============================================================================= +*/ + +/* +================== +CG_SwingAngles +================== +*/ +static void CG_SwingAngles( float destination, float swingTolerance, float clampTolerance, +                            float speed, float *angle, qboolean *swinging ) +{ +  float swing; +  float move; +  float scale; + +  if( !*swinging ) +  { +    // see if a swing should be started +    swing = AngleSubtract( *angle, destination ); + +    if( swing > swingTolerance || swing < -swingTolerance ) +      *swinging = qtrue; +  } + +  if( !*swinging ) +    return; + +  // modify the speed depending on the delta +  // so it doesn't seem so linear +  swing = AngleSubtract( destination, *angle ); +  scale = fabs( swing ); + +  if( scale < swingTolerance * 0.5 ) +    scale = 0.5; +  else if( scale < swingTolerance ) +    scale = 1.0; +  else +    scale = 2.0; + +  // swing towards the destination angle +  if( swing >= 0 ) +  { +    move = cg.frametime * scale * speed; + +    if( move >= swing ) +    { +      move = swing; +      *swinging = qfalse; +    } +    *angle = AngleMod( *angle + move ); +  } +  else if( swing < 0 ) +  { +    move = cg.frametime * scale * -speed; + +    if( move <= swing ) +    { +      move = swing; +      *swinging = qfalse; +    } +    *angle = AngleMod( *angle + move ); +  } + +  // clamp to no more than tolerance +  swing = AngleSubtract( destination, *angle ); +  if( swing > clampTolerance ) +    *angle = AngleMod( destination - ( clampTolerance - 1 ) ); +  else if( swing < -clampTolerance ) +    *angle = AngleMod( destination + ( clampTolerance - 1 ) ); +} + +/* +================= +CG_AddPainTwitch +================= +*/ +static void CG_AddPainTwitch( centity_t *cent, vec3_t torsoAngles ) +{ +  int   t; +  float f; + +  t = cg.time - cent->pe.painTime; + +  if( t >= PAIN_TWITCH_TIME ) +    return; + +  f = 1.0 - (float)t / PAIN_TWITCH_TIME; + +  if( cent->pe.painDirection ) +    torsoAngles[ ROLL ] += 20 * f; +  else +    torsoAngles[ ROLL ] -= 20 * f; +} + + +/* +=============== +CG_PlayerAngles + +Handles seperate torso motion + +  legs pivot based on direction of movement + +  head always looks exactly at cent->lerpAngles + +  if motion < 20 degrees, show in head only +  if < 45 degrees, also show in torso +=============== +*/ +static void CG_PlayerAngles( centity_t *cent, vec3_t srcAngles, +                             vec3_t legs[ 3 ], vec3_t torso[ 3 ], vec3_t head[ 3 ] ) +{ +  vec3_t        legsAngles, torsoAngles, headAngles; +  float         dest; +  static int    movementOffsets[ 8 ] = { 0, 22, 45, -22, 0, 22, -45, -22 }; +  vec3_t        velocity; +  float         speed; +  int           dir, clientNum; +  clientInfo_t  *ci; + +  VectorCopy( srcAngles, headAngles ); +  headAngles[ YAW ] = AngleMod( headAngles[ YAW ] ); +  VectorClear( legsAngles ); +  VectorClear( torsoAngles ); + +  // --------- yaw ------------- + +  // allow yaw to drift a bit +  if( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLE || +      ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STAND  ) +  { +    // if not standing still, always point all in the same direction +    cent->pe.torso.yawing = qtrue;  // always center +    cent->pe.torso.pitching = qtrue;  // always center +    cent->pe.legs.yawing = qtrue; // always center +  } + +  // adjust legs for movement dir +  if( cent->currentState.eFlags & EF_DEAD ) +  { +    // don't let dead bodies twitch +    dir = 0; +  } +  else +  { +    // did use angles2.. now uses time2.. looks a bit funny but time2 isn't used othwise +    dir = cent->currentState.time2; +    if( dir < 0 || dir > 7 ) +      CG_Error( "Bad player movement angle" ); +  } + +  legsAngles[ YAW ] = headAngles[ YAW ] + movementOffsets[ dir ]; +  torsoAngles[ YAW ] = headAngles[ YAW ] + 0.25 * movementOffsets[ dir ]; + +  // torso +  if( cent->currentState.eFlags & EF_DEAD ) +  { +    CG_SwingAngles( torsoAngles[ YAW ], 0, 0, cg_swingSpeed.value, +      ¢->pe.torso.yawAngle, ¢->pe.torso.yawing ); +    CG_SwingAngles( legsAngles[ YAW ], 0, 0, cg_swingSpeed.value, +      ¢->pe.legs.yawAngle, ¢->pe.legs.yawing ); +  } +  else +  { +    CG_SwingAngles( torsoAngles[ YAW ], 25, 90, cg_swingSpeed.value, +      ¢->pe.torso.yawAngle, ¢->pe.torso.yawing ); +    CG_SwingAngles( legsAngles[ YAW ], 40, 90, cg_swingSpeed.value, +      ¢->pe.legs.yawAngle, ¢->pe.legs.yawing ); +  } + +  torsoAngles[ YAW ] = cent->pe.torso.yawAngle; +  legsAngles[ YAW ] = cent->pe.legs.yawAngle; + +  // --------- pitch ------------- + +  // only show a fraction of the pitch angle in the torso +  if( headAngles[ PITCH ] > 180 ) +    dest = ( -360 + headAngles[ PITCH ] ) * 0.75f; +  else +    dest = headAngles[ PITCH ] * 0.75f; + +  CG_SwingAngles( dest, 15, 30, 0.1f, ¢->pe.torso.pitchAngle, ¢->pe.torso.pitching ); +  torsoAngles[ PITCH ] = cent->pe.torso.pitchAngle; + +  // +  clientNum = cent->currentState.clientNum; + +  if( clientNum >= 0 && clientNum < MAX_CLIENTS ) +  { +    ci = &cgs.clientinfo[ clientNum ]; +    if( ci->fixedtorso ) +      torsoAngles[ PITCH ] = 0.0f; +  } + +  // --------- roll ------------- + + +  // lean towards the direction of travel +  VectorCopy( cent->currentState.pos.trDelta, velocity ); +  speed = VectorNormalize( velocity ); + +  if( speed ) +  { +    vec3_t  axis[ 3 ]; +    float   side; + +    speed *= 0.05f; + +    AnglesToAxis( legsAngles, axis ); +    side = speed * DotProduct( velocity, axis[ 1 ] ); +    legsAngles[ ROLL ] -= side; + +    side = speed * DotProduct( velocity, axis[ 0 ] ); +    legsAngles[ PITCH ] += side; +  } + +  // +  clientNum = cent->currentState.clientNum; + +  if( clientNum >= 0 && clientNum < MAX_CLIENTS ) +  { +    ci = &cgs.clientinfo[ clientNum ]; + +    if( ci->fixedlegs ) +    { +      legsAngles[ YAW ] = torsoAngles[ YAW ]; +      legsAngles[ PITCH ] = 0.0f; +      legsAngles[ ROLL ] = 0.0f; +    } +  } + +  // pain twitch +  CG_AddPainTwitch( cent, torsoAngles ); + +  // pull the angles back out of the hierarchial chain +  AnglesSubtract( headAngles, torsoAngles, headAngles ); +  AnglesSubtract( torsoAngles, legsAngles, torsoAngles ); +  AnglesToAxis( legsAngles, legs ); +  AnglesToAxis( torsoAngles, torso ); +  AnglesToAxis( headAngles, head ); +} + +#define MODEL_WWSMOOTHTIME 200 + +/* +=============== +CG_PlayerWWSmoothing + +Smooth the angles of transitioning wall walkers +=============== +*/ +static void CG_PlayerWWSmoothing( centity_t *cent, vec3_t in[ 3 ], vec3_t out[ 3 ] ) +{ +  entityState_t *es = ¢->currentState; +  int           i; +  vec3_t        surfNormal, rotAxis, temp; +  vec3_t        refNormal     = { 0.0f, 0.0f,  1.0f }; +  vec3_t        ceilingNormal = { 0.0f, 0.0f, -1.0f }; +  float         stLocal, sFraction, rotAngle; +  vec3_t        inAxis[ 3 ], lastAxis[ 3 ], outAxis[ 3 ]; + +  //set surfNormal +  if( !(es->eFlags & EF_WALLCLIMB ) ) +    VectorCopy( refNormal, surfNormal ); +  else if( !( es->eFlags & EF_WALLCLIMBCEILING ) ) +    VectorCopy( es->angles2, surfNormal ); +  else +    VectorCopy( ceilingNormal, surfNormal ); + +  AxisCopy( in, inAxis ); + +  if( !VectorCompare( surfNormal, cent->pe.lastNormal ) ) +  { +    //if we moving from the ceiling to the floor special case +    //( x product of colinear vectors is undefined) +    if( VectorCompare( ceilingNormal, cent->pe.lastNormal ) && +        VectorCompare( refNormal,     surfNormal ) ) +    { +      VectorCopy( in[ 1 ], rotAxis ); +      rotAngle = 180.0f; +    } +    else +    { +      AxisCopy( cent->pe.lastAxis, lastAxis ); +      rotAngle = DotProduct( inAxis[ 0 ], lastAxis[ 0 ] ) + +                 DotProduct( inAxis[ 1 ], lastAxis[ 1 ] ) + +                 DotProduct( inAxis[ 2 ], lastAxis[ 2 ] ); + +      rotAngle = RAD2DEG( acos( ( rotAngle - 1.0f ) / 2.0f ) ); + +      CrossProduct( lastAxis[ 0 ], inAxis[ 0 ], temp ); +      VectorCopy( temp, rotAxis ); +      CrossProduct( lastAxis[ 1 ], inAxis[ 1 ], temp ); +      VectorAdd( rotAxis, temp, rotAxis ); +      CrossProduct( lastAxis[ 2 ], inAxis[ 2 ], temp ); +      VectorAdd( rotAxis, temp, rotAxis ); + +      VectorNormalize( rotAxis ); +    } + +    //iterate through smooth array +    for( i = 0; i < MAXSMOOTHS; i++ ) +    { +      //found an unused index in the smooth array +      if( cent->pe.sList[ i ].time + MODEL_WWSMOOTHTIME < cg.time ) +      { +        //copy to array and stop +        VectorCopy( rotAxis, cent->pe.sList[ i ].rotAxis ); +        cent->pe.sList[ i ].rotAngle = rotAngle; +        cent->pe.sList[ i ].time = cg.time; +        break; +      } +    } +  } + +  //iterate through ops +  for( i = MAXSMOOTHS - 1; i >= 0; i-- ) +  { +    //if this op has time remaining, perform it +    if( cg.time < cent->pe.sList[ i ].time + MODEL_WWSMOOTHTIME ) +    { +      stLocal = 1.0f - ( ( ( cent->pe.sList[ i ].time + MODEL_WWSMOOTHTIME ) - cg.time ) / MODEL_WWSMOOTHTIME ); +      sFraction = -( cos( stLocal * M_PI ) + 1.0f ) / 2.0f; + +      RotatePointAroundVector( outAxis[ 0 ], cent->pe.sList[ i ].rotAxis, +        inAxis[ 0 ], sFraction * cent->pe.sList[ i ].rotAngle ); +      RotatePointAroundVector( outAxis[ 1 ], cent->pe.sList[ i ].rotAxis, +        inAxis[ 1 ], sFraction * cent->pe.sList[ i ].rotAngle ); +      RotatePointAroundVector( outAxis[ 2 ], cent->pe.sList[ i ].rotAxis, +        inAxis[ 2 ], sFraction * cent->pe.sList[ i ].rotAngle ); + +      AxisCopy( outAxis, inAxis ); +    } +  } + +  //outAxis has been copied to inAxis +  AxisCopy( inAxis, out ); +} + +/* +=============== +CG_PlayerNonSegAngles + +Resolve angles for non-segmented models +=============== +*/ +static void CG_PlayerNonSegAngles( centity_t *cent, vec3_t srcAngles, vec3_t nonSegAxis[ 3 ] ) +{ +  vec3_t        localAngles; +  vec3_t        velocity; +  float         speed; +  int           dir; +  entityState_t *es = ¢->currentState; +  vec3_t        surfNormal; +  vec3_t        ceilingNormal = { 0.0f, 0.0f, -1.0f }; + +  VectorCopy( srcAngles, localAngles ); +  localAngles[ YAW ] = AngleMod( localAngles[ YAW ] ); +  localAngles[ PITCH ] = 0.0f; +  localAngles[ ROLL ] = 0.0f; + +  //set surfNormal +  if( !( es->eFlags & EF_WALLCLIMBCEILING ) ) +    VectorCopy( es->angles2, surfNormal ); +  else +    VectorCopy( ceilingNormal, surfNormal ); + +  //make sure that WW transitions don't cause the swing stuff to go nuts +  if( !VectorCompare( surfNormal, cent->pe.lastNormal ) ) +  { +    //stop CG_SwingAngles having an eppy +    cent->pe.nonseg.yawAngle = localAngles[ YAW ]; +    cent->pe.nonseg.yawing = qfalse; +  } + +  // --------- yaw ------------- + +  // allow yaw to drift a bit +  if( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != NSPA_STAND ) +  { +    // if not standing still, always point all in the same direction +    cent->pe.nonseg.yawing = qtrue; // always center +  } + +  // adjust legs for movement dir +  if( cent->currentState.eFlags & EF_DEAD ) +  { +    // don't let dead bodies twitch +    dir = 0; +  } +  else +  { +    // did use angles2.. now uses time2.. looks a bit funny but time2 isn't used othwise +    dir = cent->currentState.time2; +    if( dir < 0 || dir > 7 ) +      CG_Error( "Bad player movement angle" ); +  } + +  // torso +  if( cent->currentState.eFlags & EF_DEAD ) +  { +    CG_SwingAngles( localAngles[ YAW ], 0, 0, cg_swingSpeed.value, +      ¢->pe.nonseg.yawAngle, ¢->pe.nonseg.yawing ); +  } +  else +  { +    CG_SwingAngles( localAngles[ YAW ], 40, 90, cg_swingSpeed.value, +      ¢->pe.nonseg.yawAngle, ¢->pe.nonseg.yawing ); +  } + +  localAngles[ YAW ] = cent->pe.nonseg.yawAngle; + +  // --------- pitch ------------- + +  //NO PITCH! + + +  // --------- roll ------------- + + +  // lean towards the direction of travel +  VectorCopy( cent->currentState.pos.trDelta, velocity ); +  speed = VectorNormalize( velocity ); + +  if( speed ) +  { +    vec3_t  axis[ 3 ]; +    float   side; + +    //much less than with the regular model system +    speed *= 0.01f; + +    AnglesToAxis( localAngles, axis ); +    side = speed * DotProduct( velocity, axis[ 1 ] ); +    localAngles[ ROLL ] -= side; + +    side = speed * DotProduct( velocity, axis[ 0 ] ); +    localAngles[ PITCH ] += side; +  } + +  //FIXME: PAIN[123] animations? +  // pain twitch +  //CG_AddPainTwitch( cent, torsoAngles ); + +  AnglesToAxis( localAngles, nonSegAxis ); +} + + +//========================================================================== + +/* +=============== +CG_PlayerUpgrade +=============== +*/ +static void CG_PlayerUpgrades( centity_t *cent, refEntity_t *torso ) +{ +  int           held, active; +  refEntity_t   jetpack; +  refEntity_t   battpack; +  refEntity_t   flash; +  entityState_t *es = ¢->currentState; + +  held = es->modelindex; +  active = es->modelindex2; + +  if( held & ( 1 << UP_JETPACK ) ) +  { +    memset( &jetpack, 0, sizeof( jetpack ) ); +    VectorCopy( torso->lightingOrigin, jetpack.lightingOrigin ); +    jetpack.shadowPlane = torso->shadowPlane; +    jetpack.renderfx = torso->renderfx; + +    jetpack.hModel = cgs.media.jetpackModel; + +    //identity matrix +    AxisCopy( axisDefault, jetpack.axis ); + +    //FIXME: change to tag_back when it exists +    CG_PositionRotatedEntityOnTag( &jetpack, torso, torso->hModel, "tag_head" ); + +    trap_R_AddRefEntityToScene( &jetpack ); + +    if( active & ( 1 << UP_JETPACK ) ) +    { +      if( es->pos.trDelta[ 2 ] > 10.0f ) +      { +        if( cent->jetPackState != JPS_ASCENDING ) +        { +          if( CG_IsParticleSystemValid( ¢->jetPackPS ) ) +            CG_DestroyParticleSystem( ¢->jetPackPS ); + +          cent->jetPackPS = CG_SpawnNewParticleSystem( cgs.media.jetPackAscendPS ); +          cent->jetPackState = JPS_ASCENDING; +        } + +        trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, +                                vec3_origin, cgs.media.jetpackAscendSound ); +      } +      else if( es->pos.trDelta[ 2 ] < -10.0f ) +      { +        if( cent->jetPackState != JPS_DESCENDING ) +        { +          if( CG_IsParticleSystemValid( ¢->jetPackPS ) ) +            CG_DestroyParticleSystem( ¢->jetPackPS ); + +          cent->jetPackPS = CG_SpawnNewParticleSystem( cgs.media.jetPackDescendPS ); +          cent->jetPackState = JPS_DESCENDING; +        } + +        trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, +                                vec3_origin, cgs.media.jetpackDescendSound ); +      } +      else +      { +        if( cent->jetPackState != JPS_HOVERING ) +        { +          if( CG_IsParticleSystemValid( ¢->jetPackPS ) ) +            CG_DestroyParticleSystem( ¢->jetPackPS ); + +          cent->jetPackPS = CG_SpawnNewParticleSystem( cgs.media.jetPackHoverPS ); +          cent->jetPackState = JPS_HOVERING; +        } + +        trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, +                                vec3_origin, cgs.media.jetpackIdleSound ); +      } + +      memset( &flash, 0, sizeof( flash ) ); +      VectorCopy( torso->lightingOrigin, flash.lightingOrigin ); +      flash.shadowPlane = torso->shadowPlane; +      flash.renderfx = torso->renderfx; + +      flash.hModel = cgs.media.jetpackFlashModel; +      if( !flash.hModel ) +        return; + +      AxisCopy( axisDefault, flash.axis ); + +      CG_PositionRotatedEntityOnTag( &flash, &jetpack, jetpack.hModel, "tag_flash" ); +      trap_R_AddRefEntityToScene( &flash ); + +      if( CG_IsParticleSystemValid( ¢->jetPackPS ) ) +      { +        CG_SetAttachmentTag( ¢->jetPackPS->attachment, +            jetpack, jetpack.hModel, "tag_flash" ); +        CG_SetAttachmentCent( ¢->jetPackPS->attachment, cent ); +        CG_AttachToTag( ¢->jetPackPS->attachment ); +      } +    } +    else if( CG_IsParticleSystemValid( ¢->jetPackPS ) ) +    { +      CG_DestroyParticleSystem( ¢->jetPackPS ); +      cent->jetPackState = JPS_OFF; +    } +  } +  else if( CG_IsParticleSystemValid( ¢->jetPackPS ) ) +  { +    CG_DestroyParticleSystem( ¢->jetPackPS ); +    cent->jetPackState = JPS_OFF; +  } + +  if( held & ( 1 << UP_BATTPACK ) ) +  { +    memset( &battpack, 0, sizeof( battpack ) ); +    VectorCopy( torso->lightingOrigin, battpack.lightingOrigin ); +    battpack.shadowPlane = torso->shadowPlane; +    battpack.renderfx = torso->renderfx; + +    battpack.hModel = cgs.media.battpackModel; + +    //identity matrix +    AxisCopy( axisDefault, battpack.axis ); + +    //FIXME: change to tag_back when it exists +    CG_PositionRotatedEntityOnTag( &battpack, torso, torso->hModel, "tag_head" ); + +    trap_R_AddRefEntityToScene( &battpack ); +  } + +  if( es->eFlags & EF_BLOBLOCKED ) +  { +    vec3_t  temp, origin, up = { 0.0f, 0.0f, 1.0f }; +    trace_t tr; +    float   size; + +    VectorCopy( es->pos.trBase, temp ); +    temp[ 2 ] -= 4096.0f; + +    CG_Trace( &tr, es->pos.trBase, NULL, NULL, temp, es->number, MASK_SOLID ); +    VectorCopy( tr.endpos, origin ); + +    size = 32.0f; + +    if( size > 0.0f ) +      CG_ImpactMark( cgs.media.creepShader, origin, up, +                     0.0f, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, size, qtrue ); +  } +} + + +/* +=============== +CG_PlayerFloatSprite + +Float a sprite over the player's head +=============== +*/ +static void CG_PlayerFloatSprite( centity_t *cent, qhandle_t shader ) +{ +  int           rf; +  refEntity_t   ent; + +  if( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) +    rf = RF_THIRD_PERSON;   // only show in mirrors +  else +    rf = 0; + +  memset( &ent, 0, sizeof( ent ) ); +  VectorCopy( cent->lerpOrigin, ent.origin ); +  ent.origin[ 2 ] += 48; +  ent.reType = RT_SPRITE; +  ent.customShader = shader; +  ent.radius = 10; +  ent.renderfx = rf; +  ent.shaderRGBA[ 0 ] = 255; +  ent.shaderRGBA[ 1 ] = 255; +  ent.shaderRGBA[ 2 ] = 255; +  ent.shaderRGBA[ 3 ] = 255; +  trap_R_AddRefEntityToScene( &ent ); +} + + + +/* +=============== +CG_PlayerSprites + +Float sprites over the player's head +=============== +*/ +static void CG_PlayerSprites( centity_t *cent ) +{ +  if( cent->currentState.eFlags & EF_CONNECTION ) +  { +    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;*/ +  } +} + +/* +=============== +CG_PlayerShadow + +Returns the Z component of the surface being shadowed + +  should it return a full plane instead of a Z? +=============== +*/ +#define SHADOW_DISTANCE   128 +static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane, pClass_t class ) +{ +  vec3_t        end, mins, maxs; +  trace_t       trace; +  float         alpha; +  entityState_t *es = ¢->currentState; +  vec3_t        surfNormal = { 0.0f, 0.0f, 1.0f }; + +  BG_FindBBoxForClass( class, mins, maxs, NULL, NULL, NULL ); +  mins[ 2 ] = 0.0f; +  maxs[ 2 ] = 2.0f; + +  if( es->eFlags & EF_WALLCLIMB ) +  { +    if( es->eFlags & EF_WALLCLIMBCEILING ) +      VectorSet( surfNormal, 0.0f, 0.0f, -1.0f ); +    else +      VectorCopy( es->angles2, surfNormal ); +  } + +  *shadowPlane = 0; + +  if( cg_shadows.integer == 0 ) +    return qfalse; + +  // send a trace down from the player to the ground +  VectorCopy( cent->lerpOrigin, end ); +  VectorMA( cent->lerpOrigin, -SHADOW_DISTANCE, surfNormal, end ); + +  trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID ); + +  // no shadow if too high +  if( trace.fraction == 1.0 || trace.startsolid || trace.allsolid ) +    return qfalse; + +  // FIXME: stencil shadows will be broken for walls. +  //           Unfortunately there isn't much that can be +  //           done since Q3 references only the Z coord +  //           of the shadowPlane +  if( surfNormal[ 2 ] < 0.0f ) +    *shadowPlane = trace.endpos[ 2 ] - 1.0f; +  else +    *shadowPlane = trace.endpos[ 2 ] + 1.0f; + +  if( cg_shadows.integer != 1 )  // no mark for stencil or projection shadows +    return qtrue; + +  // fade the shadow out with height +  alpha = 1.0 - trace.fraction; + +  // add the mark as a temporary, so it goes directly to the renderer +  // 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 ); + +  return qtrue; +} + + +/* +=============== +CG_PlayerSplash + +Draw a mark at the water surface +=============== +*/ +static void CG_PlayerSplash( centity_t *cent, pClass_t class ) +{ +  vec3_t      start, end; +  vec3_t      mins, maxs; +  trace_t     trace; +  int         contents; + +  if( !cg_shadows.integer ) +    return; + +  BG_FindBBoxForClass( class, mins, maxs, NULL, NULL, NULL ); + +  VectorCopy( cent->lerpOrigin, end ); +  end[ 2 ] += mins[ 2 ]; + +  // if the feet aren't in liquid, don't make a mark +  // this won't handle moving water brushes, but they wouldn't draw right anyway... +  contents = trap_CM_PointContents( end, 0 ); + +  if( !( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) ) +    return; + +  VectorCopy( cent->lerpOrigin, start ); +  start[ 2 ] += 32; + +  // if the head isn't out of liquid, don't make a mark +  contents = trap_CM_PointContents( start, 0 ); + +  if( contents & ( CONTENTS_SOLID | CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) +    return; + +  // trace down to find the surface +  trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, +      ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ); + +  if( trace.fraction == 1.0f ) +    return; + +  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 ); +} + + +/* +================= +CG_LightVerts +================= +*/ +int CG_LightVerts( vec3_t normal, int numVerts, polyVert_t *verts ) +{ +  int       i, j; +  float     incoming; +  vec3_t      ambientLight; +  vec3_t      lightDir; +  vec3_t      directedLight; + +  trap_R_LightForPoint( verts[ 0 ].xyz, ambientLight, directedLight, lightDir ); + +  for( i = 0; i < numVerts; i++ ) +  { +    incoming = DotProduct( normal, lightDir ); + +    if( incoming <= 0 ) +    { +      verts[ i ].modulate[ 0 ] = ambientLight[ 0 ]; +      verts[ i ].modulate[ 1 ] = ambientLight[ 1 ]; +      verts[ i ].modulate[ 2 ] = ambientLight[ 2 ]; +      verts[ i ].modulate[ 3 ] = 255; +      continue; +    } + +    j = ( ambientLight[ 0 ] + incoming * directedLight[ 0 ] ); + +    if( j > 255 ) +      j = 255; + +    verts[ i ].modulate[ 0 ] = j; + +    j = ( ambientLight[ 1 ] + incoming * directedLight[ 1 ] ); + +    if( j > 255 ) +      j = 255; + +    verts[ i ].modulate[ 1 ] = j; + +    j = ( ambientLight[ 2 ] + incoming * directedLight[ 2 ] ); + +    if( j > 255 ) +      j = 255; + +    verts[ i ].modulate[ 2 ] = j; + +    verts[ i ].modulate[ 3 ] = 255; +  } +  return qtrue; +} + + +/* +================= +CG_LightFromDirection +================= +*/ +int CG_LightFromDirection( vec3_t point, vec3_t direction ) +{ +  int       j; +  float     incoming; +  vec3_t    ambientLight; +  vec3_t    lightDir; +  vec3_t    directedLight; +  vec3_t    result; + +  trap_R_LightForPoint( point, ambientLight, directedLight, lightDir ); + +  incoming = DotProduct( direction, lightDir ); + +  if( incoming <= 0 ) +  { +    result[ 0 ] = ambientLight[ 0 ]; +    result[ 1 ] = ambientLight[ 1 ]; +    result[ 2 ] = ambientLight[ 2 ]; +    return (int)( (float)( result[ 0 ] + result[ 1 ] + result[ 2 ] ) / 3.0f ); +  } + +  j = ( ambientLight[ 0 ] + incoming * directedLight[ 0 ] ); + +  if( j > 255 ) +    j = 255; + +  result[ 0 ] = j; + +  j = ( ambientLight[ 1 ] + incoming * directedLight[ 1 ] ); + +  if( j > 255 ) +    j = 255; + +  result[ 1 ] = j; + +  j = ( ambientLight[ 2 ] + incoming * directedLight[ 2 ] ); + +  if( j > 255 ) +    j = 255; + +  result[ 2 ] = j; + +  return (int)((float)( result[ 0 ] + result[ 1 ] + result[ 2 ] ) / 3.0f ); +} + + +/* +================= +CG_AmbientLight +================= +*/ +int CG_AmbientLight( vec3_t point ) +{ +  vec3_t      ambientLight; +  vec3_t      lightDir; +  vec3_t      directedLight; +  vec3_t      result; + +  trap_R_LightForPoint( point, ambientLight, directedLight, lightDir ); + +  result[ 0 ] = ambientLight[ 0 ]; +  result[ 1 ] = ambientLight[ 1 ]; +  result[ 2 ] = ambientLight[ 2 ]; +  return (int)((float)( result[ 0 ] + result[ 1 ] + result[ 2 ] ) / 3.0f ); +} + +#define TRACE_DEPTH 32.0f + +/* +=============== +CG_Player +=============== +*/ +void CG_Player( centity_t *cent ) +{ +  clientInfo_t  *ci; + +  // NOTE: legs is used for nonsegmented models +  //       this helps reduce code to be changed +  refEntity_t   legs; +  refEntity_t   torso; +  refEntity_t   head; +  int           clientNum; +  int           renderfx; +  qboolean      shadow = qfalse; +  float         shadowPlane; +  entityState_t *es = ¢->currentState; +  pClass_t      class = ( es->powerups >> 8 ) & 0xFF; +  float         scale; +  vec3_t        tempAxis[ 3 ], tempAxis2[ 3 ]; +  vec3_t        angles; +  int           held = es->modelindex; +  vec3_t        surfNormal = { 0.0f, 0.0f, 1.0f }; + +  // the client number is stored in clientNum.  It can't be derived +  // from the entity number, because a single client may have +  // multiple corpses on the level using the same clientinfo +  clientNum = es->clientNum; +  if( clientNum < 0 || clientNum >= MAX_CLIENTS ) +    CG_Error( "Bad clientNum on player entity" ); + +  ci = &cgs.clientinfo[ clientNum ]; + +  // it is possible to see corpses from disconnected players that may +  // not have valid clientinfo +  if( !ci->infoValid ) +    return; + +  //don't draw +  if( es->eFlags & EF_NODRAW ) +    return; + +  // get the player model information +  renderfx = 0; +  if( es->number == cg.snap->ps.clientNum ) +  { +    if( !cg.renderingThirdPerson ) +      renderfx = RF_THIRD_PERSON;     // only draw in mirrors +    else if( cg_cameraMode.integer ) +      return; +  } + +  if( cg_drawBBOX.integer ) +  { +    vec3_t  mins, maxs; + +    BG_FindBBoxForClass( class, mins, maxs, NULL, NULL, NULL ); +    CG_DrawBoundingBox( cent->lerpOrigin, mins, maxs ); +  } + +  memset( &legs,    0, sizeof( legs ) ); +  memset( &torso,   0, sizeof( torso ) ); +  memset( &head,    0, sizeof( head ) ); + +  VectorCopy( cent->lerpAngles, angles ); +  AnglesToAxis( cent->lerpAngles, tempAxis ); + +  //rotate lerpAngles to floor +  if( es->eFlags & EF_WALLCLIMB && +      BG_RotateAxis( es->angles2, tempAxis, tempAxis2, qtrue, es->eFlags & EF_WALLCLIMBCEILING ) ) +    AxisToAngles( tempAxis2, angles ); +  else +    VectorCopy( cent->lerpAngles, angles ); + +  //normalise the pitch +  if( angles[ PITCH ] < -180.0f ) +    angles[ PITCH ] += 360.0f; + +  // get the rotation information +  if( !ci->nonsegmented ) +    CG_PlayerAngles( cent, angles, legs.axis, torso.axis, head.axis ); +  else +    CG_PlayerNonSegAngles( cent, angles, legs.axis ); + +  AxisCopy( legs.axis, tempAxis ); + +  //rotate the legs axis to back to the wall +  if( es->eFlags & EF_WALLCLIMB && +      BG_RotateAxis( es->angles2, legs.axis, tempAxis, qfalse, es->eFlags & EF_WALLCLIMBCEILING ) ) +    AxisCopy( tempAxis, legs.axis ); + +  //smooth out WW transitions so the model doesn't hop around +  CG_PlayerWWSmoothing( cent, legs.axis, legs.axis ); + +  AxisCopy( tempAxis, cent->pe.lastAxis ); + +  // get the animation state (after rotation, to allow feet shuffle) +  if( !ci->nonsegmented ) +    CG_PlayerAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp, +                        &torso.oldframe, &torso.frame, &torso.backlerp ); +  else +    CG_PlayerNonSegAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp ); + +  // add the talk baloon or disconnect icon +  CG_PlayerSprites( cent ); + +  // add the shadow +  if( ( es->number == cg.snap->ps.clientNum && cg.renderingThirdPerson ) || +      es->number != cg.snap->ps.clientNum ) +  shadow = CG_PlayerShadow( cent, &shadowPlane, class ); + +  // add a water splash if partially in and out of water +  CG_PlayerSplash( cent, class ); + +  if( cg_shadows.integer == 3 && shadow ) +    renderfx |= RF_SHADOW_PLANE; + +  renderfx |= RF_LIGHTING_ORIGIN;     // use the same origin for all + +  // +  // add the legs +  // +  if( !ci->nonsegmented ) +  { +    legs.hModel = ci->legsModel; + +    if( held & ( 1 << UP_LIGHTARMOUR ) ) +      legs.customSkin = cgs.media.larmourLegsSkin; +    else +      legs.customSkin = ci->legsSkin; +  } +  else +  { +    legs.hModel = ci->nonSegModel; +    legs.customSkin = ci->nonSegSkin; +  } + +  VectorCopy( cent->lerpOrigin, legs.origin ); + +  VectorCopy( cent->lerpOrigin, legs.lightingOrigin ); +  legs.shadowPlane = shadowPlane; +  legs.renderfx = renderfx; +  VectorCopy( legs.origin, legs.oldorigin ); // don't positionally lerp at all + +  //move the origin closer into the wall with a CapTrace +  if( es->eFlags & EF_WALLCLIMB && !( es->eFlags & EF_DEAD ) && !( cg.intermissionStarted ) ) +  { +    vec3_t  start, end, mins, maxs; +    trace_t tr; + +    if( es->eFlags & EF_WALLCLIMBCEILING ) +      VectorSet( surfNormal, 0.0f, 0.0f, -1.0f ); +    else +      VectorCopy( es->angles2, surfNormal ); + +    BG_FindBBoxForClass( class, mins, maxs, NULL, NULL, NULL ); + +    VectorMA( legs.origin, -TRACE_DEPTH, surfNormal, end ); +    VectorMA( legs.origin, 1.0f, surfNormal, start ); +    CG_CapTrace( &tr, start, mins, maxs, end, es->number, MASK_PLAYERSOLID ); + +    //if the trace misses completely then just use legs.origin +    //apparently capsule traces are "smaller" than box traces +    if( tr.fraction != 1.0f ) +      VectorMA( legs.origin, tr.fraction * -TRACE_DEPTH, surfNormal, legs.origin ); + +    VectorCopy( legs.origin, legs.lightingOrigin ); +    VectorCopy( legs.origin, legs.oldorigin ); // don't positionally lerp at all +  } + +  //rescale the model +  scale = BG_FindModelScaleForClass( class ); + +  if( scale != 1.0f ) +  { +    VectorScale( legs.axis[ 0 ], scale, legs.axis[ 0 ] ); +    VectorScale( legs.axis[ 1 ], scale, legs.axis[ 1 ] ); +    VectorScale( legs.axis[ 2 ], scale, legs.axis[ 2 ] ); + +    legs.nonNormalizedAxes = qtrue; +  } + +  //offset on the Z axis if required +  VectorMA( legs.origin, BG_FindZOffsetForClass( class ), surfNormal, legs.origin ); +  VectorCopy( legs.origin, legs.lightingOrigin ); +  VectorCopy( legs.origin, legs.oldorigin ); // don't positionally lerp at all + +  trap_R_AddRefEntityToScene( &legs ); + +  // if the model failed, allow the default nullmodel to be displayed +  if( !legs.hModel ) +    return; + +  if( !ci->nonsegmented ) +  { +    // +    // add the torso +    // +    torso.hModel = ci->torsoModel; + +    if( held & ( 1 << UP_LIGHTARMOUR ) ) +      torso.customSkin = cgs.media.larmourTorsoSkin; +    else +      torso.customSkin = ci->torsoSkin; + +    if( !torso.hModel ) +      return; + +    VectorCopy( cent->lerpOrigin, torso.lightingOrigin ); + +    CG_PositionRotatedEntityOnTag( &torso, &legs, ci->legsModel, "tag_torso" ); + +    torso.shadowPlane = shadowPlane; +    torso.renderfx = renderfx; + +    trap_R_AddRefEntityToScene( &torso ); + +    // +    // add the head +    // +    head.hModel = ci->headModel; + +    if( held & ( 1 << UP_HELMET ) ) +      head.customSkin = cgs.media.larmourHeadSkin; +    else +      head.customSkin = ci->headSkin; + +    if( !head.hModel ) +      return; + +    VectorCopy( cent->lerpOrigin, head.lightingOrigin ); + +    CG_PositionRotatedEntityOnTag( &head, &torso, ci->torsoModel, "tag_head" ); + +    head.shadowPlane = shadowPlane; +    head.renderfx = renderfx; + +    trap_R_AddRefEntityToScene( &head ); +  } + +  // +  // add the gun / barrel / flash +  // +  if( es->weapon != WP_NONE ) +  { +    if( !ci->nonsegmented ) +      CG_AddPlayerWeapon( &torso, NULL, cent ); +    else +      CG_AddPlayerWeapon( &legs, NULL, cent ); +  } + +  CG_PlayerUpgrades( cent, &torso ); + +  //sanity check that particle systems are stopped when dead +  if( es->eFlags & EF_DEAD ) +  { +    if( CG_IsParticleSystemValid( ¢->muzzlePS ) ) +      CG_DestroyParticleSystem( ¢->muzzlePS ); + +    if( CG_IsParticleSystemValid( ¢->jetPackPS ) ) +      CG_DestroyParticleSystem( ¢->jetPackPS ); +  } + +  VectorCopy( surfNormal, cent->pe.lastNormal ); +} + +/* +=============== +CG_Corpse +=============== +*/ +void CG_Corpse( centity_t *cent ) +{ +  clientInfo_t  *ci; +  refEntity_t   legs; +  refEntity_t   torso; +  refEntity_t   head; +  entityState_t *es = ¢->currentState; +  int           corpseNum; +  int           renderfx; +  qboolean      shadow = qfalse; +  float         shadowPlane; +  vec3_t        origin, liveZ, deadZ; +  float         scale; + +  corpseNum = CG_GetCorpseNum( es->clientNum ); + +  if( corpseNum < 0 || corpseNum >= MAX_CLIENTS ) +    CG_Error( "Bad corpseNum on corpse entity: %d", corpseNum ); + +  ci = &cgs.corpseinfo[ corpseNum ]; + +  // it is possible to see corpses from disconnected players that may +  // not have valid clientinfo +  if( !ci->infoValid ) +    return; + +  memset( &legs, 0, sizeof( legs ) ); +  memset( &torso, 0, sizeof( torso ) ); +  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 ); + +  // get the rotation information +  if( !ci->nonsegmented ) +    CG_PlayerAngles( cent, cent->lerpAngles, legs.axis, torso.axis, head.axis ); +  else +    CG_PlayerNonSegAngles( cent, cent->lerpAngles, legs.axis ); + +  //set the correct frame (should always be dead) +  if( cg_noPlayerAnims.integer ) +    legs.oldframe = legs.frame = torso.oldframe = torso.frame = 0; +  else if( !ci->nonsegmented ) +  { +    memset( ¢->pe.legs, 0, sizeof( lerpFrame_t ) ); +    CG_RunPlayerLerpFrame( ci, ¢->pe.legs, es->legsAnim, 1 ); +    legs.oldframe = cent->pe.legs.oldFrame; +    legs.frame = cent->pe.legs.frame; +    legs.backlerp = cent->pe.legs.backlerp; + +    memset( ¢->pe.torso, 0, sizeof( lerpFrame_t ) ); +    CG_RunPlayerLerpFrame( ci, ¢->pe.torso, es->torsoAnim, 1 ); +    torso.oldframe = cent->pe.torso.oldFrame; +    torso.frame = cent->pe.torso.frame; +    torso.backlerp = cent->pe.torso.backlerp; +  } +  else +  { +    memset( ¢->pe.nonseg, 0, sizeof( lerpFrame_t ) ); +    CG_RunPlayerLerpFrame( ci, ¢->pe.nonseg, es->legsAnim, 1 ); +    legs.oldframe = cent->pe.nonseg.oldFrame; +    legs.frame = cent->pe.nonseg.frame; +    legs.backlerp = cent->pe.nonseg.backlerp; +  } + +  // add the shadow +  shadow = CG_PlayerShadow( cent, &shadowPlane, es->clientNum ); + +  // get the player model information +  renderfx = 0; + +  if( cg_shadows.integer == 3 && shadow ) +    renderfx |= RF_SHADOW_PLANE; + +  renderfx |= RF_LIGHTING_ORIGIN;     // use the same origin for all + +  // +  // add the legs +  // +  if( !ci->nonsegmented ) +  { +    legs.hModel = ci->legsModel; +    legs.customSkin = ci->legsSkin; +  } +  else +  { +    legs.hModel = ci->nonSegModel; +    legs.customSkin = ci->nonSegSkin; +  } + +  VectorCopy( origin, legs.origin ); + +  VectorCopy( origin, legs.lightingOrigin ); +  legs.shadowPlane = shadowPlane; +  legs.renderfx = renderfx; +  legs.origin[ 2 ] += BG_FindZOffsetForClass( es->clientNum ); +  VectorCopy( legs.origin, legs.oldorigin ); // don't positionally lerp at all + +  //rescale the model +  scale = BG_FindModelScaleForClass( es->clientNum ); + +  if( scale != 1.0f ) +  { +    VectorScale( legs.axis[ 0 ], scale, legs.axis[ 0 ] ); +    VectorScale( legs.axis[ 1 ], scale, legs.axis[ 1 ] ); +    VectorScale( legs.axis[ 2 ], scale, legs.axis[ 2 ] ); + +    legs.nonNormalizedAxes = qtrue; +  } + +  //CG_AddRefEntityWithPowerups( &legs, es->powerups, ci->team ); +  trap_R_AddRefEntityToScene( &legs ); + +  // if the model failed, allow the default nullmodel to be displayed +  if( !legs.hModel ) +    return; + +  if( !ci->nonsegmented ) +  { +    // +    // add the torso +    // +    torso.hModel = ci->torsoModel; +    if( !torso.hModel ) +      return; + +    torso.customSkin = ci->torsoSkin; + +    VectorCopy( origin, torso.lightingOrigin ); + +    CG_PositionRotatedEntityOnTag( &torso, &legs, ci->legsModel, "tag_torso" ); + +    torso.shadowPlane = shadowPlane; +    torso.renderfx = renderfx; + +    //CG_AddRefEntityWithPowerups( &torso, es->powerups, ci->team ); +    trap_R_AddRefEntityToScene( &torso ); + +    // +    // add the head +    // +    head.hModel = ci->headModel; +    if( !head.hModel ) +      return; + +    head.customSkin = ci->headSkin; + +    VectorCopy( origin, head.lightingOrigin ); + +    CG_PositionRotatedEntityOnTag( &head, &torso, ci->torsoModel, "tag_head"); + +    head.shadowPlane = shadowPlane; +    head.renderfx = renderfx; + +    //CG_AddRefEntityWithPowerups( &head, es->powerups, ci->team ); +    trap_R_AddRefEntityToScene( &head ); +  } +} + + +//===================================================================== + +/* +=============== +CG_ResetPlayerEntity + +A player just came into view or teleported, so reset all animation info +=============== +*/ +void CG_ResetPlayerEntity( centity_t *cent ) +{ +  cent->errorTime = -99999;   // guarantee no error decay added +  cent->extrapolated = qfalse; + +  CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], +                     ¢->pe.legs, cent->currentState.legsAnim ); +  CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], +                     ¢->pe.torso, cent->currentState.torsoAnim ); +  CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], +                     ¢->pe.nonseg, cent->currentState.legsAnim ); + +  BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin ); +  BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); + +  VectorCopy( cent->lerpOrigin, cent->rawOrigin ); +  VectorCopy( cent->lerpAngles, cent->rawAngles ); + +  memset( ¢->pe.legs, 0, sizeof( cent->pe.legs ) ); +  cent->pe.legs.yawAngle = cent->rawAngles[ YAW ]; +  cent->pe.legs.yawing = qfalse; +  cent->pe.legs.pitchAngle = 0; +  cent->pe.legs.pitching = qfalse; + +  memset( ¢->pe.torso, 0, sizeof( cent->pe.legs ) ); +  cent->pe.torso.yawAngle = cent->rawAngles[ YAW ]; +  cent->pe.torso.yawing = qfalse; +  cent->pe.torso.pitchAngle = cent->rawAngles[ PITCH ]; +  cent->pe.torso.pitching = qfalse; + +  memset( ¢->pe.nonseg, 0, sizeof( cent->pe.nonseg ) ); +  cent->pe.nonseg.yawAngle = cent->rawAngles[ YAW ]; +  cent->pe.nonseg.yawing = qfalse; +  cent->pe.nonseg.pitchAngle = cent->rawAngles[ PITCH ]; +  cent->pe.nonseg.pitching = qfalse; + +  if( cg_debugPosition.integer ) +    CG_Printf( "%i ResetPlayerEntity yaw=%i\n", cent->currentState.number, cent->pe.torso.yawAngle ); +} + +/* +================== +CG_PlayerDisconnect + +Player disconnecting +================== +*/ +void CG_PlayerDisconnect( vec3_t org ) +{ +  particleSystem_t  *ps; + +  trap_S_StartSound( org, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.disconnectSound ); + +  ps = CG_SpawnNewParticleSystem( cgs.media.disconnectPS ); + +  if( CG_IsParticleSystemValid( &ps ) ) +  { +    CG_SetAttachmentPoint( &ps->attachment, org ); +    CG_AttachToPoint( &ps->attachment ); +  } +} + +/* +================= +CG_Bleed + +This is the spurt of blood when a character gets hit +================= +*/ +void CG_Bleed( vec3_t origin, vec3_t normal, int entityNum ) +{ +  pTeam_t           team = cgs.clientinfo[ entityNum ].team; +  qhandle_t         bleedPS; +  particleSystem_t  *ps; + +  if( !cg_blood.integer ) +    return; + +  if( team == PTE_ALIENS ) +    bleedPS = cgs.media.alienBleedPS; +  else if( team == PTE_HUMANS ) +    bleedPS = cgs.media.humanBleedPS; +  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 ); +  } +} + +/* +=============== +CG_AtHighestClass + +Is the local client at the highest class possible? +=============== +*/ +qboolean CG_AtHighestClass( void ) +{ +  int       i; +  qboolean  superiorClasses = qfalse; + +  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; +    } +  } + +  return !superiorClasses; +} + diff --git a/src/cgame/cg_playerstate.c b/src/cgame/cg_playerstate.c new file mode 100644 index 0000000..e1bcb09 --- /dev/null +++ b/src/cgame/cg_playerstate.c @@ -0,0 +1,317 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +// cg_playerstate.c -- this file acts on changes in a new playerState_t +// With normal play, this will be done after local prediction, but when +// 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" + +/* +============== +CG_DamageFeedback +============== +*/ +void CG_DamageFeedback( int yawByte, int pitchByte, int damage ) +{ +  float   left, front, up; +  float   kick; +  int     health; +  float   scale; +  vec3_t  dir; +  vec3_t  angles; +  float   dist; +  float   yaw, pitch; + +  // show the attacking player's head and name in corner +  cg.attackerTime = cg.time; + +  // the lower on health you are, the greater the view kick will be +  health = cg.snap->ps.stats[STAT_HEALTH]; + +  if( health < 40 ) +    scale = 1; +  else +    scale = 40.0 / health; + +  kick = damage * scale; + +  if( kick < 5 ) +    kick = 5; + +  if( kick > 10 ) +    kick = 10; + +  // if yaw and pitch are both 255, make the damage always centered (falling, etc) +  if( yawByte == 255 && pitchByte == 255 ) +  { +    cg.damageX = 0; +    cg.damageY = 0; +    cg.v_dmg_roll = 0; +    cg.v_dmg_pitch = -kick; +  } +  else +  { +    // positional +    pitch = pitchByte / 255.0 * 360; +    yaw = yawByte / 255.0 * 360; + +    angles[ PITCH ] = pitch; +    angles[ YAW ] = yaw; +    angles[ ROLL ] = 0; + +    AngleVectors( angles, dir, NULL, NULL ); +    VectorSubtract( vec3_origin, dir, dir ); + +    front = DotProduct( dir, cg.refdef.viewaxis[ 0 ] ); +    left = DotProduct( dir, cg.refdef.viewaxis[ 1 ] ); +    up = DotProduct( dir, cg.refdef.viewaxis[ 2 ] ); + +    dir[ 0 ] = front; +    dir[ 1 ] = left; +    dir[ 2 ] = 0; +    dist = VectorLength( dir ); + +    if( dist < 0.1f ) +      dist = 0.1f; + +    cg.v_dmg_roll = kick * left; + +    cg.v_dmg_pitch = -kick * front; + +    if( front <= 0.1 ) +      front = 0.1f; + +    cg.damageX = -left / front; +    cg.damageY = up / dist; +  } + +  // clamp the position +  if( cg.damageX > 1.0 ) +    cg.damageX = 1.0; + +  if( cg.damageX < - 1.0 ) +    cg.damageX = -1.0; + +  if( cg.damageY > 1.0 ) +    cg.damageY = 1.0; + +  if( cg.damageY < - 1.0 ) +    cg.damageY = -1.0; + +  // don't let the screen flashes vary as much +  if( kick > 10 ) +    kick = 10; + +  cg.damageValue = kick; +  cg.v_dmg_time = cg.time + DAMAGE_TIME; +  cg.damageTime = cg.snap->serverTime; +} + + + + +/* +================ +CG_Respawn + +A respawn happened this snapshot +================ +*/ +void CG_Respawn( void ) +{ +  // no error decay on player movement +  cg.thisFrameTeleport = qtrue; + +  // display weapons available +  cg.weaponSelectTime = cg.time; + +  // select the weapon the server says we are using +  cg.weaponSelect = cg.snap->ps.weapon; + +  CG_ResetPainBlend( ); +} + +/* +============== +CG_CheckPlayerstateEvents + +============== +*/ +void CG_CheckPlayerstateEvents( playerState_t *ps, playerState_t *ops ) +{ +  int       i; +  int       event; +  centity_t *cent; + +  if( ps->externalEvent && ps->externalEvent != ops->externalEvent ) +  { +    cent = &cg_entities[ ps->clientNum ]; +    cent->currentState.event = ps->externalEvent; +    cent->currentState.eventParm = ps->externalEventParm; +    CG_EntityEvent( cent, cent->lerpOrigin ); +  } + +  cent = &cg.predictedPlayerEntity; // cg_entities[ ps->clientNum ]; + +  // go through the predictable events buffer +  for( i = ps->eventSequence - MAX_PS_EVENTS; i < ps->eventSequence; i++ ) +  { +    // if we have a new predictable event +    if( i >= ops->eventSequence || +      // or the server told us to play another event instead of a predicted event we already issued +      // or something the server told us changed our prediction causing a different event +      ( i > ops->eventSequence - MAX_PS_EVENTS && ps->events[ i & ( MAX_PS_EVENTS - 1 ) ] != +        ops->events[ i & ( MAX_PS_EVENTS - 1 ) ] ) ) +    { +      event = ps->events[ i & ( MAX_PS_EVENTS - 1 ) ]; + +      cent->currentState.event = event; +      cent->currentState.eventParm = ps->eventParms[ i & ( MAX_PS_EVENTS - 1 ) ]; +      CG_EntityEvent( cent, cent->lerpOrigin ); +      cg.predictableEvents[ i & ( MAX_PREDICTED_EVENTS - 1 ) ] = event; + +      cg.eventSequence++; +    } +  } +} + + +/* +================== +CG_CheckChangedPredictableEvents +================== +*/ +void CG_CheckChangedPredictableEvents( playerState_t *ps ) +{ +  int       i; +  int       event; +  centity_t *cent; + +  cent = &cg.predictedPlayerEntity; + +  for( i = ps->eventSequence - MAX_PS_EVENTS; i < ps->eventSequence; i++ ) +  { +    // +    if( i >= cg.eventSequence ) +      continue; + +    // if this event is not further back in than the maximum predictable events we remember +    if( i > cg.eventSequence - MAX_PREDICTED_EVENTS ) +    { +      // if the new playerstate event is different from a previously predicted one +      if( ps->events[ i & ( MAX_PS_EVENTS - 1 ) ] != cg.predictableEvents[ i & ( MAX_PREDICTED_EVENTS - 1 ) ] ) +      { +        event = ps->events[ i & ( MAX_PS_EVENTS - 1 ) ]; +        cent->currentState.event = event; +        cent->currentState.eventParm = ps->eventParms[ i & ( MAX_PS_EVENTS - 1 ) ]; +        CG_EntityEvent( cent, cent->lerpOrigin ); + +        cg.predictableEvents[ i & ( MAX_PREDICTED_EVENTS - 1 ) ] = event; + +        if( cg_showmiss.integer ) +          CG_Printf( "WARNING: changed predicted event\n" ); +      } +    } +  } +} + +/* +================== +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 ] ) +    return; + +  // health changes of more than -1 should make pain sounds +  if( ps->stats[ STAT_HEALTH ] < ops->stats[ STAT_HEALTH ] - 1 ) +  { +    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; +} + + +/* +=============== +CG_TransitionPlayerState + +=============== +*/ +void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ) +{ +  // check for changing follow mode +  if( ps->clientNum != ops->clientNum ) +  { +    cg.thisFrameTeleport = qtrue; +    // make sure we don't get any unwanted transition effects +    *ops = *ps; + +    CG_ResetPainBlend( ); +  } + +  // damage events (player is getting wounded) +  if( ps->damageEvent != ops->damageEvent && ps->damageCount ) +    CG_DamageFeedback( ps->damageYaw, ps->damagePitch, ps->damageCount ); + +  // respawning +  if( ps->persistant[ PERS_SPAWN_COUNT ] != ops->persistant[ PERS_SPAWN_COUNT ] ) +    CG_Respawn( ); + +  if( cg.mapRestart ) +  { +    CG_Respawn( ); +    cg.mapRestart = qfalse; +  } + +  if( cg.snap->ps.pm_type != PM_INTERMISSION && +      ps->persistant[ PERS_TEAM ] != TEAM_SPECTATOR ) +    CG_CheckLocalSounds( ps, ops ); + +  // run events +  CG_CheckPlayerstateEvents( ps, ops ); + +  // smooth the ducking viewheight change +  if( ps->viewheight != ops->viewheight ) +  { +    cg.duckChange = ps->viewheight - ops->viewheight; +    cg.duckTime = cg.time; +  } +} + diff --git a/src/cgame/cg_predict.c b/src/cgame/cg_predict.c new file mode 100644 index 0000000..34f00c4 --- /dev/null +++ b/src/cgame/cg_predict.c @@ -0,0 +1,878 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +// cg_predict.c -- this file generates cg.predictedPlayerState by either +// interpolating between snapshots from the server or locally predicting +// 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; + +static  int     cg_numSolidEntities; +static  centity_t *cg_solidEntities[MAX_ENTITIES_IN_SNAPSHOT]; +static  int     cg_numTriggerEntities; +static  centity_t *cg_triggerEntities[MAX_ENTITIES_IN_SNAPSHOT]; + +/* +==================== +CG_BuildSolidList + +When a new cg.snap has been set, this function builds a sublist +of the entities that are actually solid, to make for more +efficient collision detection +==================== +*/ +void CG_BuildSolidList( void ) +{ +  int           i; +  centity_t     *cent; +  snapshot_t    *snap; +  entityState_t *ent; + +  cg_numSolidEntities = 0; +  cg_numTriggerEntities = 0; + +  if( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport ) +    snap = cg.nextSnap; +  else +    snap = cg.snap; + +  for( i = 0; i < snap->numEntities; i++ ) +  { +    cent = &cg_entities[ snap->entities[ i ].number ]; +    ent = ¢->currentState; + +    if( ent->eType == ET_ITEM || ent->eType == ET_PUSH_TRIGGER || ent->eType == ET_TELEPORT_TRIGGER ) +    { +      cg_triggerEntities[ cg_numTriggerEntities ] = cent; +      cg_numTriggerEntities++; +      continue; +    } + +    if( cent->nextState.solid && ent->eType != ET_MISSILE ) +    { +      cg_solidEntities[ cg_numSolidEntities ] = cent; +      cg_numSolidEntities++; +      continue; +    } +  } +} + +/* +==================== +CG_ClipMoveToEntities + +==================== +*/ +static void CG_ClipMoveToEntities ( const vec3_t start, const vec3_t mins, +    const vec3_t maxs, const vec3_t end, int skipNumber, +    int mask, trace_t *tr, traceType_t collisionType ) +{ +  int           i, j, x, zd, zu; +  trace_t       trace; +  entityState_t *ent; +  clipHandle_t  cmodel; +  vec3_t        bmins, bmaxs; +  vec3_t        origin, angles; +  centity_t     *cent; + +  //SUPAR HACK +  //this causes a trace to collide with the local player +  if( skipNumber == MAGIC_TRACE_HACK ) +    j = cg_numSolidEntities + 1; +  else +    j = cg_numSolidEntities; + +  for( i = 0; i < j; i++ ) +  { +    if( i < cg_numSolidEntities ) +      cent = cg_solidEntities[ i ]; +    else +      cent = &cg.predictedPlayerEntity; + +    ent = ¢->currentState; + +    if( ent->number == skipNumber ) +      continue; + +    if( ent->solid == SOLID_BMODEL ) +    { +      // special value for bmodel +      cmodel = trap_CM_InlineModel( ent->modelindex ); +      VectorCopy( cent->lerpAngles, angles ); +      BG_EvaluateTrajectory( ¢->currentState.pos, cg.physicsTime, origin ); +    } +    else +    { +      // encoded bbox +      x = ( ent->solid & 255 ); +      zd = ( ( ent->solid >> 8 ) & 255 ); +      zu = ( ( ent->solid >> 16 ) & 255 ) - 32; + +      bmins[ 0 ] = bmins[ 1 ] = -x; +      bmaxs[ 0 ] = bmaxs[ 1 ] = x; +      bmins[ 2 ] = -zd; +      bmaxs[ 2 ] = zu; + +      if( i == cg_numSolidEntities ) +        BG_FindBBoxForClass( ( ent->powerups >> 8 ) & 0xFF, bmins, bmaxs, NULL, NULL, NULL ); + +      cmodel = trap_CM_TempBoxModel( bmins, bmaxs ); +      VectorCopy( vec3_origin, angles ); +      VectorCopy( cent->lerpOrigin, origin ); +    } + + +    if( collisionType == TT_CAPSULE ) +    { +      trap_CM_TransformedCapsuleTrace ( &trace, start, end, +        mins, maxs, cmodel,  mask, origin, angles ); +    } +    else if( collisionType == TT_AABB ) +    { +      trap_CM_TransformedBoxTrace ( &trace, start, end, +        mins, maxs, cmodel,  mask, origin, angles ); +    } +    else if( collisionType == TT_BISPHERE ) +    { +      trap_CM_TransformedBiSphereTrace( &trace, start, end, +        mins[ 0 ], maxs[ 0 ], cmodel, mask, origin ); +    } + +    if( trace.allsolid || trace.fraction < tr->fraction ) +    { +      trace.entityNum = ent->number; + +      if( tr->lateralFraction < trace.lateralFraction ) +      { +        float oldLateralFraction = tr->lateralFraction; +        *tr = trace; +        tr->lateralFraction = oldLateralFraction; +      } +      else +        *tr = trace; +    } +    else if( trace.startsolid ) +      tr->startsolid = qtrue; + +    if( tr->allsolid ) +      return; +  } +} + +/* +================ +CG_Trace +================ +*/ +void  CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, +                int skipNumber, int mask ) +{ +  trace_t t; + +  trap_CM_BoxTrace( &t, start, end, mins, maxs, 0, mask ); +  t.entityNum = t.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; +  // check all other solid models +  CG_ClipMoveToEntities( start, mins, maxs, end, skipNumber, mask, &t, TT_AABB ); + +  *result = t; +} + +/* +================ +CG_CapTrace +================ +*/ +void  CG_CapTrace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, +                   int skipNumber, int mask ) +{ +  trace_t t; + +  trap_CM_CapsuleTrace( &t, start, end, mins, maxs, 0, mask ); +  t.entityNum = t.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; +  // check all other solid models +  CG_ClipMoveToEntities( start, mins, maxs, end, skipNumber, mask, &t, TT_CAPSULE ); + +  *result = t; +} + +/* +================ +CG_BiSphereTrace +================ +*/ +void CG_BiSphereTrace( trace_t *result, const vec3_t start, const vec3_t end, +    const float startRadius, const float endRadius, int skipNumber, int mask ) +{ +  trace_t t; +  vec3_t  mins, maxs; + +  mins[ 0 ] = startRadius; +  maxs[ 0 ] = endRadius; + +  trap_CM_BiSphereTrace( &t, start, end, startRadius, endRadius, 0, mask ); +  t.entityNum = t.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; +  // check all other solid models +  CG_ClipMoveToEntities( start, mins, maxs, end, skipNumber, mask, &t, TT_BISPHERE ); + +  *result = t; +} + +/* +================ +CG_PointContents +================ +*/ +int   CG_PointContents( const vec3_t point, int passEntityNum ) +{ +  int           i; +  entityState_t *ent; +  centity_t     *cent; +  clipHandle_t  cmodel; +  int           contents; + +  contents = trap_CM_PointContents (point, 0); + +  for( i = 0; i < cg_numSolidEntities; i++ ) +  { +    cent = cg_solidEntities[ i ]; + +    ent = ¢->currentState; + +    if( ent->number == passEntityNum ) +      continue; + +    if( ent->solid != SOLID_BMODEL ) // special value for bmodel +      continue; + +    cmodel = trap_CM_InlineModel( ent->modelindex ); + +    if( !cmodel ) +      continue; + +    contents |= trap_CM_TransformedPointContents( point, cmodel, ent->origin, ent->angles ); +  } + +  return contents; +} + + +/* +======================== +CG_InterpolatePlayerState + +Generates cg.predictedPlayerState by interpolating between +cg.snap->player_state and cg.nextFrame->player_state +======================== +*/ +static void CG_InterpolatePlayerState( qboolean grabAngles ) +{ +  float         f; +  int           i; +  playerState_t *out; +  snapshot_t    *prev, *next; + +  out = &cg.predictedPlayerState; +  prev = cg.snap; +  next = cg.nextSnap; + +  *out = cg.snap->ps; + +  // if we are still allowing local input, short circuit the view angles +  if( grabAngles ) +  { +    usercmd_t cmd; +    int     cmdNum; + +    cmdNum = trap_GetCurrentCmdNumber( ); +    trap_GetUserCmd( cmdNum, &cmd ); + +    PM_UpdateViewAngles( out, &cmd ); +  } + +  // if the next frame is a teleport, we can't lerp to it +  if( cg.nextFrameTeleport ) +    return; + +  if( !next || next->serverTime <= prev->serverTime ) +    return; + +  f = (float)( cg.time - prev->serverTime ) / ( next->serverTime - prev->serverTime ); + +  i = next->ps.bobCycle; +  if( i < prev->ps.bobCycle ) +    i += 256;   // handle wraparound + +  out->bobCycle = prev->ps.bobCycle + f * ( i - prev->ps.bobCycle ); + +  for( i = 0; i < 3; i++ ) +  { +    out->origin[ i ] = prev->ps.origin[ i ] + f * ( next->ps.origin[ i ] - prev->ps.origin[ i ] ); + +    if( !grabAngles ) +      out->viewangles[ i ] = LerpAngle( prev->ps.viewangles[ i ], next->ps.viewangles[ i ], f ); + +    out->velocity[ i ] = prev->ps.velocity[ i ] + +      f * (next->ps.velocity[ i ] - prev->ps.velocity[ i ] ); +  } +} + + +/* +========================= +CG_TouchTriggerPrediction + +Predict push triggers and items +========================= +*/ +static void CG_TouchTriggerPrediction( void ) +{ +  int           i; +  trace_t       trace; +  entityState_t *ent; +  clipHandle_t  cmodel; +  centity_t     *cent; +  qboolean      spectator; + +  // dead clients don't activate triggers +  if( cg.predictedPlayerState.stats[ STAT_HEALTH ] <= 0 ) +    return; + +  spectator = ( cg.predictedPlayerState.pm_type == PM_SPECTATOR ); + +  if( cg.predictedPlayerState.pm_type != PM_NORMAL && !spectator ) +    return; + +  for( i = 0; i < cg_numTriggerEntities; i++ ) +  { +    cent = cg_triggerEntities[ i ]; +    ent = ¢->currentState; + +    if( ent->solid != SOLID_BMODEL ) +      continue; + +    cmodel = trap_CM_InlineModel( ent->modelindex ); +    if( !cmodel ) +      continue; + +    trap_CM_BoxTrace( &trace, cg.predictedPlayerState.origin, cg.predictedPlayerState.origin, +                      cg_pmove.mins, cg_pmove.maxs, cmodel, -1 ); + +    if( !trace.startsolid ) +      continue; + +    if( ent->eType == ET_TELEPORT_TRIGGER ) +      cg.hyperspace = qtrue; +  } +} + +static int CG_IsUnacceptableError( playerState_t *ps, playerState_t *pps ) +{ +  vec3_t delta; +  int i; + +  if( pps->pm_type != ps->pm_type || +    pps->pm_flags != ps->pm_flags || +    pps->pm_time != ps->pm_time ) +  { +    return 1; +  } + +  VectorSubtract( pps->origin, ps->origin, delta ); +  if( VectorLengthSquared( delta ) > 0.1f * 0.1f ) +  { +    if( cg_showmiss.integer ) +    { +      CG_Printf( "origin delta: %.2f  ", VectorLength( delta ) ); +    } +    return 2; +  } + +  VectorSubtract( pps->velocity, ps->velocity, delta ); +  if( VectorLengthSquared( delta ) > 0.1f * 0.1f ) +  { +    if( cg_showmiss.integer ) +    { +      CG_Printf( "velocity delta: %.2f  ", VectorLength( delta ) ); +    } +    return 3; +  } + +  if( pps->weaponTime != ps->weaponTime || +    pps->gravity != ps->gravity || +    pps->speed != ps->speed || +    pps->delta_angles[ 0 ] != ps->delta_angles[ 0 ] || +    pps->delta_angles[ 1 ] != ps->delta_angles[ 1 ] || +    pps->delta_angles[ 2 ] != ps->delta_angles[ 2 ] ||  +    pps->groundEntityNum != ps->groundEntityNum ) +  { +    return 4; +  } + +  if( pps->legsTimer != ps->legsTimer || +    pps->legsAnim != ps->legsAnim || +    pps->torsoTimer != ps->torsoTimer || +    pps->torsoAnim != ps->torsoAnim || +    pps->movementDir != ps->movementDir ) +  { +    return 5; +  } + +  VectorSubtract( pps->grapplePoint, ps->grapplePoint, delta ); +  if( VectorLengthSquared( delta ) > 0.1f * 0.1f ) +    return 6; + +  if( pps->eFlags != ps->eFlags ) +    return 7; + +  if( pps->eventSequence != ps->eventSequence ) +    return 8; + +  for( i = 0; i < MAX_PS_EVENTS; i++ ) +  { +    if ( pps->events[ i ] != ps->events[ i ] || +      pps->eventParms[ i ] != ps->eventParms[ i ] ) +    { +      return 9; +    } +  } + +  if( pps->externalEvent != ps->externalEvent || +    pps->externalEventParm != ps->externalEventParm || +    pps->externalEventTime != ps->externalEventTime ) +  { +    return 10; +  } + +  if( pps->clientNum != ps->clientNum || +    pps->weapon != ps->weapon || +    pps->weaponstate != ps->weaponstate ) +  { +    return 11; +  } + +  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 )  +  { +    return 12; +  } + +  if( pps->viewheight != ps->viewheight ) +  	return 13; + +  if( pps->damageEvent != ps->damageEvent || +    pps->damageYaw != ps->damageYaw || +    pps->damagePitch != ps->damagePitch || +    pps->damageCount != ps->damageCount ) +  { +    return 14; +  } + +  for( i = 0; i < MAX_STATS; i++ ) +  { +    if( pps->stats[ i ] != ps->stats[ i ] ) +      return 15; +  } + +  for( i = 0; i < MAX_PERSISTANT; i++ ) +  { +    if( pps->persistant[ i ] != ps->persistant[ i ] ) +      return 16; +  } + +  for( i = 0; i < MAX_WEAPONS; i++ ) +  { +    if( pps->ammo[ i ] != ps->ammo[ i ] ) +      return 18; +  } + +  if( pps->generic1 != ps->generic1 || +    pps->loopSound != ps->loopSound ) +  { +    return 19; +  } + +  return 0; +} + + +/* +================= +CG_PredictPlayerState + +Generates cg.predictedPlayerState for the current cg.time +cg.predictedPlayerState is guaranteed to be valid after exiting. + +For demo playback, this will be an interpolation between two valid +playerState_t. + +For normal gameplay, it will be the result of predicted usercmd_t on +top of the most recent playerState_t received from the server. + +Each new snapshot will usually have one or more new usercmd over the last, +but we simulate all unacknowledged commands each time, not just the new ones. +This means that on an internet connection, quite a few pmoves may be issued +each frame. + +OPTIMIZE: don't re-simulate unless the newly arrived snapshot playerState_t +differs from the predicted one.  Would require saving all intermediate +playerState_t during prediction. + +We detect prediction errors and allow them to be decayed off over several frames +to ease the jerk. +================= +*/ +void CG_PredictPlayerState( void ) +{ +  int     cmdNum, current, i; +  playerState_t oldPlayerState; +  qboolean  moved; +  usercmd_t oldestCmd; +  usercmd_t latestCmd; +  int stateIndex = 0, predictCmd = 0; + +  cg.hyperspace = qfalse; // will be set if touching a trigger_teleport + +  // if this is the first frame we must guarantee +  // predictedPlayerState is valid even if there is some +  // other error condition +  if( !cg.validPPS ) +  { +    cg.validPPS = qtrue; +    cg.predictedPlayerState = cg.snap->ps; +  } + + +  // demo playback just copies the moves +  if( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) ) +  { +    CG_InterpolatePlayerState( qfalse ); +    return; +  } + +  // non-predicting local movement will grab the latest angles +  if( cg_nopredict.integer || cg_synchronousClients.integer ) +  { +    CG_InterpolatePlayerState( qtrue ); +    return; +  } + +  // prepare for pmove +  cg_pmove.ps = &cg.predictedPlayerState; +  cg_pmove.pmext = &cg.pmext; +  cg_pmove.trace = CG_Trace; +  cg_pmove.pointcontents = CG_PointContents; +  cg_pmove.debugLevel = cg_debugMove.integer; + +  if( cg_pmove.ps->pm_type == PM_DEAD ) +    cg_pmove.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; +  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 + +  cg_pmove.noFootsteps = 0; + +  // save the state before the pmove so we can detect transitions +  oldPlayerState = cg.predictedPlayerState; + +  current = trap_GetCurrentCmdNumber( ); + +  // if we don't have the commands right after the snapshot, we +  // can't accurately predict a current position, so just freeze at +  // the last good position we had +  cmdNum = current - CMD_BACKUP + 1; +  trap_GetUserCmd( cmdNum, &oldestCmd ); + +  if( oldestCmd.serverTime > cg.snap->ps.commandTime && +      oldestCmd.serverTime < cg.time ) +  { // special check for map_restart +    if( cg_showmiss.integer ) +      CG_Printf( "exceeded PACKET_BACKUP on commands\n" ); + +    return; +  } + +  // get the latest command so we can know which commands are from previous map_restarts +  trap_GetUserCmd( current, &latestCmd ); + +  // get the most recent information we have, even if +  // the server time is beyond our current cg.time, +  // because predicted player positions are going to +  // be ahead of everything else anyway +  if( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport ) +  { +    cg.predictedPlayerState = cg.nextSnap->ps; +    cg.physicsTime = cg.nextSnap->serverTime; +  } +  else +  { +    cg.predictedPlayerState = cg.snap->ps; +    cg.physicsTime = cg.snap->serverTime; +  } + +  if( pmove_msec.integer < 8 ) +    trap_Cvar_Set( "pmove_msec", "8" ); +  else if( pmove_msec.integer > 33 ) +    trap_Cvar_Set( "pmove_msec", "33" ); + +  cg_pmove.pmove_fixed = pmove_fixed.integer;// | cg_pmove_fixed.integer; +  cg_pmove.pmove_msec = pmove_msec.integer; + +  // Like the comments described above, a player's state is entirely +  // re-predicted from the last valid snapshot every client frame, which +  // can be really, really, really slow.  Every old command has to be +  // run again.  For every client frame that is *not* directly after a +  // snapshot, this is unnecessary, since we have no new information. +  // For those, we'll play back the predictions from the last frame and +  // predict only the newest commands.  Essentially, we'll be doing +  // an incremental predict instead of a full predict. +  // +  // If we have a new snapshot, we can compare its player state's command +  // time to the command times in the queue to find a match.  If we find +  // a matching state, and the predicted version has not deviated, we can +  // use the predicted state as a base - and also do an incremental predict. +  // +  // With this method, we get incremental predicts on every client frame +  // except a frame following a new snapshot in which there was a prediction +  // error.  This yeilds anywhere from a 15% to 40% performance increase, +  // depending on how much of a bottleneck the CPU is. +  if( cg_optimizePrediction.integer ) +  { +    if( cg.nextFrameTeleport || cg.thisFrameTeleport ) +    { +      // do a full predict +      cg.lastPredictedCommand = 0; +      cg.stateTail = cg.stateHead; +      predictCmd = current - CMD_BACKUP + 1; +    } +    // cg.physicsTime is the current snapshot's serverTime if it's the same +    // as the last one +    else if( cg.physicsTime == cg.lastServerTime ) +    { +      // we have no new information, so do an incremental predict +      predictCmd = cg.lastPredictedCommand + 1; +    } +    else +    { +      // we have a new snapshot +      int i; +      int errorcode; +      qboolean error = qtrue; + +      // loop through the saved states queue +      for( i = cg.stateHead; i != cg.stateTail; +        i = ( i + 1 ) % NUM_SAVED_STATES ) +      { +        // if we find a predicted state whose commandTime matches the snapshot +        // player state's commandTime +        if( cg.savedPmoveStates[ i ].commandTime != +          cg.predictedPlayerState.commandTime ) +        { +          continue; +        } +        // 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; +      } + +      // if no saved states matched +      if( error ) +      { +        // do a full predict +        cg.lastPredictedCommand = 0; +        cg.stateTail = cg.stateHead; +        predictCmd = current - CMD_BACKUP + 1; +      } +    } + +    // keep track of the server time of the last snapshot so we +    // know when we're starting from a new one in future calls +    cg.lastServerTime = cg.physicsTime; +    stateIndex = cg.stateHead; +  } + +  // run cmds +  moved = qfalse; + +  for( cmdNum = current - CMD_BACKUP + 1; cmdNum <= current; cmdNum++ ) +  { +    // get the command +    trap_GetUserCmd( cmdNum, &cg_pmove.cmd ); + +    if( cg_pmove.pmove_fixed ) +      PM_UpdateViewAngles( cg_pmove.ps, &cg_pmove.cmd ); + +    // don't do anything if the time is before the snapshot player time +    if( cg_pmove.cmd.serverTime <= cg.predictedPlayerState.commandTime ) +      continue; + +    // don't do anything if the command was from a previous map_restart +    if( cg_pmove.cmd.serverTime > latestCmd.serverTime ) +      continue; + +    // check for a prediction error from last frame +    // on a lan, this will often be the exact value +    // from the snapshot, but on a wan we will have +    // to predict several commands to get to the point +    // we want to compare +    if( cg.predictedPlayerState.commandTime == oldPlayerState.commandTime ) +    { +      vec3_t  delta; +      float   len; + +      if( cg.thisFrameTeleport ) +      { +        // a teleport will not cause an error decay +        VectorClear( cg.predictedError ); + +        if( cg_showmiss.integer ) +          CG_Printf( "PredictionTeleport\n" ); + +        cg.thisFrameTeleport = qfalse; +      } +      else +      { +        vec3_t  adjusted; +        CG_AdjustPositionForMover( cg.predictedPlayerState.origin, +          cg.predictedPlayerState.groundEntityNum, cg.physicsTime, cg.oldTime, adjusted ); + +        if( cg_showmiss.integer ) +        { +          if( !VectorCompare( oldPlayerState.origin, adjusted ) ) +            CG_Printf("prediction error\n"); +        } + +        VectorSubtract( oldPlayerState.origin, adjusted, delta ); +        len = VectorLength( delta ); + +        if( len > 0.1 ) +        { +          if( cg_showmiss.integer ) +            CG_Printf( "Prediction miss: %f\n", len ); + +          if( cg_errorDecay.integer ) +          { +            int   t; +            float f; + +            t = cg.time - cg.predictedErrorTime; +            f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; + +            if( f < 0 ) +              f = 0; + +            if( f > 0 && cg_showmiss.integer ) +              CG_Printf( "Double prediction decay: %f\n", f ); + +            VectorScale( cg.predictedError, f, cg.predictedError ); +          } +          else +            VectorClear( cg.predictedError ); + +          VectorAdd( delta, cg.predictedError, cg.predictedError ); +          cg.predictedErrorTime = cg.oldTime; +        } +      } +    } + +    // don't predict gauntlet firing, which is only supposed to happen +    // when it actually inflicts damage +    for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) +      cg_pmove.autoWeaponHit[ i ] = qfalse; + +    if( cg_pmove.pmove_fixed ) +      cg_pmove.cmd.serverTime = ( ( cg_pmove.cmd.serverTime + pmove_msec.integer - 1 ) / +                                  pmove_msec.integer ) * pmove_msec.integer; + +    if( !cg_optimizePrediction.integer ) +    { +      Pmove( &cg_pmove ); +    } +    else if( cg_optimizePrediction.integer && ( cmdNum >= predictCmd || +      ( stateIndex + 1 ) % NUM_SAVED_STATES == cg.stateHead ) ) +    { +      Pmove( &cg_pmove ); +      // record the last predicted command +      cg.lastPredictedCommand = cmdNum; + +      // if we haven't run out of space in the saved states queue +      if( ( stateIndex + 1 ) % NUM_SAVED_STATES != cg.stateHead ) +      { +        // save the state for the false case ( of cmdNum >= predictCmd ) +        // in later calls to this function +        cg.savedPmoveStates[ stateIndex ] = *cg_pmove.ps; +        stateIndex = ( stateIndex + 1 ) % NUM_SAVED_STATES; +        cg.stateTail = stateIndex; +      } +    } +    else +    { +      *cg_pmove.ps = cg.savedPmoveStates[ stateIndex ]; +      stateIndex = ( stateIndex + 1 ) % NUM_SAVED_STATES; +    } + +    moved = qtrue; + +    // add push trigger movement effects +    CG_TouchTriggerPrediction( ); + +    // check for predictable events that changed from previous predictions +    //CG_CheckChangedPredictableEvents(&cg.predictedPlayerState); +  } + +  // adjust for the movement of the groundentity +  CG_AdjustPositionForMover( cg.predictedPlayerState.origin, +    cg.predictedPlayerState.groundEntityNum, +    cg.physicsTime, cg.time, cg.predictedPlayerState.origin ); + + +  // fire events and other transition triggered things +  CG_TransitionPlayerState( &cg.predictedPlayerState, &oldPlayerState ); + + +} diff --git a/src/cgame/cg_ptr.c b/src/cgame/cg_ptr.c new file mode 100644 index 0000000..1881087 --- /dev/null +++ b/src/cgame/cg_ptr.c @@ -0,0 +1,81 @@ +/* +=========================================================================== +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_scanner.c b/src/cgame/cg_scanner.c new file mode 100644 index 0000000..ab98b23 --- /dev/null +++ b/src/cgame/cg_scanner.c @@ -0,0 +1,365 @@ +/* +=========================================================================== +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" + +static entityPos_t   entityPositions; + +#define HUMAN_SCANNER_UPDATE_PERIOD 700 + +/* +============= +CG_UpdateEntityPositions + +Update this client's perception of entity positions +============= +*/ +void CG_UpdateEntityPositions( void ) +{ +  centity_t *cent = NULL; +  int       i; + +  if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_HUMANS ) +  { +    if( entityPositions.lastUpdateTime + HUMAN_SCANNER_UPDATE_PERIOD > cg.time ) +      return; +  } + +  VectorCopy( cg.refdef.vieworg, entityPositions.origin ); +  VectorCopy( cg.refdefViewAngles, entityPositions.vangles ); +  entityPositions.lastUpdateTime = cg.time; + +  entityPositions.numAlienBuildables = 0; +  entityPositions.numHumanBuildables = 0; +  entityPositions.numAlienClients = 0; +  entityPositions.numHumanClients = 0; + +  for( i = 0; i < cg.snap->numEntities; i++ ) +  { +    cent = &cg_entities[ cg.snap->entities[ i ].number ]; + +    if( cent->currentState.eType == ET_BUILDABLE ) +    { +      //TA: add to list of item positions (for creep) +      if( cent->currentState.modelindex2 == BIT_ALIENS ) +      { +        VectorCopy( cent->lerpOrigin, entityPositions.alienBuildablePos[ +            entityPositions.numAlienBuildables ] ); +        entityPositions.alienBuildableTimes[ +            entityPositions.numAlienBuildables ] = cent->miscTime; + +        if( entityPositions.numAlienBuildables < MAX_GENTITIES ) +          entityPositions.numAlienBuildables++; +      } +      else if( cent->currentState.modelindex2 == BIT_HUMANS ) +      { +        VectorCopy( cent->lerpOrigin, entityPositions.humanBuildablePos[ +            entityPositions.numHumanBuildables ] ); + +        if( entityPositions.numHumanBuildables < MAX_GENTITIES ) +          entityPositions.numHumanBuildables++; +      } +    } +    else if( cent->currentState.eType == ET_PLAYER ) +    { +      int team = cent->currentState.powerups & 0x00FF; + +      if( team == PTE_ALIENS ) +      { +        VectorCopy( cent->lerpOrigin, entityPositions.alienClientPos[ +            entityPositions.numAlienClients ] ); + +        if( entityPositions.numAlienClients < MAX_CLIENTS ) +          entityPositions.numAlienClients++; +      } +      else if( team == PTE_HUMANS ) +      { +        VectorCopy( cent->lerpOrigin, entityPositions.humanClientPos[ +            entityPositions.numHumanClients ] ); + +        if( entityPositions.numHumanClients < MAX_CLIENTS ) +          entityPositions.numHumanClients++; +      } +    } +  } +} + +#define STALKWIDTH  2.0f +#define BLIPX       16.0f +#define BLIPY       8.0f +#define FAR_ALPHA   0.8f +#define NEAR_ALPHA  1.2f + +/* +============= +CG_DrawBlips + +Draw blips and stalks for the human scanner +============= +*/ +static void CG_DrawBlips( rectDef_t *rect, vec3_t origin, vec4_t colour ) +{ +  vec3_t  drawOrigin; +  vec3_t  up = { 0, 0, 1 }; +  float   alphaMod = 1.0f; +  float   timeFractionSinceRefresh = 1.0f - +    ( (float)( cg.time - entityPositions.lastUpdateTime ) / +      (float)HUMAN_SCANNER_UPDATE_PERIOD ); +  vec4_t  localColour; + +  Vector4Copy( colour, localColour ); + +  RotatePointAroundVector( drawOrigin, up, origin, -entityPositions.vangles[ 1 ] - 90 ); +  drawOrigin[ 0 ] /= ( 2 * HELMET_RANGE / rect->w ); +  drawOrigin[ 1 ] /= ( 2 * HELMET_RANGE / rect->h ); +  drawOrigin[ 2 ] /= ( 2 * HELMET_RANGE / rect->w ); + +  alphaMod = FAR_ALPHA + +    ( ( drawOrigin[ 1 ] + ( rect->h / 2.0f ) ) / rect->h ) * ( NEAR_ALPHA - FAR_ALPHA ); + +  localColour[ 3 ] *= alphaMod; +  localColour[ 3 ] *= ( 0.5f + ( timeFractionSinceRefresh * 0.5f ) ); + +  if( localColour[ 3 ] > 1.0f ) +    localColour[ 3 ] = 1.0f; +  else if( localColour[ 3 ] < 0.0f ) +    localColour[ 3 ] = 0.0f; + +  trap_R_SetColor( localColour ); + +  if( drawOrigin[ 2 ] > 0 ) +    CG_DrawPic( rect->x + ( rect->w / 2 ) - ( STALKWIDTH / 2 ) - drawOrigin[ 0 ], +                rect->y + ( rect->h / 2 ) + drawOrigin[ 1 ] - drawOrigin[ 2 ], +                STALKWIDTH, drawOrigin[ 2 ], cgs.media.scannerLineShader ); +  else +    CG_DrawPic( rect->x + ( rect->w / 2 ) - ( STALKWIDTH / 2 ) - drawOrigin[ 0 ], +                rect->y + ( rect->h / 2 ) + drawOrigin[ 1 ], +                STALKWIDTH, -drawOrigin[ 2 ], cgs.media.scannerLineShader ); + +  CG_DrawPic( rect->x + ( rect->w / 2 ) - ( BLIPX / 2 ) - drawOrigin[ 0 ], +              rect->y + ( rect->h / 2 ) - ( BLIPY / 2 ) + drawOrigin[ 1 ] - drawOrigin[ 2 ], +              BLIPX, BLIPY, cgs.media.scannerBlipShader ); +  trap_R_SetColor( NULL ); +} + +#define BLIPX2  24.0f +#define BLIPY2  24.0f + +/* +============= +CG_DrawDir + +Draw dot marking the direction to an enemy +============= +*/ +static void CG_DrawDir( rectDef_t *rect, vec3_t origin, vec4_t colour ) +{ +  vec3_t  drawOrigin; +  vec3_t  noZOrigin; +  vec3_t  normal, antinormal, normalDiff; +  vec3_t  view, noZview; +  vec3_t  up  = { 0.0f, 0.0f,   1.0f }; +  vec3_t  top = { 0.0f, -1.0f,  0.0f }; +  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 ); + +  AngleVectors( entityPositions.vangles, view, NULL, NULL ); + +  ProjectPointOnPlane( noZOrigin, origin, normal ); +  ProjectPointOnPlane( noZview, view, normal ); +  VectorNormalize( noZOrigin ); +  VectorNormalize( noZview ); + +  //calculate the angle between the images of the blip and the view +  angle = RAD2DEG( acos( DotProduct( noZOrigin, noZview ) ) ); +  CrossProduct( noZOrigin, noZview, antinormal ); +  VectorNormalize( antinormal ); + +  //decide which way to rotate +  VectorSubtract( normal, antinormal, normalDiff ); +  if( VectorLength( normalDiff ) < 1.0f ) +    angle = 360.0f - angle; + +  RotatePointAroundVector( drawOrigin, up, top, angle ); + +  trap_R_SetColor( colour ); +  CG_DrawPic( rect->x + ( rect->w / 2 ) - ( BLIPX2 / 2 ) - drawOrigin[ 0 ] * ( rect->w / 2 ), +              rect->y + ( rect->h / 2 ) - ( BLIPY2 / 2 ) + drawOrigin[ 1 ] * ( rect->h / 2 ), +              BLIPX2, BLIPY2, cgs.media.scannerBlipShader ); +  trap_R_SetColor( NULL ); +} + +/* +============= +CG_AlienSense +============= +*/ +void CG_AlienSense( rectDef_t *rect ) +{ +  int     i; +  vec3_t  origin; +  vec3_t  relOrigin; +  vec4_t  buildable = { 1.0f, 0.0f, 0.0f, 0.7f }; +  vec4_t  client    = { 0.0f, 0.0f, 1.0f, 0.7f }; + +  VectorCopy( entityPositions.origin, origin ); + +  //draw human buildables +  for( i = 0; i < entityPositions.numHumanBuildables; i++ ) +  { +    VectorClear( relOrigin ); +    VectorSubtract( entityPositions.humanBuildablePos[ i ], origin, relOrigin ); + +    if( VectorLength( relOrigin ) < ALIENSENSE_RANGE ) +      CG_DrawDir( rect, relOrigin, buildable ); +  } + +  //draw human clients +  for( i = 0; i < entityPositions.numHumanClients; i++ ) +  { +    VectorClear( relOrigin ); +    VectorSubtract( entityPositions.humanClientPos[ i ], origin, relOrigin ); + +    if( VectorLength( relOrigin ) < ALIENSENSE_RANGE ) +      CG_DrawDir( rect, relOrigin, client ); +  } +} + +/* +============= +CG_Scanner +============= +*/ +void CG_Scanner( rectDef_t *rect, qhandle_t shader, vec4_t color ) +{ +  int     i; +  vec3_t  origin; +  vec3_t  relOrigin; +  vec4_t  hIabove; +  vec4_t  hIbelow; +  vec4_t  aIabove = { 1.0f, 0.0f, 0.0f, 0.75f }; +  vec4_t  aIbelow = { 1.0f, 0.0f, 0.0f, 0.5f }; + +  Vector4Copy( color, hIabove ); +  hIabove[ 3 ] *= 1.5f; +  Vector4Copy( color, hIbelow ); + +  VectorCopy( entityPositions.origin, origin ); + +  //draw human buildables below scanner plane +  for( i = 0; i < entityPositions.numHumanBuildables; i++ ) +  { +    VectorClear( relOrigin ); +    VectorSubtract( entityPositions.humanBuildablePos[ i ], origin, relOrigin ); + +    if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] < 0 ) ) +      CG_DrawBlips( rect, relOrigin, hIbelow ); +  } + +  //draw alien buildables below scanner plane +  for( i = 0; i < entityPositions.numAlienBuildables; i++ ) +  { +    VectorClear( relOrigin ); +    VectorSubtract( entityPositions.alienBuildablePos[ i ], origin, relOrigin ); + +    if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] < 0 ) ) +      CG_DrawBlips( rect, relOrigin, aIbelow ); +  } + +  //draw human clients below scanner plane +  for( i = 0; i < entityPositions.numHumanClients; i++ ) +  { +    VectorClear( relOrigin ); +    VectorSubtract( entityPositions.humanClientPos[ i ], origin, relOrigin ); + +    if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] < 0 ) ) +      CG_DrawBlips( rect, relOrigin, hIbelow ); +  } + +  //draw alien buildables below scanner plane +  for( i = 0; i < entityPositions.numAlienClients; i++ ) +  { +    VectorClear( relOrigin ); +    VectorSubtract( entityPositions.alienClientPos[ i ], origin, relOrigin ); + +    if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] < 0 ) ) +      CG_DrawBlips( rect, relOrigin, aIbelow ); +  } + +  if( !cg_disableScannerPlane.integer ) +  { +    trap_R_SetColor( color ); +    CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); +    trap_R_SetColor( NULL ); +  } + +  //draw human buildables above scanner plane +  for( i = 0; i < entityPositions.numHumanBuildables; i++ ) +  { +    VectorClear( relOrigin ); +    VectorSubtract( entityPositions.humanBuildablePos[ i ], origin, relOrigin ); + +    if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] > 0 ) ) +      CG_DrawBlips( rect, relOrigin, hIabove ); +  } + +  //draw alien buildables above scanner plane +  for( i = 0; i < entityPositions.numAlienBuildables; i++ ) +  { +    VectorClear( relOrigin ); +    VectorSubtract( entityPositions.alienBuildablePos[ i ], origin, relOrigin ); + +    if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] > 0 ) ) +      CG_DrawBlips( rect, relOrigin, aIabove ); +  } + +  //draw human clients above scanner plane +  for( i = 0; i < entityPositions.numHumanClients; i++ ) +  { +    VectorClear( relOrigin ); +    VectorSubtract( entityPositions.humanClientPos[ i ], origin, relOrigin ); + +    if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] > 0 ) ) +      CG_DrawBlips( rect, relOrigin, hIabove ); +  } + +  //draw alien clients above scanner plane +  for( i = 0; i < entityPositions.numAlienClients; i++ ) +  { +    VectorClear( relOrigin ); +    VectorSubtract( entityPositions.alienClientPos[ i ], origin, relOrigin ); + +    if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] > 0 ) ) +      CG_DrawBlips( rect, relOrigin, aIabove ); +  } +} diff --git a/src/cgame/cg_servercmds.c b/src/cgame/cg_servercmds.c new file mode 100644 index 0000000..7fb3e06 --- /dev/null +++ b/src/cgame/cg_servercmds.c @@ -0,0 +1,1016 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +// cg_servercmds.c -- reliably sequenced text commands sent by the server +// these are processed at snapshot transition time, so there will definately +// be a valid snapshot this frame + + +#include "cg_local.h" + +/* +================= +CG_ParseScores + +================= +*/ +static void CG_ParseScores( void ) +{ +  int   i; + +  cg.numScores = atoi( CG_Argv( 1 ) ); + +  if( cg.numScores > MAX_CLIENTS ) +    cg.numScores = MAX_CLIENTS; + +  cg.teamScores[ 0 ] = atoi( CG_Argv( 2 ) ); +  cg.teamScores[ 1 ] = atoi( CG_Argv( 3 ) ); + +  memset( cg.scores, 0, sizeof( cg.scores ) ); + +  if( cg_debugRandom.integer ) +    CG_Printf( "cg.numScores: %d\n", cg.numScores ); + +  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 ) ); + +    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; +  } +} + +/* +================= +CG_ParseTeamInfo + +================= +*/ +static void CG_ParseTeamInfo( void ) +{ +  int   i; +  int   client; + +  numSortedTeamPlayers = atoi( CG_Argv( 1 ) ); + +  for( i = 0; i < numSortedTeamPlayers; i++ ) +  { +    client = atoi( CG_Argv( i * 6 + 2 ) ); + +    sortedTeamPlayers[ i ] = client; + +    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 ) ); +  } +} + + +/* +================ +CG_ParseServerinfo + +This is called explicitly when the gamestate is first received, +and whenever the server updates any serverinfo flagged cvars +================ +*/ +void CG_ParseServerinfo( void ) +{ +  const char  *info; +  char  *mapname; + +  info = CG_ConfigString( CS_SERVERINFO ); +  cgs.timelimit = atoi( Info_ValueForKey( info, "timelimit" ) ); +  cgs.maxclients = atoi( Info_ValueForKey( info, "sv_maxclients" ) ); +  cgs.markDeconstruct = atoi( Info_ValueForKey( info, "g_markDeconstruct" ) ); +  mapname = Info_ValueForKey( info, "mapname" ); +  Com_sprintf( cgs.mapname, sizeof( cgs.mapname ), "maps/%s.bsp", mapname ); +} + +/* +================== +CG_ParseWarmup +================== +*/ +static void CG_ParseWarmup( void ) +{ +  const char  *info; +  int         warmup; + +  info = CG_ConfigString( CS_WARMUP ); + +  warmup = atoi( info ); +  cg.warmupCount = -1; + +  if( warmup == 0 && cg.warmup ) +  { +  } + +  cg.warmup = warmup; +} + +/* +================ +CG_SetConfigValues + +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 ); + +  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 ); + +  cgs.levelStartTime = atoi( CG_ConfigString( CS_LEVEL_START_TIME ) ); +  cg.warmup = atoi( CG_ConfigString( CS_WARMUP ) ); +} + + +/* +===================== +CG_ShaderStateChanged +===================== +*/ +void CG_ShaderStateChanged( void ) +{ +  char        originalShader[ MAX_QPATH ]; +  char        newShader[ MAX_QPATH ]; +  char        timeOffset[ 16 ]; +  const char  *o; +  char        *n, *t; + +  o = CG_ConfigString( CS_SHADERSTATE ); + +  while( o && *o ) +  { +    n = strstr( o, "=" ); + +    if( n && *n ) +    { +      strncpy( originalShader, o, n - o ); +      originalShader[ n - o ] = 0; +      n++; +      t = strstr( n, ":" ); + +      if( t && *t ) +      { +        strncpy( newShader, n, t - n ); +        newShader[ t - n ] = 0; +      } +      else +        break; + +      t++; +      o = strstr( t, "@" ); + +      if( o ) +      { +        strncpy( timeOffset, t, o - t ); +        timeOffset[ o - t ] = 0; +        o++; +        trap_R_RemapShader( originalShader, newShader, timeOffset ); +      } +    } +    else +      break; +  } +} + +/* +================ +CG_AnnounceAlienStageTransistion +================ +*/ +static void CG_AnnounceAlienStageTransistion( stage_t from, stage_t to ) +{ +  if( cg.predictedPlayerState.stats[ STAT_PTEAM ] != PTE_ALIENS ) +    return; + +  trap_S_StartLocalSound( cgs.media.alienStageTransition, CHAN_ANNOUNCER ); +  CG_CenterPrint( "We have evolved!", 200, GIANTCHAR_WIDTH * 4 ); +} + +/* +================ +CG_AnnounceHumanStageTransistion +================ +*/ +static void CG_AnnounceHumanStageTransistion( stage_t from, stage_t to ) +{ +  if( cg.predictedPlayerState.stats[ STAT_PTEAM ] != PTE_HUMANS ) +    return; + +  trap_S_StartLocalSound( cgs.media.humanStageTransition, CHAN_ANNOUNCER ); +  CG_CenterPrint( "Reinforcements have arrived!", 200, GIANTCHAR_WIDTH * 4 ); +} + +/* +================ +CG_ConfigStringModified + +================ +*/ +static void CG_ConfigStringModified( void ) +{ +  const char  *str; +  int         num; + +  num = atoi( CG_Argv( 1 ) ); + +  // get the gamestate from the client system, which will have the +  // new configstring already integrated +  trap_GetGameState( &cgs.gameState ); + +  // look up the individual string that was modified +  str = CG_ConfigString( num ); + +  // do something with it if necessary +  if( num == CS_MUSIC ) +    CG_StartMusic( ); +  else if( num == CS_SERVERINFO ) +    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 ) +  { +    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( cgs.voteTime ) +      trap_Cvar_Set( "ui_voteActive", "1" ); +    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; +  } +  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 ) +  { +    int cs_offset = num - CS_TEAMVOTE_TIME; + +    cgs.teamVoteTime[ cs_offset ] = atoi( str ); +    cgs.teamVoteModified[ cs_offset ] = qtrue; + +    if( cs_offset == 0 ) +    { +      if( cgs.teamVoteTime[ cs_offset ] ) +        trap_Cvar_Set( "ui_humanTeamVoteActive", "1" ); +      else +        trap_Cvar_Set( "ui_humanTeamVoteActive", "0" ); +    } +    else if( cs_offset == 1 ) +    { +      if( cgs.teamVoteTime[ cs_offset ] ) +        trap_Cvar_Set( "ui_alienTeamVoteActive", "1" ); +      else +        trap_Cvar_Set( "ui_alienTeamVoteActive", "0" ); +    } +  } +  else if( num >= CS_TEAMVOTE_YES && num <= CS_TEAMVOTE_YES + 1 ) +  { +    cgs.teamVoteYes[ num - CS_TEAMVOTE_YES ] = atoi( str ); +    cgs.teamVoteModified[ num - CS_TEAMVOTE_YES ] = qtrue; +  } +  else if( num >= CS_TEAMVOTE_NO && num <= CS_TEAMVOTE_NO + 1 ) +  { +    cgs.teamVoteNo[ num - CS_TEAMVOTE_NO ] = atoi( str ); +    cgs.teamVoteModified[ num - CS_TEAMVOTE_NO ] = qtrue; +  } +  else if( num >= CS_TEAMVOTE_STRING && num <= CS_TEAMVOTE_STRING + 1 ) +  { +    Q_strncpyz( cgs.teamVoteString[ num - CS_TEAMVOTE_STRING ], str, +      sizeof( cgs.teamVoteString[ num - CS_TEAMVOTE_STRING ] ) ); +  } +  else if( num == CS_INTERMISSION ) +    cg.intermissionStarted = atoi( str ); +  else if( num >= CS_MODELS && num < CS_MODELS+MAX_MODELS ) +    cgs.gameModels[ num - CS_MODELS ] = trap_R_RegisterModel( str ); +  else if( num >= CS_SHADERS && num < CS_SHADERS+MAX_GAME_SHADERS ) +    cgs.gameShaders[ num - CS_SHADERS ] = trap_R_RegisterShader( str ); +  else if( num >= CS_PARTICLE_SYSTEMS && num < CS_PARTICLE_SYSTEMS+MAX_GAME_PARTICLE_SYSTEMS ) +    cgs.gameParticleSystems[ num - CS_PARTICLE_SYSTEMS ] = CG_RegisterParticleSystem( (char *)str ); +  else if( num >= CS_SOUNDS && num < CS_SOUNDS+MAX_SOUNDS ) +  { +    if( str[ 0 ] != '*' ) +    {  // player specific sounds don't register here +      cgs.gameSounds[ num - CS_SOUNDS ] = trap_S_RegisterSound( str, qfalse ); +    } +  } +  else if( num >= CS_PLAYERS && num < CS_PLAYERS+MAX_CLIENTS ) +  { +    CG_NewClientInfo( num - CS_PLAYERS ); +    CG_BuildSpectatorString( ); +  } +  else if( num == CS_WINNER ) +  { +    trap_Cvar_Set( "ui_winner", str ); +  } +  else if( num == CS_SHADERSTATE ) +  { +    CG_ShaderStateChanged( ); +  } +} + + +/* +=============== +CG_MapRestart + +The server has issued a map_restart, so the next snapshot +is completely new and should not be interpolated to. + +A tournement restart will clear everything, but doesn't +require a reload of all the media +=============== +*/ +static void CG_MapRestart( void ) +{ +  if( cg_showmiss.integer ) +    CG_Printf( "CG_MapRestart\n" ); + +  CG_InitMarkPolys( ); + +  // make sure the "3 frags left" warnings play again +  cg.fraglimitWarnings = 0; + +  cg.timelimitWarnings = 0; + +  cg.intermissionStarted = qfalse; + +  cgs.voteTime = 0; + +  cg.mapRestart = qtrue; + +  CG_StartMusic( ); + +  trap_S_ClearLoopingSounds( qtrue ); + +  // 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 ) +{ +  const char *cmd = NULL;	// command to send +  const char *longMsg   = NULL;	// command parameter +  const char *shortMsg  = NULL;	// non-modal version of message +  CG_SetUIVars( ); + +  // string literals have static storage duration, this is safe, +  // cleaner and much more readable. +  switch( menu ) +  { +    case MN_TEAM: +      cmd       = "menu tremulous_teamselect\n"; +      break; + +    case MN_A_CLASS: +      cmd       = "menu tremulous_alienclass\n"; +      break; + +    case MN_H_SPAWN: +      cmd       = "menu tremulous_humanitem\n"; +      break; + +    case MN_A_BUILD: +      cmd       = "menu tremulous_alienbuild\n"; +      break; + +    case MN_H_BUILD: +      cmd       = "menu tremulous_humanbuild\n"; +      break; + +    case MN_H_ARMOURY: +      cmd       = "menu tremulous_humanarmoury\n"; +      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"; +      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"; +      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"; +      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"; +      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"; +      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"; +      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"; +      break; + +    case MN_H_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"; +      break; + +    case MN_H_REACTOR: +      longMsg   = "There can only be one Reactor. Destroy the existing one if you " +                  "wish to move it."; +      shortMsg  = "There can only be one Reactor\n"; +      cmd       = "menu tremulous_human_dialog\n"; +      break; + +    case MN_H_REPEATER: +      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"; +      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"; +      break; + +    case MN_H_RPTWARN2: +      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"; +      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"; +      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"; +      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"; +      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"; +      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"; +      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"; +      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"; +      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"; +      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"; +      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"; +      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"; +      break; + +    case MN_A_HOVEL: +      longMsg   = "There can only be one Hovel. Destroy the existing one if you " +                  "wish to move it."; +      shortMsg  = "There can only be one Hovel\n"; +      cmd       = "menu tremulous_alien_dialog\n"; +      break; + +    case MN_A_NOASSERT: +      longMsg   = "The Overmind cannot control any more structures. Destroy 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"; +      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"; +      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"; +      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"; +      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"; +      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"; +      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"; +      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"; +      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"; +      break; + +    default: +      Com_Printf( "cgame: debug: no such menu %d\n", menu ); +  } + +	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 ); + +			trap_SendConsoleCommand( cmd ); +		} +	} +  else +  { +		// There is short message and player wants it +		CG_Printf( shortMsg ); +	} +} + +/* +================= +CG_ServerCommand + +The string has been tokenized and can be retrieved with +Cmd_Argc() / Cmd_Argv() +================= +*/ +static void CG_ServerCommand( void ) +{ +  const char  *cmd; +  char        text[ MAX_SAY_TEXT ]; + +  cmd = CG_Argv( 0 ); + +  if( !cmd[ 0 ] ) +  { +    // server claimed the command +    return; +  } + +  if( !strcmp( cmd, "cp" ) ) +  { +    CG_CenterPrint( CG_Argv( 1 ), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); +    return; +  } + +  if( !strcmp( cmd, "cs" ) ) +  { +    CG_ConfigStringModified( ); +    return; +  } + +  if( !strcmp( cmd, "print" ) ) +  { +    CG_Printf( "%s", CG_Argv( 1 ) ); +    return; +  } + +  if( !strcmp( cmd, "chat" ) ) +  { +    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 ); +      CG_Printf( "%s\n", text ); +    } + +    return; +  } + +  if( !strcmp( cmd, "tchat" ) ) +  { +    Q_strncpyz( text, CG_Argv( 1 ), MAX_SAY_TEXT ); +    if( Q_stricmpn( text, "[skipnotify]", 12 ) ) +    { +      if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) +        trap_S_StartLocalSound( cgs.media.alienTalkSound, CHAN_LOCAL_SOUND ); +      else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_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; +  } + +  if( !strcmp( cmd, "scores" ) ) +  { +    CG_ParseScores( ); +    return; +  } + +  if( !strcmp( cmd, "tinfo" ) ) +  { +    CG_ParseTeamInfo( ); +    return; +  } + +  if( !strcmp( cmd, "map_restart" ) ) +  { +    CG_MapRestart( ); +    return; +  } + +  if( Q_stricmp( cmd, "remapShader" ) == 0 ) +  { +    if( trap_Argc( ) == 4 ) +      trap_R_RemapShader( CG_Argv( 1 ), CG_Argv( 2 ), CG_Argv( 3 ) ); +  } + +  // clientLevelShot is sent before taking a special screenshot for +  // the menu system during development +  if( !strcmp( cmd, "clientLevelShot" ) ) +  { +    cg.levelShot = qtrue; +    return; +  } + +  //the server has triggered a menu +  if( !strcmp( cmd, "servermenu" ) ) +  { +    if( trap_Argc( ) == 2 && !cg.demoPlayback ) +      CG_Menu( atoi( CG_Argv( 1 ) ) ); + +    return; +  } + +  //the server thinks this client should close all menus +  if( !strcmp( cmd, "serverclosemenus" ) ) +  { +    trap_SendConsoleCommand( "closemenus\n" ); +    return; +  } + +  //poison cloud effect needs to be reliable +  if( !strcmp( cmd, "poisoncloud" ) ) +  { +    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 ); +    } + +    return; +  } + +  if( !strcmp( cmd, "weaponswitch" ) ) +  { +    CG_Printf( "client weaponswitch\n" ); +    if( trap_Argc( ) == 2 ) +    { +      cg.weaponSelect = atoi( CG_Argv( 1 ) ); +      cg.weaponSelectTime = cg.time; +    } + +    return; +  } + +  // server requests a ptrc +  if( !strcmp( cmd, "ptrcrequest" ) ) +  { +    int   code = CG_ReadPTRCode( ); + +    trap_SendClientCommand( va( "ptrcverify %d", code ) ); +    return; +  } + +  // server issues a ptrc +  if( !strcmp( cmd, "ptrcissue" ) ) +  { +    if( trap_Argc( ) == 2 ) +    { +      int code = atoi( CG_Argv( 1 ) ); + +      CG_WritePTRCode( code ); +    } + +    return; +  } + +  // reply to ptrcverify +  if( !strcmp( cmd, "ptrcconfirm" ) ) +  { +    trap_SendConsoleCommand( "menu ptrc_popmenu\n" ); + +    return; +  } + +  CG_Printf( "Unknown client game command: %s\n", cmd ); +} + + +/* +==================== +CG_ExecuteNewServerCommands + +Execute all of the server commands that were received along +with this this snapshot. +==================== +*/ +void CG_ExecuteNewServerCommands( int latestSequence ) +{ +  while( cgs.serverCommandSequence < latestSequence ) +  { +    if( trap_GetServerCommand( ++cgs.serverCommandSequence ) ) +      CG_ServerCommand( ); +  } +} diff --git a/src/cgame/cg_snapshot.c b/src/cgame/cg_snapshot.c new file mode 100644 index 0000000..f439a69 --- /dev/null +++ b/src/cgame/cg_snapshot.c @@ -0,0 +1,411 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +// cg_snapshot.c -- things that happen on snapshot transition, +// not necessarily every single rendered frame + + +#include "cg_local.h" + +/* +================== +CG_ResetEntity +================== +*/ +static void CG_ResetEntity( centity_t *cent ) +{ +  // if the previous snapshot this entity was updated in is at least +  // an event window back in time then we can reset the previous event +  if( cent->snapShotTime < cg.time - EVENT_VALID_MSEC ) +    cent->previousEvent = 0; + +  cent->trailTime = cg.snap->serverTime; + +  VectorCopy( cent->currentState.origin, cent->lerpOrigin ); +  VectorCopy( cent->currentState.angles, cent->lerpAngles ); + +  if( cent->currentState.eType == ET_PLAYER ) +    CG_ResetPlayerEntity( cent ); +} + +/* +=============== +CG_TransitionEntity + +cent->nextState is moved to cent->currentState and events are fired +=============== +*/ +static void CG_TransitionEntity( centity_t *cent ) +{ +  cent->currentState = cent->nextState; +  cent->currentValid = qtrue; + +  // reset if the entity wasn't in the last frame or was teleported +  if( !cent->interpolate ) +    CG_ResetEntity( cent ); + +  // clear the next state.  if will be set by the next CG_SetNextSnap +  cent->interpolate = qfalse; + +  // check for events +  CG_CheckEvents( cent ); +} + + +/* +================== +CG_SetInitialSnapshot + +This will only happen on the very first snapshot, or +on tourney restarts.  All other times will use +CG_TransitionSnapshot instead. + +FIXME: Also called by map_restart? +================== +*/ +void CG_SetInitialSnapshot( snapshot_t *snap ) +{ +  int           i; +  centity_t     *cent; +  entityState_t *state; + +  cg.snap = snap; + +  BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].currentState, qfalse ); + +  // sort out solid entities +  CG_BuildSolidList( ); + +  CG_ExecuteNewServerCommands( snap->serverCommandSequence ); + +  // set our local weapon selection pointer to +  // what the server has indicated the current weapon is +  CG_Respawn( ); + +  for( i = 0; i < cg.snap->numEntities; i++ ) +  { +    state = &cg.snap->entities[ i ]; +    cent = &cg_entities[ state->number ]; + +    memcpy( ¢->currentState, state, sizeof( entityState_t ) ); +    //cent->currentState = *state; +    cent->interpolate = qfalse; +    cent->currentValid = qtrue; + +    CG_ResetEntity( cent ); + +    // check for events +    CG_CheckEvents( cent ); +  } +} + + +/* +=================== +CG_TransitionSnapshot + +The transition point from snap to nextSnap has passed +=================== +*/ +static void CG_TransitionSnapshot( void ) +{ +  centity_t   *cent; +  snapshot_t  *oldFrame; +  int         i; + +  if( !cg.snap ) +    CG_Error( "CG_TransitionSnapshot: NULL cg.snap" ); + +  if( !cg.nextSnap ) +    CG_Error( "CG_TransitionSnapshot: NULL cg.nextSnap" ); + +  // 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++ ) +  { +    cent = &cg_entities[ cg.snap->entities[ i ].number ]; +    cent->currentValid = qfalse; +  } + +  // move nextSnap to snap and do the transitions +  oldFrame = cg.snap; +  cg.snap = cg.nextSnap; + +  BG_PlayerStateToEntityState( &cg.snap->ps, &cg_entities[ cg.snap->ps.clientNum ].currentState, qfalse ); +  cg_entities[ cg.snap->ps.clientNum ].interpolate = qfalse; + +  for( i = 0; i < cg.snap->numEntities; i++ ) +  { +    cent = &cg_entities[ cg.snap->entities[ i ].number ]; +    CG_TransitionEntity( cent ); + +    // remember time of snapshot this entity was last updated in +    cent->snapShotTime = cg.snap->serverTime; +  } + +  cg.nextSnap = NULL; + +  // check for playerstate transition events +  if( oldFrame ) +  { +    playerState_t *ops, *ps; + +    ops = &oldFrame->ps; +    ps = &cg.snap->ps; +    // teleporting checks are irrespective of prediction +    if( ( ps->eFlags ^ ops->eFlags ) & EF_TELEPORT_BIT ) +      cg.thisFrameTeleport = qtrue; // will be cleared by prediction code + +    // if we are not doing client side movement prediction for any +    // reason, then the client events and view changes will be issued now +    if( cg.demoPlayback || ( cg.snap->ps.pm_flags & PMF_FOLLOW ) || +        cg_nopredict.integer || cg_synchronousClients.integer ) +      CG_TransitionPlayerState( ps, ops ); +  } +} + + +/* +=================== +CG_SetNextSnap + +A new snapshot has just been read in from the client system. +=================== +*/ +static void CG_SetNextSnap( snapshot_t *snap ) +{ +  int           num; +  entityState_t *es; +  centity_t     *cent; + +  cg.nextSnap = snap; + +  BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].nextState, qfalse ); +  cg_entities[ cg.snap->ps.clientNum ].interpolate = qtrue; + +  // check for extrapolation errors +  for( num = 0 ; num < snap->numEntities ; num++ ) +  { +    es = &snap->entities[ num ]; +    cent = &cg_entities[ es->number ]; + +    memcpy( ¢->nextState, es, sizeof( entityState_t ) ); +    //cent->nextState = *es; + +    // if this frame is a teleport, or the entity wasn't in the +    // previous frame, don't interpolate +    if( !cent->currentValid || ( ( cent->currentState.eFlags ^ es->eFlags ) & EF_TELEPORT_BIT ) ) +      cent->interpolate = qfalse; +    else +      cent->interpolate = qtrue; +  } + +  // if the next frame is a teleport for the playerstate, we +  // can't interpolate during demos +  if( cg.snap && ( ( snap->ps.eFlags ^ cg.snap->ps.eFlags ) & EF_TELEPORT_BIT ) ) +    cg.nextFrameTeleport = qtrue; +  else +    cg.nextFrameTeleport = qfalse; + +  // if changing follow mode, don't interpolate +  if( cg.nextSnap->ps.clientNum != cg.snap->ps.clientNum ) +    cg.nextFrameTeleport = qtrue; + +  // if changing server restarts, don't interpolate +  if( ( cg.nextSnap->snapFlags ^ cg.snap->snapFlags ) & SNAPFLAG_SERVERCOUNT ) +    cg.nextFrameTeleport = qtrue; + +  // sort out solid entities +  CG_BuildSolidList( ); +} + + +/* +======================== +CG_ReadNextSnapshot + +This is the only place new snapshots are requested +This may increment cgs.processedSnapshotNum multiple +times if the client system fails to return a +valid snapshot. +======================== +*/ +static snapshot_t *CG_ReadNextSnapshot( void ) +{ +  qboolean    r; +  snapshot_t  *dest; + +  if( cg.latestSnapshotNum > cgs.processedSnapshotNum + 1000 ) +  { +    CG_Printf( "WARNING: CG_ReadNextSnapshot: way out of range, %i > %i", +      cg.latestSnapshotNum, cgs.processedSnapshotNum ); +  } + +  while( cgs.processedSnapshotNum < cg.latestSnapshotNum ) +  { +    // decide which of the two slots to load it into +    if( cg.snap == &cg.activeSnapshots[ 0 ] ) +      dest = &cg.activeSnapshots[ 1 ]; +    else +      dest = &cg.activeSnapshots[ 0 ]; + +    // try to read the snapshot from the client system +    cgs.processedSnapshotNum++; +    r = trap_GetSnapshot( cgs.processedSnapshotNum, dest ); + +    // FIXME: why would trap_GetSnapshot return a snapshot with the same server time +    if( cg.snap && r && dest->serverTime == cg.snap->serverTime ) +    { +      //continue; +    } + +    // if it succeeded, return +    if( r ) +    { +      CG_AddLagometerSnapshotInfo( dest ); +      return dest; +    } + +    // a GetSnapshot will return failure if the snapshot +    // never arrived, or  is so old that its entities +    // have been shoved off the end of the circular +    // buffer in the client system. + +    // record as a dropped packet +    CG_AddLagometerSnapshotInfo( NULL ); + +    // If there are additional snapshots, continue trying to +    // read them. +  } + +  // nothing left to read +  return NULL; +} + + +/* +============ +CG_ProcessSnapshots + +We are trying to set up a renderable view, so determine +what the simulated time is, and try to get snapshots +both before and after that time if available. + +If we don't have a valid cg.snap after exiting this function, +then a 3D game view cannot be rendered.  This should only happen +right after the initial connection.  After cg.snap has been valid +once, it will never turn invalid. + +Even if cg.snap is valid, cg.nextSnap may not be, if the snapshot +hasn't arrived yet (it becomes an extrapolating situation instead +of an interpolating one) + +============ +*/ +void CG_ProcessSnapshots( void ) +{ +  snapshot_t  *snap; +  int         n; + +  // see what the latest snapshot the client system has is +  trap_GetCurrentSnapshotNumber( &n, &cg.latestSnapshotTime ); + +  if( n != cg.latestSnapshotNum ) +  { +    if( n < cg.latestSnapshotNum ) +    { +      // this should never happen +      CG_Error( "CG_ProcessSnapshots: n < cg.latestSnapshotNum" ); +    } + +    cg.latestSnapshotNum = n; +  } + +  // If we have yet to receive a snapshot, check for it. +  // Once we have gotten the first snapshot, cg.snap will +  // always have valid data for the rest of the game +  while( !cg.snap ) +  { +    snap = CG_ReadNextSnapshot( ); + +    if( !snap ) +    { +      // we can't continue until we get a snapshot +      return; +    } + +    // set our weapon selection to what +    // the playerstate is currently using +    if( !( snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) ) +      CG_SetInitialSnapshot( snap ); +  } + +  // loop until we either have a valid nextSnap with a serverTime +  // greater than cg.time to interpolate towards, or we run +  // out of available snapshots +  do +  { +    // if we don't have a nextframe, try and read a new one in +    if( !cg.nextSnap ) +    { +      snap = CG_ReadNextSnapshot( ); + +      // if we still don't have a nextframe, we will just have to +      // extrapolate +      if( !snap ) +        break; + +      CG_SetNextSnap( snap ); + +      // if time went backwards, we have a level restart +      if( cg.nextSnap->serverTime < cg.snap->serverTime ) +        CG_Error( "CG_ProcessSnapshots: Server time went backwards" ); +    } + +    // if our time is < nextFrame's, we have a nice interpolating state +    if( cg.time >= cg.snap->serverTime && cg.time < cg.nextSnap->serverTime ) +      break; + +    // we have passed the transition from nextFrame to frame +    CG_TransitionSnapshot( ); +  } while( 1 ); + +  // assert our valid conditions upon exiting +  if( cg.snap == NULL ) +    CG_Error( "CG_ProcessSnapshots: cg.snap == NULL" ); + +  if( cg.time < cg.snap->serverTime ) +  { +    // this can happen right after a vid_restart +    cg.time = cg.snap->serverTime; +  } + +  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 new file mode 100644 index 0000000..0893ebc --- /dev/null +++ b/src/cgame/cg_syscalls.asm @@ -0,0 +1,115 @@ +code + +equ trap_Print                        -1 +equ trap_Error                        -2 +equ trap_Milliseconds                 -3 +equ trap_Cvar_Register                -4 +equ trap_Cvar_Update                  -5 +equ trap_Cvar_Set                     -6 +equ trap_Cvar_VariableStringBuffer    -7 +equ trap_Argc                         -8 +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_FCloseFile                -14 +equ trap_SendConsoleCommand           -15 +equ trap_AddCommand                   -16 +equ trap_SendClientCommand            -17 +equ trap_UpdateScreen                 -18 +equ trap_CM_LoadMap                   -19 +equ trap_CM_NumInlineModels           -20 +equ trap_CM_InlineModel               -21 +equ trap_CM_LoadModel                 -22 +equ trap_CM_TempBoxModel              -23 +equ trap_CM_PointContents             -24 +equ trap_CM_TransformedPointContents  -25 +equ trap_CM_BoxTrace                  -26 +equ trap_CM_TransformedBoxTrace       -27 +equ trap_CM_MarkFragments             -28 +equ trap_S_StartSound                 -29 +equ trap_S_StartLocalSound            -30 +equ trap_S_ClearLoopingSounds         -31 +equ trap_S_AddLoopingSound            -32 +equ trap_S_UpdateEntityPosition       -33 +equ trap_S_Respatialize               -34 +equ trap_S_RegisterSound              -35 +equ trap_S_StartBackgroundTrack       -36 +equ trap_R_LoadWorldMap               -37 +equ trap_R_RegisterModel              -38 +equ trap_R_RegisterSkin               -39 +equ trap_R_RegisterShader             -40 +equ trap_R_ClearScene                 -41 +equ trap_R_AddRefEntityToScene        -42 +equ trap_R_AddPolyToScene             -43 +equ trap_R_AddLightToScene            -44 +equ trap_R_RenderScene                -45 +equ trap_R_SetColor                   -46 +equ trap_R_DrawStretchPic             -47 +equ trap_R_ModelBounds                -48 +equ trap_R_LerpTag                    -49 +equ trap_GetGlconfig                  -50 +equ trap_GetGameState                 -51 +equ trap_GetCurrentSnapshotNumber     -52 +equ trap_GetSnapshot                  -53 +equ trap_GetServerCommand             -54 +equ trap_GetCurrentCmdNumber          -55 +equ trap_GetUserCmd                   -56 +equ trap_SetUserCmdValue              -57 +equ trap_R_RegisterShaderNoMip        -58 +equ trap_MemoryRemaining              -59 +equ trap_R_RegisterFont               -60 +equ trap_Key_IsDown                   -61 +equ trap_Key_GetCatcher               -62 +equ trap_Key_SetCatcher               -63 +equ trap_Key_GetKey                   -64 +equ trap_Parse_AddGlobalDefine        -65 +equ trap_Parse_LoadSource             -66 +equ trap_Parse_FreeSource             -67 +equ trap_Parse_ReadToken              -68 +equ trap_Parse_SourceFileAndLine      -69 +equ trap_S_StopBackgroundTrack        -70 +equ trap_RealTime                     -71 +equ trap_SnapVector                   -72 +equ trap_RemoveCommand                -73 +equ trap_R_LightForPoint              -74 +equ trap_CIN_PlayCinematic            -75 +equ trap_CIN_StopCinematic            -76 +equ trap_CIN_RunCinematic             -77 +equ trap_CIN_DrawCinematic            -78 +equ trap_CIN_SetExtents               -79 +equ trap_R_RemapShader                -80 +equ trap_S_AddRealLoopingSound        -81 +equ trap_S_StopLoopingSound           -82 +equ trap_CM_TempCapsuleModel          -83 +equ trap_CM_CapsuleTrace              -84 +equ trap_CM_TransformedCapsuleTrace   -85 +equ trap_R_AddAdditiveLightToScene    -86 +equ trap_GetEntityToken               -87 +equ trap_R_AddPolysToScene            -88 +equ trap_R_inPVS                      -89 +equ trap_FS_Seek                      -90 +equ trap_FS_GetFileList               -91 +equ trap_LiteralArgs                  -92 +equ trap_CM_BiSphereTrace             -93 +equ trap_CM_TransformedBiSphereTrace  -94 +equ trap_GetDemoState                 -95 +equ trap_GetDemoPos                   -96 +equ trap_GetDemoName                  -97 +equ trap_Key_KeynumToStringBuf        -98 +equ trap_Key_GetBindingBuf            -99 +equ trap_Key_SetBinding               -100 + +equ memset                            -201 +equ memcpy                            -202 +equ strncpy                           -203 +equ sin                               -204 +equ cos                               -205 +equ atan2                             -206 +equ sqrt                              -207 +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 new file mode 100644 index 0000000..43afc2c --- /dev/null +++ b/src/cgame/cg_syscalls.c @@ -0,0 +1,570 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +// 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; + + +void dllEntry( intptr_t (QDECL  *syscallptr)( intptr_t arg,... ) ) +{ +  syscall = syscallptr; +} + + +int PASSFLOAT( float x ) +{ +  float floatTemp; +  floatTemp = x; +  return *(int *)&floatTemp; +} + +void trap_Print( const char *fmt ) +{ +  syscall( CG_PRINT, fmt ); +} + +void trap_Error( const char *fmt ) +{ +  syscall( CG_ERROR, fmt ); +} + +int trap_Milliseconds( void ) +{ +  return syscall( CG_MILLISECONDS ); +} + +void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ) +{ +  syscall( CG_CVAR_REGISTER, vmCvar, varName, defaultValue, flags ); +} + +void trap_Cvar_Update( vmCvar_t *vmCvar ) +{ +  syscall( CG_CVAR_UPDATE, vmCvar ); +} + +void trap_Cvar_Set( const char *var_name, const char *value ) +{ +  syscall( CG_CVAR_SET, var_name, value ); +} + +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) +{ +  syscall( CG_CVAR_VARIABLESTRINGBUFFER, var_name, buffer, bufsize ); +} + +int trap_Argc( void ) +{ +  return syscall( CG_ARGC ); +} + +void  trap_Argv( int n, char *buffer, int bufferLength ) +{ +  syscall( CG_ARGV, n, buffer, bufferLength ); +} + +void  trap_Args( char *buffer, int bufferLength ) +{ +  syscall( CG_ARGS, buffer, bufferLength ); +} + +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 ) +{ +  return syscall( CG_FS_FOPENFILE, qpath, f, mode ); +} + +void  trap_FS_Read( void *buffer, int len, fileHandle_t f ) +{ +  syscall( CG_FS_READ, buffer, len, f ); +} + +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ) +{ +  syscall( CG_FS_WRITE, buffer, len, f ); +} + +void  trap_FS_FCloseFile( fileHandle_t f ) +{ +  syscall( CG_FS_FCLOSEFILE, f ); +} + +void trap_FS_Seek( fileHandle_t f, long offset, fsOrigin_t origin ) +{ +  syscall( CG_FS_SEEK, f, offset, origin ); +} + +int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ) +{ +  return syscall( CG_FS_GETFILELIST, path, extension, listbuf, bufsize ); +} + +void  trap_SendConsoleCommand( const char *text ) +{ +  syscall( CG_SENDCONSOLECOMMAND, text ); +} + +void  trap_AddCommand( const char *cmdName ) +{ +  syscall( CG_ADDCOMMAND, cmdName ); +} + +void  trap_RemoveCommand( const char *cmdName ) +{ +    syscall( CG_REMOVECOMMAND, cmdName ); +} + +void  trap_SendClientCommand( const char *s ) +{ +  syscall( CG_SENDCLIENTCOMMAND, s ); +} + +void  trap_UpdateScreen( void ) +{ +  syscall( CG_UPDATESCREEN ); +} + +void  trap_CM_LoadMap( const char *mapname ) +{ +  syscall( CG_CM_LOADMAP, mapname ); +} + +int   trap_CM_NumInlineModels( void ) +{ +  return syscall( CG_CM_NUMINLINEMODELS ); +} + +clipHandle_t trap_CM_InlineModel( int index ) +{ +  return syscall( CG_CM_INLINEMODEL, index ); +} + +clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs ) +{ +  return syscall( CG_CM_TEMPBOXMODEL, mins, maxs ); +} + +clipHandle_t trap_CM_TempCapsuleModel( const vec3_t mins, const vec3_t maxs ) +{ +  return syscall( CG_CM_TEMPCAPSULEMODEL, mins, maxs ); +} + +int   trap_CM_PointContents( const vec3_t p, clipHandle_t model ) +{ +  return syscall( CG_CM_POINTCONTENTS, p, model ); +} + +int   trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, +                                        const vec3_t angles ) +{ +  return syscall( CG_CM_TRANSFORMEDPOINTCONTENTS, p, model, origin, angles ); +} + +void  trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, +              const vec3_t mins, const vec3_t maxs, +              clipHandle_t model, int brushmask ) +{ +  syscall( CG_CM_BOXTRACE, results, start, end, mins, maxs, model, brushmask ); +} + +void  trap_CM_CapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, +              const vec3_t mins, const vec3_t maxs, +              clipHandle_t model, int brushmask ) +{ +  syscall( CG_CM_CAPSULETRACE, results, start, end, mins, maxs, model, brushmask ); +} + +void  trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, +              const vec3_t mins, const vec3_t maxs, +              clipHandle_t model, int brushmask, +              const vec3_t origin, const vec3_t angles ) +{ +  syscall( CG_CM_TRANSFORMEDBOXTRACE, results, start, end, mins, maxs, model, brushmask, origin, angles ); +} + +void  trap_CM_TransformedCapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, +              const vec3_t mins, const vec3_t maxs, +              clipHandle_t model, int brushmask, +              const vec3_t origin, const vec3_t angles ) +{ +  syscall( CG_CM_TRANSFORMEDCAPSULETRACE, results, start, end, mins, maxs, model, brushmask, origin, angles ); +} + +void trap_CM_BiSphereTrace( trace_t *results, const vec3_t start, +             const vec3_t end, float startRad, float endRad, +             clipHandle_t model, int mask ) +{ +  syscall( CG_CM_BISPHERETRACE, results, start, end, +      PASSFLOAT( startRad ), PASSFLOAT( endRad ), model, mask ); +} + +void trap_CM_TransformedBiSphereTrace( trace_t *results, const vec3_t start, +             const vec3_t end, float startRad, float endRad, +             clipHandle_t model, int mask, +             const vec3_t origin ) +{ +  syscall( CG_CM_TRANSFORMEDBISPHERETRACE, results, start, end, PASSFLOAT( startRad ), +      PASSFLOAT( endRad ), model, mask, origin ); +} + +int   trap_CM_MarkFragments( int numPoints, const vec3_t *points, +        const vec3_t projection, +        int maxPoints, vec3_t pointBuffer, +        int maxFragments, markFragment_t *fragmentBuffer ) +{ +  return syscall( CG_CM_MARKFRAGMENTS, numPoints, points, projection, maxPoints, +                  pointBuffer, maxFragments, fragmentBuffer ); +} + +void  trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ) +{ +  syscall( CG_S_STARTSOUND, origin, entityNum, entchannel, sfx ); +} + +void  trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ) +{ +  syscall( CG_S_STARTLOCALSOUND, sfx, channelNum ); +} + +void  trap_S_ClearLoopingSounds( qboolean killall ) +{ +  syscall( CG_S_CLEARLOOPINGSOUNDS, killall ); +} + +void  trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ) +{ +  syscall( CG_S_ADDLOOPINGSOUND, entityNum, origin, velocity, sfx ); +} + +void  trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ) +{ +  syscall( CG_S_ADDREALLOOPINGSOUND, entityNum, origin, velocity, sfx ); +} + +void  trap_S_StopLoopingSound( int entityNum ) +{ +  syscall( CG_S_STOPLOOPINGSOUND, entityNum ); +} + +void  trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin ) +{ +  syscall( CG_S_UPDATEENTITYPOSITION, entityNum, origin ); +} + +void  trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ) +{ +  syscall( CG_S_RESPATIALIZE, entityNum, origin, axis, inwater ); +} + +sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ) +{ +  return syscall( CG_S_REGISTERSOUND, sample, compressed ); +} + +void  trap_S_StartBackgroundTrack( const char *intro, const char *loop ) +{ +  syscall( CG_S_STARTBACKGROUNDTRACK, intro, loop ); +} + +void  trap_R_LoadWorldMap( const char *mapname ) +{ +  syscall( CG_R_LOADWORLDMAP, mapname ); +} + +qhandle_t trap_R_RegisterModel( const char *name ) +{ +  return syscall( CG_R_REGISTERMODEL, name ); +} + +qhandle_t trap_R_RegisterSkin( const char *name ) +{ +  return syscall( CG_R_REGISTERSKIN, name ); +} + +qhandle_t trap_R_RegisterShader( const char *name ) +{ +  return syscall( CG_R_REGISTERSHADER, name ); +} + +qhandle_t trap_R_RegisterShaderNoMip( const char *name ) +{ +  return syscall( CG_R_REGISTERSHADERNOMIP, name ); +} + +void trap_R_RegisterFont( const char *fontName, int pointSize, fontInfo_t *font ) +{ +  syscall(CG_R_REGISTERFONT, fontName, pointSize, font ); +} + +void  trap_R_ClearScene( void ) +{ +  syscall( CG_R_CLEARSCENE ); +} + +void  trap_R_AddRefEntityToScene( const refEntity_t *re ) +{ +  syscall( CG_R_ADDREFENTITYTOSCENE, re ); +} + +void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ) +{ +  syscall( CG_R_ADDPOLYTOSCENE, hShader, numVerts, verts ); +} + +void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num ) +{ +  syscall( CG_R_ADDPOLYSTOSCENE, hShader, numVerts, verts, num ); +} + +int trap_R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ) +{ +  return syscall( CG_R_LIGHTFORPOINT, point, ambientLight, directedLight, lightDir ); +} + +void  trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) +{ +  syscall( CG_R_ADDLIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) ); +} + +void  trap_R_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ) +{ +  syscall( CG_R_ADDADDITIVELIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) ); +} + +void  trap_R_RenderScene( const refdef_t *fd ) +{ +  syscall( CG_R_RENDERSCENE, fd ); +} + +void  trap_R_SetColor( const float *rgba ) +{ +  syscall( CG_R_SETCOLOR, rgba ); +} + +void  trap_R_DrawStretchPic( float x, float y, float w, float h, +                 float s1, float t1, float s2, float t2, qhandle_t hShader ) +{ +  syscall( CG_R_DRAWSTRETCHPIC, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(w), PASSFLOAT(h), +           PASSFLOAT(s1), PASSFLOAT(t1), PASSFLOAT(s2), PASSFLOAT(t2), hShader ); +} + +void  trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) { +  syscall( CG_R_MODELBOUNDS, model, mins, maxs ); +} + +int   trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, +             float frac, const char *tagName ) +{ +  return syscall( CG_R_LERPTAG, tag, mod, startFrame, endFrame, PASSFLOAT(frac), tagName ); +} + +void  trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ) +{ +  syscall( CG_R_REMAP_SHADER, oldShader, newShader, timeOffset ); +} + +void trap_GetGlconfig( glconfig_t *glconfig ) +{ +  syscall( CG_GETGLCONFIG, glconfig ); +} + +void trap_GetGameState( gameState_t *gamestate ) +{ +  syscall( CG_GETGAMESTATE, gamestate ); +} + +void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ) +{ +  syscall( CG_GETCURRENTSNAPSHOTNUMBER, snapshotNumber, serverTime ); +} + +qboolean  trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ) +{ +  return syscall( CG_GETSNAPSHOT, snapshotNumber, snapshot ); +} + +qboolean  trap_GetServerCommand( int serverCommandNumber ) +{ +  return syscall( CG_GETSERVERCOMMAND, serverCommandNumber ); +} + +int trap_GetCurrentCmdNumber( void ) +{ +  return syscall( CG_GETCURRENTCMDNUMBER ); +} + +qboolean  trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd ) +{ +  return syscall( CG_GETUSERCMD, cmdNumber, ucmd ); +} + +void    trap_SetUserCmdValue( int stateValue, float sensitivityScale ) +{ +  syscall( CG_SETUSERCMDVALUE, stateValue, PASSFLOAT( sensitivityScale ) ); +} + +void    testPrintInt( char *string, int i ) +{ +  syscall( CG_TESTPRINTINT, string, i ); +} + +void    testPrintFloat( char *string, float f ) +{ +  syscall( CG_TESTPRINTFLOAT, string, PASSFLOAT(f) ); +} + +int trap_MemoryRemaining( void ) +{ +  return syscall( CG_MEMORY_REMAINING ); +} + +qboolean trap_Key_IsDown( int keynum ) +{ +  return syscall( CG_KEY_ISDOWN, keynum ); +} + +int trap_Key_GetCatcher( void ) +{ +  return syscall( CG_KEY_GETCATCHER ); +} + +void trap_Key_SetCatcher( int catcher ) +{ +  syscall( CG_KEY_SETCATCHER, catcher ); +} + +int trap_Key_GetKey( const char *binding ) +{ +  return syscall( CG_KEY_GETKEY, binding ); +} + +int trap_Parse_AddGlobalDefine( char *define ) +{ +  return syscall( CG_PARSE_ADD_GLOBAL_DEFINE, define ); +} + +int trap_Parse_LoadSource( const char *filename ) +{ +  return syscall( CG_PARSE_LOAD_SOURCE, filename ); +} + +int trap_Parse_FreeSource( int handle ) +{ +  return syscall( CG_PARSE_FREE_SOURCE, handle ); +} + +int trap_Parse_ReadToken( int handle, pc_token_t *pc_token ) +{ +  return syscall( CG_PARSE_READ_TOKEN, handle, pc_token ); +} + +int trap_Parse_SourceFileAndLine( int handle, char *filename, int *line ) +{ +  return syscall( CG_PARSE_SOURCE_FILE_AND_LINE, handle, filename, line ); +} + +void  trap_S_StopBackgroundTrack( void ) +{ +  syscall( CG_S_STOPBACKGROUNDTRACK ); +} + +int trap_RealTime(qtime_t *qtime) +{ +  return syscall( CG_REAL_TIME, qtime ); +} + +void trap_SnapVector( float *v ) +{ +  syscall( CG_SNAPVECTOR, v ); +} + +// this returns a handle.  arg0 is the name in the format "idlogo.roq", set arg1 to NULL, alteredstates to qfalse (do not alter gamestate) +int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits ) +{ +  return syscall(CG_CIN_PLAYCINEMATIC, arg0, xpos, ypos, width, height, bits); +} + +// stops playing the cinematic and ends it.  should always return FMV_EOF +// cinematics must be stopped in reverse order of when they are started +e_status trap_CIN_StopCinematic( int handle ) +{ +  return syscall(CG_CIN_STOPCINEMATIC, handle); +} + + +// will run a frame of the cinematic but will not draw it.  Will return FMV_EOF if the end of the cinematic has been reached. +e_status trap_CIN_RunCinematic( int handle ) +{ +  return syscall(CG_CIN_RUNCINEMATIC, handle); +} + + +// draws the current frame +void trap_CIN_DrawCinematic( int handle ) +{ +  syscall(CG_CIN_DRAWCINEMATIC, handle); +} + + +// allows you to resize the animation dynamically +void trap_CIN_SetExtents( int handle, int x, int y, int w, int h ) +{ +  syscall(CG_CIN_SETEXTENTS, handle, x, y, w, h); +} + +int trap_GetDemoState( void ) +{ +  return syscall( CG_GETDEMOSTATE ); +} + +int trap_GetDemoPos( void ) +{ +  return syscall( CG_GETDEMOPOS ); +} + +void trap_GetDemoName( char *buffer, int size ) +{ +  syscall( CG_GETDEMONAME, buffer, size ); +} + +void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen ) { +  syscall( CG_KEY_KEYNUMTOSTRINGBUF, keynum, buf, buflen ); +} + +void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen ) { +  syscall( CG_KEY_GETBINDINGBUF, keynum, buf, buflen ); +} + +void trap_Key_SetBinding( int keynum, const char *binding ) { +  syscall( CG_KEY_SETBINDING, keynum, binding ); +} + diff --git a/src/cgame/cg_trails.c b/src/cgame/cg_trails.c new file mode 100644 index 0000000..c8943ed --- /dev/null +++ b/src/cgame/cg_trails.c @@ -0,0 +1,1500 @@ +/* +=========================================================================== +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_trails.c -- the trail system + + +#include "cg_local.h" + +static baseTrailSystem_t  baseTrailSystems[ MAX_BASETRAIL_SYSTEMS ]; +static baseTrailBeam_t    baseTrailBeams[ MAX_BASETRAIL_BEAMS ]; +static int                numBaseTrailSystems = 0; +static int                numBaseTrailBeams = 0; + +static trailSystem_t      trailSystems[ MAX_TRAIL_SYSTEMS ]; +static trailBeam_t        trailBeams[ MAX_TRAIL_BEAMS ]; + +/* +=============== +CG_CalculateBeamNodeProperties + +Fills in trailBeamNode_t.textureCoord +=============== +*/ +static void CG_CalculateBeamNodeProperties( trailBeam_t *tb ) +{ +  trailBeamNode_t *i = NULL; +  trailSystem_t   *ts; +  baseTrailBeam_t *btb; +  float           nodeDistances[ MAX_TRAIL_BEAM_NODES ]; +  float           totalDistance = 0.0f, position = 0.0f; +  int             j, numNodes = 0; +  float           TCRange, widthRange, alphaRange; +  vec3_t          colorRange; +  float           fadeAlpha = 1.0f; + +  if( !tb || !tb->nodes ) +    return; + +  ts = tb->parent; +  btb = tb->class; + +  if( ts->destroyTime > 0 && btb->fadeOutTime ) +  { +    fadeAlpha -= ( cg.time - ts->destroyTime ) / btb->fadeOutTime; + +    if( fadeAlpha < 0.0f ) +      fadeAlpha = 0.0f; +  } + +  TCRange = tb->class->backTextureCoord - +    tb->class->frontTextureCoord; +  widthRange = tb->class->backWidth - +    tb->class->frontWidth; +  alphaRange = tb->class->backAlpha - +    tb->class->frontAlpha; +  VectorSubtract( tb->class->backColor, +      tb->class->frontColor, colorRange ); + +  for( i = tb->nodes; i && i->next; i = i->next ) +  { +    nodeDistances[ numNodes++ ] = +      Distance( i->position, i->next->position ); +  } + +  for( j = 0; j < numNodes; j++ ) +    totalDistance += nodeDistances[ j ]; + +  for( j = 0, i = tb->nodes; i; i = i->next, j++ ) +  { +    if( tb->class->textureType == TBTT_STRETCH ) +    { +      i->textureCoord = tb->class->frontTextureCoord + +        ( ( position / totalDistance ) * TCRange ); +    } +    else if( tb->class->textureType == TBTT_REPEAT ) +    { +      if( tb->class->clampToBack ) +        i->textureCoord = ( totalDistance - position ) / +          tb->class->repeatLength; +      else +        i->textureCoord = position / tb->class->repeatLength; +    } + +    i->halfWidth = ( tb->class->frontWidth + +      ( ( position / totalDistance ) * widthRange ) ) / 2.0f; +    i->alpha = (byte)( (float)0xFF * ( tb->class->frontAlpha + +      ( ( position / totalDistance ) * alphaRange ) ) * fadeAlpha ); +    VectorMA( tb->class->frontColor, ( position / totalDistance ), +        colorRange, i->color ); + +    position += nodeDistances[ j ]; +  } +} + +/* +=============== +CG_LightVertex + +Lights a particular vertex +=============== +*/ +static void CG_LightVertex( vec3_t point, byte alpha, byte *rgba ) +{ +  int     i; +  vec3_t  alight, dlight, lightdir; + +  trap_R_LightForPoint( point, alight, dlight, lightdir ); +  for( i = 0; i <= 2; i++ ) +    rgba[ i ] = (int)alight[ i ]; + +  rgba[ 3 ] = alpha; +} + +/* +=============== +CG_RenderBeam + +Renders a beam +=============== +*/ +static void CG_RenderBeam( trailBeam_t *tb ) +{ +  trailBeamNode_t   *i = NULL; +  trailBeamNode_t   *prev = NULL; +  trailBeamNode_t   *next = NULL; +  vec3_t            up; +  polyVert_t        verts[ ( MAX_TRAIL_BEAM_NODES - 1 ) * 4 ]; +  int               numVerts = 0; +  baseTrailBeam_t   *btb; +  trailSystem_t     *ts; +  baseTrailSystem_t *bts; + +  if( !tb || !tb->nodes ) +    return; + +  btb = tb->class; +  ts = tb->parent; +  bts = ts->class; + +  if( bts->thirdPersonOnly && +      ( CG_AttachmentCentNum( &ts->frontAttachment ) == cg.snap->ps.clientNum || +        CG_AttachmentCentNum( &ts->backAttachment ) == cg.snap->ps.clientNum ) && +      !cg.renderingThirdPerson ) +    return; + +  CG_CalculateBeamNodeProperties( tb ); + +  i = tb->nodes; + +  do +  { +    prev = i->prev; +    next = i->next; + +    if( prev && next ) +    { +      //this node has two neighbours +      GetPerpendicularViewVector( cg.refdef.vieworg, next->position, prev->position, up ); +    } +    else if( !prev && next ) +    { +      //this is the front +      GetPerpendicularViewVector( cg.refdef.vieworg, next->position, i->position, up ); +    } +    else if( prev && !next ) +    { +      //this is the back +      GetPerpendicularViewVector( cg.refdef.vieworg, i->position, prev->position, up ); +    } +    else +      break; + +    if( prev ) +    { +      VectorMA( i->position, i->halfWidth, up, verts[ numVerts ].xyz ); +      verts[ numVerts ].st[ 0 ] = i->textureCoord; +      verts[ numVerts ].st[ 1 ] = 1.0f; + +      if( btb->realLight ) +        CG_LightVertex( verts[ numVerts ].xyz, i->alpha, verts[ numVerts ].modulate ); +      else +      { +        VectorCopy( i->color, verts[ numVerts ].modulate ); +        verts[ numVerts ].modulate[ 3 ] = i->alpha; +      } + +      numVerts++; + +      VectorMA( i->position, -i->halfWidth, up, verts[ numVerts ].xyz ); +      verts[ numVerts ].st[ 0 ] = i->textureCoord; +      verts[ numVerts ].st[ 1 ] = 0.0f; + +      if( btb->realLight ) +        CG_LightVertex( verts[ numVerts ].xyz, i->alpha, verts[ numVerts ].modulate ); +      else +      { +        VectorCopy( i->color, verts[ numVerts ].modulate ); +        verts[ numVerts ].modulate[ 3 ] = i->alpha; +      } + +      numVerts++; +    } + +    if( next ) +    { +      VectorMA( i->position, -i->halfWidth, up, verts[ numVerts ].xyz ); +      verts[ numVerts ].st[ 0 ] = i->textureCoord; +      verts[ numVerts ].st[ 1 ] = 0.0f; + +      if( btb->realLight ) +        CG_LightVertex( verts[ numVerts ].xyz, i->alpha, verts[ numVerts ].modulate ); +      else +      { +        VectorCopy( i->color, verts[ numVerts ].modulate ); +        verts[ numVerts ].modulate[ 3 ] = i->alpha; +      } + +      numVerts++; + +      VectorMA( i->position, i->halfWidth, up, verts[ numVerts ].xyz ); +      verts[ numVerts ].st[ 0 ] = i->textureCoord; +      verts[ numVerts ].st[ 1 ] = 1.0f; + +      if( btb->realLight ) +        CG_LightVertex( verts[ numVerts ].xyz, i->alpha, verts[ numVerts ].modulate ); +      else +      { +        VectorCopy( i->color, verts[ numVerts ].modulate ); +        verts[ numVerts ].modulate[ 3 ] = i->alpha; +      } + +      numVerts++; +    } + +    i = i->next; +  } while( i ); + +  trap_R_AddPolysToScene( tb->class->shader, 4, &verts[ 0 ], numVerts / 4 ); +} + +/* +=============== +CG_AllocateBeamNode + +Allocates a trailBeamNode_t from a trailBeam_t's nodePool +=============== +*/ +static trailBeamNode_t *CG_AllocateBeamNode( trailBeam_t *tb ) +{ +  baseTrailBeam_t *btb = tb->class; +  int             i; +  trailBeamNode_t *tbn; + +  for( i = 0; i < MAX_TRAIL_BEAM_NODES; i++ ) +  { +    tbn = &tb->nodePool[ i ]; +    if( !tbn->used ) +    { +      tbn->timeLeft = btb->segmentTime; +      tbn->prev = NULL; +      tbn->next = NULL; +      tbn->used = qtrue; +      return tbn; +    } +  } + +  // no space left +  return NULL; +} + +/* +=============== +CG_DestroyBeamNode + +Removes a node from a beam +Returns the new head +=============== +*/ +static trailBeamNode_t *CG_DestroyBeamNode( trailBeamNode_t *tbn ) +{ +  trailBeamNode_t *newHead = NULL; + +  if( tbn->prev ) +  { +    if( tbn->next ) +    { +      // node is in the middle +      tbn->prev->next = tbn->next; +      tbn->next->prev = tbn->prev; +    } +    else // node is at the back +      tbn->prev->next = NULL; + +    // find the new head (shouldn't have changed) +    newHead = tbn->prev; + +    while( newHead->prev ) +      newHead = newHead->prev; +  } +  else if( tbn->next ) +  { +    //node is at the front +    tbn->next->prev = NULL; +    newHead = tbn->next; +  } + +  tbn->prev = NULL; +  tbn->next = NULL; +  tbn->used = qfalse; + +  return newHead; +} + +/* +=============== +CG_FindLastBeamNode + +Returns the last beam node in a beam +=============== +*/ +static trailBeamNode_t *CG_FindLastBeamNode( trailBeam_t *tb ) +{ +  trailBeamNode_t *i = tb->nodes; + +  while( i && i->next ) +    i = i->next; + +  return i; +} + +/* +=============== +CG_CountBeamNodes + +Returns the number of nodes in a beam +=============== +*/ +static int CG_CountBeamNodes( trailBeam_t *tb ) +{ +  trailBeamNode_t *i = tb->nodes; +  int             numNodes = 0; + +  while( i ) +  { +    numNodes++; +    i = i->next; +  } + +  return numNodes; +} + +/* +=============== +CG_PrependBeamNode + +Prepend a new beam node to the front of a beam +Returns the new node +=============== +*/ +static trailBeamNode_t *CG_PrependBeamNode( trailBeam_t *tb ) +{ +  trailBeamNode_t *i; + +  if( tb->nodes ) +  { +    // prepend another node +    i = CG_AllocateBeamNode( tb ); + +    if( i ) +    { +      i->next = tb->nodes; +      tb->nodes->prev = i; +      tb->nodes = i; +    } +  } +  else //add first node +  { +    i = CG_AllocateBeamNode( tb ); + +    if( i ) +      tb->nodes = i; +  } + +  return i; +} + +/* +=============== +CG_AppendBeamNode + +Append a new beam node to the back of a beam +Returns the new node +=============== +*/ +static trailBeamNode_t *CG_AppendBeamNode( trailBeam_t *tb ) +{ +  trailBeamNode_t *last, *i; + +  if( tb->nodes ) +  { +    // append another node +    last = CG_FindLastBeamNode( tb ); +    i = CG_AllocateBeamNode( tb ); + +    if( i ) +    { +      last->next = i; +      i->prev = last; +      i->next = NULL; +    } +  } +  else //add first node +  { +    i = CG_AllocateBeamNode( tb ); + +    if( i ) +      tb->nodes = i; +  } + +  return i; +} + +/* +=============== +CG_ApplyJitters +=============== +*/ +static void CG_ApplyJitters( trailBeam_t *tb ) +{ +  trailBeamNode_t *i = NULL; +  int             j; +  baseTrailBeam_t *btb; +  trailSystem_t   *ts; +  trailBeamNode_t *start; +  trailBeamNode_t *end; + +  if( !tb || !tb->nodes ) +    return; + +  btb = tb->class; +  ts = tb->parent; + +  for( j = 0; j < btb->numJitters; j++ ) +  { +    if( tb->nextJitterTimes[ j ] <= cg.time ) +    { +      for( i = tb->nodes; i; i = i->next ) +      { +        i->jitters[ j ][ 0 ] = ( crandom( ) * btb->jitters[ j ].magnitude ); +        i->jitters[ j ][ 1 ] = ( crandom( ) * btb->jitters[ j ].magnitude ); +      } + +      tb->nextJitterTimes[ j ] = cg.time + btb->jitters[ j ].period; +    } +  } + +  start = tb->nodes; +  end = CG_FindLastBeamNode( tb ); + +  if( !btb->jitterAttachments ) +  { +    if( CG_Attached( &ts->frontAttachment ) && start->next ) +      start = start->next; + +    if( CG_Attached( &ts->backAttachment ) && end->prev ) +      end = end->prev; +  } + +  for( i = start; i; i = i->next ) +  { +    vec3_t          forward, right, up; +    trailBeamNode_t *prev; +    trailBeamNode_t *next; +    float           upJitter = 0.0f, rightJitter = 0.0f; + +    prev = i->prev; +    next = i->next; + +    if( prev && next ) +    { +      //this node has two neighbours +      GetPerpendicularViewVector( cg.refdef.vieworg, next->position, prev->position, up ); +      VectorSubtract( next->position, prev->position, forward ); +    } +    else if( !prev && next ) +    { +      //this is the front +      GetPerpendicularViewVector( cg.refdef.vieworg, next->position, i->position, up ); +      VectorSubtract( next->position, i->position, forward ); +    } +    else if( prev && !next ) +    { +      //this is the back +      GetPerpendicularViewVector( cg.refdef.vieworg, i->position, prev->position, up ); +      VectorSubtract( i->position, prev->position, forward ); +    } + +    VectorNormalize( forward ); +    CrossProduct( forward, up, right ); +    VectorNormalize( right ); + +    for( j = 0; j < btb->numJitters; j++ ) +    { +      upJitter += i->jitters[ j ][ 0 ]; +      rightJitter += i->jitters[ j ][ 1 ]; +    } + +    VectorMA( i->position, upJitter, up, i->position ); +    VectorMA( i->position, rightJitter, right, i->position ); + +    if( i == end ) +      break; +  } +} + +/* +=============== +CG_UpdateBeam + +Updates a beam +=============== +*/ +static void CG_UpdateBeam( trailBeam_t *tb ) +{ +  baseTrailBeam_t *btb; +  trailSystem_t   *ts; +  trailBeamNode_t *i; +  int             deltaTime; +  int             nodesToAdd; +  int             j; +  int             numNodes; + +  if( !tb ) +    return; + +  btb = tb->class; +  ts = tb->parent; + +  deltaTime = cg.time - tb->lastEvalTime; +  tb->lastEvalTime = cg.time; + +  // first make sure this beam has enough nodes +  if( ts->destroyTime <= 0 ) +  { +    nodesToAdd = btb->numSegments - CG_CountBeamNodes( tb ) + 1; + +    while( nodesToAdd-- ) +    { +      i = CG_AppendBeamNode( tb ); + +      if( !tb->nodes->next && CG_Attached( &ts->frontAttachment ) ) +      { +        // this is the first node to be added +        if( !CG_AttachmentPoint( &ts->frontAttachment, i->refPosition ) ) +          CG_DestroyTrailSystem( &ts ); +      } +      else +        VectorCopy( i->prev->refPosition, i->refPosition ); +    } +  } + +  numNodes = CG_CountBeamNodes( tb ); + +  for( i = tb->nodes; i; i = i->next ) +    VectorCopy( i->refPosition, i->position ); + +  if( CG_Attached( &ts->frontAttachment ) && CG_Attached( &ts->backAttachment ) ) +  { +    // beam between two attachments +    vec3_t dir, front, back; + +    if( ts->destroyTime > 0 && ( cg.time - ts->destroyTime ) >= btb->fadeOutTime ) +    { +      tb->valid = qfalse; +      return; +    } + +    if( !CG_AttachmentPoint( &ts->frontAttachment, front ) ) +      CG_DestroyTrailSystem( &ts ); + +    if( !CG_AttachmentPoint( &ts->backAttachment, back ) ) +      CG_DestroyTrailSystem( &ts ); + +    VectorSubtract( back, front, dir ); + +    for( j = 0, i = tb->nodes; i; i = i->next, j++ ) +    { +      float scale = (float)j / (float)( numNodes - 1 ); + +      VectorMA( front, scale, dir, i->position ); +    } +  } +  else if( CG_Attached( &ts->frontAttachment ) ) +  { +    // beam from one attachment + +    // cull the trail tail +    i = CG_FindLastBeamNode( tb ); + +    if( i && i->timeLeft >= 0 ) +    { +      i->timeLeft -= deltaTime; + +      if( i->timeLeft < 0 ) +      { +        tb->nodes = CG_DestroyBeamNode( i ); + +        if( !tb->nodes ) +        { +          tb->valid = qfalse; +          return; +        } + +        // if the ts has been destroyed, stop creating new nodes +        if( ts->destroyTime <= 0 ) +          CG_PrependBeamNode( tb ); +      } +      else if( i->timeLeft >= 0 && i->prev ) +      { +        vec3_t  dir; +        float   length; + +        VectorSubtract( i->refPosition, i->prev->refPosition, dir ); +        length = VectorNormalize( dir ) * +          ( (float)i->timeLeft / (float)tb->class->segmentTime ); + +        VectorMA( i->prev->refPosition, length, dir, i->position ); +      } +    } + +    if( tb->nodes ) +    { +      if( !CG_AttachmentPoint( &ts->frontAttachment, tb->nodes->refPosition ) ) +        CG_DestroyTrailSystem( &ts ); + +      VectorCopy( tb->nodes->refPosition, tb->nodes->position ); +    } +  } + +  CG_ApplyJitters( tb ); +} + +/* +=============== +CG_ParseTrailBeamColor +=============== +*/ +static qboolean CG_ParseTrailBeamColor( byte *c, char **text_p ) +{ +  char  *token; +  int   i; + +  for( i = 0; i <= 2; i++ ) +  { +    token = COM_Parse( text_p ); + +    if( !Q_stricmp( token, "" ) ) +      return qfalse; + +    c[ i ] = (int)( (float)0xFF * atof_neg( token, qfalse ) ); +  } + +  return qtrue; +} + +/* +=============== +CG_ParseTrailBeam + +Parse a trail beam +=============== +*/ +static qboolean CG_ParseTrailBeam( baseTrailBeam_t *btb, char **text_p ) +{ +  char  *token; + +  // read optional parameters +  while( 1 ) +  { +    token = COM_Parse( text_p ); + +    if( !Q_stricmp( token, "" ) ) +      return qfalse; + +    if( !Q_stricmp( token, "segments" ) ) +    { +      token = COM_Parse( text_p ); +      if( !Q_stricmp( token, "" ) ) +        break; + +      btb->numSegments = atoi_neg( token, qfalse ); + +      if( btb->numSegments >= MAX_TRAIL_BEAM_NODES ) +      { +        btb->numSegments = MAX_TRAIL_BEAM_NODES - 1; +        CG_Printf( S_COLOR_YELLOW "WARNING: too many segments in trail beam\n" ); +      } +      continue; +    } +    else if( !Q_stricmp( token, "width" ) ) +    { +      token = COM_Parse( text_p ); +      if( !Q_stricmp( token, "" ) ) +        break; + +      btb->frontWidth = atof_neg( token, qfalse ); + +      token = COM_Parse( text_p ); +      if( !Q_stricmp( token, "" ) ) +        break; + +      if( !Q_stricmp( token, "-" ) ) +        btb->backWidth = btb->frontWidth; +      else +        btb->backWidth = atof_neg( token, qfalse ); +      continue; +    } +    else if( !Q_stricmp( token, "alpha" ) ) +    { +      token = COM_Parse( text_p ); +      if( !Q_stricmp( token, "" ) ) +        break; + +      btb->frontAlpha = atof_neg( token, qfalse ); + +      token = COM_Parse( text_p ); +      if( !Q_stricmp( token, "" ) ) +        break; + +      if( !Q_stricmp( token, "-" ) ) +        btb->backAlpha = btb->frontAlpha; +      else +        btb->backAlpha = atof_neg( token, qfalse ); +      continue; +    } +    else if( !Q_stricmp( token, "color" ) ) +    { +      token = COM_Parse( text_p ); +      if( !Q_stricmp( token, "" ) ) +        break; + +      if( !Q_stricmp( token, "{" ) ) +      { +        if( !CG_ParseTrailBeamColor( btb->frontColor, 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( !Q_stricmp( token, "" ) ) +          break; + +        if( !Q_stricmp( token, "-" ) ) +        { +          btb->backColor[ 0 ] = btb->frontColor[ 0 ]; +          btb->backColor[ 1 ] = btb->frontColor[ 1 ]; +          btb->backColor[ 2 ] = btb->frontColor[ 2 ]; +        } +        else if( !Q_stricmp( token, "{" ) ) +        { +          if( !CG_ParseTrailBeamColor( btb->backColor, text_p ) ) +            break; + +          token = COM_Parse( text_p ); +          if( Q_stricmp( token, "}" ) ) +          { +            CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" ); +            break; +          } +        } +        else +        { +          CG_Printf( S_COLOR_RED "ERROR: missing '{'\n" ); +          break; +        } +      } +      else +      { +        CG_Printf( S_COLOR_RED "ERROR: missing '{'\n" ); +        break; +      } + +      continue; +    } +    else if( !Q_stricmp( token, "segmentTime" ) ) +    { +      token = COM_Parse( text_p ); +      if( !Q_stricmp( token, "" ) ) +        break; + +      btb->segmentTime = atoi_neg( token, qfalse ); +      continue; +    } +    else if( !Q_stricmp( token, "fadeOutTime" ) ) +    { +      token = COM_Parse( text_p ); +      if( !Q_stricmp( token, "" ) ) +        break; + +      btb->fadeOutTime = atoi_neg( token, qfalse ); +      continue; +    } +    else if( !Q_stricmp( token, "shader" ) ) +    { +      token = COM_Parse( text_p ); +      if( !Q_stricmp( token, "" ) ) +        break; + +      Q_strncpyz( btb->shaderName, token, MAX_QPATH ); + +      continue; +    } +    else if( !Q_stricmp( token, "textureType" ) ) +    { +      token = COM_Parse( text_p ); +      if( !Q_stricmp( token, "" ) ) +        break; + +      if( !Q_stricmp( token, "stretch" ) ) +      { +        btb->textureType = TBTT_STRETCH; + +        token = COM_Parse( text_p ); +        if( !Q_stricmp( token, "" ) ) +          break; + +        btb->frontTextureCoord = atof_neg( token, qfalse ); + +        token = COM_Parse( text_p ); +        if( !Q_stricmp( token, "" ) ) +          break; + +        btb->backTextureCoord = atof_neg( token, qfalse ); +      } +      else if( !Q_stricmp( token, "repeat" ) ) +      { +        btb->textureType = TBTT_REPEAT; + +        token = COM_Parse( text_p ); +        if( !Q_stricmp( token, "" ) ) +          break; + +        if( !Q_stricmp( token, "front" ) ) +          btb->clampToBack = qfalse; +        else if( !Q_stricmp( token, "back" ) ) +          btb->clampToBack = qtrue; +        else +        { +          CG_Printf( S_COLOR_RED "ERROR: unknown textureType clamp \"%s\"\n", token ); +          break; +        } + +        token = COM_Parse( text_p ); +        if( !Q_stricmp( token, "" ) ) +          break; + +        btb->repeatLength = atof_neg( token, qfalse ); +      } +      else +      { +        CG_Printf( S_COLOR_RED "ERROR: unknown textureType \"%s\"\n", token ); +        break; +      } + +      continue; +    } +    else if( !Q_stricmp( token, "realLight" ) ) +    { +      btb->realLight = qtrue; + +      continue; +    } +    else if( !Q_stricmp( token, "jitter" ) ) +    { +      if( btb->numJitters == MAX_TRAIL_BEAM_JITTERS ) +      { +        CG_Printf( S_COLOR_RED "ERROR: too many jitters\n", token ); +        break; +      } + +      token = COM_Parse( text_p ); +      if( !Q_stricmp( token, "" ) ) +        break; + +      btb->jitters[ btb->numJitters ].magnitude = atof_neg( token, qfalse ); + +      token = COM_Parse( text_p ); +      if( !Q_stricmp( token, "" ) ) +        break; + +      btb->jitters[ btb->numJitters ].period = atoi_neg( token, qfalse ); + +      btb->numJitters++; + +      continue; +    } +    else if( !Q_stricmp( token, "jitterAttachments" ) ) +    { +      btb->jitterAttachments = qtrue; + +      continue; +    } +    else if( !Q_stricmp( token, "}" ) ) +      return qtrue; //reached the end of this trail beam +    else +    { +      CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in trail beam\n", token ); +      return qfalse; +    } +  } + +  return qfalse; +} + +/* +=============== +CG_InitialiseBaseTrailBeam +=============== +*/ +static void CG_InitialiseBaseTrailBeam( baseTrailBeam_t *btb ) +{ +  memset( btb, 0, sizeof( baseTrailBeam_t ) ); + +  btb->numSegments = 1; +  btb->frontWidth = btb->backWidth = 1.0f; +  btb->frontAlpha = btb->backAlpha = 1.0f; +  memset( btb->frontColor, 0xFF, sizeof( btb->frontColor ) ); +  memset( btb->backColor, 0xFF, sizeof( btb->backColor ) ); + +  btb->segmentTime = 100; + +  btb->textureType = TBTT_STRETCH; +  btb->frontTextureCoord = 0.0f; +  btb->backTextureCoord = 1.0f; +} + +/* +=============== +CG_ParseTrailSystem + +Parse a trail system section +=============== +*/ +static qboolean CG_ParseTrailSystem( baseTrailSystem_t *bts, char **text_p, const char *name ) +{ +  char *token; + +  // read optional parameters +  while( 1 ) +  { +    token = COM_Parse( text_p ); + +    if( !Q_stricmp( token, "" ) ) +      return qfalse; + +    if( !Q_stricmp( token, "{" ) ) +    { +      CG_InitialiseBaseTrailBeam( &baseTrailBeams[ numBaseTrailBeams ] ); + +      if( !CG_ParseTrailBeam( &baseTrailBeams[ numBaseTrailBeams ], text_p ) ) +      { +        CG_Printf( S_COLOR_RED "ERROR: failed to parse trail beam\n" ); +        return qfalse; +      } + +      if( bts->numBeams == MAX_BEAMS_PER_SYSTEM ) +      { +        CG_Printf( S_COLOR_RED "ERROR: trail system has > %d beams\n", MAX_BEAMS_PER_SYSTEM ); +        return qfalse; +      } +      else if( numBaseTrailBeams == MAX_BASETRAIL_BEAMS ) +      { +        CG_Printf( S_COLOR_RED "ERROR: maximum number of trail beams (%d) reached\n", +            MAX_BASETRAIL_BEAMS ); +        return qfalse; +      } +      else +      { +        //start parsing beams again +        bts->beams[ bts->numBeams ] = &baseTrailBeams[ numBaseTrailBeams ]; +        bts->numBeams++; +        numBaseTrailBeams++; +      } +      continue; +    } +    else if( !Q_stricmp( token, "thirdPersonOnly" ) ) +      bts->thirdPersonOnly = qtrue; +    else if( !Q_stricmp( token, "beam" ) ) //acceptable text +      continue; +    else if( !Q_stricmp( token, "}" ) ) +    { +      if( cg_debugTrails.integer >= 1 ) +        CG_Printf( "Parsed trail system %s\n", name ); + +      return qtrue; //reached the end of this trail system +    } +    else +    { +      CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in trail system %s\n", token, bts->name ); +      return qfalse; +    } +  } + +  return qfalse; +} + +/* +=============== +CG_ParseTrailFile + +Load the trail systems from a trail file +=============== +*/ +static qboolean CG_ParseTrailFile( const char *fileName ) +{ +  char          *text_p; +  int           i; +  int           len; +  char          *token; +  char          text[ 32000 ]; +  char          tsName[ MAX_QPATH ]; +  qboolean      tsNameSet = qfalse; +  fileHandle_t  f; + +  // load the file +  len = trap_FS_FOpenFile( fileName, &f, FS_READ ); +  if( len <= 0 ) +    return qfalse; + +  if( len >= sizeof( text ) - 1 ) +  { +    CG_Printf( S_COLOR_RED "ERROR: trail file %s too long\n", fileName ); +    return qfalse; +  } + +  trap_FS_Read( text, len, f ); +  text[ len ] = 0; +  trap_FS_FCloseFile( f ); + +  // parse the text +  text_p = text; + +  // read optional parameters +  while( 1 ) +  { +    token = COM_Parse( &text_p ); + +    if( !Q_stricmp( token, "" ) ) +      break; + +    if( !Q_stricmp( token, "{" ) ) +    { +      if( tsNameSet ) +      { +        //check for name space clashes +        for( i = 0; i < numBaseTrailSystems; i++ ) +        { +          if( !Q_stricmp( baseTrailSystems[ i ].name, tsName ) ) +          { +            CG_Printf( S_COLOR_RED "ERROR: a trail system is already named %s\n", tsName ); +            return qfalse; +          } +        } + +        Q_strncpyz( baseTrailSystems[ numBaseTrailSystems ].name, tsName, MAX_QPATH ); + +        if( !CG_ParseTrailSystem( &baseTrailSystems[ numBaseTrailSystems ], &text_p, tsName ) ) +        { +          CG_Printf( S_COLOR_RED "ERROR: %s: failed to parse trail system %s\n", fileName, tsName ); +          return qfalse; +        } + +        //start parsing trail systems again +        tsNameSet = qfalse; + +        if( numBaseTrailSystems == MAX_BASETRAIL_SYSTEMS ) +        { +          CG_Printf( S_COLOR_RED "ERROR: maximum number of trail systems (%d) reached\n", +              MAX_BASETRAIL_SYSTEMS ); +          return qfalse; +        } +        else +          numBaseTrailSystems++; + +        continue; +      } +      else +      { +        CG_Printf( S_COLOR_RED "ERROR: unamed trail system\n" ); +        return qfalse; +      } +    } + +    if( !tsNameSet ) +    { +      Q_strncpyz( tsName, token, sizeof( tsName ) ); +      tsNameSet = qtrue; +    } +    else +    { +      CG_Printf( S_COLOR_RED "ERROR: trail system already named\n" ); +      return qfalse; +    } +  } + +  return qtrue; +} + +/* +=============== +CG_LoadTrailSystems + +Load trail system templates +=============== +*/ +void CG_LoadTrailSystems( void ) +{ +  int   i, numFiles, fileLen; +  char  fileList[ MAX_TRAIL_FILES * MAX_QPATH ]; +  char  fileName[ MAX_QPATH ]; +  char  *filePtr; + +  //clear out the old +  numBaseTrailSystems = 0; +  numBaseTrailBeams = 0; + +  for( i = 0; i < MAX_BASETRAIL_SYSTEMS; i++ ) +  { +    baseTrailSystem_t  *bts = &baseTrailSystems[ i ]; +    memset( bts, 0, sizeof( baseTrailSystem_t ) ); +  } + +  for( i = 0; i < MAX_BASETRAIL_BEAMS; i++ ) +  { +    baseTrailBeam_t  *btb = &baseTrailBeams[ i ]; +    memset( btb, 0, sizeof( baseTrailBeam_t ) ); +  } + +  //and bring in the new +  numFiles = trap_FS_GetFileList( "scripts", ".trail", +      fileList, MAX_TRAIL_FILES * MAX_QPATH ); +  filePtr = fileList; + +  for( i = 0; i < numFiles; i++, filePtr += fileLen + 1 ) +  { +    fileLen = strlen( filePtr ); +    strcpy( fileName, "scripts/" ); +    strcat( fileName, filePtr ); +    CG_Printf( "...loading '%s'\n", fileName ); +    CG_ParseTrailFile( fileName ); +  } +} + +/* +=============== +CG_RegisterTrailSystem + +Load the media that a trail system needs +=============== +*/ +qhandle_t CG_RegisterTrailSystem( char *name ) +{ +  int               i, j; +  baseTrailSystem_t *bts; +  baseTrailBeam_t   *btb; + +  for( i = 0; i < MAX_BASETRAIL_SYSTEMS; i++ ) +  { +    bts = &baseTrailSystems[ i ]; + +    if( !Q_stricmp( bts->name, name ) ) +    { +      //already registered +      if( bts->registered ) +        return i + 1; + +      for( j = 0; j < bts->numBeams; j++ ) +      { +        btb = bts->beams[ j ]; + +        btb->shader = trap_R_RegisterShader( btb->shaderName ); +      } + +      if( cg_debugTrails.integer >= 1 ) +        CG_Printf( "Registered trail system %s\n", name ); + +      bts->registered = qtrue; + +      //avoid returning 0 +      return i + 1; +    } +  } + +  CG_Printf( S_COLOR_RED "ERROR: failed to register trail system %s\n", name ); +  return 0; +} + + +/* +=============== +CG_SpawnNewTrailBeam + +Allocate a new trail beam +=============== +*/ +static trailBeam_t *CG_SpawnNewTrailBeam( baseTrailBeam_t *btb, +    trailSystem_t *parent ) +{ +  int           i; +  trailBeam_t   *tb = NULL; +  trailSystem_t *ts = parent; + +  for( i = 0; i < MAX_TRAIL_BEAMS; i++ ) +  { +    tb = &trailBeams[ i ]; + +    if( !tb->valid ) +    { +      memset( tb, 0, sizeof( trailBeam_t ) ); + +      //found a free slot +      tb->class = btb; +      tb->parent = ts; + +      tb->valid = qtrue; + +      if( cg_debugTrails.integer >= 1 ) +        CG_Printf( "TB %s created\n", ts->class->name ); + +      break; +    } +  } + +  return tb; +} + + +/* +=============== +CG_SpawnNewTrailSystem + +Spawns a new trail system +=============== +*/ +trailSystem_t *CG_SpawnNewTrailSystem( qhandle_t psHandle ) +{ +  int               i, j; +  trailSystem_t     *ts = NULL; +  baseTrailSystem_t *bts = &baseTrailSystems[ psHandle - 1 ]; + +  if( !bts->registered ) +  { +    CG_Printf( S_COLOR_RED "ERROR: a trail system has not been registered yet\n" ); +    return NULL; +  } + +  for( i = 0; i < MAX_TRAIL_SYSTEMS; i++ ) +  { +    ts = &trailSystems[ i ]; + +    if( !ts->valid ) +    { +      memset( ts, 0, sizeof( trailSystem_t ) ); + +      //found a free slot +      ts->class = bts; + +      ts->valid = qtrue; +      ts->destroyTime = -1; + +      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; +} + +/* +=============== +CG_DestroyTrailSystem + +Destroy a trail system +=============== +*/ +void CG_DestroyTrailSystem( trailSystem_t **ts ) +{ +  (*ts)->destroyTime = cg.time; + +  if( CG_Attached( &(*ts)->frontAttachment ) && +      !CG_Attached( &(*ts)->backAttachment ) ) +  { +    vec3_t v; + +    // attach the trail head to a static point +    CG_AttachmentPoint( &(*ts)->frontAttachment, v ); +    CG_SetAttachmentPoint( &(*ts)->frontAttachment, v ); +    CG_AttachToPoint( &(*ts)->frontAttachment ); + +    (*ts)->frontAttachment.centValid = qfalse; // a bit naughty +  } + +  ts = NULL; +} + +/* +=============== +CG_IsTrailSystemValid + +Test a trail system for validity +=============== +*/ +qboolean CG_IsTrailSystemValid( trailSystem_t **ts ) +{ +  if( *ts == NULL || ( *ts && !(*ts)->valid ) ) +  { +    if( *ts && !(*ts)->valid ) +      *ts = NULL; + +    return qfalse; +  } + +  return qtrue; +} + +/* +=============== +CG_GarbageCollectTrailSystems + +Destroy inactive trail systems +=============== +*/ +static void CG_GarbageCollectTrailSystems( void ) +{ +  int           i, j, count; +  trailSystem_t *ts; +  trailBeam_t   *tb; +  int           centNum; + +  for( i = 0; i < MAX_TRAIL_SYSTEMS; i++ ) +  { +    ts = &trailSystems[ i ]; +    count = 0; + +    //don't bother checking already invalid systems +    if( !ts->valid ) +      continue; + +    for( j = 0; j < MAX_TRAIL_BEAMS; j++ ) +    { +      tb = &trailBeams[ j ]; + +      if( tb->valid && tb->parent == ts ) +        count++; +    } + +    if( !count ) +      ts->valid = qfalse; + +    //check systems where the parent cent has left the PVS +    //( local player entity is always valid ) +    if( ( centNum = CG_AttachmentCentNum( &ts->frontAttachment ) ) >= 0 && +        centNum != cg.snap->ps.clientNum ) +    { +      trailSystem_t *tempTS = ts; + +      if( !cg_entities[ centNum ].valid ) +        CG_DestroyTrailSystem( &tempTS ); +    } + +    if( ( centNum = CG_AttachmentCentNum( &ts->backAttachment ) ) >= 0 && +        centNum != cg.snap->ps.clientNum ) +    { +      trailSystem_t *tempTS = ts; + +      if( !cg_entities[ centNum ].valid ) +        CG_DestroyTrailSystem( &tempTS ); +    } + +    if( cg_debugTrails.integer >= 1 && !ts->valid ) +      CG_Printf( "TS %s garbage collected\n", ts->class->name ); +  } +} + +/* +=============== +CG_AddTrails + +Add trails to the scene +=============== +*/ +void CG_AddTrails( void ) +{ +  int           i; +  trailBeam_t   *tb; +  int           numTS = 0, numTB = 0; + +  //remove expired trail systems +  CG_GarbageCollectTrailSystems( ); + +  for( i = 0; i < MAX_TRAIL_BEAMS; i++ ) +  { +    tb = &trailBeams[ i ]; + +    if( tb->valid ) +    { +      CG_UpdateBeam( tb ); +      CG_RenderBeam( tb ); +    } +  } + +  if( cg_debugTrails.integer >= 2 ) +  { +    for( i = 0; i < MAX_TRAIL_SYSTEMS; i++ ) +      if( trailSystems[ i ].valid ) +        numTS++; + +    for( i = 0; i < MAX_TRAIL_BEAMS; i++ ) +      if( trailBeams[ i ].valid ) +        numTB++; + +    CG_Printf( "TS: %d  TB: %d\n", numTS, numTB ); +  } +} + +static trailSystem_t  *testTS; +static qhandle_t      testTSHandle; + +/* +=============== +CG_DestroyTestTS_f + +Destroy the test a trail system +=============== +*/ +void CG_DestroyTestTS_f( void ) +{ +  if( CG_IsTrailSystemValid( &testTS ) ) +    CG_DestroyTrailSystem( &testTS ); +} + +/* +=============== +CG_TestTS_f + +Test a trail system +=============== +*/ +void CG_TestTS_f( void ) +{ +  char tsName[ MAX_QPATH ]; + +  if( trap_Argc( ) < 2 ) +    return; + +  Q_strncpyz( tsName, CG_Argv( 1 ), MAX_QPATH ); +  testTSHandle = CG_RegisterTrailSystem( tsName ); + +  if( testTSHandle ) +  { +    CG_DestroyTestTS_f( ); + +    testTS = CG_SpawnNewTrailSystem( testTSHandle ); + +    if( CG_IsTrailSystemValid( &testTS ) ) +    { +      CG_SetAttachmentCent( &testTS->frontAttachment, &cg_entities[ 0 ] ); +      CG_AttachToCent( &testTS->frontAttachment ); +    } +  } +} diff --git a/src/cgame/cg_tutorial.c b/src/cgame/cg_tutorial.c new file mode 100644 index 0000000..5ad29b7 --- /dev/null +++ b/src/cgame/cg_tutorial.c @@ -0,0 +1,663 @@ +/* +=========================================================================== +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_tutorial.c -- the tutorial system + +#include "cg_local.h" + +typedef struct +{ +  char      *command; +  char      *humanName; +  keyNum_t  keys[ 2 ]; +} bind_t; + +static bind_t bindings[ ] = +{ +  { "+button2",       "Activate Upgrade",       { -1, -1 } }, +  { "+speed",         "Run/Walk",               { -1, -1 } }, +  { "boost",          "Sprint",                 { -1, -1 } }, +  { "+moveup",        "Jump",                   { -1, -1 } }, +  { "+movedown",      "Crouch",                 { -1, -1 } }, +  { "+attack",        "Primary Attack",         { -1, -1 } }, +  { "+button5",       "Secondary Attack",       { -1, -1 } }, +  { "reload",         "Reload",                 { -1, -1 } }, +  { "buy ammo",       "Buy Ammo",               { -1, -1 } }, +  { "itemact medkit", "Use Medkit",             { -1, -1 } }, +  { "+button7",       "Use Structure/Evolve",   { -1, -1 } }, +  { "deconstruct",    "Deconstruct Structure",  { -1, -1 } }, +  { "weapprev",       "Previous Upgrade",       { -1, -1 } }, +  { "weapnext",       "Next Upgrade",           { -1, -1 } } +}; + +static const int numBindings = sizeof( bindings ) / sizeof( bind_t ); + +/* +================= +CG_GetBindings +================= +*/ +static void CG_GetBindings( void ) +{ +  int   i, j, numKeys; +  char  buffer[ MAX_STRING_CHARS ]; + +  for( i = 0; i < numBindings; i++ ) +  { +    bindings[ i ].keys[ 0 ] = bindings[ i ].keys[ 1 ] = K_NONE; +    numKeys = 0; + +    for( j = 0; j < K_LAST_KEY; j++ ) +    { +      trap_Key_GetBindingBuf( j, buffer, MAX_STRING_CHARS ); + +      if( buffer[ 0 ] == 0 ) +        continue; + +      if( !Q_stricmp( buffer, bindings[ i ].command ) ) +      { +        bindings[ i ].keys[ numKeys++ ] = j; + +        if( numKeys > 1 ) +          break; +      } +    } +  } +} + +/* +=============== +CG_KeyNameForCommand +=============== +*/ +static const char *CG_KeyNameForCommand( const char *command ) +{ +  int         i, j; +  static char buffer[ MAX_STRING_CHARS ]; +  int         firstKeyLength; + +  buffer[ 0 ] = '\0'; + +  for( i = 0; i < numBindings; i++ ) +  { +    if( !Q_stricmp( command, bindings[ i ].command ) ) +    { +      if( bindings[ i ].keys[ 0 ] != K_NONE ) +      { +        trap_Key_KeynumToStringBuf( bindings[ i ].keys[ 0 ], +            buffer, MAX_STRING_CHARS ); +        firstKeyLength = strlen( buffer ); + +        for( j = 0; j < firstKeyLength; j++ ) +          buffer[ j ] = toupper( buffer[ j ] ); + +        if( bindings[ i ].keys[ 1 ] != K_NONE ) +        { +          Q_strcat( buffer, MAX_STRING_CHARS, " or " ); +          trap_Key_KeynumToStringBuf( bindings[ i ].keys[ 1 ], +              buffer + strlen( buffer ), MAX_STRING_CHARS - strlen( buffer ) ); + +          for( j = firstKeyLength + 4; j < strlen( buffer ); j++ ) +            buffer[ j ] = toupper( buffer[ j ] ); +        } +      } +      else +      { +        Q_strncpyz( buffer, va( "\"%s\"", bindings[ i ].humanName ), +            MAX_STRING_CHARS ); +        Q_strcat( buffer, MAX_STRING_CHARS, " (unbound)" ); +      } + +      return buffer; +    } +  } + +  return ""; +} + +#define MAX_TUTORIAL_TEXT 4096 + +/* +=============== +CG_BuildableInRange +=============== +*/ +static entityState_t *CG_BuildableInRange( playerState_t *ps, float *healthFraction ) +{ +  vec3_t        view, point; +  trace_t       trace; +  entityState_t *es; +  int           health; + +  AngleVectors( cg.refdefViewAngles, view, NULL, NULL ); +  VectorMA( cg.refdef.vieworg, 64, view, point ); +  CG_Trace( &trace, cg.refdef.vieworg, NULL, NULL, +            point, ps->clientNum, MASK_SHOT ); + +  es = &cg_entities[ trace.entityNum ].currentState; + +  if( healthFraction ) +  { +    health = es->generic1 & B_HEALTH_MASK; +    *healthFraction = (float)health / B_HEALTH_MASK; +  } + +  if( es->eType == ET_BUILDABLE && +      ps->stats[ STAT_PTEAM ] == BG_FindTeamForBuildable( es->modelindex ) ) +    return es; +  else +    return NULL; +} + +/* +=============== +CG_AlienBuilderText +=============== +*/ +static void CG_AlienBuilderText( char *text, playerState_t *ps ) +{ +  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 ) ) ); + +    Q_strcat( text, MAX_TUTORIAL_TEXT, +        va( "Press %s to cancel placing the %s\n", +          CG_KeyNameForCommand( "+button5" ), +          BG_FindHumanNameForBuildable( buildable ) ) ); +  } +  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( cgs.markDeconstruct ) +      { +        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" ) ) ); +        } +      } +      else +      { +        Q_strcat( text, MAX_TUTORIAL_TEXT, +            va( "Press %s to destroy this structure\n", +              CG_KeyNameForCommand( "deconstruct" ) ) ); +      } +    } +  } + +  if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG ) +  { +    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" ) ) ); +    } + +    Q_strcat( text, MAX_TUTORIAL_TEXT, +        va( "Press %s to launch a projectile\n", +          CG_KeyNameForCommand( "+button2" ) ) ); + +    Q_strcat( text, MAX_TUTORIAL_TEXT, +        va( "Press %s to walk on walls\n", +          CG_KeyNameForCommand( "+movedown" ) ) ); +  } +} + +/* +=============== +CG_AlienLevel0Text +=============== +*/ +static void CG_AlienLevel0Text( char *text, playerState_t *ps ) +{ +  Q_strcat( text, MAX_TUTORIAL_TEXT, +      "Touch a human to damage it\n" ); + +  Q_strcat( text, MAX_TUTORIAL_TEXT, +      va( "Press %s to walk on walls\n", +        CG_KeyNameForCommand( "+movedown" ) ) ); +} + +/* +=============== +CG_AlienLevel1Text +=============== +*/ +static void CG_AlienLevel1Text( char *text, playerState_t *ps ) +{ +  Q_strcat( text, MAX_TUTORIAL_TEXT, +      "Touch a human to grab it\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 ) +  { +    Q_strcat( text, MAX_TUTORIAL_TEXT, +        va( "Press %s to spray poisonous gas\n", +          CG_KeyNameForCommand( "+button5" ) ) ); +  } + +  Q_strcat( text, MAX_TUTORIAL_TEXT, +      va( "Press %s to walk on walls\n", +        CG_KeyNameForCommand( "+movedown" ) ) ); +} + +/* +=============== +CG_AlienLevel2Text +=============== +*/ +static void CG_AlienLevel2Text( char *text, playerState_t *ps ) +{ +  Q_strcat( text, MAX_TUTORIAL_TEXT, +      va( "Press %s to bite\n", +        CG_KeyNameForCommand( "+attack" ) ) ); + +  if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL2_UPG ) +  { +    Q_strcat( text, MAX_TUTORIAL_TEXT, +        va( "Press %s to invoke an electrical attack\n", +          CG_KeyNameForCommand( "+button5" ) ) ); +  } + +  Q_strcat( text, MAX_TUTORIAL_TEXT, +      va( "Hold down %s then touch a wall to wall jump\n", +        CG_KeyNameForCommand( "+moveup" ) ) ); +} + +/* +=============== +CG_AlienLevel3Text +=============== +*/ +static void CG_AlienLevel3Text( char *text, playerState_t *ps ) +{ +  Q_strcat( text, MAX_TUTORIAL_TEXT, +      va( "Press %s to bite\n", +        CG_KeyNameForCommand( "+attack" ) ) ); + +  if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL3_UPG ) +  { +    Q_strcat( text, MAX_TUTORIAL_TEXT, +        va( "Press %s to launch a projectile\n", +          CG_KeyNameForCommand( "+button2" ) ) ); +  } + +  Q_strcat( text, MAX_TUTORIAL_TEXT, +      va( "Hold down and release %s to pounce\n", +        CG_KeyNameForCommand( "+button5" ) ) ); +} + +/* +=============== +CG_AlienLevel4Text +=============== +*/ +static void CG_AlienLevel4Text( char *text, playerState_t *ps ) +{ +  Q_strcat( text, MAX_TUTORIAL_TEXT, +      va( "Press %s to swipe\n", +        CG_KeyNameForCommand( "+attack" ) ) ); + +  Q_strcat( text, MAX_TUTORIAL_TEXT, +      va( "Hold down and release %s to charge\n", +        CG_KeyNameForCommand( "+button5" ) ) ); +} + +/* +=============== +CG_HumanCkitText +=============== +*/ +static void CG_HumanCkitText( char *text, playerState_t *ps ) +{ +  buildable_t buildable = ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT; +  float       health; + +  if( buildable > BA_NONE ) +  { +    Q_strcat( text, MAX_TUTORIAL_TEXT, +        va( "Press %s to place the %s\n", +          CG_KeyNameForCommand( "+attack" ), +          BG_FindHumanNameForBuildable( buildable ) ) ); + +    Q_strcat( text, MAX_TUTORIAL_TEXT, +        va( "Press %s to cancel placing the %s\n", +          CG_KeyNameForCommand( "+button5" ), +          BG_FindHumanNameForBuildable( buildable ) ) ); +  } +  else +  { +    Q_strcat( text, MAX_TUTORIAL_TEXT, +        va( "Press %s to build a structure\n", +          CG_KeyNameForCommand( "+attack" ) ) ); + +    if( CG_BuildableInRange( ps, &health ) ) +    { +      if( health < 1.0f ) +      { +        Q_strcat( text, MAX_TUTORIAL_TEXT, +            va( "Hold %s to repair this structure\n", +              CG_KeyNameForCommand( "+button5" ) ) ); +      } + +      Q_strcat( text, MAX_TUTORIAL_TEXT, +          va( "Press %s to destroy this structure\n", +            CG_KeyNameForCommand( "deconstruct" ) ) ); +    } +  } +} + +/* +=============== +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 ) +    name = cg_weapons[ cg.weaponSelect ].humanName; +  else if( cg.weaponSelect > 32 ) +  { +    name = cg_upgrades[ cg.weaponSelect - 32 ].humanName; +    upgrade = cg.weaponSelect - 32; +  } + +  BG_UnpackAmmoArray( ps->weapon, ps->ammo, ps->powerups, &ammo, &clips ); + +  if( !ammo && !clips && !BG_FindInfinteAmmoForWeapon( ps->weapon ) ) +  { +    //no ammo +    switch( ps->weapon ) +    { +      case WP_MACHINEGUN: +      case WP_CHAINGUN: +      case WP_SHOTGUN: +      case WP_FLAMER: +        Q_strcat( text, MAX_TUTORIAL_TEXT, +            va( "Find an Armoury and press %s for more ammo\n", +              CG_KeyNameForCommand( "buy ammo" ) ) ); +        break; + +      case WP_LAS_GUN: +      case WP_PULSE_RIFLE: +      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", +              CG_KeyNameForCommand( "buy ammo" ) ) ); +        break; + +      default: +        break; +    } +  } +  else +  { +    switch( ps->weapon ) +    { +      case WP_BLASTER: +      case WP_MACHINEGUN: +      case WP_SHOTGUN: +      case WP_LAS_GUN: +      case WP_CHAINGUN: +      case WP_PULSE_RIFLE: +      case WP_FLAMER: +        Q_strcat( text, MAX_TUTORIAL_TEXT, +            va( "Press %s to fire the %s\n", +              CG_KeyNameForCommand( "+attack" ), +              BG_FindHumanNameForWeapon( ps->weapon ) ) ); +        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 ) ) ); + +        Q_strcat( text, MAX_TUTORIAL_TEXT, +            va( "Hold %s to zoom\n", +              CG_KeyNameForCommand( "+button5" ) ) ); +        break; + +      case WP_PAIN_SAW: +        Q_strcat( text, MAX_TUTORIAL_TEXT, +            va( "Hold %s to activate the %s\n", +              CG_KeyNameForCommand( "+attack" ), +              BG_FindHumanNameForWeapon( ps->weapon ) ) ); +        break; + +      case WP_LUCIFER_CANNON: +        Q_strcat( text, MAX_TUTORIAL_TEXT, +            va( "Hold and release %s to fire a charged shot\n", +              CG_KeyNameForCommand( "+attack" ) ) ); + +        Q_strcat( text, MAX_TUTORIAL_TEXT, +            va( "Press %s to fire the %s\n", +              CG_KeyNameForCommand( "+button5" ), +              BG_FindHumanNameForWeapon( ps->weapon ) ) ); +        break; + +      case WP_HBUILD: +      case WP_HBUILD2: +        CG_HumanCkitText( text, ps ); +        break; + +      default: +        break; +    } +  } + +  Q_strcat( text, MAX_TUTORIAL_TEXT, +      va( "Press %s and ", +          CG_KeyNameForCommand( "weapprev" ) ) ); +  Q_strcat( text, MAX_TUTORIAL_TEXT, +      va( "%s to select an upgrade\n", +          CG_KeyNameForCommand( "weapnext" ) ) ); + +  if( upgrade == UP_NONE || +      ( upgrade > UP_NONE && BG_FindUsableForUpgrade( upgrade ) ) ) +  { +    Q_strcat( text, MAX_TUTORIAL_TEXT, +        va( "Press %s to use the %s\n", +            CG_KeyNameForCommand( "+button2" ), +            name ) ); +  } + +  if( ps->stats[ STAT_HEALTH ] <= 35 && +      BG_InventoryContainsUpgrade( UP_MEDKIT, ps->stats ) ) +  { +    Q_strcat( text, MAX_TUTORIAL_TEXT, +        va( "Press %s to use your %s\n", +          CG_KeyNameForCommand( "itemact medkit" ), +          BG_FindHumanNameForUpgrade( UP_MEDKIT ) ) ); +  } + +  Q_strcat( text, MAX_TUTORIAL_TEXT, +      va( "Press %s to use a structure\n", +        CG_KeyNameForCommand( "+button7" ) ) ); + +  Q_strcat( text, MAX_TUTORIAL_TEXT, +      va( "Press %s to sprint\n", +        CG_KeyNameForCommand( "boost" ) ) ); +} + +/* +=============== +CG_SpectatorText +=============== +*/ +static void CG_SpectatorText( char *text, playerState_t *ps ) +{ +  if( cgs.clientinfo[ cg.clientNum ].team != PTE_NONE ) +  { +    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" ) ) ); +  } + +  if( ps->pm_flags & PMF_FOLLOW ) +  { +    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" ) ) ); +    Q_strcat( text, MAX_TUTORIAL_TEXT, +        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" ) ); +  } +} + +/* +=============== +CG_TutorialText + +Returns context help for the current class/weapon +=============== +*/ +const char *CG_TutorialText( void ) +{ +  playerState_t *ps; +  static char   text[ MAX_TUTORIAL_TEXT ]; + +  CG_GetBindings( ); + +  text[ 0 ] = '\0'; +  ps = &cg.snap->ps; + +  if( !cg.intermissionStarted && !cg.demoPlayback ) +  { +    if( ps->persistant[ PERS_TEAM ] == TEAM_SPECTATOR || +        ps->pm_flags & PMF_FOLLOW ) +    { +      CG_SpectatorText( text, ps ); +    } +    else if( ps->stats[ STAT_HEALTH ] > 0 ) +    { +      switch( ps->stats[ STAT_PCLASS ] ) +      { +        case PCL_ALIEN_BUILDER0: +        case PCL_ALIEN_BUILDER0_UPG: +          CG_AlienBuilderText( text, ps ); +          break; + +        case PCL_ALIEN_LEVEL0: +          CG_AlienLevel0Text( text, ps ); +          break; + +        case PCL_ALIEN_LEVEL1: +        case PCL_ALIEN_LEVEL1_UPG: +          CG_AlienLevel1Text( text, ps ); +          break; + +        case PCL_ALIEN_LEVEL2: +        case PCL_ALIEN_LEVEL2_UPG: +          CG_AlienLevel2Text( text, ps ); +          break; + +        case PCL_ALIEN_LEVEL3: +        case PCL_ALIEN_LEVEL3_UPG: +          CG_AlienLevel3Text( text, ps ); +          break; + +        case PCL_ALIEN_LEVEL4: +          CG_AlienLevel4Text( text, ps ); +          break; + +        case PCL_HUMAN: +          CG_HumanText( text, ps ); +          break; + +        default: +          break; +      } + +      if( ps->stats[ STAT_PTEAM ] == PTE_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 ) ) +        { +          Q_strcat( text, MAX_TUTORIAL_TEXT, +              va( "Press %s to evolve\n", +                CG_KeyNameForCommand( "+button7" ) ) ); +        } +      } +    } + +    Q_strcat( text, MAX_TUTORIAL_TEXT, "Press ESC for the menu" ); +  } + +  return text; +} diff --git a/src/cgame/cg_view.c b/src/cgame/cg_view.c new file mode 100644 index 0000000..477196c --- /dev/null +++ b/src/cgame/cg_view.c @@ -0,0 +1,1340 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +// cg_view.c -- setup all the parameters (position, angle, etc) +// for a 3D rendering + + +#include "cg_local.h" + + +/* +============================================================================= + +  MODEL TESTING + +The viewthing and gun positioning tools from Q2 have been integrated and +enhanced into a single model testing facility. + +Model viewing can begin with either "testmodel <modelname>" or "testgun <modelname>". + +The names must be the full pathname after the basedir, like +"models/weapons/v_launch/tris.md3" or "players/male/tris.md3" + +Testmodel will create a fake entity 100 units in front of the current view +position, directly facing the viewer.  It will remain immobile, so you can +move around it to view it from different angles. + +Testgun will cause the model to follow the player around and supress the real +view weapon model.  The default frame 0 of most guns is completely off screen, +so you will probably have to cycle a couple frames to see it. + +"nextframe", "prevframe", "nextskin", and "prevskin" commands will change the +frame or skin of the testmodel.  These are bound to F5, F6, F7, and F8 in +q3default.cfg. + +If a gun is being tested, the "gun_x", "gun_y", and "gun_z" variables will let +you adjust the positioning. + +Note that none of the model testing features update while the game is paused, so +it may be convenient to test with deathmatch set to 1 so that bringing down the +console doesn't pause the game. + +============================================================================= +*/ + +/* +================= +CG_TestModel_f + +Creates an entity in front of the current position, which +can then be moved around +================= +*/ +void CG_TestModel_f( void ) +{ +  vec3_t    angles; + +  memset( &cg.testModelEntity, 0, sizeof( cg.testModelEntity ) ); +  memset( &cg.testModelBarrelEntity, 0, sizeof( cg.testModelBarrelEntity ) ); + +  if( trap_Argc( ) < 2 ) +    return; + +  Q_strncpyz( cg.testModelName, CG_Argv( 1 ), MAX_QPATH ); +  cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName ); + +  Q_strncpyz( cg.testModelBarrelName, CG_Argv( 1 ), MAX_QPATH ); +  cg.testModelBarrelName[ strlen( cg.testModelBarrelName ) - 4 ] = '\0'; +  Q_strcat( cg.testModelBarrelName, MAX_QPATH, "_barrel.md3" ); +  cg.testModelBarrelEntity.hModel = trap_R_RegisterModel( cg.testModelBarrelName ); + +  if( trap_Argc( ) == 3 ) +  { +    cg.testModelEntity.backlerp = atof( CG_Argv( 2 ) ); +    cg.testModelEntity.frame = 1; +    cg.testModelEntity.oldframe = 0; +  } + +  if( !cg.testModelEntity.hModel ) +  { +    CG_Printf( "Can't register model\n" ); +    return; +  } + +  VectorMA( cg.refdef.vieworg, 100, cg.refdef.viewaxis[ 0 ], cg.testModelEntity.origin ); + +  angles[ PITCH ] = 0; +  angles[ YAW ] = 180 + cg.refdefViewAngles[ 1 ]; +  angles[ ROLL ] = 0; + +  AnglesToAxis( angles, cg.testModelEntity.axis ); +  cg.testGun = qfalse; + +  if( cg.testModelBarrelEntity.hModel ) +  { +    angles[ YAW ] = 0; +    angles[ PITCH ] = 0; +    angles[ ROLL ] = 0; +    AnglesToAxis( angles, cg.testModelBarrelEntity.axis ); +  } +} + +/* +================= +CG_TestGun_f + +Replaces the current view weapon with the given model +================= +*/ +void CG_TestGun_f( void ) +{ +  CG_TestModel_f( ); +  cg.testGun = qtrue; +  cg.testModelEntity.renderfx = RF_MINLIGHT | RF_DEPTHHACK | RF_FIRST_PERSON; +} + + +void CG_TestModelNextFrame_f( void ) +{ +  cg.testModelEntity.frame++; +  CG_Printf( "frame %i\n", cg.testModelEntity.frame ); +} + +void CG_TestModelPrevFrame_f( void ) +{ +  cg.testModelEntity.frame--; + +  if( cg.testModelEntity.frame < 0 ) +    cg.testModelEntity.frame = 0; + +  CG_Printf( "frame %i\n", cg.testModelEntity.frame ); +} + +void CG_TestModelNextSkin_f( void ) +{ +  cg.testModelEntity.skinNum++; +  CG_Printf( "skin %i\n", cg.testModelEntity.skinNum ); +} + +void CG_TestModelPrevSkin_f( void ) +{ +  cg.testModelEntity.skinNum--; + +  if( cg.testModelEntity.skinNum < 0 ) +    cg.testModelEntity.skinNum = 0; + +  CG_Printf( "skin %i\n", cg.testModelEntity.skinNum ); +} + +static void CG_AddTestModel( void ) +{ +  int   i; + +  // re-register the model, because the level may have changed +  cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName ); +  cg.testModelBarrelEntity.hModel = trap_R_RegisterModel( cg.testModelBarrelName ); + +  if( !cg.testModelEntity.hModel ) +  { +    CG_Printf( "Can't register model\n" ); +    return; +  } + +  // if testing a gun, set the origin reletive to the view origin +  if( cg.testGun ) +  { +    VectorCopy( cg.refdef.vieworg, cg.testModelEntity.origin ); +    VectorCopy( cg.refdef.viewaxis[ 0 ], cg.testModelEntity.axis[ 0 ] ); +    VectorCopy( cg.refdef.viewaxis[ 1 ], cg.testModelEntity.axis[ 1 ] ); +    VectorCopy( cg.refdef.viewaxis[ 2 ], cg.testModelEntity.axis[ 2 ] ); + +    // allow the position to be adjusted +    for( i = 0; i < 3; i++ ) +    { +      cg.testModelEntity.origin[ i ] += cg.refdef.viewaxis[ 0 ][ i ] * cg_gun_x.value; +      cg.testModelEntity.origin[ i ] += cg.refdef.viewaxis[ 1 ][ i ] * cg_gun_y.value; +      cg.testModelEntity.origin[ i ] += cg.refdef.viewaxis[ 2 ][ i ] * cg_gun_z.value; +    } +  } + +  trap_R_AddRefEntityToScene( &cg.testModelEntity ); + +  if( cg.testModelBarrelEntity.hModel ) +  { +    CG_PositionEntityOnTag( &cg.testModelBarrelEntity, &cg.testModelEntity, +        cg.testModelEntity.hModel, "tag_barrel" ); + +    trap_R_AddRefEntityToScene( &cg.testModelBarrelEntity ); +  } +} + + + +//============================================================================ + + +/* +================= +CG_CalcVrect + +Sets the coordinates of the rendered window +================= +*/ +static void CG_CalcVrect( void ) +{ +  int   size; + +  // the intermission should allways be full screen +  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; +  } + +  cg.refdef.width = cgs.glconfig.vidWidth * size / 100; +  cg.refdef.width &= ~1; + +  cg.refdef.height = cgs.glconfig.vidHeight * size / 100; +  cg.refdef.height &= ~1; + +  cg.refdef.x = ( cgs.glconfig.vidWidth - cg.refdef.width ) / 2; +  cg.refdef.y = ( cgs.glconfig.vidHeight - cg.refdef.height ) / 2; +} + +//============================================================================== + + +/* +=============== +CG_OffsetThirdPersonView + +=============== +*/ +#define FOCUS_DISTANCE  512 +static void CG_OffsetThirdPersonView( void ) +{ +  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 ) +  { +    if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) +      VectorSet( surfNormal, 0.0f, 0.0f, -1.0f ); +    else +      VectorCopy( cg.predictedPlayerState.grapplePoint, surfNormal ); +  } +  else +    VectorSet( surfNormal, 0.0f, 0.0f, 1.0f ); + +  VectorMA( cg.refdef.vieworg, cg.predictedPlayerState.viewheight, surfNormal, cg.refdef.vieworg ); + +  VectorCopy( cg.refdefViewAngles, focusAngles ); + +  // if dead, look at killer +  if( cg.predictedPlayerState.stats[ STAT_HEALTH ] <= 0 ) +  { +    focusAngles[ YAW ] = cg.predictedPlayerState.stats[ STAT_VIEWLOCK ]; +    cg.refdefViewAngles[ YAW ] = cg.predictedPlayerState.stats[ STAT_VIEWLOCK ]; +  } + +  //if ( focusAngles[PITCH] > 45 ) { +  //  focusAngles[PITCH] = 45;    // don't go too far overhead +  //} +  AngleVectors( focusAngles, forward, NULL, NULL ); + +  VectorMA( cg.refdef.vieworg, FOCUS_DISTANCE, forward, focusPoint ); + +  VectorCopy( cg.refdef.vieworg, view ); + +  VectorMA( view, 12, surfNormal, view ); + +  //cg.refdefViewAngles[PITCH] *= 0.5; + +  AngleVectors( cg.refdefViewAngles, forward, right, up ); + +  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 ); + +  // 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 + +  if( !cg_cameraMode.integer ) +  { +    CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); + +    if( trace.fraction != 1.0 ) +    { +      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 + +      CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); +      VectorCopy( trace.endpos, view ); +    } +  } + +  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 +  } +  cg.refdefViewAngles[ PITCH ] = -180 / M_PI * atan2( focusPoint[ 2 ], focusDist ); +  cg.refdefViewAngles[ YAW ] -= cg_thirdPersonAngle.value; +} + + +// this causes a compiler bug on mac MrC compiler +static void CG_StepOffset( void ) +{ +  float         steptime; +  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 ); + +  steptime = BG_FindSteptimeForClass( ps->stats[ STAT_PCLASS ] ); + +  // smooth out stair climbing +  timeDelta = cg.time - cg.stepTime; +  if( timeDelta < steptime ) +  { +    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; +  } +} + +#define PCLOUD_ROLL_AMPLITUDE   25.0f +#define PCLOUD_ROLL_FREQUENCY   0.4f +#define PCLOUD_ZOOM_AMPLITUDE   15 +#define PCLOUD_ZOOM_FREQUENCY   0.7f + + +/* +=============== +CG_OffsetFirstPersonView + +=============== +*/ +static void CG_OffsetFirstPersonView( void ) +{ +  float         *origin; +  float         *angles; +  float         bob; +  float         ratio; +  float         delta; +  float         speed; +  float         f; +  vec3_t        predictedVelocity; +  int           timeDelta; +  float         bob2; +  vec3_t        normal, baseOrigin; +  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 ); + + +  if( cg.snap->ps.pm_type == PM_INTERMISSION ) +    return; + +  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 ) +  { +    angles[ ROLL ] = 40; +    angles[ PITCH ] = -15; +    angles[ YAW ] = cg.snap->ps.stats[ STAT_VIEWLOCK ]; +    origin[ 2 ] += cg.predictedPlayerState.viewheight; +    return; +  } + +  // add angles based on weapon kick +  VectorAdd( angles, cg.kick_angles, angles ); + +  // add angles based on damage kick +  if( cg.damageTime ) +  { +    ratio = cg.time - cg.damageTime; +    if( ratio < DAMAGE_DEFLECT_TIME ) +    { +      ratio /= DAMAGE_DEFLECT_TIME; +      angles[ PITCH ] += ratio * cg.v_dmg_pitch; +      angles[ ROLL ] += ratio * cg.v_dmg_roll; +    } +    else +    { +      ratio = 1.0 - ( ratio - DAMAGE_DEFLECT_TIME ) / DAMAGE_RETURN_TIME; +      if( ratio > 0 ) +      { +        angles[ PITCH ] += ratio * cg.v_dmg_pitch; +        angles[ ROLL ] += ratio * cg.v_dmg_roll; +      } +    } +  } + +  // add pitch based on fall kick +#if 0 +  ratio = ( cg.time - cg.landTime) / FALL_TIME; +  if (ratio < 0) +    ratio = 0; +  angles[PITCH] += ratio * cg.fall_value; +#endif + +  // add angles based on velocity +  VectorCopy( cg.predictedPlayerState.velocity, predictedVelocity ); + +  delta = DotProduct( predictedVelocity, cg.refdef.viewaxis[ 0 ] ); +  angles[ PITCH ] += delta * cg_runpitch.value; + +  delta = DotProduct( predictedVelocity, cg.refdef.viewaxis[ 1 ] ); +  angles[ ROLL ] -= delta * cg_runroll.value; + +  // add angles based on bob +  // bob amount is class dependant + +  if( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) +    bob2 = 0.0f; +  else +    bob2 = BG_FindBobForClass( cg.predictedPlayerState.stats[ STAT_PCLASS ] ); + + +#define LEVEL4_FEEDBACK  10.0f + +  //give a charging player some feedback +  if( ps->weapon == WP_ALEVEL4 ) +  { +    if( ps->stats[ STAT_MISC ] > 0 ) +    { +      float fraction = (float)ps->stats[ STAT_MISC ] / (float)LEVEL4_CHARGE_TIME; + +      if( fraction > 1.0f ) +        fraction = 1.0f; + +      bob2 *= ( 1.0f + fraction * LEVEL4_FEEDBACK ); +    } +  } + +  if( bob2 != 0.0f ) +  { +    // make sure the bob is visible even at low speeds +    speed = cg.xyspeed > 200 ? cg.xyspeed : 200; + +    delta = cg.bobfracsin * ( bob2 ) * speed; +    if( cg.predictedPlayerState.pm_flags & PMF_DUCKED ) +      delta *= 3;   // crouching + +    angles[ PITCH ] += delta; +    delta = cg.bobfracsin * ( bob2 ) * speed; +    if( cg.predictedPlayerState.pm_flags & PMF_DUCKED ) +      delta *= 3;   // crouching accentuates roll + +    if( cg.bobcycle & 1 ) +      delta = -delta; + +    angles[ ROLL ] += delta; +  } + +#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.stats[ STAT_MISC ] > 0 ) +    { +      float   fraction1, fraction2; +      vec3_t  forward; + +      AngleVectors( angles, forward, NULL, NULL ); +      VectorNormalize( forward ); + +      fraction1 = (float)( cg.time - cg.weapon2Time ) / (float)LEVEL3_POUNCE_CHARGE_TIME; + +      if( fraction1 > 1.0f ) +        fraction1 = 1.0f; + +      fraction2 = -sin( fraction1 * M_PI / 2 ); + +      VectorMA( origin, LEVEL3_FEEDBACK * fraction2, forward, origin ); +    } +  } + +#define STRUGGLE_DIST 5.0f +#define STRUGGLE_TIME 250 + +  //allow the player to struggle a little whilst grabbed +  if( cg.predictedPlayerState.pm_type == PM_GRABBED ) +  { +    vec3_t    forward, right, up; +    usercmd_t cmd; +    int       cmdNum; +    float     fFraction, rFraction, uFraction; +    float     fFraction2, rFraction2, uFraction2; + +    cmdNum = trap_GetCurrentCmdNumber(); +    trap_GetUserCmd( cmdNum, &cmd ); + +    AngleVectors( angles, forward, right, up ); + +    fFraction = (float)( cg.time - cg.forwardMoveTime ) / STRUGGLE_TIME; +    rFraction = (float)( cg.time - cg.rightMoveTime ) / STRUGGLE_TIME; +    uFraction = (float)( cg.time - cg.upMoveTime ) / STRUGGLE_TIME; + +    if( fFraction > 1.0f ) +      fFraction = 1.0f; +    if( rFraction > 1.0f ) +      rFraction = 1.0f; +    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 ) +      VectorMA( origin, -STRUGGLE_DIST * fFraction, forward, origin ); +    else +      cg.forwardMoveTime = cg.time; + +    if( cmd.rightmove > 0 ) +      VectorMA( origin, STRUGGLE_DIST * rFraction, right, origin ); +    else if( cmd.rightmove < 0 ) +      VectorMA( origin, -STRUGGLE_DIST * rFraction, right, origin ); +    else +      cg.rightMoveTime = cg.time; + +    if( cmd.upmove > 0 ) +      VectorMA( origin, STRUGGLE_DIST * uFraction, up, origin ); +    else if( cmd.upmove < 0 ) +      VectorMA( origin, -STRUGGLE_DIST * uFraction, up, origin ); +    else +      cg.upMoveTime = cg.time; +  } + +  if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_POISONCLOUDED && +      !( 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 ); + +    angles[ ROLL ] += fraction * PCLOUD_ROLL_AMPLITUDE; +    angles[ YAW ] += fraction * PCLOUD_ROLL_AMPLITUDE; +    angles[ PITCH ] += pitchFraction * PCLOUD_ROLL_AMPLITUDE / 2.0f; +  } + +  // this *feels* more realisitic for humans +  if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_HUMANS ) +  { +    angles[PITCH] += cg.bobfracsin * bob2 * 0.5; + +    // heavy breathing effects //FIXME: sound +    if( cg.predictedPlayerState.stats[ STAT_STAMINA ] < 0 ) +    { +      float deltaBreath = (float)( +        cg.predictedPlayerState.stats[ STAT_STAMINA ] < 0 ? +        -cg.predictedPlayerState.stats[ STAT_STAMINA ] : +        cg.predictedPlayerState.stats[ STAT_STAMINA ] ) / 200.0; +      float deltaAngle = cos( (float)cg.time/150.0 ) * deltaBreath; + +      deltaAngle += ( deltaAngle < 0 ? -deltaAngle : deltaAngle ) * 0.5; + +      angles[ PITCH ] -= deltaAngle; +    } +  } + +//=================================== + +  // 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; + +  // smooth out duck height changes +  timeDelta = cg.time - cg.duckTime; +  if( timeDelta < DUCK_TIME) +  { +    cg.refdef.vieworg[ 2 ] -= cg.duckChange +      * ( DUCK_TIME - timeDelta ) / DUCK_TIME; +  } + +  // add bob height +  bob = cg.bobfracsin * cg.xyspeed * bob2; + +  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; + + +  // add fall height +  delta = cg.time - cg.landTime; + +  if( delta < LAND_DEFLECT_TIME ) +  { +    f = delta / LAND_DEFLECT_TIME; +    cg.refdef.vieworg[ 2 ] += cg.landChange * f; +  } +  else if( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) +  { +    delta -= LAND_DEFLECT_TIME; +    f = 1.0 - ( delta / LAND_RETURN_TIME ); +    cg.refdef.vieworg[ 2 ] += cg.landChange * f; +  } + +  // add step offset +  CG_StepOffset( ); + +  // add kick offset + +  VectorAdd (origin, cg.kick_origin, origin); +} + +//====================================================================== + +/* +==================== +CG_CalcFov + +Fixed fov at intermissions, otherwise account for fov variable and zooms. +==================== +*/ +#define WAVE_AMPLITUDE  1 +#define WAVE_FREQUENCY  0.4 + +#define FOVWARPTIME     400.0 + +static int CG_CalcFov( void ) +{ +  float     x; +  float     phase; +  float     v; +  int       contents; +  float     fov_x, fov_y; +  float     zoomFov; +  float     f; +  int       inwater; +  int       attribFov; +  usercmd_t cmd; +  int       cmdNum; + +  cmdNum = trap_GetCurrentCmdNumber( ); +  trap_GetUserCmd( cmdNum, &cmd ); + +  if( cg.predictedPlayerState.pm_type == PM_INTERMISSION || +      ( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) ) +  { +    // if in intermission, use a fixed value +    fov_x = 90; +  } +  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; + +    if ( fov_x < 1 ) +      fov_x = 1; +    else if ( fov_x > 160 ) +      fov_x = 160; + +    if( cg.spawnTime > ( cg.time - FOVWARPTIME ) && +        BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_PCLASS ], 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 ); + +      fov_x = 170 - temp2; +    } + +    // account for zooms +    zoomFov = BG_FindZoomFovForWeapon( cg.predictedPlayerState.weapon ); +    if ( zoomFov < 1 ) +      zoomFov = 1; +    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 ( cg.zoomed ) +      { +        f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; + +        if ( f > 1.0 ) +          fov_x = zoomFov; +        else +          fov_x = fov_x + f * ( zoomFov - fov_x ); + +        // BUTTON_ATTACK2 isn't held so unzoom next time +        if( !( cmd.buttons & BUTTON_ATTACK2 ) ) +        { +          cg.zoomed   = qfalse; +          cg.zoomTime = cg.time; +        } +      } +      else +      { +        f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; + +        if ( f > 1.0 ) +          fov_x = fov_x; +        else +          fov_x = zoomFov + f * ( fov_x - zoomFov ); + +        // BUTTON_ATTACK2 is held so zoom next time +        if( cmd.buttons & BUTTON_ATTACK2 ) +        { +          cg.zoomed   = qtrue; +          cg.zoomTime = cg.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; + +  // 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; +    v = WAVE_AMPLITUDE * sin( phase ); +    fov_x += v; +    fov_y -= v; +    inwater = qtrue; +  } +  else +    inwater = qfalse; + +  if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_POISONCLOUDED && +      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 ); +    fov_x += v; +    fov_y += v; +  } + + +  // set it +  cg.refdef.fov_x = fov_x; +  cg.refdef.fov_y = fov_y; + +  if( !cg.zoomed ) +    cg.zoomSensitivity = 1; +  else +    cg.zoomSensitivity = cg.refdef.fov_y / 75.0; + +  return inwater; +} + + + +#define NORMAL_HEIGHT 64.0f +#define NORMAL_WIDTH  6.0f + +/* +=============== +CG_DrawSurfNormal + +Draws a vector against +the surface player is looking at +=============== +*/ +static void CG_DrawSurfNormal( void ) +{ +  trace_t     tr; +  vec3_t      end, temp; +  polyVert_t  normal[ 4 ]; +  vec4_t      color = { 0.0f, 255.0f, 0.0f, 128.0f }; + +  VectorMA( cg.refdef.vieworg, 8192, cg.refdef.viewaxis[ 0 ], end ); + +  CG_Trace( &tr, cg.refdef.vieworg, NULL, NULL, end, cg.predictedPlayerState.clientNum, MASK_SOLID ); + +  VectorCopy( tr.endpos, normal[ 0 ].xyz ); +  normal[ 0 ].st[ 0 ] = 0; +  normal[ 0 ].st[ 1 ] = 0; +  Vector4Copy( color, normal[ 0 ].modulate ); + +  VectorMA( tr.endpos, NORMAL_WIDTH, cg.refdef.viewaxis[ 1 ], temp ); +  VectorCopy( temp, normal[ 1 ].xyz); +  normal[ 1 ].st[ 0 ] = 0; +  normal[ 1 ].st[ 1 ] = 1; +  Vector4Copy( color, normal[ 1 ].modulate ); + +  VectorMA( tr.endpos, NORMAL_HEIGHT, tr.plane.normal, temp ); +  VectorMA( temp, NORMAL_WIDTH, cg.refdef.viewaxis[ 1 ], temp ); +  VectorCopy( temp, normal[ 2 ].xyz ); +  normal[ 2 ].st[ 0 ] = 1; +  normal[ 2 ].st[ 1 ] = 1; +  Vector4Copy( color, normal[ 2 ].modulate ); + +  VectorMA( tr.endpos, NORMAL_HEIGHT, tr.plane.normal, temp ); +  VectorCopy( temp, normal[ 3 ].xyz ); +  normal[ 3 ].st[ 0 ] = 1; +  normal[ 3 ].st[ 1 ] = 0; +  Vector4Copy( color, normal[ 3 ].modulate ); + +  trap_R_AddPolyToScene( cgs.media.outlineShader, 4, normal ); +} + +/* +=============== +CG_addSmoothOp +=============== +*/ +void CG_addSmoothOp( vec3_t rotAxis, float rotAngle, float timeMod ) +{ +  int i; + +  //iterate through smooth array +  for( i = 0; i < MAXSMOOTHS; i++ ) +  { +    //found an unused index in the smooth array +    if( cg.sList[ i ].time + cg_wwSmoothTime.integer < cg.time ) +    { +      //copy to array and stop +      VectorCopy( rotAxis, cg.sList[ i ].rotAxis ); +      cg.sList[ i ].rotAngle = rotAngle; +      cg.sList[ i ].time = cg.time; +      cg.sList[ i ].timeMod = timeMod; +      return; +    } +  } + +  //no free indices in the smooth array +} + +/* +=============== +CG_smoothWWTransitions +=============== +*/ +static void CG_smoothWWTransitions( playerState_t *ps, const vec3_t in, vec3_t out ) +{ +  vec3_t    surfNormal, rotAxis, temp; +  vec3_t    refNormal     = { 0.0f, 0.0f,  1.0f }; +  vec3_t    ceilingNormal = { 0.0f, 0.0f, -1.0f }; +  int       i; +  float     stLocal, sFraction, rotAngle; +  float     smoothTime, timeMod; +  qboolean  performed = qfalse; +  vec3_t    inAxis[ 3 ], lastAxis[ 3 ], outAxis[ 3 ]; + +  if( cg.snap->ps.pm_flags & PMF_FOLLOW ) +  { +    VectorCopy( in, out ); +    return; +  } + +  //set surfNormal +  if( !( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) ) +    VectorCopy( ps->grapplePoint, surfNormal ); +  else +    VectorCopy( ceilingNormal, surfNormal ); + +  AnglesToAxis( in, inAxis ); + +  //if we are moving from one surface to another smooth the transition +  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 ) ) +    { +      AngleVectors( in, temp, NULL, NULL ); +      ProjectPointOnPlane( rotAxis, temp, refNormal ); +      VectorNormalize( rotAxis ); +      rotAngle = 180.0f; +      timeMod = 1.5f; +    } +    else +    { +      AnglesToAxis( cg.lastVangles, lastAxis ); +      rotAngle = DotProduct( inAxis[ 0 ], lastAxis[ 0 ] ) + +                 DotProduct( inAxis[ 1 ], lastAxis[ 1 ] ) + +                 DotProduct( inAxis[ 2 ], lastAxis[ 2 ] ); + +      rotAngle = RAD2DEG( acos( ( rotAngle - 1.0f ) / 2.0f ) ); + +      CrossProduct( lastAxis[ 0 ], inAxis[ 0 ], temp ); +      VectorCopy( temp, rotAxis ); +      CrossProduct( lastAxis[ 1 ], inAxis[ 1 ], temp ); +      VectorAdd( rotAxis, temp, rotAxis ); +      CrossProduct( lastAxis[ 2 ], inAxis[ 2 ], temp ); +      VectorAdd( rotAxis, temp, rotAxis ); + +      VectorNormalize( rotAxis ); + +      timeMod = 1.0f; +    } + +    //add the op +    CG_addSmoothOp( rotAxis, rotAngle, timeMod ); +  } + +  //iterate through ops +  for( i = MAXSMOOTHS - 1; i >= 0; i-- ) +  { +    smoothTime = (int)( cg_wwSmoothTime.integer * cg.sList[ i ].timeMod ); + +    //if this op has time remaining, perform it +    if( cg.time < cg.sList[ i ].time + smoothTime ) +    { +      stLocal = 1.0f - ( ( ( cg.sList[ i ].time + smoothTime ) - cg.time ) / smoothTime ); +      sFraction = -( cos( stLocal * M_PI ) + 1.0f ) / 2.0f; + +      RotatePointAroundVector( outAxis[ 0 ], cg.sList[ i ].rotAxis, +        inAxis[ 0 ], sFraction * cg.sList[ i ].rotAngle ); +      RotatePointAroundVector( outAxis[ 1 ], cg.sList[ i ].rotAxis, +        inAxis[ 1 ], sFraction * cg.sList[ i ].rotAngle ); +      RotatePointAroundVector( outAxis[ 2 ], cg.sList[ i ].rotAxis, +        inAxis[ 2 ], sFraction * cg.sList[ i ].rotAngle ); + +      AxisCopy( outAxis, inAxis ); +      performed = qtrue; +    } +  } + +  //if we performed any ops then return the smoothed angles +  //otherwise simply return the in angles +  if( performed ) +    AxisToAngles( outAxis, out ); +  else +    VectorCopy( in, out ); + +  //copy the current normal to the lastNormal +  VectorCopy( in, cg.lastVangles ); +  VectorCopy( surfNormal, cg.lastNormal ); +} + +/* +=============== +CG_smoothWJTransitions +=============== +*/ +static void CG_smoothWJTransitions( playerState_t *ps, const vec3_t in, vec3_t out ) +{ +  int       i; +  float     stLocal, sFraction; +  qboolean  performed = qfalse; +  vec3_t    inAxis[ 3 ], outAxis[ 3 ]; + +  if( cg.snap->ps.pm_flags & PMF_FOLLOW ) +  { +    VectorCopy( in, out ); +    return; +  } + +  AnglesToAxis( in, inAxis ); + +  //iterate through ops +  for( i = MAXSMOOTHS - 1; i >= 0; i-- ) +  { +    //if this op has time remaining, perform it +    if( cg.time < cg.sList[ i ].time + cg_wwSmoothTime.integer ) +    { +      stLocal = ( ( cg.sList[ i ].time + cg_wwSmoothTime.integer ) - cg.time ) / cg_wwSmoothTime.integer; +      sFraction = 1.0f - ( ( cos( stLocal * M_PI * 2.0f ) + 1.0f ) / 2.0f ); + +      RotatePointAroundVector( outAxis[ 0 ], cg.sList[ i ].rotAxis, +        inAxis[ 0 ], sFraction * cg.sList[ i ].rotAngle ); +      RotatePointAroundVector( outAxis[ 1 ], cg.sList[ i ].rotAxis, +        inAxis[ 1 ], sFraction * cg.sList[ i ].rotAngle ); +      RotatePointAroundVector( outAxis[ 2 ], cg.sList[ i ].rotAxis, +        inAxis[ 2 ], sFraction * cg.sList[ i ].rotAngle ); + +      AxisCopy( outAxis, inAxis ); +      performed = qtrue; +    } +  } + +  //if we performed any ops then return the smoothed angles +  //otherwise simply return the in angles +  if( performed ) +    AxisToAngles( outAxis, out ); +  else +    VectorCopy( in, out ); +} + + +/* +=============== +CG_CalcViewValues + +Sets cg.refdef view values +=============== +*/ +static int CG_CalcViewValues( void ) +{ +  playerState_t *ps; + +  memset( &cg.refdef, 0, sizeof( cg.refdef ) ); + +  // calculate size of 3D view +  CG_CalcVrect( ); + +  ps = &cg.predictedPlayerState; + +  // intermission view +  if( ps->pm_type == PM_INTERMISSION ) +  { +    VectorCopy( ps->origin, cg.refdef.vieworg ); +    VectorCopy( ps->viewangles, cg.refdefViewAngles ); +    AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); + +    return CG_CalcFov( ); +  } + +  cg.bobcycle = ( ps->bobCycle & 128 ) >> 7; +  cg.bobfracsin = fabs( sin( ( ps->bobCycle & 127 ) / 127.0 * M_PI ) ); +  cg.xyspeed = sqrt( ps->velocity[ 0 ] * ps->velocity[ 0 ] + +    ps->velocity[ 1 ] * ps->velocity[ 1 ] ); + +  VectorCopy( ps->origin, cg.refdef.vieworg ); + +  if( BG_ClassHasAbility( ps->stats[ STAT_PCLASS ], SCA_WALLCLIMBER ) ) +    CG_smoothWWTransitions( ps, ps->viewangles, cg.refdefViewAngles ); +  else if( BG_ClassHasAbility( ps->stats[ STAT_PCLASS ], 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( !( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) ) +      VectorSet( cg.lastNormal, 0.0f, 0.0f, 1.0f ); +  } + +  // add error decay +  if( cg_errorDecay.value > 0 ) +  { +    int   t; +    float f; + +    t = cg.time - cg.predictedErrorTime; +    f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; + +    if( f > 0 && f < 1 ) +      VectorMA( cg.refdef.vieworg, f, cg.predictedError, cg.refdef.vieworg ); +    else +      cg.predictedErrorTime = 0; +  } + +  //shut off the poison cloud effect if it's still on the go +  if( cg.snap->ps.stats[ STAT_HEALTH ] <= 0 ) +  { +    if( CG_IsParticleSystemValid( &cg.poisonCloudPS ) ) +      CG_DestroyParticleSystem( &cg.poisonCloudPS ); +  } + +  if( cg.renderingThirdPerson ) +  { +    // back away from character +    CG_OffsetThirdPersonView( ); +  } +  else +  { +    // offset for local bobbing and kicks +    CG_OffsetFirstPersonView( ); +  } + +  // position eye reletive to origin +  AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); + +  if( cg.hyperspace ) +    cg.refdef.rdflags |= RDF_NOWORLDMODEL | RDF_HYPERSPACE; + +  //draw the surface normal looking at +  if( cg_drawSurfNormal.integer ) +    CG_DrawSurfNormal( ); + +  // field of view +  return CG_CalcFov( ); +} + +/* +===================== +CG_AddBufferedSound +===================== +*/ +void CG_AddBufferedSound( sfxHandle_t sfx ) +{ +  if( !sfx ) +    return; + +  cg.soundBuffer[ cg.soundBufferIn ] = sfx; +  cg.soundBufferIn = ( cg.soundBufferIn + 1 ) % MAX_SOUNDBUFFER; + +  if( cg.soundBufferIn == cg.soundBufferOut ) +    cg.soundBufferOut++; +} + +/* +===================== +CG_PlayBufferedSounds +===================== +*/ +static void CG_PlayBufferedSounds( void ) +{ +  if( cg.soundTime < cg.time ) +  { +    if( cg.soundBufferOut != cg.soundBufferIn && cg.soundBuffer[ cg.soundBufferOut ] ) +    { +      trap_S_StartLocalSound( cg.soundBuffer[ cg.soundBufferOut ], CHAN_ANNOUNCER ); +      cg.soundBuffer[ cg.soundBufferOut ] = 0; +      cg.soundBufferOut = ( cg.soundBufferOut + 1 ) % MAX_SOUNDBUFFER; +      cg.soundTime = cg.time + 750; +    } +  } +} + +//========================================================================= + +/* +================= +CG_DrawActiveFrame + +Generates and draws a game scene and status information at the given time. +================= +*/ +void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ) +{ +  int   inwater; + +  cg.time = serverTime; +  cg.demoPlayback = demoPlayback; + +  // update cvars +  CG_UpdateCvars( ); + +  // if we are only updating the screen as a loading +  // pacifier, don't even try to read snapshots +  if( cg.infoScreenText[ 0 ] != 0 ) +  { +    CG_DrawLoadingScreen( ); +    return; +  } + +  // any looped sounds will be respecified as entities +  // are added to the render list +  trap_S_ClearLoopingSounds( qfalse ); + +  // clear all the render lists +  trap_R_ClearScene( ); + +  // set up cg.snap and possibly cg.nextSnap +  CG_ProcessSnapshots( ); + +  // if we haven't received any snapshots yet, all +  // we can draw is the information screen +  if( !cg.snap || ( cg.snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) ) +  { +    CG_DrawLoadingScreen( ); +    return; +  } + +  // let the client system know what our weapon and zoom settings are +  trap_SetUserCmdValue( cg.weaponSelect, cg.zoomSensitivity ); + +  // this counter will be bumped for every valid scene we generate +  cg.clientFrame++; + +  // update cg.predictedPlayerState +  CG_PredictPlayerState( ); + +  // decide on third person view +  cg.renderingThirdPerson = cg_thirdPerson.integer || ( cg.snap->ps.stats[ STAT_HEALTH ] <= 0 ); + +  // build cg.refdef +  inwater = CG_CalcViewValues( ); + +  // build the render lists +  if( !cg.hyperspace ) +  { +    CG_AddPacketEntities( );     // after calcViewValues, so predicted player state is correct +    CG_AddMarks( ); +  } + +  CG_AddViewWeapon( &cg.predictedPlayerState ); + +  //after CG_AddViewWeapon +  if( !cg.hyperspace ) +  { +    CG_AddParticles( ); +    CG_AddTrails( ); +  } + +  // add buffered sounds +  CG_PlayBufferedSounds( ); + +  // finish up the rest of the refdef +  if( cg.testModelEntity.hModel ) +    CG_AddTestModel( ); + +  cg.refdef.time = cg.time; +  memcpy( cg.refdef.areamask, cg.snap->areamask, sizeof( cg.refdef.areamask ) ); + +  //remove expired console lines +  if( cg.consoleLines[ 0 ].time + cg_consoleLatency.integer < cg.time && cg_consoleLatency.integer > 0 ) +    CG_RemoveNotifyLine( ); + +  // update audio positions +  trap_S_Respatialize( cg.snap->ps.clientNum, cg.refdef.vieworg, cg.refdef.viewaxis, inwater ); + +  // make sure the lagometerSample and frame timing isn't done twice when in stereo +  if( stereoView != STEREO_RIGHT ) +  { +    cg.frametime = cg.time - cg.oldTime; + +    if( cg.frametime < 0 ) +      cg.frametime = 0; + +    cg.oldTime = cg.time; +    CG_AddLagometerFrameInfo( ); +  } + +  if( cg_timescale.value != cg_timescaleFadeEnd.value ) +  { +    if( cg_timescale.value < cg_timescaleFadeEnd.value ) +    { +      cg_timescale.value += cg_timescaleFadeSpeed.value * ( (float)cg.frametime ) / 1000; +      if( cg_timescale.value > cg_timescaleFadeEnd.value ) +        cg_timescale.value = cg_timescaleFadeEnd.value; +    } +    else +    { +      cg_timescale.value -= cg_timescaleFadeSpeed.value * ( (float)cg.frametime ) / 1000; +      if( cg_timescale.value < cg_timescaleFadeEnd.value ) +        cg_timescale.value = cg_timescaleFadeEnd.value; +    } + +    if( cg_timescaleFadeSpeed.value ) +      trap_Cvar_Set( "timescale", va( "%f", cg_timescale.value ) ); +  } + +  // actually issue the rendering calls +  CG_DrawActive( stereoView ); + +  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 new file mode 100644 index 0000000..deae4e0 --- /dev/null +++ b/src/cgame/cg_weapons.c @@ -0,0 +1,1845 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +// cg_weapons.c -- events and effects dealing with weapons + + +#include "cg_local.h" + +/* +================= +CG_RegisterUpgrade + +The server says this item is used on this level +================= +*/ +void CG_RegisterUpgrade( int upgradeNum ) +{ +  upgradeInfo_t   *upgradeInfo; +  char            *icon; + +  upgradeInfo = &cg_upgrades[ upgradeNum ]; + +  if( upgradeNum == 0 ) +    return; + +  if( upgradeInfo->registered ) +    return; + +  memset( upgradeInfo, 0, sizeof( *upgradeInfo ) ); +  upgradeInfo->registered = qtrue; + +  if( !BG_FindNameForUpgrade( upgradeNum ) ) +    CG_Error( "Couldn't find upgrade %i", upgradeNum ); + +  upgradeInfo->humanName = BG_FindHumanNameForUpgrade( upgradeNum ); + +  //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 ) ) ) +    upgradeInfo->upgradeIcon = trap_R_RegisterShader( icon ); +} + +/* +=============== +CG_InitUpgrades + +Precaches upgrades +=============== +*/ +void CG_InitUpgrades( void ) +{ +  int   i; + +  memset( cg_upgrades, 0, sizeof( cg_upgrades ) ); + +  for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) +    CG_RegisterUpgrade( i ); +} + + +/* +=============== +CG_ParseWeaponModeSection + +Parse a weapon mode section +=============== +*/ +static qboolean CG_ParseWeaponModeSection( weaponInfoMode_t *wim, char **text_p ) +{ +  char  *token; +  int   i; + +  // read optional parameters +  while( 1 ) +  { +    token = COM_Parse( text_p ); + +    if( !token ) +      break; + +    if( !Q_stricmp( token, "" ) ) +      return qfalse; + +    if( !Q_stricmp( token, "missileModel" ) ) +    { +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      wim->missileModel = trap_R_RegisterModel( token ); + +      if( !wim->missileModel ) +        CG_Printf( S_COLOR_RED "ERROR: missile model not found %s\n", token ); + +      continue; +    } +    else if( !Q_stricmp( token, "missileSprite" ) ) +    { +      int size = 0; + +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      size = atoi( token ); + +      if( size < 0 ) +        size = 0; + +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      wim->missileSprite = trap_R_RegisterShader( token ); +      wim->missileSpriteSize = size; +      wim->usesSpriteMissle = qtrue; + +      if( !wim->missileSprite ) +        CG_Printf( S_COLOR_RED "ERROR: missile sprite not found %s\n", token ); + +      continue; +    } +    else if( !Q_stricmp( token, "missileRotates" ) ) +    { +      wim->missileRotates = qtrue; + +      continue; +    } +    else if( !Q_stricmp( token, "missileAnimates" ) ) +    { +      wim->missileAnimates = qtrue; + +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      wim->missileAnimStartFrame = atoi( token ); + +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      wim->missileAnimNumFrames = atoi( token ); + +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      wim->missileAnimFrameRate = atoi( token ); + +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      wim->missileAnimLooping = atoi( token ); + +      continue; +    } +    else if( !Q_stricmp( token, "missileParticleSystem" ) ) +    { +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      wim->missileParticleSystem = CG_RegisterParticleSystem( token ); + +      if( !wim->missileParticleSystem ) +        CG_Printf( S_COLOR_RED "ERROR: missile particle system not found %s\n", token ); + +      continue; +    } +    else if( !Q_stricmp( token, "missileTrailSystem" ) ) +    { +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      wim->missileTrailSystem = CG_RegisterTrailSystem( token ); + +      if( !wim->missileTrailSystem ) +        CG_Printf( S_COLOR_RED "ERROR: missile trail system not found %s\n", token ); + +      continue; +    } +    else if( !Q_stricmp( token, "muzzleParticleSystem" ) ) +    { +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      wim->muzzleParticleSystem = CG_RegisterParticleSystem( token ); + +      if( !wim->muzzleParticleSystem ) +        CG_Printf( S_COLOR_RED "ERROR: muzzle particle system not found %s\n", token ); + +      continue; +    } +    else if( !Q_stricmp( token, "impactParticleSystem" ) ) +    { +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      wim->impactParticleSystem = CG_RegisterParticleSystem( token ); + +      if( !wim->impactParticleSystem ) +        CG_Printf( S_COLOR_RED "ERROR: impact particle system not found %s\n", token ); + +      continue; +    } +    else if( !Q_stricmp( token, "impactMark" ) ) +    { +      int size = 0; + +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      size = atoi( token ); + +      if( size < 0 ) +        size = 0; + +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      wim->impactMark = trap_R_RegisterShader( token ); +      wim->impactMarkSize = size; + +      if( !wim->impactMark ) +        CG_Printf( S_COLOR_RED "ERROR: impact mark shader not found %s\n", token ); + +      continue; +    } +    else if( !Q_stricmp( token, "impactSound" ) ) +    { +      int index = 0; + +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      index = atoi( token ); + +      if( index < 0 ) +        index = 0; +      else if( index > 3 ) +        index = 3; + +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      wim->impactSound[ index ] = trap_S_RegisterSound( token, qfalse ); + +      continue; +    } +    else if( !Q_stricmp( token, "impactFleshSound" ) ) +    { +      int index = 0; + +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      index = atoi( token ); + +      if( index < 0 ) +        index = 0; +      else if( index > 3 ) +        index = 3; + +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      wim->impactFleshSound[ index ] = trap_S_RegisterSound( token, qfalse ); + +      continue; +    } +    else if( !Q_stricmp( token, "alwaysImpact" ) ) +    { +      wim->alwaysImpact = qtrue; + +      continue; +    } +    else if( !Q_stricmp( token, "flashDLightColor" ) ) +    { +      for( i = 0 ; i < 3 ; i++ ) +      { +        token = COM_Parse( text_p ); +        if( !token ) +          break; + +        wim->flashDlightColor[ i ] = atof( token ); +      } + +      continue; +    } +    else if( !Q_stricmp( token, "continuousFlash" ) ) +    { +      wim->continuousFlash = qtrue; + +      continue; +    } +    else if( !Q_stricmp( token, "missileDlightColor" ) ) +    { +      for( i = 0 ; i < 3 ; i++ ) +      { +        token = COM_Parse( text_p ); +        if( !token ) +          break; + +        wim->missileDlightColor[ i ] = atof( token ); +      } + +      continue; +    } +    else if( !Q_stricmp( token, "missileDlight" ) ) +    { +      int size = 0; + +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      size = atoi( token ); + +      if( size < 0 ) +        size = 0; + +      wim->missileDlight = size; + +      continue; +    } +    else if( !Q_stricmp( token, "firingSound" ) ) +    { +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      wim->firingSound = trap_S_RegisterSound( token, qfalse ); + +      continue; +    } +    else if( !Q_stricmp( token, "missileSound" ) ) +    { +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      wim->missileSound = trap_S_RegisterSound( token, qfalse ); + +      continue; +    } +    else if( !Q_stricmp( token, "flashSound" ) ) +    { +      int index = 0; + +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      index = atoi( token ); + +      if( index < 0 ) +        index = 0; +      else if( index > 3 ) +        index = 3; + +      token = COM_Parse( text_p ); +      if( !token ) +        break; + +      wim->flashSound[ index ] = trap_S_RegisterSound( token, qfalse ); + +      continue; +    } +    else if( !Q_stricmp( token, "}" ) ) +      return qtrue; //reached the end of this weapon section +    else +    { +      CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in weapon section\n", token ); +      return qfalse; +    } +  } + +  return qfalse; +} + + +/* +====================== +CG_ParseWeaponFile + +Parses a configuration file describing a weapon +====================== +*/ +static qboolean CG_ParseWeaponFile( const char *filename, weaponInfo_t *wi ) +{ +  char          *text_p; +  int           len; +  char          *token; +  char          text[ 20000 ]; +  fileHandle_t  f; +  weaponMode_t  weaponMode = WPM_NONE; + +  // load the file +  len = trap_FS_FOpenFile( filename, &f, FS_READ ); +  if( len <= 0 ) +    return qfalse; + +  if( len >= sizeof( text ) - 1 ) +  { +    CG_Printf( "File %s too long\n", filename ); +    return qfalse; +  } + +  trap_FS_Read( text, len, f ); +  text[ len ] = 0; +  trap_FS_FCloseFile( f ); + +  // parse the text +  text_p = text; + +  // read optional parameters +  while( 1 ) +  { +    token = COM_Parse( &text_p ); + +    if( !token ) +      break; + +    if( !Q_stricmp( token, "" ) ) +      break; + +    if( !Q_stricmp( token, "{" ) ) +    { +      if( weaponMode == WPM_NONE ) +      { +        CG_Printf( S_COLOR_RED "ERROR: weapon mode section started without a declaration\n" ); +        return qfalse; +      } +      else if( !CG_ParseWeaponModeSection( &wi->wim[ weaponMode ], &text_p ) ) +      { +        CG_Printf( S_COLOR_RED "ERROR: failed to parse weapon mode section\n" ); +        return qfalse; +      } + +      //start parsing ejectors again +      weaponMode = WPM_NONE; + +      continue; +    } +    else if( !Q_stricmp( token, "primary" ) ) +    { +      weaponMode = WPM_PRIMARY; +      continue; +    } +    else if( !Q_stricmp( token, "secondary" ) ) +    { +      weaponMode = WPM_SECONDARY; +      continue; +    } +    else if( !Q_stricmp( token, "tertiary" ) ) +    { +      weaponMode = WPM_TERTIARY; +      continue; +    } +    else if( !Q_stricmp( token, "weaponModel" ) ) +    { +      char path[ MAX_QPATH ]; + +      token = COM_Parse( &text_p ); +      if( !token ) +        break; + +      wi->weaponModel = trap_R_RegisterModel( token ); + +      if( !wi->weaponModel ) +        CG_Printf( S_COLOR_RED "ERROR: weapon model not found %s\n", token ); + +      strcpy( path, token ); +      COM_StripExtension( path, path, MAX_QPATH ); +      strcat( path, "_flash.md3" ); +      wi->flashModel = trap_R_RegisterModel( path ); + +      strcpy( path, token ); +      COM_StripExtension( path, path, MAX_QPATH ); +      strcat( path, "_barrel.md3" ); +      wi->barrelModel = trap_R_RegisterModel( path ); + +      strcpy( path, token ); +      COM_StripExtension( path, path, MAX_QPATH ); +      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, "idleSound" ) ) +    { +      token = COM_Parse( &text_p ); +      if( !token ) +        break; + +      wi->readySound = trap_S_RegisterSound( token, qfalse ); + +      continue; +    } +    else if( !Q_stricmp( token, "icon" ) ) +    { +      token = COM_Parse( &text_p ); +      if( !token ) +        break; + +      wi->weaponIcon = wi->ammoIcon = trap_R_RegisterShader( token ); + +      if( !wi->weaponIcon ) +        CG_Printf( S_COLOR_RED "ERROR: weapon icon not found %s\n", token ); + +      continue; +    } +    else if( !Q_stricmp( token, "crosshair" ) ) +    { +      int size = 0; + +      token = COM_Parse( &text_p ); +      if( !token ) +        break; + +      size = atoi( token ); + +      if( size < 0 ) +        size = 0; + +      token = COM_Parse( &text_p ); +      if( !token ) +        break; + +      wi->crossHair = trap_R_RegisterShader( token ); +      wi->crossHairSize = size; + +      if( !wi->crossHair ) +        CG_Printf( S_COLOR_RED "ERROR: weapon crosshair not found %s\n", token ); + +      continue; +    } +    else if( !Q_stricmp( token, "disableIn3rdPerson" ) ) +    { +      wi->disableIn3rdPerson = qtrue; + +      continue; +    } + +    Com_Printf( S_COLOR_RED "ERROR: unknown token '%s'\n", token ); +    return qfalse; +  } + +  return qtrue; +} + +/* +================= +CG_RegisterWeapon +================= +*/ +void CG_RegisterWeapon( int weaponNum ) +{ +  weaponInfo_t  *weaponInfo; +  char          path[ MAX_QPATH ]; +  vec3_t        mins, maxs; +  int           i; + +  weaponInfo = &cg_weapons[ weaponNum ]; + +  if( weaponNum == 0 ) +    return; + +  if( weaponInfo->registered ) +    return; + +  memset( weaponInfo, 0, sizeof( *weaponInfo ) ); +  weaponInfo->registered = qtrue; + +  if( !BG_FindNameForWeapon( weaponNum ) ) +    CG_Error( "Couldn't find weapon %i", weaponNum ); + +  Com_sprintf( path, MAX_QPATH, "models/weapons/%s/weapon.cfg", BG_FindNameForWeapon( weaponNum ) ); + +  weaponInfo->humanName = BG_FindHumanNameForWeapon( weaponNum ); + +  if( !CG_ParseWeaponFile( 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; +} + +/* +=============== +CG_InitWeapons + +Precaches weapons +=============== +*/ +void CG_InitWeapons( void ) +{ +  int   i; + +  memset( cg_weapons, 0, sizeof( cg_weapons ) ); + +  for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) +    CG_RegisterWeapon( i ); + +  cgs.media.level2ZapTS = CG_RegisterTrailSystem( "models/weapons/lev2zap/lightning" ); +} + + +/* +======================================================================================== + +VIEW WEAPON + +======================================================================================== +*/ + +/* +================= +CG_MapTorsoToWeaponFrame + +================= +*/ +static int CG_MapTorsoToWeaponFrame( clientInfo_t *ci, int frame ) +{ + +  // change weapon +  if( frame >= ci->animations[ TORSO_DROP ].firstFrame && +      frame < ci->animations[ TORSO_DROP ].firstFrame + 9 ) +    return frame - ci->animations[ TORSO_DROP ].firstFrame + 6; + +  // stand attack +  if( frame >= ci->animations[ TORSO_ATTACK ].firstFrame && +      frame < ci->animations[ TORSO_ATTACK ].firstFrame + 6 ) +    return 1 + frame - ci->animations[ TORSO_ATTACK ].firstFrame; + +  // stand attack 2 +  if( frame >= ci->animations[ TORSO_ATTACK2 ].firstFrame && +      frame < ci->animations[ TORSO_ATTACK2 ].firstFrame + 6 ) +    return 1 + frame - ci->animations[ TORSO_ATTACK2 ].firstFrame; + +  return 0; +} + + +/* +============== +CG_CalculateWeaponPosition +============== +*/ +static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles ) +{ +  float scale; +  int   delta; +  float fracsin; +  float bob; + +  VectorCopy( cg.refdef.vieworg, origin ); +  VectorCopy( cg.refdefViewAngles, angles ); + +  // on odd legs, invert some angles +  if( cg.bobcycle & 1 ) +    scale = -cg.xyspeed; +  else +    scale = cg.xyspeed; + +  // gun angles from bobbing +  // bob amount is class dependant +  bob = BG_FindBobForClass( cg.predictedPlayerState.stats[ STAT_PCLASS ] ); + +  if( bob != 0 ) +  { +    angles[ ROLL ] += scale * cg.bobfracsin * 0.005; +    angles[ YAW ] += scale * cg.bobfracsin * 0.01; +    angles[ PITCH ] += cg.xyspeed * cg.bobfracsin * 0.005; +  } + +  // drop the weapon when landing +  if( !BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_PCLASS ], SCA_NOWEAPONDRIFT ) ) +  { +    delta = cg.time - cg.landTime; +    if( delta < LAND_DEFLECT_TIME ) +      origin[ 2 ] += cg.landChange*0.25 * delta / LAND_DEFLECT_TIME; +    else if( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) +      origin[ 2 ] += cg.landChange*0.25 * +        ( LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta ) / LAND_RETURN_TIME; + +    // idle drift +    scale = cg.xyspeed + 40; +    fracsin = sin( cg.time * 0.001 ); +    angles[ ROLL ] += scale * fracsin * 0.01; +    angles[ YAW ] += scale * fracsin * 0.01; +    angles[ PITCH ] += scale * fracsin * 0.01; +  } +} + + +/* +====================== +CG_MachinegunSpinAngle +====================== +*/ +#define   SPIN_SPEED  0.9 +#define   COAST_TIME  1000 +static float CG_MachinegunSpinAngle( centity_t *cent, qboolean firing ) +{ +  int   delta; +  float angle; +  float speed; + +  delta = cg.time - cent->pe.barrelTime; +  if( cent->pe.barrelSpinning ) +    angle = cent->pe.barrelAngle + delta * SPIN_SPEED; +  else +  { +    if( delta > COAST_TIME ) +      delta = COAST_TIME; + +    speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME ); +    angle = cent->pe.barrelAngle + delta * speed; +  } + +  if( cent->pe.barrelSpinning == !firing ) +  { +    cent->pe.barrelTime = cg.time; +    cent->pe.barrelAngle = AngleMod( angle ); +    cent->pe.barrelSpinning = firing; +  } + +  return angle; +} + + +/* +============= +CG_AddPlayerWeapon + +Used for both the view weapon (ps is valid) and the world modelother character models (ps is NULL) +The main player will have this called for BOTH cases, so effects like light and +sound should only be done on the world model case. +============= +*/ +void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent ) +{ +  refEntity_t   gun; +  refEntity_t   barrel; +  refEntity_t   flash; +  vec3_t        angles; +  weapon_t      weaponNum; +  weaponMode_t  weaponMode; +  weaponInfo_t  *weapon; +  qboolean      noGunModel; +  qboolean      firing; + +  weaponNum = cent->currentState.weapon; +  weaponMode = cent->currentState.generic1; + +  if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES ) +    weaponMode = WPM_PRIMARY; + +  if( ( ( cent->currentState.eFlags & EF_FIRING ) && weaponMode == WPM_PRIMARY ) || +      ( ( cent->currentState.eFlags & EF_FIRING2 ) && weaponMode == WPM_SECONDARY ) || +      ( ( cent->currentState.eFlags & EF_FIRING3 ) && weaponMode == WPM_TERTIARY ) ) +    firing = qtrue; +  else +    firing = qfalse; + +  CG_RegisterWeapon( weaponNum ); +  weapon = &cg_weapons[ weaponNum ]; + +  // add the weapon +  memset( &gun, 0, sizeof( gun ) ); +  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; +    gun.shaderRGBA[ 1 ] = 255; +    gun.shaderRGBA[ 2 ] = 255; +    gun.shaderRGBA[ 3 ] = 255; + +    //set weapon[1/2]Time when respective buttons change state +    if( cg.weapon1Firing != ( cg.predictedPlayerState.eFlags & EF_FIRING ) ) +    { +      cg.weapon1Time = cg.time; +      cg.weapon1Firing = ( cg.predictedPlayerState.eFlags & EF_FIRING ); +    } + +    if( cg.weapon2Firing != ( cg.predictedPlayerState.eFlags & EF_FIRING2 ) ) +    { +      cg.weapon2Time = cg.time; +      cg.weapon2Firing = ( cg.predictedPlayerState.eFlags & EF_FIRING2 ); +    } + +    if( cg.weapon3Firing != ( cg.predictedPlayerState.eFlags & EF_FIRING3 ) ) +    { +      cg.weapon3Time = cg.time; +      cg.weapon3Firing = ( cg.predictedPlayerState.eFlags & EF_FIRING3 ); +    } +  } + +  gun.hModel = weapon->weaponModel; + +  noGunModel = ( ( !ps || cg.renderingThirdPerson ) && weapon->disableIn3rdPerson ) || !gun.hModel; + +  if( !ps ) +  { +    // add weapon ready sound +    if( firing && weapon->wim[ weaponMode ].firingSound ) +    { +      trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, +                              weapon->wim[ weaponMode ].firingSound ); +    } +    else if( weapon->readySound ) +      trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->readySound ); +  } + +  if( !noGunModel ) +  { +    CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_weapon" ); + +    trap_R_AddRefEntityToScene( &gun ); + +    // add the spinning barrel +    if( weapon->barrelModel ) +    { +      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" ); + +      trap_R_AddRefEntityToScene( &barrel ); +    } +  } + +  if( CG_IsParticleSystemValid( ¢->muzzlePS ) ) +  { +    if( ps || cg.renderingThirdPerson || +        cent->currentState.number != cg.predictedPlayerState.clientNum ) +    { +      if( noGunModel ) +        CG_SetAttachmentTag( ¢->muzzlePS->attachment, *parent, parent->hModel, "tag_weapon" ); +      else +        CG_SetAttachmentTag( ¢->muzzlePS->attachment, gun, weapon->weaponModel, "tag_flash" ); +    } + +    //if the PS is infinite disable it when not firing +    if( !firing && CG_IsParticleSystemInfinite( cent->muzzlePS ) ) +      CG_DestroyParticleSystem( ¢->muzzlePS ); +  } + +  // add the flash +  if( !weapon->wim[ weaponMode ].continuousFlash || !firing ) +  { +    // impulse flash +    if( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME ) +      return; +  } + +  memset( &flash, 0, sizeof( flash ) ); +  VectorCopy( parent->lightingOrigin, flash.lightingOrigin ); +  flash.shadowPlane = parent->shadowPlane; +  flash.renderfx = parent->renderfx; + +  flash.hModel = weapon->flashModel; +  if( flash.hModel ) +  { +    angles[ YAW ] = 0; +    angles[ PITCH ] = 0; +    angles[ ROLL ] = crandom( ) * 10; +    AnglesToAxis( angles, flash.axis ); + +    if( noGunModel ) +      CG_PositionRotatedEntityOnTag( &flash, parent, parent->hModel, "tag_weapon" ); +    else +      CG_PositionRotatedEntityOnTag( &flash, &gun, weapon->weaponModel, "tag_flash" ); + +    trap_R_AddRefEntityToScene( &flash ); +  } + +  if( ps || cg.renderingThirdPerson || +      cent->currentState.number != cg.predictedPlayerState.clientNum ) +  { +    if( weapon->wim[ weaponMode ].muzzleParticleSystem && cent->muzzlePsTrigger ) +    { +      cent->muzzlePS = CG_SpawnNewParticleSystem( weapon->wim[ weaponMode ].muzzleParticleSystem ); + +      if( CG_IsParticleSystemValid( ¢->muzzlePS ) ) +      { +        if( noGunModel ) +          CG_SetAttachmentTag( ¢->muzzlePS->attachment, *parent, parent->hModel, "tag_weapon" ); +        else +          CG_SetAttachmentTag( ¢->muzzlePS->attachment, gun, weapon->weaponModel, "tag_flash" ); + +        CG_SetAttachmentCent( ¢->muzzlePS->attachment, cent ); +        CG_AttachToTag( ¢->muzzlePS->attachment ); +      } + +      cent->muzzlePsTrigger = qfalse; +    } + +    // make a dlight for the flash +    if( weapon->wim[ weaponMode ].flashDlightColor[ 0 ] || +        weapon->wim[ weaponMode ].flashDlightColor[ 1 ] || +        weapon->wim[ weaponMode ].flashDlightColor[ 2 ] ) +    { +      trap_R_AddLightToScene( flash.origin, 300 + ( rand( ) & 31 ), +          weapon->wim[ weaponMode ].flashDlightColor[ 0 ], +          weapon->wim[ weaponMode ].flashDlightColor[ 1 ], +          weapon->wim[ weaponMode ].flashDlightColor[ 2 ] ); +    } +  } +} + +/* +============== +CG_AddViewWeapon + +Add the weapon, and flash for the player's view +============== +*/ +void CG_AddViewWeapon( playerState_t *ps ) +{ +  refEntity_t   hand; +  centity_t     *cent; +  clientInfo_t  *ci; +  float         fovOffset; +  vec3_t        angles; +  weaponInfo_t  *wi; +  weapon_t      weapon = ps->weapon; +  weaponMode_t  weaponMode = ps->generic1; + +  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 ) ) +    return; + +  // no weapon carried - can't draw it +  if( weapon == WP_NONE ) +    return; + +  if( ps->pm_type == PM_INTERMISSION ) +    return; + +  // draw a prospective buildable infront of the player +  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; + +  // allow the gun to be completely removed +  if( !cg_drawGun.integer ) +  { +    vec3_t origin; + +    VectorCopy( cg.refdef.vieworg, origin ); +    VectorMA( origin, -8, cg.refdef.viewaxis[ 2 ], origin ); + +    if( cent->muzzlePS ) +      CG_SetAttachmentPoint( ¢->muzzlePS->attachment, origin ); + +    //check for particle systems +    if( wi->wim[ weaponMode ].muzzleParticleSystem && cent->muzzlePsTrigger ) +    { +      cent->muzzlePS = CG_SpawnNewParticleSystem( wi->wim[ weaponMode ].muzzleParticleSystem ); + +      if( CG_IsParticleSystemValid( ¢->muzzlePS ) ) +      { +        CG_SetAttachmentPoint( ¢->muzzlePS->attachment, origin ); +        CG_SetAttachmentCent( ¢->muzzlePS->attachment, cent ); +        CG_AttachToPoint( ¢->muzzlePS->attachment ); +      } +      cent->muzzlePsTrigger = qfalse; +    } + +    return; +  } + +  // don't draw if testing a gun model +  if( cg.testGun ) +    return; + +  // drop gun lower at higher fov +  if( cg.refdef.fov_y > 90 ) +    fovOffset = -0.4 * ( cg.refdef.fov_y - 90 ); +  else +    fovOffset = 0; + +  memset( &hand, 0, sizeof( hand ) ); + +  // set up gun position +  CG_CalculateWeaponPosition( hand.origin, angles ); + +  VectorMA( hand.origin, cg_gun_x.value, cg.refdef.viewaxis[ 0 ], hand.origin ); +  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 ); + +  if( weapon == WP_LUCIFER_CANNON && ps->stats[ STAT_MISC ] > 0 ) +  { +    float fraction = (float)ps->stats[ STAT_MISC ] / (float)LCANNON_TOTAL_CHARGE; + +    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 ); + +  // map torso animations to weapon animations +  if( cg_gun_frame.integer ) +  { +    // development tool +    hand.frame = hand.oldframe = cg_gun_frame.integer; +    hand.backlerp = 0; +  } +  else +  { +    // get clientinfo for animation map +    ci = &cgs.clientinfo[ cent->currentState.clientNum ]; +    hand.frame = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.frame ); +    hand.oldframe = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.oldFrame ); +    hand.backlerp = cent->pe.torso.backlerp; +  } + +  hand.hModel = wi->handsModel; +  hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON | RF_MINLIGHT; + +  // add everything onto the hand +  CG_AddPlayerWeapon( &hand, ps, &cg.predictedPlayerEntity ); +} + +/* +============================================================================== + +WEAPON SELECTION + +============================================================================== +*/ + +/* +=============== +CG_WeaponSelectable +=============== +*/ +static qboolean CG_WeaponSelectable( weapon_t weapon ) +{ +  //int ammo, clips; +  // +  //BG_UnpackAmmoArray( i, cg.snap->ps.ammo, cg.snap->ps.powerups, &ammo, &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; +} + + +/* +=============== +CG_UpgradeSelectable +=============== +*/ +static qboolean CG_UpgradeSelectable( upgrade_t upgrade ) +{ +  if( !BG_InventoryContainsUpgrade( upgrade, cg.snap->ps.stats ) ) +    return qfalse; + +  return BG_FindUsableForUpgrade( upgrade ); +} + + +#define ICON_BORDER 4 + +/* +=================== +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; +  int           items[ 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 +  if( cg.predictedPlayerState.stats[ STAT_HEALTH ] <= 0 ) +    return; + +  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( ); +  } + +  // 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 if( height <= width ) +  { +    vertical = qfalse; +    iconsize = height; +    length = width / height; +  } + +  selectWindow = length / 2; + +  for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) +  { +    if( !BG_InventoryContainsWeapon( i, cg.snap->ps.stats ) ) +      continue; +       +    { +      int ammo, clips; +   +      BG_UnpackAmmoArray( i, cg.snap->ps.ammo, cg.snap->ps.powerups, &ammo, &clips ); +   +      if( !ammo && !clips && !BG_FindInfinteAmmoForWeapon( i ) ) +        colinfo[ numItems ] = 1; +      else +        colinfo[ numItems ] = 0; +    	 +    } + +    if( i == cg.weaponSelect ) +      selectedItem = numItems; + +    CG_RegisterWeapon( i ); +    items[ numItems ] = i; +    numItems++; +  } + +  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 ) ) +      colinfo[ numItems ] = 2; +     + +    if( i == cg.weaponSelect - 32 ) +      selectedItem = numItems; + +    CG_RegisterUpgrade( i ); +    items[ numItems ] = i + 32; +    numItems++; +  } + +  for( i = 0; i < length; i++ ) +  { +    int displacement = i - selectWindow; +    int item = displacement + selectedItem; + +    if( ( item >= 0 ) && ( item < numItems ) ) +    { +      switch( colinfo[ item ] ) +      { +        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( vertical ) +      y += iconsize; +    else +      x += iconsize; +  } +} + + +/* +=================== +CG_DrawItemSelectText +=================== +*/ +void CG_DrawItemSelectText( rectDef_t *rect, float scale, int textStyle ) +{ +  int x, w; +  char  *name; +  float *color; + +  color = CG_FadeColor( cg.weaponSelectTime, WEAPON_SELECT_TIME ); +  if( !color ) +    return; + +  trap_R_SetColor( color ); + +  // draw the selected name +  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 ); +        x = rect->x + rect->w / 2; +        CG_Text_Paint( x - w / 2, rect->y + rect->h, scale, color, name, 0, 0, textStyle ); +      } +    } +  } +  else if( cg.weaponSelect > 32 ) +  { +    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 ); +        x = rect->x + rect->w / 2; +        CG_Text_Paint( x - w / 2, rect->y + rect->h, scale, color, name, 0, 0, textStyle ); +      } +    } +  } + +  trap_R_SetColor( NULL ); +} + + +/* +=============== +CG_NextWeapon_f +=============== +*/ +void CG_NextWeapon_f( void ) +{ +  int   i; +  int   original; + +  if( !cg.snap ) +    return; + +  if( cg.snap->ps.pm_flags & PMF_FOLLOW ) +  { +    trap_SendClientCommand( "followprev\n" ); +    return; +  } + +  cg.weaponSelectTime = cg.time; +  original = cg.weaponSelect; + +  for( i = 0; i < 64; i++ ) +  { +    cg.weaponSelect++; +    if( cg.weaponSelect == 64 ) +      cg.weaponSelect = 0; + +    if( cg.weaponSelect <= 32 ) +    { +      if( CG_WeaponSelectable( cg.weaponSelect ) ) +        break; +    } +    else if( cg.weaponSelect > 32 ) +    { +      if( CG_UpgradeSelectable( cg.weaponSelect - 32 ) ) +        break; +    } +  } + +  if( i == 64 ) +    cg.weaponSelect = original; +} + +/* +=============== +CG_PrevWeapon_f +=============== +*/ +void CG_PrevWeapon_f( void ) +{ +  int   i; +  int   original; + +  if( !cg.snap ) +    return; + +  if( cg.snap->ps.pm_flags & PMF_FOLLOW ) +  { +    trap_SendClientCommand( "follownext\n" ); +    return; +  } + +  cg.weaponSelectTime = cg.time; +  original = cg.weaponSelect; + +  for( i = 0; i < 64; i++ ) +  { +    cg.weaponSelect--; +    if( cg.weaponSelect == -1 ) +      cg.weaponSelect = 63; + +    if( cg.weaponSelect <= 32 ) +    { +      if( CG_WeaponSelectable( cg.weaponSelect ) ) +        break; +    } +    else if( cg.weaponSelect > 32 ) +    { +      if( CG_UpgradeSelectable( cg.weaponSelect - 32 ) ) +        break; +    } +  } + +  if( i == 64 ) +    cg.weaponSelect = original; +} + +/* +=============== +CG_Weapon_f +=============== +*/ +void CG_Weapon_f( void ) +{ +  int   num; + +  if( !cg.snap ) +    return; + +  if( cg.snap->ps.pm_flags & PMF_FOLLOW ) +    return; + +  num = atoi( CG_Argv( 1 ) ); + +  if( num < 1 || num > 31 ) +    return; + +  cg.weaponSelectTime = cg.time; + +  if( !BG_InventoryContainsWeapon( num, cg.snap->ps.stats ) ) +    return;   // don't have the weapon + +  cg.weaponSelect = num; +} + + +/* +=================================================================================================== + +WEAPON EVENTS + +=================================================================================================== +*/ + +/* +================ +CG_FireWeapon + +Caused by an EV_FIRE_WEAPON event +================ +*/ +void CG_FireWeapon( centity_t *cent, weaponMode_t weaponMode ) +{ +  entityState_t     *es; +  int               c; +  weaponInfo_t      *wi; +  weapon_t          weaponNum; + +  es = ¢->currentState; + +  weaponNum = es->weapon; + +  if( weaponNum == WP_NONE ) +    return; + +  if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES ) +    weaponMode = WPM_PRIMARY; + +  if( weaponNum >= WP_NUM_WEAPONS ) +  { +    CG_Error( "CG_FireWeapon: ent->weapon >= WP_NUM_WEAPONS" ); +    return; +  } + +  wi = &cg_weapons[ weaponNum ]; + +  // mark the entity as muzzle flashing, so when it is added it will +  // append the flash to the weapon model +  cent->muzzleFlashTime = cg.time; + +  if( wi->wim[ weaponMode ].muzzleParticleSystem ) +  { +    if( !CG_IsParticleSystemValid( ¢->muzzlePS ) || +        !CG_IsParticleSystemInfinite( cent->muzzlePS ) ) +      cent->muzzlePsTrigger = qtrue; +  } + +  // play a sound +  for( c = 0; c < 4; c++ ) +  { +    if( !wi->wim[ weaponMode ].flashSound[ c ] ) +      break; +  } + +  if( c > 0 ) +  { +    c = rand( ) % c; +    if( wi->wim[ weaponMode ].flashSound[ c ] ) +      trap_S_StartSound( NULL, es->number, CHAN_WEAPON, wi->wim[ weaponMode ].flashSound[ c ] ); +  } +} + + +/* +================= +CG_MissileHitWall + +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 ) +{ +  qhandle_t           mark = 0; +  qhandle_t           ps = 0; +  int                 c; +  float               radius = 1.0f; +  weaponInfo_t        *weapon = &cg_weapons[ weaponNum ]; + +  if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES ) +    weaponMode = WPM_PRIMARY; + +  mark = weapon->wim[ weaponMode ].impactMark; +  radius = weapon->wim[ weaponMode ].impactMarkSize; +  ps = weapon->wim[ weaponMode ].impactParticleSystem; + +  if( soundType == IMPACTSOUND_FLESH ) +  { +    //flesh sound +    for( c = 0; c < 4; c++ ) +    { +      if( !weapon->wim[ weaponMode ].impactFleshSound[ c ] ) +        break; +    } + +    if( c > 0 ) +    { +      c = rand( ) % c; +      if( weapon->wim[ weaponMode ].impactFleshSound[ c ] ) +        trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, weapon->wim[ weaponMode ].impactFleshSound[ c ] ); +    } +  } +  else +  { +    //normal sound +    for( c = 0; c < 4; c++ ) +    { +      if( !weapon->wim[ weaponMode ].impactSound[ c ] ) +        break; +    } + +    if( c > 0 ) +    { +      c = rand( ) % c; +      if( weapon->wim[ weaponMode ].impactSound[ c ] ) +        trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, weapon->wim[ weaponMode ].impactSound[ c ] ); +    } +  } + +  //create impact particle system +  if( ps ) +  { +    particleSystem_t *partSystem = CG_SpawnNewParticleSystem( ps ); + +    if( CG_IsParticleSystemValid( &partSystem ) ) +    { +      CG_SetAttachmentPoint( &partSystem->attachment, origin ); +      CG_SetParticleSystemNormal( partSystem, dir ); +      CG_AttachToPoint( &partSystem->attachment ); +    } +  } + +  // +  // impact mark +  // +  if( radius > 0.0f ) +    CG_ImpactMark( mark, origin, dir, random( ) * 360, 1, 1, 1, 1, qfalse, radius, qfalse ); +} + + +/* +================= +CG_MissileHitPlayer +================= +*/ +void CG_MissileHitPlayer( weapon_t weaponNum, weaponMode_t weaponMode, +    vec3_t origin, vec3_t dir, int entityNum ) +{ +  vec3_t        normal; +  weaponInfo_t  *weapon = &cg_weapons[ weaponNum ]; + +  VectorCopy( dir, normal ); +  VectorInverse( normal ); + +  CG_Bleed( origin, normal, entityNum ); + +  if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES ) +    weaponMode = WPM_PRIMARY; + +  if( weapon->wim[ weaponMode ].alwaysImpact ) +    CG_MissileHitWall( weaponNum, weaponMode, 0, origin, dir, IMPACTSOUND_FLESH ); +} + + +/* +============================================================================ + +BULLETS + +============================================================================ +*/ + + +/* +=============== +CG_Tracer +=============== +*/ +void CG_Tracer( vec3_t source, vec3_t dest ) +{ +  vec3_t      forward, right; +  polyVert_t  verts[ 4 ]; +  vec3_t      line; +  float       len, begin, end; +  vec3_t      start, finish; +  vec3_t      midpoint; + +  // tracer +  VectorSubtract( dest, source, forward ); +  len = VectorNormalize( forward ); + +  // start at least a little ways from the muzzle +  if( len < 100 ) +    return; + +  begin = 50 + random( ) * ( len - 60 ); +  end = begin + cg_tracerLength.value; +  if( end > len ) +    end = len; + +  VectorMA( source, begin, forward, start ); +  VectorMA( source, end, forward, finish ); + +  line[ 0 ] = DotProduct( forward, cg.refdef.viewaxis[ 1 ] ); +  line[ 1 ] = DotProduct( forward, cg.refdef.viewaxis[ 2 ] ); + +  VectorScale( cg.refdef.viewaxis[ 1 ], line[ 1 ], right ); +  VectorMA( right, -line[ 0 ], cg.refdef.viewaxis[ 2 ], right ); +  VectorNormalize( right ); + +  VectorMA( finish, cg_tracerWidth.value, right, verts[ 0 ].xyz ); +  verts[ 0 ].st[ 0 ] = 0; +  verts[ 0 ].st[ 1 ] = 1; +  verts[ 0 ].modulate[ 0 ] = 255; +  verts[ 0 ].modulate[ 1 ] = 255; +  verts[ 0 ].modulate[ 2 ] = 255; +  verts[ 0 ].modulate[ 3 ] = 255; + +  VectorMA( finish, -cg_tracerWidth.value, right, verts[ 1 ].xyz ); +  verts[ 1 ].st[ 0 ] = 1; +  verts[ 1 ].st[ 1 ] = 0; +  verts[ 1 ].modulate[ 0 ] = 255; +  verts[ 1 ].modulate[ 1 ] = 255; +  verts[ 1 ].modulate[ 2 ] = 255; +  verts[ 1 ].modulate[ 3 ] = 255; + +  VectorMA( start, -cg_tracerWidth.value, right, verts[ 2 ].xyz ); +  verts[ 2 ].st[ 0 ] = 1; +  verts[ 2 ].st[ 1 ] = 1; +  verts[ 2 ].modulate[ 0 ] = 255; +  verts[ 2 ].modulate[ 1 ] = 255; +  verts[ 2 ].modulate[ 2 ] = 255; +  verts[ 2 ].modulate[ 3 ] = 255; + +  VectorMA( start, cg_tracerWidth.value, right, verts[ 3 ].xyz ); +  verts[ 3 ].st[ 0 ] = 0; +  verts[ 3 ].st[ 1 ] = 0; +  verts[ 3 ].modulate[ 0 ] = 255; +  verts[ 3 ].modulate[ 1 ] = 255; +  verts[ 3 ].modulate[ 2 ] = 255; +  verts[ 3 ].modulate[ 3 ] = 255; + +  trap_R_AddPolyToScene( cgs.media.tracerShader, 4, verts ); + +  midpoint[ 0 ] = ( start[ 0 ] + finish[ 0 ] ) * 0.5; +  midpoint[ 1 ] = ( start[ 1 ] + finish[ 1 ] ) * 0.5; +  midpoint[ 2 ] = ( start[ 2 ] + finish[ 2 ] ) * 0.5; + +  // add the tracer sound +  trap_S_StartSound( midpoint, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.tracerSound ); +} + + +/* +====================== +CG_CalcMuzzlePoint +====================== +*/ +static qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle ) +{ +  vec3_t    forward; +  centity_t *cent; +  int       anim; + +  if( entityNum == cg.snap->ps.clientNum ) +  { +    VectorCopy( cg.snap->ps.origin, muzzle ); +    muzzle[ 2 ] += cg.snap->ps.viewheight; +    AngleVectors( cg.snap->ps.viewangles, forward, NULL, NULL ); +    VectorMA( muzzle, 14, forward, muzzle ); +    return qtrue; +  } + +  cent = &cg_entities[entityNum]; + +  if( !cent->currentValid ) +    return qfalse; + +  VectorCopy( cent->currentState.pos.trBase, muzzle ); + +  AngleVectors( cent->currentState.apos.trBase, forward, NULL, NULL ); +  anim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT; + +  if( anim == LEGS_WALKCR || anim == LEGS_IDLECR ) +    muzzle[ 2 ] += CROUCH_VIEWHEIGHT; +  else +    muzzle[ 2 ] += DEFAULT_VIEWHEIGHT; + +  VectorMA( muzzle, 14, forward, muzzle ); + +  return qtrue; + +} + + +/* +====================== +CG_Bullet + +Renders bullet effects. +====================== +*/ +void CG_Bullet( vec3_t end, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum ) +{ +  vec3_t  start; + +  // if the shooter is currently valid, calc a source point and possibly +  // do trail effects +  if( sourceEntityNum >= 0 && cg_tracerChance.value > 0 ) +  { +    if( CG_CalcMuzzlePoint( sourceEntityNum, start ) ) +    { +      // draw a tracer +      if( random( ) < cg_tracerChance.value ) +        CG_Tracer( start, end ); +    } +  } + +  // impact splash and mark +  if( flesh ) +    CG_Bleed( end, normal, fleshEntityNum ); +  else +    CG_MissileHitWall( WP_MACHINEGUN, WPM_PRIMARY, 0, end, normal, IMPACTSOUND_DEFAULT ); +} + +/* +============================================================================ + +SHOTGUN TRACING + +============================================================================ +*/ + +/* +================ +CG_ShotgunPattern + +Perform the same traces the server did to locate the +hit splashes +================ +*/ +static void CG_ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, int otherEntNum ) +{ +  int       i; +  float     r, u; +  vec3_t    end; +  vec3_t    forward, right, up; +  trace_t   tr; + +  // derive the right and up vectors from the forward vector, because +  // the client won't have any other information +  VectorNormalize2( origin2, forward ); +  PerpendicularVector( right, forward ); +  CrossProduct( forward, right, up ); + +  // generate the "random" spread pattern +  for( i = 0; i < SHOTGUN_PELLETS; i++ ) +  { +    r = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16; +    u = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16; +    VectorMA( origin, 8192 * 16, forward, end ); +    VectorMA( end, r, right, end ); +    VectorMA( end, u, up, end ); + +    CG_Trace( &tr, origin, NULL, NULL, end, otherEntNum, MASK_SHOT ); + +    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 ); +      else if( tr.surfaceFlags & SURF_METALSTEPS ) +        CG_MissileHitWall( WP_SHOTGUN, WPM_PRIMARY, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_METAL ); +      else +        CG_MissileHitWall( WP_SHOTGUN, WPM_PRIMARY, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_DEFAULT ); +    } +  } +} + +/* +============== +CG_ShotgunFire +============== +*/ +void CG_ShotgunFire( entityState_t *es ) +{ +  vec3_t  v; + +  VectorSubtract( es->origin2, es->pos.trBase, v ); +  VectorNormalize( v ); +  VectorScale( v, 32, v ); +  VectorAdd( es->pos.trBase, v, v ); + +  CG_ShotgunPattern( es->pos.trBase, es->origin2, es->eventParm, es->otherEntityNum ); +} + diff --git a/src/client/keycodes.h b/src/client/keycodes.h new file mode 100644 index 0000000..ae6f189 --- /dev/null +++ b/src/client/keycodes.h @@ -0,0 +1,278 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ +// +#ifndef __KEYCODES_H__ +#define __KEYCODES_H__ + +// +// these are the key numbers that should be passed to KeyEvent +// + +// normal keys should be passed as lowercased ascii + +typedef enum { +	K_NONE = -1, +	K_TAB = 9, +	K_ENTER = 13, +	K_ESCAPE = 27, +	K_SPACE = 32, + +	K_BACKSPACE = 127, + +	K_COMMAND = 128, +	K_CAPSLOCK, +	K_POWER, +	K_PAUSE, + +	K_UPARROW, +	K_DOWNARROW, +	K_LEFTARROW, +	K_RIGHTARROW, + +	K_ALT, +	K_CTRL, +	K_SHIFT, +	K_INS, +	K_DEL, +	K_PGDN, +	K_PGUP, +	K_HOME, +	K_END, + +	K_F1, +	K_F2, +	K_F3, +	K_F4, +	K_F5, +	K_F6, +	K_F7, +	K_F8, +	K_F9, +	K_F10, +	K_F11, +	K_F12, +	K_F13, +	K_F14, +	K_F15, + +	K_KP_HOME, +	K_KP_UPARROW, +	K_KP_PGUP, +	K_KP_LEFTARROW, +	K_KP_5, +	K_KP_RIGHTARROW, +	K_KP_END, +	K_KP_DOWNARROW, +	K_KP_PGDN, +	K_KP_ENTER, +	K_KP_INS, +	K_KP_DEL, +	K_KP_SLASH, +	K_KP_MINUS, +	K_KP_PLUS, +	K_KP_NUMLOCK, +	K_KP_STAR, +	K_KP_EQUALS, + +	K_MOUSE1, +	K_MOUSE2, +	K_MOUSE3, +	K_MOUSE4, +	K_MOUSE5, + +	K_MWHEELDOWN, +	K_MWHEELUP, + +	K_JOY1, +	K_JOY2, +	K_JOY3, +	K_JOY4, +	K_JOY5, +	K_JOY6, +	K_JOY7, +	K_JOY8, +	K_JOY9, +	K_JOY10, +	K_JOY11, +	K_JOY12, +	K_JOY13, +	K_JOY14, +	K_JOY15, +	K_JOY16, +	K_JOY17, +	K_JOY18, +	K_JOY19, +	K_JOY20, +	K_JOY21, +	K_JOY22, +	K_JOY23, +	K_JOY24, +	K_JOY25, +	K_JOY26, +	K_JOY27, +	K_JOY28, +	K_JOY29, +	K_JOY30, +	K_JOY31, +	K_JOY32, + +	K_AUX1, +	K_AUX2, +	K_AUX3, +	K_AUX4, +	K_AUX5, +	K_AUX6, +	K_AUX7, +	K_AUX8, +	K_AUX9, +	K_AUX10, +	K_AUX11, +	K_AUX12, +	K_AUX13, +	K_AUX14, +	K_AUX15, +	K_AUX16, + +	K_WORLD_0, +	K_WORLD_1, +	K_WORLD_2, +	K_WORLD_3, +	K_WORLD_4, +	K_WORLD_5, +	K_WORLD_6, +	K_WORLD_7, +	K_WORLD_8, +	K_WORLD_9, +	K_WORLD_10, +	K_WORLD_11, +	K_WORLD_12, +	K_WORLD_13, +	K_WORLD_14, +	K_WORLD_15, +	K_WORLD_16, +	K_WORLD_17, +	K_WORLD_18, +	K_WORLD_19, +	K_WORLD_20, +	K_WORLD_21, +	K_WORLD_22, +	K_WORLD_23, +	K_WORLD_24, +	K_WORLD_25, +	K_WORLD_26, +	K_WORLD_27, +	K_WORLD_28, +	K_WORLD_29, +	K_WORLD_30, +	K_WORLD_31, +	K_WORLD_32, +	K_WORLD_33, +	K_WORLD_34, +	K_WORLD_35, +	K_WORLD_36, +	K_WORLD_37, +	K_WORLD_38, +	K_WORLD_39, +	K_WORLD_40, +	K_WORLD_41, +	K_WORLD_42, +	K_WORLD_43, +	K_WORLD_44, +	K_WORLD_45, +	K_WORLD_46, +	K_WORLD_47, +	K_WORLD_48, +	K_WORLD_49, +	K_WORLD_50, +	K_WORLD_51, +	K_WORLD_52, +	K_WORLD_53, +	K_WORLD_54, +	K_WORLD_55, +	K_WORLD_56, +	K_WORLD_57, +	K_WORLD_58, +	K_WORLD_59, +	K_WORLD_60, +	K_WORLD_61, +	K_WORLD_62, +	K_WORLD_63, +	K_WORLD_64, +	K_WORLD_65, +	K_WORLD_66, +	K_WORLD_67, +	K_WORLD_68, +	K_WORLD_69, +	K_WORLD_70, +	K_WORLD_71, +	K_WORLD_72, +	K_WORLD_73, +	K_WORLD_74, +	K_WORLD_75, +	K_WORLD_76, +	K_WORLD_77, +	K_WORLD_78, +	K_WORLD_79, +	K_WORLD_80, +	K_WORLD_81, +	K_WORLD_82, +	K_WORLD_83, +	K_WORLD_84, +	K_WORLD_85, +	K_WORLD_86, +	K_WORLD_87, +	K_WORLD_88, +	K_WORLD_89, +	K_WORLD_90, +	K_WORLD_91, +	K_WORLD_92, +	K_WORLD_93, +	K_WORLD_94, +	K_WORLD_95, + +	K_SUPER, +	K_COMPOSE, +	K_MODE, +	K_HELP, +	K_PRINT, +	K_SYSREQ, +	K_SCROLLOCK, +	K_BREAK, +	K_MENU, +	K_EURO, +	K_UNDO, + +	MAX_KEYS +} keyNum_t; + +// MAX_KEYS replaces K_LAST_KEY, however some mods may have used K_LAST_KEY +// in detecting binds, so we leave it defined to the old hardcoded value +// of maxiumum keys to prevent mods from crashing older versions of the engine +#define K_LAST_KEY              256 + +// The menu code needs to get both key and char events, but +// to avoid duplicating the paths, the char events are just +// distinguished by or'ing in K_CHAR_FLAG (ugly) +#define	K_CHAR_FLAG		1024 + +#endif diff --git a/src/renderer/tr_types.h b/src/renderer/tr_types.h new file mode 100644 index 0000000..5aa08ae --- /dev/null +++ b/src/renderer/tr_types.h @@ -0,0 +1,239 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ +// +#ifndef __TR_TYPES_H +#define __TR_TYPES_H + + +#define	MAX_DLIGHTS		32			// can't be increased, because bit flags are used on surfaces +#define	MAX_ENTITIES	1023		// can't be increased without changing drawsurf bit packing + +// renderfx flags +#define	RF_MINLIGHT			1		// allways have some light (viewmodel, some items) +#define	RF_THIRD_PERSON		2		// don't draw through eyes, only mirrors (player bodies, chat sprites) +#define	RF_FIRST_PERSON		4		// only draw through eyes (view weapon, damage blood blob) +#define	RF_DEPTHHACK		8		// for view weapon Z crunching +#define	RF_NOSHADOW			64		// don't add stencil shadows + +#define RF_LIGHTING_ORIGIN	128		// use refEntity->lightingOrigin instead of refEntity->origin +									// for lighting.  This allows entities to sink into the floor +									// with their origin going solid, and allows all parts of a +									// player to get the same lighting +#define	RF_SHADOW_PLANE		256		// use refEntity->shadowPlane +#define	RF_WRAP_FRAMES		512		// mod the model frames by the maxframes to allow continuous +									// animation without needing to know the frame count + +// refdef flags +#define RDF_NOWORLDMODEL	1		// used for player configuration screen +#define RDF_HYPERSPACE		4		// teleportation effect + +typedef struct { +	vec3_t		xyz; +	float		st[2]; +	byte		modulate[4]; +} polyVert_t; + +typedef struct poly_s { +	qhandle_t			hShader; +	int					numVerts; +	polyVert_t			*verts; +} poly_t; + +typedef enum { +	RT_MODEL, +	RT_POLY, +	RT_SPRITE, +	RT_BEAM, +	RT_RAIL_CORE, +	RT_RAIL_RINGS, +	RT_LIGHTNING, +	RT_PORTALSURFACE,		// doesn't draw anything, just info for portals + +	RT_MAX_REF_ENTITY_TYPE +} refEntityType_t; + +typedef struct { +	refEntityType_t	reType; +	int			renderfx; + +	qhandle_t	hModel;				// opaque type outside refresh + +	// most recent data +	vec3_t		lightingOrigin;		// so multi-part models can be lit identically (RF_LIGHTING_ORIGIN) +	float		shadowPlane;		// projection shadows go here, stencils go slightly lower + +	vec3_t		axis[3];			// rotation vectors +	qboolean	nonNormalizedAxes;	// axis are not normalized, i.e. they have scale +	float		origin[3];			// also used as MODEL_BEAM's "from" +	int			frame;				// also used as MODEL_BEAM's diameter + +	// previous data for frame interpolation +	float		oldorigin[3];		// also used as MODEL_BEAM's "to" +	int			oldframe; +	float		backlerp;			// 0.0 = current, 1.0 = old + +	// texturing +	int			skinNum;			// inline skin index +	qhandle_t	customSkin;			// NULL for default skin +	qhandle_t	customShader;		// use one image for the entire thing + +	// misc +	byte		shaderRGBA[4];		// colors used by rgbgen entity shaders +	float		shaderTexCoord[2];	// texture coordinates used by tcMod entity modifiers +	float		shaderTime;			// subtracted from refdef time to control effect start times + +	// extra sprite information +	float		radius; +	float		rotation; +} refEntity_t; + + +#define	MAX_RENDER_STRINGS			8 +#define	MAX_RENDER_STRING_LENGTH	32 + +typedef struct { +	int			x, y, width, height; +	float		fov_x, fov_y; +	vec3_t		vieworg; +	vec3_t		viewaxis[3];		// transformation matrix + +	// time in milliseconds for shader effects and other time dependent rendering issues +	int			time; + +	int			rdflags;			// RDF_NOWORLDMODEL, etc + +	// 1 bits will prevent the associated area from rendering at all +	byte		areamask[MAX_MAP_AREA_BYTES]; + +	// text messages for deform text shaders +	char		text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH]; +} refdef_t; + + +typedef enum { +	STEREO_CENTER, +	STEREO_LEFT, +	STEREO_RIGHT +} stereoFrame_t; + + +/* +** glconfig_t +** +** Contains variables specific to the OpenGL configuration +** being run right now.  These are constant once the OpenGL +** subsystem is initialized. +*/ +typedef enum { +	TC_NONE, +	TC_S3TC +} textureCompression_t; + +typedef enum { +	GLDRV_ICD,					// driver is integrated with window system +								// WARNING: there are tests that check for +								// > GLDRV_ICD for minidriverness, so this +								// should always be the lowest value in this +								// enum set +	GLDRV_STANDALONE,			// driver is a non-3Dfx standalone driver +	GLDRV_VOODOO				// driver is a 3Dfx standalone driver +} glDriverType_t; + +typedef enum { +	GLHW_GENERIC,			// where everthing works the way it should +	GLHW_3DFX_2D3D,			// Voodoo Banshee or Voodoo3, relevant since if this is +							// the hardware type then there can NOT exist a secondary +							// display adapter +	GLHW_RIVA128,			// where you can't interpolate alpha +	GLHW_RAGEPRO,			// where you can't modulate alpha on alpha textures +	GLHW_PERMEDIA2			// where you don't have src*dst +} glHardwareType_t; + +typedef struct { +	char					renderer_string[MAX_STRING_CHARS]; +	char					vendor_string[MAX_STRING_CHARS]; +	char					version_string[MAX_STRING_CHARS]; +	char					extensions_string[BIG_INFO_STRING]; + +	int						maxTextureSize;			// queried from GL +	int						maxActiveTextures;		// multitexture ability + +	int						colorBits, depthBits, stencilBits; + +	glDriverType_t			driverType; +	glHardwareType_t		hardwareType; + +	qboolean				deviceSupportsGamma; +	textureCompression_t	textureCompression; +	qboolean				textureEnvAddAvailable; + +	int						vidWidth, vidHeight; +	// aspect is the screen's physical width / height, which may be different +	// than scrWidth / scrHeight if the pixels are non-square +	// normal screens should be 4/3, but wide aspect monitors may be 16/9 +	float					windowAspect; + +	int						displayFrequency; + +	// synonymous with "does rendering consume the entire screen?", therefore +	// a Voodoo or Voodoo2 will have this set to TRUE, as will a Win32 ICD that +	// used CDS. +	qboolean				isFullscreen; +	qboolean				stereoEnabled; +	qboolean				smpActive;		// dual processor + +	qboolean				textureFilterAnisotropic; +	int							maxAnisotropy; +                 +} glconfig_t; + +// FIXME: VM should be OS agnostic .. in theory + +/* +#ifdef Q3_VM + +#define _3DFX_DRIVER_NAME	"Voodoo" +#define OPENGL_DRIVER_NAME	"Default" + +#elif defined(_WIN32) +*/ + +#if defined(Q3_VM) || defined(_WIN32) + +#define _3DFX_DRIVER_NAME	"3dfxvgl" +#define OPENGL_DRIVER_NAME	"opengl32" + +#elif defined(MACOS_X) + +#define _3DFX_DRIVER_NAME	"libMesaVoodooGL.dylib" +#define OPENGL_DRIVER_NAME	"/System/Library/Frameworks/OpenGL.framework/Libraries/libGL.dylib" + +#else + +#define _3DFX_DRIVER_NAME	"libMesaVoodooGL.so" +// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=524 +#define OPENGL_DRIVER_NAME	"libGL.so.1" + +#endif	// !defined _WIN32 + +#endif	// __TR_TYPES_H diff --git a/src/ui/menudef.h b/src/ui/menudef.h new file mode 100644 index 0000000..d4ed0b2 --- /dev/null +++ b/src/ui/menudef.h @@ -0,0 +1,362 @@ + +#define ITEM_TYPE_TEXT 0                  // simple text +#define ITEM_TYPE_BUTTON 1                // button, basically text with a border  +#define ITEM_TYPE_RADIOBUTTON 2           // toggle button, may be grouped  +#define ITEM_TYPE_CHECKBOX 3              // check box +#define ITEM_TYPE_EDITFIELD 4             // editable text, associated with a cvar +#define ITEM_TYPE_COMBO 5                 // drop down list +#define ITEM_TYPE_LISTBOX 6               // scrollable list   +#define ITEM_TYPE_MODEL 7                 // model +#define ITEM_TYPE_OWNERDRAW 8             // owner draw, name specs what it is +#define ITEM_TYPE_NUMERICFIELD 9          // editable text, associated with a cvar +#define ITEM_TYPE_SLIDER 10               // mouse speed, volume, etc. +#define ITEM_TYPE_YESNO 11                // yes no cvar setting +#define ITEM_TYPE_MULTI 12                // multiple list setting, enumerated +#define ITEM_TYPE_BIND 13                 // multiple list setting, enumerated +     +#define ITEM_ALIGN_LEFT 0                 // left alignment +#define ITEM_ALIGN_CENTER 1               // center alignment +#define ITEM_ALIGN_RIGHT 2                // right alignment + +#define ITEM_TEXTSTYLE_NORMAL 0           // normal text +#define ITEM_TEXTSTYLE_BLINK 1            // fast blinking +#define ITEM_TEXTSTYLE_PULSE 2            // slow pulsing +#define ITEM_TEXTSTYLE_SHADOWED 3         // drop shadow ( need a color for this ) +#define ITEM_TEXTSTYLE_OUTLINED 4         // drop shadow ( need a color for this ) +#define ITEM_TEXTSTYLE_OUTLINESHADOWED 5  // drop shadow ( need a color for this ) +#define ITEM_TEXTSTYLE_SHADOWEDMORE 6         // drop shadow ( need a color for this ) +#define ITEM_TEXTSTYLE_NEON     7         // drop shadow ( need a color for this ) +                           +#define WINDOW_BORDER_NONE 0              // no border +#define WINDOW_BORDER_FULL 1              // full border based on border color ( single pixel ) +#define WINDOW_BORDER_HORZ 2              // horizontal borders only +#define WINDOW_BORDER_VERT 3              // vertical borders only  +#define WINDOW_BORDER_KCGRADIENT 4        // horizontal border using the gradient bars +   +#define WINDOW_STYLE_EMPTY 0              // no background +#define WINDOW_STYLE_FILLED 1             // filled with background color +#define WINDOW_STYLE_GRADIENT 2           // gradient bar based on background color  +#define WINDOW_STYLE_SHADER   3           // gradient bar based on background color  +#define WINDOW_STYLE_TEAMCOLOR 4          // team color +#define WINDOW_STYLE_CINEMATIC 5          // cinematic + +#define MENU_TRUE 1                       // uh.. true +#define MENU_FALSE 0                      // and false + +#define HUD_VERTICAL        0x00 +#define HUD_HORIZONTAL        0x01 + +// list box element types +#define LISTBOX_TEXT  0x00 +#define LISTBOX_IMAGE 0x01 + +// list feeders +#define FEEDER_HEADS                    0x00      // model heads +#define FEEDER_MAPS                     0x01      // text maps based on game type +#define FEEDER_SERVERS                  0x02      // servers +#define FEEDER_CLANS                    0x03      // clan names +#define FEEDER_ALLMAPS                  0x04      // all maps available, in graphic format +#define FEEDER_ALIENTEAM_LIST           0x05      // red team members +#define FEEDER_HUMANTEAM_LIST           0x06      // blue team members +#define FEEDER_PLAYER_LIST              0x07      // players +#define FEEDER_TEAM_LIST                0x08      // team members for team voting +#define FEEDER_MODS                     0x09      // team members for team voting +#define FEEDER_DEMOS                    0x0a      // team members for team voting +#define FEEDER_SCOREBOARD               0x0b      // team members for team voting +#define FEEDER_Q3HEADS                  0x0c      // model heads +#define FEEDER_SERVERSTATUS             0x0d      // server status +#define FEEDER_FINDPLAYER               0x0e      // find player +#define FEEDER_CINEMATICS               0x0f      // cinematics + +//TA: tremulous menus +#define FEEDER_TREMTEAMS                0x10      //teams +#define FEEDER_TREMALIENCLASSES         0x11      //alien classes +#define FEEDER_TREMHUMANITEMS           0x12      //human items +#define FEEDER_TREMHUMANARMOURYBUY      0x13      //human buy +#define FEEDER_TREMHUMANARMOURYSELL     0x14      //human sell +#define FEEDER_TREMALIENUPGRADE         0x15      //alien upgrade +#define FEEDER_TREMALIENBUILD           0x16      //alien buildables +#define FEEDER_TREMHUMANBUILD           0x17      //human buildables +//TA: tremulous menus +#define FEEDER_IGNORE_LIST              0x18      //ignored players + +// display flags +#define CG_SHOW_BLUE_TEAM_HAS_REDFLAG     0x00000001 +#define CG_SHOW_RED_TEAM_HAS_BLUEFLAG     0x00000002 +#define CG_SHOW_ANYTEAMGAME               0x00000004 +#define CG_SHOW_HARVESTER                 0x00000008 +#define CG_SHOW_ONEFLAG                   0x00000010 +#define CG_SHOW_CTF                       0x00000020 +#define CG_SHOW_OBELISK                   0x00000040 +#define CG_SHOW_HEALTHCRITICAL            0x00000080 +#define CG_SHOW_SINGLEPLAYER              0x00000100 +#define CG_SHOW_TOURNAMENT                0x00000200 +#define CG_SHOW_DURINGINCOMINGVOICE       0x00000400 +#define CG_SHOW_IF_PLAYER_HAS_FLAG        0x00000800 +#define CG_SHOW_LANPLAYONLY               0x00001000 +#define CG_SHOW_MINED                     0x00002000 +#define CG_SHOW_HEALTHOK                  0x00004000 +#define CG_SHOW_TEAMINFO                  0x00008000 +#define CG_SHOW_NOTEAMINFO                0x00010000 +#define CG_SHOW_OTHERTEAMHASFLAG          0x00020000 +#define CG_SHOW_YOURTEAMHASENEMYFLAG      0x00040000 +#define CG_SHOW_ANYNONTEAMGAME            0x00080000 +#define CG_SHOW_2DONLY                    0x10000000 + + +#define UI_SHOW_LEADER                    0x00000001 +#define UI_SHOW_NOTLEADER                 0x00000002 +#define UI_SHOW_FAVORITESERVERS           0x00000004 +#define UI_SHOW_ANYNONTEAMGAME            0x00000008 +#define UI_SHOW_ANYTEAMGAME               0x00000010 +#define UI_SHOW_NEWHIGHSCORE              0x00000020 +#define UI_SHOW_DEMOAVAILABLE             0x00000040 +#define UI_SHOW_NEWBESTTIME               0x00000080 +#define UI_SHOW_FFA                       0x00000100 +#define UI_SHOW_NOTFFA                    0x00000200 +#define UI_SHOW_NETANYNONTEAMGAME         0x00000400 +#define UI_SHOW_NETANYTEAMGAME            0x00000800 +#define UI_SHOW_NOTFAVORITESERVERS        0x00001000 + +#define UI_SHOW_VOTEACTIVE                0x00002000 +#define UI_SHOW_CANVOTE                   0x00004000 +#define UI_SHOW_TEAMVOTEACTIVE            0x00008000 +#define UI_SHOW_CANTEAMVOTE               0x00010000 + +#define UI_SHOW_NOTSPECTATING             0x00020000 + +// owner draw types +// ideally these should be done outside of this file but +// this makes it much easier for the macro expansion to  +// convert them for the designers ( from the .menu files ) +#define CG_OWNERDRAW_BASE           1 +#define CG_PLAYER_ARMOR_ICON        1               +#define CG_PLAYER_ARMOR_VALUE       2 +#define CG_PLAYER_HEAD              3 +#define CG_PLAYER_HEALTH            4 +#define CG_PLAYER_HEALTH_BAR        92 +#define CG_PLAYER_HEALTH_CROSS      99 +#define CG_PLAYER_AMMO_ICON         5 +#define CG_PLAYER_AMMO_VALUE        6 +#define CG_PLAYER_CLIPS_VALUE       70 +#define CG_PLAYER_BUILD_TIMER       115 +#define CG_PLAYER_CREDITS_VALUE     71 +#define CG_PLAYER_BANK_VALUE        72 +#define CG_PLAYER_CREDITS_VALUE_NOPAD     106 +#define CG_PLAYER_BANK_VALUE_NOPAD        107 +#define CG_PLAYER_STAMINA           73 +#define CG_PLAYER_STAMINA_1         93 +#define CG_PLAYER_STAMINA_2         94 +#define CG_PLAYER_STAMINA_3         95 +#define CG_PLAYER_STAMINA_4         96 +#define CG_PLAYER_STAMINA_BOLT      97 +#define CG_PLAYER_BOOST_BOLT        112 +#define CG_PLAYER_CLIPS_RING        98 +#define CG_PLAYER_BUILD_TIMER_RING  113 +#define CG_PLAYER_SELECT            74 +#define CG_PLAYER_SELECTTEXT        75 +#define CG_PLAYER_WEAPONICON        111 +#define CG_PLAYER_WALLCLIMBING      103 +#define CG_PLAYER_BOOSTED           104 +#define CG_PLAYER_POISON_BARBS      105 +#define CG_PLAYER_ALIEN_SENSE       108 +#define CG_PLAYER_HUMAN_SCANNER     109 +#define CG_PLAYER_USABLE_BUILDABLE  110 +#define CG_SELECTEDPLAYER_HEAD      7 +#define CG_SELECTEDPLAYER_NAME      8 +#define CG_SELECTEDPLAYER_LOCATION  9 +#define CG_SELECTEDPLAYER_STATUS    10 +#define CG_SELECTEDPLAYER_WEAPON    11 +#define CG_SELECTEDPLAYER_POWERUP   12 + +#define CG_FLAGCARRIER_HEAD         13 +#define CG_FLAGCARRIER_NAME         14 +#define CG_FLAGCARRIER_LOCATION     15 +#define CG_FLAGCARRIER_STATUS       16 +#define CG_FLAGCARRIER_WEAPON       17 +#define CG_FLAGCARRIER_POWERUP      18 + +#define CG_PLAYER_ITEM              19 +#define CG_PLAYER_SCORE             20 + +#define CG_BLUE_FLAGHEAD            21 +#define CG_BLUE_FLAGSTATUS          22 +#define CG_BLUE_FLAGNAME            23 +#define CG_RED_FLAGHEAD             24 +#define CG_RED_FLAGSTATUS           25 +#define CG_RED_FLAGNAME             26 + +#define CG_BLUE_SCORE               27 +#define CG_RED_SCORE                28 +#define CG_RED_NAME                 29 +#define CG_BLUE_NAME                30 +#define CG_HARVESTER_SKULLS         31  // only shows in harvester +#define CG_ONEFLAG_STATUS           32  // only shows in one flag +#define CG_PLAYER_LOCATION          33 +#define CG_TEAM_COLOR               34 +#define CG_CTF_POWERUP              35 +                                         +#define CG_AREA_POWERUP             36 +#define CG_AREA_LAGOMETER           37  // painted with old system +#define CG_PLAYER_HASFLAG           38             +#define CG_GAME_TYPE                39  // not done + +#define CG_SELECTEDPLAYER_ARMOR     40       +#define CG_SELECTEDPLAYER_HEALTH    41 +#define CG_PLAYER_STATUS 42 +#define CG_FRAGGED_MSG 43               // painted with old system +#define CG_PROXMINED_MSG 44             // painted with old system +#define CG_AREA_FPSINFO 45              // painted with old system +#define CG_GAME_STATUS 49 +#define CG_KILLER 50 +#define CG_PLAYER_ARMOR_ICON2D 51               +#define CG_PLAYER_AMMO_ICON2D 52 +#define CG_ACCURACY 53 +#define CG_ASSISTS 54 +#define CG_DEFEND 55 +#define CG_EXCELLENT 56 +#define CG_IMPRESSIVE 57 +#define CG_PERFECT 58 +#define CG_GAUNTLET 59 +#define CG_SPECTATORS 60 +#define CG_TEAMINFO 61 +#define CG_VOICE_HEAD 62 +#define CG_VOICE_NAME 63 +#define CG_PLAYER_HASFLAG2D 64             +#define CG_HARVESTER_SKULLS2D 65          // only shows in harvester +#define CG_CAPFRAGLIMIT 66    +#define CG_1STPLACE 67 +#define CG_2NDPLACE 68 +#define CG_CAPTURES 69 + +//TA: loading screen +#define CG_LOAD_LEVELSHOT         76 +#define CG_LOAD_MEDIA             77 +#define CG_LOAD_MEDIA_LABEL       78 +#define CG_LOAD_BUILDABLES        79 +#define CG_LOAD_BUILDABLES_LABEL  80 +#define CG_LOAD_CHARMODEL         81 +#define CG_LOAD_CHARMODEL_LABEL   82 +#define CG_LOAD_OVERALL           83 +#define CG_LOAD_LEVELNAME         84 +#define CG_LOAD_MOTD              85 +#define CG_LOAD_HOSTNAME          86 + +#define CG_FPS                    87 +#define CG_FPS_FIXED              100 +#define CG_TIMER                  88 +#define CG_TIMER_MINS             101 +#define CG_TIMER_SECS             102 +#define CG_SNAPSHOT               89 +#define CG_LAGOMETER              90 +#define CG_PLAYER_CROSSHAIRNAMES  114 +#define CG_STAGE_REPORT_TEXT      116 +#define CG_DEMO_PLAYBACK          117 +#define CG_DEMO_RECORDING         118 + +#define CG_CONSOLE                91 +#define CG_TUTORIAL               119 +#define CG_CLOCK                  120 + + + +#define UI_OWNERDRAW_BASE 200 +#define UI_HANDICAP 200 +#define UI_PLAYERMODEL 202 +#define UI_CLANNAME 203 +#define UI_CLANLOGO 204 +#define UI_GAMETYPE 205 +#define UI_MAPPREVIEW 206 +#define UI_SKILL 207 +#define UI_BLUETEAMNAME 208 +#define UI_REDTEAMNAME 209 +#define UI_BLUETEAM1 210 +#define UI_BLUETEAM2 211 +#define UI_BLUETEAM3 212 +#define UI_BLUETEAM4 213 +#define UI_BLUETEAM5 214 +#define UI_REDTEAM1 215 +#define UI_REDTEAM2 216 +#define UI_REDTEAM3 217 +#define UI_REDTEAM4 218 +#define UI_REDTEAM5 219 +#define UI_NETSOURCE 220 +#define UI_NETMAPPREVIEW 221 +#define UI_NETFILTER 222 +#define UI_TIER 223 +#define UI_OPPONENTMODEL 224 +#define UI_TIERMAP1 225 +#define UI_TIERMAP2 226 +#define UI_TIERMAP3 227 +#define UI_PLAYERLOGO 228 +#define UI_OPPONENTLOGO 229 +#define UI_PLAYERLOGO_METAL 230 +#define UI_OPPONENTLOGO_METAL 231 +#define UI_PLAYERLOGO_NAME 232 +#define UI_OPPONENTLOGO_NAME 233 +#define UI_TIER_MAPNAME 234 +#define UI_TIER_GAMETYPE 235 +#define UI_ALLMAPS_SELECTION 236 +#define UI_OPPONENT_NAME 237 +#define UI_VOTE_KICK 238 +#define UI_BOTNAME 239 +#define UI_BOTSKILL 240 +#define UI_REDBLUE 241 +#define UI_SELECTEDPLAYER 243 +#define UI_MAPCINEMATIC 244 +#define UI_NETGAMETYPE 245 +#define UI_NETMAPCINEMATIC 246 +#define UI_SERVERREFRESHDATE 247 +#define UI_SERVERMOTD 248 +#define UI_GLINFO  249 +#define UI_KEYBINDSTATUS      250 +#define UI_CLANCINEMATIC      251 +#define UI_MAP_TIMETOBEAT     252 +#define UI_JOINGAMETYPE       253 +#define UI_PREVIEWCINEMATIC   254 +#define UI_STARTMAPCINEMATIC  255 +#define UI_MAPS_SELECTION     256 + +//TA: +//#define UI_DIALOG             257 +#define UI_TEAMINFOPANE       258 +#define UI_ACLASSINFOPANE     259 +#define UI_AUPGRADEINFOPANE   260 +#define UI_HITEMINFOPANE      261 +#define UI_HBUYINFOPANE       262 +#define UI_HSELLINFOPANE      263 +#define UI_ABUILDINFOPANE     264 +#define UI_HBUILDINFOPANE     265 + +#define UI_PLAYERLIST_SELECTION 266 +#define UI_TEAMLIST_SELECTION 267 + +#define VOICECHAT_GETFLAG     "getflag"       // command someone to get the flag +#define VOICECHAT_OFFENSE     "offense"       // command someone to go on offense +#define VOICECHAT_DEFEND      "defend"        // command someone to go on defense +#define VOICECHAT_DEFENDFLAG    "defendflag"      // command someone to defend the flag +#define VOICECHAT_PATROL      "patrol"        // command someone to go on patrol (roam) +#define VOICECHAT_CAMP        "camp"          // command someone to camp (we don't have sounds for this one) +#define VOICECHAT_FOLLOWME      "followme"        // command someone to follow you +#define VOICECHAT_RETURNFLAG    "returnflag"      // command someone to return our flag +#define VOICECHAT_FOLLOWFLAGCARRIER "followflagcarrier"   // command someone to follow the flag carrier +#define VOICECHAT_YES       "yes"         // yes, affirmative, etc. +#define VOICECHAT_NO        "no"          // no, negative, etc. +#define VOICECHAT_ONGETFLAG     "ongetflag"       // I'm getting the flag +#define VOICECHAT_ONOFFENSE     "onoffense"       // I'm on offense +#define VOICECHAT_ONDEFENSE     "ondefense"       // I'm on defense +#define VOICECHAT_ONPATROL      "onpatrol"        // I'm on patrol (roaming) +#define VOICECHAT_ONCAMPING     "oncamp"        // I'm camping somewhere +#define VOICECHAT_ONFOLLOW      "onfollow"        // I'm following +#define VOICECHAT_ONFOLLOWCARRIER "onfollowcarrier"   // I'm following the flag carrier +#define VOICECHAT_ONRETURNFLAG    "onreturnflag"      // I'm returning our flag +#define VOICECHAT_INPOSITION    "inposition"      // I'm in position +#define VOICECHAT_IHAVEFLAG     "ihaveflag"       // I have the flag +#define VOICECHAT_BASEATTACK    "baseattack"      // the base is under attack +#define VOICECHAT_ENEMYHASFLAG    "enemyhasflag"      // the enemy has our flag (CTF) +#define VOICECHAT_STARTLEADER   "startleader"     // I'm the leader +#define VOICECHAT_STOPLEADER    "stopleader"      // I resign leadership +#define VOICECHAT_TRASH       "trash"         // lots of trash talk +#define VOICECHAT_WHOISLEADER   "whoisleader"     // who is the team leader +#define VOICECHAT_WANTONDEFENSE   "wantondefense"     // I want to be on defense +#define VOICECHAT_WANTONOFFENSE   "wantonoffense"     // I want to be on offense diff --git a/src/ui/ui_atoms.c b/src/ui/ui_atoms.c new file mode 100644 index 0000000..64efa91 --- /dev/null +++ b/src/ui/ui_atoms.c @@ -0,0 +1,519 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +/********************************************************************** +  UI_ATOMS.C + +  User interface building blocks and support functions. +**********************************************************************/ +#include "ui_local.h" + +qboolean    m_entersound;    // after a frame, so caching won't disrupt the sound + +void QDECL Com_Error( int level, const char *error, ... ) { +  va_list    argptr; +  char    text[1024]; + +  va_start (argptr, error); +  vsprintf (text, error, argptr); +  va_end (argptr); + +  trap_Error( va("%s", text) ); +} + +void QDECL Com_Printf( const char *msg, ... ) { +  va_list    argptr; +  char    text[1024]; + +  va_start (argptr, msg); +  vsprintf (text, msg, argptr); +  va_end (argptr); + +  trap_Print( va("%s", text) ); +} + +qboolean newUI = qfalse; + + +/* +================= +UI_ClampCvar +================= +*/ +float UI_ClampCvar( float min, float max, float value ) +{ +  if ( value < min ) return min; +  if ( value > max ) return max; +  return value; +} + +/* +================= +UI_StartDemoLoop +================= +*/ +void UI_StartDemoLoop( void ) { +  trap_Cmd_ExecuteText( EXEC_APPEND, "d1\n" ); +} + +char *UI_Argv( int arg ) { +  static char  buffer[MAX_STRING_CHARS]; + +  trap_Argv( arg, buffer, sizeof( buffer ) ); + +  return buffer; +} + + +char *UI_Cvar_VariableString( const char *var_name ) { +  static char  buffer[MAX_STRING_CHARS]; + +  trap_Cvar_VariableStringBuffer( var_name, buffer, sizeof( buffer ) ); + +  return buffer; +} + + + +void UI_SetBestScores(postGameInfo_t *newInfo, qboolean postGame) { +  trap_Cvar_Set("ui_scoreAccuracy",     va("%i%%", newInfo->accuracy)); +  trap_Cvar_Set("ui_scoreImpressives",  va("%i", newInfo->impressives)); +  trap_Cvar_Set("ui_scoreExcellents",   va("%i", newInfo->excellents)); +  trap_Cvar_Set("ui_scoreDefends",       va("%i", newInfo->defends)); +  trap_Cvar_Set("ui_scoreAssists",       va("%i", newInfo->assists)); +  trap_Cvar_Set("ui_scoreGauntlets",     va("%i", newInfo->gauntlets)); +  trap_Cvar_Set("ui_scoreScore",         va("%i", newInfo->score)); +  trap_Cvar_Set("ui_scorePerfect",       va("%i", newInfo->perfects)); +  trap_Cvar_Set("ui_scoreTeam",          va("%i to %i", newInfo->redScore, newInfo->blueScore)); +  trap_Cvar_Set("ui_scoreBase",          va("%i", newInfo->baseScore)); +  trap_Cvar_Set("ui_scoreTimeBonus",    va("%i", newInfo->timeBonus)); +  trap_Cvar_Set("ui_scoreSkillBonus",    va("%i", newInfo->skillBonus)); +  trap_Cvar_Set("ui_scoreShutoutBonus",  va("%i", newInfo->shutoutBonus)); +  trap_Cvar_Set("ui_scoreTime",          va("%02i:%02i", newInfo->time / 60, newInfo->time % 60)); +  trap_Cvar_Set("ui_scoreCaptures",    va("%i", newInfo->captures)); +  if (postGame) { +    trap_Cvar_Set("ui_scoreAccuracy2",     va("%i%%", newInfo->accuracy)); +    trap_Cvar_Set("ui_scoreImpressives2",  va("%i", newInfo->impressives)); +    trap_Cvar_Set("ui_scoreExcellents2",   va("%i", newInfo->excellents)); +    trap_Cvar_Set("ui_scoreDefends2",       va("%i", newInfo->defends)); +    trap_Cvar_Set("ui_scoreAssists2",       va("%i", newInfo->assists)); +    trap_Cvar_Set("ui_scoreGauntlets2",     va("%i", newInfo->gauntlets)); +    trap_Cvar_Set("ui_scoreScore2",         va("%i", newInfo->score)); +    trap_Cvar_Set("ui_scorePerfect2",       va("%i", newInfo->perfects)); +    trap_Cvar_Set("ui_scoreTeam2",          va("%i to %i", newInfo->redScore, newInfo->blueScore)); +    trap_Cvar_Set("ui_scoreBase2",          va("%i", newInfo->baseScore)); +    trap_Cvar_Set("ui_scoreTimeBonus2",    va("%i", newInfo->timeBonus)); +    trap_Cvar_Set("ui_scoreSkillBonus2",    va("%i", newInfo->skillBonus)); +    trap_Cvar_Set("ui_scoreShutoutBonus2",  va("%i", newInfo->shutoutBonus)); +    trap_Cvar_Set("ui_scoreTime2",          va("%02i:%02i", newInfo->time / 60, newInfo->time % 60)); +    trap_Cvar_Set("ui_scoreCaptures2",    va("%i", newInfo->captures)); +  } +} + +void UI_LoadBestScores(const char *map, int game) { +  char    fileName[MAX_QPATH]; +  fileHandle_t f; +  postGameInfo_t newInfo; +  memset(&newInfo, 0, sizeof(postGameInfo_t)); +  Com_sprintf(fileName, MAX_QPATH, "games/%s_%i.game", map, game); +  if (trap_FS_FOpenFile(fileName, &f, FS_READ) >= 0) { +    int size = 0; +    trap_FS_Read(&size, sizeof(int), f); +    if (size == sizeof(postGameInfo_t)) { +      trap_FS_Read(&newInfo, sizeof(postGameInfo_t), f); +    } +    trap_FS_FCloseFile(f); +  } +  UI_SetBestScores(&newInfo, qfalse); + +  Com_sprintf(fileName, MAX_QPATH, "demos/%s_%d.dm_%d", map, game, (int)trap_Cvar_VariableValue("protocol")); +  uiInfo.demoAvailable = qfalse; +  if (trap_FS_FOpenFile(fileName, &f, FS_READ) >= 0) { +    uiInfo.demoAvailable = qtrue; +    trap_FS_FCloseFile(f); +  } +} + +/* +=============== +UI_ClearScores +=============== +*/ +void UI_ClearScores( void ) { +  char  gameList[4096]; +  char *gameFile; +  int    i, len, count, size; +  fileHandle_t f; +  postGameInfo_t newInfo; + +  count = trap_FS_GetFileList( "games", "game", gameList, sizeof(gameList) ); + +  size = sizeof(postGameInfo_t); +  memset(&newInfo, 0, size); + +  if (count > 0) { +    gameFile = gameList; +    for ( i = 0; i < count; i++ ) { +      len = strlen(gameFile); +      if (trap_FS_FOpenFile(va("games/%s",gameFile), &f, FS_WRITE) >= 0) { +        trap_FS_Write(&size, sizeof(int), f); +        trap_FS_Write(&newInfo, size, f); +        trap_FS_FCloseFile(f); +      } +      gameFile += len + 1; +    } +  } + +  UI_SetBestScores(&newInfo, qfalse); + +} + + + +static void  UI_Cache_f( void ) { +  Display_CacheAll(); +} + +/* +======================= +UI_CalcPostGameStats +======================= +*/ +static void UI_CalcPostGameStats( void ) { +  char    map[MAX_QPATH]; +  char    fileName[MAX_QPATH]; +  char    info[MAX_INFO_STRING]; +  fileHandle_t f; +  int size, game, time, adjustedTime; +  postGameInfo_t oldInfo; +  postGameInfo_t newInfo; +  qboolean newHigh = qfalse; + +  trap_GetConfigString( CS_SERVERINFO, info, sizeof(info) ); +  Q_strncpyz( map, Info_ValueForKey( info, "mapname" ), sizeof(map) ); +  game = atoi(Info_ValueForKey(info, "g_gametype")); + +  // compose file name +  Com_sprintf(fileName, MAX_QPATH, "games/%s_%i.game", map, game); +  // see if we have one already +  memset(&oldInfo, 0, sizeof(postGameInfo_t)); +  if (trap_FS_FOpenFile(fileName, &f, FS_READ) >= 0) { +  // if so load it +    size = 0; +    trap_FS_Read(&size, sizeof(int), f); +    if (size == sizeof(postGameInfo_t)) { +      trap_FS_Read(&oldInfo, sizeof(postGameInfo_t), f); +    } +    trap_FS_FCloseFile(f); +  } + +  newInfo.accuracy = atoi(UI_Argv(3)); +  newInfo.impressives = atoi(UI_Argv(4)); +  newInfo.excellents = atoi(UI_Argv(5)); +  newInfo.defends = atoi(UI_Argv(6)); +  newInfo.assists = atoi(UI_Argv(7)); +  newInfo.gauntlets = atoi(UI_Argv(8)); +  newInfo.baseScore = atoi(UI_Argv(9)); +  newInfo.perfects = atoi(UI_Argv(10)); +  newInfo.redScore = atoi(UI_Argv(11)); +  newInfo.blueScore = atoi(UI_Argv(12)); +  time = atoi(UI_Argv(13)); +  newInfo.captures = atoi(UI_Argv(14)); + +  newInfo.time = (time - trap_Cvar_VariableValue("ui_matchStartTime")) / 1000; +  adjustedTime = uiInfo.mapList[ui_currentMap.integer].timeToBeat[game]; +  if (newInfo.time < adjustedTime) { +    newInfo.timeBonus = (adjustedTime - newInfo.time) * 10; +  } else { +    newInfo.timeBonus = 0; +  } + +  if (newInfo.redScore > newInfo.blueScore && newInfo.blueScore <= 0) { +    newInfo.shutoutBonus = 100; +  } else { +    newInfo.shutoutBonus = 0; +  } + +  newInfo.skillBonus = trap_Cvar_VariableValue("g_spSkill"); +  if (newInfo.skillBonus <= 0) { +    newInfo.skillBonus = 1; +  } +  newInfo.score = newInfo.baseScore + newInfo.shutoutBonus + newInfo.timeBonus; +  newInfo.score *= newInfo.skillBonus; + +  // see if the score is higher for this one +  newHigh = (newInfo.redScore > newInfo.blueScore && newInfo.score > oldInfo.score); + +  if  (newHigh) { +    // if so write out the new one +    uiInfo.newHighScoreTime = uiInfo.uiDC.realTime + 20000; +    if (trap_FS_FOpenFile(fileName, &f, FS_WRITE) >= 0) { +      size = sizeof(postGameInfo_t); +      trap_FS_Write(&size, sizeof(int), f); +      trap_FS_Write(&newInfo, sizeof(postGameInfo_t), f); +      trap_FS_FCloseFile(f); +    } +  } + +  if (newInfo.time < oldInfo.time) { +    uiInfo.newBestTime = uiInfo.uiDC.realTime + 20000; +  } + +  // put back all the ui overrides +  trap_Cvar_Set("capturelimit", UI_Cvar_VariableString("ui_saveCaptureLimit")); +  trap_Cvar_Set("fraglimit", UI_Cvar_VariableString("ui_saveFragLimit")); +  trap_Cvar_Set("cg_drawTimer", UI_Cvar_VariableString("ui_drawTimer")); +  trap_Cvar_Set("g_doWarmup", UI_Cvar_VariableString("ui_doWarmup")); +  trap_Cvar_Set("g_Warmup", UI_Cvar_VariableString("ui_Warmup")); +  trap_Cvar_Set("sv_pure", UI_Cvar_VariableString("ui_pure")); +  trap_Cvar_Set("g_friendlyFire", UI_Cvar_VariableString("ui_friendlyFire")); + +  UI_SetBestScores(&newInfo, qtrue); +  UI_ShowPostGame(newHigh); + + +} + + +/* +================= +UI_ConsoleCommand +================= +*/ +qboolean UI_ConsoleCommand( int realTime ) +{ +  char  *cmd; +  char  *arg1; + +  uiInfo.uiDC.frameTime = realTime - uiInfo.uiDC.realTime; +  uiInfo.uiDC.realTime = realTime; + +  cmd = UI_Argv( 0 ); + +  // ensure minimum menu data is available +  //Menu_Cache(); + +  if ( Q_stricmp (cmd, "ui_test") == 0 ) { +    UI_ShowPostGame(qtrue); +  } + +  if ( Q_stricmp (cmd, "ui_report") == 0 ) { +    UI_Report(); +    return qtrue; +  } + +  if ( Q_stricmp (cmd, "ui_load") == 0 ) { +    UI_Load(); +    return qtrue; +  } + +  if ( Q_stricmp (cmd, "remapShader") == 0 ) { +    if (trap_Argc() == 4) { +      char shader1[MAX_QPATH]; +      char shader2[MAX_QPATH]; +      Q_strncpyz(shader1, UI_Argv(1), sizeof(shader1)); +      Q_strncpyz(shader2, UI_Argv(2), sizeof(shader2)); +      trap_R_RemapShader(shader1, shader2, UI_Argv(3)); +      return qtrue; +    } +  } + +  if ( Q_stricmp (cmd, "postgame") == 0 ) { +    UI_CalcPostGameStats(); +    return qtrue; +  } + +  if ( Q_stricmp (cmd, "ui_cache") == 0 ) { +    UI_Cache_f(); +    return qtrue; +  } + +  if ( Q_stricmp (cmd, "ui_teamOrders") == 0 ) { +    //UI_TeamOrdersMenu_f(); +    return qtrue; +  } + +  if( Q_stricmp ( cmd, "menu" ) == 0 ) +  { +    arg1 = UI_Argv( 1 ); + +    if( Menu_Count( ) > 0 ) +    { +      trap_Key_SetCatcher( KEYCATCH_UI ); +      Menus_ActivateByName( arg1 ); +      return qtrue; +    } +  } + +  if( Q_stricmp ( cmd, "closemenus" ) == 0 ) +  { +    if( Menu_Count( ) > 0 ) +    { +      trap_Key_SetCatcher( trap_Key_GetCatcher( ) & ~KEYCATCH_UI ); +      trap_Key_ClearStates( ); +      trap_Cvar_Set( "cl_paused", "0" ); +      Menus_CloseAll( ); +      return qtrue; +    } +  } + +  return qfalse; +} + +/* +================= +UI_Shutdown +================= +*/ +void UI_Shutdown( void ) { +} + +/* +================ +UI_AdjustFrom640 + +Adjusted for resolution and screen aspect ratio +================ +*/ +void UI_AdjustFrom640( float *x, float *y, float *w, float *h ) { +  // expect valid pointers +#if 0 +  *x = *x * uiInfo.uiDC.scale + uiInfo.uiDC.bias; +  *y *= uiInfo.uiDC.scale; +  *w *= uiInfo.uiDC.scale; +  *h *= uiInfo.uiDC.scale; +#endif + +  *x *= uiInfo.uiDC.xscale; +  *y *= uiInfo.uiDC.yscale; +  *w *= uiInfo.uiDC.xscale; +  *h *= uiInfo.uiDC.yscale; + +} + +void UI_DrawNamedPic( float x, float y, float width, float height, const char *picname ) { +  qhandle_t  hShader; + +  hShader = trap_R_RegisterShaderNoMip( picname ); +  UI_AdjustFrom640( &x, &y, &width, &height ); +  trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); +} + +void UI_DrawHandlePic( float x, float y, float w, float h, qhandle_t hShader ) { +  float  s0; +  float  s1; +  float  t0; +  float  t1; + +  if( w < 0 ) {  // flip about vertical +    w  = -w; +    s0 = 1; +    s1 = 0; +  } +  else { +    s0 = 0; +    s1 = 1; +  } + +  if( h < 0 ) {  // flip about horizontal +    h  = -h; +    t0 = 1; +    t1 = 0; +  } +  else { +    t0 = 0; +    t1 = 1; +  } + +  UI_AdjustFrom640( &x, &y, &w, &h ); +  trap_R_DrawStretchPic( x, y, w, h, s0, t0, s1, t1, hShader ); +} + +/* +================ +UI_FillRect + +Coordinates are 640*480 virtual values +================= +*/ +void UI_FillRect( float x, float y, float width, float height, const float *color ) { +  trap_R_SetColor( color ); + +  UI_AdjustFrom640( &x, &y, &width, &height ); +  trap_R_DrawStretchPic( x, y, width, height, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); + +  trap_R_SetColor( NULL ); +} + +void UI_DrawSides(float x, float y, float w, float h) { +  UI_AdjustFrom640( &x, &y, &w, &h ); +  trap_R_DrawStretchPic( x, y, 1, h, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); +  trap_R_DrawStretchPic( x + w - 1, y, 1, h, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); +} + +void UI_DrawTopBottom(float x, float y, float w, float h) { +  UI_AdjustFrom640( &x, &y, &w, &h ); +  trap_R_DrawStretchPic( x, y, w, 1, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); +  trap_R_DrawStretchPic( x, y + h - 1, w, 1, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); +} +/* +================ +UI_DrawRect + +Coordinates are 640*480 virtual values +================= +*/ +void UI_DrawRect( float x, float y, float width, float height, const float *color ) { +  trap_R_SetColor( color ); + +  UI_DrawTopBottom(x, y, width, height); +  UI_DrawSides(x, y, width, height); + +  trap_R_SetColor( NULL ); +} + +void UI_SetColor( const float *rgba ) { +  trap_R_SetColor( rgba ); +} + +void UI_UpdateScreen( void ) { +  trap_UpdateScreen(); +} + + +void UI_DrawTextBox (int x, int y, int width, int lines) +{ +  UI_FillRect( x + BIGCHAR_WIDTH/2, y + BIGCHAR_HEIGHT/2, ( width + 1 ) * BIGCHAR_WIDTH, ( lines + 1 ) * BIGCHAR_HEIGHT, colorBlack ); +  UI_DrawRect( x + BIGCHAR_WIDTH/2, y + BIGCHAR_HEIGHT/2, ( width + 1 ) * BIGCHAR_WIDTH, ( lines + 1 ) * BIGCHAR_HEIGHT, colorWhite ); +} + +qboolean UI_CursorInRect (int x, int y, int width, int height) +{ +  if (uiInfo.uiDC.cursorx < x || +    uiInfo.uiDC.cursory < y || +    uiInfo.uiDC.cursorx > x+width || +    uiInfo.uiDC.cursory > y+height) +    return qfalse; + +  return qtrue; +} diff --git a/src/ui/ui_gameinfo.c b/src/ui/ui_gameinfo.c new file mode 100644 index 0000000..43639e5 --- /dev/null +++ b/src/ui/ui_gameinfo.c @@ -0,0 +1,333 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +// +// gameinfo.c +// + +#include "ui_local.h" + + +// +// arena and bot info +// + + +int       ui_numBots; +static char   *ui_botInfos[MAX_BOTS]; + +static int    ui_numArenas; +static char   *ui_arenaInfos[MAX_ARENAS]; + +/* +=============== +UI_ParseInfos +=============== +*/ +int UI_ParseInfos( char *buf, int max, char *infos[] ) { +  char  *token; +  int   count; +  char  key[MAX_TOKEN_CHARS]; +  char  info[MAX_INFO_STRING]; + +  count = 0; + +  while ( 1 ) { +    token = COM_Parse( &buf ); +    if ( !token[0] ) { +      break; +    } +    if ( strcmp( token, "{" ) ) { +      Com_Printf( "Missing { in info file\n" ); +      break; +    } + +    if ( count == max ) { +      Com_Printf( "Max infos exceeded\n" ); +      break; +    } + +    info[0] = '\0'; +    while ( 1 ) { +      token = COM_ParseExt( &buf, qtrue ); +      if ( !token[0] ) { +        Com_Printf( "Unexpected end of info file\n" ); +        break; +      } +      if ( !strcmp( token, "}" ) ) { +        break; +      } +      Q_strncpyz( key, token, sizeof( key ) ); + +      token = COM_ParseExt( &buf, qfalse ); +      if ( !token[0] ) { +        strcpy( token, "<NULL>" ); +      } +      Info_SetValueForKey( info, key, token ); +    } +    //NOTE: extra space for arena number +    infos[count] = UI_Alloc(strlen(info) + strlen("\\num\\") + strlen(va("%d", MAX_ARENAS)) + 1); +    if (infos[count]) { +      strcpy(infos[count], info); +      count++; +    } +  } +  return count; +} + +/* +=============== +UI_LoadArenasFromFile +=============== +*/ +static void UI_LoadArenasFromFile( char *filename ) { +  int       len; +  fileHandle_t  f; +  char      buf[MAX_ARENAS_TEXT]; + +  len = trap_FS_FOpenFile( filename, &f, FS_READ ); +  if ( !f ) { +    trap_Print( va( S_COLOR_RED "file not found: %s\n", filename ) ); +    return; +  } +  if ( len >= MAX_ARENAS_TEXT ) { +    trap_Print( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_ARENAS_TEXT ) ); +    trap_FS_FCloseFile( f ); +    return; +  } + +  trap_FS_Read( buf, len, f ); +  buf[len] = 0; +  trap_FS_FCloseFile( f ); + +  ui_numArenas += UI_ParseInfos( buf, MAX_ARENAS - ui_numArenas, &ui_arenaInfos[ui_numArenas] ); +} + +/* +================= +UI_MapNameCompare +================= +*/ +static int UI_MapNameCompare( const void *a, const void *b ) +{ +  mapInfo *A = (mapInfo *)a; +  mapInfo *B = (mapInfo *)b; + +  return Q_stricmp( A->mapName, B->mapName ); +} + +/* +=============== +UI_LoadArenas +=============== +*/ +void UI_LoadArenas( void ) { +  int     numdirs; +  char    filename[128]; +  char    dirlist[1024]; +  char*   dirptr; +  int     i, n; +  int     dirlen; +  char    *type; + +  ui_numArenas = 0; +  uiInfo.mapCount = 0; + +  // get all arenas from .arena files +  numdirs = trap_FS_GetFileList("scripts", ".arena", dirlist, 1024 ); +  dirptr  = dirlist; +  for (i = 0; i < numdirs; i++, dirptr += dirlen+1) { +    dirlen = strlen(dirptr); +    strcpy(filename, "scripts/"); +    strcat(filename, dirptr); +    UI_LoadArenasFromFile(filename); +  } +  trap_Print( va( "[skipnotify]%i arenas parsed\n", ui_numArenas ) ); +  if (UI_OutOfMemory()) { +    trap_Print(S_COLOR_YELLOW"WARNING: not anough memory in pool to load all arenas\n"); +  } + +  for( n = 0; n < ui_numArenas; n++ ) +  { +      // determine type +    type = Info_ValueForKey( ui_arenaInfos[ n ], "type" ); +    // if no type specified, it will be treated as "ffa" + +    if( *type && strstr( type, "tremulous" ) ) +      uiInfo.mapList[ uiInfo.mapCount ].typeBits |= ( 1 << 0 ); +    else +      continue; //not a trem map + +    uiInfo.mapList[uiInfo.mapCount].cinematic = -1; +    uiInfo.mapList[uiInfo.mapCount].mapLoadName = String_Alloc(Info_ValueForKey(ui_arenaInfos[n], "map")); +    uiInfo.mapList[uiInfo.mapCount].mapName = String_Alloc(Info_ValueForKey(ui_arenaInfos[n], "longname")); +    uiInfo.mapList[uiInfo.mapCount].levelShot = -1; +    uiInfo.mapList[uiInfo.mapCount].imageName = String_Alloc(va("levelshots/%s", uiInfo.mapList[uiInfo.mapCount].mapLoadName)); + +    uiInfo.mapCount++; +    if( uiInfo.mapCount >= MAX_MAPS ) +      break; +  } + +  qsort( uiInfo.mapList, uiInfo.mapCount, sizeof( mapInfo ), UI_MapNameCompare ); +} + + +/* +=============== +UI_LoadBotsFromFile +=============== +*/ +static void UI_LoadBotsFromFile( char *filename ) { +  int       len; +  fileHandle_t  f; +  char      buf[MAX_BOTS_TEXT]; + +  len = trap_FS_FOpenFile( filename, &f, FS_READ ); +  if ( !f ) { +    trap_Print( va( S_COLOR_RED "file not found: %s\n", filename ) ); +    return; +  } +  if ( len >= MAX_BOTS_TEXT ) { +    trap_Print( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_BOTS_TEXT ) ); +    trap_FS_FCloseFile( f ); +    return; +  } + +  trap_FS_Read( buf, len, f ); +  buf[len] = 0; +  trap_FS_FCloseFile( f ); + +  COM_Compress(buf); + +  ui_numBots += UI_ParseInfos( buf, MAX_BOTS - ui_numBots, &ui_botInfos[ui_numBots] ); +} + +/* +=============== +UI_LoadBots +=============== +*/ +void UI_LoadBots( void ) { +  vmCvar_t  botsFile; +  int     numdirs; +  char    filename[128]; +  char    dirlist[1024]; +  char*   dirptr; +  int     i; +  int     dirlen; + +  ui_numBots = 0; + +  trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM ); +  if( *botsFile.string ) { +    UI_LoadBotsFromFile(botsFile.string); +  } +  else { +    UI_LoadBotsFromFile("scripts/bots.txt"); +  } + +  // get all bots from .bot files +  numdirs = trap_FS_GetFileList("scripts", ".bot", dirlist, 1024 ); +  dirptr  = dirlist; +  for (i = 0; i < numdirs; i++, dirptr += dirlen+1) { +    dirlen = strlen(dirptr); +    strcpy(filename, "scripts/"); +    strcat(filename, dirptr); +    UI_LoadBotsFromFile(filename); +  } +  trap_Print( va( "%i bots parsed\n", ui_numBots ) ); +} + + +/* +=============== +UI_GetBotInfoByNumber +=============== +*/ +char *UI_GetBotInfoByNumber( int num ) { +  if( num < 0 || num >= ui_numBots ) { +    trap_Print( va( S_COLOR_RED "Invalid bot number: %i\n", num ) ); +    return NULL; +  } +  return ui_botInfos[num]; +} + + +/* +=============== +UI_GetBotInfoByName +=============== +*/ +char *UI_GetBotInfoByName( const char *name ) { +  int   n; +  char  *value; + +  for ( n = 0; n < ui_numBots ; n++ ) { +    value = Info_ValueForKey( ui_botInfos[n], "name" ); +    if ( !Q_stricmp( value, name ) ) { +      return ui_botInfos[n]; +    } +  } + +  return NULL; +} + +int UI_GetNumBots() { +  return ui_numBots; +} + + +char *UI_GetBotNameByNumber( int num ) { +  char *info = UI_GetBotInfoByNumber(num); +  if (info) { +    return Info_ValueForKey( info, "name" ); +  } +  return "Sarge"; +} + +void UI_ServerInfo( void ) +{ +  char      info[ MAX_INFO_VALUE ]; + +  info[0] = '\0'; +  if( trap_GetConfigString( CS_SERVERINFO, info, sizeof( info ) ) ) +  {  +    trap_Cvar_Set( "ui_serverinfo_mapname", +      Info_ValueForKey( info, "mapname" ) ); +    trap_Cvar_Set( "ui_serverinfo_timelimit", +      Info_ValueForKey( info, "timelimit" ) ); +    trap_Cvar_Set( "ui_serverinfo_sd", +      Info_ValueForKey( info, "g_suddenDeathTime" ) ); +    trap_Cvar_Set( "ui_serverinfo_hostname", +      Info_ValueForKey( info, "sv_hostname" ) ); +    trap_Cvar_Set( "ui_serverinfo_maxclients", +      Info_ValueForKey( info, "sv_maxclients" ) ); +    trap_Cvar_Set( "ui_serverinfo_version", +      Info_ValueForKey( info, "version" ) ); +    trap_Cvar_Set( "ui_serverinfo_unlagged", +      Info_ValueForKey( info, "g_unlagged" ) ); +    trap_Cvar_Set( "ui_serverinfo_ff", +      Info_ValueForKey( info, "ff" ) ); +  } +} diff --git a/src/ui/ui_local.h b/src/ui/ui_local.h new file mode 100644 index 0000000..7afdf65 --- /dev/null +++ b/src/ui/ui_local.h @@ -0,0 +1,1207 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +#ifndef __UI_LOCAL_H__ +#define __UI_LOCAL_H__ + +#include "../qcommon/q_shared.h" +#include "../renderer/tr_types.h" +#include "ui_public.h" +#include "../client/keycodes.h" +#include "../game/bg_public.h" +#include "ui_shared.h" + +// global display context + +extern vmCvar_t  ui_ffa_fraglimit; +extern vmCvar_t  ui_ffa_timelimit; + +extern vmCvar_t  ui_tourney_fraglimit; +extern vmCvar_t  ui_tourney_timelimit; + +extern vmCvar_t  ui_team_fraglimit; +extern vmCvar_t  ui_team_timelimit; +extern vmCvar_t  ui_team_friendly; + +extern vmCvar_t  ui_ctf_capturelimit; +extern vmCvar_t  ui_ctf_timelimit; +extern vmCvar_t  ui_ctf_friendly; + +extern vmCvar_t  ui_arenasFile; +extern vmCvar_t  ui_botsFile; +extern vmCvar_t  ui_spScores1; +extern vmCvar_t  ui_spScores2; +extern vmCvar_t  ui_spScores3; +extern vmCvar_t  ui_spScores4; +extern vmCvar_t  ui_spScores5; +extern vmCvar_t  ui_spAwards; +extern vmCvar_t  ui_spVideos; +extern vmCvar_t  ui_spSkill; + +extern vmCvar_t  ui_spSelection; + +extern vmCvar_t  ui_browserMaster; +extern vmCvar_t  ui_browserGameType; +extern vmCvar_t  ui_browserSortKey; +extern vmCvar_t  ui_browserShowFull; +extern vmCvar_t  ui_browserShowEmpty; + +extern vmCvar_t  ui_brassTime; +extern vmCvar_t  ui_drawCrosshair; +extern vmCvar_t  ui_drawCrosshairNames; +extern vmCvar_t  ui_marks; + +extern vmCvar_t  ui_server1; +extern vmCvar_t  ui_server2; +extern vmCvar_t  ui_server3; +extern vmCvar_t  ui_server4; +extern vmCvar_t  ui_server5; +extern vmCvar_t  ui_server6; +extern vmCvar_t  ui_server7; +extern vmCvar_t  ui_server8; +extern vmCvar_t  ui_server9; +extern vmCvar_t  ui_server10; +extern vmCvar_t  ui_server11; +extern vmCvar_t  ui_server12; +extern vmCvar_t  ui_server13; +extern vmCvar_t  ui_server14; +extern vmCvar_t  ui_server15; +extern vmCvar_t  ui_server16; + +extern vmCvar_t  ui_captureLimit; +extern vmCvar_t  ui_fragLimit; +extern vmCvar_t  ui_gameType; +extern vmCvar_t  ui_netGameType; +extern vmCvar_t  ui_actualNetGameType; +extern vmCvar_t  ui_joinGameType; +extern vmCvar_t  ui_netSource; +extern vmCvar_t  ui_serverFilterType; +extern vmCvar_t  ui_dedicated; +extern vmCvar_t  ui_opponentName; +extern vmCvar_t  ui_menuFiles; +extern vmCvar_t  ui_currentTier; +extern vmCvar_t  ui_currentMap; +extern vmCvar_t  ui_currentNetMap; +extern vmCvar_t  ui_mapIndex; +extern vmCvar_t  ui_currentOpponent; +extern vmCvar_t  ui_selectedPlayer; +extern vmCvar_t  ui_selectedPlayerName; +extern vmCvar_t  ui_lastServerRefresh_0; +extern vmCvar_t  ui_lastServerRefresh_1; +extern vmCvar_t  ui_lastServerRefresh_2; +extern vmCvar_t  ui_lastServerRefresh_3; +extern vmCvar_t  ui_singlePlayerActive; +extern vmCvar_t  ui_scoreAccuracy; +extern vmCvar_t  ui_scoreImpressives; +extern vmCvar_t  ui_scoreExcellents; +extern vmCvar_t  ui_scoreDefends; +extern vmCvar_t  ui_scoreAssists; +extern vmCvar_t  ui_scoreGauntlets; +extern vmCvar_t  ui_scoreScore; +extern vmCvar_t  ui_scorePerfect; +extern vmCvar_t  ui_scoreTeam; +extern vmCvar_t  ui_scoreBase; +extern vmCvar_t  ui_scoreTimeBonus; +extern vmCvar_t  ui_scoreSkillBonus; +extern vmCvar_t  ui_scoreShutoutBonus; +extern vmCvar_t  ui_scoreTime; +extern vmCvar_t  ui_smallFont; +extern vmCvar_t  ui_bigFont; +extern vmCvar_t ui_serverStatusTimeOut; + +//TA: bank values +extern vmCvar_t  ui_bank; + + +// +// ui_qmenu.c +// + +#define RCOLUMN_OFFSET      ( BIGCHAR_WIDTH ) +#define LCOLUMN_OFFSET      (-BIGCHAR_WIDTH ) + +#define SLIDER_RANGE      10 +#define  MAX_EDIT_LINE      256 + +#define MAX_MENUDEPTH      8 +#define MAX_MENUITEMS      128 + +#define MTYPE_NULL        0 +#define MTYPE_SLIDER      1 +#define MTYPE_ACTION      2 +#define MTYPE_SPINCONTROL    3 +#define MTYPE_FIELD        4 +#define MTYPE_RADIOBUTTON    5 +#define MTYPE_BITMAP      6 +#define MTYPE_TEXT        7 +#define MTYPE_SCROLLLIST    8 +#define MTYPE_PTEXT        9 +#define MTYPE_BTEXT        10 + +#define QMF_BLINK        0x00000001 +#define QMF_SMALLFONT      0x00000002 +#define QMF_LEFT_JUSTIFY    0x00000004 +#define QMF_CENTER_JUSTIFY    0x00000008 +#define QMF_RIGHT_JUSTIFY    0x00000010 +#define QMF_NUMBERSONLY      0x00000020  // edit field is only numbers +#define QMF_HIGHLIGHT      0x00000040 +#define QMF_HIGHLIGHT_IF_FOCUS  0x00000080  // steady focus +#define QMF_PULSEIFFOCUS    0x00000100  // pulse if focus +#define QMF_HASMOUSEFOCUS    0x00000200 +#define QMF_NOONOFFTEXT      0x00000400 +#define QMF_MOUSEONLY      0x00000800  // only mouse input allowed +#define QMF_HIDDEN        0x00001000  // skips drawing +#define QMF_GRAYED        0x00002000  // grays and disables +#define QMF_INACTIVE      0x00004000  // disables any input +#define QMF_NODEFAULTINIT    0x00008000  // skip default initialization +#define QMF_OWNERDRAW      0x00010000 +#define QMF_PULSE        0x00020000 +#define QMF_LOWERCASE      0x00040000  // edit field is all lower case +#define QMF_UPPERCASE      0x00080000  // edit field is all upper case +#define QMF_SILENT        0x00100000 + +// callback notifications +#define QM_GOTFOCUS        1 +#define QM_LOSTFOCUS      2 +#define QM_ACTIVATED      3 + +typedef struct _tag_menuframework +{ +  int  cursor; +  int cursor_prev; + +  int  nitems; +  void *items[MAX_MENUITEMS]; + +  void (*draw) (void); +  sfxHandle_t (*key) (int key); + +  qboolean  wrapAround; +  qboolean  fullscreen; +  qboolean  showlogo; +} menuframework_s; + +typedef struct +{ +  int type; +  const char *name; +  int  id; +  int x, y; +  int left; +  int  top; +  int  right; +  int  bottom; +  menuframework_s *parent; +  int menuPosition; +  unsigned flags; + +  void (*callback)( void *self, int event ); +  void (*statusbar)( void *self ); +  void (*ownerdraw)( void *self ); +} menucommon_s; + +typedef struct { +  int    cursor; +  int    scroll; +  int    widthInChars; +  char  buffer[MAX_EDIT_LINE]; +  int    maxchars; +} mfield_t; + +typedef struct +{ +  menucommon_s  generic; +  mfield_t    field; +} menufield_s; + +typedef struct +{ +  menucommon_s generic; + +  float minvalue; +  float maxvalue; +  float curvalue; + +  float range; +} menuslider_s; + +typedef struct +{ +  menucommon_s generic; + +  int  oldvalue; +  int curvalue; +  int  numitems; +  int  top; + +  const char **itemnames; + +  int width; +  int height; +  int  columns; +  int  seperation; +} menulist_s; + +typedef struct +{ +  menucommon_s generic; +} menuaction_s; + +typedef struct +{ +  menucommon_s generic; +  int curvalue; +} menuradiobutton_s; + +typedef struct +{ +  menucommon_s  generic; +  char*      focuspic; +  char*      errorpic; +  qhandle_t    shader; +  qhandle_t    focusshader; +  int        width; +  int        height; +  float*      focuscolor; +} menubitmap_s; + +typedef struct +{ +  menucommon_s  generic; +  char*      string; +  int        style; +  float*      color; +} menutext_s; + +extern void      Menu_Cache( void ); +extern void      Menu_Focus( menucommon_s *m ); +extern void      Menu_AddItem( menuframework_s *menu, void *item ); +extern void      Menu_AdjustCursor( menuframework_s *menu, int dir ); +extern void      Menu_Draw( menuframework_s *menu ); +extern void      *Menu_ItemAtCursor( menuframework_s *m ); +extern sfxHandle_t  Menu_ActivateItem( menuframework_s *s, menucommon_s* item ); +extern void      Menu_SetCursor( menuframework_s *s, int cursor ); +extern void      Menu_SetCursorToItem( menuframework_s *m, void* ptr ); +extern sfxHandle_t  Menu_DefaultKey( menuframework_s *s, int key ); +extern void      Bitmap_Init( menubitmap_s *b ); +extern void      Bitmap_Draw( menubitmap_s *b ); +extern void      ScrollList_Draw( menulist_s *l ); +extern sfxHandle_t  ScrollList_Key( menulist_s *l, int key ); +extern sfxHandle_t  menu_in_sound; +extern sfxHandle_t  menu_move_sound; +extern sfxHandle_t  menu_out_sound; +extern sfxHandle_t  menu_buzz_sound; +extern sfxHandle_t  menu_null_sound; +extern sfxHandle_t  weaponChangeSound; +extern vec4_t    menu_text_color; +extern vec4_t    menu_grayed_color; +extern vec4_t    menu_dark_color; +extern vec4_t    menu_highlight_color; +extern vec4_t    menu_red_color; +extern vec4_t    menu_black_color; +extern vec4_t    menu_dim_color; +extern vec4_t    color_black; +extern vec4_t    color_white; +extern vec4_t    color_yellow; +extern vec4_t    color_blue; +extern vec4_t    color_orange; +extern vec4_t    color_red; +extern vec4_t    color_dim; +extern vec4_t    name_color; +extern vec4_t    list_color; +extern vec4_t    listbar_color; +extern vec4_t    text_color_disabled; +extern vec4_t    text_color_normal; +extern vec4_t    text_color_highlight; + +extern char  *ui_medalNames[]; +extern char  *ui_medalPicNames[]; +extern char  *ui_medalSounds[]; + +// +// ui_mfield.c +// +extern void      MField_Clear( mfield_t *edit ); +extern void      MField_KeyDownEvent( mfield_t *edit, int key ); +extern void      MField_CharEvent( mfield_t *edit, int ch ); +extern void      MField_Draw( mfield_t *edit, int x, int y, int style, vec4_t color ); +extern void      MenuField_Init( menufield_s* m ); +extern void      MenuField_Draw( menufield_s *f ); +extern sfxHandle_t  MenuField_Key( menufield_s* m, int* key ); + +// +// ui_main.c +// +void UI_Report( void ); +void UI_Load( void ); +void UI_LoadMenus(const char *menuFile, qboolean reset); +void _UI_SetActiveMenu( uiMenuCommand_t menu ); +int UI_AdjustTimeByGame(int time); +void UI_ShowPostGame(qboolean newHigh); +void UI_ClearScores( void ); +void UI_LoadArenas(void); +void UI_ServerInfo(void); + +// +// ui_menu.c +// +extern void MainMenu_Cache( void ); +extern void UI_MainMenu(void); +extern void UI_RegisterCvars( void ); +extern void UI_UpdateCvars( void ); + +// +// ui_credits.c +// +extern void UI_CreditMenu( void ); + +// +// ui_ingame.c +// +extern void InGame_Cache( void ); +extern void UI_InGameMenu(void); + +// +// ui_confirm.c +// +extern void ConfirmMenu_Cache( void ); +extern void UI_ConfirmMenu( const char *question, void (*draw)( void ), void (*action)( qboolean result ) ); + +// +// ui_setup.c +// +extern void UI_SetupMenu_Cache( void ); +extern void UI_SetupMenu(void); + +// +// ui_team.c +// +extern void UI_TeamMainMenu( void ); +extern void TeamMain_Cache( void ); + +// +// ui_connect.c +// +extern void UI_DrawConnectScreen( qboolean overlay ); + +// +// ui_controls2.c +// +extern void UI_ControlsMenu( void ); +extern void Controls_Cache( void ); + +// +// ui_demo2.c +// +extern void UI_DemosMenu( void ); +extern void Demos_Cache( void ); + +// +// ui_cinematics.c +// +extern void UI_CinematicsMenu( void ); +extern void UI_CinematicsMenu_f( void ); +extern void UI_CinematicsMenu_Cache( void ); + +// +// ui_mods.c +// +extern void UI_ModsMenu( void ); +extern void UI_ModsMenu_Cache( void ); + +// +// ui_playermodel.c +// +extern void UI_PlayerModelMenu( void ); +extern void PlayerModel_Cache( void ); + +// +// ui_playersettings.c +// +extern void UI_PlayerSettingsMenu( void ); +extern void PlayerSettings_Cache( void ); + +// +// ui_preferences.c +// +extern void UI_PreferencesMenu( void ); +extern void Preferences_Cache( void ); + +// +// ui_specifyleague.c +// +extern void UI_SpecifyLeagueMenu( void ); +extern void SpecifyLeague_Cache( void ); + +// +// ui_specifyserver.c +// +extern void UI_SpecifyServerMenu( void ); +extern void SpecifyServer_Cache( void ); + +// +// ui_servers2.c +// +#define MAX_FAVORITESERVERS 16 + +extern void UI_ArenaServersMenu( void ); +extern void ArenaServers_Cache( void ); + +// +// ui_startserver.c +// +extern void UI_StartServerMenu( qboolean multiplayer ); +extern void StartServer_Cache( void ); +extern void ServerOptions_Cache( void ); +extern void UI_BotSelectMenu( char *bot ); +extern void UI_BotSelectMenu_Cache( void ); + +// +// ui_serverinfo.c +// +extern void UI_ServerInfoMenu( void ); +extern void ServerInfo_Cache( void ); + +// +// ui_video.c +// +extern void UI_GraphicsOptionsMenu( void ); +extern void GraphicsOptions_Cache( void ); +extern void DriverInfo_Cache( void ); + +// +// ui_players.c +// + +//FIXME ripped from cg_local.h +typedef struct { +  int      oldFrame; +  int      oldFrameTime;    // time when ->oldFrame was exactly on + +  int      frame; +  int      frameTime;      // time when ->frame will be exactly on + +  float    backlerp; + +  float    yawAngle; +  qboolean  yawing; +  float    pitchAngle; +  qboolean  pitching; + +  int      animationNumber;  // may include ANIM_TOGGLEBIT +  animation_t  *animation; +  int      animationTime;    // time when the first frame of the animation will be exact +} lerpFrame_t; + +typedef struct { +  // model info +  qhandle_t    legsModel; +  qhandle_t    legsSkin; +  lerpFrame_t    legs; + +  qhandle_t    torsoModel; +  qhandle_t    torsoSkin; +  lerpFrame_t    torso; + +  qhandle_t    headModel; +  qhandle_t    headSkin; + +  animation_t    animations[MAX_PLAYER_TOTALANIMATIONS]; + +  qhandle_t    weaponModel; +  qhandle_t    barrelModel; +  qhandle_t    flashModel; +  vec3_t      flashDlightColor; +  int        muzzleFlashTime; + +  // currently in use drawing parms +  vec3_t      viewAngles; +  vec3_t      moveAngles; +  weapon_t    currentWeapon; +  int        legsAnim; +  int        torsoAnim; + +  // animation vars +  weapon_t    weapon; +  weapon_t    lastWeapon; +  weapon_t    pendingWeapon; +  int        weaponTimer; +  int        pendingLegsAnim; +  int        torsoAnimationTimer; + +  int        pendingTorsoAnim; +  int        legsAnimationTimer; + +  qboolean    chat; +  qboolean    newModel; + +  qboolean    barrelSpinning; +  float      barrelAngle; +  int        barrelTime; + +  int        realWeapon; +} playerInfo_t; + +void UI_DrawPlayer( float x, float y, float w, float h, playerInfo_t *pi, int time ); +void UI_PlayerInfo_SetModel( playerInfo_t *pi, const char *model, const char *headmodel, char *teamName ); +void UI_PlayerInfo_SetInfo( playerInfo_t *pi, int legsAnim, int torsoAnim, vec3_t viewAngles, vec3_t moveAngles, weapon_t weaponNum, qboolean chat ); +qboolean UI_RegisterClientModelname( playerInfo_t *pi, const char *modelSkinName , const char *headName, const char *teamName); + +// +// ui_atoms.c +// +// this is only used in the old ui, the new ui has it's own version +typedef struct { +  int          frametime; +  int          realtime; +  int          cursorx; +  int          cursory; +  glconfig_t   glconfig; +  qboolean     debug; +  qhandle_t    whiteShader; +  qhandle_t    charset; +  qhandle_t    charsetProp; +  qhandle_t    charsetPropGlow; +  qhandle_t    charsetPropB; +  qhandle_t    cursor; +  qhandle_t    rb_on; +  qhandle_t    rb_off; +  float        scale; +  float        bias; +  qboolean     demoversion; +  qboolean     firstdraw; +} uiStatic_t; + + +// new ui stuff +#define UI_NUMFX 7 +#define MAX_HEADS 64 +#define MAX_ALIASES 64 +#define MAX_HEADNAME  32 +#define MAX_TEAMS 64 +#define MAX_GAMETYPES 16 +#define MAX_MAPS 128 +#define MAX_SPMAPS 16 +#define PLAYERS_PER_TEAM 5 +#define MAX_PINGREQUESTS    32 +#define MAX_ADDRESSLENGTH    64 +#define MAX_HOSTNAMELENGTH    22 +#define MAX_MAPNAMELENGTH    16 +#define MAX_STATUSLENGTH    64 +#define MAX_LISTBOXWIDTH    59 +#define UI_FONT_THRESHOLD    0.1 +#define MAX_DISPLAY_SERVERS    2048 +#define MAX_SERVERSTATUS_LINES  128 +#define MAX_SERVERSTATUS_TEXT  1024 +#define MAX_FOUNDPLAYER_SERVERS  16 +#define TEAM_MEMBERS 5 +#define GAMES_ALL      0 +#define GAMES_FFA      1 +#define GAMES_TEAMPLAY    2 +#define GAMES_TOURNEY    3 +#define GAMES_CTF      4 +#define MAPS_PER_TIER 3 +#define MAX_TIERS 16 +#define MAX_MODS 64 +#define MAX_DEMOS 256 +#define MAX_MOVIES 256 +#define MAX_PLAYERMODELS 256 + + +typedef struct { +  const char *name; +  const char *imageName; +  qhandle_t headImage; +  const char *base; +  qboolean active; +  int reference; +} characterInfo; + +typedef struct { +  const char *name; +  const char *ai; +  const char *action; +} aliasInfo; + +typedef struct { +  const char *teamName; +  const char *imageName; +  const char *teamMembers[TEAM_MEMBERS]; +  qhandle_t teamIcon; +  qhandle_t teamIcon_Metal; +  qhandle_t teamIcon_Name; +  int cinematic; +} teamInfo; + +typedef struct { +  const char *gameType; +  int gtEnum; +} gameTypeInfo; + +typedef struct { +  const char *mapName; +  const char *mapLoadName; +  const char *imageName; +  const char *opponentName; +  int teamMembers; +  int typeBits; +  int cinematic; +  int timeToBeat[MAX_GAMETYPES]; +  qhandle_t levelShot; +  qboolean active; +} mapInfo; + +typedef struct { +  const char *tierName; +  const char *maps[MAPS_PER_TIER]; +  int gameTypes[MAPS_PER_TIER]; +  qhandle_t mapHandles[MAPS_PER_TIER]; +} tierInfo; + +typedef struct serverFilter_s { +  const char *description; +  const char *basedir; +} serverFilter_t; + +typedef struct { +  char  adrstr[MAX_ADDRESSLENGTH]; +  int    start; +} pinglist_t; + + +typedef struct serverStatus_s { +  pinglist_t pingList[MAX_PINGREQUESTS]; +  int    numqueriedservers; +  int    currentping; +  int    nextpingtime; +  int    maxservers; +  int    refreshtime; +  int    numServers; +  int    sortKey; +  int    sortDir; +  qboolean sorted; +  int    lastCount; +  qboolean refreshActive; +  int    currentServer; +  int    displayServers[MAX_DISPLAY_SERVERS]; +  int    numDisplayServers; +  int    numPlayersOnServers; +  int    nextDisplayRefresh; +  int    nextSortTime; +  qhandle_t currentServerPreview; +  int    currentServerCinematic; +  int    motdLen; +  int    motdWidth; +  int    motdPaintX; +  int    motdPaintX2; +  int    motdOffset; +  int    motdTime; +  char  motd[MAX_STRING_CHARS]; +} serverStatus_t; + + +typedef struct { +  char    adrstr[MAX_ADDRESSLENGTH]; +  char    name[MAX_ADDRESSLENGTH]; +  int      startTime; +  int      serverNum; +  qboolean  valid; +} pendingServer_t; + +typedef struct { +  int num; +  pendingServer_t server[MAX_SERVERSTATUSREQUESTS]; +} pendingServerStatus_t; + +typedef struct { +  char address[MAX_ADDRESSLENGTH]; +  char *lines[MAX_SERVERSTATUS_LINES][4]; +  char text[MAX_SERVERSTATUS_TEXT]; +  char pings[MAX_CLIENTS * 3]; +  int numLines; +} serverStatusInfo_t; + +typedef struct { +  const char *modName; +  const char *modDescr; +} modInfo_t; + +//TA: tremulous menus +#define MAX_INFOPANE_TEXT     4096 +#define MAX_INFOPANE_GRAPHICS 16 +#define MAX_INFOPANES         128 + +typedef enum +{ +  INFOPANE_TOP, +  INFOPANE_BOTTOM, +  INFOPANE_LEFT, +  INFOPANE_RIGHT +} tremIPSide_t; + +typedef struct +{ +  qhandle_t     graphic; + +  tremIPSide_t  side; +  int           offset; + +  int           width, height; +} tremIPGraphic_t; + +typedef struct +{ +  const char      *name; +  char            text[ MAX_INFOPANE_TEXT ]; +  int             align; + +  tremIPGraphic_t graphics[ MAX_INFOPANE_GRAPHICS ]; +  int             numGraphics; +} tremInfoPane_t; + +typedef struct +{ +  const char      *text; +  const char      *cmd; +  tremInfoPane_t  *infopane; +} tremMenuItem_t; +//TA: tremulous menus + +typedef struct { +  displayContextDef_t uiDC; +  int newHighScoreTime; +  int newBestTime; +  int showPostGameTime; +  qboolean newHighScore; +  qboolean demoAvailable; +  qboolean soundHighScore; + +  int characterCount; +  int botIndex; +  characterInfo characterList[MAX_HEADS]; + +  int aliasCount; +  aliasInfo aliasList[MAX_ALIASES]; + +  int teamCount; +  teamInfo teamList[MAX_TEAMS]; + +  int numGameTypes; +  gameTypeInfo gameTypes[MAX_GAMETYPES]; + +  int numJoinGameTypes; +  gameTypeInfo joinGameTypes[MAX_GAMETYPES]; + +  int redBlue; +  int playerCount; +  int myTeamCount; +  int teamIndex; +  int playerRefresh; +  int playerIndex; +  int playerNumber; +  int myPlayerIndex; +  int ignoreIndex; +  qboolean teamLeader; +  char playerNames[MAX_CLIENTS][MAX_NAME_LENGTH]; +  char rawPlayerNames[MAX_CLIENTS][MAX_NAME_LENGTH]; +  char teamNames[MAX_CLIENTS][MAX_NAME_LENGTH]; +  char rawTeamNames[MAX_CLIENTS][MAX_NAME_LENGTH]; +  int clientNums[MAX_CLIENTS]; +  int teamClientNums[MAX_CLIENTS]; +  clientList_t ignoreList[MAX_CLIENTS]; + +  int mapCount; +  mapInfo mapList[MAX_MAPS]; + + +  int tierCount; +  tierInfo tierList[MAX_TIERS]; + +  int skillIndex; + +  modInfo_t modList[MAX_MODS]; +  int modCount; +  int modIndex; + +  const char *demoList[MAX_DEMOS]; +  int demoCount; +  int demoIndex; + +  const char *movieList[MAX_MOVIES]; +  int movieCount; +  int movieIndex; +  int previewMovie; + +  tremInfoPane_t  tremInfoPanes[ MAX_INFOPANES ]; +  int             tremInfoPaneCount; + +//TA: tremulous menus +  tremMenuItem_t  tremTeamList[ 4 ]; +  int             tremTeamCount; +  int             tremTeamIndex; + +  tremMenuItem_t  tremAlienClassList[ 3 ]; +  int             tremAlienClassCount; +  int             tremAlienClassIndex; + +  tremMenuItem_t  tremHumanItemList[ 3 ]; +  int             tremHumanItemCount; +  int             tremHumanItemIndex; + +  tremMenuItem_t  tremHumanArmouryBuyList[ 32 ]; +  int             tremHumanArmouryBuyCount; +  int             tremHumanArmouryBuyIndex; + +  tremMenuItem_t  tremHumanArmourySellList[ 32 ]; +  int             tremHumanArmourySellCount; +  int             tremHumanArmourySellIndex; + +  tremMenuItem_t  tremAlienUpgradeList[ 16 ]; +  int             tremAlienUpgradeCount; +  int             tremAlienUpgradeIndex; + +  tremMenuItem_t  tremAlienBuildList[ 32 ]; +  int             tremAlienBuildCount; +  int             tremAlienBuildIndex; + +  tremMenuItem_t  tremHumanBuildList[ 32 ]; +  int             tremHumanBuildCount; +  int             tremHumanBuildIndex; +//TA: tremulous menus + +  serverStatus_t serverStatus; + +  // for the showing the status of a server +  char serverStatusAddress[MAX_ADDRESSLENGTH]; +  serverStatusInfo_t serverStatusInfo; +  int nextServerStatusRefresh; + +  // to retrieve the status of server to find a player +  pendingServerStatus_t pendingServerStatus; +  char findPlayerName[MAX_STRING_CHARS]; +  char foundPlayerServerAddresses[MAX_FOUNDPLAYER_SERVERS][MAX_ADDRESSLENGTH]; +  char foundPlayerServerNames[MAX_FOUNDPLAYER_SERVERS][MAX_ADDRESSLENGTH]; +  int currentFoundPlayerServer; +  int numFoundPlayerServers; +  int nextFindPlayerRefresh; + +  int currentCrosshair; +  int startPostGameTime; +  sfxHandle_t newHighScoreSound; + +  int        q3HeadCount; +  char      q3HeadNames[MAX_PLAYERMODELS][64]; +  qhandle_t  q3HeadIcons[MAX_PLAYERMODELS]; +  int        q3SelectedHead; + +  int effectsColor; + +  qboolean inGameLoad; +}  uiInfo_t; + +extern uiInfo_t uiInfo; + + +extern void      UI_Init( void ); +extern void      UI_Shutdown( void ); +extern void      UI_KeyEvent( int key ); +extern void      UI_MouseEvent( int dx, int dy ); +extern void      UI_Refresh( int realtime ); +extern qboolean    UI_ConsoleCommand( int realTime ); +extern float    UI_ClampCvar( float min, float max, float value ); +extern void      UI_DrawNamedPic( float x, float y, float width, float height, const char *picname ); +extern void      UI_DrawHandlePic( float x, float y, float w, float h, qhandle_t hShader ); +extern void      UI_FillRect( float x, float y, float width, float height, const float *color ); +extern void      UI_DrawRect( float x, float y, float width, float height, const float *color ); +extern void     UI_DrawTopBottom(float x, float y, float w, float h); +extern void     UI_DrawSides(float x, float y, float w, float h); +extern void      UI_UpdateScreen( void ); +extern void      UI_SetColor( const float *rgba ); +extern void      UI_LerpColor(vec4_t a, vec4_t b, vec4_t c, float t); +extern void      UI_DrawBannerString( int x, int y, const char* str, int style, vec4_t color ); +extern float    UI_ProportionalSizeScale( int style ); +extern void      UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t color ); +extern int      UI_ProportionalStringWidth( const char* str ); +extern void      UI_DrawString( int x, int y, const char* str, int style, vec4_t color ); +extern void      UI_DrawChar( int x, int y, int ch, int style, vec4_t color ); +extern qboolean   UI_CursorInRect (int x, int y, int width, int height); +extern void      UI_AdjustFrom640( float *x, float *y, float *w, float *h ); +extern void      UI_DrawTextBox (int x, int y, int width, int lines); +extern qboolean    UI_IsFullscreen( void ); +extern void      UI_SetActiveMenu( uiMenuCommand_t menu ); +extern void      UI_PushMenu ( menuframework_s *menu ); +extern void      UI_PopMenu (void); +extern void      UI_ForceMenuOff (void); +extern char      *UI_Argv( int arg ); +extern char      *UI_Cvar_VariableString( const char *var_name ); +extern void      UI_Refresh( int time ); +extern void      UI_KeyEvent( int key ); +extern void      UI_StartDemoLoop( void ); +extern qboolean    m_entersound; +void UI_LoadBestScores(const char *map, int game); +extern uiStatic_t  uis; + +// +// ui_spLevel.c +// +void UI_SPLevelMenu_Cache( void ); +void UI_SPLevelMenu( void ); +void UI_SPLevelMenu_f( void ); +void UI_SPLevelMenu_ReInit( void ); + +// +// ui_spArena.c +// +void UI_SPArena_Start( const char *arenaInfo ); + +// +// ui_spPostgame.c +// +void UI_SPPostgameMenu_Cache( void ); +void UI_SPPostgameMenu_f( void ); + +// +// ui_spSkill.c +// +void UI_SPSkillMenu( const char *arenaInfo ); +void UI_SPSkillMenu_Cache( void ); + +// +// ui_syscalls.c +// +void      trap_Print( const char *string ); +void      trap_Error( const char *string ); +int        trap_Milliseconds( void ); +void      trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); +void      trap_Cvar_Update( vmCvar_t *vmCvar ); +void      trap_Cvar_Set( const char *var_name, const char *value ); +float      trap_Cvar_VariableValue( const char *var_name ); +void      trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); +void      trap_Cvar_SetValue( const char *var_name, float value ); +void      trap_Cvar_Reset( const char *name ); +void      trap_Cvar_Create( const char *var_name, const char *var_value, int flags ); +void      trap_Cvar_InfoStringBuffer( int bit, char *buffer, int bufsize ); +int        trap_Argc( void ); +void      trap_Argv( int n, char *buffer, int bufferLength ); +void      trap_Cmd_ExecuteText( int exec_when, const char *text );  // don't use EXEC_NOW! +int        trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t 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 ); +int        trap_FS_GetFileList(  const char *path, const char *extension, char *listbuf, int bufsize ); +int       trap_FS_Seek( fileHandle_t f, long offset, int origin ); // fsOrigin_t +qhandle_t    trap_R_RegisterModel( const char *name ); +qhandle_t    trap_R_RegisterSkin( const char *name ); +qhandle_t    trap_R_RegisterShaderNoMip( const char *name ); +void      trap_R_ClearScene( void ); +void      trap_R_AddRefEntityToScene( const refEntity_t *re ); +void      trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ); +void      trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ); +void      trap_R_RenderScene( const refdef_t *fd ); +void      trap_R_SetColor( const float *rgba ); +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 ); +void      trap_UpdateScreen( void ); +int        trap_CM_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, float frac, const char *tagName ); +void      trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ); +sfxHandle_t    trap_S_RegisterSound( const char *sample, qboolean compressed ); +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 ); +qboolean    trap_Key_IsDown( int keynum ); +qboolean    trap_Key_GetOverstrikeMode( void ); +void      trap_Key_SetOverstrikeMode( qboolean state ); +void      trap_Key_ClearStates( void ); +int        trap_Key_GetCatcher( void ); +void      trap_Key_SetCatcher( int catcher ); +void      trap_GetClipboardData( char *buf, int bufsize ); +void      trap_GetClientState( uiClientState_t *state ); +void      trap_GetGlconfig( glconfig_t *glconfig ); +int        trap_GetConfigString( int index, char* buff, int buffsize ); +int        trap_LAN_GetServerCount( int source ); +void      trap_LAN_GetServerAddressString( int source, int n, char *buf, int buflen ); +void      trap_LAN_GetServerInfo( int source, int n, char *buf, int buflen ); +int        trap_LAN_GetServerPing( int source, int n ); +int        trap_LAN_GetPingQueueCount( void ); +void      trap_LAN_ClearPing( int n ); +void      trap_LAN_GetPing( int n, char *buf, int buflen, int *pingtime ); +void      trap_LAN_GetPingInfo( int n, char *buf, int buflen ); +void      trap_LAN_LoadCachedServers( void ); +void      trap_LAN_SaveCachedServers( void ); +void      trap_LAN_MarkServerVisible(int source, int n, qboolean visible); +int        trap_LAN_ServerIsVisible( int source, int n); +qboolean    trap_LAN_UpdateVisiblePings( int source ); +int        trap_LAN_AddServer(int source, const char *name, const char *addr); +void      trap_LAN_RemoveServer(int source, const char *addr); +void      trap_LAN_ResetPings(int n); +int        trap_LAN_ServerStatus( const char *serverAddress, char *serverStatus, int maxLen ); +int        trap_LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int s2 ); +int        trap_MemoryRemaining( void ); +void      trap_R_RegisterFont(const char *pFontname, int pointSize, fontInfo_t *font); +void      trap_S_StopBackgroundTrack( void ); +void      trap_S_StartBackgroundTrack( const char *intro, const char *loop); +int        trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits); +e_status    trap_CIN_StopCinematic(int handle); +e_status    trap_CIN_RunCinematic (int handle); +void      trap_CIN_DrawCinematic (int handle); +void      trap_CIN_SetExtents (int handle, int x, int y, int w, int h); +int        trap_RealTime(qtime_t *qtime); +void      trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ); + +void      trap_SetPbClStatus( int status ); + +// +// ui_addbots.c +// +void UI_AddBots_Cache( void ); +void UI_AddBotsMenu( void ); + +// +// ui_removebots.c +// +void UI_RemoveBots_Cache( void ); +void UI_RemoveBotsMenu( void ); + +// +// ui_teamorders.c +// +extern void UI_TeamOrdersMenu( void ); +extern void UI_TeamOrdersMenu_f( void ); +extern void UI_TeamOrdersMenu_Cache( void ); + +// +// ui_loadconfig.c +// +void UI_LoadConfig_Cache( void ); +void UI_LoadConfigMenu( void ); + +// +// ui_saveconfig.c +// +void UI_SaveConfigMenu_Cache( void ); +void UI_SaveConfigMenu( void ); + +// +// ui_display.c +// +void UI_DisplayOptionsMenu_Cache( void ); +void UI_DisplayOptionsMenu( void ); + +// +// ui_sound.c +// +void UI_SoundOptionsMenu_Cache( void ); +void UI_SoundOptionsMenu( void ); + +// +// ui_network.c +// +void UI_NetworkOptionsMenu_Cache( void ); +void UI_NetworkOptionsMenu( void ); + +// +// ui_gameinfo.c +// +typedef enum { +  AWARD_ACCURACY, +  AWARD_IMPRESSIVE, +  AWARD_EXCELLENT, +  AWARD_GAUNTLET, +  AWARD_FRAGS, +  AWARD_PERFECT +} awardType_t; + +const char *UI_GetArenaInfoByNumber( int num ); +const char *UI_GetArenaInfoByMap( const char *map ); +const char *UI_GetSpecialArenaInfo( const char *tag ); +int UI_GetNumArenas( void ); +int UI_GetNumSPArenas( void ); +int UI_GetNumSPTiers( void ); + +char *UI_GetBotInfoByNumber( int num ); +char *UI_GetBotInfoByName( const char *name ); +int UI_GetNumBots( void ); +void UI_LoadBots( void ); +char *UI_GetBotNameByNumber( int num ); + +void UI_GetBestScore( int level, int *score, int *skill ); +void UI_SetBestScore( int level, int score ); +int UI_TierCompleted( int levelWon ); +qboolean UI_ShowTierVideo( int tier ); +qboolean UI_CanShowTierVideo( int tier ); +int  UI_GetCurrentGame( void ); +void UI_NewGame( void ); +void UI_LogAwardData( int award, int data ); +int UI_GetAwardLevel( int award ); + +void UI_SPUnlock_f( void ); +void UI_SPUnlockMedals_f( void ); + +void UI_InitGameinfo( void ); + +// +// ui_login.c +// +void Login_Cache( void ); +void UI_LoginMenu( void ); + +// +// ui_signup.c +// +void Signup_Cache( void ); +void UI_SignupMenu( void ); + +// +// ui_rankstatus.c +// +void RankStatus_Cache( void ); +void UI_RankStatusMenu( void ); + + +// new ui + +#define ASSET_BACKGROUND "uiBackground" + +// for tracking sp game info in Team Arena +typedef struct postGameInfo_s { +  int score; +  int redScore; +  int blueScore; +  int perfects; +  int accuracy; +  int impressives; +  int excellents; +  int defends; +  int assists; +  int gauntlets; +  int  captures; +  int time; +  int timeBonus; +  int shutoutBonus; +  int skillBonus; +  int baseScore; +} postGameInfo_t; + + + +#endif diff --git a/src/ui/ui_main.c b/src/ui/ui_main.c new file mode 100644 index 0000000..b193f4f --- /dev/null +++ b/src/ui/ui_main.c @@ -0,0 +1,6500 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +/* +======================================================================= + +USER INTERFACE MAIN + +======================================================================= +*/ + +// use this to get a demo build without an explicit demo build, i.e. to get the demo ui files to build +//#define PRE_RELEASE_TADEMO + +#include "ui_local.h" + +uiInfo_t uiInfo; + +static const char *MonthAbbrev[] = { +  "Jan","Feb","Mar", +  "Apr","May","Jun", +  "Jul","Aug","Sep", +  "Oct","Nov","Dec" +}; + + +static const char *skillLevels[] = { +  "I Can Win", +  "Bring It On", +  "Hurt Me Plenty", +  "Hardcore", +  "Nightmare" +}; + +static const int numSkillLevels = sizeof(skillLevels) / sizeof(const char*); + + +static const char *netSources[] = { +  "LAN", +  "Mplayer", +  "Internet", +  "Favorites" +}; +static const int numNetSources = sizeof(netSources) / sizeof(const char*); + +static const serverFilter_t serverFilters[] = { +  {"All", "" }, +  {"Quake 3 Arena", "" }, +  {"Team Arena", "missionpack" }, +  {"Rocket Arena", "arena" }, +  {"Alliance", "alliance20" }, +  {"Weapons Factory Arena", "wfa" }, +  {"OSP", "osp" }, +}; + +static const char *teamArenaGameTypes[] = { +  "FFA", +  "TOURNAMENT", +  "SP", +  "TEAM DM", +  "CTF", +  "1FCTF", +  "OVERLOAD", +  "HARVESTER", +  "TEAMTOURNAMENT" +}; + +static int const numTeamArenaGameTypes = sizeof(teamArenaGameTypes) / sizeof(const char*); + + +static const char *teamArenaGameNames[] = { +  "Free For All", +  "Tournament", +  "Single Player", +  "Team Deathmatch", +  "Capture the Flag", +  "One Flag CTF", +  "Overload", +  "Harvester", +  "Team Tournament", +}; + +static int const numTeamArenaGameNames = sizeof(teamArenaGameNames) / sizeof(const char*); + + +static const int numServerFilters = sizeof(serverFilters) / sizeof(serverFilter_t); + +static const char *sortKeys[] = { +  "Server Name", +  "Map Name", +  "Open Player Spots", +  "Game Type", +  "Ping Time" +}; +static const int numSortKeys = sizeof(sortKeys) / sizeof(const char*); + +static char* netnames[] = { +  "???", +  "UDP", +  "IPX", +  NULL +}; + +static int gamecodetoui[] = {4,2,3,0,5,1,6}; + + +static void UI_StartServerRefresh(qboolean full); +static void UI_StopServerRefresh( void ); +static void UI_DoServerRefresh( void ); +static void UI_FeederSelection(float feederID, int index); +static void UI_BuildServerDisplayList(qboolean force); +static void UI_BuildServerStatus(qboolean force); +static void UI_BuildFindPlayerList(qboolean force); +static int QDECL UI_ServersQsortCompare( const void *arg1, const void *arg2 ); +static int UI_MapCountByGameType(qboolean singlePlayer); +static int UI_HeadCountByTeam( void ); +static const char *UI_SelectedMap(int index, int *actual); +static const char *UI_SelectedHead(int index, int *actual); +static int UI_GetIndexFromSelection(int actual); + +int ProcessNewUI( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6 ); + +/* +================ +vmMain + +This is the only way control passes into the module. +This must be the very first function compiled into the .qvm file +================ +*/ +vmCvar_t  ui_new; +vmCvar_t  ui_debug; +vmCvar_t  ui_initialized; +vmCvar_t  ui_teamArenaFirstRun; + +void _UI_Init( qboolean ); +void _UI_Shutdown( void ); +void _UI_KeyEvent( int key, qboolean down ); +void _UI_MouseEvent( int dx, int dy ); +void _UI_Refresh( int realtime ); +qboolean _UI_IsFullscreen( void ); +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  ) { +  switch ( command ) { +    case UI_GETAPIVERSION: +      return UI_API_VERSION; + +    case UI_INIT: +      _UI_Init(arg0); +      return 0; + +    case UI_SHUTDOWN: +      _UI_Shutdown(); +      return 0; + +    case UI_KEY_EVENT: +      _UI_KeyEvent( arg0, arg1 ); +      return 0; + +    case UI_MOUSE_EVENT: +      _UI_MouseEvent( arg0, arg1 ); +      return 0; + +    case UI_REFRESH: +      _UI_Refresh( arg0 ); +      return 0; + +    case UI_IS_FULLSCREEN: +      return _UI_IsFullscreen(); + +    case UI_SET_ACTIVE_MENU: +      _UI_SetActiveMenu( arg0 ); +      return 0; + +    case UI_CONSOLE_COMMAND: +      return UI_ConsoleCommand(arg0); + +    case UI_DRAW_CONNECT_SCREEN: +      UI_DrawConnectScreen( arg0 ); +      return 0; +  } + +  return -1; +} + + + +void AssetCache( void ) { +  uiInfo.uiDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( ASSET_GRADIENTBAR ); +  uiInfo.uiDC.Assets.scrollBar = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR ); +  uiInfo.uiDC.Assets.scrollBarArrowDown = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWDOWN ); +  uiInfo.uiDC.Assets.scrollBarArrowUp = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWUP ); +  uiInfo.uiDC.Assets.scrollBarArrowLeft = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWLEFT ); +  uiInfo.uiDC.Assets.scrollBarArrowRight = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWRIGHT ); +  uiInfo.uiDC.Assets.scrollBarThumb = trap_R_RegisterShaderNoMip( ASSET_SCROLL_THUMB ); +  uiInfo.uiDC.Assets.sliderBar = trap_R_RegisterShaderNoMip( ASSET_SLIDER_BAR ); +  uiInfo.uiDC.Assets.sliderThumb = trap_R_RegisterShaderNoMip( ASSET_SLIDER_THUMB ); +} + +void _UI_DrawSides(float x, float y, float w, float h, float size) { +  UI_AdjustFrom640( &x, &y, &w, &h ); +  size *= uiInfo.uiDC.xscale; +  trap_R_DrawStretchPic( x, y, size, h, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); +  trap_R_DrawStretchPic( x + w - size, y, size, h, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); +} + +void _UI_DrawTopBottom(float x, float y, float w, float h, float size) { +  UI_AdjustFrom640( &x, &y, &w, &h ); +  size *= uiInfo.uiDC.yscale; +  trap_R_DrawStretchPic( x, y, w, size, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); +  trap_R_DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); +} +/* +================ +UI_DrawRect + +Coordinates are 640*480 virtual values +================= +*/ +void _UI_DrawRect( float x, float y, float width, float height, float size, const float *color ) { +  trap_R_SetColor( color ); + +  _UI_DrawTopBottom(x, y, width, height, size); +  _UI_DrawSides(x, y, width, height, size); + +  trap_R_SetColor( NULL ); +} + + + + +int Text_Width(const char *text, float scale, int limit) { +  int count,len; +  float out; +  glyphInfo_t *glyph; +  float useScale; +  const char *s = text; +  fontInfo_t *font = &uiInfo.uiDC.Assets.textFont; +  if (scale <= ui_smallFont.value) { +    font = &uiInfo.uiDC.Assets.smallFont; +  } else if (scale >= ui_bigFont.value) { +    font = &uiInfo.uiDC.Assets.bigFont; +  } +  useScale = scale * font->glyphScale; +  out = 0; +  if (text) { +    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]; +        out += glyph->xSkip; +        s++; +        count++; +      } +    } +  } +  return out * useScale; +} + +int Text_Height(const char *text, float scale, int limit) { +  int len, count; +  float max; +  glyphInfo_t *glyph; +  float useScale; +  const char *s = text; // bk001206 - unsigned +  fontInfo_t *font = &uiInfo.uiDC.Assets.textFont; +  if (scale <= ui_smallFont.value) { +    font = &uiInfo.uiDC.Assets.smallFont; +  } else if (scale >= ui_bigFont.value) { +    font = &uiInfo.uiDC.Assets.bigFont; +  } +  useScale = scale * font->glyphScale; +  max = 0; +  if (text) { +    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]; +        if (max < glyph->height) { +          max = glyph->height; +        } +        s++; +        count++; +      } +    } +  } +  return max * useScale; +} + +void 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; +  UI_AdjustFrom640( &x, &y, &w, &h ); +  trap_R_DrawStretchPic( x, y, w, h, s, t, s2, t2, hShader ); +} + +void 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 = &uiInfo.uiDC.Assets.textFont; +  if (scale <= ui_smallFont.value) { +    font = &uiInfo.uiDC.Assets.smallFont; +  } else if (scale >= ui_bigFont.value) { +    font = &uiInfo.uiDC.Assets.bigFont; +  } +  useScale = scale * font->glyphScale; +  if (text) { +    const char *s = text; // bk001206 - unsigned +    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]; +      //int yadj = Assets.textFont.glyphs[text[i]].bottom + Assets.textFont.glyphs[text[i]].top; +      //float yadj = scale * (Assets.textFont.glyphs[text[i]].imageHeight - Assets.textFont.glyphs[text[i]].height); +      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 ); +          Text_PaintChar(x + ofs, y - yadj + ofs, +                            glyph->imageWidth, +                            glyph->imageHeight, +                            useScale, +                            glyph->s, +                            glyph->t, +                            glyph->s2, +                            glyph->t2, +                            glyph->glyph); +          trap_R_SetColor( newColor ); +          colorBlack[3] = 1.0; +        } +        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 ); +          Text_PaintChar( x - 1.5, y - yadj - 1.5, +                          glyph->imageWidth + 3, +                          glyph->imageHeight + 3, +                          useScale, +                          glyph->s, +                          glyph->t, +                          glyph->s2, +                          glyph->t2, +                          glyph->glyph ); + +          trap_R_SetColor( outer ); +          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 ); +          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 ); +        } + +        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; +        s++; +        count++; +      } +    } +    trap_R_SetColor( NULL ); +  } +} + +void Text_PaintWithCursor(float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style) { +  int len, count; +  vec4_t newColor; +  glyphInfo_t *glyph, *glyph2; +  float yadj; +  float useScale; +  fontInfo_t *font = &uiInfo.uiDC.Assets.textFont; +  if (scale <= ui_smallFont.value) { +    font = &uiInfo.uiDC.Assets.smallFont; +  } else if (scale >= ui_bigFont.value) { +    font = &uiInfo.uiDC.Assets.bigFont; +  } +  useScale = scale * font->glyphScale; +  if (text) { +    const char *s = text; // bk001206 - unsigned +    trap_R_SetColor( color ); +    memcpy(&newColor[0], &color[0], sizeof(vec4_t)); +    len = strlen(text); +    if (limit > 0 && len > limit) { +      len = limit; +    } +    count = 0; +    glyph2 = &font->glyphs[ (int) cursor]; // bk001206 - possible signed char +    while (s && *s && count < len) { +      glyph = &font->glyphs[(int)*s]; +      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 { +        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 ); +          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 ); +          Text_PaintChar( x - 1.5, y - yadj - 1.5, +                          glyph->imageWidth + 3, +                          glyph->imageHeight + 3, +                          useScale, +                          glyph->s, +                          glyph->t, +                          glyph->s2, +                          glyph->t2, +                          glyph->glyph ); + +          trap_R_SetColor( outer ); +          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 ); +          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 ); +        } + +        Text_PaintChar(x, y - yadj, +                          glyph->imageWidth, +                          glyph->imageHeight, +                          useScale, +                          glyph->s, +                          glyph->t, +                          glyph->s2, +                          glyph->t2, +                          glyph->glyph); + +        // CG_DrawPic(x, y - yadj, scale * uiDC.Assets.textFont.glyphs[text[i]].imageWidth, scale * uiDC.Assets.textFont.glyphs[text[i]].imageHeight, uiDC.Assets.textFont.glyphs[text[i]].glyph); +        yadj = useScale * glyph2->top; +        if (count == cursorPos && !((uiInfo.uiDC.realTime/BLINK_DIVISOR) & 1)) { +          Text_PaintChar(x, y - yadj, +                            glyph2->imageWidth, +                            glyph2->imageHeight, +                            useScale, +                            glyph2->s, +                            glyph2->t, +                            glyph2->s2, +                            glyph2->t2, +                            glyph2->glyph); +        } + +        x += (glyph->xSkip * useScale); +        s++; +        count++; +      } +    } +    // need to paint cursor at end of text +    if (cursorPos == len && !((uiInfo.uiDC.realTime/BLINK_DIVISOR) & 1)) { +        yadj = useScale * glyph2->top; +        Text_PaintChar(x, y - yadj, +                          glyph2->imageWidth, +                          glyph2->imageHeight, +                          useScale, +                          glyph2->s, +                          glyph2->t, +                          glyph2->s2, +                          glyph2->t2, +                          glyph2->glyph); + +    } + + +    trap_R_SetColor( NULL ); +  } +} + + +static void 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; +  if (text) { +    const char *s = text; // bk001206 - unsigned +    float max = *maxX; +    float useScale; +    fontInfo_t *font = &uiInfo.uiDC.Assets.textFont; +    if (scale <= ui_smallFont.value) { +      font = &uiInfo.uiDC.Assets.smallFont; +    } else if (scale > ui_bigFont.value) { +      font = &uiInfo.uiDC.Assets.bigFont; +    } +    useScale = scale * font->glyphScale; +    trap_R_SetColor( color ); +    len = strlen(text); +    if (limit > 0 && len > limit) { +      len = limit; +    } +    count = 0; +    while (s && *s && count < len) { +      glyph = &font->glyphs[(int)*s]; +      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 (Text_Width(s, useScale, 1) + x > max) { +          *maxX = 0; +          break; +        } +        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++; +      } +    } +    trap_R_SetColor( NULL ); +  } + +} + + +void UI_ShowPostGame(qboolean newHigh) { +  trap_Cvar_Set ("cg_cameraOrbit", "0"); +  trap_Cvar_Set("cg_thirdPerson", "0"); +  trap_Cvar_Set( "sv_killserver", "1" ); +  uiInfo.soundHighScore = newHigh; +  _UI_SetActiveMenu(UIMENU_POSTGAME); +} +/* +================= +_UI_Refresh +================= +*/ + +void UI_DrawCenteredPic(qhandle_t image, int w, int h) { +  int x, y; +  x = (SCREEN_WIDTH - w) / 2; +  y = (SCREEN_HEIGHT - h) / 2; +  UI_DrawHandlePic(x, y, w, h, image); +} + +int frameCount = 0; +int startTime; + +#define UI_FPS_FRAMES 4 +void _UI_Refresh( int realtime ) +{ +  static int index; +  static int  previousTimes[UI_FPS_FRAMES]; + +  //if ( !( trap_Key_GetCatcher() & KEYCATCH_UI ) ) { +  //  return; +  //} + +  uiInfo.uiDC.frameTime = realtime - uiInfo.uiDC.realTime; +  uiInfo.uiDC.realTime = realtime; + +  previousTimes[index % UI_FPS_FRAMES] = uiInfo.uiDC.frameTime; +  index++; +  if ( index > UI_FPS_FRAMES ) { +    int i, total; +    // average multiple frames together to smooth changes out a bit +    total = 0; +    for ( i = 0 ; i < UI_FPS_FRAMES ; i++ ) { +      total += previousTimes[i]; +    } +    if ( !total ) { +      total = 1; +    } +    uiInfo.uiDC.FPS = 1000 * UI_FPS_FRAMES / total; +  } + + + +  UI_UpdateCvars(); + +  if (Menu_Count() > 0) { +    // paint all the menus +    Menu_PaintAll(); +    // refresh server browser list +    UI_DoServerRefresh(); +    // refresh server status +    UI_BuildServerStatus(qfalse); +    // refresh find player list +    UI_BuildFindPlayerList(qfalse); +  } + +  // draw cursor +  UI_SetColor( NULL ); + +  //TA: don't draw the cursor whilst loading +  if( Menu_Count( ) > 0 && !trap_Cvar_VariableValue( "ui_loading" ) ) +    UI_DrawHandlePic( uiInfo.uiDC.cursorx-16, uiInfo.uiDC.cursory-16, 32, 32, uiInfo.uiDC.Assets.cursor); + +#ifndef NDEBUG +  if (uiInfo.uiDC.debug) +  { +    // cursor coordinates +    //FIXME +    //UI_DrawString( 0, 0, va("(%d,%d)",uis.cursorx,uis.cursory), UI_LEFT|UI_SMALLFONT, colorRed ); +  } +#endif + +} + +/* +================= +_UI_Shutdown +================= +*/ +void _UI_Shutdown( void ) { +  trap_LAN_SaveCachedServers(); +} + +char *defaultMenu = NULL; + +char *GetMenuBuffer(const char *filename) { +  int len; +  fileHandle_t  f; +  static char buf[MAX_MENUFILE]; + +  len = trap_FS_FOpenFile( filename, &f, FS_READ ); +  if ( !f ) { +    trap_Print( va( S_COLOR_RED "menu file not found: %s, using default\n", filename ) ); +    return defaultMenu; +  } +  if ( len >= MAX_MENUFILE ) { +    trap_Print( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", filename, len, MAX_MENUFILE ) ); +    trap_FS_FCloseFile( f ); +    return defaultMenu; +  } + +  trap_FS_Read( buf, len, f ); +  buf[len] = 0; +  trap_FS_FCloseFile( f ); +  //COM_Compress(buf); +  return buf; + +} + +qboolean Asset_Parse(int handle) { +  pc_token_t token; +  const char *tempStr; + +  if (!trap_Parse_ReadToken(handle, &token)) +    return qfalse; +  if (Q_stricmp(token.string, "{") != 0) { +    return qfalse; +  } + +  while ( 1 ) { + +    memset(&token, 0, sizeof(pc_token_t)); + +    if (!trap_Parse_ReadToken(handle, &token)) +      return qfalse; + +    if (Q_stricmp(token.string, "}") == 0) { +      return qtrue; +    } + +    // font +    if (Q_stricmp(token.string, "font") == 0) { +      int pointSize; +      if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle,&pointSize)) { +        return qfalse; +      } +      trap_R_RegisterFont(tempStr, pointSize, &uiInfo.uiDC.Assets.textFont); +      uiInfo.uiDC.Assets.fontRegistered = qtrue; +      continue; +    } + +    if (Q_stricmp(token.string, "smallFont") == 0) { +      int pointSize; +      if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle,&pointSize)) { +        return qfalse; +      } +      trap_R_RegisterFont(tempStr, pointSize, &uiInfo.uiDC.Assets.smallFont); +      continue; +    } + +    if (Q_stricmp(token.string, "bigFont") == 0) { +      int pointSize; +      if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle,&pointSize)) { +        return qfalse; +      } +      trap_R_RegisterFont(tempStr, pointSize, &uiInfo.uiDC.Assets.bigFont); +      continue; +    } + + +    // gradientbar +    if (Q_stricmp(token.string, "gradientbar") == 0) { +      if (!PC_String_Parse(handle, &tempStr)) { +        return qfalse; +      } +      uiInfo.uiDC.Assets.gradientBar = trap_R_RegisterShaderNoMip(tempStr); +      continue; +    } + +    // enterMenuSound +    if (Q_stricmp(token.string, "menuEnterSound") == 0) { +      if (!PC_String_Parse(handle, &tempStr)) { +        return qfalse; +      } +      uiInfo.uiDC.Assets.menuEnterSound = trap_S_RegisterSound( tempStr, qfalse ); +      continue; +    } + +    // exitMenuSound +    if (Q_stricmp(token.string, "menuExitSound") == 0) { +      if (!PC_String_Parse(handle, &tempStr)) { +        return qfalse; +      } +      uiInfo.uiDC.Assets.menuExitSound = trap_S_RegisterSound( tempStr, qfalse ); +      continue; +    } + +    // itemFocusSound +    if (Q_stricmp(token.string, "itemFocusSound") == 0) { +      if (!PC_String_Parse(handle, &tempStr)) { +        return qfalse; +      } +      uiInfo.uiDC.Assets.itemFocusSound = trap_S_RegisterSound( tempStr, qfalse ); +      continue; +    } + +    // menuBuzzSound +    if (Q_stricmp(token.string, "menuBuzzSound") == 0) { +      if (!PC_String_Parse(handle, &tempStr)) { +        return qfalse; +      } +      uiInfo.uiDC.Assets.menuBuzzSound = trap_S_RegisterSound( tempStr, qfalse ); +      continue; +    } + +    if (Q_stricmp(token.string, "cursor") == 0) { +      if (!PC_String_Parse(handle, &uiInfo.uiDC.Assets.cursorStr)) { +        return qfalse; +      } +      uiInfo.uiDC.Assets.cursor = trap_R_RegisterShaderNoMip( uiInfo.uiDC.Assets.cursorStr); +      continue; +    } + +    if (Q_stricmp(token.string, "fadeClamp") == 0) { +      if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.fadeClamp)) { +        return qfalse; +      } +      continue; +    } + +    if (Q_stricmp(token.string, "fadeCycle") == 0) { +      if (!PC_Int_Parse(handle, &uiInfo.uiDC.Assets.fadeCycle)) { +        return qfalse; +      } +      continue; +    } + +    if (Q_stricmp(token.string, "fadeAmount") == 0) { +      if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.fadeAmount)) { +        return qfalse; +      } +      continue; +    } + +    if (Q_stricmp(token.string, "shadowX") == 0) { +      if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.shadowX)) { +        return qfalse; +      } +      continue; +    } + +    if (Q_stricmp(token.string, "shadowY") == 0) { +      if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.shadowY)) { +        return qfalse; +      } +      continue; +    } + +    if (Q_stricmp(token.string, "shadowColor") == 0) { +      if (!PC_Color_Parse(handle, &uiInfo.uiDC.Assets.shadowColor)) { +        return qfalse; +      } +      uiInfo.uiDC.Assets.shadowFadeClamp = uiInfo.uiDC.Assets.shadowColor[3]; +      continue; +    } + +  } +  return qfalse; +} + +void Font_Report( void ) { +  int i; +  Com_Printf("Font Info\n"); +  Com_Printf("=========\n"); +  for ( i = 32; i < 96; i++) { +    Com_Printf("Glyph handle %i: %i\n", i, uiInfo.uiDC.Assets.textFont.glyphs[i].glyph); +  } +} + +void UI_Report( void ) { +  String_Report(); +  //Font_Report(); + +} + +void UI_ParseMenu(const char *menuFile) { +  int handle; +  pc_token_t token; + +  /*Com_Printf("Parsing menu file:%s\n", menuFile);*/ + +  handle = trap_Parse_LoadSource(menuFile); +  if (!handle) { +    return; +  } + +  while ( 1 ) { +    memset(&token, 0, sizeof(pc_token_t)); +    if (!trap_Parse_ReadToken( handle, &token )) { +      break; +    } + +    //if ( Q_stricmp( token, "{" ) ) { +    //  Com_Printf( "Missing { in menu file\n" ); +    //  break; +    //} + +    //if ( menuCount == MAX_MENUS ) { +    //  Com_Printf( "Too many menus!\n" ); +    //  break; +    //} + +    if ( token.string[0] == '}' ) { +      break; +    } + +    if (Q_stricmp(token.string, "assetGlobalDef") == 0) { +      if (Asset_Parse(handle)) { +        continue; +      } else { +        break; +      } +    } + +    if (Q_stricmp(token.string, "menudef") == 0) { +      // start a new menu +      Menu_New(handle); +    } +  } +  trap_Parse_FreeSource(handle); +} + +/* +=============== +UI_FindInfoPaneByName +=============== +*/ +tremInfoPane_t *UI_FindInfoPaneByName( const char *name ) +{ +  int i; + +  for( i = 0; i < uiInfo.tremInfoPaneCount; i++ ) +  { +    if( !Q_stricmp( uiInfo.tremInfoPanes[ i ].name, name ) ) +      return &uiInfo.tremInfoPanes[ i ]; +  } + +  //create a dummy infopane demanding the user write the infopane +  uiInfo.tremInfoPanes[ i ].name = String_Alloc( name ); +  strncpy( uiInfo.tremInfoPanes[ i ].text, "Not implemented.\n\nui/infopanes.def\n", MAX_INFOPANE_TEXT ); +  Q_strcat( uiInfo.tremInfoPanes[ i ].text, MAX_INFOPANE_TEXT, String_Alloc( name ) ); + +  uiInfo.tremInfoPaneCount++; + +  return &uiInfo.tremInfoPanes[ i ]; +} + +/* +=============== +UI_LoadInfoPane +=============== +*/ +qboolean UI_LoadInfoPane( int handle ) +{ +  pc_token_t  token; +  qboolean    valid = qfalse; + +  while( 1 ) +  { +    memset( &token, 0, sizeof( pc_token_t ) ); + +    if( !trap_Parse_ReadToken( handle, &token ) ) +      break; + +    if( !Q_stricmp( token.string, "name" ) ) +    { +      memset( &token, 0, sizeof( pc_token_t ) ); + +      if( !trap_Parse_ReadToken( handle, &token ) ) +        break; + +      uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].name = String_Alloc( token.string ); +      valid = qtrue; +    } +    else if( !Q_stricmp( token.string, "graphic" ) ) +    { +      int *graphic; + +      memset( &token, 0, sizeof( pc_token_t ) ); + +      if( !trap_Parse_ReadToken( handle, &token ) ) +        break; + +      graphic = &uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].numGraphics; + +      if( !Q_stricmp( token.string, "top" ) ) +        uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].side = INFOPANE_TOP; +      else if( !Q_stricmp( token.string, "bottom" ) ) +        uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].side = INFOPANE_BOTTOM; +      else if( !Q_stricmp( token.string, "left" ) ) +        uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].side = INFOPANE_LEFT; +      else if( !Q_stricmp( token.string, "right" ) ) +        uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].side = INFOPANE_RIGHT; +      else +        break; + +      memset( &token, 0, sizeof( pc_token_t ) ); + +      if( !trap_Parse_ReadToken( handle, &token ) ) +        break; + +      if( !Q_stricmp( token.string, "center" ) ) +        uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].offset = -1; +      else +        uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].offset = token.intvalue; + +      memset( &token, 0, sizeof( pc_token_t ) ); + +      if( !trap_Parse_ReadToken( handle, &token ) ) +        break; + +      uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].graphic = +        trap_R_RegisterShaderNoMip( token.string ); + +      memset( &token, 0, sizeof( pc_token_t ) ); + +      if( !trap_Parse_ReadToken( handle, &token ) ) +        break; + +      uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].width = token.intvalue; + +      memset( &token, 0, sizeof( pc_token_t ) ); + +      if( !trap_Parse_ReadToken( handle, &token ) ) +        break; + +      uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].height = token.intvalue; + +      //increment graphics +      (*graphic)++; + +      if( *graphic == MAX_INFOPANE_GRAPHICS ) +        break; +    } +    else if( !Q_stricmp( token.string, "text" ) ) +    { +      memset( &token, 0, sizeof( pc_token_t ) ); + +      if( !trap_Parse_ReadToken( handle, &token ) ) +        break; + +      Q_strcat( uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].text, MAX_INFOPANE_TEXT, token.string ); +    } +    else if( !Q_stricmp( token.string, "align" ) ) +    { +      memset( &token, 0, sizeof( pc_token_t ) ); + +      if( !trap_Parse_ReadToken( handle, &token ) ) +        break; + +      if( !Q_stricmp( token.string, "left" ) ) +        uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].align = ITEM_ALIGN_LEFT; +      else if( !Q_stricmp( token.string, "right" ) ) +        uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].align = ITEM_ALIGN_RIGHT; +      else if( !Q_stricmp( token.string, "center" ) ) +        uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].align = ITEM_ALIGN_CENTER; +    } +    else if( token.string[ 0 ] == '}' ) +    { +      //reached the end, break +      break; +    } +    else +      break; +  } + +  if( valid ) +  { +    uiInfo.tremInfoPaneCount++; +    return qtrue; +  } +  else +  { +    return qfalse; +  } +} + +/* +=============== +UI_LoadInfoPanes +=============== +*/ +void UI_LoadInfoPanes( const char *file ) +{ +  pc_token_t token; +  int handle; +  int count; + +  uiInfo.tremInfoPaneCount = count = 0; + +  handle = trap_Parse_LoadSource( file ); + +  if( !handle ) +  { +    trap_Error( va( S_COLOR_YELLOW "infopane file not found: %s\n", file ) ); +    return; +  } + +  while( 1 ) +  { +    if( !trap_Parse_ReadToken( handle, &token ) ) +      break; + +    if( token.string[ 0 ] == 0 ) +      break; + +    if( token.string[ 0 ] == '{' ) +    { +      if( UI_LoadInfoPane( handle ) ) +        count++; + +      if( count == MAX_INFOPANES ) +        break; +    } +  } + +  trap_Parse_FreeSource( handle ); +} + +qboolean Load_Menu(int handle) { +  pc_token_t token; + +  if (!trap_Parse_ReadToken(handle, &token)) +    return qfalse; +  if (token.string[0] != '{') { +    return qfalse; +  } + +  while ( 1 ) { + +    if (!trap_Parse_ReadToken(handle, &token)) +      return qfalse; + +    if ( token.string[0] == 0 ) { +      return qfalse; +    } + +    if ( token.string[0] == '}' ) { +      return qtrue; +    } + +    UI_ParseMenu(token.string); +  } +  return qfalse; +} + +void UI_LoadMenus(const char *menuFile, qboolean reset) { +  pc_token_t token; +  int handle; +  int start; + +  start = trap_Milliseconds(); + +  handle = trap_Parse_LoadSource( menuFile ); +  if (!handle) { +    trap_Error( va( S_COLOR_YELLOW "menu file not found: %s, using default\n", menuFile ) ); +    handle = trap_Parse_LoadSource( "ui/menus.txt" ); +    if (!handle) { +      trap_Error( va( S_COLOR_RED "default menu file not found: ui/menus.txt, unable to continue!\n" ) ); +    } +  } + +  ui_new.integer = 1; + +  if (reset) { +    Menu_Reset(); +  } + +  while ( 1 ) { +    if (!trap_Parse_ReadToken(handle, &token)) +      break; +    if( token.string[0] == 0 || token.string[0] == '}') { +      break; +    } + +    if ( token.string[0] == '}' ) { +      break; +    } + +    if (Q_stricmp(token.string, "loadmenu") == 0) { +      if (Load_Menu(handle)) { +        continue; +      } else { +        break; +      } +    } +  } + +  Com_Printf("UI menu load time = %d milli seconds\n", trap_Milliseconds() - start); + +  trap_Parse_FreeSource( handle ); +} + +void UI_Load( void ) { +  char lastName[1024]; +  menuDef_t *menu = Menu_GetFocused(); +  char *menuSet = UI_Cvar_VariableString("ui_menuFiles"); +  if (menu && menu->window.name) { +    strcpy(lastName, menu->window.name); +  } +  if (menuSet == NULL || menuSet[0] == '\0') { +    menuSet = "ui/menus.txt"; +  } + +  String_Init(); + +/*  UI_ParseGameInfo("gameinfo.txt"); +  UI_LoadArenas();*/ + +  UI_LoadMenus(menuSet, qtrue); +  Menus_CloseAll(); +  Menus_ActivateByName(lastName); + +} + +static const char *handicapValues[] = {"None","95","90","85","80","75","70","65","60","55","50","45","40","35","30","25","20","15","10","5",NULL}; + +static void UI_DrawHandicap(rectDef_t *rect, float scale, vec4_t color, int textStyle) { +  int i, h; + +  h = Com_Clamp( 5, 100, trap_Cvar_VariableValue("handicap") ); +  i = 20 - h / 5; + +  Text_Paint(rect->x, rect->y, scale, color, handicapValues[i], 0, 0, textStyle); +} + +static void UI_DrawClanName(rectDef_t *rect, float scale, vec4_t color, int textStyle) { +  Text_Paint(rect->x, rect->y, scale, color, UI_Cvar_VariableString("ui_teamName"), 0, 0, textStyle); +} + + +static void UI_SetCapFragLimits(qboolean uiVars) { +  int cap = 5; +  int frag = 10; +  if (uiVars) { +    trap_Cvar_Set("ui_captureLimit", va("%d", cap)); +    trap_Cvar_Set("ui_fragLimit", va("%d", frag)); +  } else { +    trap_Cvar_Set("capturelimit", va("%d", cap)); +    trap_Cvar_Set("fraglimit", va("%d", frag)); +  } +} +// ui_gameType assumes gametype 0 is -1 ALL and will not show +static void UI_DrawGameType(rectDef_t *rect, float scale, vec4_t color, int textStyle) { +  Text_Paint(rect->x, rect->y, scale, color, uiInfo.gameTypes[ui_gameType.integer].gameType, 0, 0, textStyle); +} + +static void UI_DrawNetGameType(rectDef_t *rect, float scale, vec4_t color, int textStyle) { +  if (ui_netGameType.integer < 0 || ui_netGameType.integer > uiInfo.numGameTypes) { +    trap_Cvar_Set("ui_netGameType", "0"); +    trap_Cvar_Set("ui_actualNetGameType", "0"); +  } +  Text_Paint(rect->x, rect->y, scale, color, uiInfo.gameTypes[ui_netGameType.integer].gameType , 0, 0, textStyle); +} + +static void UI_DrawJoinGameType(rectDef_t *rect, float scale, vec4_t color, int textStyle) { +  if (ui_joinGameType.integer < 0 || ui_joinGameType.integer > uiInfo.numJoinGameTypes) { +    trap_Cvar_Set("ui_joinGameType", "0"); +  } +  Text_Paint(rect->x, rect->y, scale, color, uiInfo.joinGameTypes[ui_joinGameType.integer].gameType , 0, 0, textStyle); +} + + + +static int UI_TeamIndexFromName(const char *name) { +  int i; + +  if (name && *name) { +    for (i = 0; i < uiInfo.teamCount; i++) { +      if (Q_stricmp(name, uiInfo.teamList[i].teamName) == 0) { +        return i; +      } +    } +  } + +  return 0; + +} + +static void UI_DrawClanLogo(rectDef_t *rect, float scale, vec4_t color) { +  int i; +  i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); +  if (i >= 0 && i < uiInfo.teamCount) { +    trap_R_SetColor( color ); + +    if (uiInfo.teamList[i].teamIcon == -1) { +      uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName); +      uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName)); +      uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName)); +    } + +    UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon); +    trap_R_SetColor(NULL); +  } +} + +static void UI_DrawClanCinematic(rectDef_t *rect, float scale, vec4_t color) { +  int i; +  i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); +  if (i >= 0 && i < uiInfo.teamCount) { + +    if (uiInfo.teamList[i].cinematic >= -2) { +      if (uiInfo.teamList[i].cinematic == -1) { +        uiInfo.teamList[i].cinematic = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.teamList[i].imageName), 0, 0, 0, 0, (CIN_loop | CIN_silent) ); +      } +      if (uiInfo.teamList[i].cinematic >= 0) { +        trap_CIN_RunCinematic(uiInfo.teamList[i].cinematic); +        trap_CIN_SetExtents(uiInfo.teamList[i].cinematic, rect->x, rect->y, rect->w, rect->h); +        trap_CIN_DrawCinematic(uiInfo.teamList[i].cinematic); +      } else { +          trap_R_SetColor( color ); +        UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Metal); +        trap_R_SetColor(NULL); +        uiInfo.teamList[i].cinematic = -2; +      } +    } else { +      trap_R_SetColor( color ); +      UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon); +      trap_R_SetColor(NULL); +    } +  } + +} + +static void UI_DrawPreviewCinematic(rectDef_t *rect, float scale, vec4_t color) { +  if (uiInfo.previewMovie > -2) { +    uiInfo.previewMovie = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.movieList[uiInfo.movieIndex]), 0, 0, 0, 0, (CIN_loop | CIN_silent) ); +    if (uiInfo.previewMovie >= 0) { +      trap_CIN_RunCinematic(uiInfo.previewMovie); +      trap_CIN_SetExtents(uiInfo.previewMovie, rect->x, rect->y, rect->w, rect->h); +      trap_CIN_DrawCinematic(uiInfo.previewMovie); +    } else { +      uiInfo.previewMovie = -2; +    } +  } + +} + + +#define GRAPHIC_BWIDTH  8.0f +/* +=============== +UI_DrawInfoPane +=============== +*/ +static void UI_DrawInfoPane( tremInfoPane_t *pane, rectDef_t *rect, float text_x, float text_y, +                             float scale, vec4_t color, int textStyle ) +{ +  int       i; +  float     maxLeft = 0, maxTop = 0; +  float     maxRight = 0, maxBottom = 0; +  float     x = rect->x - text_x, y = rect->y - text_y, w, h; +  float     xoffset = 0, yoffset = 0; +  menuDef_t dummyParent; +  itemDef_t textItem; + +  //iterate through graphics +  for( i = 0; i < pane->numGraphics; i++ ) +  { +    float width         = pane->graphics[ i ].width; +    float height        = pane->graphics[ i ].height; +    qhandle_t graphic = pane->graphics[ i ].graphic; + +    if( pane->graphics[ i ].side == INFOPANE_TOP || pane->graphics[ i ].side == INFOPANE_BOTTOM ) +    { +      //set horizontal offset of graphic +      if( pane->graphics[ i ].offset < 0 ) +        xoffset = ( rect->w / 2 ) - ( pane->graphics[ i ].width / 2 ); +      else +        xoffset = pane->graphics[ i ].offset + GRAPHIC_BWIDTH; +    } +    else if( pane->graphics[ i ].side == INFOPANE_LEFT || pane->graphics[ i ].side == INFOPANE_RIGHT ) +    { +      //set vertical offset of graphic +      if( pane->graphics[ i ].offset < 0 ) +        yoffset = ( rect->h / 2 ) - ( pane->graphics[ i ].height / 2 ); +      else +        yoffset = pane->graphics[ i ].offset + GRAPHIC_BWIDTH; +    } + +    if( pane->graphics[ i ].side == INFOPANE_LEFT ) +    { +      //set the horizontal offset of the text +      if( pane->graphics[ i ].width > maxLeft ) +        maxLeft = pane->graphics[ i ].width + GRAPHIC_BWIDTH; + +      xoffset = GRAPHIC_BWIDTH; +    } +    else if( pane->graphics[ i ].side == INFOPANE_RIGHT ) +    { +      if( pane->graphics[ i ].width > maxRight ) +        maxRight = pane->graphics[ i ].width + GRAPHIC_BWIDTH; + +      xoffset = rect->w - width - GRAPHIC_BWIDTH; +    } +    else if( pane->graphics[ i ].side == INFOPANE_TOP ) +    { +      //set the vertical offset of the text +      if( pane->graphics[ i ].height > maxTop ) +        maxTop = pane->graphics[ i ].height + GRAPHIC_BWIDTH; + +      yoffset = GRAPHIC_BWIDTH; +    } +    else if( pane->graphics[ i ].side == INFOPANE_BOTTOM ) +    { +      if( pane->graphics[ i ].height > maxBottom ) +        maxBottom = pane->graphics[ i ].height + GRAPHIC_BWIDTH; + +      yoffset = rect->h - height - GRAPHIC_BWIDTH; +    } + +    //draw the graphic +    UI_DrawHandlePic( x + xoffset, y + yoffset, width, height, graphic ); +  } + +  //offset the text +  x = rect->x + maxLeft; +  y = rect->y + maxTop; +  w = rect->w - ( maxLeft + maxRight + 16 + ( 2 * text_x ) ); //16 to ensure text within frame +  h = rect->h - ( maxTop + maxBottom ); + +  textItem.text = pane->text; + +  textItem.parent = &dummyParent; +  memcpy( textItem.window.foreColor, color, sizeof( vec4_t ) ); +  textItem.window.flags = 0; + +  switch( pane->align ) +  { +    case ITEM_ALIGN_LEFT: +      textItem.window.rect.x = x; +      break; + +    case ITEM_ALIGN_RIGHT: +      textItem.window.rect.x = x + w; +      break; + +    case ITEM_ALIGN_CENTER: +      textItem.window.rect.x = x + ( w / 2 ); +      break; + +    default: +      textItem.window.rect.x = x; +      break; +  } + +  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 = pane->align; +  textItem.textalignx = text_x; +  textItem.textaligny = text_y; +  textItem.textscale = scale; +  textItem.textStyle = textStyle; + +  textItem.enableCvar = NULL; +  textItem.cvarTest = NULL; + +  //hack to utilise existing autowrap code +  Item_Text_AutoWrapped_Paint( &textItem ); +} + + +static void UI_DrawSkill(rectDef_t *rect, float scale, vec4_t color, int textStyle) { +  int i; +  i = trap_Cvar_VariableValue( "g_spSkill" ); +  if (i < 1 || i > numSkillLevels) { +    i = 1; +  } +  Text_Paint(rect->x, rect->y, scale, color, skillLevels[i-1],0, 0, textStyle); +} + + +static void UI_DrawTeamName(rectDef_t *rect, float scale, vec4_t color, qboolean blue, int textStyle) { +  int i; +  i = UI_TeamIndexFromName(UI_Cvar_VariableString((blue) ? "ui_blueTeam" : "ui_redTeam")); +  if (i >= 0 && i < uiInfo.teamCount) { +    Text_Paint(rect->x, rect->y, scale, color, va("%s: %s", (blue) ? "Blue" : "Red", uiInfo.teamList[i].teamName),0, 0, textStyle); +  } +} + +static void UI_DrawTeamMember(rectDef_t *rect, float scale, vec4_t color, qboolean blue, int num, int textStyle) { +  // 0 - None +  // 1 - Human +  // 2..NumCharacters - Bot +  int value = trap_Cvar_VariableValue(va(blue ? "ui_blueteam%i" : "ui_redteam%i", num)); +  const char *text; +  if (value <= 0) { +    text = "Closed"; +  } else if (value == 1) { +    text = "Human"; +  } else { +    value -= 2; + +    if( value >= UI_GetNumBots( ) ) +      value = 0; + +    text = UI_GetBotNameByNumber(value); +  } +  Text_Paint(rect->x, rect->y, scale, color, text, 0, 0, textStyle); +} + +static void UI_DrawMapPreview(rectDef_t *rect, float scale, vec4_t color, qboolean net) { +  int map = (net) ? ui_currentNetMap.integer : ui_currentMap.integer; +  if (map < 0 || map > uiInfo.mapCount) { +    if (net) { +      ui_currentNetMap.integer = 0; +      trap_Cvar_Set("ui_currentNetMap", "0"); +    } else { +      ui_currentMap.integer = 0; +      trap_Cvar_Set("ui_currentMap", "0"); +    } +    map = 0; +  } + +  if (uiInfo.mapList[map].levelShot == -1) { +    uiInfo.mapList[map].levelShot = trap_R_RegisterShaderNoMip(uiInfo.mapList[map].imageName); +  } + +  if (uiInfo.mapList[map].levelShot > 0) { +    UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.mapList[map].levelShot); +  } else { +    UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, trap_R_RegisterShaderNoMip("gfx/2d/load_screen")); +  } +} + + +static void UI_DrawMapTimeToBeat(rectDef_t *rect, float scale, vec4_t color, int textStyle) { +  int minutes, seconds, time; +  if (ui_currentMap.integer < 0 || ui_currentMap.integer > uiInfo.mapCount) { +    ui_currentMap.integer = 0; +    trap_Cvar_Set("ui_currentMap", "0"); +  } + +  time = uiInfo.mapList[ui_currentMap.integer].timeToBeat[uiInfo.gameTypes[ui_gameType.integer].gtEnum]; + +  minutes = time / 60; +  seconds = time % 60; + +  Text_Paint(rect->x, rect->y, scale, color, va("%02i:%02i", minutes, seconds), 0, 0, textStyle); +} + + + +static void UI_DrawMapCinematic(rectDef_t *rect, float scale, vec4_t color, qboolean net) { + +  int map = (net) ? ui_currentNetMap.integer : ui_currentMap.integer; +  if (map < 0 || map > uiInfo.mapCount) { +    if (net) { +      ui_currentNetMap.integer = 0; +      trap_Cvar_Set("ui_currentNetMap", "0"); +    } else { +      ui_currentMap.integer = 0; +      trap_Cvar_Set("ui_currentMap", "0"); +    } +    map = 0; +  } + +  if (uiInfo.mapList[map].cinematic >= -1) { +    if (uiInfo.mapList[map].cinematic == -1) { +      uiInfo.mapList[map].cinematic = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.mapList[map].mapLoadName), 0, 0, 0, 0, (CIN_loop | CIN_silent) ); +    } +    if (uiInfo.mapList[map].cinematic >= 0) { +      trap_CIN_RunCinematic(uiInfo.mapList[map].cinematic); +      trap_CIN_SetExtents(uiInfo.mapList[map].cinematic, rect->x, rect->y, rect->w, rect->h); +      trap_CIN_DrawCinematic(uiInfo.mapList[map].cinematic); +    } else { +      uiInfo.mapList[map].cinematic = -2; +    } +  } else { +    UI_DrawMapPreview(rect, scale, color, net); +  } +} + + + +static qboolean updateModel = qtrue; +static qboolean q3Model = qfalse; + +static void UI_DrawPlayerModel(rectDef_t *rect) { +  static playerInfo_t info; +  char model[MAX_QPATH]; +  char team[256]; +  char head[256]; +  vec3_t  viewangles; +  vec3_t  moveangles; + +    if (trap_Cvar_VariableValue("ui_Q3Model")) { +    strcpy(model, UI_Cvar_VariableString("model")); +    strcpy(head, UI_Cvar_VariableString("headmodel")); +    if (!q3Model) { +      q3Model = qtrue; +      updateModel = qtrue; +    } +    team[0] = '\0'; +  } else { + +    strcpy(team, UI_Cvar_VariableString("ui_teamName")); +    strcpy(model, UI_Cvar_VariableString("team_model")); +    strcpy(head, UI_Cvar_VariableString("team_headmodel")); +    if (q3Model) { +      q3Model = qfalse; +      updateModel = qtrue; +    } +  } +  if (updateModel) { +    memset( &info, 0, sizeof(playerInfo_t) ); +    viewangles[YAW]   = 180 - 10; +    viewangles[PITCH] = 0; +    viewangles[ROLL]  = 0; +    VectorClear( moveangles ); +    UI_PlayerInfo_SetModel( &info, model, head, team); +    UI_PlayerInfo_SetInfo( &info, LEGS_IDLE, TORSO_STAND, viewangles, vec3_origin, WP_MACHINEGUN, qfalse ); +//    UI_RegisterClientModelname( &info, model, head, team); +    updateModel = qfalse; +  } + +  UI_DrawPlayer( rect->x, rect->y, rect->w, rect->h, &info, uiInfo.uiDC.realTime / 2); + +} + +static void UI_DrawNetSource(rectDef_t *rect, float scale, vec4_t color, int textStyle) { +  if (ui_netSource.integer < 0 || ui_netSource.integer > numNetSources) { +    ui_netSource.integer = 0; +  } +  Text_Paint(rect->x, rect->y, scale, color, va("Source: %s", netSources[ui_netSource.integer]), 0, 0, textStyle); +} + +static void UI_DrawNetMapPreview(rectDef_t *rect, float scale, vec4_t color) { + +  if (uiInfo.serverStatus.currentServerPreview > 0) { +    UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.serverStatus.currentServerPreview); +  } else { +    UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, trap_R_RegisterShaderNoMip("gfx/2d/load_screen")); +  } +} + +static void UI_DrawNetMapCinematic(rectDef_t *rect, float scale, vec4_t color) { +  if (ui_currentNetMap.integer < 0 || ui_currentNetMap.integer > uiInfo.mapCount) { +    ui_currentNetMap.integer = 0; +    trap_Cvar_Set("ui_currentNetMap", "0"); +  } + +  if (uiInfo.serverStatus.currentServerCinematic >= 0) { +    trap_CIN_RunCinematic(uiInfo.serverStatus.currentServerCinematic); +    trap_CIN_SetExtents(uiInfo.serverStatus.currentServerCinematic, rect->x, rect->y, rect->w, rect->h); +    trap_CIN_DrawCinematic(uiInfo.serverStatus.currentServerCinematic); +  } else { +    UI_DrawNetMapPreview(rect, scale, color); +  } +} + + + +static void UI_DrawNetFilter(rectDef_t *rect, float scale, vec4_t color, int textStyle) { +  if (ui_serverFilterType.integer < 0 || ui_serverFilterType.integer > numServerFilters) { +    ui_serverFilterType.integer = 0; +  } +  Text_Paint(rect->x, rect->y, scale, color, va("Filter: %s", serverFilters[ui_serverFilterType.integer].description), 0, 0, textStyle); +} + + +static void UI_DrawTier(rectDef_t *rect, float scale, vec4_t color, int textStyle) { +  int i; +  i = trap_Cvar_VariableValue( "ui_currentTier" ); +  if (i < 0 || i >= uiInfo.tierCount) { +    i = 0; +  } +  Text_Paint(rect->x, rect->y, scale, color, va("Tier: %s", uiInfo.tierList[i].tierName),0, 0, textStyle); +} + +static void UI_DrawTierMap(rectDef_t *rect, int index) { +  int i; +  i = trap_Cvar_VariableValue( "ui_currentTier" ); +  if (i < 0 || i >= uiInfo.tierCount) { +    i = 0; +  } + +  if (uiInfo.tierList[i].mapHandles[index] == -1) { +    uiInfo.tierList[i].mapHandles[index] = trap_R_RegisterShaderNoMip(va("levelshots/%s", uiInfo.tierList[i].maps[index])); +  } + +  UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.tierList[i].mapHandles[index]); +} + +static const char *UI_EnglishMapName(const char *map) { +  int i; +  for (i = 0; i < uiInfo.mapCount; i++) { +    if (Q_stricmp(map, uiInfo.mapList[i].mapLoadName) == 0) { +      return uiInfo.mapList[i].mapName; +    } +  } +  return ""; +} + +static void UI_DrawTierMapName(rectDef_t *rect, float scale, vec4_t color, int textStyle) { +  int i, j; +  i = trap_Cvar_VariableValue( "ui_currentTier" ); +  if (i < 0 || i >= uiInfo.tierCount) { +    i = 0; +  } +  j = trap_Cvar_VariableValue("ui_currentMap"); +  if (j < 0 || j > MAPS_PER_TIER) { +    j = 0; +  } + +  Text_Paint(rect->x, rect->y, scale, color, UI_EnglishMapName(uiInfo.tierList[i].maps[j]), 0, 0, textStyle); +} + +static void UI_DrawTierGameType(rectDef_t *rect, float scale, vec4_t color, int textStyle) { +  int i, j; +  i = trap_Cvar_VariableValue( "ui_currentTier" ); +  if (i < 0 || i >= uiInfo.tierCount) { +    i = 0; +  } +  j = trap_Cvar_VariableValue("ui_currentMap"); +  if (j < 0 || j > MAPS_PER_TIER) { +    j = 0; +  } + +  Text_Paint(rect->x, rect->y, scale, color, uiInfo.gameTypes[uiInfo.tierList[i].gameTypes[j]].gameType , 0, 0, textStyle); +} + + +static const char *UI_AIFromName(const char *name) { +  int j; +  for (j = 0; j < uiInfo.aliasCount; j++) { +    if (Q_stricmp(uiInfo.aliasList[j].name, name) == 0) { +      return uiInfo.aliasList[j].ai; +    } +  } +  return "James"; +} + +static qboolean updateOpponentModel = qtrue; +static void UI_DrawOpponent(rectDef_t *rect) { +  static playerInfo_t info2; +  char model[MAX_QPATH]; +  char headmodel[MAX_QPATH]; +  char team[256]; +  vec3_t  viewangles; +  vec3_t  moveangles; + +  if (updateOpponentModel) { + +    strcpy(model, UI_Cvar_VariableString("ui_opponentModel")); +    strcpy(headmodel, UI_Cvar_VariableString("ui_opponentModel")); +    team[0] = '\0'; + +    memset( &info2, 0, sizeof(playerInfo_t) ); +    viewangles[YAW]   = 180 - 10; +    viewangles[PITCH] = 0; +    viewangles[ROLL]  = 0; +    VectorClear( moveangles ); +    UI_PlayerInfo_SetModel( &info2, model, headmodel, ""); +    UI_PlayerInfo_SetInfo( &info2, LEGS_IDLE, TORSO_STAND, viewangles, vec3_origin, WP_MACHINEGUN, qfalse ); +    UI_RegisterClientModelname( &info2, model, headmodel, team); +    updateOpponentModel = qfalse; +  } + +  UI_DrawPlayer( rect->x, rect->y, rect->w, rect->h, &info2, uiInfo.uiDC.realTime / 2); + +} + +static void UI_NextOpponent( void ) { +  int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName")); +  int j = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); +  i++; +  if (i >= uiInfo.teamCount) { +    i = 0; +  } +  if (i == j) { +    i++; +    if ( i >= uiInfo.teamCount) { +      i = 0; +    } +  } +  trap_Cvar_Set( "ui_opponentName", uiInfo.teamList[i].teamName ); +} + +static void UI_PriorOpponent( void ) { +  int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName")); +  int j = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); +  i--; +  if (i < 0) { +    i = uiInfo.teamCount - 1; +  } +  if (i == j) { +    i--; +    if ( i < 0) { +      i = uiInfo.teamCount - 1; +    } +  } +  trap_Cvar_Set( "ui_opponentName", uiInfo.teamList[i].teamName ); +} + +static void UI_DrawPlayerLogo(rectDef_t *rect, vec3_t color) { +  int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); + +  if (uiInfo.teamList[i].teamIcon == -1) { +    uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName); +    uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName)); +    uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName)); +  } + +  trap_R_SetColor( color ); +  UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon ); +  trap_R_SetColor( NULL ); +} + +static void UI_DrawPlayerLogoMetal(rectDef_t *rect, vec3_t color) { +  int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); +  if (uiInfo.teamList[i].teamIcon == -1) { +    uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName); +    uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName)); +    uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName)); +  } + +  trap_R_SetColor( color ); +  UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Metal ); +  trap_R_SetColor( NULL ); +} + +static void UI_DrawPlayerLogoName(rectDef_t *rect, vec3_t color) { +  int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); +  if (uiInfo.teamList[i].teamIcon == -1) { +    uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName); +    uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName)); +    uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName)); +  } + +  trap_R_SetColor( color ); +  UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Name ); +  trap_R_SetColor( NULL ); +} + +static void UI_DrawOpponentLogo(rectDef_t *rect, vec3_t color) { +  int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName")); +  if (uiInfo.teamList[i].teamIcon == -1) { +    uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName); +    uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName)); +    uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName)); +  } + +  trap_R_SetColor( color ); +  UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon ); +  trap_R_SetColor( NULL ); +} + +static void UI_DrawOpponentLogoMetal(rectDef_t *rect, vec3_t color) { +  int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName")); +  if (uiInfo.teamList[i].teamIcon == -1) { +    uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName); +    uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName)); +    uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName)); +  } + +  trap_R_SetColor( color ); +  UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Metal ); +  trap_R_SetColor( NULL ); +} + +static void UI_DrawOpponentLogoName(rectDef_t *rect, vec3_t color) { +  int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName")); +  if (uiInfo.teamList[i].teamIcon == -1) { +    uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName); +    uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName)); +    uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName)); +  } + +  trap_R_SetColor( color ); +  UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Name ); +  trap_R_SetColor( NULL ); +} + +static void UI_DrawAllMapsSelection(rectDef_t *rect, float scale, vec4_t color, int textStyle, qboolean net) { +  int map = (net) ? ui_currentNetMap.integer : ui_currentMap.integer; +  if (map >= 0 && map < uiInfo.mapCount) { +    Text_Paint(rect->x, rect->y, scale, color, uiInfo.mapList[map].mapName, 0, 0, textStyle); +  } +} + +static void UI_DrawPlayerListSelection( rectDef_t *rect, float scale, +  vec4_t color, int textStyle ) +{ +  if( uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount ) +  { +    Text_Paint(rect->x, rect->y, scale, color, +      uiInfo.rawPlayerNames[ uiInfo.playerIndex ], +      0, 0, textStyle); +  } +} + +static void UI_DrawTeamListSelection( rectDef_t *rect, float scale, +  vec4_t color, int textStyle ) +{ +  if( uiInfo.teamIndex >= 0 && uiInfo.teamIndex < uiInfo.myTeamCount ) +  { +    Text_Paint(rect->x, rect->y, scale, color, +      uiInfo.rawTeamNames[ uiInfo.teamIndex ], +      0, 0, textStyle); +  } +} + +static void UI_DrawOpponentName(rectDef_t *rect, float scale, vec4_t color, int textStyle) { +  Text_Paint(rect->x, rect->y, scale, color, UI_Cvar_VariableString("ui_opponentName"), 0, 0, textStyle); +} + + +static int UI_OwnerDrawWidth(int ownerDraw, float scale) { +  int i, h, value; +  const char *text; +  const char *s = NULL; + +  switch( ownerDraw ) +  { +    case UI_HANDICAP: +        h = Com_Clamp( 5, 100, trap_Cvar_VariableValue("handicap") ); +        i = 20 - h / 5; +        s = handicapValues[i]; +      break; +    case UI_CLANNAME: +        s = UI_Cvar_VariableString("ui_teamName"); +      break; +    case UI_GAMETYPE: +        s = uiInfo.gameTypes[ui_gameType.integer].gameType; +      break; +    case UI_SKILL: +        i = trap_Cvar_VariableValue( "g_spSkill" ); +        if (i < 1 || i > numSkillLevels) { +          i = 1; +        } +        s = skillLevels[i-1]; +      break; +    case UI_BLUETEAMNAME: +        i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_blueTeam")); +        if (i >= 0 && i < uiInfo.teamCount) { +          s = va("%s: %s", "Blue", uiInfo.teamList[i].teamName); +        } +      break; +    case UI_REDTEAMNAME: +        i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_redTeam")); +        if (i >= 0 && i < uiInfo.teamCount) { +          s = va("%s: %s", "Red", uiInfo.teamList[i].teamName); +        } +      break; +    case UI_BLUETEAM1: +    case UI_BLUETEAM2: +    case UI_BLUETEAM3: +    case UI_BLUETEAM4: +    case UI_BLUETEAM5: +      value = trap_Cvar_VariableValue(va("ui_blueteam%i", ownerDraw-UI_BLUETEAM1 + 1)); +      if (value <= 0) { +        text = "Closed"; +      } else if (value == 1) { +        text = "Human"; +      } else { +        value -= 2; +        if (value >= uiInfo.aliasCount) { +          value = 0; +        } +        text = uiInfo.aliasList[value].name; +      } +      s = va("%i. %s", ownerDraw-UI_BLUETEAM1 + 1, text); +      break; +    case UI_REDTEAM1: +    case UI_REDTEAM2: +    case UI_REDTEAM3: +    case UI_REDTEAM4: +    case UI_REDTEAM5: +      value = trap_Cvar_VariableValue(va("ui_redteam%i", ownerDraw-UI_REDTEAM1 + 1)); +      if (value <= 0) { +        text = "Closed"; +      } else if (value == 1) { +        text = "Human"; +      } else { +        value -= 2; +        if (value >= uiInfo.aliasCount) { +          value = 0; +        } +        text = uiInfo.aliasList[value].name; +      } +      s = va("%i. %s", ownerDraw-UI_REDTEAM1 + 1, text); +      break; +    case UI_NETSOURCE: +      if (ui_netSource.integer < 0 || ui_netSource.integer > uiInfo.numJoinGameTypes) { +        ui_netSource.integer = 0; +      } +      s = va("Source: %s", netSources[ui_netSource.integer]); +      break; +    case UI_NETFILTER: +      if (ui_serverFilterType.integer < 0 || ui_serverFilterType.integer > numServerFilters) { +        ui_serverFilterType.integer = 0; +      } +      s = va("Filter: %s", serverFilters[ui_serverFilterType.integer].description ); +      break; +    case UI_TIER: +      break; +    case UI_TIER_MAPNAME: +      break; +    case UI_TIER_GAMETYPE: +      break; +    case UI_ALLMAPS_SELECTION: +      break; +    case UI_PLAYERLIST_SELECTION: +      break; +    case UI_TEAMLIST_SELECTION: +      break; +    case UI_OPPONENT_NAME: +      break; +    case UI_KEYBINDSTATUS: +      if (Display_KeyBindPending()) { +        s = "Waiting for new key... Press ESCAPE to cancel"; +      } else { +        s = "Press ENTER or CLICK to change, Press BACKSPACE to clear"; +      } +      break; +    case UI_SERVERREFRESHDATE: +      s = UI_Cvar_VariableString(va("ui_lastServerRefresh_%i", ui_netSource.integer)); +      break; +    default: +      break; +  } + +  if (s) { +    return Text_Width(s, scale, 0); +  } +  return 0; +} + +static void UI_DrawBotName(rectDef_t *rect, float scale, vec4_t color, int textStyle) { +  int value = uiInfo.botIndex; +  const char *text = ""; + +  if( value >= UI_GetNumBots( ) ) +    value = 0; + +  text = UI_GetBotNameByNumber( value ); + +  Text_Paint(rect->x, rect->y, scale, color, text, 0, 0, textStyle); +} + +static void UI_DrawBotSkill(rectDef_t *rect, float scale, vec4_t color, int textStyle) { +  if (uiInfo.skillIndex >= 0 && uiInfo.skillIndex < numSkillLevels) { +    Text_Paint(rect->x, rect->y, scale, color, skillLevels[uiInfo.skillIndex], 0, 0, textStyle); +  } +} + +static void UI_DrawRedBlue(rectDef_t *rect, float scale, vec4_t color, int textStyle) { +  Text_Paint(rect->x, rect->y, scale, color, (uiInfo.redBlue == 0) ? "Red" : "Blue", 0, 0, textStyle); +} + +/* +=============== +UI_BuildPlayerList +=============== +*/ +static void UI_BuildPlayerList( void ) { +  uiClientState_t cs; +  int   n, count, team, team2, playerTeamNumber; +  char  info[MAX_INFO_STRING]; + +  trap_GetClientState( &cs ); +  trap_GetConfigString( CS_PLAYERS + cs.clientNum, info, MAX_INFO_STRING ); +  uiInfo.playerNumber = cs.clientNum; +  uiInfo.teamLeader = atoi(Info_ValueForKey(info, "tl")); +  team = atoi(Info_ValueForKey(info, "t")); +  trap_GetConfigString( CS_SERVERINFO, info, sizeof(info) ); +  count = atoi( Info_ValueForKey( info, "sv_maxclients" ) ); +  uiInfo.playerCount = 0; +  uiInfo.myTeamCount = 0; +  uiInfo.myPlayerIndex = 0; +  playerTeamNumber = 0; +  for( n = 0; n < count; n++ ) { +    trap_GetConfigString( CS_PLAYERS + n, info, MAX_INFO_STRING ); + +    if (info[0]) { +      BG_ClientListParse( &uiInfo.ignoreList[ uiInfo.playerCount ], +        Info_ValueForKey( info, "ig" ) ); +      Q_strncpyz( uiInfo.rawPlayerNames[uiInfo.playerCount], +        Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH ); +      Q_strncpyz( uiInfo.playerNames[uiInfo.playerCount], +        Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH ); +      Q_CleanStr( uiInfo.playerNames[uiInfo.playerCount] ); +      uiInfo.clientNums[uiInfo.playerCount] = n; +      if( n == uiInfo.playerNumber ) +        uiInfo.myPlayerIndex = uiInfo.playerCount; +      uiInfo.playerCount++; +      team2 = atoi(Info_ValueForKey(info, "t")); +      if (team2 == team) { +        Q_strncpyz( uiInfo.rawTeamNames[uiInfo.myTeamCount], +          Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH ); +        Q_strncpyz( uiInfo.teamNames[uiInfo.myTeamCount], +          Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH ); +        Q_CleanStr( uiInfo.teamNames[uiInfo.myTeamCount] ); +        uiInfo.teamClientNums[uiInfo.myTeamCount] = n; +        if (uiInfo.playerNumber == n) { +          playerTeamNumber = uiInfo.myTeamCount; +        } +        uiInfo.myTeamCount++; +      } +    } +  } + +  if (!uiInfo.teamLeader) { +    trap_Cvar_Set("cg_selectedPlayer", va("%d", playerTeamNumber)); +  } + +  n = trap_Cvar_VariableValue("cg_selectedPlayer"); +  if (n < 0 || n > uiInfo.myTeamCount) { +    n = 0; +  } +  if (n < uiInfo.myTeamCount) { +    trap_Cvar_Set("cg_selectedPlayerName", uiInfo.teamNames[n]); +  } +} + + +static void UI_DrawSelectedPlayer(rectDef_t *rect, float scale, vec4_t color, int textStyle) { +  char name[ MAX_NAME_LENGTH ]; +  char *s; + +  if (uiInfo.uiDC.realTime > uiInfo.playerRefresh) { +    uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000; +    UI_BuildPlayerList(); +  } +  if( uiInfo.teamLeader ) +    s = UI_Cvar_VariableString("cg_selectedPlayerName"); +  else +    s = UI_Cvar_VariableString("name"); +  Q_strncpyz( name, s, sizeof( name ) ); +  Text_Paint(rect->x, rect->y, scale, color, name, 0, 0, textStyle); +} + +static void UI_DrawServerRefreshDate(rectDef_t *rect, float scale, vec4_t color, int textStyle) { +  if (uiInfo.serverStatus.refreshActive) { +    vec4_t lowLight, newColor; +    lowLight[0] = 0.8 * color[0]; +    lowLight[1] = 0.8 * color[1]; +    lowLight[2] = 0.8 * color[2]; +    lowLight[3] = 0.8 * color[3]; +    LerpColor(color,lowLight,newColor,0.5+0.5*sin(uiInfo.uiDC.realTime / PULSE_DIVISOR)); +    Text_Paint(rect->x, rect->y, scale, newColor, va("Getting info for %d servers (ESC to cancel)", trap_LAN_GetServerCount(ui_netSource.integer)), 0, 0, textStyle); +  } else { +    char buff[64]; +    Q_strncpyz(buff, UI_Cvar_VariableString(va("ui_lastServerRefresh_%i", ui_netSource.integer)), 64); +    Text_Paint(rect->x, rect->y, scale, color, va("Refresh Time: %s", buff), 0, 0, textStyle); +  } +} + +static void UI_DrawServerMOTD(rectDef_t *rect, float scale, vec4_t color) { +  if (uiInfo.serverStatus.motdLen) { +    float maxX; + +    if (uiInfo.serverStatus.motdWidth == -1) { +      uiInfo.serverStatus.motdWidth = 0; +      uiInfo.serverStatus.motdPaintX = rect->x + 1; +      uiInfo.serverStatus.motdPaintX2 = -1; +    } + +    if (uiInfo.serverStatus.motdOffset > uiInfo.serverStatus.motdLen) { +      uiInfo.serverStatus.motdOffset = 0; +      uiInfo.serverStatus.motdPaintX = rect->x + 1; +      uiInfo.serverStatus.motdPaintX2 = -1; +    } + +    if (uiInfo.uiDC.realTime > uiInfo.serverStatus.motdTime) { +      uiInfo.serverStatus.motdTime = uiInfo.uiDC.realTime + 10; +      if (uiInfo.serverStatus.motdPaintX <= rect->x + 2) { +        if (uiInfo.serverStatus.motdOffset < uiInfo.serverStatus.motdLen) { +          uiInfo.serverStatus.motdPaintX += Text_Width(&uiInfo.serverStatus.motd[uiInfo.serverStatus.motdOffset], scale, 1) - 1; +          uiInfo.serverStatus.motdOffset++; +        } else { +          uiInfo.serverStatus.motdOffset = 0; +          if (uiInfo.serverStatus.motdPaintX2 >= 0) { +            uiInfo.serverStatus.motdPaintX = uiInfo.serverStatus.motdPaintX2; +          } else { +            uiInfo.serverStatus.motdPaintX = rect->x + rect->w - 2; +          } +          uiInfo.serverStatus.motdPaintX2 = -1; +        } +      } else { +        //serverStatus.motdPaintX--; +        uiInfo.serverStatus.motdPaintX -= 2; +        if (uiInfo.serverStatus.motdPaintX2 >= 0) { +          //serverStatus.motdPaintX2--; +          uiInfo.serverStatus.motdPaintX2 -= 2; +        } +      } +    } + +    maxX = rect->x + rect->w - 2; +    Text_Paint_Limit(&maxX, uiInfo.serverStatus.motdPaintX, rect->y + rect->h - 3, scale, color, &uiInfo.serverStatus.motd[uiInfo.serverStatus.motdOffset], 0, 0); +    if (uiInfo.serverStatus.motdPaintX2 >= 0) { +      float maxX2 = rect->x + rect->w - 2; +      Text_Paint_Limit(&maxX2, uiInfo.serverStatus.motdPaintX2, rect->y + rect->h - 3, scale, color, uiInfo.serverStatus.motd, 0, uiInfo.serverStatus.motdOffset); +    } +    if (uiInfo.serverStatus.motdOffset && maxX > 0) { +      // if we have an offset ( we are skipping the first part of the string ) and we fit the string +      if (uiInfo.serverStatus.motdPaintX2 == -1) { +            uiInfo.serverStatus.motdPaintX2 = rect->x + rect->w - 2; +      } +    } else { +      uiInfo.serverStatus.motdPaintX2 = -1; +    } + +  } +} + +static void UI_DrawKeyBindStatus(rectDef_t *rect, float scale, vec4_t color, int textStyle) { +//  int ofs = 0; TTimo: unused +  if (Display_KeyBindPending()) { +    Text_Paint(rect->x, rect->y, scale, color, "Waiting for new key... Press ESCAPE to cancel", 0, 0, textStyle); +  } else { +    Text_Paint(rect->x, rect->y, scale, color, "Press ENTER or CLICK to change, Press BACKSPACE to clear", 0, 0, textStyle); +  } +} + +static void UI_DrawGLInfo(rectDef_t *rect, float scale, vec4_t color, int textStyle) { +  char * eptr; +  char buff[1024]; +  const char *lines[64]; +  int y, numLines, i; + +  Text_Paint(rect->x + 2, rect->y, scale, color, va("VENDOR: %s", uiInfo.uiDC.glconfig.vendor_string), 0, 30, textStyle); +  Text_Paint(rect->x + 2, rect->y + 15, scale, color, va("VERSION: %s: %s", uiInfo.uiDC.glconfig.version_string,uiInfo.uiDC.glconfig.renderer_string), 0, 30, textStyle); +  Text_Paint(rect->x + 2, rect->y + 30, scale, color, va ("PIXELFORMAT: color(%d-bits) Z(%d-bits) stencil(%d-bits)", uiInfo.uiDC.glconfig.colorBits, uiInfo.uiDC.glconfig.depthBits, uiInfo.uiDC.glconfig.stencilBits), 0, 30, textStyle); + +  // build null terminated extension strings +  // TTimo: https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=399 +  // in TA this was not directly crashing, but displaying a nasty broken shader right in the middle +  // brought down the string size to 1024, there's not much that can be shown on the screen anyway +  Q_strncpyz(buff, uiInfo.uiDC.glconfig.extensions_string, 1024); +  eptr = buff; +  y = rect->y + 45; +  numLines = 0; +  while ( y < rect->y + rect->h && *eptr ) +  { +    while ( *eptr && *eptr == ' ' ) +      *eptr++ = '\0'; + +    // track start of valid string +    if (*eptr && *eptr != ' ') { +      lines[numLines++] = eptr; +    } + +    while ( *eptr && *eptr != ' ' ) +      eptr++; +  } + +  i = 0; +  while (i < numLines) { +    Text_Paint(rect->x + 2, y, scale, color, lines[i++], 0, 20, textStyle); +    if (i < numLines) { +      Text_Paint(rect->x + rect->w / 2, y, scale, color, lines[i++], 0, 20, textStyle); +    } +    y += 10; +    if (y > rect->y + rect->h - 11) { +      break; +    } +  } + + +} + +// FIXME: table drive +// +static void UI_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 ) +{ +  rectDef_t       rect; +  tremInfoPane_t  *pane = NULL; + +  rect.x = x + text_x; +  rect.y = y + text_y; +  rect.w = w; +  rect.h = h; + +  switch( ownerDraw ) +  { +    case UI_TEAMINFOPANE: +      if( ( pane = uiInfo.tremTeamList[ uiInfo.tremTeamIndex ].infopane ) ) +        UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle ); +      break; + +    case UI_ACLASSINFOPANE: +      if( ( pane = uiInfo.tremAlienClassList[ uiInfo.tremAlienClassIndex ].infopane ) ) +        UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle ); +      break; + +    case UI_AUPGRADEINFOPANE: +      if( ( pane = uiInfo.tremAlienUpgradeList[ uiInfo.tremAlienUpgradeIndex ].infopane ) ) +        UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle ); +      break; + +    case UI_HITEMINFOPANE: +      if( ( pane = uiInfo.tremHumanItemList[ uiInfo.tremHumanItemIndex ].infopane ) ) +        UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle ); +      break; + +    case UI_HBUYINFOPANE: +      if( ( pane = uiInfo.tremHumanArmouryBuyList[ uiInfo.tremHumanArmouryBuyIndex ].infopane ) ) +        UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle ); +      break; + +    case UI_HSELLINFOPANE: +      if( ( pane = uiInfo.tremHumanArmourySellList[ uiInfo.tremHumanArmourySellIndex ].infopane ) ) +        UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle ); +      break; + +    case UI_ABUILDINFOPANE: +      if( ( pane = uiInfo.tremAlienBuildList[ uiInfo.tremAlienBuildIndex ].infopane ) ) +        UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle ); +      break; + +    case UI_HBUILDINFOPANE: +      if( ( pane = uiInfo.tremHumanBuildList[ uiInfo.tremHumanBuildIndex ].infopane ) ) +        UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle ); +      break; + +    case UI_HANDICAP: +      UI_DrawHandicap(&rect, scale, color, textStyle); +      break; +    case UI_PLAYERMODEL: +      UI_DrawPlayerModel(&rect); +      break; +    case UI_CLANNAME: +      UI_DrawClanName(&rect, scale, color, textStyle); +      break; +    case UI_CLANLOGO: +      UI_DrawClanLogo(&rect, scale, color); +      break; +    case UI_CLANCINEMATIC: +      UI_DrawClanCinematic(&rect, scale, color); +      break; +    case UI_PREVIEWCINEMATIC: +      UI_DrawPreviewCinematic(&rect, scale, color); +      break; +    case UI_GAMETYPE: +      UI_DrawGameType(&rect, scale, color, textStyle); +      break; +    case UI_NETGAMETYPE: +      UI_DrawNetGameType(&rect, scale, color, textStyle); +      break; +    case UI_JOINGAMETYPE: +    UI_DrawJoinGameType(&rect, scale, color, textStyle); +    break; +    case UI_MAPPREVIEW: +      UI_DrawMapPreview(&rect, scale, color, qtrue); +      break; +    case UI_MAP_TIMETOBEAT: +      UI_DrawMapTimeToBeat(&rect, scale, color, textStyle); +      break; +    case UI_MAPCINEMATIC: +      UI_DrawMapCinematic(&rect, scale, color, qfalse); +      break; +    case UI_STARTMAPCINEMATIC: +      UI_DrawMapCinematic(&rect, scale, color, qtrue); +      break; +    case UI_SKILL: +      UI_DrawSkill(&rect, scale, color, textStyle); +      break; +    case UI_BLUETEAMNAME: +      UI_DrawTeamName(&rect, scale, color, qtrue, textStyle); +      break; +    case UI_REDTEAMNAME: +      UI_DrawTeamName(&rect, scale, color, qfalse, textStyle); +      break; +    case UI_BLUETEAM1: +    case UI_BLUETEAM2: +    case UI_BLUETEAM3: +    case UI_BLUETEAM4: +    case UI_BLUETEAM5: +      UI_DrawTeamMember(&rect, scale, color, qtrue, ownerDraw - UI_BLUETEAM1 + 1, textStyle); +      break; +    case UI_REDTEAM1: +    case UI_REDTEAM2: +    case UI_REDTEAM3: +    case UI_REDTEAM4: +    case UI_REDTEAM5: +      UI_DrawTeamMember(&rect, scale, color, qfalse, ownerDraw - UI_REDTEAM1 + 1, textStyle); +      break; +    case UI_NETSOURCE: +      UI_DrawNetSource(&rect, scale, color, textStyle); +      break; +    case UI_NETMAPPREVIEW: +      UI_DrawNetMapPreview(&rect, scale, color); +      break; +    case UI_NETMAPCINEMATIC: +      UI_DrawNetMapCinematic(&rect, scale, color); +      break; +    case UI_NETFILTER: +      UI_DrawNetFilter(&rect, scale, color, textStyle); +      break; +    case UI_TIER: +      UI_DrawTier(&rect, scale, color, textStyle); +      break; +    case UI_OPPONENTMODEL: +      UI_DrawOpponent(&rect); +      break; +    case UI_TIERMAP1: +      UI_DrawTierMap(&rect, 0); +      break; +    case UI_TIERMAP2: +      UI_DrawTierMap(&rect, 1); +      break; +    case UI_TIERMAP3: +      UI_DrawTierMap(&rect, 2); +      break; +    case UI_PLAYERLOGO: +      UI_DrawPlayerLogo(&rect, color); +      break; +    case UI_PLAYERLOGO_METAL: +      UI_DrawPlayerLogoMetal(&rect, color); +      break; +    case UI_PLAYERLOGO_NAME: +      UI_DrawPlayerLogoName(&rect, color); +      break; +    case UI_OPPONENTLOGO: +      UI_DrawOpponentLogo(&rect, color); +      break; +    case UI_OPPONENTLOGO_METAL: +      UI_DrawOpponentLogoMetal(&rect, color); +      break; +    case UI_OPPONENTLOGO_NAME: +      UI_DrawOpponentLogoName(&rect, color); +      break; +    case UI_TIER_MAPNAME: +      UI_DrawTierMapName(&rect, scale, color, textStyle); +      break; +    case UI_TIER_GAMETYPE: +      UI_DrawTierGameType(&rect, scale, color, textStyle); +      break; +    case UI_ALLMAPS_SELECTION: +      UI_DrawAllMapsSelection(&rect, scale, color, textStyle, qtrue); +      break; +    case UI_MAPS_SELECTION: +      UI_DrawAllMapsSelection(&rect, scale, color, textStyle, qfalse); +      break; +    case UI_PLAYERLIST_SELECTION: +      UI_DrawPlayerListSelection(&rect, scale, color, textStyle); +      break; +    case UI_TEAMLIST_SELECTION: +      UI_DrawTeamListSelection(&rect, scale, color, textStyle); +      break; +    case UI_OPPONENT_NAME: +      UI_DrawOpponentName(&rect, scale, color, textStyle); +      break; +    case UI_BOTNAME: +      UI_DrawBotName(&rect, scale, color, textStyle); +      break; +    case UI_BOTSKILL: +      UI_DrawBotSkill(&rect, scale, color, textStyle); +      break; +    case UI_REDBLUE: +      UI_DrawRedBlue(&rect, scale, color, textStyle); +      break; +    case UI_SELECTEDPLAYER: +      UI_DrawSelectedPlayer(&rect, scale, color, textStyle); +      break; +    case UI_SERVERREFRESHDATE: +      UI_DrawServerRefreshDate(&rect, scale, color, textStyle); +      break; +    case UI_SERVERMOTD: +      UI_DrawServerMOTD(&rect, scale, color); +      break; +    case UI_GLINFO: +      UI_DrawGLInfo(&rect,scale, color, textStyle); +      break; +    case UI_KEYBINDSTATUS: +      UI_DrawKeyBindStatus(&rect,scale, color, textStyle); +      break; +    default: +      break; +  } + +} + +static qboolean UI_OwnerDrawVisible(int flags) { +  qboolean vis = qtrue; +  uiClientState_t cs; +  pTeam_t         team; +  char            info[ MAX_INFO_STRING ]; + +  trap_GetClientState( &cs ); +  trap_GetConfigString( CS_PLAYERS + cs.clientNum, info, MAX_INFO_STRING ); +  team = atoi( Info_ValueForKey( info, "t" ) ); + + +  while (flags) { + +    if( flags & UI_SHOW_NOTSPECTATING ) +    { +      if( team == PTE_NONE ) +        vis = qfalse; + +      flags &= ~UI_SHOW_NOTSPECTATING; +    } + +    if( flags & UI_SHOW_VOTEACTIVE ) +    { +      if( !trap_Cvar_VariableValue( "ui_voteActive" ) ) +        vis = qfalse; + +      flags &= ~UI_SHOW_VOTEACTIVE; +    } + +    if( flags & UI_SHOW_CANVOTE ) +    { +      if( trap_Cvar_VariableValue( "ui_voteActive" ) ) +        vis = qfalse; + +      flags &= ~UI_SHOW_CANVOTE; +    } + +    if( flags & UI_SHOW_TEAMVOTEACTIVE ) +    { +      if( team == PTE_ALIENS ) +      { +        if( !trap_Cvar_VariableValue( "ui_alienTeamVoteActive" ) ) +          vis = qfalse; +      } +      else if( team == PTE_HUMANS ) +      { +        if( !trap_Cvar_VariableValue( "ui_humanTeamVoteActive" ) ) +          vis = qfalse; +      } + +      flags &= ~UI_SHOW_TEAMVOTEACTIVE; +    } + +    if( flags & UI_SHOW_CANTEAMVOTE ) +    { +      if( team == PTE_ALIENS ) +      { +        if( trap_Cvar_VariableValue( "ui_alienTeamVoteActive" ) ) +          vis = qfalse; +      } +      else if( team == PTE_HUMANS ) +      { +        if( trap_Cvar_VariableValue( "ui_humanTeamVoteActive" ) ) +          vis = qfalse; +      } + +      flags &= ~UI_SHOW_CANTEAMVOTE; +    } + +    if (flags & UI_SHOW_LEADER) { +      // these need to show when this client can give orders to a player or a group +      if (!uiInfo.teamLeader) { +        vis = qfalse; +      } else { +        // if showing yourself +        if (ui_selectedPlayer.integer < uiInfo.myTeamCount && uiInfo.teamClientNums[ui_selectedPlayer.integer] == uiInfo.playerNumber) { +          vis = qfalse; +        } +      } +      flags &= ~UI_SHOW_LEADER; +    } +    if (flags & UI_SHOW_NOTLEADER) { +      // these need to show when this client is assigning their own status or they are NOT the leader +      if (uiInfo.teamLeader) { +        // if not showing yourself +        if (!(ui_selectedPlayer.integer < uiInfo.myTeamCount && uiInfo.teamClientNums[ui_selectedPlayer.integer] == uiInfo.playerNumber)) { +          vis = qfalse; +        } +        // these need to show when this client can give orders to a player or a group +      } +      flags &= ~UI_SHOW_NOTLEADER; +    } +    if (flags & UI_SHOW_FAVORITESERVERS) { +      // this assumes you only put this type of display flag on something showing in the proper context +      if (ui_netSource.integer != AS_FAVORITES) { +        vis = qfalse; +      } +      flags &= ~UI_SHOW_FAVORITESERVERS; +    } +    if (flags & UI_SHOW_NOTFAVORITESERVERS) { +      // this assumes you only put this type of display flag on something showing in the proper context +      if (ui_netSource.integer == AS_FAVORITES) { +        vis = qfalse; +      } +      flags &= ~UI_SHOW_NOTFAVORITESERVERS; +    } +    if (flags & UI_SHOW_NEWHIGHSCORE) { +      if (uiInfo.newHighScoreTime < uiInfo.uiDC.realTime) { +        vis = qfalse; +      } else { +        if (uiInfo.soundHighScore) { +          if (trap_Cvar_VariableValue("sv_killserver") == 0) { +            // wait on server to go down before playing sound +            trap_S_StartLocalSound(uiInfo.newHighScoreSound, CHAN_ANNOUNCER); +            uiInfo.soundHighScore = qfalse; +          } +        } +      } +      flags &= ~UI_SHOW_NEWHIGHSCORE; +    } +    if (flags & UI_SHOW_NEWBESTTIME) { +      if (uiInfo.newBestTime < uiInfo.uiDC.realTime) { +        vis = qfalse; +      } +      flags &= ~UI_SHOW_NEWBESTTIME; +    } +    if (flags & UI_SHOW_DEMOAVAILABLE) { +      if (!uiInfo.demoAvailable) { +        vis = qfalse; +      } +      flags &= ~UI_SHOW_DEMOAVAILABLE; +    } else { +      flags = 0; +    } +  } +  return vis; +} + +static qboolean UI_Handicap_HandleKey(int flags, float *special, int key) { +  if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { +    int h; +    h = Com_Clamp( 5, 100, trap_Cvar_VariableValue("handicap") ); +    if (key == K_MOUSE2) { +      h -= 5; +    } else { +      h += 5; +    } +    if (h > 100) { +      h = 5; +    } else if (h < 0) { +      h = 100; +    } +    trap_Cvar_Set( "handicap", va( "%i", h) ); +    return qtrue; +  } +  return qfalse; +} + +static qboolean UI_ClanName_HandleKey(int flags, float *special, int key) { +  if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { +    int i; +    i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); +    if (uiInfo.teamList[i].cinematic >= 0) { +      trap_CIN_StopCinematic(uiInfo.teamList[i].cinematic); +      uiInfo.teamList[i].cinematic = -1; +    } +    if (key == K_MOUSE2) { +      i--; +    } else { +      i++; +    } +    if (i >= uiInfo.teamCount) { +      i = 0; +    } else if (i < 0) { +      i = uiInfo.teamCount - 1; +    } +    trap_Cvar_Set( "ui_teamName", uiInfo.teamList[i].teamName); +  UI_HeadCountByTeam(); +  UI_FeederSelection(FEEDER_HEADS, 0); +  updateModel = qtrue; +    return qtrue; +  } +  return qfalse; +} + +static qboolean UI_GameType_HandleKey(int flags, float *special, int key, qboolean resetMap) { +  if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { +    int oldCount = UI_MapCountByGameType(qtrue); + +    // hard coded mess here +    if (key == K_MOUSE2) { +      ui_gameType.integer--; +      if (ui_gameType.integer == 2) { +        ui_gameType.integer = 1; +      } else if (ui_gameType.integer < 2) { +        ui_gameType.integer = uiInfo.numGameTypes - 1; +      } +    } else { +      ui_gameType.integer++; +      if (ui_gameType.integer >= uiInfo.numGameTypes) { +        ui_gameType.integer = 1; +      } else if (ui_gameType.integer == 2) { +        ui_gameType.integer = 3; +      } +    } + +    trap_Cvar_Set("ui_Q3Model", "0"); + +    trap_Cvar_Set("ui_gameType", va("%d", ui_gameType.integer)); +    UI_SetCapFragLimits(qtrue); +    UI_LoadBestScores(uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum); +    if (resetMap && oldCount != UI_MapCountByGameType(qtrue)) { +      trap_Cvar_Set( "ui_currentMap", "0"); +      Menu_SetFeederSelection(NULL, FEEDER_MAPS, 0, NULL); +    } +    return qtrue; +  } +  return qfalse; +} + +static qboolean UI_NetGameType_HandleKey(int flags, float *special, int key) { +  if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { + +    if (key == K_MOUSE2) { +      ui_netGameType.integer--; +    } else { +      ui_netGameType.integer++; +    } + +    if (ui_netGameType.integer < 0) { +      ui_netGameType.integer = uiInfo.numGameTypes - 1; +    } else if (ui_netGameType.integer >= uiInfo.numGameTypes) { +      ui_netGameType.integer = 0; +    } + +    trap_Cvar_Set( "ui_netGameType", va("%d", ui_netGameType.integer)); +    trap_Cvar_Set( "ui_actualnetGameType", va("%d", uiInfo.gameTypes[ui_netGameType.integer].gtEnum)); +    trap_Cvar_Set( "ui_currentNetMap", "0"); +    UI_MapCountByGameType(qfalse); +    Menu_SetFeederSelection(NULL, FEEDER_ALLMAPS, 0, NULL); +    return qtrue; +  } +  return qfalse; +} + +static qboolean UI_JoinGameType_HandleKey(int flags, float *special, int key) { +  if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { + +    if (key == K_MOUSE2) { +      ui_joinGameType.integer--; +    } else { +      ui_joinGameType.integer++; +    } + +    if (ui_joinGameType.integer < 0) { +      ui_joinGameType.integer = uiInfo.numJoinGameTypes - 1; +    } else if (ui_joinGameType.integer >= uiInfo.numJoinGameTypes) { +      ui_joinGameType.integer = 0; +    } + +    trap_Cvar_Set( "ui_joinGameType", va("%d", ui_joinGameType.integer)); +    UI_BuildServerDisplayList(qtrue); +    return qtrue; +  } +  return qfalse; +} + + + +static qboolean UI_Skill_HandleKey(int flags, float *special, int key) { +  if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { +    int i = trap_Cvar_VariableValue( "g_spSkill" ); + +    if (key == K_MOUSE2) { +      i--; +    } else { +      i++; +    } + +    if (i < 1) { +      i = numSkillLevels; +    } else if (i > numSkillLevels) { +      i = 1; +    } + +    trap_Cvar_Set("g_spSkill", va("%i", i)); +    return qtrue; +  } +  return qfalse; +} + +static qboolean UI_TeamName_HandleKey(int flags, float *special, int key, qboolean blue) { +  if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { +    int i; +    i = UI_TeamIndexFromName(UI_Cvar_VariableString((blue) ? "ui_blueTeam" : "ui_redTeam")); + +    if (key == K_MOUSE2) { +      i--; +    } else { +      i++; +    } + +    if (i >= uiInfo.teamCount) { +      i = 0; +    } else if (i < 0) { +      i = uiInfo.teamCount - 1; +    } + +    trap_Cvar_Set( (blue) ? "ui_blueTeam" : "ui_redTeam", uiInfo.teamList[i].teamName); + +    return qtrue; +  } +  return qfalse; +} + +static qboolean UI_TeamMember_HandleKey(int flags, float *special, int key, qboolean blue, int num) { +  if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { +    // 0 - None +    // 1 - Human +    // 2..NumCharacters - Bot +    char *cvar = va(blue ? "ui_blueteam%i" : "ui_redteam%i", num); +    int value = trap_Cvar_VariableValue(cvar); + +    if (key == K_MOUSE2) { +      value--; +    } else { +      value++; +    } + +    if( value >= UI_GetNumBots( ) + 2 ) +      value = 0; +    else if( value < 0 ) +      value = UI_GetNumBots( ) + 2 - 1; + +    trap_Cvar_Set(cvar, va("%i", value)); +    return qtrue; +  } +  return qfalse; +} + +static qboolean UI_NetSource_HandleKey(int flags, float *special, int key) { +  if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { + +    if (key == K_MOUSE2) { +      ui_netSource.integer--; +      if (ui_netSource.integer == AS_MPLAYER) +        ui_netSource.integer--; +    } else { +      ui_netSource.integer++; +      if (ui_netSource.integer == AS_MPLAYER) +        ui_netSource.integer++; +    } + +    if (ui_netSource.integer >= numNetSources) { +      ui_netSource.integer = 0; +    } else if (ui_netSource.integer < 0) { +      ui_netSource.integer = numNetSources - 1; +    } + +    UI_BuildServerDisplayList(qtrue); +    if (ui_netSource.integer != AS_GLOBAL) { +      UI_StartServerRefresh(qtrue); +    } +    trap_Cvar_Set( "ui_netSource", va("%d", ui_netSource.integer)); +    return qtrue; +  } +  return qfalse; +} + +static qboolean UI_NetFilter_HandleKey(int flags, float *special, int key) { +  if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { + +    if (key == K_MOUSE2) { +      ui_serverFilterType.integer--; +    } else { +      ui_serverFilterType.integer++; +    } + +    if (ui_serverFilterType.integer >= numServerFilters) { +      ui_serverFilterType.integer = 0; +    } else if (ui_serverFilterType.integer < 0) { +      ui_serverFilterType.integer = numServerFilters - 1; +    } +    UI_BuildServerDisplayList(qtrue); +    return qtrue; +  } +  return qfalse; +} + +static qboolean UI_OpponentName_HandleKey(int flags, float *special, int key) { +  if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { +    if (key == K_MOUSE2) { +      UI_PriorOpponent(); +    } else { +      UI_NextOpponent(); +    } +    return qtrue; +  } +  return qfalse; +} + +static qboolean UI_BotName_HandleKey(int flags, float *special, int key) { +  if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { +    int value = uiInfo.botIndex; + +    if (key == K_MOUSE2) { +      value--; +    } else { +      value++; +    } + + +    if( value >= UI_GetNumBots( ) + 2 ) +      value = 0; +    else if( value < 0 ) +      value = UI_GetNumBots( ) + 2 - 1; + +    uiInfo.botIndex = value; +    return qtrue; +  } +  return qfalse; +} + +static qboolean UI_BotSkill_HandleKey(int flags, float *special, int key) { +  if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { +    if (key == K_MOUSE2) { +      uiInfo.skillIndex--; +    } else { +      uiInfo.skillIndex++; +    } +    if (uiInfo.skillIndex >= numSkillLevels) { +      uiInfo.skillIndex = 0; +    } else if (uiInfo.skillIndex < 0) { +      uiInfo.skillIndex = numSkillLevels-1; +    } +    return qtrue; +  } +  return qfalse; +} + +static qboolean UI_RedBlue_HandleKey(int flags, float *special, int key) { +  if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { +    uiInfo.redBlue ^= 1; +    return qtrue; +  } +  return qfalse; +} + + + +static qboolean UI_SelectedPlayer_HandleKey(int flags, float *special, int key) { +  if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { +    int selected; + +    UI_BuildPlayerList(); +    if (!uiInfo.teamLeader) { +      return qfalse; +    } +    selected = trap_Cvar_VariableValue("cg_selectedPlayer"); + +    if (key == K_MOUSE2) { +      selected--; +    } else { +      selected++; +    } + +    if (selected > uiInfo.myTeamCount) { +      selected = 0; +    } else if (selected < 0) { +      selected = uiInfo.myTeamCount; +    } + +    if (selected == uiInfo.myTeamCount) { +      trap_Cvar_Set( "cg_selectedPlayerName", "Everyone"); +    } else { +      trap_Cvar_Set( "cg_selectedPlayerName", uiInfo.teamNames[selected]); +    } +    trap_Cvar_Set( "cg_selectedPlayer", va("%d", selected)); +  } +  return qfalse; +} + + +static qboolean UI_OwnerDrawHandleKey(int ownerDraw, int flags, float *special, int key) { +  switch (ownerDraw) { +    case UI_HANDICAP: +      return UI_Handicap_HandleKey(flags, special, key); +      break; +    case UI_CLANNAME: +      return UI_ClanName_HandleKey(flags, special, key); +      break; +    case UI_GAMETYPE: +      return UI_GameType_HandleKey(flags, special, key, qtrue); +      break; +    case UI_NETGAMETYPE: +      return UI_NetGameType_HandleKey(flags, special, key); +      break; +    case UI_JOINGAMETYPE: +      return UI_JoinGameType_HandleKey(flags, special, key); +      break; +    case UI_SKILL: +      return UI_Skill_HandleKey(flags, special, key); +      break; +    case UI_BLUETEAMNAME: +      return UI_TeamName_HandleKey(flags, special, key, qtrue); +      break; +    case UI_REDTEAMNAME: +      return UI_TeamName_HandleKey(flags, special, key, qfalse); +      break; +    case UI_BLUETEAM1: +    case UI_BLUETEAM2: +    case UI_BLUETEAM3: +    case UI_BLUETEAM4: +    case UI_BLUETEAM5: +      UI_TeamMember_HandleKey(flags, special, key, qtrue, ownerDraw - UI_BLUETEAM1 + 1); +      break; +    case UI_REDTEAM1: +    case UI_REDTEAM2: +    case UI_REDTEAM3: +    case UI_REDTEAM4: +    case UI_REDTEAM5: +      UI_TeamMember_HandleKey(flags, special, key, qfalse, ownerDraw - UI_REDTEAM1 + 1); +      break; +    case UI_NETSOURCE: +      UI_NetSource_HandleKey(flags, special, key); +      break; +    case UI_NETFILTER: +      UI_NetFilter_HandleKey(flags, special, key); +      break; +    case UI_OPPONENT_NAME: +      UI_OpponentName_HandleKey(flags, special, key); +      break; +    case UI_BOTNAME: +      return UI_BotName_HandleKey(flags, special, key); +      break; +    case UI_BOTSKILL: +      return UI_BotSkill_HandleKey(flags, special, key); +      break; +    case UI_REDBLUE: +      UI_RedBlue_HandleKey(flags, special, key); +      break; +    case UI_SELECTEDPLAYER: +      UI_SelectedPlayer_HandleKey(flags, special, key); +      break; +    default: +      break; +  } + +  return qfalse; +} + + +static float UI_GetValue(int ownerDraw) { +  return 0; +} + +/* +================= +UI_ServersQsortCompare +================= +*/ +static int QDECL UI_ServersQsortCompare( const void *arg1, const void *arg2 ) { +  return trap_LAN_CompareServers( ui_netSource.integer, uiInfo.serverStatus.sortKey, uiInfo.serverStatus.sortDir, *(int*)arg1, *(int*)arg2); +} + + +/* +================= +UI_ServersSort +================= +*/ +void UI_ServersSort(int column, qboolean force) { + +  if ( !force ) { +    if ( uiInfo.serverStatus.sortKey == column ) { +      return; +    } +  } + +  uiInfo.serverStatus.sortKey = column; +  qsort( &uiInfo.serverStatus.displayServers[0], uiInfo.serverStatus.numDisplayServers, sizeof(int), UI_ServersQsortCompare); +} + + +/* +=============== +UI_GetCurrentAlienStage +=============== +*/ +static stage_t UI_GetCurrentAlienStage( void ) +{ +  char    buffer[ MAX_TOKEN_CHARS ]; +  stage_t stage, dummy; + +  trap_Cvar_VariableStringBuffer( "ui_stages", buffer, sizeof( buffer ) ); +  sscanf( buffer, "%d %d", (int *)&stage , (int *)&dummy ); + +  return stage; +} + +/* +=============== +UI_GetCurrentHumanStage +=============== +*/ +static stage_t UI_GetCurrentHumanStage( void ) +{ +  char    buffer[ MAX_TOKEN_CHARS ]; +  stage_t stage, dummy; + +  trap_Cvar_VariableStringBuffer( "ui_stages", buffer, sizeof( buffer ) ); +  sscanf( buffer, "%d %d", (int *)&dummy, (int *)&stage ); + +  return stage; +} + +/* +=============== +UI_LoadTremTeams +=============== +*/ +static void UI_LoadTremTeams( void ) +{ +  uiInfo.tremTeamCount = 4; + +  uiInfo.tremTeamList[ 0 ].text = String_Alloc( "Aliens" ); +  uiInfo.tremTeamList[ 0 ].cmd = String_Alloc( "cmd team aliens\n" ); +  uiInfo.tremTeamList[ 0 ].infopane = UI_FindInfoPaneByName( "alienteam" ); + +  uiInfo.tremTeamList[ 1 ].text = String_Alloc( "Humans" ); +  uiInfo.tremTeamList[ 1 ].cmd = String_Alloc( "cmd team humans\n" ); +  uiInfo.tremTeamList[ 1 ].infopane = UI_FindInfoPaneByName( "humanteam" ); + +  uiInfo.tremTeamList[ 2 ].text = String_Alloc( "Spectate" ); +  uiInfo.tremTeamList[ 2 ].cmd = String_Alloc( "cmd team spectate\n" ); +  uiInfo.tremTeamList[ 2 ].infopane = UI_FindInfoPaneByName( "spectateteam" ); + +  uiInfo.tremTeamList[ 3 ].text = String_Alloc( "Auto select" ); +  uiInfo.tremTeamList[ 3 ].cmd = String_Alloc( "cmd team auto\n" ); +  uiInfo.tremTeamList[ 3 ].infopane = UI_FindInfoPaneByName( "autoteam" ); +} + +/* +=============== +UI_AddClass +=============== +*/ +static void UI_AddClass( pClass_t class ) +{ +  uiInfo.tremAlienClassList[ uiInfo.tremAlienClassCount ].text = +    String_Alloc( BG_FindHumanNameForClassNum( class ) ); +  uiInfo.tremAlienClassList[ uiInfo.tremAlienClassCount ].cmd = +    String_Alloc( va( "cmd class %s\n", BG_FindNameForClassNum( class ) ) ); +  uiInfo.tremAlienClassList[ uiInfo.tremAlienClassCount ].infopane = +    UI_FindInfoPaneByName( va( "%sclass", BG_FindNameForClassNum( class ) ) ); + +  uiInfo.tremAlienClassCount++; +} + +/* +=============== +UI_LoadTremAlienClasses +=============== +*/ +static void UI_LoadTremAlienClasses( void ) +{ +  uiInfo.tremAlienClassCount = 0; + +  if( BG_ClassIsAllowed( PCL_ALIEN_LEVEL0 ) ) +    UI_AddClass( PCL_ALIEN_LEVEL0 ); + +  if( BG_ClassIsAllowed( PCL_ALIEN_BUILDER0_UPG ) && +      BG_FindStagesForClass( PCL_ALIEN_BUILDER0_UPG, UI_GetCurrentAlienStage( ) ) ) +    UI_AddClass( PCL_ALIEN_BUILDER0_UPG ); +  else if( BG_ClassIsAllowed( PCL_ALIEN_BUILDER0 ) ) +    UI_AddClass( PCL_ALIEN_BUILDER0 ); +} + +/* +=============== +UI_AddItem +=============== +*/ +static void UI_AddItem( weapon_t weapon ) +{ +  uiInfo.tremHumanItemList[ uiInfo.tremHumanItemCount ].text = +    String_Alloc( BG_FindHumanNameForWeapon( weapon ) ); +  uiInfo.tremHumanItemList[ uiInfo.tremHumanItemCount ].cmd = +    String_Alloc( va( "cmd class %s\n", BG_FindNameForWeapon( weapon ) ) ); +  uiInfo.tremHumanItemList[ uiInfo.tremHumanItemCount ].infopane = +    UI_FindInfoPaneByName( va( "%sitem", BG_FindNameForWeapon( weapon ) ) ); + +  uiInfo.tremHumanItemCount++; +} + +/* +=============== +UI_LoadTremHumanItems +=============== +*/ +static void UI_LoadTremHumanItems( void ) +{ +  uiInfo.tremHumanItemCount = 0; + +  if( BG_WeaponIsAllowed( WP_MACHINEGUN ) ) +    UI_AddItem( WP_MACHINEGUN ); + +  if( BG_WeaponIsAllowed( WP_HBUILD2 ) && +      BG_FindStagesForWeapon( WP_HBUILD2, UI_GetCurrentHumanStage( ) ) ) +    UI_AddItem( WP_HBUILD2 ); +  else if( BG_WeaponIsAllowed( WP_HBUILD ) ) +    UI_AddItem( WP_HBUILD ); +} + +/* +=============== +UI_ParseCarriageList +=============== +*/ +static void UI_ParseCarriageList( int *weapons, int *upgrades ) +{ +  int  i; +  char carriageCvar[ MAX_TOKEN_CHARS ]; +  char *iterator; +  char buffer[ MAX_TOKEN_CHARS ]; +  char *bufPointer; + +  trap_Cvar_VariableStringBuffer( "ui_carriage", carriageCvar, sizeof( carriageCvar ) ); +  iterator = carriageCvar; + +  if( weapons ) +    *weapons = 0; + +  if( upgrades ) +    *upgrades = 0; + +  //simple parser to give rise to weapon/upgrade list +  while( iterator && iterator[ 0 ] != '$' ) +  { +    bufPointer = buffer; + +    if( iterator[ 0 ] == 'W' ) +    { +      iterator++; + +      while( iterator[ 0 ] != ' ' ) +        *bufPointer++ = *iterator++; + +      *bufPointer++ = '\n'; + +      i = atoi( buffer ); + +      if( weapons ) +        *weapons |= ( 1 << i ); +    } +    else if( iterator[ 0 ] == 'U' ) +    { +      iterator++; + +      while( iterator[ 0 ] != ' ' ) +        *bufPointer++ = *iterator++; + +      *bufPointer++ = '\n'; + +      i = atoi( buffer ); + +      if( upgrades ) +        *upgrades |= ( 1 << i ); +    } + +    iterator++; +  } +} + +/* +=============== +UI_LoadTremHumanArmouryBuys +=============== +*/ +static void UI_LoadTremHumanArmouryBuys( void ) +{ +  int i, j = 0; +  stage_t stage = UI_GetCurrentHumanStage( ); +  int weapons, upgrades; +  int slots = 0; + +  UI_ParseCarriageList( &weapons, &upgrades ); + +  for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) +  { +    if( weapons & ( 1 << i ) ) +      slots |= BG_FindSlotsForWeapon( i ); +  } + +  for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) +  { +    if( upgrades & ( 1 << i ) ) +      slots |= BG_FindSlotsForUpgrade( i ); +  } + +  uiInfo.tremHumanArmouryBuyCount = 0; + +  for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) +  { +    if( BG_FindTeamForWeapon( i ) == WUT_HUMANS && +        BG_FindPurchasableForWeapon( i ) && +        BG_FindStagesForWeapon( i, stage ) && +        BG_WeaponIsAllowed( i ) && +        !( BG_FindSlotsForWeapon( i ) & slots ) && +        !( weapons & ( 1 << i ) ) ) +    { +      uiInfo.tremHumanArmouryBuyList[ j ].text = +        String_Alloc( BG_FindHumanNameForWeapon( i ) ); +      uiInfo.tremHumanArmouryBuyList[ j ].cmd = +        String_Alloc( va( "cmd buy %s retrigger\n", BG_FindNameForWeapon( i ) ) ); +      uiInfo.tremHumanArmouryBuyList[ j ].infopane = +        UI_FindInfoPaneByName( va( "%sitem", BG_FindNameForWeapon( i ) ) ); + +      j++; + +      uiInfo.tremHumanArmouryBuyCount++; +    } +  } + +  for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) +  { +    if( BG_FindTeamForUpgrade( i ) == WUT_HUMANS && +        BG_FindPurchasableForUpgrade( i ) && +        BG_FindStagesForUpgrade( i, stage ) && +        BG_UpgradeIsAllowed( i ) && +        !( BG_FindSlotsForUpgrade( i ) & slots ) && +        !( upgrades & ( 1 << i ) ) ) +    { +      uiInfo.tremHumanArmouryBuyList[ j ].text = +        String_Alloc( BG_FindHumanNameForUpgrade( i ) ); +      uiInfo.tremHumanArmouryBuyList[ j ].cmd = +        String_Alloc( va( "cmd buy %s retrigger\n", BG_FindNameForUpgrade( i ) ) ); +      uiInfo.tremHumanArmouryBuyList[ j ].infopane = +        UI_FindInfoPaneByName( va( "%sitem", BG_FindNameForUpgrade( i ) ) ); + +      j++; + +      uiInfo.tremHumanArmouryBuyCount++; +    } +  } +} + +/* +=============== +UI_LoadTremHumanArmourySells +=============== +*/ +static void UI_LoadTremHumanArmourySells( void ) +{ +  int weapons, upgrades; +  int i, j = 0; + +  uiInfo.tremHumanArmourySellCount = 0; +  UI_ParseCarriageList( &weapons, &upgrades ); + +  for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) +  { +    if( weapons & ( 1 << i ) ) +    { +      uiInfo.tremHumanArmourySellList[ j ].text = String_Alloc( BG_FindHumanNameForWeapon( i ) ); +      uiInfo.tremHumanArmourySellList[ j ].cmd = +        String_Alloc( va( "cmd sell %s retrigger\n", BG_FindNameForWeapon( i ) ) ); +      uiInfo.tremHumanArmourySellList[ j ].infopane = +        UI_FindInfoPaneByName( va( "%sitem", BG_FindNameForWeapon( i ) ) ); + +      j++; + +      uiInfo.tremHumanArmourySellCount++; +    } +  } + +  for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) +  { +    if( upgrades & ( 1 << i ) ) +    { +      uiInfo.tremHumanArmourySellList[ j ].text = String_Alloc( BG_FindHumanNameForUpgrade( i ) ); +      uiInfo.tremHumanArmourySellList[ j ].cmd = +        String_Alloc( va( "cmd sell %s retrigger\n", BG_FindNameForUpgrade( i ) ) ); +      uiInfo.tremHumanArmourySellList[ j ].infopane = +        UI_FindInfoPaneByName( va( "%sitem", BG_FindNameForUpgrade( i ) ) ); + +      j++; + +      uiInfo.tremHumanArmourySellCount++; +    } +  } +} + +/* +=============== +UI_LoadTremAlienUpgrades +=============== +*/ +static void UI_LoadTremAlienUpgrades( void ) +{ +  int     i, j = 0; +  int     class, credits; +  char    ui_currentClass[ MAX_STRING_CHARS ]; +  stage_t stage = UI_GetCurrentAlienStage( ); + +  trap_Cvar_VariableStringBuffer( "ui_currentClass", ui_currentClass, MAX_STRING_CHARS ); +  sscanf( ui_currentClass, "%d %d", &class, &credits ); + +  uiInfo.tremAlienUpgradeCount = 0; + +  for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ ) +  { +    if( BG_ClassCanEvolveFromTo( class, i, credits, 0 ) >= 0 && +        BG_FindStagesForClass( i, stage ) && +        BG_ClassIsAllowed( i ) ) +    { +      uiInfo.tremAlienUpgradeList[ j ].text = String_Alloc( BG_FindHumanNameForClassNum( i ) ); +      uiInfo.tremAlienUpgradeList[ j ].cmd = +        String_Alloc( va( "cmd class %s\n", BG_FindNameForClassNum( i ) ) ); +      uiInfo.tremAlienUpgradeList[ j ].infopane = +        UI_FindInfoPaneByName( va( "%sclass", BG_FindNameForClassNum( i ) ) ); + +      j++; + +      uiInfo.tremAlienUpgradeCount++; +    } +  } +} + +/* +=============== +UI_LoadTremAlienBuilds +=============== +*/ +static void UI_LoadTremAlienBuilds( void ) +{ +  int     weapons; +  int     i, j = 0; +  stage_t stage; + +  UI_ParseCarriageList( &weapons, NULL ); +  stage = UI_GetCurrentAlienStage( ); + +  uiInfo.tremAlienBuildCount = 0; + +  for( i = BA_NONE +1; i < BA_NUM_BUILDABLES; i++ ) +  { +    if( BG_FindTeamForBuildable( i ) == BIT_ALIENS && +        BG_FindBuildWeaponForBuildable( i ) & weapons && +        BG_FindStagesForBuildable( i, stage ) && +        BG_BuildableIsAllowed( i ) ) +    { +      uiInfo.tremAlienBuildList[ j ].text = +        String_Alloc( BG_FindHumanNameForBuildable( i ) ); +      uiInfo.tremAlienBuildList[ j ].cmd = +        String_Alloc( va( "cmd build %s\n", BG_FindNameForBuildable( i ) ) ); +      uiInfo.tremAlienBuildList[ j ].infopane = +        UI_FindInfoPaneByName( va( "%sbuild", BG_FindNameForBuildable( i ) ) ); + +      j++; + +      uiInfo.tremAlienBuildCount++; +    } +  } +} + +/* +=============== +UI_LoadTremHumanBuilds +=============== +*/ +static void UI_LoadTremHumanBuilds( void ) +{ +  int     weapons; +  int     i, j = 0; +  stage_t stage; + +  UI_ParseCarriageList( &weapons, NULL ); +  stage = UI_GetCurrentHumanStage( ); + +  uiInfo.tremHumanBuildCount = 0; + +  for( i = BA_NONE +1; i < BA_NUM_BUILDABLES; i++ ) +  { +    if( BG_FindTeamForBuildable( i ) == BIT_HUMANS && +        BG_FindBuildWeaponForBuildable( i ) & weapons && +        BG_FindStagesForBuildable( i, stage ) && +        BG_BuildableIsAllowed( i ) ) +    { +      uiInfo.tremHumanBuildList[ j ].text = +        String_Alloc( BG_FindHumanNameForBuildable( i ) ); +      uiInfo.tremHumanBuildList[ j ].cmd = +        String_Alloc( va( "cmd build %s\n", BG_FindNameForBuildable( i ) ) ); +      uiInfo.tremHumanBuildList[ j ].infopane = +        UI_FindInfoPaneByName( va( "%sbuild", BG_FindNameForBuildable( i ) ) ); + +      j++; + +      uiInfo.tremHumanBuildCount++; +    } +  } +} + +/* +=============== +UI_LoadMods +=============== +*/ +static void UI_LoadMods( void ) { +  int   numdirs; +  char  dirlist[2048]; +  char  *dirptr; +  char  *descptr; +  int   i; +  int   dirlen; + +  uiInfo.modCount = 0; +  numdirs = trap_FS_GetFileList( "$modlist", "", dirlist, sizeof(dirlist) ); +  dirptr  = dirlist; +  for( i = 0; i < numdirs; i++ ) { +    dirlen = strlen( dirptr ) + 1; +    descptr = dirptr + dirlen; +    uiInfo.modList[uiInfo.modCount].modName = String_Alloc(dirptr); +    uiInfo.modList[uiInfo.modCount].modDescr = String_Alloc(descptr); +    dirptr += dirlen + strlen(descptr) + 1; +    uiInfo.modCount++; +    if (uiInfo.modCount >= MAX_MODS) { +      break; +    } +  } + +} + + +/* +=============== +UI_LoadMovies +=============== +*/ +static void UI_LoadMovies( void ) { +  char  movielist[4096]; +  char  *moviename; +  int   i, len; + +  uiInfo.movieCount = trap_FS_GetFileList( "video", "roq", movielist, 4096 ); + +  if (uiInfo.movieCount) { +    if (uiInfo.movieCount > MAX_MOVIES) { +      uiInfo.movieCount = MAX_MOVIES; +    } +    moviename = movielist; +    for ( i = 0; i < uiInfo.movieCount; i++ ) { +      len = strlen( moviename ); +      if (!Q_stricmp(moviename +  len - 4,".roq")) { +        moviename[len-4] = '\0'; +      } +      Q_strupr(moviename); +      uiInfo.movieList[i] = String_Alloc(moviename); +      moviename += len + 1; +    } +  } + +} + + + +/* +=============== +UI_LoadDemos +=============== +*/ +static void UI_LoadDemos( void ) { +  char  demolist[4096]; +  char demoExt[32]; +  char  *demoname; +  int   i, len; + +  Com_sprintf(demoExt, sizeof(demoExt), "dm_%d", (int)trap_Cvar_VariableValue("protocol")); + +  uiInfo.demoCount = trap_FS_GetFileList( "demos", demoExt, demolist, 4096 ); + +  Com_sprintf(demoExt, sizeof(demoExt), ".dm_%d", (int)trap_Cvar_VariableValue("protocol")); + +  if (uiInfo.demoCount) { +    if (uiInfo.demoCount > MAX_DEMOS) { +      uiInfo.demoCount = MAX_DEMOS; +    } +    demoname = demolist; +    for ( i = 0; i < uiInfo.demoCount; i++ ) { +      len = strlen( demoname ); +      if (!Q_stricmp(demoname +  len - strlen(demoExt), demoExt)) { +        demoname[len-strlen(demoExt)] = '\0'; +      } +      Q_strupr(demoname); +      uiInfo.demoList[i] = String_Alloc(demoname); +      demoname += len + 1; +    } +  } + +} + + +static qboolean UI_SetNextMap(int actual, int index) { +  int i; +  for (i = actual + 1; i < uiInfo.mapCount; i++) { +    if (uiInfo.mapList[i].active) { +      Menu_SetFeederSelection(NULL, FEEDER_MAPS, index + 1, "skirmish"); +      return qtrue; +    } +  } +  return qfalse; +} + + +static void UI_StartSkirmish(qboolean next) { +  int i, k, g, delay, temp; +  float skill; +  char buff[MAX_STRING_CHARS]; + +  if (next) { +    int actual; +    int index = trap_Cvar_VariableValue("ui_mapIndex"); +    UI_MapCountByGameType(qtrue); +    UI_SelectedMap(index, &actual); +    if (UI_SetNextMap(actual, index)) { +    } else { +      UI_GameType_HandleKey(0, NULL, K_MOUSE1, qfalse); +      UI_MapCountByGameType(qtrue); +      Menu_SetFeederSelection(NULL, FEEDER_MAPS, 0, "skirmish"); +    } +  } + +  g = uiInfo.gameTypes[ui_gameType.integer].gtEnum; +  trap_Cvar_SetValue( "g_gametype", g ); +  trap_Cmd_ExecuteText( EXEC_APPEND, va( "wait ; wait ; map %s\n", uiInfo.mapList[ui_currentMap.integer].mapLoadName) ); +  skill = trap_Cvar_VariableValue( "g_spSkill" ); +  trap_Cvar_Set("ui_scoreMap", uiInfo.mapList[ui_currentMap.integer].mapName); + +  k = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName")); + +  trap_Cvar_Set("ui_singlePlayerActive", "1"); + +  // set up sp overrides, will be replaced on postgame +  temp = trap_Cvar_VariableValue( "capturelimit" ); +  trap_Cvar_Set("ui_saveCaptureLimit", va("%i", temp)); +  temp = trap_Cvar_VariableValue( "fraglimit" ); +  trap_Cvar_Set("ui_saveFragLimit", va("%i", temp)); + +  UI_SetCapFragLimits(qfalse); + +  temp = trap_Cvar_VariableValue( "cg_drawTimer" ); +  trap_Cvar_Set("ui_drawTimer", va("%i", temp)); +  temp = trap_Cvar_VariableValue( "g_doWarmup" ); +  trap_Cvar_Set("ui_doWarmup", va("%i", temp)); +  temp = trap_Cvar_VariableValue( "g_friendlyFire" ); +  trap_Cvar_Set("ui_friendlyFire", va("%i", temp)); +  temp = trap_Cvar_VariableValue( "sv_maxClients" ); +  trap_Cvar_Set("ui_maxClients", va("%i", temp)); +  temp = trap_Cvar_VariableValue( "g_warmup" ); +  trap_Cvar_Set("ui_Warmup", va("%i", temp)); +  temp = trap_Cvar_VariableValue( "sv_pure" ); +  trap_Cvar_Set("ui_pure", va("%i", temp)); + +  trap_Cvar_Set("cg_cameraOrbit", "0"); +  trap_Cvar_Set("cg_thirdPerson", "0"); +  trap_Cvar_Set("cg_drawTimer", "1"); +  trap_Cvar_Set("g_doWarmup", "1"); +  trap_Cvar_Set("g_warmup", "15"); +  trap_Cvar_Set("sv_pure", "0"); +  trap_Cvar_Set("g_friendlyFire", "0"); +  trap_Cvar_Set("g_redTeam", UI_Cvar_VariableString("ui_teamName")); +  trap_Cvar_Set("g_blueTeam", UI_Cvar_VariableString("ui_opponentName")); + +  if (trap_Cvar_VariableValue("ui_recordSPDemo")) { +    Com_sprintf(buff, MAX_STRING_CHARS, "%s_%i", uiInfo.mapList[ui_currentMap.integer].mapLoadName, g); +    trap_Cvar_Set("ui_recordSPDemoName", buff); +  } + +  delay = 500; + +  { +    temp = uiInfo.mapList[ui_currentMap.integer].teamMembers * 2; +    trap_Cvar_Set("sv_maxClients", va("%d", temp)); +    for (i =0; i < uiInfo.mapList[ui_currentMap.integer].teamMembers; i++) { +      Com_sprintf( buff, sizeof(buff), "addbot %s %f %s %i %s\n", UI_AIFromName(uiInfo.teamList[k].teamMembers[i]), skill, "", delay, uiInfo.teamList[k].teamMembers[i]); +      trap_Cmd_ExecuteText( EXEC_APPEND, buff ); +      delay += 500; +    } +    k = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); +    for (i =0; i < uiInfo.mapList[ui_currentMap.integer].teamMembers-1; i++) { +      Com_sprintf( buff, sizeof(buff), "addbot %s %f %s %i %s\n", UI_AIFromName(uiInfo.teamList[k].teamMembers[i]), skill, "", delay, uiInfo.teamList[k].teamMembers[i]); +      trap_Cmd_ExecuteText( EXEC_APPEND, buff ); +      delay += 500; +    } +  } +} + +static void UI_Update(const char *name) { +  int val = trap_Cvar_VariableValue(name); + +  if (Q_stricmp(name, "ui_SetName") == 0) { +    trap_Cvar_Set( "name", UI_Cvar_VariableString("ui_Name")); +  } else if (Q_stricmp(name, "ui_setRate") == 0) { +    float rate = trap_Cvar_VariableValue("rate"); +    if (rate >= 5000) { +      trap_Cvar_Set("cl_maxpackets", "30"); +      trap_Cvar_Set("cl_packetdup", "1"); +    } else if (rate >= 4000) { +      trap_Cvar_Set("cl_maxpackets", "15"); +      trap_Cvar_Set("cl_packetdup", "2");   // favor less prediction errors when there's packet loss +    } else { +      trap_Cvar_Set("cl_maxpackets", "15"); +      trap_Cvar_Set("cl_packetdup", "1");   // favor lower bandwidth +    } +  } else if (Q_stricmp(name, "ui_GetName") == 0) { +    trap_Cvar_Set( "ui_Name", UI_Cvar_VariableString("name")); +  } else if (Q_stricmp(name, "r_colorbits") == 0) { +    switch (val) { +      case 0: +        trap_Cvar_SetValue( "r_depthbits", 0 ); +        trap_Cvar_SetValue( "r_stencilbits", 0 ); +      break; +      case 16: +        trap_Cvar_SetValue( "r_depthbits", 16 ); +        trap_Cvar_SetValue( "r_stencilbits", 0 ); +      break; +      case 32: +        trap_Cvar_SetValue( "r_depthbits", 24 ); +      break; +    } +  } else if (Q_stricmp(name, "r_lodbias") == 0) { +    switch (val) { +      case 0: +        trap_Cvar_SetValue( "r_subdivisions", 4 ); +      break; +      case 1: +        trap_Cvar_SetValue( "r_subdivisions", 12 ); +      break; +      case 2: +        trap_Cvar_SetValue( "r_subdivisions", 20 ); +      break; +    } +  } else if (Q_stricmp(name, "ui_glCustom") == 0) { +    switch (val) { +      case 0: // high quality +        trap_Cvar_SetValue( "r_fullScreen", 1 ); +        trap_Cvar_SetValue( "r_subdivisions", 4 ); +        trap_Cvar_SetValue( "r_vertexlight", 0 ); +        trap_Cvar_SetValue( "r_lodbias", 0 ); +        trap_Cvar_SetValue( "r_colorbits", 32 ); +        trap_Cvar_SetValue( "r_depthbits", 24 ); +        trap_Cvar_SetValue( "r_picmip", 0 ); +        trap_Cvar_SetValue( "r_mode", 4 ); +        trap_Cvar_SetValue( "r_texturebits", 32 ); +        trap_Cvar_SetValue( "r_fastSky", 0 ); +        trap_Cvar_SetValue( "r_inGameVideo", 1 ); +        trap_Cvar_SetValue( "cg_shadows", 1 ); +        trap_Cvar_SetValue( "cg_brassTime", 2500 ); +        trap_Cvar_SetValue( "cg_bounceParticles", 1 ); +        trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_LINEAR" ); +      break; +      case 1: // normal +        trap_Cvar_SetValue( "r_fullScreen", 1 ); +        trap_Cvar_SetValue( "r_subdivisions", 12 ); +        trap_Cvar_SetValue( "r_vertexlight", 0 ); +        trap_Cvar_SetValue( "r_lodbias", 0 ); +        trap_Cvar_SetValue( "r_colorbits", 0 ); +        trap_Cvar_SetValue( "r_depthbits", 24 ); +        trap_Cvar_SetValue( "r_picmip", 1 ); +        trap_Cvar_SetValue( "r_mode", 3 ); +        trap_Cvar_SetValue( "r_texturebits", 0 ); +        trap_Cvar_SetValue( "r_fastSky", 0 ); +        trap_Cvar_SetValue( "r_inGameVideo", 1 ); +        trap_Cvar_SetValue( "cg_brassTime", 2500 ); +        trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_LINEAR" ); +        trap_Cvar_SetValue( "cg_shadows", 0 ); +        trap_Cvar_SetValue( "cg_bounceParticles", 0 ); +      break; +      case 2: // fast +        trap_Cvar_SetValue( "r_fullScreen", 1 ); +        trap_Cvar_SetValue( "r_subdivisions", 8 ); +        trap_Cvar_SetValue( "r_vertexlight", 0 ); +        trap_Cvar_SetValue( "r_lodbias", 1 ); +        trap_Cvar_SetValue( "r_colorbits", 0 ); +        trap_Cvar_SetValue( "r_depthbits", 0 ); +        trap_Cvar_SetValue( "r_picmip", 1 ); +        trap_Cvar_SetValue( "r_mode", 3 ); +        trap_Cvar_SetValue( "r_texturebits", 0 ); +        trap_Cvar_SetValue( "cg_shadows", 0 ); +        trap_Cvar_SetValue( "r_fastSky", 1 ); +        trap_Cvar_SetValue( "r_inGameVideo", 0 ); +        trap_Cvar_SetValue( "cg_brassTime", 0 ); +        trap_Cvar_SetValue( "cg_bounceParticles", 0 ); +        trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_NEAREST" ); +      break; +      case 3: // fastest +        trap_Cvar_SetValue( "r_fullScreen", 1 ); +        trap_Cvar_SetValue( "r_subdivisions", 20 ); +        trap_Cvar_SetValue( "r_vertexlight", 1 ); +        trap_Cvar_SetValue( "r_lodbias", 2 ); +        trap_Cvar_SetValue( "r_colorbits", 16 ); +        trap_Cvar_SetValue( "r_depthbits", 16 ); +        trap_Cvar_SetValue( "r_mode", 3 ); +        trap_Cvar_SetValue( "r_picmip", 2 ); +        trap_Cvar_SetValue( "r_texturebits", 16 ); +        trap_Cvar_SetValue( "cg_shadows", 0 ); +        trap_Cvar_SetValue( "cg_brassTime", 0 ); +        trap_Cvar_SetValue( "r_fastSky", 1 ); +        trap_Cvar_SetValue( "r_inGameVideo", 0 ); +        trap_Cvar_SetValue( "cg_bounceParticles", 0 ); +        trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_NEAREST" ); +      break; +    } +  } else if (Q_stricmp(name, "ui_mousePitch") == 0) { +    if (val == 0) { +      trap_Cvar_SetValue( "m_pitch", 0.022f ); +    } else { +      trap_Cvar_SetValue( "m_pitch", -0.022f ); +    } +  } +} + +static void UI_RunMenuScript(char **args) { +  const char *name, *name2; +  char buff[1024]; +  const char *cmd; + +  if (String_Parse(args, &name)) { +    if (Q_stricmp(name, "StartServer") == 0) { +      int i, clients, oldclients; +      float skill; +      trap_Cvar_Set("cg_thirdPerson", "0"); +      trap_Cvar_Set("cg_cameraOrbit", "0"); +      trap_Cvar_Set("ui_singlePlayerActive", "0"); +      trap_Cvar_SetValue( "dedicated", Com_Clamp( 0, 2, ui_dedicated.integer ) ); +      trap_Cvar_SetValue( "g_gametype", Com_Clamp( 0, 8, uiInfo.gameTypes[ui_netGameType.integer].gtEnum ) ); +      trap_Cvar_Set("g_redTeam", UI_Cvar_VariableString("ui_teamName")); +      trap_Cvar_Set("g_blueTeam", UI_Cvar_VariableString("ui_opponentName")); +      trap_Cmd_ExecuteText( EXEC_APPEND, va( "wait ; wait ; map %s\n", uiInfo.mapList[ui_currentNetMap.integer].mapLoadName ) ); +      skill = trap_Cvar_VariableValue( "g_spSkill" ); +      // set max clients based on spots +      oldclients = trap_Cvar_VariableValue( "sv_maxClients" ); +      clients = 0; +      for (i = 0; i < PLAYERS_PER_TEAM; i++) { +        int bot = trap_Cvar_VariableValue( va("ui_blueteam%i", i+1)); +        if (bot >= 0) { +          clients++; +        } +        bot = trap_Cvar_VariableValue( va("ui_redteam%i", i+1)); +        if (bot >= 0) { +          clients++; +        } +      } +      if (clients == 0) { +        clients = 8; +      } + +      if (oldclients > clients) { +        clients = oldclients; +      } + +      trap_Cvar_Set("sv_maxClients", va("%d",clients)); + +      for (i = 0; i < PLAYERS_PER_TEAM; i++) { +        int bot = trap_Cvar_VariableValue( va("ui_blueteam%i", i+1)); +        if (bot > 1) { +          Com_sprintf( buff, sizeof(buff), "addbot %s %f \n", UI_GetBotNameByNumber(bot-2), skill); +          trap_Cmd_ExecuteText( EXEC_APPEND, buff ); +        } +        bot = trap_Cvar_VariableValue( va("ui_redteam%i", i+1)); +        if (bot > 1) { +          Com_sprintf( buff, sizeof(buff), "addbot %s %f \n", UI_GetBotNameByNumber(bot-2), skill); +          trap_Cmd_ExecuteText( EXEC_APPEND, buff ); +        } +      } +    } else if (Q_stricmp(name, "updateSPMenu") == 0) { +      UI_SetCapFragLimits(qtrue); +      UI_MapCountByGameType(qtrue); +      ui_mapIndex.integer = UI_GetIndexFromSelection(ui_currentMap.integer); +      trap_Cvar_Set("ui_mapIndex", va("%d", ui_mapIndex.integer)); +      Menu_SetFeederSelection(NULL, FEEDER_MAPS, ui_mapIndex.integer, "skirmish"); +      UI_GameType_HandleKey(0, NULL, K_MOUSE1, qfalse); +      UI_GameType_HandleKey(0, NULL, K_MOUSE2, qfalse); +    } else if (Q_stricmp(name, "resetDefaults") == 0) { +      trap_Cmd_ExecuteText( EXEC_APPEND, "exec default.cfg\n"); +      trap_Cmd_ExecuteText( EXEC_APPEND, "cvar_restart\n"); +      Controls_SetDefaults(); +      trap_Cvar_Set("com_introPlayed", "1" ); +      trap_Cmd_ExecuteText( EXEC_APPEND, "vid_restart\n" ); +    } else if (Q_stricmp(name, "loadArenas") == 0) { +      UI_LoadArenas(); +      UI_MapCountByGameType(qfalse); +      Menu_SetFeederSelection(NULL, FEEDER_ALLMAPS, 0, "createserver"); +    } else if (Q_stricmp(name, "loadServerInfo") == 0) { +      UI_ServerInfo(); +    } else if (Q_stricmp(name, "saveControls") == 0) { +      Controls_SetConfig(qtrue); +    } else if (Q_stricmp(name, "loadControls") == 0) { +      Controls_GetConfig(); +    } else if (Q_stricmp(name, "clearError") == 0) { +      trap_Cvar_Set("com_errorMessage", ""); +    } else if (Q_stricmp(name, "loadGameInfo") == 0) { +/*      UI_ParseGameInfo("gameinfo.txt"); +      UI_LoadBestScores(uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum);*/ +    } else if (Q_stricmp(name, "resetScores") == 0) { +      UI_ClearScores(); +    } else if (Q_stricmp(name, "RefreshServers") == 0) { +      UI_StartServerRefresh(qtrue); +      UI_BuildServerDisplayList(qtrue); +    } else if (Q_stricmp(name, "InitServerList") == 0) { +      int time = trap_RealTime( NULL ); +      int last; +      int sortColumn; + +      // set up default sorting +      if(!uiInfo.serverStatus.sorted && Int_Parse(args, &sortColumn)) +      { +        uiInfo.serverStatus.sortKey = sortColumn; +	uiInfo.serverStatus.sortDir = 0; +      } + +      // refresh if older than 3 days or if list is empty +      last = atoi( UI_Cvar_VariableString( va( "ui_lastServerRefresh_%i_time", +        ui_netSource.integer ) ) ); +      if( trap_LAN_GetServerCount( ui_netSource.integer ) < 1 || +        ( time - last ) > 3600 ) +      { +        UI_StartServerRefresh(qtrue); +        UI_BuildServerDisplayList(qtrue); +      } +    } else if (Q_stricmp(name, "RefreshFilter") == 0) { +      UI_StartServerRefresh(qfalse); +      UI_BuildServerDisplayList(qtrue); +    } else if (Q_stricmp(name, "RunSPDemo") == 0) { +      if (uiInfo.demoAvailable) { +        trap_Cmd_ExecuteText( EXEC_APPEND, va("demo %s_%i\n", uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum)); +      } +    } else if (Q_stricmp(name, "LoadDemos") == 0) { +      UI_LoadDemos(); +    } else if (Q_stricmp(name, "LoadMovies") == 0) { +      UI_LoadMovies(); +    } else if (Q_stricmp(name, "LoadMods") == 0) { +      UI_LoadMods(); +    } + +//TA: tremulous menus +    else if( Q_stricmp( name, "LoadTeams" ) == 0 ) +      UI_LoadTremTeams( ); +    else if( Q_stricmp( name, "JoinTeam" ) == 0 ) +    { +      if( ( cmd = uiInfo.tremTeamList[ uiInfo.tremTeamIndex ].cmd ) ) +        trap_Cmd_ExecuteText( EXEC_APPEND, cmd ); +    } +    else if( Q_stricmp( name, "LoadHumanItems" ) == 0 ) +      UI_LoadTremHumanItems( ); +    else if( Q_stricmp( name, "SpawnWithHumanItem" ) == 0 ) +    { +      if( ( cmd = uiInfo.tremHumanItemList[ uiInfo.tremHumanItemIndex ].cmd ) ) +        trap_Cmd_ExecuteText( EXEC_APPEND, cmd ); +    } +    else if( Q_stricmp( name, "LoadAlienClasses" ) == 0 ) +      UI_LoadTremAlienClasses( ); +    else if( Q_stricmp( name, "SpawnAsAlienClass" ) == 0 ) +    { +      if( ( cmd = uiInfo.tremAlienClassList[ uiInfo.tremAlienClassIndex ].cmd ) ) +        trap_Cmd_ExecuteText( EXEC_APPEND, cmd ); +    } +    else if( Q_stricmp( name, "LoadHumanArmouryBuys" ) == 0 ) +      UI_LoadTremHumanArmouryBuys( ); +    else if( Q_stricmp( name, "BuyFromArmoury" ) == 0 ) +    { +      if( ( cmd = uiInfo.tremHumanArmouryBuyList[ uiInfo.tremHumanArmouryBuyIndex ].cmd ) ) +        trap_Cmd_ExecuteText( EXEC_APPEND, cmd ); +    } +    else if( Q_stricmp( name, "LoadHumanArmourySells" ) == 0 ) +      UI_LoadTremHumanArmourySells( ); +    else if( Q_stricmp( name, "SellToArmoury" ) == 0 ) +    { +      if( ( cmd = uiInfo.tremHumanArmourySellList[ uiInfo.tremHumanArmourySellIndex ].cmd ) ) +        trap_Cmd_ExecuteText( EXEC_APPEND, cmd ); +    } +    else if( Q_stricmp( name, "LoadAlienUpgrades" ) == 0 ) +    { +      UI_LoadTremAlienUpgrades( ); + +      //disallow the menu if it would be empty +      if( uiInfo.tremAlienUpgradeCount <= 0 ) +        Menus_CloseAll( ); +    } +    else if( Q_stricmp( name, "UpgradeToNewClass" ) == 0 ) +    { +      if( ( cmd = uiInfo.tremAlienUpgradeList[ uiInfo.tremAlienUpgradeIndex ].cmd ) ) +        trap_Cmd_ExecuteText( EXEC_APPEND, cmd ); +    } +    else if( Q_stricmp( name, "LoadAlienBuilds" ) == 0 ) +      UI_LoadTremAlienBuilds( ); +    else if( Q_stricmp( name, "BuildAlienBuildable" ) == 0 ) +    { +      if( ( cmd = uiInfo.tremAlienBuildList[ uiInfo.tremAlienBuildIndex ].cmd ) ) +        trap_Cmd_ExecuteText( EXEC_APPEND, cmd ); +    } +    else if( Q_stricmp( name, "LoadHumanBuilds" ) == 0 ) +      UI_LoadTremHumanBuilds( ); +    else if( Q_stricmp( name, "BuildHumanBuildable" ) == 0 ) +    { +      if( ( cmd = uiInfo.tremHumanBuildList[ uiInfo.tremHumanBuildIndex ].cmd ) ) +        trap_Cmd_ExecuteText( EXEC_APPEND, cmd ); +    } +    else if( Q_stricmp( name, "PTRCRestore" ) == 0 ) +    { +      int           len; +      char          text[ 16 ]; +      fileHandle_t  f; +      char          command[ 32 ]; + +      // load the file +      len = trap_FS_FOpenFile( "ptrc.cfg", &f, FS_READ ); + +      if( len > 0 && ( len < sizeof( text ) - 1 ) ) +      { +        trap_FS_Read( text, len, f ); +        text[ len ] = 0; +        trap_FS_FCloseFile( f ); + +        Com_sprintf( command, 32, "ptrcrestore %s", text ); + +        trap_Cmd_ExecuteText( EXEC_APPEND, command ); +      } +    } +//TA: tremulous menus + +    else if (Q_stricmp(name, "playMovie") == 0) { +      if (uiInfo.previewMovie >= 0) { +        trap_CIN_StopCinematic(uiInfo.previewMovie); +      } +      trap_Cmd_ExecuteText( EXEC_APPEND, va("cinematic %s.roq 2\n", uiInfo.movieList[uiInfo.movieIndex])); +    } else if (Q_stricmp(name, "RunMod") == 0) { +      trap_Cvar_Set( "fs_game", uiInfo.modList[uiInfo.modIndex].modName); +      trap_Cmd_ExecuteText( EXEC_APPEND, "vid_restart;" ); +    } else if (Q_stricmp(name, "RunDemo") == 0) { +      trap_Cmd_ExecuteText( EXEC_APPEND, va("demo %s\n", uiInfo.demoList[uiInfo.demoIndex])); +    } else if (Q_stricmp(name, "Tremulous") == 0) { +      trap_Cvar_Set( "fs_game", ""); +      trap_Cmd_ExecuteText( EXEC_APPEND, "vid_restart;" ); +    } else if (Q_stricmp(name, "closeJoin") == 0) { +      if (uiInfo.serverStatus.refreshActive) { +        UI_StopServerRefresh(); +        uiInfo.serverStatus.nextDisplayRefresh = 0; +        uiInfo.nextServerStatusRefresh = 0; +        uiInfo.nextFindPlayerRefresh = 0; +        UI_BuildServerDisplayList(qtrue); +      } else { +        Menus_CloseByName("joinserver"); +        Menus_OpenByName("main"); +      } +    } else if (Q_stricmp(name, "StopRefresh") == 0) { +      UI_StopServerRefresh(); +      uiInfo.serverStatus.nextDisplayRefresh = 0; +      uiInfo.nextServerStatusRefresh = 0; +      uiInfo.nextFindPlayerRefresh = 0; +    } else if (Q_stricmp(name, "UpdateFilter") == 0) { +      if (ui_netSource.integer == AS_LOCAL) { +        UI_StartServerRefresh(qtrue); +      } +      UI_BuildServerDisplayList(qtrue); +      UI_FeederSelection(FEEDER_SERVERS, 0); +    } else if (Q_stricmp(name, "ServerStatus") == 0) { +      trap_LAN_GetServerAddressString(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], uiInfo.serverStatusAddress, sizeof(uiInfo.serverStatusAddress)); +      UI_BuildServerStatus(qtrue); +    } else if (Q_stricmp(name, "FoundPlayerServerStatus") == 0) { +      Q_strncpyz(uiInfo.serverStatusAddress, uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer], sizeof(uiInfo.serverStatusAddress)); +      UI_BuildServerStatus(qtrue); +      Menu_SetFeederSelection(NULL, FEEDER_FINDPLAYER, 0, NULL); +    } else if (Q_stricmp(name, "FindPlayer") == 0) { +      UI_BuildFindPlayerList(qtrue); +      // clear the displayed server status info +      uiInfo.serverStatusInfo.numLines = 0; +      Menu_SetFeederSelection(NULL, FEEDER_FINDPLAYER, 0, NULL); +    } else if (Q_stricmp(name, "JoinServer") == 0) { +      trap_Cvar_Set("cg_thirdPerson", "0"); +      trap_Cvar_Set("cg_cameraOrbit", "0"); +      trap_Cvar_Set("ui_singlePlayerActive", "0"); +      if (uiInfo.serverStatus.currentServer >= 0 && uiInfo.serverStatus.currentServer < uiInfo.serverStatus.numDisplayServers) { +        trap_LAN_GetServerAddressString(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], buff, 1024); +        trap_Cmd_ExecuteText( EXEC_APPEND, va( "connect %s\n", buff ) ); +      } +    } else if (Q_stricmp(name, "FoundPlayerJoinServer") == 0) { +      trap_Cvar_Set("ui_singlePlayerActive", "0"); +      if (uiInfo.currentFoundPlayerServer >= 0 && uiInfo.currentFoundPlayerServer < uiInfo.numFoundPlayerServers) { +        trap_Cmd_ExecuteText( EXEC_APPEND, va( "connect %s\n", uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer] ) ); +      } +    } else if (Q_stricmp(name, "Quit") == 0) { +      trap_Cvar_Set("ui_singlePlayerActive", "0"); +      trap_Cmd_ExecuteText( EXEC_NOW, "quit"); +    } else if (Q_stricmp(name, "Controls") == 0) { +      trap_Cvar_Set( "cl_paused", "1" ); +      trap_Key_SetCatcher( KEYCATCH_UI ); +      Menus_CloseAll(); +      Menus_ActivateByName("setup_menu2"); +    } else if (Q_stricmp(name, "Leave") == 0) { +      trap_Cmd_ExecuteText( EXEC_APPEND, "disconnect\n" ); +      trap_Key_SetCatcher( KEYCATCH_UI ); +      Menus_CloseAll(); +      Menus_ActivateByName("main"); +    } else if (Q_stricmp(name, "ServerSort") == 0) { +      int sortColumn; +      if (Int_Parse(args, &sortColumn)) { +        // if same column we're already sorting on then flip the direction +        if (sortColumn == uiInfo.serverStatus.sortKey) { +          uiInfo.serverStatus.sortDir = !uiInfo.serverStatus.sortDir; +        } +        // make sure we sort again +        UI_ServersSort(sortColumn, qtrue); +        uiInfo.serverStatus.sorted = qtrue; +      } +    } else if (Q_stricmp(name, "nextSkirmish") == 0) { +      UI_StartSkirmish(qtrue); +    } else if (Q_stricmp(name, "SkirmishStart") == 0) { +      UI_StartSkirmish(qfalse); +    } else if (Q_stricmp(name, "closeingame") == 0) { +      trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); +      trap_Key_ClearStates(); +      trap_Cvar_Set( "cl_paused", "0" ); +      Menus_CloseAll(); +    } else if (Q_stricmp(name, "voteMap") == 0) { +      if (ui_currentNetMap.integer >=0 && ui_currentNetMap.integer < uiInfo.mapCount) { +        trap_Cmd_ExecuteText( EXEC_APPEND, va("callvote map %s\n",uiInfo.mapList[ui_currentNetMap.integer].mapLoadName) ); +      } +    } +    else if( Q_stricmp( name, "voteKick" ) == 0 ) +    { +      if( uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount ) +      { +        trap_Cmd_ExecuteText( EXEC_APPEND, va( "callvote kick %d\n", +              uiInfo.clientNums[ uiInfo.playerIndex ] ) ); +      } +    } +    else if( Q_stricmp( name, "voteMute" ) == 0 ) +    { +      if( uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount ) +      { +        trap_Cmd_ExecuteText( EXEC_APPEND, va( "callvote mute %d\n", +              uiInfo.clientNums[ uiInfo.playerIndex ] ) ); +      } +    } +    else if( Q_stricmp( name, "voteUnMute" ) == 0 ) +    { +      if( uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount ) +      { +        trap_Cmd_ExecuteText( EXEC_APPEND, va( "callvote unmute %d\n", +              uiInfo.clientNums[ uiInfo.playerIndex ] ) ); +      } +    } +    else if( Q_stricmp( name, "voteTeamKick" ) == 0 ) +    { +      if( uiInfo.teamIndex >= 0 && uiInfo.teamIndex < uiInfo.myTeamCount ) +      { +        trap_Cmd_ExecuteText( EXEC_APPEND, va( "callteamvote kick %d\n", +              uiInfo.teamClientNums[ uiInfo.teamIndex ] ) ); +      } +    } +    else if( Q_stricmp( name, "voteTeamDenyBuild" ) == 0 ) +    { +      if( uiInfo.teamIndex >= 0 && uiInfo.teamIndex < uiInfo.myTeamCount ) +      { +        trap_Cmd_ExecuteText( EXEC_APPEND, va( "callteamvote denybuild %d\n", +              uiInfo.teamClientNums[ uiInfo.teamIndex ] ) ); +      } +    } +    else if( Q_stricmp( name, "voteTeamAllowBuild" ) == 0 ) +    { +      if( uiInfo.teamIndex >= 0 && uiInfo.teamIndex < uiInfo.myTeamCount ) +      { +        trap_Cmd_ExecuteText( EXEC_APPEND, va( "callteamvote allowbuild %d\n", +              uiInfo.teamClientNums[ uiInfo.teamIndex ] ) ); +      } +    } +    else if (Q_stricmp(name, "addFavorite") == 0) { +      if (ui_netSource.integer != AS_FAVORITES) { +        char name[MAX_NAME_LENGTH]; +        char addr[MAX_NAME_LENGTH]; +        int res; + +        trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], buff, MAX_STRING_CHARS); +        name[0] = addr[0] = '\0'; +        Q_strncpyz(name,  Info_ValueForKey(buff, "hostname"), MAX_NAME_LENGTH); +        Q_strncpyz(addr,  Info_ValueForKey(buff, "addr"), MAX_NAME_LENGTH); +        if (strlen(name) > 0 && strlen(addr) > 0) { +          res = trap_LAN_AddServer(AS_FAVORITES, name, addr); +          if (res == 0) { +            // server already in the list +            Com_Printf("Favorite already in list\n"); +          } +          else if (res == -1) { +            // list full +            Com_Printf("Favorite list full\n"); +          } +          else { +            // successfully added +            Com_Printf("Added favorite server %s\n", addr); +          } +        } +      } +    } else if (Q_stricmp(name, "deleteFavorite") == 0) { +      if (ui_netSource.integer == AS_FAVORITES) { +        char addr[MAX_NAME_LENGTH]; +        trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], buff, MAX_STRING_CHARS); +        addr[0] = '\0'; +        Q_strncpyz(addr,  Info_ValueForKey(buff, "addr"), MAX_NAME_LENGTH); +        if (strlen(addr) > 0) { +          trap_LAN_RemoveServer(AS_FAVORITES, addr); +        } +      } +    } else if (Q_stricmp(name, "createFavorite") == 0) { +      if (ui_netSource.integer == AS_FAVORITES) { +        char name[MAX_NAME_LENGTH]; +        char addr[MAX_NAME_LENGTH]; +        int res; + +        name[0] = addr[0] = '\0'; +        Q_strncpyz(name,  UI_Cvar_VariableString("ui_favoriteName"), MAX_NAME_LENGTH); +        Q_strncpyz(addr,  UI_Cvar_VariableString("ui_favoriteAddress"), MAX_NAME_LENGTH); +        if (strlen(name) > 0 && strlen(addr) > 0) { +          res = trap_LAN_AddServer(AS_FAVORITES, name, addr); +          if (res == 0) { +            // server already in the list +            Com_Printf("Favorite already in list\n"); +          } +          else if (res == -1) { +            // list full +            Com_Printf("Favorite list full\n"); +          } +          else { +            // successfully added +            Com_Printf("Added favorite server %s\n", addr); +          } +        } +      } +    } else if (Q_stricmp(name, "orders") == 0) { +      const char *orders; +      if (String_Parse(args, &orders)) { +        int selectedPlayer = trap_Cvar_VariableValue("cg_selectedPlayer"); +        if (selectedPlayer < uiInfo.myTeamCount) { +          strcpy(buff, orders); +          trap_Cmd_ExecuteText( EXEC_APPEND, va(buff, uiInfo.teamClientNums[selectedPlayer]) ); +          trap_Cmd_ExecuteText( EXEC_APPEND, "\n" ); +        } else { +          int i; +          for (i = 0; i < uiInfo.myTeamCount; i++) { +            if (Q_stricmp(UI_Cvar_VariableString("name"), uiInfo.teamNames[i]) == 0) { +              continue; +            } +            strcpy(buff, orders); +            trap_Cmd_ExecuteText( EXEC_APPEND, va(buff, uiInfo.teamNames[i]) ); +            trap_Cmd_ExecuteText( EXEC_APPEND, "\n" ); +          } +        } +        trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); +        trap_Key_ClearStates(); +        trap_Cvar_Set( "cl_paused", "0" ); +        Menus_CloseAll(); +      } +    } else if (Q_stricmp(name, "voiceOrdersTeam") == 0) { +      const char *orders; +      if (String_Parse(args, &orders)) { +        int selectedPlayer = trap_Cvar_VariableValue("cg_selectedPlayer"); +        if (selectedPlayer == uiInfo.myTeamCount) { +          trap_Cmd_ExecuteText( EXEC_APPEND, orders ); +          trap_Cmd_ExecuteText( EXEC_APPEND, "\n" ); +        } +        trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); +        trap_Key_ClearStates(); +        trap_Cvar_Set( "cl_paused", "0" ); +        Menus_CloseAll(); +      } +    } else if (Q_stricmp(name, "voiceOrders") == 0) { +      const char *orders; +      if (String_Parse(args, &orders)) { +        int selectedPlayer = trap_Cvar_VariableValue("cg_selectedPlayer"); +        if (selectedPlayer < uiInfo.myTeamCount) { +          strcpy(buff, orders); +          trap_Cmd_ExecuteText( EXEC_APPEND, va(buff, uiInfo.teamClientNums[selectedPlayer]) ); +          trap_Cmd_ExecuteText( EXEC_APPEND, "\n" ); +        } +        trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); +        trap_Key_ClearStates(); +        trap_Cvar_Set( "cl_paused", "0" ); +        Menus_CloseAll(); +      } +    } else if (Q_stricmp(name, "glCustom") == 0) { +      trap_Cvar_Set("ui_glCustom", "4"); +    } else if (Q_stricmp(name, "update") == 0) { +      if (String_Parse(args, &name2)) +        UI_Update(name2); +    } else if (Q_stricmp(name, "InitIgnoreList") == 0) { +      UI_BuildPlayerList(); +    } else if (Q_stricmp(name, "ToggleIgnore") == 0) { +      if( uiInfo.ignoreIndex >= 0 && uiInfo.ignoreIndex < uiInfo.playerCount ) +      { +        if( BG_ClientListTest( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ], +          uiInfo.clientNums[ uiInfo.ignoreIndex ] ) ) +        { +          BG_ClientListRemove( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ], +            uiInfo.clientNums[ uiInfo.ignoreIndex ] ); +          trap_Cmd_ExecuteText( EXEC_NOW, va( "unignore %i\n",  +            uiInfo.clientNums[ uiInfo.ignoreIndex ] ) ); +        } +        else +        { +          BG_ClientListAdd( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ], +            uiInfo.clientNums[ uiInfo.ignoreIndex ] ); +          trap_Cmd_ExecuteText( EXEC_NOW, va( "ignore %i\n",  +            uiInfo.clientNums[ uiInfo.ignoreIndex ] ) ); +        } +      } +    } else if (Q_stricmp(name, "IgnorePlayer") == 0) { +      if( uiInfo.ignoreIndex >= 0 && uiInfo.ignoreIndex < uiInfo.playerCount ) +      { +        if( !BG_ClientListTest( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ], +          uiInfo.clientNums[ uiInfo.ignoreIndex ] ) ) +        { +          BG_ClientListAdd( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ], +            uiInfo.clientNums[ uiInfo.ignoreIndex ] ); +          trap_Cmd_ExecuteText( EXEC_NOW, va( "ignore %i\n",  +            uiInfo.clientNums[ uiInfo.ignoreIndex ] ) ); +        } +      } +    } else if (Q_stricmp(name, "UnIgnorePlayer") == 0) { +      if( uiInfo.ignoreIndex >= 0 && uiInfo.ignoreIndex < uiInfo.playerCount ) +      { +        if( BG_ClientListTest( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ], +          uiInfo.clientNums[ uiInfo.ignoreIndex ] ) ) +        { +          BG_ClientListRemove( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ], +            uiInfo.clientNums[ uiInfo.ignoreIndex ] ); +          trap_Cmd_ExecuteText( EXEC_NOW, va( "unignore %i\n",  +            uiInfo.clientNums[ uiInfo.ignoreIndex ] ) ); +        } +      } +    } else if (Q_stricmp(name, "setPbClStatus") == 0) { +      int stat; +      if ( Int_Parse( args, &stat ) ) +        trap_SetPbClStatus( stat ); +    } +    else { +      Com_Printf("unknown UI script %s\n", name); +    } +  } +} + +static void UI_GetTeamColor(vec4_t *color) { +} + +/* +================== +UI_MapCountByGameType +================== +*/ +static int UI_MapCountByGameType(qboolean singlePlayer) { +  int i, c, game; +  c = 0; +  game = singlePlayer ? uiInfo.gameTypes[ui_gameType.integer].gtEnum : uiInfo.gameTypes[ui_netGameType.integer].gtEnum; + +  for (i = 0; i < uiInfo.mapCount; i++) { +    uiInfo.mapList[i].active = qfalse; +    if ( uiInfo.mapList[i].typeBits & (1 << game)) { +      if (singlePlayer) { +        if (!(uiInfo.mapList[i].typeBits & (1 << 2))) { +          continue; +        } +      } +      c++; +      uiInfo.mapList[i].active = qtrue; +    } +  } +  return c; +} + +qboolean UI_hasSkinForBase(const char *base, const char *team) { +  char  test[1024]; + +  Com_sprintf( test, sizeof( test ), "models/players/%s/%s/lower_default.skin", base, team ); + +  if (trap_FS_FOpenFile(test, NULL, FS_READ)) { +    return qtrue; +  } +  Com_sprintf( test, sizeof( test ), "models/players/characters/%s/%s/lower_default.skin", base, team ); + +  if (trap_FS_FOpenFile(test, NULL, FS_READ)) { +    return qtrue; +  } +  return qfalse; +} + +/* +================== +UI_MapCountByTeam +================== +*/ +static int UI_HeadCountByTeam( void ) { +  static int init = 0; +  int i, j, k, c, tIndex; + +  c = 0; +  if (!init) { +    for (i = 0; i < uiInfo.characterCount; i++) { +      uiInfo.characterList[i].reference = 0; +      for (j = 0; j < uiInfo.teamCount; j++) { +        if (UI_hasSkinForBase(uiInfo.characterList[i].base, uiInfo.teamList[j].teamName)) { +          uiInfo.characterList[i].reference |= (1<<j); +        } +      } +    } +    init = 1; +  } + +  tIndex = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); + +  // do names +  for (i = 0; i < uiInfo.characterCount; i++) { +    uiInfo.characterList[i].active = qfalse; +    for(j = 0; j < TEAM_MEMBERS; j++) { +      if (uiInfo.teamList[tIndex].teamMembers[j] != NULL) { +        if (uiInfo.characterList[i].reference&(1<<tIndex)) {// && Q_stricmp(uiInfo.teamList[tIndex].teamMembers[j], uiInfo.characterList[i].name)==0) { +          uiInfo.characterList[i].active = qtrue; +          c++; +          break; +        } +      } +    } +  } + +  // and then aliases +  for(j = 0; j < TEAM_MEMBERS; j++) { +    for(k = 0; k < uiInfo.aliasCount; k++) { +      if (uiInfo.aliasList[k].name != NULL) { +        if (Q_stricmp(uiInfo.teamList[tIndex].teamMembers[j], uiInfo.aliasList[k].name)==0) { +          for (i = 0; i < uiInfo.characterCount; i++) { +            if (uiInfo.characterList[i].headImage != -1 && uiInfo.characterList[i].reference&(1<<tIndex) && Q_stricmp(uiInfo.aliasList[k].ai, uiInfo.characterList[i].name)==0) { +              if (uiInfo.characterList[i].active == qfalse) { +                uiInfo.characterList[i].active = qtrue; +                c++; +              } +              break; +            } +          } +        } +      } +    } +  } +  return c; +} + +/* +================== +UI_InsertServerIntoDisplayList +================== +*/ +static void UI_InsertServerIntoDisplayList(int num, int position) { +  int i; + +  if (position < 0 || position > uiInfo.serverStatus.numDisplayServers ) { +    return; +  } +  // +  uiInfo.serverStatus.numDisplayServers++; +  for (i = uiInfo.serverStatus.numDisplayServers; i > position; i--) { +    uiInfo.serverStatus.displayServers[i] = uiInfo.serverStatus.displayServers[i-1]; +  } +  uiInfo.serverStatus.displayServers[position] = num; +} + +/* +================== +UI_RemoveServerFromDisplayList +================== +*/ +static void UI_RemoveServerFromDisplayList(int num) { +  int i, j; + +  for (i = 0; i < uiInfo.serverStatus.numDisplayServers; i++) { +    if (uiInfo.serverStatus.displayServers[i] == num) { +      uiInfo.serverStatus.numDisplayServers--; +      for (j = i; j < uiInfo.serverStatus.numDisplayServers; j++) { +        uiInfo.serverStatus.displayServers[j] = uiInfo.serverStatus.displayServers[j+1]; +      } +      return; +    } +  } +} + +/* +================== +UI_BinaryServerInsertion +================== +*/ +static void UI_BinaryServerInsertion(int num) { +  int mid, offset, res, len; + +  // use binary search to insert server +  len = uiInfo.serverStatus.numDisplayServers; +  mid = len; +  offset = 0; +  res = 0; +  while(mid > 0) { +    mid = len >> 1; +    // +    res = trap_LAN_CompareServers( ui_netSource.integer, uiInfo.serverStatus.sortKey, +          uiInfo.serverStatus.sortDir, num, uiInfo.serverStatus.displayServers[offset+mid]); +    // if equal +    if (res == 0) { +      UI_InsertServerIntoDisplayList(num, offset+mid); +      return; +    } +    // if larger +    else if (res == 1) { +      offset += mid; +      len -= mid; +    } +    // if smaller +    else { +      len -= mid; +    } +  } +  if (res == 1) { +    offset++; +  } +  UI_InsertServerIntoDisplayList(num, offset); +} + +/* +================== +UI_BuildServerDisplayList +================== +*/ +static void UI_BuildServerDisplayList(qboolean force) { +  int i, count, clients, maxClients, ping, game, len, visible; +  char info[MAX_STRING_CHARS]; +//  qboolean startRefresh = qtrue; TTimo: unused +  static int numinvisible; + +  if (!(force || uiInfo.uiDC.realTime > uiInfo.serverStatus.nextDisplayRefresh)) { +    return; +  } +  // if we shouldn't reset +  if ( force == 2 ) { +    force = 0; +  } + +  // do motd updates here too +  trap_Cvar_VariableStringBuffer( "cl_motdString", uiInfo.serverStatus.motd, sizeof(uiInfo.serverStatus.motd) ); +  len = strlen(uiInfo.serverStatus.motd); +  if (len != uiInfo.serverStatus.motdLen) { +    uiInfo.serverStatus.motdLen = len; +    uiInfo.serverStatus.motdWidth = -1; +  } + +  if (force) { +    numinvisible = 0; +    // clear number of displayed servers +    uiInfo.serverStatus.numDisplayServers = 0; +    uiInfo.serverStatus.numPlayersOnServers = 0; +    // set list box index to zero +    Menu_SetFeederSelection(NULL, FEEDER_SERVERS, 0, NULL); +    // mark all servers as visible so we store ping updates for them +    trap_LAN_MarkServerVisible(ui_netSource.integer, -1, qtrue); +  } + +  // get the server count (comes from the master) +  count = trap_LAN_GetServerCount(ui_netSource.integer); +  if (count == -1 || (ui_netSource.integer == AS_LOCAL && count == 0) ) { +    // still waiting on a response from the master +    uiInfo.serverStatus.numDisplayServers = 0; +    uiInfo.serverStatus.numPlayersOnServers = 0; +    uiInfo.serverStatus.nextDisplayRefresh = uiInfo.uiDC.realTime + 500; +    return; +  } + +  visible = qfalse; +  for (i = 0; i < count; i++) { +    // if we already got info for this server +    if (!trap_LAN_ServerIsVisible(ui_netSource.integer, i)) { +      continue; +    } +    visible = qtrue; +    // get the ping for this server +    ping = trap_LAN_GetServerPing(ui_netSource.integer, i); +    if (ping > 0 || ui_netSource.integer == AS_FAVORITES) { + +      trap_LAN_GetServerInfo(ui_netSource.integer, i, info, MAX_STRING_CHARS); + +      clients = atoi(Info_ValueForKey(info, "clients")); +      uiInfo.serverStatus.numPlayersOnServers += clients; + +      if (ui_browserShowEmpty.integer == 0) { +        if (clients == 0) { +          trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse); +          continue; +        } +      } + +      if (ui_browserShowFull.integer == 0) { +        maxClients = atoi(Info_ValueForKey(info, "sv_maxclients")); +        if (clients == maxClients) { +          trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse); +          continue; +        } +      } + +      if (uiInfo.joinGameTypes[ui_joinGameType.integer].gtEnum != -1) { +        game = atoi(Info_ValueForKey(info, "gametype")); +        if (game != uiInfo.joinGameTypes[ui_joinGameType.integer].gtEnum) { +          trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse); +          continue; +        } +      } + +      // make sure we never add a favorite server twice +      if (ui_netSource.integer == AS_FAVORITES) { +        UI_RemoveServerFromDisplayList(i); +      } +      // insert the server into the list +      UI_BinaryServerInsertion(i); +      // done with this server +      if (ping > 0) { +        trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse); +        numinvisible++; +      } +    } +  } + +  uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime; + +  // if there were no servers visible for ping updates +  if (!visible) { +//    UI_StopServerRefresh(); +//    uiInfo.serverStatus.nextDisplayRefresh = 0; +  } +} + +typedef struct +{ +  char *name, *altName; +} serverStatusCvar_t; + +serverStatusCvar_t serverStatusCvars[] = { +  {"sv_hostname", "Name"}, +  {"Address", ""}, +  {"gamename", "Game name"}, +  {"g_gametype", "Game type"}, +  {"mapname", "Map"}, +  {"version", ""}, +  {"protocol", ""}, +  {"timelimit", ""}, +  {"fraglimit", ""}, +  {NULL, NULL} +}; + +/* +================== +UI_SortServerStatusInfo +================== +*/ +static void UI_SortServerStatusInfo( serverStatusInfo_t *info ) { +  int i, j, index; +  char *tmp1, *tmp2; + +  // FIXME: if "gamename" == "baseq3" or "missionpack" then +  // replace the gametype number by FFA, CTF etc. +  // +  index = 0; +  for (i = 0; serverStatusCvars[i].name; i++) { +    for (j = 0; j < info->numLines; j++) { +      if ( !info->lines[j][1] || info->lines[j][1][0] ) { +        continue; +      } +      if ( !Q_stricmp(serverStatusCvars[i].name, info->lines[j][0]) ) { +        // swap lines +        tmp1 = info->lines[index][0]; +        tmp2 = info->lines[index][3]; +        info->lines[index][0] = info->lines[j][0]; +        info->lines[index][3] = info->lines[j][3]; +        info->lines[j][0] = tmp1; +        info->lines[j][3] = tmp2; +        // +        if ( strlen(serverStatusCvars[i].altName) ) { +          info->lines[index][0] = serverStatusCvars[i].altName; +        } +        index++; +      } +    } +  } +} + +/* +================== +UI_GetServerStatusInfo +================== +*/ +static int UI_GetServerStatusInfo( const char *serverAddress, serverStatusInfo_t *info ) { +  char *p, *score, *ping, *name; +  int i, len; + +  if (!info) { +    trap_LAN_ServerStatus( serverAddress, NULL, 0); +    return qfalse; +  } +  memset(info, 0, sizeof(*info)); +  if ( trap_LAN_ServerStatus( serverAddress, info->text, sizeof(info->text)) ) { +    Q_strncpyz(info->address, serverAddress, sizeof(info->address)); +    p = info->text; +    info->numLines = 0; +    info->lines[info->numLines][0] = "Address"; +    info->lines[info->numLines][1] = ""; +    info->lines[info->numLines][2] = ""; +    info->lines[info->numLines][3] = info->address; +    info->numLines++; +    // get the cvars +    while (p && *p) { +      p = strchr(p, '\\'); +      if (!p) break; +      *p++ = '\0'; +      if (*p == '\\') +        break; +      info->lines[info->numLines][0] = p; +      info->lines[info->numLines][1] = ""; +      info->lines[info->numLines][2] = ""; +      p = strchr(p, '\\'); +      if (!p) break; +      *p++ = '\0'; +      info->lines[info->numLines][3] = p; + +      info->numLines++; +      if (info->numLines >= MAX_SERVERSTATUS_LINES) +        break; +    } +    // get the player list +    if (info->numLines < MAX_SERVERSTATUS_LINES-3) { +      // empty line +      info->lines[info->numLines][0] = ""; +      info->lines[info->numLines][1] = ""; +      info->lines[info->numLines][2] = ""; +      info->lines[info->numLines][3] = ""; +      info->numLines++; +      // header +      info->lines[info->numLines][0] = "num"; +      info->lines[info->numLines][1] = "score"; +      info->lines[info->numLines][2] = "ping"; +      info->lines[info->numLines][3] = "name"; +      info->numLines++; +      // parse players +      i = 0; +      len = 0; +      while (p && *p) { +        if (*p == '\\') +          *p++ = '\0'; +        if (!p) +          break; +        score = p; +        p = strchr(p, ' '); +        if (!p) +          break; +        *p++ = '\0'; +        ping = p; +        p = strchr(p, ' '); +        if (!p) +          break; +        *p++ = '\0'; +        name = p; +        Com_sprintf(&info->pings[len], sizeof(info->pings)-len, "%d", i); +        info->lines[info->numLines][0] = &info->pings[len]; +        len += strlen(&info->pings[len]) + 1; +        info->lines[info->numLines][1] = score; +        info->lines[info->numLines][2] = ping; +        info->lines[info->numLines][3] = name; +        info->numLines++; +        if (info->numLines >= MAX_SERVERSTATUS_LINES) +          break; +        p = strchr(p, '\\'); +        if (!p) +          break; +        *p++ = '\0'; +        // +        i++; +      } +    } +    UI_SortServerStatusInfo( info ); +    return qtrue; +  } +  return qfalse; +} + +/* +================== +stristr +================== +*/ +static char *stristr(char *str, char *charset) { +  int i; + +  while(*str) { +    for (i = 0; charset[i] && str[i]; i++) { +      if (toupper(charset[i]) != toupper(str[i])) break; +    } +    if (!charset[i]) return str; +    str++; +  } +  return NULL; +} + +/* +================== +UI_BuildFindPlayerList +================== +*/ +static void UI_BuildFindPlayerList(qboolean force) { +  static int numFound, numTimeOuts; +  int i, j, resend; +  serverStatusInfo_t info; +  char name[MAX_NAME_LENGTH+2]; +  char infoString[MAX_STRING_CHARS]; + +  if (!force) { +    if (!uiInfo.nextFindPlayerRefresh || uiInfo.nextFindPlayerRefresh > uiInfo.uiDC.realTime) { +      return; +    } +  } +  else { +    memset(&uiInfo.pendingServerStatus, 0, sizeof(uiInfo.pendingServerStatus)); +    uiInfo.numFoundPlayerServers = 0; +    uiInfo.currentFoundPlayerServer = 0; +    trap_Cvar_VariableStringBuffer( "ui_findPlayer", uiInfo.findPlayerName, sizeof(uiInfo.findPlayerName)); +    Q_CleanStr(uiInfo.findPlayerName); +    // should have a string of some length +    if (!strlen(uiInfo.findPlayerName)) { +      uiInfo.nextFindPlayerRefresh = 0; +      return; +    } +    // set resend time +    resend = ui_serverStatusTimeOut.integer / 2 - 10; +    if (resend < 50) { +      resend = 50; +    } +    trap_Cvar_Set("cl_serverStatusResendTime", va("%d", resend)); +    // reset all server status requests +    trap_LAN_ServerStatus( NULL, NULL, 0); +    // +    uiInfo.numFoundPlayerServers = 1; +    Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], +            sizeof(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1]), +              "searching %d...", uiInfo.pendingServerStatus.num); +    numFound = 0; +    numTimeOuts++; +  } +  for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) { +    // if this pending server is valid +    if (uiInfo.pendingServerStatus.server[i].valid) { +      // try to get the server status for this server +      if (UI_GetServerStatusInfo( uiInfo.pendingServerStatus.server[i].adrstr, &info ) ) { +        // +        numFound++; +        // parse through the server status lines +        for (j = 0; j < info.numLines; j++) { +          // should have ping info +          if ( !info.lines[j][2] || !info.lines[j][2][0] ) { +            continue; +          } +          // clean string first +          Q_strncpyz(name, info.lines[j][3], sizeof(name)); +          Q_CleanStr(name); +          // if the player name is a substring +          if (stristr(name, uiInfo.findPlayerName)) { +            // add to found server list if we have space (always leave space for a line with the number found) +            if (uiInfo.numFoundPlayerServers < MAX_FOUNDPLAYER_SERVERS-1) { +              // +              Q_strncpyz(uiInfo.foundPlayerServerAddresses[uiInfo.numFoundPlayerServers-1], +                    uiInfo.pendingServerStatus.server[i].adrstr, +                      sizeof(uiInfo.foundPlayerServerAddresses[0])); +              Q_strncpyz(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], +                    uiInfo.pendingServerStatus.server[i].name, +                      sizeof(uiInfo.foundPlayerServerNames[0])); +              uiInfo.numFoundPlayerServers++; +            } +            else { +              // can't add any more so we're done +              uiInfo.pendingServerStatus.num = uiInfo.serverStatus.numDisplayServers; +            } +          } +        } +        Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], +                sizeof(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1]), +                  "searching %d/%d...", uiInfo.pendingServerStatus.num, numFound); +        // retrieved the server status so reuse this spot +        uiInfo.pendingServerStatus.server[i].valid = qfalse; +      } +    } +    // if empty pending slot or timed out +    if (!uiInfo.pendingServerStatus.server[i].valid || +      uiInfo.pendingServerStatus.server[i].startTime < uiInfo.uiDC.realTime - ui_serverStatusTimeOut.integer) { +      if (uiInfo.pendingServerStatus.server[i].valid) { +        numTimeOuts++; +      } +      // reset server status request for this address +      UI_GetServerStatusInfo( uiInfo.pendingServerStatus.server[i].adrstr, NULL ); +      // reuse pending slot +      uiInfo.pendingServerStatus.server[i].valid = qfalse; +      // if we didn't try to get the status of all servers in the main browser yet +      if (uiInfo.pendingServerStatus.num < uiInfo.serverStatus.numDisplayServers) { +        uiInfo.pendingServerStatus.server[i].startTime = uiInfo.uiDC.realTime; +        trap_LAN_GetServerAddressString(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.pendingServerStatus.num], +              uiInfo.pendingServerStatus.server[i].adrstr, sizeof(uiInfo.pendingServerStatus.server[i].adrstr)); +        trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.pendingServerStatus.num], infoString, sizeof(infoString)); +        Q_strncpyz(uiInfo.pendingServerStatus.server[i].name, Info_ValueForKey(infoString, "hostname"), sizeof(uiInfo.pendingServerStatus.server[0].name)); +        uiInfo.pendingServerStatus.server[i].valid = qtrue; +        uiInfo.pendingServerStatus.num++; +        Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], +                sizeof(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1]), +                  "searching %d/%d...", uiInfo.pendingServerStatus.num, numFound); +      } +    } +  } +  for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) { +    if (uiInfo.pendingServerStatus.server[i].valid) { +      break; +    } +  } +  // if still trying to retrieve server status info +  if (i < MAX_SERVERSTATUSREQUESTS) { +    uiInfo.nextFindPlayerRefresh = uiInfo.uiDC.realTime + 25; +  } +  else { +    // add a line that shows the number of servers found +    if (!uiInfo.numFoundPlayerServers) { +      Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], sizeof(uiInfo.foundPlayerServerAddresses[0]), "no servers found"); +    } +    else { +      Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], sizeof(uiInfo.foundPlayerServerAddresses[0]), +            "%d server%s found with player %s", uiInfo.numFoundPlayerServers-1, +            uiInfo.numFoundPlayerServers == 2 ? "":"s", uiInfo.findPlayerName); +    } +    uiInfo.nextFindPlayerRefresh = 0; +    // show the server status info for the selected server +    UI_FeederSelection(FEEDER_FINDPLAYER, uiInfo.currentFoundPlayerServer); +  } +} + +/* +================== +UI_BuildServerStatus +================== +*/ +static void UI_BuildServerStatus(qboolean force) { + +  if (uiInfo.nextFindPlayerRefresh) { +    return; +  } +  if (!force) { +    if (!uiInfo.nextServerStatusRefresh || uiInfo.nextServerStatusRefresh > uiInfo.uiDC.realTime) { +      return; +    } +  } +  else { +    Menu_SetFeederSelection(NULL, FEEDER_SERVERSTATUS, 0, NULL); +    uiInfo.serverStatusInfo.numLines = 0; +    // reset all server status requests +    trap_LAN_ServerStatus( NULL, NULL, 0); +  } +  if (uiInfo.serverStatus.currentServer < 0 || uiInfo.serverStatus.currentServer > uiInfo.serverStatus.numDisplayServers || uiInfo.serverStatus.numDisplayServers == 0) { +    return; +  } +  if (UI_GetServerStatusInfo( uiInfo.serverStatusAddress, &uiInfo.serverStatusInfo ) ) { +    uiInfo.nextServerStatusRefresh = 0; +    UI_GetServerStatusInfo( uiInfo.serverStatusAddress, NULL ); +  } +  else { +    uiInfo.nextServerStatusRefresh = uiInfo.uiDC.realTime + 500; +  } +} + +/* +================== +UI_FeederCount +================== +*/ +static int UI_FeederCount(float feederID) { + +  if (feederID == FEEDER_HEADS) { +    return UI_HeadCountByTeam(); +  } else if (feederID == FEEDER_Q3HEADS) { +    return uiInfo.q3HeadCount; +  } else if (feederID == FEEDER_CINEMATICS) { +    return uiInfo.movieCount; +  } else if (feederID == FEEDER_MAPS || feederID == FEEDER_ALLMAPS) { +    return UI_MapCountByGameType(feederID == FEEDER_MAPS ? qtrue : qfalse); +  } else if (feederID == FEEDER_SERVERS) { +    return uiInfo.serverStatus.numDisplayServers; +  } else if (feederID == FEEDER_SERVERSTATUS) { +    return uiInfo.serverStatusInfo.numLines; +  } else if (feederID == FEEDER_FINDPLAYER) { +    return uiInfo.numFoundPlayerServers; +  } else if (feederID == FEEDER_PLAYER_LIST) { +    if (uiInfo.uiDC.realTime > uiInfo.playerRefresh) { +      uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000; +      UI_BuildPlayerList(); +    } +    return uiInfo.playerCount; +  } else if (feederID == FEEDER_TEAM_LIST) { +    if (uiInfo.uiDC.realTime > uiInfo.playerRefresh) { +      uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000; +      UI_BuildPlayerList(); +    } +    return uiInfo.myTeamCount; +  } else if (feederID == FEEDER_IGNORE_LIST) { +    return uiInfo.playerCount; +  } else if (feederID == FEEDER_MODS) { +    return uiInfo.modCount; +  } else if (feederID == FEEDER_DEMOS) { +    return uiInfo.demoCount; +  } + +//TA: tremulous menus +  else if( feederID == FEEDER_TREMTEAMS ) +    return uiInfo.tremTeamCount; +  else if( feederID == FEEDER_TREMHUMANITEMS ) +    return uiInfo.tremHumanItemCount; +  else if( feederID == FEEDER_TREMALIENCLASSES ) +    return uiInfo.tremAlienClassCount; +  else if( feederID == FEEDER_TREMHUMANARMOURYBUY ) +    return uiInfo.tremHumanArmouryBuyCount; +  else if( feederID == FEEDER_TREMHUMANARMOURYSELL ) +    return uiInfo.tremHumanArmourySellCount; +  else if( feederID == FEEDER_TREMALIENUPGRADE ) +    return uiInfo.tremAlienUpgradeCount; +  else if( feederID == FEEDER_TREMALIENBUILD ) +    return uiInfo.tremAlienBuildCount; +  else if( feederID == FEEDER_TREMHUMANBUILD ) +    return uiInfo.tremHumanBuildCount; +//TA: tremulous menus + +  return 0; +} + +static const char *UI_SelectedMap(int index, int *actual) { +  int i, c; +  c = 0; +  *actual = 0; +  for (i = 0; i < uiInfo.mapCount; i++) { +    if (uiInfo.mapList[i].active) { +      if (c == index) { +        *actual = i; +        return uiInfo.mapList[i].mapName; +      } else { +        c++; +      } +    } +  } +  return ""; +} + +static const char *UI_SelectedHead(int index, int *actual) { +  int i, c; +  c = 0; +  *actual = 0; +  for (i = 0; i < uiInfo.characterCount; i++) { +    if (uiInfo.characterList[i].active) { +      if (c == index) { +        *actual = i; +        return uiInfo.characterList[i].name; +      } else { +        c++; +      } +    } +  } +  return ""; +} + +static int UI_GetIndexFromSelection(int actual) { +  int i, c; +  c = 0; +  for (i = 0; i < uiInfo.mapCount; i++) { +    if (uiInfo.mapList[i].active) { +      if (i == actual) { +        return c; +      } +        c++; +    } +  } +  return 0; +} + +static void UI_UpdatePendingPings( void ) { +  trap_LAN_ResetPings(ui_netSource.integer); +  uiInfo.serverStatus.refreshActive = qtrue; +  uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000; + +} + +static const char *UI_FeederItemText(float feederID, int index, int column, qhandle_t *handle) { +  static char info[MAX_STRING_CHARS]; +  static char hostname[1024]; +  static char clientBuff[32]; +  static int lastColumn = -1; +  static int lastTime = 0; +  *handle = -1; +  if (feederID == FEEDER_HEADS) { +    int actual; +    return UI_SelectedHead(index, &actual); +  } else if (feederID == FEEDER_Q3HEADS) { +    if (index >= 0 && index < uiInfo.q3HeadCount) { +      return uiInfo.q3HeadNames[index]; +    } +  } else if (feederID == FEEDER_MAPS || feederID == FEEDER_ALLMAPS) { +    int actual; +    return UI_SelectedMap(index, &actual); +  } else if (feederID == FEEDER_SERVERS) { +    if (index >= 0 && index < uiInfo.serverStatus.numDisplayServers) { +      int ping; +      if (lastColumn != column || lastTime > uiInfo.uiDC.realTime + 5000) { +        trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[index], info, MAX_STRING_CHARS); +        lastColumn = column; +        lastTime = uiInfo.uiDC.realTime; +      } + +      ping = atoi(Info_ValueForKey(info, "ping")); +      if (ping == -1) { +        // if we ever see a ping that is out of date, do a server refresh +        // UI_UpdatePendingPings(); +      } +      switch (column) { +        case SORT_HOST : +          if (ping <= 0) { +            return Info_ValueForKey(info, "addr"); +          } else { +            if ( ui_netSource.integer == AS_LOCAL ) { +              Com_sprintf( hostname, sizeof(hostname), "%s [%s]", +                      Info_ValueForKey(info, "hostname"), +                      netnames[atoi(Info_ValueForKey(info, "nettype"))] ); +              return hostname; +            } +            else +            { +              char *text; + +              Com_sprintf( hostname, sizeof(hostname), "%s", Info_ValueForKey(info, "hostname")); + +              // Strip leading whitespace +              text = hostname; +              while( *text != '\0' && *text == ' ' ) +                text++; + +              return text; +            } +          } +        case SORT_MAP : +          return Info_ValueForKey(info, "mapname"); +        case SORT_CLIENTS : +          Com_sprintf( clientBuff, sizeof(clientBuff), "%s (%s)", Info_ValueForKey(info, "clients"), Info_ValueForKey(info, "sv_maxclients")); +          return clientBuff; +        case SORT_PING : +          if (ping <= 0) { +            return "..."; +          } else { +            return Info_ValueForKey(info, "ping"); +          } +      } +    } +  } else if (feederID == FEEDER_SERVERSTATUS) { +    if ( index >= 0 && index < uiInfo.serverStatusInfo.numLines ) { +      if ( column >= 0 && column < 4 ) { +        return uiInfo.serverStatusInfo.lines[index][column]; +      } +    } +  } else if (feederID == FEEDER_FINDPLAYER) { +    if ( index >= 0 && index < uiInfo.numFoundPlayerServers ) { +      //return uiInfo.foundPlayerServerAddresses[index]; +      return uiInfo.foundPlayerServerNames[index]; +    } +  } else if (feederID == FEEDER_PLAYER_LIST) { +    if (index >= 0 && index < uiInfo.playerCount) { +      return uiInfo.playerNames[index]; +    } +  } else if (feederID == FEEDER_TEAM_LIST) { +    if (index >= 0 && index < uiInfo.myTeamCount) { +      return uiInfo.teamNames[index]; +    } +  } else if (feederID == FEEDER_IGNORE_LIST) { +    if (index >= 0 && index < uiInfo.playerCount) { +      switch( column ) +      { +        case 1: +          // am I ignoring him +          return ( BG_ClientListTest(&uiInfo.ignoreList[ uiInfo.myPlayerIndex ], +            uiInfo.clientNums[ index ] ) ) ? "X" : ""; +        case 2: +          // is he ignoring me +          return ( BG_ClientListTest( &uiInfo.ignoreList[ index ], +            uiInfo.playerNumber ) ) ? "X" : ""; +        default: +          return uiInfo.playerNames[index]; +      } +    } +  } else if (feederID == FEEDER_MODS) { +    if (index >= 0 && index < uiInfo.modCount) { +      if (uiInfo.modList[index].modDescr && *uiInfo.modList[index].modDescr) { +        return uiInfo.modList[index].modDescr; +      } else { +        return uiInfo.modList[index].modName; +      } +    } +  } else if (feederID == FEEDER_CINEMATICS) { +    if (index >= 0 && index < uiInfo.movieCount) { +      return uiInfo.movieList[index]; +    } +  } else if (feederID == FEEDER_DEMOS) { +    if (index >= 0 && index < uiInfo.demoCount) { +      return uiInfo.demoList[index]; +    } +  } + +//TA: tremulous menus +  else if( feederID == FEEDER_TREMTEAMS ) +  { +    if( index >= 0 && index < uiInfo.tremTeamCount ) +      return uiInfo.tremTeamList[ index ].text; +  } +  else if( feederID == FEEDER_TREMHUMANITEMS ) +  { +    if( index >= 0 && index < uiInfo.tremHumanItemCount ) +      return uiInfo.tremHumanItemList[ index ].text; +  } +  else if( feederID == FEEDER_TREMALIENCLASSES ) +  { +    if( index >= 0 && index < uiInfo.tremAlienClassCount ) +      return uiInfo.tremAlienClassList[ index ].text; +  } +  else if( feederID == FEEDER_TREMHUMANARMOURYBUY ) +  { +    if( index >= 0 && index < uiInfo.tremHumanArmouryBuyCount ) +      return uiInfo.tremHumanArmouryBuyList[ index ].text; +  } +  else if( feederID == FEEDER_TREMHUMANARMOURYSELL ) +  { +    if( index >= 0 && index < uiInfo.tremHumanArmourySellCount ) +      return uiInfo.tremHumanArmourySellList[ index ].text; +  } +  else if( feederID == FEEDER_TREMALIENUPGRADE ) +  { +    if( index >= 0 && index < uiInfo.tremAlienUpgradeCount ) +      return uiInfo.tremAlienUpgradeList[ index ].text; +  } +  else if( feederID == FEEDER_TREMALIENBUILD ) +  { +    if( index >= 0 && index < uiInfo.tremAlienBuildCount ) +      return uiInfo.tremAlienBuildList[ index ].text; +  } +  else if( feederID == FEEDER_TREMHUMANBUILD ) +  { +    if( index >= 0 && index < uiInfo.tremHumanBuildCount ) +      return uiInfo.tremHumanBuildList[ index ].text; +  } +//TA: tremulous menus + +  return ""; +} + + +static qhandle_t UI_FeederItemImage(float feederID, int index) { +  if (feederID == FEEDER_HEADS) { +  int actual; +  UI_SelectedHead(index, &actual); +  index = actual; +  if (index >= 0 && index < uiInfo.characterCount) { +    if (uiInfo.characterList[index].headImage == -1) { +      uiInfo.characterList[index].headImage = trap_R_RegisterShaderNoMip(uiInfo.characterList[index].imageName); +    } +    return uiInfo.characterList[index].headImage; +  } +  } else if (feederID == FEEDER_Q3HEADS) { +    if (index >= 0 && index < uiInfo.q3HeadCount) { +      return uiInfo.q3HeadIcons[index]; +    } +  } else if (feederID == FEEDER_ALLMAPS || feederID == FEEDER_MAPS) { +    int actual; +    UI_SelectedMap(index, &actual); +    index = actual; +    if (index >= 0 && index < uiInfo.mapCount) { +      if (uiInfo.mapList[index].levelShot == -1) { +        uiInfo.mapList[index].levelShot = trap_R_RegisterShaderNoMip(uiInfo.mapList[index].imageName); +      } +      return uiInfo.mapList[index].levelShot; +    } +  } +  return 0; +} + +static void UI_FeederSelection(float feederID, int index) { +  static char info[MAX_STRING_CHARS]; +  if (feederID == FEEDER_HEADS) { +  int actual; +  UI_SelectedHead(index, &actual); +  index = actual; +    if (index >= 0 && index < uiInfo.characterCount) { +    trap_Cvar_Set( "team_model", va("%s", uiInfo.characterList[index].base)); +    trap_Cvar_Set( "team_headmodel", va("*%s", uiInfo.characterList[index].name)); +    updateModel = qtrue; +    } +  } else if (feederID == FEEDER_Q3HEADS) { +    if (index >= 0 && index < uiInfo.q3HeadCount) { +      trap_Cvar_Set( "model", uiInfo.q3HeadNames[index]); +      trap_Cvar_Set( "headmodel", uiInfo.q3HeadNames[index]); +      updateModel = qtrue; +    } +  } else if (feederID == FEEDER_MAPS || feederID == FEEDER_ALLMAPS) { +    int actual, map; +    map = (feederID == FEEDER_ALLMAPS) ? ui_currentNetMap.integer : ui_currentMap.integer; +    if (uiInfo.mapList[map].cinematic >= 0) { +      trap_CIN_StopCinematic(uiInfo.mapList[map].cinematic); +      uiInfo.mapList[map].cinematic = -1; +    } +    UI_SelectedMap(index, &actual); +    trap_Cvar_Set("ui_mapIndex", va("%d", index)); +    ui_mapIndex.integer = index; + +    if (feederID == FEEDER_MAPS) { +      ui_currentMap.integer = actual; +      trap_Cvar_Set("ui_currentMap", va("%d", actual)); +      uiInfo.mapList[ui_currentMap.integer].cinematic = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.mapList[ui_currentMap.integer].mapLoadName), 0, 0, 0, 0, (CIN_loop | CIN_silent) ); +      UI_LoadBestScores(uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum); +      trap_Cvar_Set("ui_opponentModel", uiInfo.mapList[ui_currentMap.integer].opponentName); +      updateOpponentModel = qtrue; +    } else { +      ui_currentNetMap.integer = actual; +      trap_Cvar_Set("ui_currentNetMap", va("%d", actual)); +      uiInfo.mapList[ui_currentNetMap.integer].cinematic = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.mapList[ui_currentNetMap.integer].mapLoadName), 0, 0, 0, 0, (CIN_loop | CIN_silent) ); +    } + +  } else if (feederID == FEEDER_SERVERS) { +    const char *mapName = NULL; +    uiInfo.serverStatus.currentServer = index; +    trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[index], info, MAX_STRING_CHARS); +    uiInfo.serverStatus.currentServerPreview = trap_R_RegisterShaderNoMip(va("levelshots/%s", Info_ValueForKey(info, "mapname"))); +    if (uiInfo.serverStatus.currentServerCinematic >= 0) { +      trap_CIN_StopCinematic(uiInfo.serverStatus.currentServerCinematic); +      uiInfo.serverStatus.currentServerCinematic = -1; +    } +    mapName = Info_ValueForKey(info, "mapname"); +    if (mapName && *mapName) { +      uiInfo.serverStatus.currentServerCinematic = trap_CIN_PlayCinematic(va("%s.roq", mapName), 0, 0, 0, 0, (CIN_loop | CIN_silent) ); +    } +  } else if (feederID == FEEDER_SERVERSTATUS) { +    // +  } else if (feederID == FEEDER_FINDPLAYER) { +    uiInfo.currentFoundPlayerServer = index; +    // +    if ( index < uiInfo.numFoundPlayerServers-1) { +      // build a new server status for this server +      Q_strncpyz(uiInfo.serverStatusAddress, uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer], sizeof(uiInfo.serverStatusAddress)); +      Menu_SetFeederSelection(NULL, FEEDER_SERVERSTATUS, 0, NULL); +      UI_BuildServerStatus(qtrue); +    } +  } else if (feederID == FEEDER_PLAYER_LIST) { +    uiInfo.playerIndex = index; +  } else if (feederID == FEEDER_TEAM_LIST) { +    uiInfo.teamIndex = index; +  } else if (feederID == FEEDER_IGNORE_LIST) { +    uiInfo.ignoreIndex = index; +  } else if (feederID == FEEDER_MODS) { +    uiInfo.modIndex = index; +  } else if (feederID == FEEDER_CINEMATICS) { +    uiInfo.movieIndex = index; +    if (uiInfo.previewMovie >= 0) { +      trap_CIN_StopCinematic(uiInfo.previewMovie); +    } +    uiInfo.previewMovie = -1; +  } else if (feederID == FEEDER_DEMOS) { +    uiInfo.demoIndex = index; +  } + +//TA: tremulous menus +  else if( feederID == FEEDER_TREMTEAMS ) +    uiInfo.tremTeamIndex = index; +  else if( feederID == FEEDER_TREMHUMANITEMS ) +    uiInfo.tremHumanItemIndex = index; +  else if( feederID == FEEDER_TREMALIENCLASSES ) +    uiInfo.tremAlienClassIndex = index; +  else if( feederID == FEEDER_TREMHUMANARMOURYBUY ) +    uiInfo.tremHumanArmouryBuyIndex = index; +  else if( feederID == FEEDER_TREMHUMANARMOURYSELL ) +    uiInfo.tremHumanArmourySellIndex = index; +  else if( feederID == FEEDER_TREMALIENUPGRADE ) +    uiInfo.tremAlienUpgradeIndex = index; +  else if( feederID == FEEDER_TREMALIENBUILD ) +    uiInfo.tremAlienBuildIndex = index; +  else if( feederID == FEEDER_TREMHUMANBUILD ) +    uiInfo.tremHumanBuildIndex = index; +//TA: tremulous menus +} + +static void UI_Pause(qboolean b) { +  if (b) { +    // pause the game and set the ui keycatcher +    trap_Cvar_Set( "cl_paused", "1" ); +    trap_Key_SetCatcher( KEYCATCH_UI ); +  } else { +    // unpause the game and clear the ui keycatcher +    trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); +    trap_Key_ClearStates(); +    trap_Cvar_Set( "cl_paused", "0" ); +  } +} + +static int UI_PlayCinematic(const char *name, float x, float y, float w, float h) { +  return trap_CIN_PlayCinematic(name, x, y, w, h, (CIN_loop | CIN_silent)); +} + +static void UI_StopCinematic(int handle) { +  if (handle >= 0) { +    trap_CIN_StopCinematic(handle); +  } else { +    handle = abs(handle); +    if (handle == UI_MAPCINEMATIC) { +      if (uiInfo.mapList[ui_currentMap.integer].cinematic >= 0) { +        trap_CIN_StopCinematic(uiInfo.mapList[ui_currentMap.integer].cinematic); +        uiInfo.mapList[ui_currentMap.integer].cinematic = -1; +      } +    } else if (handle == UI_NETMAPCINEMATIC) { +      if (uiInfo.serverStatus.currentServerCinematic >= 0) { +        trap_CIN_StopCinematic(uiInfo.serverStatus.currentServerCinematic); +        uiInfo.serverStatus.currentServerCinematic = -1; +      } +    } else if (handle == UI_CLANCINEMATIC) { +      int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); +      if (i >= 0 && i < uiInfo.teamCount) { +        if (uiInfo.teamList[i].cinematic >= 0) { +          trap_CIN_StopCinematic(uiInfo.teamList[i].cinematic); +          uiInfo.teamList[i].cinematic = -1; +        } +      } +    } +  } +} + +static void UI_DrawCinematic(int handle, float x, float y, float w, float h) { +  trap_CIN_SetExtents(handle, x, y, w, h); +  trap_CIN_DrawCinematic(handle); +} + +static void UI_RunCinematicFrame(int handle) { +  trap_CIN_RunCinematic(handle); +} + + +/* +================= +UI_Init +================= +*/ +void _UI_Init( qboolean inGameLoad ) { +  const char *menuSet; +  int start; + +  BG_InitClassOverrides( ); +  BG_InitAllowedGameElements( ); + +  //uiInfo.inGameLoad = inGameLoad; + +  UI_RegisterCvars(); +  UI_InitMemory(); + +  // cache redundant calulations +  trap_GetGlconfig( &uiInfo.uiDC.glconfig ); + +  // for 640x480 virtualized screen +  uiInfo.uiDC.yscale = uiInfo.uiDC.glconfig.vidHeight * (1.0/480.0); +  uiInfo.uiDC.xscale = uiInfo.uiDC.glconfig.vidWidth * (1.0/640.0); +  if ( uiInfo.uiDC.glconfig.vidWidth * 480 > uiInfo.uiDC.glconfig.vidHeight * 640 ) { +    // wide screen +    uiInfo.uiDC.bias = 0.5 * ( uiInfo.uiDC.glconfig.vidWidth - ( uiInfo.uiDC.glconfig.vidHeight * (640.0/480.0) ) ); +  } +  else { +    // no wide screen +    uiInfo.uiDC.bias = 0; +  } + + +  //UI_Load(); +  uiInfo.uiDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip; +  uiInfo.uiDC.setColor = &UI_SetColor; +  uiInfo.uiDC.drawHandlePic = &UI_DrawHandlePic; +  uiInfo.uiDC.drawStretchPic = &trap_R_DrawStretchPic; +  uiInfo.uiDC.drawText = &Text_Paint; +  uiInfo.uiDC.textWidth = &Text_Width; +  uiInfo.uiDC.textHeight = &Text_Height; +  uiInfo.uiDC.registerModel = &trap_R_RegisterModel; +  uiInfo.uiDC.modelBounds = &trap_R_ModelBounds; +  uiInfo.uiDC.fillRect = &UI_FillRect; +  uiInfo.uiDC.drawRect = &_UI_DrawRect; +  uiInfo.uiDC.drawSides = &_UI_DrawSides; +  uiInfo.uiDC.drawTopBottom = &_UI_DrawTopBottom; +  uiInfo.uiDC.clearScene = &trap_R_ClearScene; +  uiInfo.uiDC.drawSides = &_UI_DrawSides; +  uiInfo.uiDC.addRefEntityToScene = &trap_R_AddRefEntityToScene; +  uiInfo.uiDC.renderScene = &trap_R_RenderScene; +  uiInfo.uiDC.registerFont = &trap_R_RegisterFont; +  uiInfo.uiDC.ownerDrawItem = &UI_OwnerDraw; +  uiInfo.uiDC.getValue = &UI_GetValue; +  uiInfo.uiDC.ownerDrawVisible = &UI_OwnerDrawVisible; +  uiInfo.uiDC.runScript = &UI_RunMenuScript; +  uiInfo.uiDC.getTeamColor = &UI_GetTeamColor; +  uiInfo.uiDC.setCVar = trap_Cvar_Set; +  uiInfo.uiDC.getCVarString = trap_Cvar_VariableStringBuffer; +  uiInfo.uiDC.getCVarValue = trap_Cvar_VariableValue; +  uiInfo.uiDC.drawTextWithCursor = &Text_PaintWithCursor; +  uiInfo.uiDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode; +  uiInfo.uiDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode; +  uiInfo.uiDC.startLocalSound = &trap_S_StartLocalSound; +  uiInfo.uiDC.ownerDrawHandleKey = &UI_OwnerDrawHandleKey; +  uiInfo.uiDC.feederCount = &UI_FeederCount; +  uiInfo.uiDC.feederItemImage = &UI_FeederItemImage; +  uiInfo.uiDC.feederItemText = &UI_FeederItemText; +  uiInfo.uiDC.feederSelection = &UI_FeederSelection; +  uiInfo.uiDC.setBinding = &trap_Key_SetBinding; +  uiInfo.uiDC.getBindingBuf = &trap_Key_GetBindingBuf; +  uiInfo.uiDC.keynumToStringBuf = &trap_Key_KeynumToStringBuf; +  uiInfo.uiDC.executeText = &trap_Cmd_ExecuteText; +  uiInfo.uiDC.Error = &Com_Error; +  uiInfo.uiDC.Print = &Com_Printf; +  uiInfo.uiDC.Pause = &UI_Pause; +  uiInfo.uiDC.ownerDrawWidth = &UI_OwnerDrawWidth; +  uiInfo.uiDC.registerSound = &trap_S_RegisterSound; +  uiInfo.uiDC.startBackgroundTrack = &trap_S_StartBackgroundTrack; +  uiInfo.uiDC.stopBackgroundTrack = &trap_S_StopBackgroundTrack; +  uiInfo.uiDC.playCinematic = &UI_PlayCinematic; +  uiInfo.uiDC.stopCinematic = &UI_StopCinematic; +  uiInfo.uiDC.drawCinematic = &UI_DrawCinematic; +  uiInfo.uiDC.runCinematicFrame = &UI_RunCinematicFrame; + +  Init_Display(&uiInfo.uiDC); + +  String_Init(); + +  uiInfo.uiDC.whiteShader = trap_R_RegisterShaderNoMip( "white" ); + +  AssetCache(); + +  start = trap_Milliseconds(); + +  uiInfo.teamCount = 0; +  uiInfo.characterCount = 0; +  uiInfo.aliasCount = 0; + +/*  UI_ParseTeamInfo("teaminfo.txt"); +  UI_LoadTeams(); +  UI_ParseGameInfo("gameinfo.txt");*/ + +  menuSet = UI_Cvar_VariableString("ui_menuFiles"); +  if (menuSet == NULL || menuSet[0] == '\0') { +    menuSet = "ui/menus.txt"; +  } + +#if 0 +  if (uiInfo.inGameLoad) { +    UI_LoadMenus("ui/ingame.txt", qtrue); +  } else { // bk010222: left this: UI_LoadMenus(menuSet, qtrue); +  } +#else +  UI_LoadMenus(menuSet, qtrue); +  UI_LoadMenus("ui/ingame.txt", qfalse); +  UI_LoadMenus("ui/tremulous.txt", qfalse); + +  UI_LoadInfoPanes( "ui/infopanes.def" ); + +  if( uiInfo.uiDC.debug ) +  { +    int i, j; + +    for( i = 0; i < uiInfo.tremInfoPaneCount; i++ ) +    { +      Com_Printf( "name: %s\n", uiInfo.tremInfoPanes[ i ].name ); + +      Com_Printf( "text: %s\n", uiInfo.tremInfoPanes[ i ].text ); + +      for( j = 0; j < uiInfo.tremInfoPanes[ i ].numGraphics; j++ ) +        Com_Printf( "graphic %d: %d %d %d %d\n", j, uiInfo.tremInfoPanes[ i ].graphics[ j ].side, +                                                    uiInfo.tremInfoPanes[ i ].graphics[ j ].offset, +                                                    uiInfo.tremInfoPanes[ i ].graphics[ j ].width, +                                                    uiInfo.tremInfoPanes[ i ].graphics[ j ].height ); +    } +  } +#endif + +  Menus_CloseAll(); + +  trap_LAN_LoadCachedServers(); +  UI_LoadBestScores(uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum); + +  // sets defaults for ui temp cvars +  uiInfo.effectsColor = gamecodetoui[(int)trap_Cvar_VariableValue("color1")-1]; +  uiInfo.currentCrosshair = (int)trap_Cvar_VariableValue("cg_drawCrosshair"); +  trap_Cvar_Set("ui_mousePitch", (trap_Cvar_VariableValue("m_pitch") >= 0) ? "0" : "1"); + +  uiInfo.serverStatus.currentServerCinematic = -1; +  uiInfo.previewMovie = -1; + +  if (trap_Cvar_VariableValue("ui_TeamArenaFirstRun") == 0) { +    trap_Cvar_Set("s_volume", "0.8"); +    trap_Cvar_Set("s_musicvolume", "0.5"); +    trap_Cvar_Set("ui_TeamArenaFirstRun", "1"); +  } + +  trap_Cvar_Register(NULL, "debug_protocol", "", 0 ); + +  trap_Cvar_Set("ui_actualNetGameType", va("%d", ui_netGameType.integer)); +} + + +/* +================= +UI_KeyEvent +================= +*/ +void _UI_KeyEvent( int key, qboolean down ) { + +  if (Menu_Count() > 0) { +    menuDef_t *menu = Menu_GetFocused(); +    if (menu) { +      if (key == K_ESCAPE && down && !Menus_AnyFullScreenVisible()) { +        Menus_CloseAll(); +      } else { +        Menu_HandleKey(menu, key, down ); +      } +    } else { +      trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); +      trap_Key_ClearStates(); +      trap_Cvar_Set( "cl_paused", "0" ); +    } +  } + +  //if ((s > 0) && (s != menu_null_sound)) { +  //  trap_S_StartLocalSound( s, CHAN_LOCAL_SOUND ); +  //} +} + +/* +================= +UI_MouseEvent +================= +*/ +void _UI_MouseEvent( int dx, int dy ) +{ +  // update mouse screen position +  uiInfo.uiDC.cursorx += dx; +  if (uiInfo.uiDC.cursorx < 0) +    uiInfo.uiDC.cursorx = 0; +  else if (uiInfo.uiDC.cursorx > SCREEN_WIDTH) +    uiInfo.uiDC.cursorx = SCREEN_WIDTH; + +  uiInfo.uiDC.cursory += dy; +  if (uiInfo.uiDC.cursory < 0) +    uiInfo.uiDC.cursory = 0; +  else if (uiInfo.uiDC.cursory > SCREEN_HEIGHT) +    uiInfo.uiDC.cursory = SCREEN_HEIGHT; + +  if (Menu_Count() > 0) { +    //menuDef_t *menu = Menu_GetFocused(); +    //Menu_HandleMouseMove(menu, uiInfo.uiDC.cursorx, uiInfo.uiDC.cursory); +    Display_MouseMove(NULL, uiInfo.uiDC.cursorx, uiInfo.uiDC.cursory); +  } + +} + +void UI_LoadNonIngame( void ) { +  const char *menuSet = UI_Cvar_VariableString("ui_menuFiles"); +  if (menuSet == NULL || menuSet[0] == '\0') { +    menuSet = "ui/menus.txt"; +  } +  UI_LoadMenus(menuSet, qfalse); +  uiInfo.inGameLoad = qfalse; +} + +void _UI_SetActiveMenu( uiMenuCommand_t menu ) { +  char buf[256]; + +  // this should be the ONLY way the menu system is brought up +  // enusure minumum menu data is cached +  if (Menu_Count() > 0) { +    vec3_t v; +    v[0] = v[1] = v[2] = 0; +    switch ( menu ) { +    case UIMENU_NONE: +      trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); +      trap_Key_ClearStates(); +      trap_Cvar_Set( "cl_paused", "0" ); +      Menus_CloseAll(); + +      return; +    case UIMENU_MAIN: +      //trap_Cvar_Set( "sv_killserver", "1" ); +      trap_Key_SetCatcher( KEYCATCH_UI ); +      //trap_S_StartLocalSound( trap_S_RegisterSound("sound/misc/menu_background.wav", qfalse) , CHAN_LOCAL_SOUND ); +      //trap_S_StartBackgroundTrack("sound/misc/menu_background.wav", NULL); +      if (uiInfo.inGameLoad) { +        UI_LoadNonIngame(); +      } +      Menus_CloseAll(); +      Menus_ActivateByName("main"); +      trap_Cvar_Set( "ui_loading", "0" ); +      trap_Cvar_VariableStringBuffer("com_errorMessage", buf, sizeof(buf)); +      if (strlen(buf)) { +        if (!ui_singlePlayerActive.integer) { +          if( trap_Cvar_VariableValue( "com_errorCode" ) == ERR_SERVERDISCONNECT ) +            Menus_ActivateByName("drop_popmenu"); +          else +            Menus_ActivateByName("error_popmenu"); +        } else { +          trap_Cvar_Set("com_errorMessage", ""); +        } +      } +      return; +    case UIMENU_TEAM: +      trap_Key_SetCatcher( KEYCATCH_UI ); +      Menus_ActivateByName("team"); +      return; +    case UIMENU_POSTGAME: +      //trap_Cvar_Set( "sv_killserver", "1" ); +      trap_Key_SetCatcher( KEYCATCH_UI ); +      if (uiInfo.inGameLoad) { +        UI_LoadNonIngame(); +      } +      Menus_CloseAll(); +      Menus_ActivateByName("endofgame"); +      return; +    case UIMENU_INGAME: +      trap_Cvar_Set( "cl_paused", "1" ); +      trap_Key_SetCatcher( KEYCATCH_UI ); +      UI_BuildPlayerList(); +      Menus_CloseAll(); +      Menus_ActivateByName("ingame"); +      return; +    } +  } +} + +qboolean _UI_IsFullscreen( void ) { +  return Menus_AnyFullScreenVisible(); +} + + + +static connstate_t  lastConnState; +static char     lastLoadingText[MAX_INFO_VALUE]; + +static void UI_ReadableSize ( char *buf, int bufsize, int value ) +{ +  if (value > 1024*1024*1024 ) { // gigs +    Com_sprintf( buf, bufsize, "%d", value / (1024*1024*1024) ); +    Com_sprintf( buf+strlen(buf), bufsize-strlen(buf), ".%02d GB", +      (value % (1024*1024*1024))*100 / (1024*1024*1024) ); +  } else if (value > 1024*1024 ) { // megs +    Com_sprintf( buf, bufsize, "%d", value / (1024*1024) ); +    Com_sprintf( buf+strlen(buf), bufsize-strlen(buf), ".%02d MB", +      (value % (1024*1024))*100 / (1024*1024) ); +  } else if (value > 1024 ) { // kilos +    Com_sprintf( buf, bufsize, "%d KB", value / 1024 ); +  } else { // bytes +    Com_sprintf( buf, bufsize, "%d bytes", value ); +  } +} + +// Assumes time is in msec +static void UI_PrintTime ( char *buf, int bufsize, int time ) { +  time /= 1000;  // change to seconds + +  if (time > 3600) { // in the hours range +    Com_sprintf( buf, bufsize, "%d hr %d min", time / 3600, (time % 3600) / 60 ); +  } else if (time > 60) { // mins +    Com_sprintf( buf, bufsize, "%d min %d sec", time / 60, time % 60 ); +  } else  { // secs +    Com_sprintf( buf, bufsize, "%d sec", time ); +  } +} + +void Text_PaintCenter(float x, float y, float scale, vec4_t color, const char *text, float adjust) { +  int len = Text_Width(text, scale, 0); +  Text_Paint(x - len / 2, y, scale, color, text, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); +} + +void Text_PaintCenter_AutoWrapped(float x, float y, float xmax, float ystep, float scale, vec4_t color, const char *str, float adjust) { +  int width; +  char *s1,*s2,*s3; +  char c_bcp; +  char buf[1024]; + +  if (!str || str[0]=='\0') +    return; + +  Q_strncpyz(buf, str, sizeof(buf)); +  s1 = s2 = s3 = buf; + +  while (1) { +    do { +      s3++; +    } while (*s3!=' ' && *s3!='\0'); +    c_bcp = *s3; +    *s3 = '\0'; +    width = Text_Width(s1, scale, 0); +    *s3 = c_bcp; +    if (width > xmax) { +      if (s1==s2) +      { +        // fuck, don't have a clean cut, we'll overflow +        s2 = s3; +      } +      *s2 = '\0'; +      Text_PaintCenter(x, y, scale, color, s1, adjust); +      y += ystep; +      if (c_bcp == '\0') +      { +        // that was the last word +        // we could start a new loop, but that wouldn't be much use +        // even if the word is too long, we would overflow it (see above) +        // so just print it now if needed +        s2++; +        if (*s2 != '\0') // if we are printing an overflowing line we have s2 == s3 +          Text_PaintCenter(x, y, scale, color, s2, adjust); +        break; +      } +      s2++; +      s1 = s2; +      s3 = s2; +    } +    else +    { +      s2 = s3; +      if (c_bcp == '\0') // we reached the end +      { +        Text_PaintCenter(x, y, scale, color, s1, adjust); +        break; +      } +    } +  } +} + + +static void UI_DisplayDownloadInfo( const char *downloadName, float centerPoint, float yStart, float scale ) { +  static char dlText[]  = "Downloading:"; +  static char etaText[] = "Estimated time left:"; +  static char xferText[]  = "Transfer rate:"; + +  int downloadSize, downloadCount, downloadTime; +  char dlSizeBuf[64], totalSizeBuf[64], xferRateBuf[64], dlTimeBuf[64]; +  int xferRate; +  int leftWidth; +  const char *s; + +  downloadSize = trap_Cvar_VariableValue( "cl_downloadSize" ); +  downloadCount = trap_Cvar_VariableValue( "cl_downloadCount" ); +  downloadTime = trap_Cvar_VariableValue( "cl_downloadTime" ); + +  leftWidth = 320; + +  UI_SetColor(colorWhite); +  Text_PaintCenter(centerPoint, yStart + 112, scale, colorWhite, dlText, 0); +  Text_PaintCenter(centerPoint, yStart + 192, scale, colorWhite, etaText, 0); +  Text_PaintCenter(centerPoint, yStart + 248, scale, colorWhite, xferText, 0); + +  if (downloadSize > 0) { +    s = va( "%s (%d%%)", downloadName, downloadCount * 100 / downloadSize ); +  } else { +    s = downloadName; +  } + +  Text_PaintCenter(centerPoint, yStart+136, scale, colorWhite, s, 0); + +  UI_ReadableSize( dlSizeBuf,   sizeof dlSizeBuf,   downloadCount ); +  UI_ReadableSize( totalSizeBuf,  sizeof totalSizeBuf,  downloadSize ); + +  if (downloadCount < 4096 || !downloadTime) { +    Text_PaintCenter(leftWidth, yStart+216, scale, colorWhite, "estimating", 0); +    Text_PaintCenter(leftWidth, yStart+160, scale, colorWhite, va("(%s of %s copied)", dlSizeBuf, totalSizeBuf), 0); +  } else { +    if ((uiInfo.uiDC.realTime - downloadTime) / 1000) { +      xferRate = downloadCount / ((uiInfo.uiDC.realTime - downloadTime) / 1000); +    } else { +      xferRate = 0; +    } +    UI_ReadableSize( xferRateBuf, sizeof xferRateBuf, xferRate ); + +    // Extrapolate estimated completion time +    if (downloadSize && xferRate) { +      int n = downloadSize / xferRate; // estimated time for entire d/l in secs + +      // We do it in K (/1024) because we'd overflow around 4MB +      UI_PrintTime ( dlTimeBuf, sizeof dlTimeBuf, +        (n - (((downloadCount/1024) * n) / (downloadSize/1024))) * 1000); + +      Text_PaintCenter(leftWidth, yStart+216, scale, colorWhite, dlTimeBuf, 0); +      Text_PaintCenter(leftWidth, yStart+160, scale, colorWhite, va("(%s of %s copied)", dlSizeBuf, totalSizeBuf), 0); +    } else { +      Text_PaintCenter(leftWidth, yStart+216, scale, colorWhite, "estimating", 0); +      if (downloadSize) { +        Text_PaintCenter(leftWidth, yStart+160, scale, colorWhite, va("(%s of %s copied)", dlSizeBuf, totalSizeBuf), 0); +      } else { +        Text_PaintCenter(leftWidth, yStart+160, scale, colorWhite, va("(%s copied)", dlSizeBuf), 0); +      } +    } + +    if (xferRate) { +      Text_PaintCenter(leftWidth, yStart+272, scale, colorWhite, va("%s/Sec", xferRateBuf), 0); +    } +  } +} + +/* +======================== +UI_DrawConnectScreen + +This will also be overlaid on the cgame info screen during loading +to prevent it from blinking away too rapidly on local or lan games. +======================== +*/ +void UI_DrawConnectScreen( qboolean overlay ) { +  char      *s; +  uiClientState_t cstate; +  char      info[MAX_INFO_VALUE]; +  char text[256]; +  float centerPoint, yStart, scale; + +  menuDef_t *menu = Menus_FindByName("Connect"); + + +  if ( !overlay && menu ) { +    Menu_Paint(menu, qtrue); +  } + +  if (!overlay) { +    centerPoint = 320; +    yStart = 130; +    scale = 0.5f; +  } else { +    centerPoint = 320; +    yStart = 32; +    scale = 0.6f; +    return; +  } + +  // see what information we should display +  trap_GetClientState( &cstate ); + +  info[0] = '\0'; +  if( trap_GetConfigString( CS_SERVERINFO, info, sizeof(info) ) ) { +    Text_PaintCenter(centerPoint, yStart, scale, colorWhite, va( "Loading %s", Info_ValueForKey( info, "mapname" )), 0); +  } + +  if (!Q_stricmp(cstate.servername,"localhost")) { +    Text_PaintCenter(centerPoint, yStart + 48, scale, colorWhite, va("Starting up..."), ITEM_TEXTSTYLE_SHADOWEDMORE); +  } else { +    strcpy(text, va("Connecting to %s", cstate.servername)); +    Text_PaintCenter(centerPoint, yStart + 48, scale, colorWhite,text , ITEM_TEXTSTYLE_SHADOWEDMORE); +  } + + +  // display global MOTD at bottom +  Text_PaintCenter(centerPoint, 600, scale, colorWhite, Info_ValueForKey( cstate.updateInfoString, "motd" ), 0); +  // print any server info (server full, bad version, etc) +  if ( cstate.connState < CA_CONNECTED ) { +    Text_PaintCenter_AutoWrapped(centerPoint, yStart + 176, 630, 20, scale, colorWhite, cstate.messageString, 0); +  } + +  if ( lastConnState > cstate.connState ) { +    lastLoadingText[0] = '\0'; +  } +  lastConnState = cstate.connState; + +  switch ( cstate.connState ) { +  case CA_CONNECTING: +    s = va("Awaiting connection...%i", cstate.connectPacketCount); +    break; +  case CA_CHALLENGING: +    s = va("Awaiting challenge...%i", cstate.connectPacketCount); +    break; +  case CA_CONNECTED: { +    char downloadName[MAX_INFO_VALUE]; + +      trap_Cvar_VariableStringBuffer( "cl_downloadName", downloadName, sizeof(downloadName) ); +      if (*downloadName) { +        UI_DisplayDownloadInfo( downloadName, centerPoint, yStart, scale ); +        return; +      } +    } +    s = "Awaiting gamestate..."; +    break; +  case CA_LOADING: +    return; +  case CA_PRIMED: +    return; +  default: +    return; +  } + + +  if (Q_stricmp(cstate.servername,"localhost")) { +    Text_PaintCenter(centerPoint, yStart + 80, scale, colorWhite, s, 0); +  } + +  // password required / connection rejected information goes here +} + + +/* +================ +cvars +================ +*/ + +typedef struct { +  vmCvar_t  *vmCvar; +  char    *cvarName; +  char    *defaultString; +  int     cvarFlags; +} cvarTable_t; + +vmCvar_t  ui_ffa_fraglimit; +vmCvar_t  ui_ffa_timelimit; + +vmCvar_t  ui_tourney_fraglimit; +vmCvar_t  ui_tourney_timelimit; + +vmCvar_t  ui_team_fraglimit; +vmCvar_t  ui_team_timelimit; +vmCvar_t  ui_team_friendly; + +vmCvar_t  ui_ctf_capturelimit; +vmCvar_t  ui_ctf_timelimit; +vmCvar_t  ui_ctf_friendly; + +vmCvar_t  ui_arenasFile; +vmCvar_t  ui_botsFile; +vmCvar_t  ui_spScores1; +vmCvar_t  ui_spScores2; +vmCvar_t  ui_spScores3; +vmCvar_t  ui_spScores4; +vmCvar_t  ui_spScores5; +vmCvar_t  ui_spAwards; +vmCvar_t  ui_spVideos; +vmCvar_t  ui_spSkill; + +vmCvar_t  ui_spSelection; + +vmCvar_t  ui_browserMaster; +vmCvar_t  ui_browserGameType; +vmCvar_t  ui_browserSortKey; +vmCvar_t  ui_browserShowFull; +vmCvar_t  ui_browserShowEmpty; + +vmCvar_t  ui_brassTime; +vmCvar_t  ui_drawCrosshair; +vmCvar_t  ui_drawCrosshairNames; +vmCvar_t  ui_marks; + +vmCvar_t  ui_server1; +vmCvar_t  ui_server2; +vmCvar_t  ui_server3; +vmCvar_t  ui_server4; +vmCvar_t  ui_server5; +vmCvar_t  ui_server6; +vmCvar_t  ui_server7; +vmCvar_t  ui_server8; +vmCvar_t  ui_server9; +vmCvar_t  ui_server10; +vmCvar_t  ui_server11; +vmCvar_t  ui_server12; +vmCvar_t  ui_server13; +vmCvar_t  ui_server14; +vmCvar_t  ui_server15; +vmCvar_t  ui_server16; + +vmCvar_t  ui_redteam; +vmCvar_t  ui_redteam1; +vmCvar_t  ui_redteam2; +vmCvar_t  ui_redteam3; +vmCvar_t  ui_redteam4; +vmCvar_t  ui_redteam5; +vmCvar_t  ui_blueteam; +vmCvar_t  ui_blueteam1; +vmCvar_t  ui_blueteam2; +vmCvar_t  ui_blueteam3; +vmCvar_t  ui_blueteam4; +vmCvar_t  ui_blueteam5; +vmCvar_t  ui_teamName; +vmCvar_t  ui_dedicated; +vmCvar_t  ui_gameType; +vmCvar_t  ui_netGameType; +vmCvar_t  ui_actualNetGameType; +vmCvar_t  ui_joinGameType; +vmCvar_t  ui_netSource; +vmCvar_t  ui_serverFilterType; +vmCvar_t  ui_opponentName; +vmCvar_t  ui_menuFiles; +vmCvar_t  ui_currentTier; +vmCvar_t  ui_currentMap; +vmCvar_t  ui_currentNetMap; +vmCvar_t  ui_mapIndex; +vmCvar_t  ui_currentOpponent; +vmCvar_t  ui_selectedPlayer; +vmCvar_t  ui_selectedPlayerName; +vmCvar_t  ui_lastServerRefresh_0; +vmCvar_t  ui_lastServerRefresh_1; +vmCvar_t  ui_lastServerRefresh_2; +vmCvar_t  ui_lastServerRefresh_3; +vmCvar_t  ui_lastServerRefresh_0_time; +vmCvar_t  ui_lastServerRefresh_1_time; +vmCvar_t  ui_lastServerRefresh_2_time; +vmCvar_t  ui_lastServerRefresh_3_time; +vmCvar_t  ui_singlePlayerActive; +vmCvar_t  ui_scoreAccuracy; +vmCvar_t  ui_scoreImpressives; +vmCvar_t  ui_scoreExcellents; +vmCvar_t  ui_scoreCaptures; +vmCvar_t  ui_scoreDefends; +vmCvar_t  ui_scoreAssists; +vmCvar_t  ui_scoreGauntlets; +vmCvar_t  ui_scoreScore; +vmCvar_t  ui_scorePerfect; +vmCvar_t  ui_scoreTeam; +vmCvar_t  ui_scoreBase; +vmCvar_t  ui_scoreTimeBonus; +vmCvar_t  ui_scoreSkillBonus; +vmCvar_t  ui_scoreShutoutBonus; +vmCvar_t  ui_scoreTime; +vmCvar_t  ui_captureLimit; +vmCvar_t  ui_fragLimit; +vmCvar_t  ui_smallFont; +vmCvar_t  ui_bigFont; +vmCvar_t  ui_findPlayer; +vmCvar_t  ui_Q3Model; +vmCvar_t  ui_hudFiles; +vmCvar_t  ui_recordSPDemo; +vmCvar_t  ui_realCaptureLimit; +vmCvar_t  ui_realWarmUp; +vmCvar_t  ui_serverStatusTimeOut; + +//TA: bank values +vmCvar_t  ui_bank; +vmCvar_t  ui_winner; + + +// bk001129 - made static to avoid aliasing +static cvarTable_t    cvarTable[] = { +  { &ui_ffa_fraglimit, "ui_ffa_fraglimit", "20", CVAR_ARCHIVE }, +  { &ui_ffa_timelimit, "ui_ffa_timelimit", "0", CVAR_ARCHIVE }, + +  { &ui_tourney_fraglimit, "ui_tourney_fraglimit", "0", CVAR_ARCHIVE }, +  { &ui_tourney_timelimit, "ui_tourney_timelimit", "15", CVAR_ARCHIVE }, + +  { &ui_team_fraglimit, "ui_team_fraglimit", "0", CVAR_ARCHIVE }, +  { &ui_team_timelimit, "ui_team_timelimit", "20", CVAR_ARCHIVE }, +  { &ui_team_friendly, "ui_team_friendly",  "1", CVAR_ARCHIVE }, + +  { &ui_ctf_capturelimit, "ui_ctf_capturelimit", "8", CVAR_ARCHIVE }, +  { &ui_ctf_timelimit, "ui_ctf_timelimit", "30", CVAR_ARCHIVE }, +  { &ui_ctf_friendly, "ui_ctf_friendly",  "0", CVAR_ARCHIVE }, + +  { &ui_arenasFile, "g_arenasFile", "", CVAR_INIT|CVAR_ROM }, +  { &ui_botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM }, +  { &ui_spScores1, "g_spScores1", "", CVAR_ARCHIVE | CVAR_ROM }, +  { &ui_spScores2, "g_spScores2", "", CVAR_ARCHIVE | CVAR_ROM }, +  { &ui_spScores3, "g_spScores3", "", CVAR_ARCHIVE | CVAR_ROM }, +  { &ui_spScores4, "g_spScores4", "", CVAR_ARCHIVE | CVAR_ROM }, +  { &ui_spScores5, "g_spScores5", "", CVAR_ARCHIVE | CVAR_ROM }, +  { &ui_spAwards, "g_spAwards", "", CVAR_ARCHIVE | CVAR_ROM }, +  { &ui_spVideos, "g_spVideos", "", CVAR_ARCHIVE | CVAR_ROM }, +  { &ui_spSkill, "g_spSkill", "2", CVAR_ARCHIVE }, + +  { &ui_spSelection, "ui_spSelection", "", CVAR_ROM }, +  { &ui_winner, "ui_winner", "", CVAR_ROM }, + +  { &ui_browserMaster, "ui_browserMaster", "0", CVAR_ARCHIVE }, +  { &ui_browserGameType, "ui_browserGameType", "0", CVAR_ARCHIVE }, +  { &ui_browserSortKey, "ui_browserSortKey", "4", CVAR_ARCHIVE }, +  { &ui_browserShowFull, "ui_browserShowFull", "1", CVAR_ARCHIVE }, +  { &ui_browserShowEmpty, "ui_browserShowEmpty", "1", CVAR_ARCHIVE }, + +  { &ui_brassTime, "cg_brassTime", "2500", CVAR_ARCHIVE }, +  { &ui_drawCrosshair, "cg_drawCrosshair", "4", CVAR_ARCHIVE }, +  { &ui_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE }, +  { &ui_marks, "cg_marks", "1", CVAR_ARCHIVE }, + +  { &ui_server1, "server1", "", CVAR_ARCHIVE }, +  { &ui_server2, "server2", "", CVAR_ARCHIVE }, +  { &ui_server3, "server3", "", CVAR_ARCHIVE }, +  { &ui_server4, "server4", "", CVAR_ARCHIVE }, +  { &ui_server5, "server5", "", CVAR_ARCHIVE }, +  { &ui_server6, "server6", "", CVAR_ARCHIVE }, +  { &ui_server7, "server7", "", CVAR_ARCHIVE }, +  { &ui_server8, "server8", "", CVAR_ARCHIVE }, +  { &ui_server9, "server9", "", CVAR_ARCHIVE }, +  { &ui_server10, "server10", "", CVAR_ARCHIVE }, +  { &ui_server11, "server11", "", CVAR_ARCHIVE }, +  { &ui_server12, "server12", "", CVAR_ARCHIVE }, +  { &ui_server13, "server13", "", CVAR_ARCHIVE }, +  { &ui_server14, "server14", "", CVAR_ARCHIVE }, +  { &ui_server15, "server15", "", CVAR_ARCHIVE }, +  { &ui_server16, "server16", "", CVAR_ARCHIVE }, +  { &ui_new, "ui_new", "0", CVAR_TEMP }, +  { &ui_debug, "ui_debug", "0", CVAR_TEMP }, +  { &ui_initialized, "ui_initialized", "0", CVAR_TEMP }, +  { &ui_teamName, "ui_teamName", "Pagans", CVAR_ARCHIVE }, +  { &ui_opponentName, "ui_opponentName", "Stroggs", CVAR_ARCHIVE }, +  { &ui_redteam, "ui_redteam", "Pagans", CVAR_ARCHIVE }, +  { &ui_blueteam, "ui_blueteam", "Stroggs", CVAR_ARCHIVE }, +  { &ui_dedicated, "ui_dedicated", "0", CVAR_ARCHIVE }, +  { &ui_gameType, "ui_gametype", "3", CVAR_ARCHIVE }, +  { &ui_joinGameType, "ui_joinGametype", "0", CVAR_ARCHIVE }, +  { &ui_netGameType, "ui_netGametype", "3", CVAR_ARCHIVE }, +  { &ui_actualNetGameType, "ui_actualNetGametype", "3", CVAR_ARCHIVE }, +  { &ui_redteam1, "ui_redteam1", "0", CVAR_ARCHIVE }, +  { &ui_redteam2, "ui_redteam2", "0", CVAR_ARCHIVE }, +  { &ui_redteam3, "ui_redteam3", "0", CVAR_ARCHIVE }, +  { &ui_redteam4, "ui_redteam4", "0", CVAR_ARCHIVE }, +  { &ui_redteam5, "ui_redteam5", "0", CVAR_ARCHIVE }, +  { &ui_blueteam1, "ui_blueteam1", "0", CVAR_ARCHIVE }, +  { &ui_blueteam2, "ui_blueteam2", "0", CVAR_ARCHIVE }, +  { &ui_blueteam3, "ui_blueteam3", "0", CVAR_ARCHIVE }, +  { &ui_blueteam4, "ui_blueteam4", "0", CVAR_ARCHIVE }, +  { &ui_blueteam5, "ui_blueteam5", "0", CVAR_ARCHIVE }, +  { &ui_netSource, "ui_netSource", "0", CVAR_ARCHIVE }, +  { &ui_menuFiles, "ui_menuFiles", "ui/menus.txt", CVAR_ARCHIVE }, +  { &ui_currentTier, "ui_currentTier", "0", CVAR_ARCHIVE }, +  { &ui_currentMap, "ui_currentMap", "0", CVAR_ARCHIVE }, +  { &ui_currentNetMap, "ui_currentNetMap", "0", CVAR_ARCHIVE }, +  { &ui_mapIndex, "ui_mapIndex", "0", CVAR_ARCHIVE }, +  { &ui_currentOpponent, "ui_currentOpponent", "0", CVAR_ARCHIVE }, +  { &ui_selectedPlayer, "cg_selectedPlayer", "0", CVAR_ARCHIVE}, +  { &ui_selectedPlayerName, "cg_selectedPlayerName", "", CVAR_ARCHIVE}, +  { &ui_lastServerRefresh_0, "ui_lastServerRefresh_0", "", CVAR_ARCHIVE}, +  { &ui_lastServerRefresh_1, "ui_lastServerRefresh_1", "", CVAR_ARCHIVE}, +  { &ui_lastServerRefresh_2, "ui_lastServerRefresh_2", "", CVAR_ARCHIVE}, +  { &ui_lastServerRefresh_3, "ui_lastServerRefresh_3", "", CVAR_ARCHIVE}, +  { &ui_lastServerRefresh_0, "ui_lastServerRefresh_0_time", "", CVAR_ARCHIVE}, +  { &ui_lastServerRefresh_1, "ui_lastServerRefresh_1_time", "", CVAR_ARCHIVE}, +  { &ui_lastServerRefresh_2, "ui_lastServerRefresh_2_time", "", CVAR_ARCHIVE}, +  { &ui_lastServerRefresh_3, "ui_lastServerRefresh_3_time", "", CVAR_ARCHIVE}, +  { &ui_singlePlayerActive, "ui_singlePlayerActive", "0", 0}, +  { &ui_scoreAccuracy, "ui_scoreAccuracy", "0", CVAR_ARCHIVE}, +  { &ui_scoreImpressives, "ui_scoreImpressives", "0", CVAR_ARCHIVE}, +  { &ui_scoreExcellents, "ui_scoreExcellents", "0", CVAR_ARCHIVE}, +  { &ui_scoreCaptures, "ui_scoreCaptures", "0", CVAR_ARCHIVE}, +  { &ui_scoreDefends, "ui_scoreDefends", "0", CVAR_ARCHIVE}, +  { &ui_scoreAssists, "ui_scoreAssists", "0", CVAR_ARCHIVE}, +  { &ui_scoreGauntlets, "ui_scoreGauntlets", "0",CVAR_ARCHIVE}, +  { &ui_scoreScore, "ui_scoreScore", "0", CVAR_ARCHIVE}, +  { &ui_scorePerfect, "ui_scorePerfect", "0", CVAR_ARCHIVE}, +  { &ui_scoreTeam, "ui_scoreTeam", "0 to 0", CVAR_ARCHIVE}, +  { &ui_scoreBase, "ui_scoreBase", "0", CVAR_ARCHIVE}, +  { &ui_scoreTime, "ui_scoreTime", "00:00", CVAR_ARCHIVE}, +  { &ui_scoreTimeBonus, "ui_scoreTimeBonus", "0", CVAR_ARCHIVE}, +  { &ui_scoreSkillBonus, "ui_scoreSkillBonus", "0", CVAR_ARCHIVE}, +  { &ui_scoreShutoutBonus, "ui_scoreShutoutBonus", "0", CVAR_ARCHIVE}, +  { &ui_fragLimit, "ui_fragLimit", "10", 0}, +  { &ui_captureLimit, "ui_captureLimit", "5", 0}, +  { &ui_smallFont, "ui_smallFont", "0.2", CVAR_ARCHIVE}, +  { &ui_bigFont, "ui_bigFont", "0.5", CVAR_ARCHIVE}, +  { &ui_findPlayer, "ui_findPlayer", "Sarge", CVAR_ARCHIVE}, +  { &ui_Q3Model, "ui_q3model", "0", CVAR_ARCHIVE}, +  { &ui_hudFiles, "cg_hudFiles", "ui/hud.txt", CVAR_ARCHIVE}, +  { &ui_recordSPDemo, "ui_recordSPDemo", "0", CVAR_ARCHIVE}, +  { &ui_teamArenaFirstRun, "ui_teamArenaFirstRun", "0", CVAR_ARCHIVE}, +  { &ui_realWarmUp, "g_warmup", "20", CVAR_ARCHIVE}, +  { &ui_realCaptureLimit, "capturelimit", "8", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART}, +  { &ui_serverStatusTimeOut, "ui_serverStatusTimeOut", "7000", CVAR_ARCHIVE}, + +  { &ui_bank, "ui_bank", "0", 0 }, + +}; + +// bk001129 - made static to avoid aliasing +static int    cvarTableSize = sizeof(cvarTable) / sizeof(cvarTable[0]); + + +/* +================= +UI_RegisterCvars +================= +*/ +void UI_RegisterCvars( void ) { +  int     i; +  cvarTable_t *cv; + +  for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) { +    trap_Cvar_Register( cv->vmCvar, cv->cvarName, cv->defaultString, cv->cvarFlags ); +  } +} + +/* +================= +UI_UpdateCvars +================= +*/ +void UI_UpdateCvars( void ) { +  int     i; +  cvarTable_t *cv; + +  for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) { +    trap_Cvar_Update( cv->vmCvar ); +  } +} + + +/* +================= +ArenaServers_StopRefresh +================= +*/ +static void UI_StopServerRefresh( void ) +{ +  int count; + +  if (!uiInfo.serverStatus.refreshActive) { +    // not currently refreshing +    return; +  } +  uiInfo.serverStatus.refreshActive = qfalse; +  Com_Printf("%d servers listed in browser with %d players.\n", +          uiInfo.serverStatus.numDisplayServers, +          uiInfo.serverStatus.numPlayersOnServers); +  count = trap_LAN_GetServerCount(ui_netSource.integer); +  if (count - uiInfo.serverStatus.numDisplayServers > 0) { +    Com_Printf("%d servers not listed due to packet loss or pings higher than %d\n", +            count - uiInfo.serverStatus.numDisplayServers, +            (int) trap_Cvar_VariableValue("cl_maxPing")); +  } + +} + +/* +================= +UI_DoServerRefresh +================= +*/ +static void UI_DoServerRefresh( void ) +{ +  qboolean wait = qfalse; + +  if (!uiInfo.serverStatus.refreshActive) { +    return; +  } +  if (ui_netSource.integer != AS_FAVORITES) { +    if (ui_netSource.integer == AS_LOCAL) { +      if (!trap_LAN_GetServerCount(ui_netSource.integer)) { +        wait = qtrue; +      } +    } else { +      if (trap_LAN_GetServerCount(ui_netSource.integer) < 0) { +        wait = qtrue; +      } +    } +  } + +  if (uiInfo.uiDC.realTime < uiInfo.serverStatus.refreshtime) { +    if (wait) { +      return; +    } +  } + +  // if still trying to retrieve pings +  if (trap_LAN_UpdateVisiblePings(ui_netSource.integer)) { +    uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000; +  } else if (!wait) { +    // get the last servers in the list +    UI_BuildServerDisplayList(2); +    // stop the refresh +    UI_StopServerRefresh(); +  } +  // +  UI_BuildServerDisplayList(qfalse); +} + +/* +================= +UI_StartServerRefresh +================= +*/ +static void UI_StartServerRefresh(qboolean full) +{ +  int   i; +  char  *ptr; +  int   time; +  qtime_t q; + +  time = trap_RealTime(&q); +  trap_Cvar_Set( va("ui_lastServerRefresh_%i_time", ui_netSource.integer ), +                        va( "%i", time ) ); +  trap_Cvar_Set( va("ui_lastServerRefresh_%i", ui_netSource.integer), +			va("%s-%i, %i at %i:%02i", MonthAbbrev[q.tm_mon],q.tm_mday, 1900+q.tm_year,q.tm_hour,q.tm_min)); + +  if (!full) { +    UI_UpdatePendingPings(); +    return; +  } + +  uiInfo.serverStatus.refreshActive = qtrue; +  uiInfo.serverStatus.nextDisplayRefresh = uiInfo.uiDC.realTime + 1000; +  // clear number of displayed servers +  uiInfo.serverStatus.numDisplayServers = 0; +  uiInfo.serverStatus.numPlayersOnServers = 0; +  // mark all servers as visible so we store ping updates for them +  trap_LAN_MarkServerVisible(ui_netSource.integer, -1, qtrue); +  // reset all the pings +  trap_LAN_ResetPings(ui_netSource.integer); +  // +  if( ui_netSource.integer == AS_LOCAL ) { +    trap_Cmd_ExecuteText( EXEC_NOW, "localservers\n" ); +    uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000; +    return; +  } + +  uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 5000; +  if( ui_netSource.integer == AS_GLOBAL || ui_netSource.integer == AS_MPLAYER ) { +    if( ui_netSource.integer == AS_GLOBAL ) { +      i = 0; +    } +    else { +      i = 1; +    } + +    ptr = UI_Cvar_VariableString("debug_protocol"); +    if (strlen(ptr)) { +      trap_Cmd_ExecuteText( EXEC_NOW, va( "globalservers %d %s full empty\n", i, ptr)); +    } +    else { +      trap_Cmd_ExecuteText( EXEC_NOW, va( "globalservers %d %d full empty\n", i, (int)trap_Cvar_VariableValue( "protocol" ) ) ); +    } +  } +} + diff --git a/src/ui/ui_players.c b/src/ui/ui_players.c new file mode 100644 index 0000000..5dbfdd3 --- /dev/null +++ b/src/ui/ui_players.c @@ -0,0 +1,1369 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +// ui_players.c + +#include "ui_local.h" + + +#define UI_TIMER_GESTURE    2300 +#define UI_TIMER_JUMP      1000 +#define UI_TIMER_LAND      130 +#define UI_TIMER_WEAPON_SWITCH  300 +#define UI_TIMER_ATTACK      500 +#define  UI_TIMER_MUZZLE_FLASH  20 +#define  UI_TIMER_WEAPON_DELAY  250 + +#define JUMP_HEIGHT        56 + +#define SWINGSPEED        0.3f + +#define SPIN_SPEED        0.9f +#define COAST_TIME        1000 + + +static int      dp_realtime; +static float    jumpHeight; +sfxHandle_t weaponChangeSound; + + +/* +=============== +UI_PlayerInfo_SetWeapon +=============== +*/ +static void UI_PlayerInfo_SetWeapon( playerInfo_t *pi, weapon_t weaponNum ) +{ +  //TA: FIXME: this is probably useless for trem +/*  gitem_t *  item; +  char    path[MAX_QPATH]; + +  pi->currentWeapon = weaponNum; +tryagain: +  pi->realWeapon = weaponNum; +  pi->weaponModel = 0; +  pi->barrelModel = 0; +  pi->flashModel = 0; + +  if ( weaponNum == WP_NONE ) { +    return; +  } + +  if ( item->classname ) { +    pi->weaponModel = trap_R_RegisterModel( item->world_model[0] ); +  } + +  if( pi->weaponModel == 0 ) { +    if( weaponNum == WP_MACHINEGUN ) { +      weaponNum = WP_NONE; +      goto tryagain; +    } +    weaponNum = WP_MACHINEGUN; +    goto tryagain; +  } + +  if ( weaponNum == WP_MACHINEGUN ) { +    strcpy( path, item->world_model[0] ); +    COM_StripExtension( path, path ); +    strcat( path, "_barrel.md3" ); +    pi->barrelModel = trap_R_RegisterModel( path ); +  } + +  strcpy( path, item->world_model[0] ); +  COM_StripExtension( path, path ); +  strcat( path, "_flash.md3" ); +  pi->flashModel = trap_R_RegisterModel( path ); + +  switch( weaponNum ) { +  case WP_GAUNTLET: +    MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 ); +    break; + +  case WP_MACHINEGUN: +    MAKERGB( pi->flashDlightColor, 1, 1, 0 ); +    break; + +  case WP_SHOTGUN: +    MAKERGB( pi->flashDlightColor, 1, 1, 0 ); +    break; + +  case WP_GRENADE_LAUNCHER: +    MAKERGB( pi->flashDlightColor, 1, 0.7f, 0.5f ); +    break; + +  case WP_ROCKET_LAUNCHER: +    MAKERGB( pi->flashDlightColor, 1, 0.75f, 0 ); +    break; + +  case WP_TESLAGEN: +    MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 ); +    break; + +  case WP_RAILGUN: +    MAKERGB( pi->flashDlightColor, 1, 0.5f, 0 ); +    break; + +  case WP_BFG: +    MAKERGB( pi->flashDlightColor, 1, 0.7f, 1 ); +    break; + +  case WP_GRAPPLING_HOOK: +    MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 ); +    break; + +  default: +    MAKERGB( pi->flashDlightColor, 1, 1, 1 ); +    break; +  }*/ +} + + +/* +=============== +UI_ForceLegsAnim +=============== +*/ +static void UI_ForceLegsAnim( playerInfo_t *pi, int anim ) { +  pi->legsAnim = ( ( pi->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; + +  if ( anim == LEGS_JUMP ) { +    pi->legsAnimationTimer = UI_TIMER_JUMP; +  } +} + + +/* +=============== +UI_SetLegsAnim +=============== +*/ +static void UI_SetLegsAnim( playerInfo_t *pi, int anim ) { +  if ( pi->pendingLegsAnim ) { +    anim = pi->pendingLegsAnim; +    pi->pendingLegsAnim = 0; +  } +  UI_ForceLegsAnim( pi, anim ); +} + + +/* +=============== +UI_ForceTorsoAnim +=============== +*/ +static void UI_ForceTorsoAnim( playerInfo_t *pi, int anim ) { +  pi->torsoAnim = ( ( pi->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; + +  if ( anim == TORSO_GESTURE ) { +    pi->torsoAnimationTimer = UI_TIMER_GESTURE; +  } + +  if ( anim == TORSO_ATTACK || anim == TORSO_ATTACK2 ) { +    pi->torsoAnimationTimer = UI_TIMER_ATTACK; +  } +} + + +/* +=============== +UI_SetTorsoAnim +=============== +*/ +static void UI_SetTorsoAnim( playerInfo_t *pi, int anim ) { +  if ( pi->pendingTorsoAnim ) { +    anim = pi->pendingTorsoAnim; +    pi->pendingTorsoAnim = 0; +  } + +  UI_ForceTorsoAnim( pi, anim ); +} + + +/* +=============== +UI_TorsoSequencing +=============== +*/ +static void UI_TorsoSequencing( playerInfo_t *pi ) { +  int    currentAnim; + +  currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT; + +  if ( pi->weapon != pi->currentWeapon ) { +    if ( currentAnim != TORSO_DROP ) { +      pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH; +      UI_ForceTorsoAnim( pi, TORSO_DROP ); +    } +  } + +  if ( pi->torsoAnimationTimer > 0 ) { +    return; +  } + +  if( currentAnim == TORSO_GESTURE ) { +    UI_SetTorsoAnim( pi, TORSO_STAND ); +    return; +  } + +  if( currentAnim == TORSO_ATTACK || currentAnim == TORSO_ATTACK2 ) { +    UI_SetTorsoAnim( pi, TORSO_STAND ); +    return; +  } + +  if ( currentAnim == TORSO_DROP ) { +    UI_PlayerInfo_SetWeapon( pi, pi->weapon ); +    pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH; +    UI_ForceTorsoAnim( pi, TORSO_RAISE ); +    return; +  } + +  if ( currentAnim == TORSO_RAISE ) { +    UI_SetTorsoAnim( pi, TORSO_STAND ); +    return; +  } +} + + +/* +=============== +UI_LegsSequencing +=============== +*/ +static void UI_LegsSequencing( playerInfo_t *pi ) { +  int    currentAnim; + +  currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT; + +  if ( pi->legsAnimationTimer > 0 ) { +    if ( currentAnim == LEGS_JUMP ) { +      jumpHeight = JUMP_HEIGHT * sin( M_PI * ( UI_TIMER_JUMP - pi->legsAnimationTimer ) / UI_TIMER_JUMP ); +    } +    return; +  } + +  if ( currentAnim == LEGS_JUMP ) { +    UI_ForceLegsAnim( pi, LEGS_LAND ); +    pi->legsAnimationTimer = UI_TIMER_LAND; +    jumpHeight = 0; +    return; +  } + +  if ( currentAnim == LEGS_LAND ) { +    UI_SetLegsAnim( pi, LEGS_IDLE ); +    return; +  } +} + + +/* +====================== +UI_PositionEntityOnTag +====================== +*/ +static void UI_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, +              clipHandle_t parentModel, char *tagName ) { +  int        i; +  orientation_t  lerped; + +  // lerp the tag +  trap_CM_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, +    1.0 - parent->backlerp, tagName ); + +  // FIXME: allow origin offsets along tag? +  VectorCopy( parent->origin, entity->origin ); +  for ( i = 0 ; i < 3 ; i++ ) { +    VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); +  } + +  // cast away const because of compiler problems +  MatrixMultiply( lerped.axis, ((refEntity_t*)parent)->axis, entity->axis ); +  entity->backlerp = parent->backlerp; +} + + +/* +====================== +UI_PositionRotatedEntityOnTag +====================== +*/ +static void UI_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, +              clipHandle_t parentModel, char *tagName ) { +  int        i; +  orientation_t  lerped; +  vec3_t      tempAxis[3]; + +  // lerp the tag +  trap_CM_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, +    1.0 - parent->backlerp, tagName ); + +  // FIXME: allow origin offsets along tag? +  VectorCopy( parent->origin, entity->origin ); +  for ( i = 0 ; i < 3 ; i++ ) { +    VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); +  } + +  // cast away const because of compiler problems +  MatrixMultiply( entity->axis, ((refEntity_t *)parent)->axis, tempAxis ); +  MatrixMultiply( lerped.axis, tempAxis, entity->axis ); +} + + +/* +=============== +UI_SetLerpFrameAnimation +=============== +*/ +static void UI_SetLerpFrameAnimation( playerInfo_t *ci, lerpFrame_t *lf, int newAnimation ) { +  animation_t  *anim; + +  lf->animationNumber = newAnimation; +  newAnimation &= ~ANIM_TOGGLEBIT; + +  if ( newAnimation < 0 || newAnimation >= MAX_PLAYER_ANIMATIONS ) { +    trap_Error( va("Bad animation number: %i", newAnimation) ); +  } + +  anim = &ci->animations[ newAnimation ]; + +  lf->animation = anim; +  lf->animationTime = lf->frameTime + anim->initialLerp; +} + + +/* +=============== +UI_RunLerpFrame +=============== +*/ +static void UI_RunLerpFrame( playerInfo_t *ci, lerpFrame_t *lf, int newAnimation ) { +  int      f; +  animation_t  *anim; + +  // see if the animation sequence is switching +  if ( newAnimation != lf->animationNumber || !lf->animation ) { +    UI_SetLerpFrameAnimation( ci, lf, newAnimation ); +  } + +  // if we have passed the current frame, move it to +  // oldFrame and calculate a new frame +  if ( dp_realtime >= lf->frameTime ) { +    lf->oldFrame = lf->frame; +    lf->oldFrameTime = lf->frameTime; + +    // get the next frame based on the animation +    anim = lf->animation; +    if ( dp_realtime < lf->animationTime ) { +      lf->frameTime = lf->animationTime;    // initial lerp +    } else { +      lf->frameTime = lf->oldFrameTime + anim->frameLerp; +    } +    f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp; +    if ( f >= anim->numFrames ) { +      f -= anim->numFrames; +      if ( anim->loopFrames ) { +        f %= anim->loopFrames; +        f += anim->numFrames - anim->loopFrames; +      } else { +        f = anim->numFrames - 1; +        // the animation is stuck at the end, so it +        // can immediately transition to another sequence +        lf->frameTime = dp_realtime; +      } +    } +    lf->frame = anim->firstFrame + f; +    if ( dp_realtime > lf->frameTime ) { +      lf->frameTime = dp_realtime; +    } +  } + +  if ( lf->frameTime > dp_realtime + 200 ) { +    lf->frameTime = dp_realtime; +  } + +  if ( lf->oldFrameTime > dp_realtime ) { +    lf->oldFrameTime = dp_realtime; +  } +  // calculate current lerp value +  if ( lf->frameTime == lf->oldFrameTime ) { +    lf->backlerp = 0; +  } else { +    lf->backlerp = 1.0 - (float)( dp_realtime - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime ); +  } +} + + +/* +=============== +UI_PlayerAnimation +=============== +*/ +static void UI_PlayerAnimation( playerInfo_t *pi, int *legsOld, int *legs, float *legsBackLerp, +            int *torsoOld, int *torso, float *torsoBackLerp ) { + +  // legs animation +  pi->legsAnimationTimer -= uiInfo.uiDC.frameTime; +  if ( pi->legsAnimationTimer < 0 ) { +    pi->legsAnimationTimer = 0; +  } + +  UI_LegsSequencing( pi ); + +  if ( pi->legs.yawing && ( pi->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_IDLE ) { +    UI_RunLerpFrame( pi, &pi->legs, LEGS_TURN ); +  } else { +    UI_RunLerpFrame( pi, &pi->legs, pi->legsAnim ); +  } +  *legsOld = pi->legs.oldFrame; +  *legs = pi->legs.frame; +  *legsBackLerp = pi->legs.backlerp; + +  // torso animation +  pi->torsoAnimationTimer -= uiInfo.uiDC.frameTime; +  if ( pi->torsoAnimationTimer < 0 ) { +    pi->torsoAnimationTimer = 0; +  } + +  UI_TorsoSequencing( pi ); + +  UI_RunLerpFrame( pi, &pi->torso, pi->torsoAnim ); +  *torsoOld = pi->torso.oldFrame; +  *torso = pi->torso.frame; +  *torsoBackLerp = pi->torso.backlerp; +} + + +/* +================== +UI_SwingAngles +================== +*/ +static void UI_SwingAngles( float destination, float swingTolerance, float clampTolerance, +          float speed, float *angle, qboolean *swinging ) { +  float  swing; +  float  move; +  float  scale; + +  if ( !*swinging ) { +    // see if a swing should be started +    swing = AngleSubtract( *angle, destination ); +    if ( swing > swingTolerance || swing < -swingTolerance ) { +      *swinging = qtrue; +    } +  } + +  if ( !*swinging ) { +    return; +  } + +  // modify the speed depending on the delta +  // so it doesn't seem so linear +  swing = AngleSubtract( destination, *angle ); +  scale = fabs( swing ); +  if ( scale < swingTolerance * 0.5 ) { +    scale = 0.5; +  } else if ( scale < swingTolerance ) { +    scale = 1.0; +  } else { +    scale = 2.0; +  } + +  // swing towards the destination angle +  if ( swing >= 0 ) { +    move = uiInfo.uiDC.frameTime * scale * speed; +    if ( move >= swing ) { +      move = swing; +      *swinging = qfalse; +    } +    *angle = AngleMod( *angle + move ); +  } else if ( swing < 0 ) { +    move = uiInfo.uiDC.frameTime * scale * -speed; +    if ( move <= swing ) { +      move = swing; +      *swinging = qfalse; +    } +    *angle = AngleMod( *angle + move ); +  } + +  // clamp to no more than tolerance +  swing = AngleSubtract( destination, *angle ); +  if ( swing > clampTolerance ) { +    *angle = AngleMod( destination - (clampTolerance - 1) ); +  } else if ( swing < -clampTolerance ) { +    *angle = AngleMod( destination + (clampTolerance - 1) ); +  } +} + + +/* +====================== +UI_MovedirAdjustment +====================== +*/ +static float UI_MovedirAdjustment( playerInfo_t *pi ) { +  vec3_t    relativeAngles; +  vec3_t    moveVector; + +  VectorSubtract( pi->viewAngles, pi->moveAngles, relativeAngles ); +  AngleVectors( relativeAngles, moveVector, NULL, NULL ); +  if ( Q_fabs( moveVector[0] ) < 0.01 ) { +    moveVector[0] = 0.0; +  } +  if ( Q_fabs( moveVector[1] ) < 0.01 ) { +    moveVector[1] = 0.0; +  } + +  if ( moveVector[1] == 0 && moveVector[0] > 0 ) { +    return 0; +  } +  if ( moveVector[1] < 0 && moveVector[0] > 0 ) { +    return 22; +  } +  if ( moveVector[1] < 0 && moveVector[0] == 0 ) { +    return 45; +  } +  if ( moveVector[1] < 0 && moveVector[0] < 0 ) { +    return -22; +  } +  if ( moveVector[1] == 0 && moveVector[0] < 0 ) { +    return 0; +  } +  if ( moveVector[1] > 0 && moveVector[0] < 0 ) { +    return 22; +  } +  if ( moveVector[1] > 0 && moveVector[0] == 0 ) { +    return  -45; +  } + +  return -22; +} + + +/* +=============== +UI_PlayerAngles +=============== +*/ +static void UI_PlayerAngles( playerInfo_t *pi, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] ) { +  vec3_t    legsAngles, torsoAngles, headAngles; +  float    dest; +  float    adjust; + +  VectorCopy( pi->viewAngles, headAngles ); +  headAngles[YAW] = AngleMod( headAngles[YAW] ); +  VectorClear( legsAngles ); +  VectorClear( torsoAngles ); + +  // --------- yaw ------------- + +  // allow yaw to drift a bit +  if ( ( pi->legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLE +    || ( pi->torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STAND  ) { +    // if not standing still, always point all in the same direction +    pi->torso.yawing = qtrue;  // always center +    pi->torso.pitching = qtrue;  // always center +    pi->legs.yawing = qtrue;  // always center +  } + +  // adjust legs for movement dir +  adjust = UI_MovedirAdjustment( pi ); +  legsAngles[YAW] = headAngles[YAW] + adjust; +  torsoAngles[YAW] = headAngles[YAW] + 0.25 * adjust; + + +  // torso +  UI_SwingAngles( torsoAngles[YAW], 25, 90, SWINGSPEED, &pi->torso.yawAngle, &pi->torso.yawing ); +  UI_SwingAngles( legsAngles[YAW], 40, 90, SWINGSPEED, &pi->legs.yawAngle, &pi->legs.yawing ); + +  torsoAngles[YAW] = pi->torso.yawAngle; +  legsAngles[YAW] = pi->legs.yawAngle; + +  // --------- pitch ------------- + +  // only show a fraction of the pitch angle in the torso +  if ( headAngles[PITCH] > 180 ) { +    dest = (-360 + headAngles[PITCH]) * 0.75; +  } else { +    dest = headAngles[PITCH] * 0.75; +  } +  UI_SwingAngles( dest, 15, 30, 0.1f, &pi->torso.pitchAngle, &pi->torso.pitching ); +  torsoAngles[PITCH] = pi->torso.pitchAngle; + +  // pull the angles back out of the hierarchial chain +  AnglesSubtract( headAngles, torsoAngles, headAngles ); +  AnglesSubtract( torsoAngles, legsAngles, torsoAngles ); +  AnglesToAxis( legsAngles, legs ); +  AnglesToAxis( torsoAngles, torso ); +  AnglesToAxis( headAngles, head ); +} + + +/* +=============== +UI_PlayerFloatSprite +=============== +*/ +static void UI_PlayerFloatSprite( playerInfo_t *pi, vec3_t origin, qhandle_t shader ) { +  refEntity_t    ent; + +  memset( &ent, 0, sizeof( ent ) ); +  VectorCopy( origin, ent.origin ); +  ent.origin[2] += 48; +  ent.reType = RT_SPRITE; +  ent.customShader = shader; +  ent.radius = 10; +  ent.renderfx = 0; +  trap_R_AddRefEntityToScene( &ent ); +} + + +/* +====================== +UI_MachinegunSpinAngle +====================== +*/ +float  UI_MachinegunSpinAngle( playerInfo_t *pi ) { +  int    delta; +  float  angle; +  float  speed; +  int    torsoAnim; + +  delta = dp_realtime - pi->barrelTime; +  if ( pi->barrelSpinning ) { +    angle = pi->barrelAngle + delta * SPIN_SPEED; +  } else { +    if ( delta > COAST_TIME ) { +      delta = COAST_TIME; +    } + +    speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME ); +    angle = pi->barrelAngle + delta * speed; +  } + +  torsoAnim = pi->torsoAnim  & ~ANIM_TOGGLEBIT; +  if( torsoAnim == TORSO_ATTACK2 ) { +    torsoAnim = TORSO_ATTACK; +  } +  if ( pi->barrelSpinning == !(torsoAnim == TORSO_ATTACK) ) { +    pi->barrelTime = dp_realtime; +    pi->barrelAngle = AngleMod( angle ); +    pi->barrelSpinning = !!(torsoAnim == TORSO_ATTACK); +  } + +  return angle; +} + + +/* +=============== +UI_DrawPlayer +=============== +*/ +void UI_DrawPlayer( float x, float y, float w, float h, playerInfo_t *pi, int time ) { +  refdef_t    refdef; +  refEntity_t    legs; +  refEntity_t    torso; +  refEntity_t    head; +  refEntity_t    gun; +  refEntity_t    barrel; +  refEntity_t    flash; +  vec3_t      origin; +  int        renderfx; +  vec3_t      mins = {-16, -16, -24}; +  vec3_t      maxs = {16, 16, 32}; +  float      len; +  float      xx; + +  if ( !pi->legsModel || !pi->torsoModel || !pi->headModel || !pi->animations[0].numFrames ) { +    return; +  } + +  // this allows the ui to cache the player model on the main menu +  if (w == 0 || h == 0) { +    return; +  } + +  dp_realtime = time; + +  if ( pi->pendingWeapon != -1 && dp_realtime > pi->weaponTimer ) { +    pi->weapon = pi->pendingWeapon; +    pi->lastWeapon = pi->pendingWeapon; +    pi->pendingWeapon = -1; +    pi->weaponTimer = 0; +    if( pi->currentWeapon != pi->weapon ) { +      trap_S_StartLocalSound( weaponChangeSound, CHAN_LOCAL ); +    } +  } + +  UI_AdjustFrom640( &x, &y, &w, &h ); + +  y -= jumpHeight; + +  memset( &refdef, 0, sizeof( refdef ) ); +  memset( &legs, 0, sizeof(legs) ); +  memset( &torso, 0, sizeof(torso) ); +  memset( &head, 0, sizeof(head) ); + +  refdef.rdflags = RDF_NOWORLDMODEL; + +  AxisClear( refdef.viewaxis ); + +  refdef.x = x; +  refdef.y = y; +  refdef.width = w; +  refdef.height = h; + +  refdef.fov_x = (int)((float)refdef.width / 640.0f * 90.0f); +  xx = refdef.width / tan( refdef.fov_x / 360 * M_PI ); +  refdef.fov_y = atan2( refdef.height, xx ); +  refdef.fov_y *= ( 360 / (float)M_PI ); + +  // calculate distance so the player nearly fills the box +  len = 0.7 * ( maxs[2] - mins[2] ); +  origin[0] = len / tan( DEG2RAD(refdef.fov_x) * 0.5 ); +  origin[1] = 0.5 * ( mins[1] + maxs[1] ); +  origin[2] = -0.5 * ( mins[2] + maxs[2] ); + +  refdef.time = dp_realtime; + +  trap_R_ClearScene(); + +  // get the rotation information +  UI_PlayerAngles( pi, legs.axis, torso.axis, head.axis ); + +  // get the animation state (after rotation, to allow feet shuffle) +  UI_PlayerAnimation( pi, &legs.oldframe, &legs.frame, &legs.backlerp, +     &torso.oldframe, &torso.frame, &torso.backlerp ); + +  renderfx = RF_LIGHTING_ORIGIN | RF_NOSHADOW; + +  // +  // add the legs +  // +  legs.hModel = pi->legsModel; +  legs.customSkin = pi->legsSkin; + +  VectorCopy( origin, legs.origin ); + +  VectorCopy( origin, legs.lightingOrigin ); +  legs.renderfx = renderfx; +  VectorCopy (legs.origin, legs.oldorigin); + +  trap_R_AddRefEntityToScene( &legs ); + +  if (!legs.hModel) { +    return; +  } + +  // +  // add the torso +  // +  torso.hModel = pi->torsoModel; +  if (!torso.hModel) { +    return; +  } + +  torso.customSkin = pi->torsoSkin; + +  VectorCopy( origin, torso.lightingOrigin ); + +  UI_PositionRotatedEntityOnTag( &torso, &legs, pi->legsModel, "tag_torso"); + +  torso.renderfx = renderfx; + +  trap_R_AddRefEntityToScene( &torso ); + +  // +  // add the head +  // +  head.hModel = pi->headModel; +  if (!head.hModel) { +    return; +  } +  head.customSkin = pi->headSkin; + +  VectorCopy( origin, head.lightingOrigin ); + +  UI_PositionRotatedEntityOnTag( &head, &torso, pi->torsoModel, "tag_head"); + +  head.renderfx = renderfx; + +  trap_R_AddRefEntityToScene( &head ); + +  // +  // add the gun +  // +  if ( pi->currentWeapon != WP_NONE ) { +    memset( &gun, 0, sizeof(gun) ); +    gun.hModel = pi->weaponModel; +    VectorCopy( origin, gun.lightingOrigin ); +    UI_PositionEntityOnTag( &gun, &torso, pi->torsoModel, "tag_weapon"); +    gun.renderfx = renderfx; +    trap_R_AddRefEntityToScene( &gun ); +  } + +  // +  // add the spinning barrel +  // +  if ( pi->realWeapon == WP_MACHINEGUN ) { +    vec3_t  angles; + +    memset( &barrel, 0, sizeof(barrel) ); +    VectorCopy( origin, barrel.lightingOrigin ); +    barrel.renderfx = renderfx; + +    barrel.hModel = pi->barrelModel; +    angles[YAW] = 0; +    angles[PITCH] = 0; +    angles[ROLL] = UI_MachinegunSpinAngle( pi ); +/*    if( pi->realWeapon == WP_GAUNTLET || pi->realWeapon == WP_BFG ) { +      angles[PITCH] = angles[ROLL]; +      angles[ROLL] = 0; +    }*/ +    AnglesToAxis( angles, barrel.axis ); + +    UI_PositionRotatedEntityOnTag( &barrel, &gun, pi->weaponModel, "tag_barrel"); + +    trap_R_AddRefEntityToScene( &barrel ); +  } + +  // +  // add muzzle flash +  // +  if ( dp_realtime <= pi->muzzleFlashTime ) { +    if ( pi->flashModel ) { +      memset( &flash, 0, sizeof(flash) ); +      flash.hModel = pi->flashModel; +      VectorCopy( origin, flash.lightingOrigin ); +      UI_PositionEntityOnTag( &flash, &gun, pi->weaponModel, "tag_flash"); +      flash.renderfx = renderfx; +      trap_R_AddRefEntityToScene( &flash ); +    } + +    // make a dlight for the flash +    if ( pi->flashDlightColor[0] || pi->flashDlightColor[1] || pi->flashDlightColor[2] ) { +      trap_R_AddLightToScene( flash.origin, 200 + (rand()&31), pi->flashDlightColor[0], +        pi->flashDlightColor[1], pi->flashDlightColor[2] ); +    } +  } + +  // +  // add the chat icon +  // +  if ( pi->chat ) { +    UI_PlayerFloatSprite( pi, origin, trap_R_RegisterShaderNoMip( "sprites/balloon3" ) ); +  } + +  // +  // add an accent light +  // +  origin[0] -= 100;  // + = behind, - = in front +  origin[1] += 100;  // + = left, - = right +  origin[2] += 100;  // + = above, - = below +  trap_R_AddLightToScene( origin, 500, 1.0, 1.0, 1.0 ); + +  origin[0] -= 100; +  origin[1] -= 100; +  origin[2] -= 100; +  trap_R_AddLightToScene( origin, 500, 1.0, 0.0, 0.0 ); + +  trap_R_RenderScene( &refdef ); +} + +/* +========================== +UI_FileExists +========================== +*/ +static qboolean  UI_FileExists(const char *filename) { +  int len; + +  len = trap_FS_FOpenFile( filename, NULL, FS_READ ); +  if (len>0) { +    return qtrue; +  } +  return qfalse; +} + +/* +========================== +UI_FindClientHeadFile +========================== +*/ +static qboolean  UI_FindClientHeadFile( char *filename, int length, const char *teamName, const char *headModelName, const char *headSkinName, const char *base, const char *ext ) { +  char *team, *headsFolder; +  int i; + +  team = "default"; + +  if ( headModelName[0] == '*' ) { +    headsFolder = "heads/"; +    headModelName++; +  } +  else { +    headsFolder = ""; +  } +  while(1) { +    for ( i = 0; i < 2; i++ ) { +      if ( i == 0 && teamName && *teamName ) { +        Com_sprintf( filename, length, "models/players/%s%s/%s/%s%s_%s.%s", headsFolder, headModelName, headSkinName, teamName, base, team, ext ); +      } +      else { +        Com_sprintf( filename, length, "models/players/%s%s/%s/%s_%s.%s", headsFolder, headModelName, headSkinName, base, team, ext ); +      } +      if ( UI_FileExists( filename ) ) { +        return qtrue; +      } +      if ( i == 0 && teamName && *teamName ) { +        Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, headSkinName, ext ); +      } +      else { +        Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, headSkinName, ext ); +      } +      if ( UI_FileExists( filename ) ) { +        return qtrue; +      } +      if ( !teamName || !*teamName ) { +        break; +      } +    } +    // if tried the heads folder first +    if ( headsFolder[0] ) { +      break; +    } +    headsFolder = "heads/"; +  } + +  return qfalse; +} + +/* +========================== +UI_RegisterClientSkin +========================== +*/ +static qboolean  UI_RegisterClientSkin( playerInfo_t *pi, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName , const char *teamName) { +  char    filename[MAX_QPATH*2]; + +  if (teamName && *teamName) { +    Com_sprintf( filename, sizeof( filename ), "models/players/%s/%s/lower_%s.skin", modelName, teamName, skinName ); +  } else { +    Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower_%s.skin", modelName, skinName ); +  } +  pi->legsSkin = trap_R_RegisterSkin( filename ); +  if (!pi->legsSkin) { +    if (teamName && *teamName) { +      Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%s/lower_%s.skin", modelName, teamName, skinName ); +    } else { +      Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/lower_%s.skin", modelName, skinName ); +    } +    pi->legsSkin = trap_R_RegisterSkin( filename ); +  } + +  if (teamName && *teamName) { +    Com_sprintf( filename, sizeof( filename ), "models/players/%s/%s/upper_%s.skin", modelName, teamName, skinName ); +  } else { +    Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper_%s.skin", modelName, skinName ); +  } +  pi->torsoSkin = trap_R_RegisterSkin( filename ); +  if (!pi->torsoSkin) { +    if (teamName && *teamName) { +      Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%s/upper_%s.skin", modelName, teamName, skinName ); +    } else { +      Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/upper_%s.skin", modelName, skinName ); +    } +    pi->torsoSkin = trap_R_RegisterSkin( filename ); +  } + +  if ( UI_FindClientHeadFile( filename, sizeof(filename), teamName, headModelName, headSkinName, "head", "skin" ) ) { +    pi->headSkin = trap_R_RegisterSkin( filename ); +  } + +  if ( !pi->legsSkin || !pi->torsoSkin || !pi->headSkin ) { +    return qfalse; +  } + +  return qtrue; +} + + +/* +====================== +UI_ParseAnimationFile +====================== +*/ +static qboolean UI_ParseAnimationFile( const char *filename, animation_t *animations ) { +  char    *text_p, *prev; +  int      len; +  int      i; +  char    *token; +  float    fps; +  int      skip; +  char    text[20000]; +  fileHandle_t  f; + +  memset( animations, 0, sizeof( animation_t ) * MAX_PLAYER_ANIMATIONS ); + +  // load the file +  len = trap_FS_FOpenFile( filename, &f, FS_READ ); +  if ( len <= 0 ) { +    return qfalse; +  } +  if ( len >= ( sizeof( text ) - 1 ) ) { +    Com_Printf( "File %s too long\n", filename ); +    trap_FS_FCloseFile( f ); +    return qfalse; +  } +  trap_FS_Read( text, len, f ); +  text[len] = 0; +  trap_FS_FCloseFile( f ); + +  COM_Compress(text); + +  // parse the text +  text_p = text; +  skip = 0;  // quite the compiler warning + +  // read optional parameters +  while ( 1 ) { +    prev = text_p;  // so we can unget +    token = COM_Parse( &text_p ); +    if ( !token ) { +      break; +    } +    if ( !Q_stricmp( token, "footsteps" ) ) { +      token = COM_Parse( &text_p ); +      if ( !token ) { +        break; +      } +      continue; +    } else if ( !Q_stricmp( token, "headoffset" ) ) { +      for ( i = 0 ; i < 3 ; i++ ) { +        token = COM_Parse( &text_p ); +        if ( !token ) { +          break; +        } +      } +      continue; +    } else if ( !Q_stricmp( token, "sex" ) ) { +      token = COM_Parse( &text_p ); +      if ( !token ) { +        break; +      } +      continue; +    } + +    // if it is a number, start parsing animations +    if ( token[0] >= '0' && token[0] <= '9' ) { +      text_p = prev;  // unget the token +      break; +    } + +    Com_Printf( "unknown token '%s' is %s\n", token, filename ); +  } + +  // read information for each frame +  for ( i = 0 ; i < MAX_PLAYER_ANIMATIONS ; i++ ) { + +    token = COM_Parse( &text_p ); +    if ( !token ) { +      break; +    } +    animations[i].firstFrame = atoi( token ); +    // leg only frames are adjusted to not count the upper body only frames +    if ( i == LEGS_WALKCR ) { +      skip = animations[LEGS_WALKCR].firstFrame - animations[TORSO_GESTURE].firstFrame; +    } +    if ( i >= LEGS_WALKCR ) { +      animations[i].firstFrame -= skip; +    } + +    token = COM_Parse( &text_p ); +    if ( !token ) { +      break; +    } +    animations[i].numFrames = atoi( token ); + +    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_PLAYER_ANIMATIONS ) { +    Com_Printf( "Error parsing animation file: %s", filename ); +    return qfalse; +  } + +  return qtrue; +} + +/* +========================== +UI_RegisterClientModelname +========================== +*/ +qboolean UI_RegisterClientModelname( playerInfo_t *pi, const char *modelSkinName, const char *headModelSkinName, const char *teamName ) { +  char    modelName[MAX_QPATH]; +  char    skinName[MAX_QPATH]; +  char    headModelName[MAX_QPATH]; +  char    headSkinName[MAX_QPATH]; +  char    filename[MAX_QPATH]; +  char    *slash; + +  pi->torsoModel = 0; +  pi->headModel = 0; + +  if ( !modelSkinName[0] ) { +    return qfalse; +  } + +  Q_strncpyz( modelName, modelSkinName, sizeof( modelName ) ); + +  slash = strchr( modelName, '/' ); +  if ( !slash ) { +    // modelName did not include a skin name +    Q_strncpyz( skinName, "default", sizeof( skinName ) ); +  } else { +    Q_strncpyz( skinName, slash + 1, sizeof( skinName ) ); +    *slash = '\0'; +  } + +  Q_strncpyz( headModelName, headModelSkinName, sizeof( headModelName ) ); +  slash = strchr( headModelName, '/' ); +  if ( !slash ) { +    // modelName did not include a skin name +    Q_strncpyz( headSkinName, "default", sizeof( skinName ) ); +  } else { +    Q_strncpyz( headSkinName, slash + 1, sizeof( skinName ) ); +    *slash = '\0'; +  } + +  // load cmodels before models so filecache works + +  Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.md3", modelName ); +  pi->legsModel = trap_R_RegisterModel( filename ); +  if ( !pi->legsModel ) { +    Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/lower.md3", modelName ); +    pi->legsModel = trap_R_RegisterModel( filename ); +    if ( !pi->legsModel ) { +      Com_Printf( "Failed to load model file %s\n", filename ); +      return qfalse; +    } +  } + +  Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.md3", modelName ); +  pi->torsoModel = trap_R_RegisterModel( filename ); +  if ( !pi->torsoModel ) { +    Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/upper.md3", modelName ); +    pi->torsoModel = trap_R_RegisterModel( filename ); +    if ( !pi->torsoModel ) { +      Com_Printf( "Failed to load model file %s\n", filename ); +      return qfalse; +    } +  } + +  if (headModelName && headModelName[0] == '*' ) { +    Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", &headModelName[1], &headModelName[1] ); +  } +  else { +    Com_sprintf( filename, sizeof( filename ), "models/players/%s/head.md3", headModelName ); +  } +  pi->headModel = trap_R_RegisterModel( filename ); +  if ( !pi->headModel && headModelName[0] != '*') { +    Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", headModelName, headModelName ); +    pi->headModel = trap_R_RegisterModel( filename ); +  } + +  if (!pi->headModel) { +    Com_Printf( "Failed to load model file %s\n", filename ); +    return qfalse; +  } + +  // if any skins failed to load, fall back to default +  if ( !UI_RegisterClientSkin( pi, modelName, skinName, headModelName, headSkinName, teamName) ) { +    if ( !UI_RegisterClientSkin( pi, modelName, "default", headModelName, "default", teamName ) ) { +      Com_Printf( "Failed to load skin file: %s : %s\n", modelName, skinName ); +      return qfalse; +    } +  } + +  // load the animations +  Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", modelName ); +  if ( !UI_ParseAnimationFile( filename, pi->animations ) ) { +    Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/animation.cfg", modelName ); +    if ( !UI_ParseAnimationFile( filename, pi->animations ) ) { +      Com_Printf( "Failed to load animation file %s\n", filename ); +      return qfalse; +    } +  } + +  return qtrue; +} + + +/* +=============== +UI_PlayerInfo_SetModel +=============== +*/ +void UI_PlayerInfo_SetModel( playerInfo_t *pi, const char *model, const char *headmodel, char *teamName ) { +  memset( pi, 0, sizeof(*pi) ); +  UI_RegisterClientModelname( pi, model, headmodel, teamName ); +  pi->weapon = WP_MACHINEGUN; +  pi->currentWeapon = pi->weapon; +  pi->lastWeapon = pi->weapon; +  pi->pendingWeapon = -1; +  pi->weaponTimer = 0; +  pi->chat = qfalse; +  pi->newModel = qtrue; +  UI_PlayerInfo_SetWeapon( pi, pi->weapon ); +} + + +/* +=============== +UI_PlayerInfo_SetInfo +=============== +*/ +void UI_PlayerInfo_SetInfo( playerInfo_t *pi, int legsAnim, int torsoAnim, vec3_t viewAngles, vec3_t moveAngles, weapon_t weaponNumber, qboolean chat ) { +  int      currentAnim; +  weapon_t  weaponNum; + +  pi->chat = chat; + +  // view angles +  VectorCopy( viewAngles, pi->viewAngles ); + +  // move angles +  VectorCopy( moveAngles, pi->moveAngles ); + +  if ( pi->newModel ) { +    pi->newModel = qfalse; + +    jumpHeight = 0; +    pi->pendingLegsAnim = 0; +    UI_ForceLegsAnim( pi, legsAnim ); +    pi->legs.yawAngle = viewAngles[YAW]; +    pi->legs.yawing = qfalse; + +    pi->pendingTorsoAnim = 0; +    UI_ForceTorsoAnim( pi, torsoAnim ); +    pi->torso.yawAngle = viewAngles[YAW]; +    pi->torso.yawing = qfalse; + +    if ( weaponNumber != -1 ) { +      pi->weapon = weaponNumber; +      pi->currentWeapon = weaponNumber; +      pi->lastWeapon = weaponNumber; +      pi->pendingWeapon = -1; +      pi->weaponTimer = 0; +      UI_PlayerInfo_SetWeapon( pi, pi->weapon ); +    } + +    return; +  } + +  // weapon +  if ( weaponNumber == -1 ) { +    pi->pendingWeapon = -1; +    pi->weaponTimer = 0; +  } +  else if ( weaponNumber != WP_NONE ) { +    pi->pendingWeapon = weaponNumber; +    pi->weaponTimer = dp_realtime + UI_TIMER_WEAPON_DELAY; +  } +  weaponNum = pi->lastWeapon; +  pi->weapon = weaponNum; + +  if ( torsoAnim == BOTH_DEATH1 || legsAnim == BOTH_DEATH1 ) { +    torsoAnim = legsAnim = BOTH_DEATH1; +    pi->weapon = pi->currentWeapon = WP_NONE; +    UI_PlayerInfo_SetWeapon( pi, pi->weapon ); + +    jumpHeight = 0; +    pi->pendingLegsAnim = 0; +    UI_ForceLegsAnim( pi, legsAnim ); + +    pi->pendingTorsoAnim = 0; +    UI_ForceTorsoAnim( pi, torsoAnim ); + +    return; +  } + +  // leg animation +  currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT; +  if ( legsAnim != LEGS_JUMP && ( currentAnim == LEGS_JUMP || currentAnim == LEGS_LAND ) ) { +    pi->pendingLegsAnim = legsAnim; +  } +  else if ( legsAnim != currentAnim ) { +    jumpHeight = 0; +    pi->pendingLegsAnim = 0; +    UI_ForceLegsAnim( pi, legsAnim ); +  } + +  // torso animation +  if ( torsoAnim == TORSO_STAND || torsoAnim == TORSO_STAND2 ) { +    if ( weaponNum == WP_NONE ) { +      torsoAnim = TORSO_STAND2; +    } +    else { +      torsoAnim = TORSO_STAND; +    } +  } + +  if ( torsoAnim == TORSO_ATTACK || torsoAnim == TORSO_ATTACK2 ) { +    if ( weaponNum == WP_NONE ) { +      torsoAnim = TORSO_ATTACK2; +    } +    else { +      torsoAnim = TORSO_ATTACK; +    } +    pi->muzzleFlashTime = dp_realtime + UI_TIMER_MUZZLE_FLASH; +    //FIXME play firing sound here +  } + +  currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT; + +  if ( weaponNum != pi->currentWeapon || currentAnim == TORSO_RAISE || currentAnim == TORSO_DROP ) { +    pi->pendingTorsoAnim = torsoAnim; +  } +  else if ( ( currentAnim == TORSO_GESTURE || currentAnim == TORSO_ATTACK ) && ( torsoAnim != currentAnim ) ) { +    pi->pendingTorsoAnim = torsoAnim; +  } +  else if ( torsoAnim != currentAnim ) { +    pi->pendingTorsoAnim = 0; +    UI_ForceTorsoAnim( pi, torsoAnim ); +  } +} diff --git a/src/ui/ui_shared.c b/src/ui/ui_shared.c new file mode 100644 index 0000000..2ca88cb --- /dev/null +++ b/src/ui/ui_shared.c @@ -0,0 +1,6075 @@ +/* +=========================================================================== +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 "ui_shared.h" + +#define SCROLL_TIME_START         500 +#define SCROLL_TIME_ADJUST        150 +#define SCROLL_TIME_ADJUSTOFFSET  40 +#define SCROLL_TIME_FLOOR         20 + +typedef struct scrollInfo_s { +  int nextScrollTime; +  int nextAdjustTime; +  int adjustValue; +  int scrollKey; +  float xStart; +  float yStart; +  itemDef_t *item; +  qboolean scrollDir; +} scrollInfo_t; + +static scrollInfo_t scrollInfo; + +//TA: hack to prevent compiler warnings +void voidFunction( void *var ) { return; } +qboolean voidFunction2( itemDef_t *var1, int var2 ) { return qfalse; } + +static void (*captureFunc) (void *p) = voidFunction; +static void *captureData = NULL; +static itemDef_t *itemCapture = NULL;   // item that has the mouse captured ( if any ) + +displayContextDef_t *DC = NULL; + +static qboolean g_waitingForKey = qfalse; +static qboolean g_editingField = qfalse; + +static itemDef_t *g_bindItem = NULL; +static itemDef_t *g_editItem = NULL; + +menuDef_t Menus[MAX_MENUS];      // defined menus +int menuCount = 0;               // how many + +menuDef_t *menuStack[MAX_OPEN_MENUS]; +int openMenuCount = 0; + +static qboolean debugMode = qfalse; + +#define DOUBLE_CLICK_DELAY 300 +static int lastListBoxClickTime = 0; + +void Item_RunScript(itemDef_t *item, const char *s); +void Item_SetupKeywordHash(void); +void Menu_SetupKeywordHash(void); +int BindingIDFromName(const char *name); +qboolean Item_Bind_HandleKey(itemDef_t *item, int key, qboolean down); +itemDef_t *Menu_SetPrevCursorItem(menuDef_t *menu); +itemDef_t *Menu_SetNextCursorItem(menuDef_t *menu); +static qboolean Menu_OverActiveItem(menuDef_t *menu, float x, float y); + +#ifdef CGAME +#define MEM_POOL_SIZE  128 * 1024 +#else +#define MEM_POOL_SIZE  1024 * 1024 +#endif + +//TA: hacked variable name to avoid conflict with new cgame Alloc +static char   UI_memoryPool[MEM_POOL_SIZE]; +static int    allocPoint, outOfMemory; + +/* +=============== +UI_Alloc +=============== +*/ +void *UI_Alloc( int size ) +{ +  char  *p; + +  if( allocPoint + size > MEM_POOL_SIZE ) +  { +    outOfMemory = qtrue; + +    if( DC->Print ) +      DC->Print( "UI_Alloc: Failure. Out of memory!\n" ); +    //DC->trap_Print(S_COLOR_YELLOW"WARNING: UI Out of Memory!\n"); +    return NULL; +  } + +  p = &UI_memoryPool[ allocPoint ]; + +  allocPoint += ( size + 15 ) & ~15; + +  return p; +} + +/* +=============== +UI_InitMemory +=============== +*/ +void UI_InitMemory( void ) +{ +  allocPoint = 0; +  outOfMemory = qfalse; +} + +qboolean UI_OutOfMemory( ) +{ +  return outOfMemory; +} + + + + + +#define HASH_TABLE_SIZE 2048 +/* +================ +return a hash value for the string +================ +*/ +static long hashForString(const char *str) { +  int   i; +  long  hash; +  char  letter; + +  hash = 0; +  i = 0; +  while (str[i] != '\0') { +    letter = tolower(str[i]); +    hash+=(long)(letter)*(i+119); +    i++; +  } +  hash &= (HASH_TABLE_SIZE-1); +  return hash; +} + +typedef struct stringDef_s { +  struct stringDef_s *next; +  const char *str; +} stringDef_t; + +static int strPoolIndex = 0; +static char strPool[STRING_POOL_SIZE]; + +static int strHandleCount = 0; +static stringDef_t *strHandle[HASH_TABLE_SIZE]; + + +const char *String_Alloc(const char *p) { +  int len; +  long hash; +  stringDef_t *str, *last; +  static const char *staticNULL = ""; + +  if (p == NULL) { +    return NULL; +  } + +  if (*p == 0) { +    return staticNULL; +  } + +  hash = hashForString(p); + +  str = strHandle[hash]; +  while (str) { +    if (strcmp(p, str->str) == 0) { +      return str->str; +    } +    str = str->next; +  } + +  len = strlen(p); +  if (len + strPoolIndex + 1 < STRING_POOL_SIZE) { +    int ph = strPoolIndex; +    strcpy(&strPool[strPoolIndex], p); +    strPoolIndex += len + 1; + +    str = strHandle[hash]; +    last = str; +    while (str && str->next) { +      last = str; +      str = str->next; +    } + +    str  = UI_Alloc(sizeof(stringDef_t)); +    str->next = NULL; +    str->str = &strPool[ph]; +    if (last) { +      last->next = str; +    } else { +      strHandle[hash] = str; +    } +    return &strPool[ph]; +  } +  return NULL; +} + +void String_Report( void ) { +  float f; +  Com_Printf("Memory/String Pool Info\n"); +  Com_Printf("----------------\n"); +  f = strPoolIndex; +  f /= STRING_POOL_SIZE; +  f *= 100; +  Com_Printf("String Pool is %.1f%% full, %i bytes out of %i used.\n", f, strPoolIndex, STRING_POOL_SIZE); +  f = allocPoint; +  f /= MEM_POOL_SIZE; +  f *= 100; +  Com_Printf("Memory Pool is %.1f%% full, %i bytes out of %i used.\n", f, allocPoint, MEM_POOL_SIZE); +} + +/* +================= +String_Init +================= +*/ +void String_Init( void ) +{ +  int i; +  for( i = 0; i < HASH_TABLE_SIZE; i++ ) +    strHandle[ i ] = 0; + +  strHandleCount = 0; +  strPoolIndex = 0; +  menuCount = 0; +  openMenuCount = 0; +  UI_InitMemory( ); +  Item_SetupKeywordHash( ); +  Menu_SetupKeywordHash( ); + +  if( DC && DC->getBindingBuf ) +    Controls_GetConfig( ); +} + +/* +================= +PC_SourceWarning +================= +*/ +void PC_SourceWarning(int handle, char *format, ...) { +  int line; +  char filename[128]; +  va_list argptr; +  static char string[4096]; + +  va_start (argptr, format); +  vsprintf (string, format, argptr); +  va_end (argptr); + +  filename[0] = '\0'; +  line = 0; +  trap_Parse_SourceFileAndLine(handle, filename, &line); + +  Com_Printf(S_COLOR_YELLOW "WARNING: %s, line %d: %s\n", filename, line, string); +} + +/* +================= +PC_SourceError +================= +*/ +void PC_SourceError(int handle, char *format, ...) { +  int line; +  char filename[128]; +  va_list argptr; +  static char string[4096]; + +  va_start (argptr, format); +  vsprintf (string, format, argptr); +  va_end (argptr); + +  filename[0] = '\0'; +  line = 0; +  trap_Parse_SourceFileAndLine(handle, filename, &line); + +  Com_Printf(S_COLOR_RED "ERROR: %s, line %d: %s\n", filename, line, string); +} + +/* +================= +LerpColor +================= +*/ +void LerpColor(vec4_t a, vec4_t b, vec4_t c, float t) +{ +  int i; + +  // lerp and clamp each component +  for (i=0; i<4; i++) +  { +    c[i] = a[i] + t*(b[i]-a[i]); +    if (c[i] < 0) +      c[i] = 0; +    else if (c[i] > 1.0) +      c[i] = 1.0; +  } +} + +/* +================= +Float_Parse +================= +*/ +qboolean Float_Parse(char **p, float *f) { +  char  *token; +  token = COM_ParseExt(p, qfalse); +  if (token && token[0] != 0) { +    *f = atof(token); +    return qtrue; +  } else { +    return qfalse; +  } +} + +/* +================= +PC_Float_Parse +================= +*/ +qboolean PC_Float_Parse(int handle, float *f) { +  pc_token_t token; +  int negative = qfalse; + +  if (!trap_Parse_ReadToken(handle, &token)) +    return qfalse; +  if (token.string[0] == '-') { +    if (!trap_Parse_ReadToken(handle, &token)) +      return qfalse; +    negative = qtrue; +  } +  if (token.type != TT_NUMBER) { +    PC_SourceError(handle, "expected float but found %s\n", token.string); +    return qfalse; +  } +  if (negative) +    *f = -token.floatvalue; +  else +    *f = token.floatvalue; +  return qtrue; +} + +/* +================= +Color_Parse +================= +*/ +qboolean Color_Parse(char **p, vec4_t *c) { +  int i; +  float f; + +  for (i = 0; i < 4; i++) { +    if (!Float_Parse(p, &f)) { +      return qfalse; +    } +    (*c)[i] = f; +  } +  return qtrue; +} + +/* +================= +PC_Color_Parse +================= +*/ +qboolean PC_Color_Parse(int handle, vec4_t *c) { +  int i; +  float f; + +  for (i = 0; i < 4; i++) { +    if (!PC_Float_Parse(handle, &f)) { +      return qfalse; +    } +    (*c)[i] = f; +  } +  return qtrue; +} + +/* +================= +Int_Parse +================= +*/ +qboolean Int_Parse(char **p, int *i) { +  char  *token; +  token = COM_ParseExt(p, qfalse); + +  if (token && token[0] != 0) { +    *i = atoi(token); +    return qtrue; +  } else { +    return qfalse; +  } +} + +/* +================= +PC_Int_Parse +================= +*/ +qboolean PC_Int_Parse(int handle, int *i) { +  pc_token_t token; +  int negative = qfalse; + +  if (!trap_Parse_ReadToken(handle, &token)) +    return qfalse; +  if (token.string[0] == '-') { +    if (!trap_Parse_ReadToken(handle, &token)) +      return qfalse; +    negative = qtrue; +  } +  if (token.type != TT_NUMBER) { +    PC_SourceError(handle, "expected integer but found %s\n", token.string); +    return qfalse; +  } +  *i = token.intvalue; +  if (negative) +    *i = - *i; +  return qtrue; +} + +/* +================= +Rect_Parse +================= +*/ +qboolean Rect_Parse(char **p, rectDef_t *r) { +  if (Float_Parse(p, &r->x)) { +    if (Float_Parse(p, &r->y)) { +      if (Float_Parse(p, &r->w)) { +        if (Float_Parse(p, &r->h)) { +          return qtrue; +        } +      } +    } +  } +  return qfalse; +} + +/* +================= +PC_Rect_Parse +================= +*/ +qboolean PC_Rect_Parse(int handle, rectDef_t *r) { +  if (PC_Float_Parse(handle, &r->x)) { +    if (PC_Float_Parse(handle, &r->y)) { +      if (PC_Float_Parse(handle, &r->w)) { +        if (PC_Float_Parse(handle, &r->h)) { +          return qtrue; +        } +      } +    } +  } +  return qfalse; +} + +/* +================= +String_Parse +================= +*/ +qboolean String_Parse(char **p, const char **out) { +  char *token; + +  token = COM_ParseExt(p, qfalse); +  if (token && token[0] != 0) { +    *(out) = String_Alloc(token); +    return qtrue; +  } +  return qfalse; +} + +/* +================= +PC_String_Parse +================= +*/ +qboolean PC_String_Parse(int handle, const char **out) { +  pc_token_t token; + +  if (!trap_Parse_ReadToken(handle, &token)) +    return qfalse; + +  *(out) = String_Alloc(token.string); +    return qtrue; +} + +/* +================= +PC_Script_Parse +================= +*/ +qboolean PC_Script_Parse(int handle, const char **out) { +  char script[1024]; +  pc_token_t token; + +  memset(script, 0, sizeof(script)); +  // scripts start with { and have ; separated command lists.. commands are command, arg.. +  // basically we want everything between the { } as it will be interpreted at run time + +  if (!trap_Parse_ReadToken(handle, &token)) +    return qfalse; +  if (Q_stricmp(token.string, "{") != 0) { +      return qfalse; +  } + +  while ( 1 ) { +    if (!trap_Parse_ReadToken(handle, &token)) +      return qfalse; + +    if (Q_stricmp(token.string, "}") == 0) { +      *out = String_Alloc(script); +      return qtrue; +    } + +    if (token.string[1] != '\0') { +      Q_strcat(script, 1024, va("\"%s\"", token.string)); +    } else { +      Q_strcat(script, 1024, token.string); +    } +    Q_strcat(script, 1024, " "); +  } +  return qfalse;  // bk001105 - LCC   missing return value +} + +// display, window, menu, item code +// + +/* +================== +Init_Display + +Initializes the display with a structure to all the drawing routines +================== +*/ +void Init_Display( displayContextDef_t *dc ) +{ +  DC = dc; +} + + + +// type and style painting + +void GradientBar_Paint( rectDef_t *rect, vec4_t color ) +{ +  // gradient bar takes two paints +  DC->setColor( color ); +  DC->drawHandlePic( rect->x, rect->y, rect->w, rect->h, DC->Assets.gradientBar ); +  DC->setColor( NULL ); +} + + +/* +================== +Window_Init + +Initializes a window structure ( windowDef_t ) with defaults + +================== +*/ +void Window_Init(Window *w) { +  memset(w, 0, sizeof(windowDef_t)); +  w->borderSize = 1; +  w->foreColor[0] = w->foreColor[1] = w->foreColor[2] = w->foreColor[3] = 1.0; +  w->cinematic = -1; +} + +void Fade(int *flags, float *f, float clamp, int *nextTime, int offsetTime, qboolean bFlags, float fadeAmount) { +  if (*flags & (WINDOW_FADINGOUT | WINDOW_FADINGIN)) { +    if (DC->realTime > *nextTime) { +      *nextTime = DC->realTime + offsetTime; +      if (*flags & WINDOW_FADINGOUT) { +        *f -= fadeAmount; +        if (bFlags && *f <= 0.0) { +          *flags &= ~(WINDOW_FADINGOUT | WINDOW_VISIBLE); +        } +      } else { +        *f += fadeAmount; +        if (*f >= clamp) { +          *f = clamp; +          if (bFlags) { +            *flags &= ~WINDOW_FADINGIN; +          } +        } +      } +    } +  } +} + + + +void Window_Paint(Window *w, float fadeAmount, float fadeClamp, float fadeCycle) { +  //float bordersize = 0; +  vec4_t color; +  rectDef_t fillRect = w->rect; + + +  if (debugMode) { +    color[0] = color[1] = color[2] = color[3] = 1; +    DC->drawRect(w->rect.x, w->rect.y, w->rect.w, w->rect.h, 1, color); +  } + +  if (w == NULL || (w->style == 0 && w->border == 0)) { +    return; +  } + +  if (w->border != 0) { +    fillRect.x += w->borderSize; +    fillRect.y += w->borderSize; +    fillRect.w -= w->borderSize + 1; +    fillRect.h -= w->borderSize + 1; +  } + +  if (w->style == WINDOW_STYLE_FILLED) { +    // box, but possible a shader that needs filled +    if (w->background) { +      Fade(&w->flags, &w->backColor[3], fadeClamp, &w->nextTime, fadeCycle, qtrue, fadeAmount); +      DC->setColor(w->backColor); +      DC->drawHandlePic(fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->background); +      DC->setColor(NULL); +    } else { +      DC->fillRect(fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->backColor); +    } +  } else if (w->style == WINDOW_STYLE_GRADIENT) { +    GradientBar_Paint(&fillRect, w->backColor); +    // gradient bar +  } else if (w->style == WINDOW_STYLE_SHADER) { +    if (w->flags & WINDOW_FORECOLORSET) { +      DC->setColor(w->foreColor); +    } +    DC->drawHandlePic(fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->background); +    DC->setColor(NULL); +  } else if (w->style == WINDOW_STYLE_TEAMCOLOR) { +    if (DC->getTeamColor) { +      DC->getTeamColor(&color); +      DC->fillRect(fillRect.x, fillRect.y, fillRect.w, fillRect.h, color); +    } +  } else if (w->style == WINDOW_STYLE_CINEMATIC) { +    if (w->cinematic == -1) { +      w->cinematic = DC->playCinematic(w->cinematicName, fillRect.x, fillRect.y, fillRect.w, fillRect.h); +      if (w->cinematic == -1) { +        w->cinematic = -2; +      } +    } +    if (w->cinematic >= 0) { +      DC->runCinematicFrame(w->cinematic); +      DC->drawCinematic(w->cinematic, fillRect.x, fillRect.y, fillRect.w, fillRect.h); +    } +  } + +  if (w->border == WINDOW_BORDER_FULL) { +    // full +    // HACK HACK HACK +    if (w->style == WINDOW_STYLE_TEAMCOLOR) { +      if (color[0] > 0) { +        // red +        color[0] = 1; +        color[1] = color[2] = .5; + +      } else { +        color[2] = 1; +        color[0] = color[1] = .5; +      } +      color[3] = 1; +      DC->drawRect(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize, color); +    } else { +      DC->drawRect(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize, w->borderColor); +    } +  } else if (w->border == WINDOW_BORDER_HORZ) { +    // top/bottom +    DC->setColor(w->borderColor); +    DC->drawTopBottom(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize); +    DC->setColor( NULL ); +  } else if (w->border == WINDOW_BORDER_VERT) { +    // left right +    DC->setColor(w->borderColor); +    DC->drawSides(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize); +    DC->setColor( NULL ); +  } else if (w->border == WINDOW_BORDER_KCGRADIENT) { +    // this is just two gradient bars along each horz edge +    rectDef_t r = w->rect; +    r.h = w->borderSize; +    GradientBar_Paint(&r, w->borderColor); +    r.y = w->rect.y + w->rect.h - 1; +    GradientBar_Paint(&r, w->borderColor); +  } + +} + + +void Item_SetScreenCoords(itemDef_t *item, float x, float y) { + +  if (item == NULL) { +    return; +  } + +  if (item->window.border != 0) { +    x += item->window.borderSize; +    y += item->window.borderSize; +  } + +  item->window.rect.x = x + item->window.rectClient.x; +  item->window.rect.y = y + item->window.rectClient.y; +  item->window.rect.w = item->window.rectClient.w; +  item->window.rect.h = item->window.rectClient.h; + +  // force the text rects to recompute +  item->textRect.w = 0; +  item->textRect.h = 0; +} + +// FIXME: consolidate this with nearby stuff +void Item_UpdatePosition(itemDef_t *item) { +  float x, y; +  menuDef_t *menu; + +  if (item == NULL || item->parent == NULL) { +    return; +  } + +  menu = item->parent; + +  x = menu->window.rect.x; +  y = menu->window.rect.y; + +  if (menu->window.border != 0) { +    x += menu->window.borderSize; +    y += menu->window.borderSize; +  } + +  Item_SetScreenCoords(item, x, y); + +} + +// menus +void Menu_UpdatePosition(menuDef_t *menu) { +  int i; +  float x, y; + +  if (menu == NULL) { +    return; +  } + +  x = menu->window.rect.x; +  y = menu->window.rect.y; +  if (menu->window.border != 0) { +    x += menu->window.borderSize; +    y += menu->window.borderSize; +  } + +  for (i = 0; i < menu->itemCount; i++) { +    Item_SetScreenCoords(menu->items[i], x, y); +  } +} + +void Menu_PostParse(menuDef_t *menu) { +  if (menu == NULL) { +    return; +  } +  if (menu->fullScreen) { +    menu->window.rect.x = 0; +    menu->window.rect.y = 0; +    menu->window.rect.w = 640; +    menu->window.rect.h = 480; +  } +  Menu_UpdatePosition(menu); +} + +itemDef_t *Menu_ClearFocus(menuDef_t *menu) { +  int i; +  itemDef_t *ret = NULL; + +  if (menu == NULL) { +    return NULL; +  } + +  for (i = 0; i < menu->itemCount; i++) { +    if (menu->items[i]->window.flags & WINDOW_HASFOCUS) { +      ret = menu->items[i]; +    } +    menu->items[i]->window.flags &= ~WINDOW_HASFOCUS; +    if (menu->items[i]->leaveFocus) { +      Item_RunScript(menu->items[i], menu->items[i]->leaveFocus); +    } +  } + +  return ret; +} + +qboolean IsVisible(int flags) { +  return (flags & WINDOW_VISIBLE && !(flags & WINDOW_FADINGOUT)); +} + +qboolean Rect_ContainsPoint(rectDef_t *rect, float x, float y) { +  if (rect) { +    if (x > rect->x && x < rect->x + rect->w && y > rect->y && y < rect->y + rect->h) { +      return qtrue; +    } +  } +  return qfalse; +} + +int Menu_ItemsMatchingGroup(menuDef_t *menu, const char *name) { +  int i; +  int count = 0; +  for (i = 0; i < menu->itemCount; i++) { +    if (Q_stricmp(menu->items[i]->window.name, name) == 0 || (menu->items[i]->window.group && Q_stricmp(menu->items[i]->window.group, name) == 0)) { +      count++; +    } +  } +  return count; +} + +itemDef_t *Menu_GetMatchingItemByNumber(menuDef_t *menu, int index, const char *name) { +  int i; +  int count = 0; +  for (i = 0; i < menu->itemCount; i++) { +    if (Q_stricmp(menu->items[i]->window.name, name) == 0 || (menu->items[i]->window.group && Q_stricmp(menu->items[i]->window.group, name) == 0)) { +      if (count == index) { +        return menu->items[i]; +      } +      count++; +    } +  } +  return NULL; +} + + + +void Script_SetColor(itemDef_t *item, char **args) { +  const char *name; +  int i; +  float f; +  vec4_t *out; +  // expecting type of color to set and 4 args for the color +  if (String_Parse(args, &name)) { +      out = NULL; +      if (Q_stricmp(name, "backcolor") == 0) { +        out = &item->window.backColor; +        item->window.flags |= WINDOW_BACKCOLORSET; +      } else if (Q_stricmp(name, "forecolor") == 0) { +        out = &item->window.foreColor; +        item->window.flags |= WINDOW_FORECOLORSET; +      } else if (Q_stricmp(name, "bordercolor") == 0) { +        out = &item->window.borderColor; +      } + +      if (out) { +        for (i = 0; i < 4; i++) { +          if (!Float_Parse(args, &f)) { +            return; +          } +          (*out)[i] = f; +        } +      } +  } +} + +void Script_SetAsset(itemDef_t *item, char **args) { +  const char *name; +  // expecting name to set asset to +  if (String_Parse(args, &name)) { +    // check for a model +    if (item->type == ITEM_TYPE_MODEL) { +    } +  } +} + +void Script_SetBackground(itemDef_t *item, char **args) { +  const char *name; +  // expecting name to set asset to +  if (String_Parse(args, &name)) { +    item->window.background = DC->registerShaderNoMip(name); +  } +} + + + + +itemDef_t *Menu_FindItemByName(menuDef_t *menu, const char *p) { +  int i; +  if (menu == NULL || p == NULL) { +    return NULL; +  } + +  for (i = 0; i < menu->itemCount; i++) { +    if (Q_stricmp(p, menu->items[i]->window.name) == 0) { +      return menu->items[i]; +    } +  } + +  return NULL; +} + +void Script_SetTeamColor(itemDef_t *item, char **args) { +  if (DC->getTeamColor) { +    int i; +    vec4_t color; +    DC->getTeamColor(&color); +    for (i = 0; i < 4; i++) { +      item->window.backColor[i] = color[i]; +    } +  } +} + +void Script_SetItemColor(itemDef_t *item, char **args) { +  const char *itemname; +  const char *name; +  vec4_t color; +  int i; +  vec4_t *out; +  // expecting type of color to set and 4 args for the color +  if (String_Parse(args, &itemname) && String_Parse(args, &name)) { +    itemDef_t *item2; +    int j; +    int count = Menu_ItemsMatchingGroup(item->parent, itemname); + +    if (!Color_Parse(args, &color)) { +      return; +    } + +    for (j = 0; j < count; j++) { +      item2 = Menu_GetMatchingItemByNumber(item->parent, j, itemname); +      if (item2 != NULL) { +        out = NULL; +        if (Q_stricmp(name, "backcolor") == 0) { +          out = &item2->window.backColor; +        } else if (Q_stricmp(name, "forecolor") == 0) { +          out = &item2->window.foreColor; +          item2->window.flags |= WINDOW_FORECOLORSET; +        } else if (Q_stricmp(name, "bordercolor") == 0) { +          out = &item2->window.borderColor; +        } + +        if (out) { +          for (i = 0; i < 4; i++) { +            (*out)[i] = color[i]; +          } +        } +      } +    } +  } +} + + +void Menu_ShowItemByName(menuDef_t *menu, const char *p, qboolean bShow) { +  itemDef_t *item; +  int i; +  int count = Menu_ItemsMatchingGroup(menu, p); +  for (i = 0; i < count; i++) { +    item = Menu_GetMatchingItemByNumber(menu, i, p); +    if (item != NULL) { +      if (bShow) { +        item->window.flags |= WINDOW_VISIBLE; +      } else { +        item->window.flags &= ~WINDOW_VISIBLE; +        // stop cinematics playing in the window +        if (item->window.cinematic >= 0) { +          DC->stopCinematic(item->window.cinematic); +          item->window.cinematic = -1; +        } +      } +    } +  } +} + +void Menu_FadeItemByName(menuDef_t *menu, const char *p, qboolean fadeOut) { +  itemDef_t *item; +  int i; +  int count = Menu_ItemsMatchingGroup(menu, p); +  for (i = 0; i < count; i++) { +    item = Menu_GetMatchingItemByNumber(menu, i, p); +    if (item != NULL) { +      if (fadeOut) { +        item->window.flags |= (WINDOW_FADINGOUT | WINDOW_VISIBLE); +        item->window.flags &= ~WINDOW_FADINGIN; +      } else { +        item->window.flags |= (WINDOW_VISIBLE | WINDOW_FADINGIN); +        item->window.flags &= ~WINDOW_FADINGOUT; +      } +    } +  } +} + +menuDef_t *Menus_FindByName(const char *p) { +  int i; +  for (i = 0; i < menuCount; i++) { +    if (Q_stricmp(Menus[i].window.name, p) == 0) { +      return &Menus[i]; +    } +  } +  return NULL; +} + +void Menus_ShowByName(const char *p) { +  menuDef_t *menu = Menus_FindByName(p); +  if (menu) { +    Menus_Activate(menu); +  } +} + +void Menus_OpenByName(const char *p) { +  Menus_ActivateByName(p); +} + +static void Menu_RunCloseScript(menuDef_t *menu) { +  if (menu && menu->window.flags & WINDOW_VISIBLE && menu->onClose) { +    itemDef_t item; +    item.parent = menu; +    Item_RunScript(&item, menu->onClose); +  } +} + +void Menus_CloseByName(const char *p) { +  menuDef_t *menu = Menus_FindByName(p); +  if (menu != NULL) { +    Menu_RunCloseScript(menu); +    menu->window.flags &= ~(WINDOW_VISIBLE | WINDOW_HASFOCUS); +  } +} + +void Menus_CloseAll( void ) { +  int i; +  for (i = 0; i < menuCount; i++) { +    Menu_RunCloseScript(&Menus[i]); +    Menus[i].window.flags &= ~(WINDOW_HASFOCUS | WINDOW_VISIBLE); +  } +} + + +void Script_Show(itemDef_t *item, char **args) { +  const char *name; +  if (String_Parse(args, &name)) { +    Menu_ShowItemByName(item->parent, name, qtrue); +  } +} + +void Script_Hide(itemDef_t *item, char **args) { +  const char *name; +  if (String_Parse(args, &name)) { +    Menu_ShowItemByName(item->parent, name, qfalse); +  } +} + +void Script_FadeIn(itemDef_t *item, char **args) { +  const char *name; +  if (String_Parse(args, &name)) { +    Menu_FadeItemByName(item->parent, name, qfalse); +  } +} + +void Script_FadeOut(itemDef_t *item, char **args) { +  const char *name; +  if (String_Parse(args, &name)) { +    Menu_FadeItemByName(item->parent, name, qtrue); +  } +} + + + +void Script_Open(itemDef_t *item, char **args) { +  const char *name; +  if (String_Parse(args, &name)) { +    Menus_OpenByName(name); +  } +} + +void Script_ConditionalOpen(itemDef_t *item, char **args) { +  const char *cvar; +  const char *name1; +  const char *name2; +  float           val; + +  if ( String_Parse(args, &cvar) && String_Parse(args, &name1) && String_Parse(args, &name2) ) { +    val = DC->getCVarValue( cvar ); +    if ( val == 0.f ) { +      Menus_OpenByName(name2); +    } else { +      Menus_OpenByName(name1); +    } +  } +} + +void Script_Close(itemDef_t *item, char **args) { +  const char *name; +  if (String_Parse(args, &name)) { +    Menus_CloseByName(name); +  } +} + +void Menu_TransitionItemByName(menuDef_t *menu, const char *p, rectDef_t rectFrom, rectDef_t rectTo, int time, float amt) { +  itemDef_t *item; +  int i; +  int count = Menu_ItemsMatchingGroup(menu, p); +  for (i = 0; i < count; i++) { +    item = Menu_GetMatchingItemByNumber(menu, i, p); +    if (item != NULL) { +      item->window.flags |= (WINDOW_INTRANSITION | WINDOW_VISIBLE); +      item->window.offsetTime = time; +      memcpy(&item->window.rectClient, &rectFrom, sizeof(rectDef_t)); +      memcpy(&item->window.rectEffects, &rectTo, sizeof(rectDef_t)); +      item->window.rectEffects2.x = abs(rectTo.x - rectFrom.x) / amt; +      item->window.rectEffects2.y = abs(rectTo.y - rectFrom.y) / amt; +      item->window.rectEffects2.w = abs(rectTo.w - rectFrom.w) / amt; +      item->window.rectEffects2.h = abs(rectTo.h - rectFrom.h) / amt; +      Item_UpdatePosition(item); +    } +  } +} + + +void Script_Transition(itemDef_t *item, char **args) { +  const char *name; +  rectDef_t rectFrom, rectTo; +  int time; +  float amt; + +  if (String_Parse(args, &name)) { +    if ( Rect_Parse(args, &rectFrom) && Rect_Parse(args, &rectTo) && Int_Parse(args, &time) && Float_Parse(args, &amt)) { +      Menu_TransitionItemByName(item->parent, name, rectFrom, rectTo, time, amt); +    } +  } +} + + +void Menu_OrbitItemByName(menuDef_t *menu, const char *p, float x, float y, float cx, float cy, int time) { +  itemDef_t *item; +  int i; +  int count = Menu_ItemsMatchingGroup(menu, p); +  for (i = 0; i < count; i++) { +    item = Menu_GetMatchingItemByNumber(menu, i, p); +    if (item != NULL) { +      item->window.flags |= (WINDOW_ORBITING | WINDOW_VISIBLE); +      item->window.offsetTime = time; +      item->window.rectEffects.x = cx; +      item->window.rectEffects.y = cy; +      item->window.rectClient.x = x; +      item->window.rectClient.y = y; +      Item_UpdatePosition(item); +    } +  } +} + + +void Script_Orbit(itemDef_t *item, char **args) { +  const char *name; +  float cx, cy, x, y; +  int time; + +  if (String_Parse(args, &name)) { +    if ( Float_Parse(args, &x) && Float_Parse(args, &y) && Float_Parse(args, &cx) && Float_Parse(args, &cy) && Int_Parse(args, &time) ) { +      Menu_OrbitItemByName(item->parent, name, x, y, cx, cy, time); +    } +  } +} + + + +void Script_SetFocus(itemDef_t *item, char **args) { +  const char *name; +  itemDef_t *focusItem; + +  if (String_Parse(args, &name)) { +    focusItem = Menu_FindItemByName(item->parent, name); +    if (focusItem && !(focusItem->window.flags & WINDOW_DECORATION) && !(focusItem->window.flags & WINDOW_HASFOCUS)) { +      Menu_ClearFocus(item->parent); +      focusItem->window.flags |= WINDOW_HASFOCUS; +      if (focusItem->onFocus) { +        Item_RunScript(focusItem, focusItem->onFocus); +      } +      if (DC->Assets.itemFocusSound) { +        DC->startLocalSound( DC->Assets.itemFocusSound, CHAN_LOCAL_SOUND ); +      } +    } +  } +} + +void Script_SetPlayerModel(itemDef_t *item, char **args) { +  const char *name; +  if (String_Parse(args, &name)) { +    DC->setCVar("team_model", name); +  } +} + +void Script_SetPlayerHead(itemDef_t *item, char **args) { +  const char *name; +  if (String_Parse(args, &name)) { +    DC->setCVar("team_headmodel", name); +  } +} + +void Script_SetCvar(itemDef_t *item, char **args) { +  const char *cvar, *val; +  if (String_Parse(args, &cvar) && String_Parse(args, &val)) { +    DC->setCVar(cvar, val); +  } + +} + +void Script_Exec(itemDef_t *item, char **args) { +  const char *val; +  if (String_Parse(args, &val)) { +    DC->executeText(EXEC_APPEND, va("%s ; ", val)); +  } +} + +void Script_Play(itemDef_t *item, char **args) { +  const char *val; +  if (String_Parse(args, &val)) { +    DC->startLocalSound(DC->registerSound(val, qfalse), CHAN_LOCAL_SOUND); +  } +} + +void Script_playLooped(itemDef_t *item, char **args) { +  const char *val; +  if (String_Parse(args, &val)) { +    DC->stopBackgroundTrack(); +    DC->startBackgroundTrack(val, val); +  } +} + + +commandDef_t commandList[] = +{ +  {"fadein", &Script_FadeIn},                   // group/name +  {"fadeout", &Script_FadeOut},                 // group/name +  {"show", &Script_Show},                       // group/name +  {"hide", &Script_Hide},                       // group/name +  {"setcolor", &Script_SetColor},               // works on this +  {"open", &Script_Open},                       // menu +  {"conditionalopen", &Script_ConditionalOpen}, // menu +  {"close", &Script_Close},                     // menu +  {"setasset", &Script_SetAsset},               // works on this +  {"setbackground", &Script_SetBackground},     // works on this +  {"setitemcolor", &Script_SetItemColor},       // group/name +  {"setteamcolor", &Script_SetTeamColor},       // sets this background color to team color +  {"setfocus", &Script_SetFocus},               // sets this background color to team color +  {"setplayermodel", &Script_SetPlayerModel},   // sets this background color to team color +  {"setplayerhead", &Script_SetPlayerHead},     // sets this background color to team color +  {"transition", &Script_Transition},           // group/name +  {"setcvar", &Script_SetCvar},           // group/name +  {"exec", &Script_Exec},           // group/name +  {"play", &Script_Play},           // group/name +  {"playlooped", &Script_playLooped},           // group/name +  {"orbit", &Script_Orbit}                      // group/name +}; + +int scriptCommandCount = sizeof(commandList) / sizeof(commandDef_t); + + +void Item_RunScript(itemDef_t *item, const char *s) { +  char script[1024], *p; +  int i; +  qboolean bRan; +  memset(script, 0, sizeof(script)); +  if (item && s && s[0]) { +    Q_strcat(script, 1024, s); +    p = script; +    while (1) { +      const char *command; +      // expect command then arguments, ; ends command, NULL ends script +      if (!String_Parse(&p, &command)) { +        return; +      } + +      if (command[0] == ';' && command[1] == '\0') { +        continue; +      } + +      bRan = qfalse; +      for (i = 0; i < scriptCommandCount; i++) { +        if (Q_stricmp(command, commandList[i].name) == 0) { +          (commandList[i].handler(item, &p)); +          bRan = qtrue; +          break; +        } +      } +      // not in our auto list, pass to handler +      if (!bRan) { +        DC->runScript(&p); +      } +    } +  } +} + + +qboolean Item_EnableShowViaCvar(itemDef_t *item, int flag) { +  char script[1024], *p; +  memset(script, 0, sizeof(script)); +  if (item && item->enableCvar && *item->enableCvar && item->cvarTest && *item->cvarTest) { +    char buff[1024]; +    DC->getCVarString(item->cvarTest, buff, sizeof(buff)); + +    Q_strcat(script, 1024, item->enableCvar); +    p = script; +    while (1) { +      const char *val; +      // expect value then ; or NULL, NULL ends list +      if (!String_Parse(&p, &val)) { +        return (item->cvarFlags & flag) ? qfalse : qtrue; +      } + +      if (val[0] == ';' && val[1] == '\0') { +        continue; +      } + +      // enable it if any of the values are true +      if (item->cvarFlags & flag) { +        if (Q_stricmp(buff, val) == 0) { +          return qtrue; +        } +      } else { +        // disable it if any of the values are true +        if (Q_stricmp(buff, val) == 0) { +          return qfalse; +        } +      } + +    } +    return (item->cvarFlags & flag) ? qfalse : qtrue; +  } +  return qtrue; +} + + +// will optionaly set focus to this item +qboolean Item_SetFocus(itemDef_t *item, float x, float y) { +  int i; +  itemDef_t *oldFocus; +  sfxHandle_t *sfx = &DC->Assets.itemFocusSound; +  qboolean playSound = qfalse; +  menuDef_t *parent; // bk001206: = (menuDef_t*)item->parent; +  // sanity check, non-null, not a decoration and does not already have the focus +  if (item == NULL || item->window.flags & WINDOW_DECORATION || item->window.flags & WINDOW_HASFOCUS || !(item->window.flags & WINDOW_VISIBLE)) { +    return qfalse; +  } + +  // bk001206 - this can be NULL. +  parent = (menuDef_t*)item->parent; + +  // items can be enabled and disabled based on cvars +  if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) { +    return qfalse; +  } + +  if (item->cvarFlags & (CVAR_SHOW | CVAR_HIDE) && !Item_EnableShowViaCvar(item, CVAR_SHOW)) { +    return qfalse; +  } + +  oldFocus = Menu_ClearFocus(item->parent); + +  if (item->type == ITEM_TYPE_TEXT) { +    rectDef_t r; +    r = item->textRect; +    r.y -= r.h; +    if (Rect_ContainsPoint(&r, x, y)) { +      item->window.flags |= WINDOW_HASFOCUS; +      if (item->focusSound) { +        sfx = &item->focusSound; +      } +      playSound = qtrue; +    } else { +      if (oldFocus) { +        oldFocus->window.flags |= WINDOW_HASFOCUS; +        if (oldFocus->onFocus) { +          Item_RunScript(oldFocus, oldFocus->onFocus); +        } +      } +    } +  } else { +      item->window.flags |= WINDOW_HASFOCUS; +    if (item->onFocus) { +      Item_RunScript(item, item->onFocus); +    } +    if (item->focusSound) { +      sfx = &item->focusSound; +    } +    playSound = qtrue; +  } + +  if (playSound && sfx) { +    DC->startLocalSound( *sfx, CHAN_LOCAL_SOUND ); +  } + +  for (i = 0; i < parent->itemCount; i++) { +    if (parent->items[i] == item) { +      parent->cursorItem = i; +      break; +    } +  } + +  return qtrue; +} + +int Item_ListBox_MaxScroll(itemDef_t *item) { +  listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; +  int count = DC->feederCount(item->special); +  int max; + +  if (item->window.flags & WINDOW_HORIZONTAL) { +    max = count - (item->window.rect.w / listPtr->elementWidth) + 1; +  } +  else { +    max = count - (item->window.rect.h / listPtr->elementHeight) + 1; +  } +  if (max < 0) { +    return 0; +  } +  return max; +} + +int Item_ListBox_ThumbPosition(itemDef_t *item) { +  float max, pos, size; +  listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + +  max = Item_ListBox_MaxScroll(item); +  if (item->window.flags & WINDOW_HORIZONTAL) { +    size = item->window.rect.w - (SCROLLBAR_SIZE * 2) - 2; +    if (max > 0) { +      pos = (size-SCROLLBAR_SIZE) / (float) max; +    } else { +      pos = 0; +    } +    pos *= listPtr->startPos; +    return item->window.rect.x + 1 + SCROLLBAR_SIZE + pos; +  } +  else { +    size = item->window.rect.h - (SCROLLBAR_SIZE * 2) - 2; +    if (max > 0) { +      pos = (size-SCROLLBAR_SIZE) / (float) max; +    } else { +      pos = 0; +    } +    pos *= listPtr->startPos; +    return item->window.rect.y + 1 + SCROLLBAR_SIZE + pos; +  } +} + +int Item_ListBox_ThumbDrawPosition(itemDef_t *item) { +  int min, max; + +  if (itemCapture == item) { +    if (item->window.flags & WINDOW_HORIZONTAL) { +      min = item->window.rect.x + SCROLLBAR_SIZE + 1; +      max = item->window.rect.x + item->window.rect.w - 2*SCROLLBAR_SIZE - 1; +      if (DC->cursorx >= min + SCROLLBAR_SIZE/2 && DC->cursorx <= max + SCROLLBAR_SIZE/2) { +        return DC->cursorx - SCROLLBAR_SIZE/2; +      } +      else { +        return Item_ListBox_ThumbPosition(item); +      } +    } +    else { +      min = item->window.rect.y + SCROLLBAR_SIZE + 1; +      max = item->window.rect.y + item->window.rect.h - 2*SCROLLBAR_SIZE - 1; +      if (DC->cursory >= min + SCROLLBAR_SIZE/2 && DC->cursory <= max + SCROLLBAR_SIZE/2) { +        return DC->cursory - SCROLLBAR_SIZE/2; +      } +      else { +        return Item_ListBox_ThumbPosition(item); +      } +    } +  } +  else { +    return Item_ListBox_ThumbPosition(item); +  } +} + +float Item_Slider_ThumbPosition(itemDef_t *item) { +  float value, range, x; +  editFieldDef_t *editDef = item->typeData; + +  if (item->text) { +    x = item->textRect.x + item->textRect.w + 8; +  } else { +    x = item->window.rect.x; +  } + +  if (editDef == NULL && item->cvar) { +    return x; +  } + +  value = DC->getCVarValue(item->cvar); + +  if (value < editDef->minVal) { +    value = editDef->minVal; +  } else if (value > editDef->maxVal) { +    value = editDef->maxVal; +  } + +  range = editDef->maxVal - editDef->minVal; +  value -= editDef->minVal; +  value /= range; +  //value /= (editDef->maxVal - editDef->minVal); +  value *= SLIDER_WIDTH; +  x += value; +  // vm fuckage +  //x = x + (((float)value / editDef->maxVal) * SLIDER_WIDTH); +  return x; +} + +int Item_Slider_OverSlider(itemDef_t *item, float x, float y) { +  rectDef_t r; + +  r.x = Item_Slider_ThumbPosition(item) - (SLIDER_THUMB_WIDTH / 2); +  r.y = item->window.rect.y - 2; +  r.w = SLIDER_THUMB_WIDTH; +  r.h = SLIDER_THUMB_HEIGHT; + +  if (Rect_ContainsPoint(&r, x, y)) { +    return WINDOW_LB_THUMB; +  } +  return 0; +} + +int Item_ListBox_OverLB(itemDef_t *item, float x, float y) { +  rectDef_t r; +  listBoxDef_t *listPtr; +  int thumbstart; +  int count; + +  count = DC->feederCount(item->special); +  listPtr = (listBoxDef_t*)item->typeData; +  if (item->window.flags & WINDOW_HORIZONTAL) { +    // check if on left arrow +    r.x = item->window.rect.x; +    r.y = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE; +    r.h = r.w = SCROLLBAR_SIZE; +    if (Rect_ContainsPoint(&r, x, y)) { +      return WINDOW_LB_LEFTARROW; +    } +    // check if on right arrow +    r.x = item->window.rect.x + item->window.rect.w - SCROLLBAR_SIZE; +    if (Rect_ContainsPoint(&r, x, y)) { +      return WINDOW_LB_RIGHTARROW; +    } +    // check if on thumb +    thumbstart = Item_ListBox_ThumbPosition(item); +    r.x = thumbstart; +    if (Rect_ContainsPoint(&r, x, y)) { +      return WINDOW_LB_THUMB; +    } +    r.x = item->window.rect.x + SCROLLBAR_SIZE; +    r.w = thumbstart - r.x; +    if (Rect_ContainsPoint(&r, x, y)) { +      return WINDOW_LB_PGUP; +    } +    r.x = thumbstart + SCROLLBAR_SIZE; +    r.w = item->window.rect.x + item->window.rect.w - SCROLLBAR_SIZE; +    if (Rect_ContainsPoint(&r, x, y)) { +      return WINDOW_LB_PGDN; +    } +  } else { +    r.x = item->window.rect.x + item->window.rect.w - SCROLLBAR_SIZE; +    r.y = item->window.rect.y; +    r.h = r.w = SCROLLBAR_SIZE; +    if (Rect_ContainsPoint(&r, x, y)) { +      return WINDOW_LB_LEFTARROW; +    } +    r.y = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE; +    if (Rect_ContainsPoint(&r, x, y)) { +      return WINDOW_LB_RIGHTARROW; +    } +    thumbstart = Item_ListBox_ThumbPosition(item); +    r.y = thumbstart; +    if (Rect_ContainsPoint(&r, x, y)) { +      return WINDOW_LB_THUMB; +    } +    r.y = item->window.rect.y + SCROLLBAR_SIZE; +    r.h = thumbstart - r.y; +    if (Rect_ContainsPoint(&r, x, y)) { +      return WINDOW_LB_PGUP; +    } +    r.y = thumbstart + SCROLLBAR_SIZE; +    r.h = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE; +    if (Rect_ContainsPoint(&r, x, y)) { +      return WINDOW_LB_PGDN; +    } +  } +  return 0; +} + + +void Item_ListBox_MouseEnter(itemDef_t *item, float x, float y) +{ +  rectDef_t r; +  listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + +  item->window.flags &= ~(WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW | WINDOW_LB_THUMB | WINDOW_LB_PGUP | WINDOW_LB_PGDN); +  item->window.flags |= Item_ListBox_OverLB(item, x, y); + +  if (item->window.flags & WINDOW_HORIZONTAL) { +    if (!(item->window.flags & (WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW | WINDOW_LB_THUMB | WINDOW_LB_PGUP | WINDOW_LB_PGDN))) { +      // check for selection hit as we have exausted buttons and thumb +      if (listPtr->elementStyle == LISTBOX_IMAGE) { +        r.x = item->window.rect.x; +        r.y = item->window.rect.y; +        r.h = item->window.rect.h - SCROLLBAR_SIZE; +        r.w = item->window.rect.w - listPtr->drawPadding; +        if (Rect_ContainsPoint(&r, x, y)) { +          listPtr->cursorPos =  (int)((x - r.x) / listPtr->elementWidth)  + listPtr->startPos; +          if (listPtr->cursorPos >= listPtr->endPos) { +            listPtr->cursorPos = listPtr->endPos; +          } +        } +      } else { +        // text hit.. +      } +    } +  } else if (!(item->window.flags & (WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW | WINDOW_LB_THUMB | WINDOW_LB_PGUP | WINDOW_LB_PGDN))) { +    r.x = item->window.rect.x; +    r.y = item->window.rect.y; +    r.w = item->window.rect.w - SCROLLBAR_SIZE; +    r.h = item->window.rect.h - listPtr->drawPadding; +    if (Rect_ContainsPoint(&r, x, y)) { +      listPtr->cursorPos =  (int)((y - 2 - r.y) / listPtr->elementHeight)  + listPtr->startPos; +      if (listPtr->cursorPos > listPtr->endPos) { +        listPtr->cursorPos = listPtr->endPos; +      } +    } +  } +} + +void Item_MouseEnter(itemDef_t *item, float x, float y) { +  rectDef_t r; +  if (item) { +    r = item->textRect; +    r.y -= r.h; +    // in the text rect? + +    // items can be enabled and disabled based on cvars +    if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) { +      return; +    } + +    if (item->cvarFlags & (CVAR_SHOW | CVAR_HIDE) && !Item_EnableShowViaCvar(item, CVAR_SHOW)) { +      return; +    } + +    if (Rect_ContainsPoint(&r, x, y)) { +      if (!(item->window.flags & WINDOW_MOUSEOVERTEXT)) { +        Item_RunScript(item, item->mouseEnterText); +        item->window.flags |= WINDOW_MOUSEOVERTEXT; +      } +      if (!(item->window.flags & WINDOW_MOUSEOVER)) { +        Item_RunScript(item, item->mouseEnter); +        item->window.flags |= WINDOW_MOUSEOVER; +      } + +    } else { +      // not in the text rect +      if (item->window.flags & WINDOW_MOUSEOVERTEXT) { +        // if we were +        Item_RunScript(item, item->mouseExitText); +        item->window.flags &= ~WINDOW_MOUSEOVERTEXT; +      } +      if (!(item->window.flags & WINDOW_MOUSEOVER)) { +        Item_RunScript(item, item->mouseEnter); +        item->window.flags |= WINDOW_MOUSEOVER; +      } + +      if (item->type == ITEM_TYPE_LISTBOX) { +        Item_ListBox_MouseEnter(item, x, y); +      } +    } +  } +} + +void Item_MouseLeave(itemDef_t *item) { +  if (item) { +    if (item->window.flags & WINDOW_MOUSEOVERTEXT) { +      Item_RunScript(item, item->mouseExitText); +      item->window.flags &= ~WINDOW_MOUSEOVERTEXT; +    } +    Item_RunScript(item, item->mouseExit); +    item->window.flags &= ~(WINDOW_LB_RIGHTARROW | WINDOW_LB_LEFTARROW); +  } +} + +itemDef_t *Menu_HitTest(menuDef_t *menu, float x, float y) { +  int i; +  for (i = 0; i < menu->itemCount; i++) { +    if (Rect_ContainsPoint(&menu->items[i]->window.rect, x, y)) { +      return menu->items[i]; +    } +  } +  return NULL; +} + +void Item_SetMouseOver(itemDef_t *item, qboolean focus) { +  if (item) { +    if (focus) { +      item->window.flags |= WINDOW_MOUSEOVER; +    } else { +      item->window.flags &= ~WINDOW_MOUSEOVER; +    } +  } +} + + +qboolean Item_OwnerDraw_HandleKey(itemDef_t *item, int key) { +  if (item && DC->ownerDrawHandleKey) { +    return DC->ownerDrawHandleKey(item->window.ownerDraw, item->window.ownerDrawFlags, &item->special, key); +  } +  return qfalse; +} + +qboolean Item_ListBox_HandleKey(itemDef_t *item, int key, qboolean down, qboolean force) { +  listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; +  int count = DC->feederCount(item->special); +  int max, viewmax; + +  if (force || (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && item->window.flags & WINDOW_HASFOCUS)) { +    max = Item_ListBox_MaxScroll(item); +    if (item->window.flags & WINDOW_HORIZONTAL) { +      viewmax = (item->window.rect.w / listPtr->elementWidth); +      if ( key == K_LEFTARROW || key == K_KP_LEFTARROW ) +      { +        if (!listPtr->notselectable) { +          listPtr->cursorPos--; +          if (listPtr->cursorPos < 0) { +            listPtr->cursorPos = 0; +          } +          if (listPtr->cursorPos < listPtr->startPos) { +            listPtr->startPos = listPtr->cursorPos; +          } +          if (listPtr->cursorPos >= listPtr->startPos + viewmax) { +            listPtr->startPos = listPtr->cursorPos - viewmax + 1; +          } +          item->cursorPos = listPtr->cursorPos; +          DC->feederSelection(item->special, item->cursorPos); +        } +        else { +          listPtr->startPos--; +          if (listPtr->startPos < 0) +            listPtr->startPos = 0; +        } +        return qtrue; +      } +      if ( key == K_RIGHTARROW || key == K_KP_RIGHTARROW ) +      { +        if (!listPtr->notselectable) { +          listPtr->cursorPos++; +          if (listPtr->cursorPos < listPtr->startPos) { +            listPtr->startPos = listPtr->cursorPos; +          } +          if (listPtr->cursorPos >= count) { +            listPtr->cursorPos = count-1; +          } +          if (listPtr->cursorPos >= listPtr->startPos + viewmax) { +            listPtr->startPos = listPtr->cursorPos - viewmax + 1; +          } +          item->cursorPos = listPtr->cursorPos; +          DC->feederSelection(item->special, item->cursorPos); +        } +        else { +          listPtr->startPos++; +          if (listPtr->startPos >= count) +            listPtr->startPos = count-1; +        } +        return qtrue; +      } +    } +    else { +      viewmax = (item->window.rect.h / listPtr->elementHeight); +      if ( key == K_UPARROW || key == K_KP_UPARROW ) +      { +        if (!listPtr->notselectable) { +          listPtr->cursorPos--; +          if (listPtr->cursorPos < 0) { +            listPtr->cursorPos = 0; +          } +          if (listPtr->cursorPos < listPtr->startPos) { +            listPtr->startPos = listPtr->cursorPos; +          } +          if (listPtr->cursorPos >= listPtr->startPos + viewmax) { +            listPtr->startPos = listPtr->cursorPos - viewmax + 1; +          } +          item->cursorPos = listPtr->cursorPos; +          DC->feederSelection(item->special, item->cursorPos); +        } +        else { +          listPtr->startPos--; +          if (listPtr->startPos < 0) +            listPtr->startPos = 0; +        } +        return qtrue; +      } +      if ( key == K_DOWNARROW || key == K_KP_DOWNARROW ) +      { +        if (!listPtr->notselectable) { +          listPtr->cursorPos++; +          if (listPtr->cursorPos < listPtr->startPos) { +            listPtr->startPos = listPtr->cursorPos; +          } +          if (listPtr->cursorPos >= count) { +            listPtr->cursorPos = count-1; +          } +          if (listPtr->cursorPos >= listPtr->startPos + viewmax) { +            listPtr->startPos = listPtr->cursorPos - viewmax + 1; +          } +          item->cursorPos = listPtr->cursorPos; +          DC->feederSelection(item->special, item->cursorPos); +        } +        else { +          listPtr->startPos++; +          if (listPtr->startPos > max) +            listPtr->startPos = max; +        } +        return qtrue; +      } +    } +    // mouse hit +    if (key == K_MOUSE1 || key == K_MOUSE2) { +      if (item->window.flags & WINDOW_LB_LEFTARROW) { +        listPtr->startPos--; +        if (listPtr->startPos < 0) { +          listPtr->startPos = 0; +        } +      } else if (item->window.flags & WINDOW_LB_RIGHTARROW) { +        // one down +        listPtr->startPos++; +        if (listPtr->startPos > max) { +          listPtr->startPos = max; +        } +      } else if (item->window.flags & WINDOW_LB_PGUP) { +        // page up +        listPtr->startPos -= viewmax; +        if (listPtr->startPos < 0) { +          listPtr->startPos = 0; +        } +      } else if (item->window.flags & WINDOW_LB_PGDN) { +        // page down +        listPtr->startPos += viewmax; +        if (listPtr->startPos > max) { +          listPtr->startPos = max; +        } +      } else if (item->window.flags & WINDOW_LB_THUMB) { +        // Display_SetCaptureItem(item); +      } else { +        // select an item +        if (DC->realTime < lastListBoxClickTime && listPtr->doubleClick) { +          Item_RunScript(item, listPtr->doubleClick); +        } +        lastListBoxClickTime = DC->realTime + DOUBLE_CLICK_DELAY; +        if (item->cursorPos != listPtr->cursorPos) { +          item->cursorPos = listPtr->cursorPos; +          DC->feederSelection(item->special, item->cursorPos); +        } +      } +      return qtrue; +    } + +    // Scroll wheel +    if (key == K_MWHEELUP) { +      listPtr->startPos--; +      if (listPtr->startPos < 0) { +        listPtr->startPos = 0; +      } +      return qtrue; +    } +    if (key == K_MWHEELDOWN) { +      listPtr->startPos++; +      if (listPtr->startPos > max) { +        listPtr->startPos = max; +      } +      return qtrue; +    } + +    // Invoke the doubleClick handler when enter is pressed +    if( key == K_ENTER ) +    { +      if( listPtr->doubleClick ) +        Item_RunScript( item, listPtr->doubleClick ); + +      return qtrue; +    } + +    if ( key == K_HOME || key == K_KP_HOME) { +      // home +      listPtr->startPos = 0; +      return qtrue; +    } +    if ( key == K_END || key == K_KP_END) { +      // end +      listPtr->startPos = max; +      return qtrue; +    } +    if (key == K_PGUP || key == K_KP_PGUP ) { +      // page up +      if (!listPtr->notselectable) { +        listPtr->cursorPos -= viewmax; +        if (listPtr->cursorPos < 0) { +          listPtr->cursorPos = 0; +        } +        if (listPtr->cursorPos < listPtr->startPos) { +          listPtr->startPos = listPtr->cursorPos; +        } +        if (listPtr->cursorPos >= listPtr->startPos + viewmax) { +          listPtr->startPos = listPtr->cursorPos - viewmax + 1; +        } +        item->cursorPos = listPtr->cursorPos; +        DC->feederSelection(item->special, item->cursorPos); +      } +      else { +        listPtr->startPos -= viewmax; +        if (listPtr->startPos < 0) { +          listPtr->startPos = 0; +        } +      } +      return qtrue; +    } +    if ( key == K_PGDN || key == K_KP_PGDN ) { +      // page down +      if (!listPtr->notselectable) { +        listPtr->cursorPos += viewmax; +        if (listPtr->cursorPos < listPtr->startPos) { +          listPtr->startPos = listPtr->cursorPos; +        } +        if (listPtr->cursorPos >= count) { +          listPtr->cursorPos = count-1; +        } +        if (listPtr->cursorPos >= listPtr->startPos + viewmax) { +          listPtr->startPos = listPtr->cursorPos - viewmax + 1; +        } +        item->cursorPos = listPtr->cursorPos; +        DC->feederSelection(item->special, item->cursorPos); +      } +      else { +        listPtr->startPos += viewmax; +        if (listPtr->startPos > max) { +          listPtr->startPos = max; +        } +      } +      return qtrue; +    } +  } +  return qfalse; +} + +qboolean Item_YesNo_HandleKey(itemDef_t *item, int key) { + +  if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && item->window.flags & WINDOW_HASFOCUS && item->cvar) { +    if (key == K_MOUSE1 || key == K_ENTER || key == K_MOUSE2 || key == K_MOUSE3) { +      DC->setCVar(item->cvar, va("%i", !DC->getCVarValue(item->cvar))); +      return qtrue; +    } +  } + +  return qfalse; + +} + +int Item_Multi_CountSettings(itemDef_t *item) { +  multiDef_t *multiPtr = (multiDef_t*)item->typeData; +  if (multiPtr == NULL) { +    return 0; +  } +  return multiPtr->count; +} + +int Item_Multi_FindCvarByValue(itemDef_t *item) { +  char buff[1024]; +  float value = 0; +  int i; +  multiDef_t *multiPtr = (multiDef_t*)item->typeData; +  if (multiPtr) { +    if (multiPtr->strDef) { +      DC->getCVarString(item->cvar, buff, sizeof(buff)); +    } else { +      value = DC->getCVarValue(item->cvar); +    } +    for (i = 0; i < multiPtr->count; i++) { +      if (multiPtr->strDef) { +        if (Q_stricmp(buff, multiPtr->cvarStr[i]) == 0) { +          return i; +        } +      } else { +        if (multiPtr->cvarValue[i] == value) { +          return i; +        } +      } +    } +  } +  return 0; +} + +const char *Item_Multi_Setting(itemDef_t *item) { +  char buff[1024]; +  float value = 0; +  int i; +  multiDef_t *multiPtr = (multiDef_t*)item->typeData; +  if (multiPtr) { +    if (multiPtr->strDef) { +      DC->getCVarString(item->cvar, buff, sizeof(buff)); +    } else { +      value = DC->getCVarValue(item->cvar); +    } +    for (i = 0; i < multiPtr->count; i++) { +      if (multiPtr->strDef) { +        if (Q_stricmp(buff, multiPtr->cvarStr[i]) == 0) { +          return multiPtr->cvarList[i]; +        } +      } else { +        if (multiPtr->cvarValue[i] == value) { +          return multiPtr->cvarList[i]; +        } +      } +    } +  } +  return ""; +} + +qboolean Item_Multi_HandleKey(itemDef_t *item, int key) { +  multiDef_t *multiPtr = (multiDef_t*)item->typeData; +  if (multiPtr) { +    if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && item->window.flags & WINDOW_HASFOCUS && item->cvar) { +      if (key == K_MOUSE1 || key == K_ENTER || key == K_MOUSE2 || key == K_MOUSE3) { +        int current = Item_Multi_FindCvarByValue(item) + 1; +        int max = Item_Multi_CountSettings(item); +        if ( current < 0 || current >= max ) { +          current = 0; +        } +        if (multiPtr->strDef) { +          DC->setCVar(item->cvar, multiPtr->cvarStr[current]); +        } else { +          float value = multiPtr->cvarValue[current]; +          if (((float)((int) value)) == value) { +            DC->setCVar(item->cvar, va("%i", (int) value )); +          } +          else { +            DC->setCVar(item->cvar, va("%f", value )); +          } +        } +        return qtrue; +      } +    } +  } +  return qfalse; +} + +qboolean Item_TextField_HandleKey(itemDef_t *item, int key) { +  char buff[1024]; +  int len; +  itemDef_t *newItem = NULL; +  editFieldDef_t *editPtr = (editFieldDef_t*)item->typeData; + +  if (item->cvar) { + +    memset(buff, 0, sizeof(buff)); +    DC->getCVarString(item->cvar, buff, sizeof(buff)); +    len = strlen(buff); +    if (editPtr->maxChars && len > editPtr->maxChars) { +      len = editPtr->maxChars; +    } +    if ( key & K_CHAR_FLAG ) { +      key &= ~K_CHAR_FLAG; + + +      if (key == 'h' - 'a' + 1 )  { // ctrl-h is backspace +        if ( item->cursorPos > 0 ) { +          memmove( &buff[item->cursorPos - 1], &buff[item->cursorPos], len + 1 - item->cursorPos); +          item->cursorPos--; +          if (item->cursorPos < editPtr->paintOffset) { +            editPtr->paintOffset--; +          } +        } +        DC->setCVar(item->cvar, buff); +          return qtrue; +      } + + +      // +      // ignore any non printable chars +      // +      if ( key < 32 || !item->cvar) { +          return qtrue; +        } + +      if (item->type == ITEM_TYPE_NUMERICFIELD) { +        if (key < '0' || key > '9') { +          return qfalse; +        } +      } + +      if (!DC->getOverstrikeMode()) { +        if (( len == MAX_EDITFIELD - 1 ) || (editPtr->maxChars && len >= editPtr->maxChars)) { +          return qtrue; +        } +        memmove( &buff[item->cursorPos + 1], &buff[item->cursorPos], len + 1 - item->cursorPos ); +      } else { +        if (editPtr->maxChars && item->cursorPos >= editPtr->maxChars) { +          return qtrue; +        } +      } + +      buff[item->cursorPos] = key; + +      DC->setCVar(item->cvar, buff); + +      if (item->cursorPos < len + 1) { +        item->cursorPos++; +        if (editPtr->maxPaintChars && item->cursorPos > editPtr->maxPaintChars) { +          editPtr->paintOffset++; +        } +      } + +    } else { + +      if ( key == K_DEL || key == K_KP_DEL ) { +        if ( item->cursorPos < len ) { +          memmove( buff + item->cursorPos, buff + item->cursorPos + 1, len - item->cursorPos); +          DC->setCVar(item->cvar, buff); +        } +        return qtrue; +      } + +      if ( key == K_RIGHTARROW || key == K_KP_RIGHTARROW ) +      { +        if (editPtr->maxPaintChars && item->cursorPos >= editPtr->maxPaintChars && item->cursorPos < len) { +          item->cursorPos++; +          editPtr->paintOffset++; +          return qtrue; +        } +        if (item->cursorPos < len) { +          item->cursorPos++; +        } +        return qtrue; +      } + +      if ( key == K_LEFTARROW || key == K_KP_LEFTARROW ) +      { +        if ( item->cursorPos > 0 ) { +          item->cursorPos--; +        } +        if (item->cursorPos < editPtr->paintOffset) { +          editPtr->paintOffset--; +        } +        return qtrue; +      } + +      if ( key == K_HOME || key == K_KP_HOME) {// || ( tolower(key) == 'a' && trap_Key_IsDown( K_CTRL ) ) ) { +        item->cursorPos = 0; +        editPtr->paintOffset = 0; +        return qtrue; +      } + +      if ( key == K_END || key == K_KP_END)  {// ( tolower(key) == 'e' && trap_Key_IsDown( K_CTRL ) ) ) { +        item->cursorPos = len; +        if(item->cursorPos > editPtr->maxPaintChars) { +          editPtr->paintOffset = len - editPtr->maxPaintChars; +        } +        return qtrue; +      } + +      if ( key == K_INS || key == K_KP_INS ) { +        DC->setOverstrikeMode(!DC->getOverstrikeMode()); +        return qtrue; +      } +    } + +    if (key == K_TAB || key == K_DOWNARROW || key == K_KP_DOWNARROW) { +      newItem = Menu_SetNextCursorItem(item->parent); +      if (newItem && (newItem->type == ITEM_TYPE_EDITFIELD || newItem->type == ITEM_TYPE_NUMERICFIELD)) { +        g_editItem = newItem; +      } +    } + +    if (key == K_UPARROW || key == K_KP_UPARROW) { +      newItem = Menu_SetPrevCursorItem(item->parent); +      if (newItem && (newItem->type == ITEM_TYPE_EDITFIELD || newItem->type == ITEM_TYPE_NUMERICFIELD)) { +        g_editItem = newItem; +      } +    } + +    if ( key == K_ENTER || key == K_KP_ENTER || key == K_ESCAPE)  { +      return qfalse; +    } + +    return qtrue; +  } +  return qfalse; + +} + +static void Scroll_ListBox_AutoFunc(void *p) { +  scrollInfo_t *si = (scrollInfo_t*)p; +  if (DC->realTime > si->nextScrollTime) { +    // need to scroll which is done by simulating a click to the item +    // this is done a bit sideways as the autoscroll "knows" that the item is a listbox +    // so it calls it directly +    Item_ListBox_HandleKey(si->item, si->scrollKey, qtrue, qfalse); +    si->nextScrollTime = DC->realTime + si->adjustValue; +  } + +  if (DC->realTime > si->nextAdjustTime) { +    si->nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST; +    if (si->adjustValue > SCROLL_TIME_FLOOR) { +      si->adjustValue -= SCROLL_TIME_ADJUSTOFFSET; +    } +  } +} + +static void Scroll_ListBox_ThumbFunc(void *p) { +  scrollInfo_t *si = (scrollInfo_t*)p; +  rectDef_t r; +  int pos, max; + +  listBoxDef_t *listPtr = (listBoxDef_t*)si->item->typeData; +  if (si->item->window.flags & WINDOW_HORIZONTAL) { +    if (DC->cursorx == si->xStart) { +      return; +    } +    r.x = si->item->window.rect.x + SCROLLBAR_SIZE + 1; +    r.y = si->item->window.rect.y + si->item->window.rect.h - SCROLLBAR_SIZE - 1; +    r.h = SCROLLBAR_SIZE; +    r.w = si->item->window.rect.w - (SCROLLBAR_SIZE*2) - 2; +    max = Item_ListBox_MaxScroll(si->item); +    // +    pos = (DC->cursorx - r.x - SCROLLBAR_SIZE/2) * max / (r.w - SCROLLBAR_SIZE); +    if (pos < 0) { +      pos = 0; +    } +    else if (pos > max) { +      pos = max; +    } +    listPtr->startPos = pos; +    si->xStart = DC->cursorx; +  } +  else if (DC->cursory != si->yStart) { + +    r.x = si->item->window.rect.x + si->item->window.rect.w - SCROLLBAR_SIZE - 1; +    r.y = si->item->window.rect.y + SCROLLBAR_SIZE + 1; +    r.h = si->item->window.rect.h - (SCROLLBAR_SIZE*2) - 2; +    r.w = SCROLLBAR_SIZE; +    max = Item_ListBox_MaxScroll(si->item); +    // +    pos = (DC->cursory - r.y - SCROLLBAR_SIZE/2) * max / (r.h - SCROLLBAR_SIZE); +    if (pos < 0) { +      pos = 0; +    } +    else if (pos > max) { +      pos = max; +    } +    listPtr->startPos = pos; +    si->yStart = DC->cursory; +  } + +  if (DC->realTime > si->nextScrollTime) { +    // need to scroll which is done by simulating a click to the item +    // this is done a bit sideways as the autoscroll "knows" that the item is a listbox +    // so it calls it directly +    Item_ListBox_HandleKey(si->item, si->scrollKey, qtrue, qfalse); +    si->nextScrollTime = DC->realTime + si->adjustValue; +  } + +  if (DC->realTime > si->nextAdjustTime) { +    si->nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST; +    if (si->adjustValue > SCROLL_TIME_FLOOR) { +      si->adjustValue -= SCROLL_TIME_ADJUSTOFFSET; +    } +  } +} + +static void Scroll_Slider_ThumbFunc(void *p) { +  float x, value, cursorx; +  scrollInfo_t *si = (scrollInfo_t*)p; +  editFieldDef_t *editDef = si->item->typeData; + +  if (si->item->text) { +    x = si->item->textRect.x + si->item->textRect.w + 8; +  } else { +    x = si->item->window.rect.x; +  } + +  cursorx = DC->cursorx; + +  if (cursorx < x) { +    cursorx = x; +  } else if (cursorx > x + SLIDER_WIDTH) { +    cursorx = x + SLIDER_WIDTH; +  } +  value = cursorx - x; +  value /= SLIDER_WIDTH; +  value *= (editDef->maxVal - editDef->minVal); +  value += editDef->minVal; +  DC->setCVar(si->item->cvar, va("%f", value)); +} + +void Item_StartCapture(itemDef_t *item, int key) { +  int flags; +  switch (item->type) { +    case ITEM_TYPE_EDITFIELD: +    case ITEM_TYPE_NUMERICFIELD: + +    case ITEM_TYPE_LISTBOX: +    { +      flags = Item_ListBox_OverLB(item, DC->cursorx, DC->cursory); +      if (flags & (WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW)) { +        scrollInfo.nextScrollTime = DC->realTime + SCROLL_TIME_START; +        scrollInfo.nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST; +        scrollInfo.adjustValue = SCROLL_TIME_START; +        scrollInfo.scrollKey = key; +        scrollInfo.scrollDir = (flags & WINDOW_LB_LEFTARROW) ? qtrue : qfalse; +        scrollInfo.item = item; +        captureData = &scrollInfo; +        captureFunc = &Scroll_ListBox_AutoFunc; +        itemCapture = item; +      } else if (flags & WINDOW_LB_THUMB) { +        scrollInfo.scrollKey = key; +        scrollInfo.item = item; +        scrollInfo.xStart = DC->cursorx; +        scrollInfo.yStart = DC->cursory; +        captureData = &scrollInfo; +        captureFunc = &Scroll_ListBox_ThumbFunc; +        itemCapture = item; +      } +      break; +    } +    case ITEM_TYPE_SLIDER: +    { +      flags = Item_Slider_OverSlider(item, DC->cursorx, DC->cursory); +      if (flags & WINDOW_LB_THUMB) { +        scrollInfo.scrollKey = key; +        scrollInfo.item = item; +        scrollInfo.xStart = DC->cursorx; +        scrollInfo.yStart = DC->cursory; +        captureData = &scrollInfo; +        captureFunc = &Scroll_Slider_ThumbFunc; +        itemCapture = item; +      } +      break; +    } +  } +} + +void Item_StopCapture(itemDef_t *item) { + +} + +qboolean Item_Slider_HandleKey(itemDef_t *item, int key, qboolean down) { +  float x, value, width, work; + +  if (item->window.flags & WINDOW_HASFOCUS && item->cvar && Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory)) { +    if (key == K_MOUSE1 || key == K_ENTER || key == K_MOUSE2 || key == K_MOUSE3) { +      editFieldDef_t *editDef = item->typeData; +      if (editDef) { +        rectDef_t testRect; +        width = SLIDER_WIDTH; +        if (item->text) { +          x = item->textRect.x + item->textRect.w + 8; +        } else { +          x = item->window.rect.x; +        } + +        testRect = item->window.rect; +        testRect.x = x; +        value = (float)SLIDER_THUMB_WIDTH / 2; +        testRect.x -= value; +        testRect.w = (SLIDER_WIDTH + (float)SLIDER_THUMB_WIDTH / 2); +        if (Rect_ContainsPoint(&testRect, DC->cursorx, DC->cursory)) { +          work = DC->cursorx - x; +          value = work / width; +          value *= (editDef->maxVal - editDef->minVal); +          // vm fuckage +          // value = (((float)(DC->cursorx - x)/ SLIDER_WIDTH) * (editDef->maxVal - editDef->minVal)); +          value += editDef->minVal; +          DC->setCVar(item->cvar, va("%f", value)); +          return qtrue; +        } +      } +    } +  } +  return qfalse; +} + + +qboolean Item_HandleKey(itemDef_t *item, int key, qboolean down) { + +  if (itemCapture) { +    Item_StopCapture(itemCapture); +    itemCapture = NULL; +    captureFunc = voidFunction; +    captureData = NULL; +  } else { +    // bk001206 - parentheses +    if ( down && ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3 ) ) { +      Item_StartCapture(item, key); +    } +  } + +  if (!down) { +    return qfalse; +  } + +  switch (item->type) { +    case ITEM_TYPE_BUTTON: +      return qfalse; +      break; +    case ITEM_TYPE_RADIOBUTTON: +      return qfalse; +      break; +    case ITEM_TYPE_CHECKBOX: +      return qfalse; +      break; +    case ITEM_TYPE_EDITFIELD: +    case ITEM_TYPE_NUMERICFIELD: +      //return Item_TextField_HandleKey(item, key); +      return qfalse; +      break; +    case ITEM_TYPE_COMBO: +      return qfalse; +      break; +    case ITEM_TYPE_LISTBOX: +      return Item_ListBox_HandleKey(item, key, down, qfalse); +      break; +    case ITEM_TYPE_YESNO: +      return Item_YesNo_HandleKey(item, key); +      break; +    case ITEM_TYPE_MULTI: +      return Item_Multi_HandleKey(item, key); +      break; +    case ITEM_TYPE_OWNERDRAW: +      return Item_OwnerDraw_HandleKey(item, key); +      break; +    case ITEM_TYPE_BIND: +      return Item_Bind_HandleKey(item, key, down); +      break; +    case ITEM_TYPE_SLIDER: +      return Item_Slider_HandleKey(item, key, down); +      break; +    //case ITEM_TYPE_IMAGE: +    //  Item_Image_Paint(item); +    //  break; +    default: +      return qfalse; +      break; +  } + +  //return qfalse; +} + +void Item_Action(itemDef_t *item) { +  if (item) { +    Item_RunScript(item, item->action); +  } +} + +itemDef_t *Menu_SetPrevCursorItem(menuDef_t *menu) { +  qboolean wrapped = qfalse; +  int oldCursor = menu->cursorItem; + +  if (menu->cursorItem < 0) { +    menu->cursorItem = menu->itemCount-1; +    wrapped = qtrue; +  } + +  while (menu->cursorItem > -1) { + +    menu->cursorItem--; +    if (menu->cursorItem < 0 && !wrapped) { +      wrapped = qtrue; +      menu->cursorItem = menu->itemCount -1; +    } + +    if (Item_SetFocus(menu->items[menu->cursorItem], DC->cursorx, DC->cursory)) { +      Menu_HandleMouseMove(menu, menu->items[menu->cursorItem]->window.rect.x + 1, menu->items[menu->cursorItem]->window.rect.y + 1); +      return menu->items[menu->cursorItem]; +    } +  } +  menu->cursorItem = oldCursor; +  return NULL; + +} + +itemDef_t *Menu_SetNextCursorItem(menuDef_t *menu) { + +  qboolean wrapped = qfalse; +  int oldCursor = menu->cursorItem; + + +  if (menu->cursorItem == -1) { +    menu->cursorItem = 0; +    wrapped = qtrue; +  } + +  while (menu->cursorItem < menu->itemCount) { + +    menu->cursorItem++; +    if (menu->cursorItem >= menu->itemCount && !wrapped) { +      wrapped = qtrue; +      menu->cursorItem = 0; +    } +    if (Item_SetFocus(menu->items[menu->cursorItem], DC->cursorx, DC->cursory)) { +      Menu_HandleMouseMove(menu, menu->items[menu->cursorItem]->window.rect.x + 1, menu->items[menu->cursorItem]->window.rect.y + 1); +      return menu->items[menu->cursorItem]; +    } + +  } + +  menu->cursorItem = oldCursor; +  return NULL; +} + +static void Window_CloseCinematic(windowDef_t *window) { +  if (window->style == WINDOW_STYLE_CINEMATIC && window->cinematic >= 0) { +    DC->stopCinematic(window->cinematic); +    window->cinematic = -1; +  } +} + +static void Menu_CloseCinematics(menuDef_t *menu) { +  if (menu) { +    int i; +    Window_CloseCinematic(&menu->window); +    for (i = 0; i < menu->itemCount; i++) { +      Window_CloseCinematic(&menu->items[i]->window); +      if (menu->items[i]->type == ITEM_TYPE_OWNERDRAW) { +        DC->stopCinematic(0-menu->items[i]->window.ownerDraw); +      } +    } +  } +} + +static void Display_CloseCinematics( void ) { +  int i; +  for (i = 0; i < menuCount; i++) { +    Menu_CloseCinematics(&Menus[i]); +  } +} + +void  Menus_Activate(menuDef_t *menu) { +  menu->window.flags |= (WINDOW_HASFOCUS | WINDOW_VISIBLE); +  if (menu->onOpen) { +    itemDef_t item; +    item.parent = menu; +    Item_RunScript(&item, menu->onOpen); +  } + +  if (menu->soundName && *menu->soundName) { +//    DC->stopBackgroundTrack();          // you don't want to do this since it will reset s_rawend +    DC->startBackgroundTrack(menu->soundName, menu->soundName); +  } + +  Display_CloseCinematics(); + +} + +int Display_VisibleMenuCount( void ) { +  int i, count; +  count = 0; +  for (i = 0; i < menuCount; i++) { +    if (Menus[i].window.flags & (WINDOW_FORCED | WINDOW_VISIBLE)) { +      count++; +    } +  } +  return count; +} + +void Menus_HandleOOBClick(menuDef_t *menu, int key, qboolean down) { +  if (menu) { +    int i; +    // basically the behaviour we are looking for is if there are windows in the stack.. see if +    // the cursor is within any of them.. if not close them otherwise activate them and pass the +    // key on.. force a mouse move to activate focus and script stuff +    if (down && menu->window.flags & WINDOW_OOB_CLICK) { +      Menu_RunCloseScript(menu); +      menu->window.flags &= ~(WINDOW_HASFOCUS | WINDOW_VISIBLE); +    } + +    for (i = 0; i < menuCount; i++) { +      if (Menu_OverActiveItem(&Menus[i], DC->cursorx, DC->cursory)) { +        Menu_RunCloseScript(menu); +        menu->window.flags &= ~(WINDOW_HASFOCUS | WINDOW_VISIBLE); +        Menus_Activate(&Menus[i]); +        Menu_HandleMouseMove(&Menus[i], DC->cursorx, DC->cursory); +        Menu_HandleKey(&Menus[i], key, down); +      } +    } + +    if (Display_VisibleMenuCount() == 0) { +      if (DC->Pause) { +        DC->Pause(qfalse); +      } +    } +    Display_CloseCinematics(); +  } +} + +static rectDef_t *Item_CorrectedTextRect(itemDef_t *item) { +  static rectDef_t rect; +  memset(&rect, 0, sizeof(rectDef_t)); +  if (item) { +    rect = item->textRect; +    if (rect.w) { +      rect.y -= rect.h; +    } +  } +  return ▭ +} + +void Menu_HandleKey(menuDef_t *menu, int key, qboolean down) { +  int i; +  itemDef_t *item = NULL; +  qboolean inHandler = qfalse; + +  if (inHandler) { +    return; +  } + +  inHandler = qtrue; +  if (g_waitingForKey && down) { +    Item_Bind_HandleKey(g_bindItem, key, down); +    inHandler = qfalse; +    return; +  } + +  if (g_editingField && down) { +    if (!Item_TextField_HandleKey(g_editItem, key)) { +      g_editingField = qfalse; +      g_editItem = NULL; +      inHandler = qfalse; +      return; +    } else if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3) { +      g_editingField = qfalse; +      g_editItem = NULL; +      Display_MouseMove(NULL, DC->cursorx, DC->cursory); +    } else if (key == K_TAB || key == K_UPARROW || key == K_DOWNARROW) { +      return; +    } +  } + +  if (menu == NULL) { +    inHandler = qfalse; +    return; +  } + +    // see if the mouse is within the window bounds and if so is this a mouse click +  if (down && !(menu->window.flags & WINDOW_POPUP) && !Rect_ContainsPoint(&menu->window.rect, DC->cursorx, DC->cursory)) { +    static qboolean inHandleKey = qfalse; +    // bk001206 - parentheses +    if (!inHandleKey && ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3 ) ) { +      inHandleKey = qtrue; +      Menus_HandleOOBClick(menu, key, down); +      inHandleKey = qfalse; +      inHandler = qfalse; +      return; +    } +  } + +  // get the item with focus +  for (i = 0; i < menu->itemCount; i++) { +    if (menu->items[i]->window.flags & WINDOW_HASFOCUS) { +      item = menu->items[i]; +    } +  } + +  if (item != NULL) { +    if (Item_HandleKey(item, key, down)) { +      Item_Action(item); +      inHandler = qfalse; +      return; +    } +  } + +  if (!down) { +    inHandler = qfalse; +    return; +  } + +  // default handling +  switch ( key ) { + +    case K_F11: +      if (DC->getCVarValue("developer")) { +        debugMode ^= 1; +      } +      break; + +    case K_F12: +      if (DC->getCVarValue("developer")) { +        DC->executeText(EXEC_APPEND, "screenshot\n"); +      } +      break; +    case K_KP_UPARROW: +    case K_UPARROW: +      Menu_SetPrevCursorItem(menu); +      break; + +    case K_ESCAPE: +      if (!g_waitingForKey && menu->onESC) { +        itemDef_t it; +        it.parent = menu; +        Item_RunScript(&it, menu->onESC); +      } +      break; +    case K_TAB: +    case K_KP_DOWNARROW: +    case K_DOWNARROW: +      Menu_SetNextCursorItem(menu); +      break; + +    case K_MOUSE1: +    case K_MOUSE2: +      if (item) { +        if (item->type == ITEM_TYPE_TEXT) { +          if (Rect_ContainsPoint(Item_CorrectedTextRect(item), DC->cursorx, DC->cursory)) { +            Item_Action(item); +          } +        } else if (item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_NUMERICFIELD) { +          if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory)) { +            item->cursorPos = 0; +            g_editingField = qtrue; +            g_editItem = item; +            DC->setOverstrikeMode(qtrue); +          } +        } else { +          if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory)) { +            Item_Action(item); +          } +        } +      } +      break; + +    case K_JOY1: +    case K_JOY2: +    case K_JOY3: +    case K_JOY4: +    case K_AUX1: +    case K_AUX2: +    case K_AUX3: +    case K_AUX4: +    case K_AUX5: +    case K_AUX6: +    case K_AUX7: +    case K_AUX8: +    case K_AUX9: +    case K_AUX10: +    case K_AUX11: +    case K_AUX12: +    case K_AUX13: +    case K_AUX14: +    case K_AUX15: +    case K_AUX16: +      break; +    case K_KP_ENTER: +    case K_ENTER: +      if (item) { +        if (item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_NUMERICFIELD) { +          item->cursorPos = 0; +          g_editingField = qtrue; +          g_editItem = item; +          DC->setOverstrikeMode(qtrue); +        } else { +            Item_Action(item); +        } +      } +      break; +  } +  inHandler = qfalse; +} + +void ToWindowCoords(float *x, float *y, windowDef_t *window) { +  if (window->border != 0) { +    *x += window->borderSize; +    *y += window->borderSize; +  } +  *x += window->rect.x; +  *y += window->rect.y; +} + +void Rect_ToWindowCoords(rectDef_t *rect, windowDef_t *window) { +  ToWindowCoords(&rect->x, &rect->y, window); +} + +void Item_SetTextExtents(itemDef_t *item, int *width, int *height, const char *text) { +  const char *textPtr = (text) ? text : item->text; + +  if (textPtr == NULL ) { +    return; +  } + +  *width = item->textRect.w; +  *height = item->textRect.h; + +  // keeps us from computing the widths and heights more than once +  if (*width == 0 || (item->type == ITEM_TYPE_OWNERDRAW && item->textalignment == ITEM_ALIGN_CENTER)) { +    int originalWidth = DC->textWidth(item->text, item->textscale, 0); + +    if (item->type == ITEM_TYPE_OWNERDRAW && (item->textalignment == ITEM_ALIGN_CENTER || item->textalignment == ITEM_ALIGN_RIGHT)) { +      originalWidth += DC->ownerDrawWidth(item->window.ownerDraw, item->textscale); +    } else if (item->type == ITEM_TYPE_EDITFIELD && item->textalignment == ITEM_ALIGN_CENTER && item->cvar) { +      char buff[256]; +      DC->getCVarString(item->cvar, buff, 256); +      originalWidth += DC->textWidth(buff, item->textscale, 0); +    } + +    *width = DC->textWidth(textPtr, item->textscale, 0); +    *height = DC->textHeight(textPtr, item->textscale, 0); +    item->textRect.w = *width; +    item->textRect.h = *height; +    item->textRect.x = item->textalignx; +    item->textRect.y = item->textaligny; +    if (item->textalignment == ITEM_ALIGN_RIGHT) { +      item->textRect.x = item->textalignx - originalWidth; +    } else if (item->textalignment == ITEM_ALIGN_CENTER) { +      item->textRect.x = item->textalignx - originalWidth / 2; +    } + +    ToWindowCoords(&item->textRect.x, &item->textRect.y, &item->window); +  } +} + +void Item_TextColor(itemDef_t *item, vec4_t *newColor) { +  vec4_t lowLight; +  menuDef_t *parent = (menuDef_t*)item->parent; + +  Fade(&item->window.flags, &item->window.foreColor[3], parent->fadeClamp, &item->window.nextTime, parent->fadeCycle, qtrue, parent->fadeAmount); + +  if (item->window.flags & WINDOW_HASFOCUS) { +/*    lowLight[0] = 0.8 * parent->focusColor[0]; +    lowLight[1] = 0.8 * parent->focusColor[1]; +    lowLight[2] = 0.8 * parent->focusColor[2]; +    lowLight[3] = 0.8 * parent->focusColor[3]; +    LerpColor(parent->focusColor,lowLight,*newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/ +    //TA: +    memcpy(newColor, &parent->focusColor, sizeof(vec4_t)); +  } else if (item->textStyle == ITEM_TEXTSTYLE_BLINK && !((DC->realTime/BLINK_DIVISOR) & 1)) { +    lowLight[0] = 0.8 * item->window.foreColor[0]; +    lowLight[1] = 0.8 * item->window.foreColor[1]; +    lowLight[2] = 0.8 * item->window.foreColor[2]; +    lowLight[3] = 0.8 * item->window.foreColor[3]; +    LerpColor(item->window.foreColor,lowLight,*newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR)); +  } else { +    memcpy(newColor, &item->window.foreColor, sizeof(vec4_t)); +    // items can be enabled and disabled based on cvars +  } + +  if (item->enableCvar != NULL && *item->enableCvar && item->cvarTest != NULL && *item->cvarTest) { +    if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) { +      memcpy(newColor, &parent->disableColor, sizeof(vec4_t)); +    } +  } +} + +int Item_Text_AutoWrapped_Lines( itemDef_t *item ) +{ +  char        text[ 1024 ]; +  const char  *p, *textPtr, *newLinePtr; +  char        buff[ 1024 ]; +  int         len, textWidth, newLine; +  int         lines = 0; + +  textWidth = 0; +  newLinePtr = NULL; + +  if( item->text == NULL ) +  { +    if( item->cvar == NULL ) +      return 0; +    else +    { +      DC->getCVarString( item->cvar, text, sizeof( text ) ); +      textPtr = text; +    } +  } +  else +    textPtr = item->text; + +  if( *textPtr == '\0' ) +    return 0; + +  len = 0; +  buff[ 0 ] = '\0'; +  newLine = 0; +  p = textPtr; + +  while( p ) +  { +    textWidth = DC->textWidth( buff, item->textscale, 0 ); + +    if( *p == ' ' || *p == '\t' || *p == '\n' || *p == '\0' ) +    { +      newLine = len; +      newLinePtr = p + 1; +    } + +    //TA: forceably split lines that are too long (where normal splitage has failed) +    if( textWidth > item->window.rect.w && newLine == 0 && *p != '\n' ) +    { +      newLine = len; +      newLinePtr = p; +    } + +    if( ( newLine && textWidth > item->window.rect.w ) || *p == '\n' || *p == '\0' ) +    { +      if( len ) +        buff[ newLine ] = '\0'; + +      if( !( *p == '\n' && !*( p + 1 ) ) ) +        lines++; + +      if( *p == '\0' ) +        break; + +      // +      p = newLinePtr; +      len = 0; +      newLine = 0; + +      continue; +    } + +    buff[ len++ ] = *p++; +    buff[ len ] = '\0'; +  } + +  return lines; +} + +#define MAX_AUTOWRAP_CACHE  16 +#define MAX_AUTOWRAP_LINES  32 +#define MAX_AUTOWRAP_TEXT   512 + +typedef struct +{ +  //this is used purely for checking for cache hits +  char      text[ MAX_AUTOWRAP_TEXT * MAX_AUTOWRAP_LINES ]; +  rectDef_t rect; +  int       textWidth, textHeight; +  char      lines[ MAX_AUTOWRAP_LINES ][ MAX_AUTOWRAP_TEXT ]; +  int       lineOffsets[ MAX_AUTOWRAP_LINES ][ 2 ]; +  int       numLines; +} autoWrapCache_t; + +static int              cacheIndex = 0; +static autoWrapCache_t  awc[ MAX_AUTOWRAP_CACHE ]; + +static int checkCache( const char *text, rectDef_t *rect, int width, int height ) +{ +  int i; + +  for( i = 0; i < MAX_AUTOWRAP_CACHE; i++ ) +  { +    if( Q_stricmp( text, awc[ i ].text ) ) +      continue; + +    if( rect->x != awc[ i ].rect.x || +        rect->y != awc[ i ].rect.y || +        rect->w != awc[ i ].rect.w || +        rect->h != awc[ i ].rect.h ) +      continue; + +    if( awc[ i ].textWidth != width || awc[ i ].textHeight != height ) +      continue; + +    //this is a match +    return i; +  } + +  //no match - autowrap isn't cached +  return -1; +} + +void Item_Text_AutoWrapped_Paint( itemDef_t *item ) +{ +  char        text[ 1024 ]; +  const char  *p, *textPtr, *newLinePtr; +  char        buff[ 1024 ]; +  char        lastCMod[ 2 ] = { 0, 0 }; +  qboolean    forwardColor = qfalse; +  int         width, height, len, textWidth, newLine, newLineWidth; +  int         skipLines, totalLines, lineNum = 0; +  float       y, totalY, diffY; +  vec4_t      color; +  int         cache, i; + +  textWidth = 0; +  newLinePtr = NULL; + +  if( item->text == NULL ) +  { +    if( item->cvar == NULL ) +      return; +    else +    { +      DC->getCVarString( item->cvar, text, sizeof( text ) ); +      textPtr = text; +    } +  } +  else +    textPtr = item->text; + +  if( *textPtr == '\0' ) +    return; + +  Item_TextColor( item, &color ); +  Item_SetTextExtents( item, &width, &height, textPtr ); + +  //check if this block is cached +  cache = checkCache( textPtr, &item->window.rect, width, height ); +  if( cache >= 0 ) +  { +    lineNum = awc[ cache ].numLines; + +    for( i = 0; i < lineNum; i++ ) +    { +      item->textRect.x = awc[ cache ].lineOffsets[ i ][ 0 ]; +      item->textRect.y = awc[ cache ].lineOffsets[ i ][ 1 ]; + +      DC->drawText( item->textRect.x, item->textRect.y, item->textscale, color, +                    awc[ cache ].lines[ i ], 0, 0, item->textStyle ); +    } +  } +  else +  { +    y = item->textaligny; +    len = 0; +    buff[ 0 ] = '\0'; +    newLine = 0; +    newLineWidth = 0; +    p = textPtr; + +    totalLines = Item_Text_AutoWrapped_Lines( item ); + +    totalY = totalLines * ( height + 5 ); +    diffY = totalY - item->window.rect.h; + +    if( diffY > 0.0f ) +      skipLines = (int)( diffY / ( (float)height + 5.0f ) ); +    else +      skipLines = 0; + +    //set up a cache entry +    strcpy( awc[ cacheIndex ].text, textPtr ); +    awc[ cacheIndex ].rect.x = item->window.rect.x; +    awc[ cacheIndex ].rect.y = item->window.rect.y; +    awc[ cacheIndex ].rect.w = item->window.rect.w; +    awc[ cacheIndex ].rect.h = item->window.rect.h; +    awc[ cacheIndex ].textWidth = width; +    awc[ cacheIndex ].textHeight = height; + +    while( p ) +    { +      textWidth = DC->textWidth( buff, item->textscale, 0 ); + +      if( *p == '^' ) +      { +        lastCMod[ 0 ] = p[ 0 ]; +        lastCMod[ 1 ] = p[ 1 ]; +      } + +      if( *p == ' ' || *p == '\t' || *p == '\n' || *p == '\0' ) +      { +        newLine = len; +        newLinePtr = p+1; +        newLineWidth = textWidth; + +        if( *p == '\n' ) //don't forward colours past deilberate \n's +          lastCMod[ 0 ] = lastCMod[ 1 ] = 0; +        else +          forwardColor = qtrue; +      } + +      //TA: forceably split lines that are too long (where normal splitage has failed) +      if( textWidth > item->window.rect.w && newLine == 0 && *p != '\n' ) +      { +        newLine = len; +        newLinePtr = p; +        newLineWidth = textWidth; + +        forwardColor = qtrue; +      } + +      if( ( newLine && textWidth > item->window.rect.w ) || *p == '\n' || *p == '\0' ) +      { +        if( len ) +        { +          if( item->textalignment == ITEM_ALIGN_LEFT ) +            item->textRect.x = item->textalignx; +          else if( item->textalignment == ITEM_ALIGN_RIGHT ) +            item->textRect.x = item->textalignx - newLineWidth; +          else if( item->textalignment == ITEM_ALIGN_CENTER ) +            item->textRect.x = item->textalignx - newLineWidth / 2; + +          item->textRect.y = y; +          ToWindowCoords( &item->textRect.x, &item->textRect.y, &item->window ); +          // +          buff[ newLine ] = '\0'; + +          if( !skipLines ) +          { +            DC->drawText( item->textRect.x, item->textRect.y, item->textscale, color, buff, 0, 0, item->textStyle ); + +            strcpy( awc[ cacheIndex ].lines[ lineNum ], buff ); +            awc[ cacheIndex ].lineOffsets[ lineNum ][ 0 ] = item->textRect.x; +            awc[ cacheIndex ].lineOffsets[ lineNum ][ 1 ] = item->textRect.y; + +            lineNum++; +          } +        } +        if( *p == '\0' ) +          break; + +        // +        if( !skipLines ) +          y += height + 5; + +        if( skipLines ) +          skipLines--; + +        p = newLinePtr; +        len = 0; +        newLine = 0; +        newLineWidth = 0; + +        if( forwardColor && lastCMod[ 0 ] != 0 ) +        { +          buff[ len++ ] = lastCMod[ 0 ]; +          buff[ len++ ] = lastCMod[ 1 ]; +          buff[ len ] = '\0'; + +          forwardColor = qfalse; +        } + +        continue; +      } + +      buff[ len++ ] = *p++; +      buff[ len ] = '\0'; +    } + +    //mark the end of the lines list +    awc[ cacheIndex ].numLines = lineNum; + +    //increment cacheIndex +    cacheIndex = ( cacheIndex + 1 ) % MAX_AUTOWRAP_CACHE; +  } +} + +void Item_Text_Wrapped_Paint(itemDef_t *item) { +  char text[1024]; +  const char *p, *start, *textPtr; +  char buff[1024]; +  int width, height; +  float x, y; +  vec4_t color; + +  // now paint the text and/or any optional images +  // default to left + +  if (item->text == NULL) { +    if (item->cvar == NULL) { +      return; +    } +    else { +      DC->getCVarString(item->cvar, text, sizeof(text)); +      textPtr = text; +    } +  } +  else { +    textPtr = item->text; +  } +  if (*textPtr == '\0') { +    return; +  } + +  Item_TextColor(item, &color); +  Item_SetTextExtents(item, &width, &height, textPtr); + +  x = item->textRect.x; +  y = item->textRect.y; +  start = textPtr; +  p = strchr(textPtr, '\r'); +  while (p && *p) { +    strncpy(buff, start, p-start+1); +    buff[p-start] = '\0'; +    DC->drawText(x, y, item->textscale, color, buff, 0, 0, item->textStyle); +    y += height + 5; +    start += p - start + 1; +    p = strchr(p+1, '\r'); +  } +  DC->drawText(x, y, item->textscale, color, start, 0, 0, item->textStyle); +} + +void Item_Text_Paint(itemDef_t *item) { +  char text[1024]; +  const char *textPtr; +  int height, width; +  vec4_t color; + +  if (item->window.flags & WINDOW_WRAPPED) { +    Item_Text_Wrapped_Paint(item); +    return; +  } +  if (item->window.flags & WINDOW_AUTOWRAPPED) { +    Item_Text_AutoWrapped_Paint(item); +    return; +  } + +  if (item->text == NULL) { +    if (item->cvar == NULL) { +      return; +    } +    else { +      DC->getCVarString(item->cvar, text, sizeof(text)); +      textPtr = text; +    } +  } +  else { +    textPtr = item->text; +  } + +  // this needs to go here as it sets extents for cvar types as well +  Item_SetTextExtents(item, &width, &height, textPtr); + +  if (*textPtr == '\0') { +    return; +  } + + +  Item_TextColor(item, &color); + +  //FIXME: this is a fucking mess +/* +  adjust = 0; +  if (item->textStyle == ITEM_TEXTSTYLE_OUTLINED || item->textStyle == ITEM_TEXTSTYLE_OUTLINESHADOWED) { +    adjust = 0.5; +  } + +  if (item->textStyle == ITEM_TEXTSTYLE_SHADOWED || item->textStyle == ITEM_TEXTSTYLE_OUTLINESHADOWED) { +    Fade(&item->window.flags, &DC->Assets.shadowColor[3], DC->Assets.fadeClamp, &item->window.nextTime, DC->Assets.fadeCycle, qfalse); +    DC->drawText(item->textRect.x + DC->Assets.shadowX, item->textRect.y + DC->Assets.shadowY, item->textscale, DC->Assets.shadowColor, textPtr, adjust); +  } +*/ + + +//  if (item->textStyle == ITEM_TEXTSTYLE_OUTLINED || item->textStyle == ITEM_TEXTSTYLE_OUTLINESHADOWED) { +//    Fade(&item->window.flags, &item->window.outlineColor[3], DC->Assets.fadeClamp, &item->window.nextTime, DC->Assets.fadeCycle, qfalse); +//    /* +//    Text_Paint(item->textRect.x-1, item->textRect.y-1, item->textscale, item->window.foreColor, textPtr, adjust); +//    Text_Paint(item->textRect.x, item->textRect.y-1, item->textscale, item->window.foreColor, textPtr, adjust); +//    Text_Paint(item->textRect.x+1, item->textRect.y-1, item->textscale, item->window.foreColor, textPtr, adjust); +//    Text_Paint(item->textRect.x-1, item->textRect.y, item->textscale, item->window.foreColor, textPtr, adjust); +//    Text_Paint(item->textRect.x+1, item->textRect.y, item->textscale, item->window.foreColor, textPtr, adjust); +//    Text_Paint(item->textRect.x-1, item->textRect.y+1, item->textscale, item->window.foreColor, textPtr, adjust); +//    Text_Paint(item->textRect.x, item->textRect.y+1, item->textscale, item->window.foreColor, textPtr, adjust); +//    Text_Paint(item->textRect.x+1, item->textRect.y+1, item->textscale, item->window.foreColor, textPtr, adjust); +//    */ +//    DC->drawText(item->textRect.x - 1, item->textRect.y + 1, item->textscale * 1.02, item->window.outlineColor, textPtr, adjust); +//  } + +  DC->drawText(item->textRect.x, item->textRect.y, item->textscale, color, textPtr, 0, 0, item->textStyle); +} + + + +//float     trap_Cvar_VariableValue( const char *var_name ); +//void      trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); + +void Item_TextField_Paint(itemDef_t *item) { +  char buff[1024]; +  vec4_t newColor; +  int offset; +  menuDef_t *parent = (menuDef_t*)item->parent; +  editFieldDef_t *editPtr = (editFieldDef_t*)item->typeData; + +  Item_Text_Paint(item); + +  buff[0] = '\0'; + +  if (item->cvar) { +    DC->getCVarString(item->cvar, buff, sizeof(buff)); +  } + +  parent = (menuDef_t*)item->parent; + +  if (item->window.flags & WINDOW_HASFOCUS) { +/*    lowLight[0] = 0.8 * parent->focusColor[0]; +    lowLight[1] = 0.8 * parent->focusColor[1]; +    lowLight[2] = 0.8 * parent->focusColor[2]; +    lowLight[3] = 0.8 * parent->focusColor[3]; +    LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/ +    //TA: +    memcpy(newColor, &parent->focusColor, sizeof(vec4_t)); +  } else { +    memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t)); +  } + +  offset = (item->text && *item->text) ? 8 : 0; +  if (item->window.flags & WINDOW_HASFOCUS && g_editingField) { +    char cursor = DC->getOverstrikeMode() ? '_' : '|'; +    DC->drawTextWithCursor(item->textRect.x + item->textRect.w + offset, item->textRect.y, item->textscale, newColor, buff + editPtr->paintOffset, item->cursorPos - editPtr->paintOffset , cursor, editPtr->maxPaintChars, item->textStyle); +  } else { +    DC->drawText(item->textRect.x + item->textRect.w + offset, item->textRect.y, item->textscale, newColor, buff + editPtr->paintOffset, 0, editPtr->maxPaintChars, item->textStyle); +  } + +} + +void Item_YesNo_Paint(itemDef_t *item) { +  vec4_t newColor; +  float value; +  menuDef_t *parent = (menuDef_t*)item->parent; + +  value = (item->cvar) ? DC->getCVarValue(item->cvar) : 0; + +  if (item->window.flags & WINDOW_HASFOCUS) { +/*    lowLight[0] = 0.8 * parent->focusColor[0]; +    lowLight[1] = 0.8 * parent->focusColor[1]; +    lowLight[2] = 0.8 * parent->focusColor[2]; +    lowLight[3] = 0.8 * parent->focusColor[3]; +    LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/ +    //TA: +    memcpy(newColor, &parent->focusColor, sizeof(vec4_t)); +  } else { +    memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t)); +  } + +  if (item->text) { +    Item_Text_Paint(item); +    DC->drawText(item->textRect.x + item->textRect.w + 8, item->textRect.y, item->textscale, newColor, (value != 0) ? "Yes" : "No", 0, 0, item->textStyle); +  } else { +    DC->drawText(item->textRect.x, item->textRect.y, item->textscale, newColor, (value != 0) ? "Yes" : "No", 0, 0, item->textStyle); +  } +} + +void Item_Multi_Paint(itemDef_t *item) { +  vec4_t newColor; +  const char *text = ""; +  menuDef_t *parent = (menuDef_t*)item->parent; + +  if (item->window.flags & WINDOW_HASFOCUS) { +/*    lowLight[0] = 0.8 * parent->focusColor[0]; +    lowLight[1] = 0.8 * parent->focusColor[1]; +    lowLight[2] = 0.8 * parent->focusColor[2]; +    lowLight[3] = 0.8 * parent->focusColor[3]; +    LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/ +    //TA: +    memcpy(newColor, &parent->focusColor, sizeof(vec4_t)); +  } else { +    memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t)); +  } + +  text = Item_Multi_Setting(item); + +  if (item->text) { +    Item_Text_Paint(item); +    DC->drawText(item->textRect.x + item->textRect.w + 8, item->textRect.y, item->textscale, newColor, text, 0, 0, item->textStyle); +  } else { +    DC->drawText(item->textRect.x, item->textRect.y, item->textscale, newColor, text, 0, 0, item->textStyle); +  } +} + + +typedef struct { +  char  *command; +  int   id; +  int   defaultbind1; +  int   defaultbind2; +  int   bind1; +  int   bind2; +} bind_t; + +typedef struct +{ +  char* name; +  float defaultvalue; +  float value; +} configcvar_t; + + +static bind_t g_bindings[] = +{ +  { "+scores",      K_TAB,         -1, -1, -1 }, +  { "+button2",     K_ENTER,       -1, -1, -1 }, +  { "+speed",       K_SHIFT,       -1, -1, -1 }, +  { "boost",        'x',           -1, -1, -1 }, //TA: human sprinting +  { "+forward",     K_UPARROW,     -1, -1, -1 }, +  { "+back",        K_DOWNARROW,   -1, -1, -1 }, +  { "+moveleft",    ',',           -1, -1, -1 }, +  { "+moveright",   '.',           -1, -1, -1 }, +  { "+moveup",      K_SPACE,       -1, -1, -1 }, +  { "+movedown",    'c',           -1, -1, -1 }, +  { "+left",        K_LEFTARROW,   -1, -1, -1 }, +  { "+right",       K_RIGHTARROW,  -1, -1, -1 }, +  { "+strafe",      K_ALT,         -1, -1, -1 }, +  { "+lookup",      K_PGDN,        -1, -1, -1 }, +  { "+lookdown",    K_DEL,         -1, -1, -1 }, +  { "+mlook",       '/',           -1, -1, -1 }, +  { "centerview",   K_END,         -1, -1, -1 }, +  { "+zoom",        -1,            -1, -1, -1 }, +  { "weapon 1",     '1',           -1, -1, -1 }, +  { "weapon 2",     '2',           -1, -1, -1 }, +  { "weapon 3",     '3',           -1, -1, -1 }, +  { "weapon 4",     '4',           -1, -1, -1 }, +  { "weapon 5",     '5',           -1, -1, -1 }, +  { "weapon 6",     '6',           -1, -1, -1 }, +  { "weapon 7",     '7',           -1, -1, -1 }, +  { "weapon 8",     '8',           -1, -1, -1 }, +  { "weapon 9",     '9',           -1, -1, -1 }, +  { "weapon 10",    '0',           -1, -1, -1 }, +  { "weapon 11",    -1,            -1, -1, -1 }, +  { "weapon 12",    -1,            -1, -1, -1 }, +  { "weapon 13",    -1,            -1, -1, -1 }, +  { "+attack",      K_MOUSE1,      -1, -1, -1 }, +  { "+button5",     K_MOUSE2,      -1, -1, -1 }, //TA: secondary attack +  { "reload",       'r',           -1, -1, -1 }, //TA: reload +  { "buy ammo",     'b',           -1, -1, -1 }, //TA: buy ammo +  { "itemact medkit", 'm',         -1, -1, -1 }, //TA: use medkit +  { "+button7",     'q',           -1, -1, -1 }, //TA: buildable use +  { "deconstruct",  'e',           -1, -1, -1 }, //TA: buildable destroy +  { "weapprev",     '[',           -1, -1, -1 }, +  { "weapnext",     ']',           -1, -1, -1 }, +  { "+button3",     K_MOUSE3,      -1, -1, -1 }, +  { "+button4",     K_MOUSE4,      -1, -1, -1 }, +  { "vote yes",     K_F1,          -1, -1, -1 }, +  { "vote no",      K_F2,          -1, -1, -1 }, +  { "teamvote yes", K_F3,          -1, -1, -1 }, +  { "teamvote no",  K_F4,          -1, -1, -1 }, +  { "scoresUp",      K_KP_PGUP,    -1, -1, -1 }, +  { "scoresDown",    K_KP_PGDN,    -1, -1, -1 }, +  // bk001205 - this one below was:  '-1' +  { "messagemode",  -1,            -1, -1, -1 }, +  { "messagemode2", -1,            -1, -1, -1 }, +  { "messagemode3", -1,            -1, -1, -1 }, +  { "messagemode4", -1,            -1, -1, -1 } +}; + + +static const int g_bindCount = sizeof(g_bindings) / sizeof(bind_t); + +/* +================= +Controls_GetKeyAssignment +================= +*/ +static void Controls_GetKeyAssignment (char *command, int *twokeys) +{ +  int   count; +  int   j; +  char  b[256]; + +  twokeys[0] = twokeys[1] = -1; +  count = 0; + +  for ( j = 0; j < 256; j++ ) +  { +    DC->getBindingBuf( j, b, 256 ); +    if ( *b == 0 ) { +      continue; +    } +    if ( !Q_stricmp( b, command ) ) { +      twokeys[count] = j; +      count++; +      if (count == 2) { +        break; +      } +    } +  } +} + +/* +================= +Controls_GetConfig +================= +*/ +void Controls_GetConfig( void ) +{ +  int   i; +  int   twokeys[ 2 ]; + +  // iterate each command, get its numeric binding +  for( i = 0; i < g_bindCount; i++ ) +  { +    Controls_GetKeyAssignment( g_bindings[ i ].command, twokeys ); + +    g_bindings[ i ].bind1 = twokeys[ 0 ]; +    g_bindings[ i ].bind2 = twokeys[ 1 ]; +  } + +  //s_controls.invertmouse.curvalue  = DC->getCVarValue( "m_pitch" ) < 0; +  //s_controls.smoothmouse.curvalue  = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "m_filter" ) ); +  //s_controls.alwaysrun.curvalue    = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "cl_run" ) ); +  //s_controls.autoswitch.curvalue   = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "cg_autoswitch" ) ); +  //s_controls.sensitivity.curvalue  = UI_ClampCvar( 2, 30, Controls_GetCvarValue( "sensitivity" ) ); +  //s_controls.joyenable.curvalue    = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "in_joystick" ) ); +  //s_controls.joythreshold.curvalue = UI_ClampCvar( 0.05, 0.75, Controls_GetCvarValue( "joy_threshold" ) ); +  //s_controls.freelook.curvalue     = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "cl_freelook" ) ); +} + +/* +================= +Controls_SetConfig +================= +*/ +void Controls_SetConfig(qboolean restart) +{ +  int   i; + +  // iterate each command, get its numeric binding +  for (i=0; i < g_bindCount; i++) +  { + +    if (g_bindings[i].bind1 != -1) +    { +      DC->setBinding( g_bindings[i].bind1, g_bindings[i].command ); + +      if (g_bindings[i].bind2 != -1) +        DC->setBinding( g_bindings[i].bind2, g_bindings[i].command ); +    } +  } + +  //if ( s_controls.invertmouse.curvalue ) +  //  DC->setCVar("m_pitch", va("%f),-fabs( DC->getCVarValue( "m_pitch" ) ) ); +  //else +  //  trap_Cvar_SetValue( "m_pitch", fabs( trap_Cvar_VariableValue( "m_pitch" ) ) ); + +  //trap_Cvar_SetValue( "m_filter", s_controls.smoothmouse.curvalue ); +  //trap_Cvar_SetValue( "cl_run", s_controls.alwaysrun.curvalue ); +  //trap_Cvar_SetValue( "cg_autoswitch", s_controls.autoswitch.curvalue ); +  //trap_Cvar_SetValue( "sensitivity", s_controls.sensitivity.curvalue ); +  //trap_Cvar_SetValue( "in_joystick", s_controls.joyenable.curvalue ); +  //trap_Cvar_SetValue( "joy_threshold", s_controls.joythreshold.curvalue ); +  //trap_Cvar_SetValue( "cl_freelook", s_controls.freelook.curvalue ); +  DC->executeText(EXEC_APPEND, "in_restart\n"); +  //trap_Cmd_ExecuteText( EXEC_APPEND, "in_restart\n" ); +} + +/* +================= +Controls_SetDefaults +================= +*/ +void Controls_SetDefaults( void ) +{ +  int i; + +  // iterate each command, set its default binding +  for (i=0; i < g_bindCount; i++) +  { +    g_bindings[i].bind1 = g_bindings[i].defaultbind1; +    g_bindings[i].bind2 = g_bindings[i].defaultbind2; +  } + +  //s_controls.invertmouse.curvalue  = Controls_GetCvarDefault( "m_pitch" ) < 0; +  //s_controls.smoothmouse.curvalue  = Controls_GetCvarDefault( "m_filter" ); +  //s_controls.alwaysrun.curvalue    = Controls_GetCvarDefault( "cl_run" ); +  //s_controls.autoswitch.curvalue   = Controls_GetCvarDefault( "cg_autoswitch" ); +  //s_controls.sensitivity.curvalue  = Controls_GetCvarDefault( "sensitivity" ); +  //s_controls.joyenable.curvalue    = Controls_GetCvarDefault( "in_joystick" ); +  //s_controls.joythreshold.curvalue = Controls_GetCvarDefault( "joy_threshold" ); +  //s_controls.freelook.curvalue     = Controls_GetCvarDefault( "cl_freelook" ); +} + +int BindingIDFromName(const char *name) { +  int i; +  for (i=0; i < g_bindCount; i++) +  { +    if (Q_stricmp(name, g_bindings[i].command) == 0) { +      return i; +    } +  } +  return -1; +} + +char g_nameBind1[32]; +char g_nameBind2[32]; + +void BindingFromName(const char *cvar) { +  int i, b1, b2; + +  // iterate each command, set its default binding +  for (i=0; i < g_bindCount; i++) +  { +    if (Q_stricmp(cvar, g_bindings[i].command) == 0) { +      b1 = g_bindings[i].bind1; +      if (b1 == -1) { +        break; +      } +        DC->keynumToStringBuf( b1, g_nameBind1, 32 ); +        Q_strupr(g_nameBind1); + +        b2 = g_bindings[i].bind2; +        if (b2 != -1) +        { +          DC->keynumToStringBuf( b2, g_nameBind2, 32 ); +          Q_strupr(g_nameBind2); +          strcat( g_nameBind1, " or " ); +          strcat( g_nameBind1, g_nameBind2 ); +        } +      return; +    } +  } +  strcpy(g_nameBind1, "???"); +} + +void Item_Slider_Paint(itemDef_t *item) { +  vec4_t newColor; +  float x, y, value; +  menuDef_t *parent = (menuDef_t*)item->parent; + +  value = (item->cvar) ? DC->getCVarValue(item->cvar) : 0; + +  if (item->window.flags & WINDOW_HASFOCUS) { +/*    lowLight[0] = 0.8 * parent->focusColor[0]; +    lowLight[1] = 0.8 * parent->focusColor[1]; +    lowLight[2] = 0.8 * parent->focusColor[2]; +    lowLight[3] = 0.8 * parent->focusColor[3]; +    LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/ +    //TA: +    memcpy(newColor, &parent->focusColor, sizeof(vec4_t)); +  } else { +    memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t)); +  } + +  y = item->window.rect.y; +  if (item->text) { +    Item_Text_Paint(item); +    x = item->textRect.x + item->textRect.w + 8; +  } else { +    x = item->window.rect.x; +  } +  DC->setColor(newColor); +  DC->drawHandlePic( x, y, SLIDER_WIDTH, SLIDER_HEIGHT, DC->Assets.sliderBar ); + +  x = Item_Slider_ThumbPosition(item); +  DC->drawHandlePic( x - (SLIDER_THUMB_WIDTH / 2), y - 2, SLIDER_THUMB_WIDTH, SLIDER_THUMB_HEIGHT, DC->Assets.sliderThumb ); + +} + +void Item_Bind_Paint(itemDef_t *item) { +  vec4_t newColor, lowLight; +  float value; +  int maxChars = 0; +  menuDef_t *parent = (menuDef_t*)item->parent; +  editFieldDef_t *editPtr = (editFieldDef_t*)item->typeData; +  if (editPtr) { +    maxChars = editPtr->maxPaintChars; +  } + +  value = (item->cvar) ? DC->getCVarValue(item->cvar) : 0; + +  if (item->window.flags & WINDOW_HASFOCUS) { +    if (g_bindItem == item) { +      lowLight[0] = 0.8f * 1.0f; +      lowLight[1] = 0.8f * 0.0f; +      lowLight[2] = 0.8f * 0.0f; +      lowLight[3] = 0.8f * 1.0f; +    } else { +      lowLight[0] = 0.8f * parent->focusColor[0]; +      lowLight[1] = 0.8f * parent->focusColor[1]; +      lowLight[2] = 0.8f * parent->focusColor[2]; +      lowLight[3] = 0.8f * parent->focusColor[3]; +    } +    /*LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/ +    //TA: +    memcpy(newColor, &parent->focusColor, sizeof(vec4_t)); +  } else { +    memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t)); +  } + +  if (item->text) { +    Item_Text_Paint(item); +    BindingFromName(item->cvar); +    DC->drawText(item->textRect.x + item->textRect.w + 8, item->textRect.y, item->textscale, newColor, g_nameBind1, 0, maxChars, item->textStyle); +  } else { +    DC->drawText(item->textRect.x, item->textRect.y, item->textscale, newColor, (value != 0) ? "FIXME" : "FIXME", 0, maxChars, item->textStyle); +  } +} + +qboolean Display_KeyBindPending( void ) { +  return g_waitingForKey; +} + +qboolean Item_Bind_HandleKey(itemDef_t *item, int key, qboolean down) { +  int     id; +  int     i; + +  if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && !g_waitingForKey) +  { +    if (down && (key == K_MOUSE1 || key == K_ENTER)) { +      g_waitingForKey = qtrue; +      g_bindItem = item; +    } +    return qtrue; +  } +  else +  { +    if (!g_waitingForKey || g_bindItem == NULL) { +      return qtrue; +    } + +    if (key & K_CHAR_FLAG) { +      return qtrue; +    } + +    switch (key) +    { +      case K_ESCAPE: +        g_waitingForKey = qfalse; +        return qtrue; + +      case K_BACKSPACE: +        id = BindingIDFromName(item->cvar); +        if (id != -1) { +          g_bindings[id].bind1 = -1; +          g_bindings[id].bind2 = -1; +        } +        Controls_SetConfig(qtrue); +        g_waitingForKey = qfalse; +        g_bindItem = NULL; +        return qtrue; + +      case '`': +        return qtrue; +    } +  } + +  if (key != -1) +  { + +    for (i=0; i < g_bindCount; i++) +    { + +      if (g_bindings[i].bind2 == key) { +        g_bindings[i].bind2 = -1; +      } + +      if (g_bindings[i].bind1 == key) +      { +        g_bindings[i].bind1 = g_bindings[i].bind2; +        g_bindings[i].bind2 = -1; +      } +    } +  } + + +  id = BindingIDFromName(item->cvar); + +  if (id != -1) { +    if (key == -1) { +      if( g_bindings[id].bind1 != -1 ) { +        DC->setBinding( g_bindings[id].bind1, "" ); +        g_bindings[id].bind1 = -1; +      } +      if( g_bindings[id].bind2 != -1 ) { +        DC->setBinding( g_bindings[id].bind2, "" ); +        g_bindings[id].bind2 = -1; +      } +    } +    else if (g_bindings[id].bind1 == -1) { +      g_bindings[id].bind1 = key; +    } +    else if (g_bindings[id].bind1 != key && g_bindings[id].bind2 == -1) { +      g_bindings[id].bind2 = key; +    } +    else { +      DC->setBinding( g_bindings[id].bind1, "" ); +      DC->setBinding( g_bindings[id].bind2, "" ); +      g_bindings[id].bind1 = key; +      g_bindings[id].bind2 = -1; +    } +  } + +  Controls_SetConfig(qtrue); +  g_waitingForKey = qfalse; + +  return qtrue; +} + + + +void AdjustFrom640(float *x, float *y, float *w, float *h) { +  //*x = *x * DC->scale + DC->bias; +  *x *= DC->xscale; +  *y *= DC->yscale; +  *w *= DC->xscale; +  *h *= DC->yscale; +} + +void Item_Model_Paint(itemDef_t *item) { +  float x, y, w, h; +  refdef_t refdef; +  refEntity_t   ent; +  vec3_t      mins, maxs, origin; +  vec3_t      angles; +  modelDef_t *modelPtr = (modelDef_t*)item->typeData; + +  if (modelPtr == NULL) { +    return; +  } + +  // setup the refdef +  memset( &refdef, 0, sizeof( refdef ) ); +  refdef.rdflags = RDF_NOWORLDMODEL; +  AxisClear( refdef.viewaxis ); +  x = item->window.rect.x+1; +  y = item->window.rect.y+1; +  w = item->window.rect.w-2; +  h = item->window.rect.h-2; + +  AdjustFrom640( &x, &y, &w, &h ); + +  refdef.x = x; +  refdef.y = y; +  refdef.width = w; +  refdef.height = h; + +  DC->modelBounds( item->asset, mins, maxs ); + +  origin[2] = -0.5 * ( mins[2] + maxs[2] ); +  origin[1] = 0.5 * ( mins[1] + maxs[1] ); + +  // calculate distance so the model nearly fills the box +  if (qtrue) { +    float len = 0.5 * ( maxs[2] - mins[2] ); +    origin[0] = len / 0.268;  // len / tan( fov/2 ) +    //origin[0] = len / tan(w/2); +  } else { +    origin[0] = item->textscale; +  } +  refdef.fov_x = (modelPtr->fov_x) ? modelPtr->fov_x : w; +  refdef.fov_y = (modelPtr->fov_y) ? modelPtr->fov_y : h; + +  //refdef.fov_x = (int)((float)refdef.width / 640.0f * 90.0f); +  //xx = refdef.width / tan( refdef.fov_x / 360 * M_PI ); +  //refdef.fov_y = atan2( refdef.height, xx ); +  //refdef.fov_y *= ( 360 / M_PI ); + +  DC->clearScene(); + +  refdef.time = DC->realTime; + +  // add the model + +  memset( &ent, 0, sizeof(ent) ); + +  //adjust = 5.0 * sin( (float)uis.realtime / 500 ); +  //adjust = 360 % (int)((float)uis.realtime / 1000); +  //VectorSet( angles, 0, 0, 1 ); + +  // use item storage to track +  if (modelPtr->rotationSpeed) { +    if (DC->realTime > item->window.nextTime) { +      item->window.nextTime = DC->realTime + modelPtr->rotationSpeed; +      modelPtr->angle = (int)(modelPtr->angle + 1) % 360; +    } +  } +  VectorSet( angles, 0, modelPtr->angle, 0 ); +  AnglesToAxis( angles, ent.axis ); + +  ent.hModel = item->asset; +  VectorCopy( origin, ent.origin ); +  VectorCopy( origin, ent.lightingOrigin ); +  ent.renderfx = RF_LIGHTING_ORIGIN | RF_NOSHADOW; +  VectorCopy( ent.origin, ent.oldorigin ); + +  DC->addRefEntityToScene( &ent ); +  DC->renderScene( &refdef ); + +} + + +void Item_Image_Paint(itemDef_t *item) { +  if (item == NULL) { +    return; +  } +  DC->drawHandlePic(item->window.rect.x+1, item->window.rect.y+1, item->window.rect.w-2, item->window.rect.h-2, item->asset); +} + +void Item_ListBox_Paint(itemDef_t *item) { +  float x, y, size, thumb; +  int i, count; +  qhandle_t image; +  qhandle_t optionalImage; +  listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + +  // the listbox is horizontal or vertical and has a fixed size scroll bar going either direction +  // elements are enumerated from the DC and either text or image handles are acquired from the DC as well +  // textscale is used to size the text, textalignx and textaligny are used to size image elements +  // there is no clipping available so only the last completely visible item is painted +  count = DC->feederCount(item->special); +  // default is vertical if horizontal flag is not here +  if (item->window.flags & WINDOW_HORIZONTAL) { +    // draw scrollbar in bottom of the window +    // bar +    x = item->window.rect.x + 1; +    y = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE - 1; +    DC->drawHandlePic(x, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarArrowLeft); +    x += SCROLLBAR_SIZE - 1; +    size = item->window.rect.w - (SCROLLBAR_SIZE * 2); +    DC->drawHandlePic(x, y, size+1, SCROLLBAR_SIZE, DC->Assets.scrollBar); +    x += size - 1; +    DC->drawHandlePic(x, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarArrowRight); +    // thumb +    thumb = Item_ListBox_ThumbDrawPosition(item);//Item_ListBox_ThumbPosition(item); +    if (thumb > x - SCROLLBAR_SIZE - 1) { +      thumb = x - SCROLLBAR_SIZE - 1; +    } +    DC->drawHandlePic(thumb, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarThumb); +    // +    listPtr->endPos = listPtr->startPos; +    size = item->window.rect.w - 2; +    // items +    // size contains max available space +    if (listPtr->elementStyle == LISTBOX_IMAGE) { +      // fit = 0; +      x = item->window.rect.x + 1; +      y = item->window.rect.y + 1; +      for (i = listPtr->startPos; i < count; i++) { +        // always draw at least one +        // which may overdraw the box if it is too small for the element +        image = DC->feederItemImage(item->special, i); +        if (image) { +          DC->drawHandlePic(x+1, y+1, listPtr->elementWidth - 2, listPtr->elementHeight - 2, image); +        } + +        if (i == item->cursorPos) { +          DC->drawRect(x, y, listPtr->elementWidth-1, listPtr->elementHeight-1, item->window.borderSize, item->window.borderColor); +        } + +        listPtr->endPos++; +        size -= listPtr->elementWidth; +        if (size < listPtr->elementWidth) { +          listPtr->drawPadding = size; //listPtr->elementWidth - size; +          break; +        } +        x += listPtr->elementWidth; +        // fit++; +      } +    } else { +      // +    } +  } else { +    // draw scrollbar to right side of the window +    x = item->window.rect.x + item->window.rect.w - SCROLLBAR_SIZE - 1; +    y = item->window.rect.y + 1; +    DC->drawHandlePic(x, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarArrowUp); +    y += SCROLLBAR_SIZE - 1; + +    listPtr->endPos = listPtr->startPos; +    size = item->window.rect.h - (SCROLLBAR_SIZE * 2); +    DC->drawHandlePic(x, y, SCROLLBAR_SIZE, size+1, DC->Assets.scrollBar); +    y += size - 1; +    DC->drawHandlePic(x, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarArrowDown); +    // thumb +    thumb = Item_ListBox_ThumbDrawPosition(item);//Item_ListBox_ThumbPosition(item); +    if (thumb > y - SCROLLBAR_SIZE - 1) { +      thumb = y - SCROLLBAR_SIZE - 1; +    } +    DC->drawHandlePic(x, thumb, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarThumb); + +    // adjust size for item painting +    size = item->window.rect.h - 2; +    if (listPtr->elementStyle == LISTBOX_IMAGE) { +      // fit = 0; +      x = item->window.rect.x + 1; +      y = item->window.rect.y + 1; +      for (i = listPtr->startPos; i < count; i++) { +        // always draw at least one +        // which may overdraw the box if it is too small for the element +        image = DC->feederItemImage(item->special, i); +        if (image) { +          DC->drawHandlePic(x+1, y+1, listPtr->elementWidth - 2, listPtr->elementHeight - 2, image); +        } + +        if (i == item->cursorPos) { +          DC->drawRect(x, y, listPtr->elementWidth - 1, listPtr->elementHeight - 1, item->window.borderSize, item->window.borderColor); +        } + +        listPtr->endPos++; +        size -= listPtr->elementWidth; +        if (size < listPtr->elementHeight) { +          listPtr->drawPadding = listPtr->elementHeight - size; +          break; +        } +        y += listPtr->elementHeight; +        // fit++; +      } +    } else { +      x = item->window.rect.x + 1; +      y = item->window.rect.y + 1; +      for (i = listPtr->startPos; i < count; i++) { +        const char *text; +        // always draw at least one +        // which may overdraw the box if it is too small for the element + +        if (listPtr->numColumns > 0) { +          int j; +          for (j = 0; j < listPtr->numColumns; j++) { +            text = DC->feederItemText(item->special, i, j, &optionalImage); +            if (optionalImage >= 0) { +              DC->drawHandlePic(x + 4 + listPtr->columnInfo[j].pos, y - 1 + listPtr->elementHeight / 2, listPtr->columnInfo[j].width, listPtr->columnInfo[j].width, optionalImage); +            } else if (text) { +              //TA: +              int alignOffset = 0.0f, tw; + +              tw = DC->textWidth( text, item->textscale, 0 ); + +              switch( listPtr->columnInfo[ j ].align ) +              { +                case ITEM_ALIGN_LEFT: +                  alignOffset = 0.0f; +                  break; + +                case ITEM_ALIGN_RIGHT: +                  alignOffset = listPtr->columnInfo[ j ].width - tw; +                  break; + +                case ITEM_ALIGN_CENTER: +                  alignOffset = ( listPtr->columnInfo[ j ].width / 2.0f ) - ( tw / 2.0f ); +                  break; + +                default: +                  alignOffset = 0.0f; +              } + +              DC->drawText( x + 4 + listPtr->columnInfo[j].pos + alignOffset, y + listPtr->elementHeight, +                            item->textscale, item->window.foreColor, text, 0, +                            listPtr->columnInfo[j].maxChars, item->textStyle ); +            } +          } +        } else { +          text = DC->feederItemText(item->special, i, 0, &optionalImage); +          if (optionalImage >= 0) { +            //DC->drawHandlePic(x + 4 + listPtr->elementHeight, y, listPtr->columnInfo[j].width, listPtr->columnInfo[j].width, optionalImage); +          } else if (text) { +            DC->drawText(x + 4, y + listPtr->elementHeight, item->textscale, item->window.foreColor, text, 0, 0, item->textStyle); +          } +        } + +        if (i == item->cursorPos) { +          DC->fillRect(x + 2, y + 2, item->window.rect.w - SCROLLBAR_SIZE - 4, listPtr->elementHeight, item->window.outlineColor); +        } + +        listPtr->endPos++; +        size -= listPtr->elementHeight; +        if (size < listPtr->elementHeight) { +          listPtr->drawPadding = listPtr->elementHeight - size; +          break; +        } +        y += listPtr->elementHeight; +        // fit++; +      } +    } +  } + +  //TA: FIXME: hacky fix to off-by-one bug +  listPtr->endPos--; +} + + +void Item_OwnerDraw_Paint(itemDef_t *item) { +  menuDef_t *parent; + +  if (item == NULL) { +    return; +  } +  parent = (menuDef_t*)item->parent; + +  if (DC->ownerDrawItem) { +    vec4_t color, lowLight; +    menuDef_t *parent = (menuDef_t*)item->parent; +    Fade(&item->window.flags, &item->window.foreColor[3], parent->fadeClamp, &item->window.nextTime, parent->fadeCycle, qtrue, parent->fadeAmount); +    memcpy(&color, &item->window.foreColor, sizeof(color)); +    if (item->numColors > 0 && DC->getValue) { +      // if the value is within one of the ranges then set color to that, otherwise leave at default +      int i; +      float f = DC->getValue(item->window.ownerDraw); +      for (i = 0; i < item->numColors; i++) { +        if (f >= item->colorRanges[i].low && f <= item->colorRanges[i].high) { +          memcpy(&color, &item->colorRanges[i].color, sizeof(color)); +          break; +        } +      } +    } + +    if (item->window.flags & WINDOW_HASFOCUS) { +/*      lowLight[0] = 0.8 * parent->focusColor[0]; +      lowLight[1] = 0.8 * parent->focusColor[1]; +      lowLight[2] = 0.8 * parent->focusColor[2]; +      lowLight[3] = 0.8 * parent->focusColor[3]; +      LerpColor(parent->focusColor,lowLight,color,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/ +      //TA: +      memcpy(color, &parent->focusColor, sizeof(vec4_t)); +    } else if (item->textStyle == ITEM_TEXTSTYLE_BLINK && !((DC->realTime/BLINK_DIVISOR) & 1)) { +      lowLight[0] = 0.8 * item->window.foreColor[0]; +      lowLight[1] = 0.8 * item->window.foreColor[1]; +      lowLight[2] = 0.8 * item->window.foreColor[2]; +      lowLight[3] = 0.8 * item->window.foreColor[3]; +      LerpColor(item->window.foreColor,lowLight,color,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR)); +    } + +    if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) { +      memcpy(color, parent->disableColor, sizeof(vec4_t)); // bk001207 - FIXME: Com_Memcpy +    } + +    if (item->text) { +      Item_Text_Paint(item); +        if (item->text[0]) { +          // +8 is an offset kludge to properly align owner draw items that have text combined with them +          DC->ownerDrawItem(item->textRect.x + item->textRect.w + 8, item->window.rect.y, item->window.rect.w, item->window.rect.h, 0, item->textaligny, item->window.ownerDraw, item->window.ownerDrawFlags, item->alignment, item->special, item->textscale, color, item->window.background, item->textStyle ); +        } else { +          DC->ownerDrawItem(item->textRect.x + item->textRect.w, item->window.rect.y, item->window.rect.w, item->window.rect.h, 0, item->textaligny, item->window.ownerDraw, item->window.ownerDrawFlags, item->alignment, item->special, item->textscale, color, item->window.background, item->textStyle ); +        } +      } else { +      DC->ownerDrawItem(item->window.rect.x, item->window.rect.y, item->window.rect.w, item->window.rect.h, item->textalignx, item->textaligny, item->window.ownerDraw, item->window.ownerDrawFlags, item->alignment, item->special, item->textscale, color, item->window.background, item->textStyle ); +    } +  } +} + + +void Item_Paint(itemDef_t *item) { +  vec4_t red; +  menuDef_t *parent = (menuDef_t*)item->parent; +  red[0] = red[3] = 1; +  red[1] = red[2] = 0; + +  if (item == NULL) { +    return; +  } + +  if (item->window.flags & WINDOW_ORBITING) { +    if (DC->realTime > item->window.nextTime) { +      float rx, ry, a, c, s, w, h; + +      item->window.nextTime = DC->realTime + item->window.offsetTime; +      // translate +      w = item->window.rectClient.w / 2; +      h = item->window.rectClient.h / 2; +      rx = item->window.rectClient.x + w - item->window.rectEffects.x; +      ry = item->window.rectClient.y + h - item->window.rectEffects.y; +      a = 3 * M_PI / 180; +      c = cos(a); +      s = sin(a); +      item->window.rectClient.x = (rx * c - ry * s) + item->window.rectEffects.x - w; +      item->window.rectClient.y = (rx * s + ry * c) + item->window.rectEffects.y - h; +      Item_UpdatePosition(item); + +    } +  } + + +  if (item->window.flags & WINDOW_INTRANSITION) { +    if (DC->realTime > item->window.nextTime) { +      int done = 0; +      item->window.nextTime = DC->realTime + item->window.offsetTime; +      // transition the x,y +      if (item->window.rectClient.x == item->window.rectEffects.x) { +        done++; +      } else { +        if (item->window.rectClient.x < item->window.rectEffects.x) { +          item->window.rectClient.x += item->window.rectEffects2.x; +          if (item->window.rectClient.x > item->window.rectEffects.x) { +            item->window.rectClient.x = item->window.rectEffects.x; +            done++; +          } +        } else { +          item->window.rectClient.x -= item->window.rectEffects2.x; +          if (item->window.rectClient.x < item->window.rectEffects.x) { +            item->window.rectClient.x = item->window.rectEffects.x; +            done++; +          } +        } +      } +      if (item->window.rectClient.y == item->window.rectEffects.y) { +        done++; +      } else { +        if (item->window.rectClient.y < item->window.rectEffects.y) { +          item->window.rectClient.y += item->window.rectEffects2.y; +          if (item->window.rectClient.y > item->window.rectEffects.y) { +            item->window.rectClient.y = item->window.rectEffects.y; +            done++; +          } +        } else { +          item->window.rectClient.y -= item->window.rectEffects2.y; +          if (item->window.rectClient.y < item->window.rectEffects.y) { +            item->window.rectClient.y = item->window.rectEffects.y; +            done++; +          } +        } +      } +      if (item->window.rectClient.w == item->window.rectEffects.w) { +        done++; +      } else { +        if (item->window.rectClient.w < item->window.rectEffects.w) { +          item->window.rectClient.w += item->window.rectEffects2.w; +          if (item->window.rectClient.w > item->window.rectEffects.w) { +            item->window.rectClient.w = item->window.rectEffects.w; +            done++; +          } +        } else { +          item->window.rectClient.w -= item->window.rectEffects2.w; +          if (item->window.rectClient.w < item->window.rectEffects.w) { +            item->window.rectClient.w = item->window.rectEffects.w; +            done++; +          } +        } +      } +      if (item->window.rectClient.h == item->window.rectEffects.h) { +        done++; +      } else { +        if (item->window.rectClient.h < item->window.rectEffects.h) { +          item->window.rectClient.h += item->window.rectEffects2.h; +          if (item->window.rectClient.h > item->window.rectEffects.h) { +            item->window.rectClient.h = item->window.rectEffects.h; +            done++; +          } +        } else { +          item->window.rectClient.h -= item->window.rectEffects2.h; +          if (item->window.rectClient.h < item->window.rectEffects.h) { +            item->window.rectClient.h = item->window.rectEffects.h; +            done++; +          } +        } +      } + +      Item_UpdatePosition(item); + +      if (done == 4) { +        item->window.flags &= ~WINDOW_INTRANSITION; +      } + +    } +  } + +  if (item->window.ownerDrawFlags && DC->ownerDrawVisible) { +    if (!DC->ownerDrawVisible(item->window.ownerDrawFlags)) { +      item->window.flags &= ~WINDOW_VISIBLE; +    } else { +      item->window.flags |= WINDOW_VISIBLE; +    } +  } + +  if (item->cvarFlags & (CVAR_SHOW | CVAR_HIDE)) { +    if (!Item_EnableShowViaCvar(item, CVAR_SHOW)) { +      return; +    } +  } + +  if (item->window.flags & WINDOW_TIMEDVISIBLE) { + +  } + +  if (!(item->window.flags & WINDOW_VISIBLE)) { +    return; +  } + +  // paint the rect first.. +  Window_Paint(&item->window, parent->fadeAmount , parent->fadeClamp, parent->fadeCycle); + +  if (debugMode) { +    vec4_t color; +    rectDef_t *r = Item_CorrectedTextRect(item); +    color[1] = color[3] = 1; +    color[0] = color[2] = 0; +    DC->drawRect(r->x, r->y, r->w, r->h, 1, color); +  } + +  //DC->drawRect(item->window.rect.x, item->window.rect.y, item->window.rect.w, item->window.rect.h, 1, red); + +  switch (item->type) { +    case ITEM_TYPE_OWNERDRAW: +      Item_OwnerDraw_Paint(item); +      break; +    case ITEM_TYPE_TEXT: +    case ITEM_TYPE_BUTTON: +      Item_Text_Paint(item); +      break; +    case ITEM_TYPE_RADIOBUTTON: +      break; +    case ITEM_TYPE_CHECKBOX: +      break; +    case ITEM_TYPE_EDITFIELD: +    case ITEM_TYPE_NUMERICFIELD: +      Item_TextField_Paint(item); +      break; +    case ITEM_TYPE_COMBO: +      break; +    case ITEM_TYPE_LISTBOX: +      Item_ListBox_Paint(item); +      break; +    //case ITEM_TYPE_IMAGE: +    //  Item_Image_Paint(item); +    //  break; +    case ITEM_TYPE_MODEL: +      Item_Model_Paint(item); +      break; +    case ITEM_TYPE_YESNO: +      Item_YesNo_Paint(item); +      break; +    case ITEM_TYPE_MULTI: +      Item_Multi_Paint(item); +      break; +    case ITEM_TYPE_BIND: +      Item_Bind_Paint(item); +      break; +    case ITEM_TYPE_SLIDER: +      Item_Slider_Paint(item); +      break; +    default: +      break; +  } + +} + +void Menu_Init(menuDef_t *menu) { +  memset(menu, 0, sizeof(menuDef_t)); +  menu->cursorItem = -1; +  menu->fadeAmount = DC->Assets.fadeAmount; +  menu->fadeClamp = DC->Assets.fadeClamp; +  menu->fadeCycle = DC->Assets.fadeCycle; +  Window_Init(&menu->window); +} + +itemDef_t *Menu_GetFocusedItem(menuDef_t *menu) { +  int i; +  if (menu) { +    for (i = 0; i < menu->itemCount; i++) { +      if (menu->items[i]->window.flags & WINDOW_HASFOCUS) { +        return menu->items[i]; +      } +    } +  } +  return NULL; +} + +menuDef_t *Menu_GetFocused( void ) { +  int i; +  for (i = 0; i < menuCount; i++) { +    if (Menus[i].window.flags & WINDOW_HASFOCUS && Menus[i].window.flags & WINDOW_VISIBLE) { +      return &Menus[i]; +    } +  } +  return NULL; +} + +void Menu_ScrollFeeder(menuDef_t *menu, int feeder, qboolean down) { +  if (menu) { +    int i; +    for (i = 0; i < menu->itemCount; i++) { +      if (menu->items[i]->special == feeder) { +        Item_ListBox_HandleKey(menu->items[i], (down) ? K_DOWNARROW : K_UPARROW, qtrue, qtrue); +        return; +      } +    } +  } +} + + + +void Menu_SetFeederSelection(menuDef_t *menu, int feeder, int index, const char *name) { +  if (menu == NULL) { +    if (name == NULL) { +      menu = Menu_GetFocused(); +    } else { +      menu = Menus_FindByName(name); +    } +  } + +  if (menu) { +    int i; +    for (i = 0; i < menu->itemCount; i++) { +      if (menu->items[i]->special == feeder) { +        if (index == 0) { +          listBoxDef_t *listPtr = (listBoxDef_t*)menu->items[i]->typeData; +          listPtr->cursorPos = 0; +          listPtr->startPos = 0; +        } +        menu->items[i]->cursorPos = index; +        DC->feederSelection(menu->items[i]->special, menu->items[i]->cursorPos); +        return; +      } +    } +  } +} + +qboolean Menus_AnyFullScreenVisible( void ) { +  int i; +  for (i = 0; i < menuCount; i++) { +    if (Menus[i].window.flags & WINDOW_VISIBLE && Menus[i].fullScreen) { +      return qtrue; +    } +  } +  return qfalse; +} + +menuDef_t *Menus_ActivateByName(const char *p) { +  int i, j; +  menuDef_t *m = NULL; +  menuDef_t *focus = Menu_GetFocused(); + +  for (i = 0; i < menuCount; i++) { +    if (Q_stricmp(Menus[i].window.name, p) == 0) { +      m = &Menus[i]; +      Menus_Activate(m); +      Menu_HandleMouseMove( m, DC->cursorx, DC->cursory ); //TA: force the item under the cursor to focus + +      for( j = 0; j < m->itemCount; j++ ) //TA: reset selection in listboxes when opened +      { +        if( m->items[ j ]->type == ITEM_TYPE_LISTBOX ) +        { +          listBoxDef_t *listPtr = (listBoxDef_t*)m->items[ j ]->typeData; +          m->items[ j ]->cursorPos = 0; +          listPtr->startPos = 0; +          DC->feederSelection( m->items[ j ]->special, 0 ); +        } +      } + +      if (openMenuCount < MAX_OPEN_MENUS && focus != NULL) { +        menuStack[openMenuCount++] = focus; +      } +    } else { +      Menus[i].window.flags &= ~WINDOW_HASFOCUS; +    } +  } +  Display_CloseCinematics(); +  return m; +} + + +void Item_Init(itemDef_t *item) { +  memset(item, 0, sizeof(itemDef_t)); +  item->textscale = 0.55f; +  Window_Init(&item->window); +} + +void Menu_HandleMouseMove(menuDef_t *menu, float x, float y) { +  int i, pass; +  qboolean focusSet = qfalse; + +  itemDef_t *overItem; +  if (menu == NULL) { +    return; +  } + +  if (!(menu->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED))) { +    return; +  } + +  if (itemCapture) { +    //Item_MouseMove(itemCapture, x, y); +    return; +  } + +  if (g_waitingForKey || g_editingField) { +    return; +  } + +  // FIXME: this is the whole issue of focus vs. mouse over.. +  // need a better overall solution as i don't like going through everything twice +  for (pass = 0; pass < 2; pass++) { +    for (i = 0; i < menu->itemCount; i++) { +      // turn off focus each item +      // menu->items[i].window.flags &= ~WINDOW_HASFOCUS; + +      if (!(menu->items[i]->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED))) { +        continue; +      } + +      // items can be enabled and disabled based on cvars +      if (menu->items[i]->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(menu->items[i], CVAR_ENABLE)) { +        continue; +      } + +      if (menu->items[i]->cvarFlags & (CVAR_SHOW | CVAR_HIDE) && !Item_EnableShowViaCvar(menu->items[i], CVAR_SHOW)) { +        continue; +      } + + + +      if (Rect_ContainsPoint(&menu->items[i]->window.rect, x, y)) { +        if (pass == 1) { +          overItem = menu->items[i]; +          if (overItem->type == ITEM_TYPE_TEXT && overItem->text) { +            if (!Rect_ContainsPoint(Item_CorrectedTextRect(overItem), x, y)) { +              continue; +            } +          } +          // if we are over an item +          if (IsVisible(overItem->window.flags)) { +            // different one +            Item_MouseEnter(overItem, x, y); +            // Item_SetMouseOver(overItem, qtrue); + +            // if item is not a decoration see if it can take focus +            if (!focusSet) { +              focusSet = Item_SetFocus(overItem, x, y); +            } +          } +        } +      } else if (menu->items[i]->window.flags & WINDOW_MOUSEOVER) { +          Item_MouseLeave(menu->items[i]); +          Item_SetMouseOver(menu->items[i], qfalse); +      } +    } +  } + +} + +void Menu_Paint(menuDef_t *menu, qboolean forcePaint) { +  int i; + +  if (menu == NULL) { +    return; +  } + +  if (!(menu->window.flags & WINDOW_VISIBLE) &&  !forcePaint) { +    return; +  } + +  if (menu->window.ownerDrawFlags && DC->ownerDrawVisible && !DC->ownerDrawVisible(menu->window.ownerDrawFlags)) { +    return; +  } + +  if (forcePaint) { +    menu->window.flags |= WINDOW_FORCED; +  } + +  // draw the background if necessary +  if (menu->fullScreen) { +    // implies a background shader +    // FIXME: make sure we have a default shader if fullscreen is set with no background +    DC->drawHandlePic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, menu->window.background ); +  } else if (menu->window.background) { +    // this allows a background shader without being full screen +    //UI_DrawHandlePic(menu->window.rect.x, menu->window.rect.y, menu->window.rect.w, menu->window.rect.h, menu->backgroundShader); +  } + +  // paint the background and or border +  Window_Paint(&menu->window, menu->fadeAmount, menu->fadeClamp, menu->fadeCycle ); + +  for (i = 0; i < menu->itemCount; i++) { +    Item_Paint(menu->items[i]); +  } + +  if (debugMode) { +    vec4_t color; +    color[0] = color[2] = color[3] = 1; +    color[1] = 0; +    DC->drawRect(menu->window.rect.x, menu->window.rect.y, menu->window.rect.w, menu->window.rect.h, 1, color); +  } +} + +/* +=============== +Item_ValidateTypeData +=============== +*/ +void Item_ValidateTypeData(itemDef_t *item) { +  if (item->typeData) { +    return; +  } + +  if (item->type == ITEM_TYPE_LISTBOX) { +    item->typeData = UI_Alloc(sizeof(listBoxDef_t)); +    memset(item->typeData, 0, sizeof(listBoxDef_t)); +  } else if (item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_NUMERICFIELD || item->type == ITEM_TYPE_YESNO || item->type == ITEM_TYPE_BIND || item->type == ITEM_TYPE_SLIDER || item->type == ITEM_TYPE_TEXT) { +    item->typeData = UI_Alloc(sizeof(editFieldDef_t)); +    memset(item->typeData, 0, sizeof(editFieldDef_t)); +    if (item->type == ITEM_TYPE_EDITFIELD) { +      if (!((editFieldDef_t *) item->typeData)->maxPaintChars) { +        ((editFieldDef_t *) item->typeData)->maxPaintChars = MAX_EDITFIELD; +      } +    } +  } else if (item->type == ITEM_TYPE_MULTI) { +    item->typeData = UI_Alloc(sizeof(multiDef_t)); +  } else if (item->type == ITEM_TYPE_MODEL) { +    item->typeData = UI_Alloc(sizeof(modelDef_t)); +  } +} + +/* +=============== +Keyword Hash +=============== +*/ + +#define KEYWORDHASH_SIZE  512 + +typedef struct keywordHash_s +{ +  char *keyword; +  qboolean (*func)(itemDef_t *item, int handle); +  struct keywordHash_s *next; +} keywordHash_t; + +int KeywordHash_Key(char *keyword) { +  int register hash, i; + +  hash = 0; +  for (i = 0; keyword[i] != '\0'; i++) { +    if (keyword[i] >= 'A' && keyword[i] <= 'Z') +      hash += (keyword[i] + ('a' - 'A')) * (119 + i); +    else +      hash += keyword[i] * (119 + i); +  } +  hash = (hash ^ (hash >> 10) ^ (hash >> 20)) & (KEYWORDHASH_SIZE-1); +  return hash; +} + +void KeywordHash_Add(keywordHash_t *table[], keywordHash_t *key) { +  int hash; + +  hash = KeywordHash_Key(key->keyword); +/* +  if (table[hash]) { +    int collision = qtrue; +  } +*/ +  key->next = table[hash]; +  table[hash] = key; +} + +keywordHash_t *KeywordHash_Find(keywordHash_t *table[], char *keyword) +{ +  keywordHash_t *key; +  int hash; + +  hash = KeywordHash_Key(keyword); +  for (key = table[hash]; key; key = key->next) { +    if (!Q_stricmp(key->keyword, keyword)) +      return key; +  } +  return NULL; +} + +/* +=============== +Item Keyword Parse functions +=============== +*/ + +// name <string> +qboolean ItemParse_name( itemDef_t *item, int handle ) { +  if (!PC_String_Parse(handle, &item->window.name)) { +    return qfalse; +  } +  return qtrue; +} + +// name <string> +qboolean ItemParse_focusSound( itemDef_t *item, int handle ) { +  const char *temp; +  if (!PC_String_Parse(handle, &temp)) { +    return qfalse; +  } +  item->focusSound = DC->registerSound(temp, qfalse); +  return qtrue; +} + + +// text <string> +qboolean ItemParse_text( itemDef_t *item, int handle ) { +  if (!PC_String_Parse(handle, &item->text)) { +    return qfalse; +  } +  return qtrue; +} + +// group <string> +qboolean ItemParse_group( itemDef_t *item, int handle ) { +  if (!PC_String_Parse(handle, &item->window.group)) { +    return qfalse; +  } +  return qtrue; +} + +// asset_model <string> +qboolean ItemParse_asset_model( itemDef_t *item, int handle ) { +  const char *temp; +  modelDef_t *modelPtr; +  Item_ValidateTypeData(item); +  modelPtr = (modelDef_t*)item->typeData; + +  if (!PC_String_Parse(handle, &temp)) { +    return qfalse; +  } +  item->asset = DC->registerModel(temp); +  modelPtr->angle = rand() % 360; +  return qtrue; +} + +// asset_shader <string> +qboolean ItemParse_asset_shader( itemDef_t *item, int handle ) { +  const char *temp; + +  if (!PC_String_Parse(handle, &temp)) { +    return qfalse; +  } +  item->asset = DC->registerShaderNoMip(temp); +  return qtrue; +} + +// model_origin <number> <number> <number> +qboolean ItemParse_model_origin( itemDef_t *item, int handle ) { +  modelDef_t *modelPtr; +  Item_ValidateTypeData(item); +  modelPtr = (modelDef_t*)item->typeData; + +  if (PC_Float_Parse(handle, &modelPtr->origin[0])) { +    if (PC_Float_Parse(handle, &modelPtr->origin[1])) { +      if (PC_Float_Parse(handle, &modelPtr->origin[2])) { +        return qtrue; +      } +    } +  } +  return qfalse; +} + +// model_fovx <number> +qboolean ItemParse_model_fovx( itemDef_t *item, int handle ) { +  modelDef_t *modelPtr; +  Item_ValidateTypeData(item); +  modelPtr = (modelDef_t*)item->typeData; + +  if (!PC_Float_Parse(handle, &modelPtr->fov_x)) { +    return qfalse; +  } +  return qtrue; +} + +// model_fovy <number> +qboolean ItemParse_model_fovy( itemDef_t *item, int handle ) { +  modelDef_t *modelPtr; +  Item_ValidateTypeData(item); +  modelPtr = (modelDef_t*)item->typeData; + +  if (!PC_Float_Parse(handle, &modelPtr->fov_y)) { +    return qfalse; +  } +  return qtrue; +} + +// model_rotation <integer> +qboolean ItemParse_model_rotation( itemDef_t *item, int handle ) { +  modelDef_t *modelPtr; +  Item_ValidateTypeData(item); +  modelPtr = (modelDef_t*)item->typeData; + +  if (!PC_Int_Parse(handle, &modelPtr->rotationSpeed)) { +    return qfalse; +  } +  return qtrue; +} + +// model_angle <integer> +qboolean ItemParse_model_angle( itemDef_t *item, int handle ) { +  modelDef_t *modelPtr; +  Item_ValidateTypeData(item); +  modelPtr = (modelDef_t*)item->typeData; + +  if (!PC_Int_Parse(handle, &modelPtr->angle)) { +    return qfalse; +  } +  return qtrue; +} + +// rect <rectangle> +qboolean ItemParse_rect( itemDef_t *item, int handle ) { +  if (!PC_Rect_Parse(handle, &item->window.rectClient)) { +    return qfalse; +  } +  return qtrue; +} + +// style <integer> +qboolean ItemParse_style( itemDef_t *item, int handle ) { +  if (!PC_Int_Parse(handle, &item->window.style)) { +    return qfalse; +  } +  return qtrue; +} + +// decoration +qboolean ItemParse_decoration( itemDef_t *item, int handle ) { +  item->window.flags |= WINDOW_DECORATION; +  return qtrue; +} + +// notselectable +qboolean ItemParse_notselectable( itemDef_t *item, int handle ) { +  listBoxDef_t *listPtr; +  Item_ValidateTypeData(item); +  listPtr = (listBoxDef_t*)item->typeData; +  if (item->type == ITEM_TYPE_LISTBOX && listPtr) { +    listPtr->notselectable = qtrue; +  } +  return qtrue; +} + +// manually wrapped +qboolean ItemParse_wrapped( itemDef_t *item, int handle ) { +  item->window.flags |= WINDOW_WRAPPED; +  return qtrue; +} + +// auto wrapped +qboolean ItemParse_autowrapped( itemDef_t *item, int handle ) { +  item->window.flags |= WINDOW_AUTOWRAPPED; +  return qtrue; +} + + +// horizontalscroll +qboolean ItemParse_horizontalscroll( itemDef_t *item, int handle ) { +  item->window.flags |= WINDOW_HORIZONTAL; +  return qtrue; +} + +// type <integer> +qboolean ItemParse_type( itemDef_t *item, int handle ) { +  if (!PC_Int_Parse(handle, &item->type)) { +    return qfalse; +  } +  Item_ValidateTypeData(item); +  return qtrue; +} + +// elementwidth, used for listbox image elements +// uses textalignx for storage +qboolean ItemParse_elementwidth( itemDef_t *item, int handle ) { +  listBoxDef_t *listPtr; + +  Item_ValidateTypeData(item); +  listPtr = (listBoxDef_t*)item->typeData; +  if (!PC_Float_Parse(handle, &listPtr->elementWidth)) { +    return qfalse; +  } +  return qtrue; +} + +// elementheight, used for listbox image elements +// uses textaligny for storage +qboolean ItemParse_elementheight( itemDef_t *item, int handle ) { +  listBoxDef_t *listPtr; + +  Item_ValidateTypeData(item); +  listPtr = (listBoxDef_t*)item->typeData; +  if (!PC_Float_Parse(handle, &listPtr->elementHeight)) { +    return qfalse; +  } +  return qtrue; +} + +// feeder <float> +qboolean ItemParse_feeder( itemDef_t *item, int handle ) { +  if (!PC_Float_Parse(handle, &item->special)) { +    return qfalse; +  } +  return qtrue; +} + +// elementtype, used to specify what type of elements a listbox contains +// uses textstyle for storage +qboolean ItemParse_elementtype( itemDef_t *item, int handle ) { +  listBoxDef_t *listPtr; + +  Item_ValidateTypeData(item); +  if (!item->typeData) +    return qfalse; +  listPtr = (listBoxDef_t*)item->typeData; +  if (!PC_Int_Parse(handle, &listPtr->elementStyle)) { +    return qfalse; +  } +  return qtrue; +} + +// columns sets a number of columns and an x pos and width per.. +qboolean ItemParse_columns( itemDef_t *item, int handle ) { +  int num, i; +  listBoxDef_t *listPtr; + +  Item_ValidateTypeData(item); +  if (!item->typeData) +    return qfalse; +  listPtr = (listBoxDef_t*)item->typeData; +  if (PC_Int_Parse(handle, &num)) { +    if (num > MAX_LB_COLUMNS) { +      num = MAX_LB_COLUMNS; +    } +    listPtr->numColumns = num; +    for (i = 0; i < num; i++) { +      int pos, width, maxChars, align; + +      if( PC_Int_Parse( handle, &pos ) && +          PC_Int_Parse( handle, &width ) && +          PC_Int_Parse( handle, &maxChars ) && +          PC_Int_Parse( handle, &align ) ) +      { +        listPtr->columnInfo[i].pos = pos; +        listPtr->columnInfo[i].width = width; +        listPtr->columnInfo[i].maxChars = maxChars; +        listPtr->columnInfo[i].align = align; +      } else { +        return qfalse; +      } +    } +  } else { +    return qfalse; +  } +  return qtrue; +} + +qboolean ItemParse_border( itemDef_t *item, int handle ) { +  if (!PC_Int_Parse(handle, &item->window.border)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean ItemParse_bordersize( itemDef_t *item, int handle ) { +  if (!PC_Float_Parse(handle, &item->window.borderSize)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean ItemParse_visible( itemDef_t *item, int handle ) { +  int i; + +  if (!PC_Int_Parse(handle, &i)) { +    return qfalse; +  } +  if (i) { +    item->window.flags |= WINDOW_VISIBLE; +  } +  return qtrue; +} + +qboolean ItemParse_ownerdraw( itemDef_t *item, int handle ) { +  if (!PC_Int_Parse(handle, &item->window.ownerDraw)) { +    return qfalse; +  } +  item->type = ITEM_TYPE_OWNERDRAW; +  return qtrue; +} + +qboolean ItemParse_align( itemDef_t *item, int handle ) { +  if (!PC_Int_Parse(handle, &item->alignment)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean ItemParse_textalign( itemDef_t *item, int handle ) { +  if (!PC_Int_Parse(handle, &item->textalignment)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean ItemParse_textalignx( itemDef_t *item, int handle ) { +  if (!PC_Float_Parse(handle, &item->textalignx)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean ItemParse_textaligny( itemDef_t *item, int handle ) { +  if (!PC_Float_Parse(handle, &item->textaligny)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean ItemParse_textscale( itemDef_t *item, int handle ) { +  if (!PC_Float_Parse(handle, &item->textscale)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean ItemParse_textstyle( itemDef_t *item, int handle ) { +  if (!PC_Int_Parse(handle, &item->textStyle)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean ItemParse_backcolor( itemDef_t *item, int handle ) { +  int i; +  float f; + +  for (i = 0; i < 4; i++) { +    if (!PC_Float_Parse(handle, &f)) { +      return qfalse; +    } +    item->window.backColor[i]  = f; +  } +  return qtrue; +} + +qboolean ItemParse_forecolor( itemDef_t *item, int handle ) { +  int i; +  float f; + +  for (i = 0; i < 4; i++) { +    if (!PC_Float_Parse(handle, &f)) { +      return qfalse; +    } +    item->window.foreColor[i]  = f; +    item->window.flags |= WINDOW_FORECOLORSET; +  } +  return qtrue; +} + +qboolean ItemParse_bordercolor( itemDef_t *item, int handle ) { +  int i; +  float f; + +  for (i = 0; i < 4; i++) { +    if (!PC_Float_Parse(handle, &f)) { +      return qfalse; +    } +    item->window.borderColor[i]  = f; +  } +  return qtrue; +} + +qboolean ItemParse_outlinecolor( itemDef_t *item, int handle ) { +  if (!PC_Color_Parse(handle, &item->window.outlineColor)){ +    return qfalse; +  } +  return qtrue; +} + +qboolean ItemParse_background( itemDef_t *item, int handle ) { +  const char *temp; + +  if (!PC_String_Parse(handle, &temp)) { +    return qfalse; +  } +  item->window.background = DC->registerShaderNoMip(temp); +  return qtrue; +} + +qboolean ItemParse_cinematic( itemDef_t *item, int handle ) { +  if (!PC_String_Parse(handle, &item->window.cinematicName)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean ItemParse_doubleClick( itemDef_t *item, int handle ) { +  listBoxDef_t *listPtr; + +  Item_ValidateTypeData(item); +  if (!item->typeData) { +    return qfalse; +  } + +  listPtr = (listBoxDef_t*)item->typeData; + +  if (!PC_Script_Parse(handle, &listPtr->doubleClick)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean ItemParse_onFocus( itemDef_t *item, int handle ) { +  if (!PC_Script_Parse(handle, &item->onFocus)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean ItemParse_leaveFocus( itemDef_t *item, int handle ) { +  if (!PC_Script_Parse(handle, &item->leaveFocus)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean ItemParse_mouseEnter( itemDef_t *item, int handle ) { +  if (!PC_Script_Parse(handle, &item->mouseEnter)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean ItemParse_mouseExit( itemDef_t *item, int handle ) { +  if (!PC_Script_Parse(handle, &item->mouseExit)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean ItemParse_mouseEnterText( itemDef_t *item, int handle ) { +  if (!PC_Script_Parse(handle, &item->mouseEnterText)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean ItemParse_mouseExitText( itemDef_t *item, int handle ) { +  if (!PC_Script_Parse(handle, &item->mouseExitText)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean ItemParse_action( itemDef_t *item, int handle ) { +  if (!PC_Script_Parse(handle, &item->action)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean ItemParse_special( itemDef_t *item, int handle ) { +  if (!PC_Float_Parse(handle, &item->special)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean ItemParse_cvarTest( itemDef_t *item, int handle ) { +  if (!PC_String_Parse(handle, &item->cvarTest)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean ItemParse_cvar( itemDef_t *item, int handle ) { +  editFieldDef_t *editPtr; + +  Item_ValidateTypeData(item); +  if (!PC_String_Parse(handle, &item->cvar)) { +    return qfalse; +  } +  if (item->typeData) { +    editPtr = (editFieldDef_t*)item->typeData; +    editPtr->minVal = -1; +    editPtr->maxVal = -1; +    editPtr->defVal = -1; +  } +  return qtrue; +} + +qboolean ItemParse_maxChars( itemDef_t *item, int handle ) { +  editFieldDef_t *editPtr; +  int maxChars; + +  Item_ValidateTypeData(item); +  if (!item->typeData) +    return qfalse; + +  if (!PC_Int_Parse(handle, &maxChars)) { +    return qfalse; +  } +  editPtr = (editFieldDef_t*)item->typeData; +  editPtr->maxChars = maxChars; +  return qtrue; +} + +qboolean ItemParse_maxPaintChars( itemDef_t *item, int handle ) { +  editFieldDef_t *editPtr; +  int maxChars; + +  Item_ValidateTypeData(item); +  if (!item->typeData) +    return qfalse; + +  if (!PC_Int_Parse(handle, &maxChars)) { +    return qfalse; +  } +  editPtr = (editFieldDef_t*)item->typeData; +  editPtr->maxPaintChars = maxChars; +  return qtrue; +} + + + +qboolean ItemParse_cvarFloat( itemDef_t *item, int handle ) { +  editFieldDef_t *editPtr; + +  Item_ValidateTypeData(item); +  if (!item->typeData) +    return qfalse; +  editPtr = (editFieldDef_t*)item->typeData; +  if (PC_String_Parse(handle, &item->cvar) && +    PC_Float_Parse(handle, &editPtr->defVal) && +    PC_Float_Parse(handle, &editPtr->minVal) && +    PC_Float_Parse(handle, &editPtr->maxVal)) { +    return qtrue; +  } +  return qfalse; +} + +qboolean ItemParse_cvarStrList( itemDef_t *item, int handle ) { +  pc_token_t token; +  multiDef_t *multiPtr; +  int pass; + +  Item_ValidateTypeData(item); +  if (!item->typeData) +    return qfalse; +  multiPtr = (multiDef_t*)item->typeData; +  multiPtr->count = 0; +  multiPtr->strDef = qtrue; + +  if (!trap_Parse_ReadToken(handle, &token)) +    return qfalse; +  if (*token.string != '{') { +    return qfalse; +  } + +  pass = 0; +  while ( 1 ) { +    if (!trap_Parse_ReadToken(handle, &token)) { +      PC_SourceError(handle, "end of file inside menu item\n"); +      return qfalse; +    } + +    if (*token.string == '}') { +      return qtrue; +    } + +    if (*token.string == ',' || *token.string == ';') { +      continue; +    } + +    if (pass == 0) { +      multiPtr->cvarList[multiPtr->count] = String_Alloc(token.string); +      pass = 1; +    } else { +      multiPtr->cvarStr[multiPtr->count] = String_Alloc(token.string); +      pass = 0; +      multiPtr->count++; +      if (multiPtr->count >= MAX_MULTI_CVARS) { +        return qfalse; +      } +    } + +  } +  return qfalse;  // bk001205 - LCC missing return value +} + +qboolean ItemParse_cvarFloatList( itemDef_t *item, int handle ) { +  pc_token_t token; +  multiDef_t *multiPtr; + +  Item_ValidateTypeData(item); +  if (!item->typeData) +    return qfalse; +  multiPtr = (multiDef_t*)item->typeData; +  multiPtr->count = 0; +  multiPtr->strDef = qfalse; + +  if (!trap_Parse_ReadToken(handle, &token)) +    return qfalse; +  if (*token.string != '{') { +    return qfalse; +  } + +  while ( 1 ) { +    if (!trap_Parse_ReadToken(handle, &token)) { +      PC_SourceError(handle, "end of file inside menu item\n"); +      return qfalse; +    } + +    if (*token.string == '}') { +      return qtrue; +    } + +    if (*token.string == ',' || *token.string == ';') { +      continue; +    } + +    multiPtr->cvarList[multiPtr->count] = String_Alloc(token.string); +    if (!PC_Float_Parse(handle, &multiPtr->cvarValue[multiPtr->count])) { +      return qfalse; +    } + +    multiPtr->count++; +    if (multiPtr->count >= MAX_MULTI_CVARS) { +      return qfalse; +    } + +  } +  return qfalse;  // bk001205 - LCC missing return value +} + + + +qboolean ItemParse_addColorRange( itemDef_t *item, int handle ) { +  colorRangeDef_t color; + +  if (PC_Float_Parse(handle, &color.low) && +    PC_Float_Parse(handle, &color.high) && +    PC_Color_Parse(handle, &color.color) ) { +    if (item->numColors < MAX_COLOR_RANGES) { +      memcpy(&item->colorRanges[item->numColors], &color, sizeof(color)); +      item->numColors++; +    } +    return qtrue; +  } +  return qfalse; +} + +qboolean ItemParse_ownerdrawFlag( itemDef_t *item, int handle ) { +  int i; +  if (!PC_Int_Parse(handle, &i)) { +    return qfalse; +  } +  item->window.ownerDrawFlags |= i; +  return qtrue; +} + +qboolean ItemParse_enableCvar( itemDef_t *item, int handle ) { +  if (PC_Script_Parse(handle, &item->enableCvar)) { +    item->cvarFlags = CVAR_ENABLE; +    return qtrue; +  } +  return qfalse; +} + +qboolean ItemParse_disableCvar( itemDef_t *item, int handle ) { +  if (PC_Script_Parse(handle, &item->enableCvar)) { +    item->cvarFlags = CVAR_DISABLE; +    return qtrue; +  } +  return qfalse; +} + +qboolean ItemParse_showCvar( itemDef_t *item, int handle ) { +  if (PC_Script_Parse(handle, &item->enableCvar)) { +    item->cvarFlags = CVAR_SHOW; +    return qtrue; +  } +  return qfalse; +} + +qboolean ItemParse_hideCvar( itemDef_t *item, int handle ) { +  if (PC_Script_Parse(handle, &item->enableCvar)) { +    item->cvarFlags = CVAR_HIDE; +    return qtrue; +  } +  return qfalse; +} + + +keywordHash_t itemParseKeywords[] = { +  {"name", ItemParse_name, NULL}, +  {"text", ItemParse_text, NULL}, +  {"group", ItemParse_group, NULL}, +  {"asset_model", ItemParse_asset_model, NULL}, +  {"asset_shader", ItemParse_asset_shader, NULL}, +  {"model_origin", ItemParse_model_origin, NULL}, +  {"model_fovx", ItemParse_model_fovx, NULL}, +  {"model_fovy", ItemParse_model_fovy, NULL}, +  {"model_rotation", ItemParse_model_rotation, NULL}, +  {"model_angle", ItemParse_model_angle, NULL}, +  {"rect", ItemParse_rect, NULL}, +  {"style", ItemParse_style, NULL}, +  {"decoration", ItemParse_decoration, NULL}, +  {"notselectable", ItemParse_notselectable, NULL}, +  {"wrapped", ItemParse_wrapped, NULL}, +  {"autowrapped", ItemParse_autowrapped, NULL}, +  {"horizontalscroll", ItemParse_horizontalscroll, NULL}, +  {"type", ItemParse_type, NULL}, +  {"elementwidth", ItemParse_elementwidth, NULL}, +  {"elementheight", ItemParse_elementheight, NULL}, +  {"feeder", ItemParse_feeder, NULL}, +  {"elementtype", ItemParse_elementtype, NULL}, +  {"columns", ItemParse_columns, NULL}, +  {"border", ItemParse_border, NULL}, +  {"bordersize", ItemParse_bordersize, NULL}, +  {"visible", ItemParse_visible, NULL}, +  {"ownerdraw", ItemParse_ownerdraw, NULL}, +  {"align", ItemParse_align, NULL}, +  {"textalign", ItemParse_textalign, NULL}, +  {"textalignx", ItemParse_textalignx, NULL}, +  {"textaligny", ItemParse_textaligny, NULL}, +  {"textscale", ItemParse_textscale, NULL}, +  {"textstyle", ItemParse_textstyle, NULL}, +  {"backcolor", ItemParse_backcolor, NULL}, +  {"forecolor", ItemParse_forecolor, NULL}, +  {"bordercolor", ItemParse_bordercolor, NULL}, +  {"outlinecolor", ItemParse_outlinecolor, NULL}, +  {"background", ItemParse_background, NULL}, +  {"onFocus", ItemParse_onFocus, NULL}, +  {"leaveFocus", ItemParse_leaveFocus, NULL}, +  {"mouseEnter", ItemParse_mouseEnter, NULL}, +  {"mouseExit", ItemParse_mouseExit, NULL}, +  {"mouseEnterText", ItemParse_mouseEnterText, NULL}, +  {"mouseExitText", ItemParse_mouseExitText, NULL}, +  {"action", ItemParse_action, NULL}, +  {"special", ItemParse_special, NULL}, +  {"cvar", ItemParse_cvar, NULL}, +  {"maxChars", ItemParse_maxChars, NULL}, +  {"maxPaintChars", ItemParse_maxPaintChars, NULL}, +  {"focusSound", ItemParse_focusSound, NULL}, +  {"cvarFloat", ItemParse_cvarFloat, NULL}, +  {"cvarStrList", ItemParse_cvarStrList, NULL}, +  {"cvarFloatList", ItemParse_cvarFloatList, NULL}, +  {"addColorRange", ItemParse_addColorRange, NULL}, +  {"ownerdrawFlag", ItemParse_ownerdrawFlag, NULL}, +  {"enableCvar", ItemParse_enableCvar, NULL}, +  {"cvarTest", ItemParse_cvarTest, NULL}, +  {"disableCvar", ItemParse_disableCvar, NULL}, +  {"showCvar", ItemParse_showCvar, NULL}, +  {"hideCvar", ItemParse_hideCvar, NULL}, +  {"cinematic", ItemParse_cinematic, NULL}, +  {"doubleclick", ItemParse_doubleClick, NULL}, +  {NULL, voidFunction2, NULL} +}; + +keywordHash_t *itemParseKeywordHash[KEYWORDHASH_SIZE]; + +/* +=============== +Item_SetupKeywordHash +=============== +*/ +void Item_SetupKeywordHash( void ) +{ +  int i; + +  memset( itemParseKeywordHash, 0, sizeof( itemParseKeywordHash ) ); + +  for( i = 0; itemParseKeywords[ i ].keyword; i++ ) +    KeywordHash_Add( itemParseKeywordHash, &itemParseKeywords[ i ] ); +} + +/* +=============== +Item_Parse +=============== +*/ +qboolean Item_Parse(int handle, itemDef_t *item) { +  pc_token_t token; +  keywordHash_t *key; + + +  if (!trap_Parse_ReadToken(handle, &token)) +    return qfalse; +  if (*token.string != '{') { +    return qfalse; +  } +  while ( 1 ) { +    if (!trap_Parse_ReadToken(handle, &token)) { +      PC_SourceError(handle, "end of file inside menu item\n"); +      return qfalse; +    } + +    if (*token.string == '}') { +      return qtrue; +    } + +    key = KeywordHash_Find(itemParseKeywordHash, token.string); +    if (!key) { +      PC_SourceError(handle, "unknown menu item keyword %s", token.string); +      continue; +    } +    if ( !key->func(item, handle) ) { +      PC_SourceError(handle, "couldn't parse menu item keyword %s", token.string); +      return qfalse; +    } +  } +  return qfalse;  // bk001205 - LCC missing return value +} + + +// Item_InitControls +// init's special control types +void Item_InitControls(itemDef_t *item) { +  if (item == NULL) { +    return; +  } +  if (item->type == ITEM_TYPE_LISTBOX) { +    listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; +    item->cursorPos = 0; +    if (listPtr) { +      listPtr->cursorPos = 0; +      listPtr->startPos = 0; +      listPtr->endPos = 0; +      listPtr->cursorPos = 0; +    } +  } +} + +/* +=============== +Menu Keyword Parse functions +=============== +*/ + +qboolean MenuParse_font( itemDef_t *item, int handle ) { +  menuDef_t *menu = (menuDef_t*)item; +  if (!PC_String_Parse(handle, &menu->font)) { +    return qfalse; +  } +  if (!DC->Assets.fontRegistered) { +    DC->registerFont(menu->font, 48, &DC->Assets.textFont); +    DC->Assets.fontRegistered = qtrue; +  } +  return qtrue; +} + +qboolean MenuParse_name( itemDef_t *item, int handle ) { +  menuDef_t *menu = (menuDef_t*)item; +  if (!PC_String_Parse(handle, &menu->window.name)) { +    return qfalse; +  } +  if (Q_stricmp(menu->window.name, "main") == 0) { +    // default main as having focus +    //menu->window.flags |= WINDOW_HASFOCUS; +  } +  return qtrue; +} + +qboolean MenuParse_fullscreen( itemDef_t *item, int handle ) { +  menuDef_t *menu = (menuDef_t*)item; +  if (!PC_Int_Parse(handle, (int*) &menu->fullScreen)) { // bk001206 - cast qboolean +    return qfalse; +  } +  return qtrue; +} + +qboolean MenuParse_rect( itemDef_t *item, int handle ) { +  menuDef_t *menu = (menuDef_t*)item; +  if (!PC_Rect_Parse(handle, &menu->window.rect)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean MenuParse_style( itemDef_t *item, int handle ) { +  menuDef_t *menu = (menuDef_t*)item; +  if (!PC_Int_Parse(handle, &menu->window.style)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean MenuParse_visible( itemDef_t *item, int handle ) { +  int i; +  menuDef_t *menu = (menuDef_t*)item; + +  if (!PC_Int_Parse(handle, &i)) { +    return qfalse; +  } +  if (i) { +    menu->window.flags |= WINDOW_VISIBLE; +  } +  return qtrue; +} + +qboolean MenuParse_onOpen( itemDef_t *item, int handle ) { +  menuDef_t *menu = (menuDef_t*)item; +  if (!PC_Script_Parse(handle, &menu->onOpen)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean MenuParse_onClose( itemDef_t *item, int handle ) { +  menuDef_t *menu = (menuDef_t*)item; +  if (!PC_Script_Parse(handle, &menu->onClose)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean MenuParse_onESC( itemDef_t *item, int handle ) { +  menuDef_t *menu = (menuDef_t*)item; +  if (!PC_Script_Parse(handle, &menu->onESC)) { +    return qfalse; +  } +  return qtrue; +} + + + +qboolean MenuParse_border( itemDef_t *item, int handle ) { +  menuDef_t *menu = (menuDef_t*)item; +  if (!PC_Int_Parse(handle, &menu->window.border)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean MenuParse_borderSize( itemDef_t *item, int handle ) { +  menuDef_t *menu = (menuDef_t*)item; +  if (!PC_Float_Parse(handle, &menu->window.borderSize)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean MenuParse_backcolor( itemDef_t *item, int handle ) { +  int i; +  float f; +  menuDef_t *menu = (menuDef_t*)item; + +  for (i = 0; i < 4; i++) { +    if (!PC_Float_Parse(handle, &f)) { +      return qfalse; +    } +    menu->window.backColor[i]  = f; +  } +  return qtrue; +} + +qboolean MenuParse_forecolor( itemDef_t *item, int handle ) { +  int i; +  float f; +  menuDef_t *menu = (menuDef_t*)item; + +  for (i = 0; i < 4; i++) { +    if (!PC_Float_Parse(handle, &f)) { +      return qfalse; +    } +    menu->window.foreColor[i]  = f; +    menu->window.flags |= WINDOW_FORECOLORSET; +  } +  return qtrue; +} + +qboolean MenuParse_bordercolor( itemDef_t *item, int handle ) { +  int i; +  float f; +  menuDef_t *menu = (menuDef_t*)item; + +  for (i = 0; i < 4; i++) { +    if (!PC_Float_Parse(handle, &f)) { +      return qfalse; +    } +    menu->window.borderColor[i]  = f; +  } +  return qtrue; +} + +qboolean MenuParse_focuscolor( itemDef_t *item, int handle ) { +  int i; +  float f; +  menuDef_t *menu = (menuDef_t*)item; + +  for (i = 0; i < 4; i++) { +    if (!PC_Float_Parse(handle, &f)) { +      return qfalse; +    } +    menu->focusColor[i]  = f; +  } +  return qtrue; +} + +qboolean MenuParse_disablecolor( itemDef_t *item, int handle ) { +  int i; +  float f; +  menuDef_t *menu = (menuDef_t*)item; +  for (i = 0; i < 4; i++) { +    if (!PC_Float_Parse(handle, &f)) { +      return qfalse; +    } +    menu->disableColor[i]  = f; +  } +  return qtrue; +} + + +qboolean MenuParse_outlinecolor( itemDef_t *item, int handle ) { +  menuDef_t *menu = (menuDef_t*)item; +  if (!PC_Color_Parse(handle, &menu->window.outlineColor)){ +    return qfalse; +  } +  return qtrue; +} + +qboolean MenuParse_background( itemDef_t *item, int handle ) { +  const char *buff; +  menuDef_t *menu = (menuDef_t*)item; + +  if (!PC_String_Parse(handle, &buff)) { +    return qfalse; +  } +  menu->window.background = DC->registerShaderNoMip(buff); +  return qtrue; +} + +qboolean MenuParse_cinematic( itemDef_t *item, int handle ) { +  menuDef_t *menu = (menuDef_t*)item; + +  if (!PC_String_Parse(handle, &menu->window.cinematicName)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean MenuParse_ownerdrawFlag( itemDef_t *item, int handle ) { +  int i; +  menuDef_t *menu = (menuDef_t*)item; + +  if (!PC_Int_Parse(handle, &i)) { +    return qfalse; +  } +  menu->window.ownerDrawFlags |= i; +  return qtrue; +} + +qboolean MenuParse_ownerdraw( itemDef_t *item, int handle ) { +  menuDef_t *menu = (menuDef_t*)item; + +  if (!PC_Int_Parse(handle, &menu->window.ownerDraw)) { +    return qfalse; +  } +  return qtrue; +} + + +// decoration +qboolean MenuParse_popup( itemDef_t *item, int handle ) { +  menuDef_t *menu = (menuDef_t*)item; +  menu->window.flags |= WINDOW_POPUP; +  return qtrue; +} + + +qboolean MenuParse_outOfBounds( itemDef_t *item, int handle ) { +  menuDef_t *menu = (menuDef_t*)item; + +  menu->window.flags |= WINDOW_OOB_CLICK; +  return qtrue; +} + +qboolean MenuParse_soundLoop( itemDef_t *item, int handle ) { +  menuDef_t *menu = (menuDef_t*)item; + +  if (!PC_String_Parse(handle, &menu->soundName)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean MenuParse_fadeClamp( itemDef_t *item, int handle ) { +  menuDef_t *menu = (menuDef_t*)item; + +  if (!PC_Float_Parse(handle, &menu->fadeClamp)) { +    return qfalse; +  } +  return qtrue; +} + +qboolean MenuParse_fadeAmount( itemDef_t *item, int handle ) { +  menuDef_t *menu = (menuDef_t*)item; + +  if (!PC_Float_Parse(handle, &menu->fadeAmount)) { +    return qfalse; +  } +  return qtrue; +} + + +qboolean MenuParse_fadeCycle( itemDef_t *item, int handle ) { +  menuDef_t *menu = (menuDef_t*)item; + +  if (!PC_Int_Parse(handle, &menu->fadeCycle)) { +    return qfalse; +  } +  return qtrue; +} + + +qboolean MenuParse_itemDef( itemDef_t *item, int handle ) { +  menuDef_t *menu = (menuDef_t*)item; +  if (menu->itemCount < MAX_MENUITEMS) { +    menu->items[menu->itemCount] = UI_Alloc(sizeof(itemDef_t)); +    Item_Init(menu->items[menu->itemCount]); +    if (!Item_Parse(handle, menu->items[menu->itemCount])) { +      return qfalse; +    } +    Item_InitControls(menu->items[menu->itemCount]); +    menu->items[menu->itemCount++]->parent = menu; +  } +  return qtrue; +} + +keywordHash_t menuParseKeywords[] = { +  {"font", MenuParse_font, NULL}, +  {"name", MenuParse_name, NULL}, +  {"fullscreen", MenuParse_fullscreen, NULL}, +  {"rect", MenuParse_rect, NULL}, +  {"style", MenuParse_style, NULL}, +  {"visible", MenuParse_visible, NULL}, +  {"onOpen", MenuParse_onOpen, NULL}, +  {"onClose", MenuParse_onClose, NULL}, +  {"onESC", MenuParse_onESC, NULL}, +  {"border", MenuParse_border, NULL}, +  {"borderSize", MenuParse_borderSize, NULL}, +  {"backcolor", MenuParse_backcolor, NULL}, +  {"forecolor", MenuParse_forecolor, NULL}, +  {"bordercolor", MenuParse_bordercolor, NULL}, +  {"focuscolor", MenuParse_focuscolor, NULL}, +  {"disablecolor", MenuParse_disablecolor, NULL}, +  {"outlinecolor", MenuParse_outlinecolor, NULL}, +  {"background", MenuParse_background, NULL}, +  {"ownerdraw", MenuParse_ownerdraw, NULL}, +  {"ownerdrawFlag", MenuParse_ownerdrawFlag, NULL}, +  {"outOfBoundsClick", MenuParse_outOfBounds, NULL}, +  {"soundLoop", MenuParse_soundLoop, NULL}, +  {"itemDef", MenuParse_itemDef, NULL}, +  {"cinematic", MenuParse_cinematic, NULL}, +  {"popup", MenuParse_popup, NULL}, +  {"fadeClamp", MenuParse_fadeClamp, NULL}, +  {"fadeCycle", MenuParse_fadeCycle, NULL}, +  {"fadeAmount", MenuParse_fadeAmount, NULL}, +  {NULL, voidFunction2, NULL} +}; + +keywordHash_t *menuParseKeywordHash[KEYWORDHASH_SIZE]; + +/* +=============== +Menu_SetupKeywordHash +=============== +*/ +void Menu_SetupKeywordHash( void ) +{ +  int i; + +  memset( menuParseKeywordHash, 0, sizeof( menuParseKeywordHash ) ); + +  for(i = 0; menuParseKeywords[ i ].keyword; i++ ) +    KeywordHash_Add( menuParseKeywordHash, &menuParseKeywords[ i ] ); +} + +/* +=============== +Menu_Parse +=============== +*/ +qboolean Menu_Parse(int handle, menuDef_t *menu) { +  pc_token_t token; +  keywordHash_t *key; + +  if (!trap_Parse_ReadToken(handle, &token)) +    return qfalse; +  if (*token.string != '{') { +    return qfalse; +  } + +  while ( 1 ) { + +    memset(&token, 0, sizeof(pc_token_t)); +    if (!trap_Parse_ReadToken(handle, &token)) { +      PC_SourceError(handle, "end of file inside menu\n"); +      return qfalse; +    } + +    if (*token.string == '}') { +      return qtrue; +    } + +    key = KeywordHash_Find(menuParseKeywordHash, token.string); +    if (!key) { +      PC_SourceError(handle, "unknown menu keyword %s", token.string); +      continue; +    } +    if ( !key->func((itemDef_t*)menu, handle) ) { +      PC_SourceError(handle, "couldn't parse menu keyword %s", token.string); +      return qfalse; +    } +  } +  return qfalse;  // bk001205 - LCC missing return value +} + +/* +=============== +Menu_New +=============== +*/ +void Menu_New(int handle) { +  menuDef_t *menu = &Menus[menuCount]; + +  if (menuCount < MAX_MENUS) { +    Menu_Init(menu); +    if (Menu_Parse(handle, menu)) { +      Menu_PostParse(menu); +      menuCount++; +    } +  } +} + +int Menu_Count( void ) { +  return menuCount; +} + +void Menu_PaintAll( void ) { +  int i; +  if (captureFunc) { +    captureFunc(captureData); +  } + +  for (i = 0; i < Menu_Count(); i++) { +    Menu_Paint(&Menus[i], qfalse); +  } + +  if (debugMode) { +    vec4_t v = {1, 1, 1, 1}; +    DC->drawText(5, 25, .5, v, va("fps: %f", DC->FPS), 0, 0, 0); +  } +} + +void Menu_Reset( void ) +{ +  menuCount = 0; +} + +displayContextDef_t *Display_GetContext( void ) { +  return DC; +} + +void *Display_CaptureItem(int x, int y) { +  int i; + +  for (i = 0; i < menuCount; i++) { +    // turn off focus each item +    // menu->items[i].window.flags &= ~WINDOW_HASFOCUS; +    if (Rect_ContainsPoint(&Menus[i].window.rect, x, y)) { +      return &Menus[i]; +    } +  } +  return NULL; +} + + +// FIXME: +qboolean Display_MouseMove(void *p, int x, int y) { +  int i; +  menuDef_t *menu = p; + +  if (menu == NULL) { +    menu = Menu_GetFocused(); +    if (menu) { +      if (menu->window.flags & WINDOW_POPUP) { +        Menu_HandleMouseMove(menu, x, y); +        return qtrue; +      } +    } +    for (i = 0; i < menuCount; i++) { +      Menu_HandleMouseMove(&Menus[i], x, y); +    } +  } else { +    menu->window.rect.x += x; +    menu->window.rect.y += y; +    Menu_UpdatePosition(menu); +  } +  return qtrue; + +} + +int Display_CursorType(int x, int y) { +  int i; +  for (i = 0; i < menuCount; i++) { +    rectDef_t r2; +    r2.x = Menus[i].window.rect.x - 3; +    r2.y = Menus[i].window.rect.y - 3; +    r2.w = r2.h = 7; +    if (Rect_ContainsPoint(&r2, x, y)) { +      return CURSOR_SIZER; +    } +  } +  return CURSOR_ARROW; +} + + +void Display_HandleKey(int key, qboolean down, int x, int y) { +  menuDef_t *menu = Display_CaptureItem(x, y); +  if (menu == NULL) { +    menu = Menu_GetFocused(); +  } +  if (menu) { +    Menu_HandleKey(menu, key, down ); +  } +} + +static void Window_CacheContents(windowDef_t *window) { +  if (window) { +    if (window->cinematicName) { +      int cin = DC->playCinematic(window->cinematicName, 0, 0, 0, 0); +      DC->stopCinematic(cin); +    } +  } +} + + +static void Item_CacheContents(itemDef_t *item) { +  if (item) { +    Window_CacheContents(&item->window); +  } + +} + +static void Menu_CacheContents(menuDef_t *menu) { +  if (menu) { +    int i; +    Window_CacheContents(&menu->window); +    for (i = 0; i < menu->itemCount; i++) { +      Item_CacheContents(menu->items[i]); +    } + +    if (menu->soundName && *menu->soundName) { +      DC->registerSound(menu->soundName, qfalse); +    } +  } + +} + +void Display_CacheAll( void ) { +  int i; +  for (i = 0; i < menuCount; i++) { +    Menu_CacheContents(&Menus[i]); +  } +} + + +static qboolean Menu_OverActiveItem(menuDef_t *menu, float x, float y) { +  if (menu && menu->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED)) { +    if (Rect_ContainsPoint(&menu->window.rect, x, y)) { +      int i; +      for (i = 0; i < menu->itemCount; i++) { +        // turn off focus each item +        // menu->items[i].window.flags &= ~WINDOW_HASFOCUS; + +        if (!(menu->items[i]->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED))) { +          continue; +        } + +        if (menu->items[i]->window.flags & WINDOW_DECORATION) { +          continue; +        } + +        if (Rect_ContainsPoint(&menu->items[i]->window.rect, x, y)) { +          itemDef_t *overItem = menu->items[i]; +          if (overItem->type == ITEM_TYPE_TEXT && overItem->text) { +            if (Rect_ContainsPoint(Item_CorrectedTextRect(overItem), x, y)) { +              return qtrue; +            } else { +              continue; +            } +          } else { +            return qtrue; +          } +        } +      } + +    } +  } +  return qfalse; +} + diff --git a/src/ui/ui_shared.h b/src/ui/ui_shared.h new file mode 100644 index 0000000..e55cc7f --- /dev/null +++ b/src/ui/ui_shared.h @@ -0,0 +1,454 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +#ifndef __UI_SHARED_H +#define __UI_SHARED_H + + +#include "../qcommon/q_shared.h" +#include "../renderer/tr_types.h" +#include "../client/keycodes.h" + +#include "../ui/menudef.h" + +#define MAX_MENUNAME 32 +#define MAX_ITEMTEXT 64 +#define MAX_ITEMACTION 64 +#define MAX_MENUDEFFILE 4096 +#define MAX_MENUFILE 32768 +#define MAX_MENUS 256 +#define MAX_MENUITEMS 128 +#define MAX_COLOR_RANGES 10 +#define MAX_OPEN_MENUS 16 + +#define WINDOW_MOUSEOVER      0x00000001  // mouse is over it, non exclusive +#define WINDOW_HASFOCUS        0x00000002  // has cursor focus, exclusive +#define WINDOW_VISIBLE        0x00000004  // is visible +#define WINDOW_GREY            0x00000008  // is visible but grey ( non-active ) +#define WINDOW_DECORATION      0x00000010  // for decoration only, no mouse, keyboard, etc.. +#define WINDOW_FADINGOUT      0x00000020  // fading out, non-active +#define WINDOW_FADINGIN        0x00000040  // fading in +#define WINDOW_MOUSEOVERTEXT  0x00000080  // mouse is over it, non exclusive +#define WINDOW_INTRANSITION    0x00000100  // window is in transition +#define WINDOW_FORECOLORSET    0x00000200  // forecolor was explicitly set ( used to color alpha images or not ) +#define WINDOW_HORIZONTAL      0x00000400  // for list boxes and sliders, vertical is default this is set of horizontal +#define WINDOW_LB_LEFTARROW    0x00000800  // mouse is over left/up arrow +#define WINDOW_LB_RIGHTARROW  0x00001000  // mouse is over right/down arrow +#define WINDOW_LB_THUMB        0x00002000  // mouse is over thumb +#define WINDOW_LB_PGUP        0x00004000  // mouse is over page up +#define WINDOW_LB_PGDN        0x00008000  // mouse is over page down +#define WINDOW_ORBITING        0x00010000  // item is in orbit +#define WINDOW_OOB_CLICK      0x00020000  // close on out of bounds click +#define WINDOW_WRAPPED        0x00040000  // manually wrap text +#define WINDOW_AUTOWRAPPED      0x00080000  // auto wrap text +#define WINDOW_FORCED          0x00100000  // forced open +#define WINDOW_POPUP          0x00200000  // popup +#define WINDOW_BACKCOLORSET    0x00400000  // backcolor was explicitly set +#define WINDOW_TIMEDVISIBLE    0x00800000  // visibility timing ( NOT implemented ) + + +// CGAME cursor type bits +#define CURSOR_NONE          0x00000001 +#define CURSOR_ARROW        0x00000002 +#define CURSOR_SIZER        0x00000004 + +#ifdef CGAME +#define STRING_POOL_SIZE 128*1024 +#else +#define STRING_POOL_SIZE 384*1024 +#endif +#define MAX_STRING_HANDLES 4096 + +#define MAX_SCRIPT_ARGS 12 +#define MAX_EDITFIELD 256 + +#define ART_FX_BASE      "menu/art/fx_base" +#define ART_FX_BLUE      "menu/art/fx_blue" +#define ART_FX_CYAN      "menu/art/fx_cyan" +#define ART_FX_GREEN    "menu/art/fx_grn" +#define ART_FX_RED      "menu/art/fx_red" +#define ART_FX_TEAL      "menu/art/fx_teal" +#define ART_FX_WHITE    "menu/art/fx_white" +#define ART_FX_YELLOW    "menu/art/fx_yel" + +#define ASSET_GRADIENTBAR "ui/assets/gradientbar2.tga" +#define ASSET_SCROLLBAR             "ui/assets/scrollbar.tga" +#define ASSET_SCROLLBAR_ARROWDOWN   "ui/assets/scrollbar_arrow_dwn_a.tga" +#define ASSET_SCROLLBAR_ARROWUP     "ui/assets/scrollbar_arrow_up_a.tga" +#define ASSET_SCROLLBAR_ARROWLEFT   "ui/assets/scrollbar_arrow_left.tga" +#define ASSET_SCROLLBAR_ARROWRIGHT  "ui/assets/scrollbar_arrow_right.tga" +#define ASSET_SCROLL_THUMB          "ui/assets/scrollbar_thumb.tga" +#define ASSET_SLIDER_BAR            "ui/assets/slider2.tga" +#define ASSET_SLIDER_THUMB          "ui/assets/sliderbutt_1.tga" +#define SCROLLBAR_SIZE 16.0 +#define SLIDER_WIDTH 96.0 +#define SLIDER_HEIGHT 16.0 +#define SLIDER_THUMB_WIDTH 12.0 +#define SLIDER_THUMB_HEIGHT 20.0 +#define  NUM_CROSSHAIRS      10 + +typedef struct { +  const char *command; +  const char *args[MAX_SCRIPT_ARGS]; +} scriptDef_t; + + +typedef struct { +  float x;    // horiz position +  float y;    // vert position +  float w;    // width +  float h;    // height; +} rectDef_t; + +typedef rectDef_t Rectangle; + +// FIXME: do something to separate text vs window stuff +typedef struct { +  Rectangle rect;                 // client coord rectangle +  Rectangle rectClient;           // screen coord rectangle +  const char *name;               // +  const char *group;              // if it belongs to a group +  const char *cinematicName;      // cinematic name +  int cinematic;                  // cinematic handle +  int style;                      // +  int border;                     // +  int ownerDraw;                  // ownerDraw style +  int ownerDrawFlags;              // show flags for ownerdraw items +  float borderSize;               // +  int flags;                      // visible, focus, mouseover, cursor +  Rectangle rectEffects;          // for various effects +  Rectangle rectEffects2;         // for various effects +  int offsetTime;                 // time based value for various effects +  int nextTime;                   // time next effect should cycle +  vec4_t foreColor;               // text color +  vec4_t backColor;               // border color +  vec4_t borderColor;             // border color +  vec4_t outlineColor;            // border color +  qhandle_t background;           // background asset +} windowDef_t; + +typedef windowDef_t Window; + +typedef struct { +  vec4_t  color; +  float    low; +  float    high; +} colorRangeDef_t; + +// FIXME: combine flags into bitfields to save space +// FIXME: consolidate all of the common stuff in one structure for menus and items +// THINKABOUTME: is there any compelling reason not to have items contain items +// and do away with a menu per say.. major issue is not being able to dynamically allocate +// and destroy stuff.. Another point to consider is adding an alloc free call for vm's and have +// the engine just allocate the pool for it based on a cvar +// many of the vars are re-used for different item types, as such they are not always named appropriately +// the benefits of c++ in DOOM will greatly help crap like this +// FIXME: need to put a type ptr that points to specific type info per type +// +#define MAX_LB_COLUMNS 16 + +typedef struct columnInfo_s { +  int pos; +  int width; +  int maxChars; +  int align; +} columnInfo_t; + +typedef struct listBoxDef_s { +  int startPos; +  int endPos; +  int drawPadding; +  int cursorPos; +  float elementWidth; +  float elementHeight; +  int elementStyle; +  int numColumns; +  columnInfo_t columnInfo[MAX_LB_COLUMNS]; +  const char *doubleClick; +  qboolean notselectable; +} listBoxDef_t; + +typedef struct editFieldDef_s { +  float minVal;                  //  edit field limits +  float maxVal;                  // +  float defVal;                  // +  float range;                   // +  int maxChars;                  // for edit fields +  int maxPaintChars;             // for edit fields +  int paintOffset;               // +} editFieldDef_t; + +#define MAX_MULTI_CVARS 32 + +typedef struct multiDef_s { +  const char *cvarList[MAX_MULTI_CVARS]; +  const char *cvarStr[MAX_MULTI_CVARS]; +  float cvarValue[MAX_MULTI_CVARS]; +  int count; +  qboolean strDef; +} multiDef_t; + +typedef struct modelDef_s { +  int angle; +  vec3_t origin; +  float fov_x; +  float fov_y; +  int rotationSpeed; +} modelDef_t; + +#define CVAR_ENABLE    0x00000001 +#define CVAR_DISABLE  0x00000002 +#define CVAR_SHOW      0x00000004 +#define CVAR_HIDE      0x00000008 + +typedef struct itemDef_s { +  Window window;                 // common positional, border, style, layout info +  Rectangle textRect;            // rectangle the text ( if any ) consumes +  int type;                      // text, button, radiobutton, checkbox, textfield, listbox, combo +  int alignment;                 // left center right +  int textalignment;             // ( optional ) alignment for text within rect based on text width +  float textalignx;              // ( optional ) text alignment x coord +  float textaligny;              // ( optional ) text alignment x coord +  float textscale;               // scale percentage from 72pts +  int textStyle;                 // ( optional ) style, normal and shadowed are it for now +  const char *text;              // display text +  void *parent;                  // menu owner +  qhandle_t asset;               // handle to asset +  const char *mouseEnterText;    // mouse enter script +  const char *mouseExitText;     // mouse exit script +  const char *mouseEnter;        // mouse enter script +  const char *mouseExit;         // mouse exit script +  const char *action;            // select script +  const char *onFocus;           // select script +  const char *leaveFocus;        // select script +  const char *cvar;              // associated cvar +  const char *cvarTest;          // associated cvar for enable actions +  const char *enableCvar;         // enable, disable, show, or hide based on value, this can contain a list +  int cvarFlags;                 //  what type of action to take on cvarenables +  sfxHandle_t focusSound; +  int numColors;                 // number of color ranges +  colorRangeDef_t colorRanges[MAX_COLOR_RANGES]; +  float special;                 // used for feeder id's etc.. diff per type +  int cursorPos;                 // cursor position in characters +  void *typeData;                 // type specific data ptr's +} itemDef_t; + +typedef struct { +  Window window; +  const char  *font;                // font +  qboolean fullScreen;              // covers entire screen +  int itemCount;                    // number of items; +  int fontIndex;                    // +  int cursorItem;                    // which item as the cursor +  int fadeCycle;                    // +  float fadeClamp;                  // +  float fadeAmount;                  // +  const char *onOpen;                // run when the menu is first opened +  const char *onClose;              // run when the menu is closed +  const char *onESC;                // run when the menu is closed +  const char *soundName;            // background loop sound for menu + +  vec4_t focusColor;                // focus color for items +  vec4_t disableColor;              // focus color for items +  itemDef_t *items[MAX_MENUITEMS];  // items this menu contains +} menuDef_t; + +typedef struct { +  const char *fontStr; +  const char *cursorStr; +  const char *gradientStr; +  fontInfo_t textFont; +  fontInfo_t smallFont; +  fontInfo_t bigFont; +  qhandle_t cursor; +  qhandle_t gradientBar; +  qhandle_t scrollBarArrowUp; +  qhandle_t scrollBarArrowDown; +  qhandle_t scrollBarArrowLeft; +  qhandle_t scrollBarArrowRight; +  qhandle_t scrollBar; +  qhandle_t scrollBarThumb; +  qhandle_t buttonMiddle; +  qhandle_t buttonInside; +  qhandle_t solidBox; +  qhandle_t sliderBar; +  qhandle_t sliderThumb; +  sfxHandle_t menuEnterSound; +  sfxHandle_t menuExitSound; +  sfxHandle_t menuBuzzSound; +  sfxHandle_t itemFocusSound; +  float fadeClamp; +  int fadeCycle; +  float fadeAmount; +  float shadowX; +  float shadowY; +  vec4_t shadowColor; +  float shadowFadeClamp; +  qboolean fontRegistered; + +} cachedAssets_t; + +typedef struct { +  const char *name; +  void (*handler) (itemDef_t *item, char** args); +} commandDef_t; + +typedef struct { +  qhandle_t (*registerShaderNoMip) (const char *p); +  void (*setColor) (const vec4_t v); +  void (*drawHandlePic) (float x, float y, float w, float h, qhandle_t asset); +  void (*drawStretchPic) (float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ); +  void (*drawText) (float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style ); +  int (*textWidth) (const char *text, float scale, int limit); +  int (*textHeight) (const char *text, float scale, int limit); +  qhandle_t (*registerModel) (const char *p); +  void (*modelBounds) (qhandle_t model, vec3_t min, vec3_t max); +  void (*fillRect) ( float x, float y, float w, float h, const vec4_t color); +  void (*drawRect) ( float x, float y, float w, float h, float size, const vec4_t color); +  void (*drawSides) (float x, float y, float w, float h, float size); +  void (*drawTopBottom) (float x, float y, float w, float h, float size); +  void (*clearScene) (void); +  void (*addRefEntityToScene) (const refEntity_t *re ); +  void (*renderScene) ( const refdef_t *fd ); +  void (*registerFont) (const char *pFontname, int pointSize, fontInfo_t *font); +  void (*ownerDrawItem) (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); +  float (*getValue) (int ownerDraw); +  qboolean (*ownerDrawVisible) (int flags); +  void (*runScript)(char **p); +  void (*getTeamColor)(vec4_t *color); +  void (*getCVarString)(const char *cvar, char *buffer, int bufsize); +  float (*getCVarValue)(const char *cvar); +  void (*setCVar)(const char *cvar, const char *value); +  void (*drawTextWithCursor)(float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style); +  void (*setOverstrikeMode)(qboolean b); +  qboolean (*getOverstrikeMode)( void ); +  void (*startLocalSound)( sfxHandle_t sfx, int channelNum ); +  qboolean (*ownerDrawHandleKey)(int ownerDraw, int flags, float *special, int key); +  int (*feederCount)(float feederID); +  const char *(*feederItemText)(float feederID, int index, int column, qhandle_t *handle); +  qhandle_t (*feederItemImage)(float feederID, int index); +  void (*feederSelection)(float feederID, int index); +  void (*keynumToStringBuf)( int keynum, char *buf, int buflen ); +  void (*getBindingBuf)( int keynum, char *buf, int buflen ); +  void (*setBinding)( int keynum, const char *binding ); +  void (*executeText)(int exec_when, const char *text ); +  void (*Error)(int level, const char *error, ...); +  void (*Print)(const char *msg, ...); +  void (*Pause)(qboolean b); +  int (*ownerDrawWidth)(int ownerDraw, float scale); +  sfxHandle_t (*registerSound)(const char *name, qboolean compressed); +  void (*startBackgroundTrack)( const char *intro, const char *loop); +  void (*stopBackgroundTrack)( void ); +  int (*playCinematic)(const char *name, float x, float y, float w, float h); +  void (*stopCinematic)(int handle); +  void (*drawCinematic)(int handle, float x, float y, float w, float h); +  void (*runCinematicFrame)(int handle); + +  float      yscale; +  float      xscale; +  float      bias; +  int        realTime; +  int        frameTime; +  int        cursorx; +  int        cursory; +  qboolean  debug; + +  cachedAssets_t Assets; + +  glconfig_t glconfig; +  qhandle_t  whiteShader; +  qhandle_t gradientImage; +  qhandle_t cursor; +  float FPS; + +} displayContextDef_t; + +const char *String_Alloc(const char *p); +void String_Init( void ); +void String_Report( void ); +void Init_Display(displayContextDef_t *dc); +void Display_ExpandMacros(char * buff); +void Menu_Init(menuDef_t *menu); +void Item_Init(itemDef_t *item); +void Menu_PostParse(menuDef_t *menu); +menuDef_t *Menu_GetFocused( void ); +void Menu_HandleKey(menuDef_t *menu, int key, qboolean down); +void Menu_HandleMouseMove(menuDef_t *menu, float x, float y); +void Menu_ScrollFeeder(menuDef_t *menu, int feeder, qboolean down); +qboolean Float_Parse(char **p, float *f); +qboolean Color_Parse(char **p, vec4_t *c); +qboolean Int_Parse(char **p, int *i); +qboolean Rect_Parse(char **p, rectDef_t *r); +qboolean String_Parse(char **p, const char **out); +qboolean Script_Parse(char **p, const char **out); +qboolean PC_Float_Parse(int handle, float *f); +qboolean PC_Color_Parse(int handle, vec4_t *c); +qboolean PC_Int_Parse(int handle, int *i); +qboolean PC_Rect_Parse(int handle, rectDef_t *r); +qboolean PC_String_Parse(int handle, const char **out); +qboolean PC_Script_Parse(int handle, const char **out); +int Menu_Count( void ); +void Menu_New(int handle); +void Menu_PaintAll( void ); +menuDef_t *Menus_ActivateByName(const char *p); +void Menu_Reset( void ); +qboolean Menus_AnyFullScreenVisible( void ); +void  Menus_Activate(menuDef_t *menu); + +displayContextDef_t *Display_GetContext( void ); +void *Display_CaptureItem(int x, int y); +qboolean Display_MouseMove(void *p, int x, int y); +int Display_CursorType(int x, int y); +qboolean Display_KeyBindPending( void ); +void Menus_OpenByName(const char *p); +menuDef_t *Menus_FindByName(const char *p); +void Menus_ShowByName(const char *p); +void Menus_CloseByName(const char *p); +void Display_HandleKey(int key, qboolean down, int x, int y); +void LerpColor(vec4_t a, vec4_t b, vec4_t c, float t); +void Menus_CloseAll( void ); +void Menu_Paint(menuDef_t *menu, qboolean forcePaint); +void Menu_SetFeederSelection(menuDef_t *menu, int feeder, int index, const char *name); +void Display_CacheAll( void ); + +void *UI_Alloc( int size ); +void UI_InitMemory( void ); +qboolean UI_OutOfMemory( void ); + +void Controls_GetConfig( void ); +void Controls_SetConfig(qboolean restart); +void Controls_SetDefaults( void ); + +//for cg_draw.c +void Item_Text_AutoWrapped_Paint( itemDef_t *item ); + +int      trap_Parse_AddGlobalDefine( char *define ); +int      trap_Parse_LoadSource( const char *filename ); +int      trap_Parse_FreeSource( int handle ); +int      trap_Parse_ReadToken( int handle, pc_token_t *pc_token ); +int      trap_Parse_SourceFileAndLine( int handle, char *filename, int *line ); + +void    BindingFromName( const char *cvar ); +extern char g_nameBind1[ 32 ]; +extern char g_nameBind2[ 32 ]; +#endif diff --git a/src/ui/ui_syscalls.asm b/src/ui/ui_syscalls.asm new file mode 100644 index 0000000..1b236fe --- /dev/null +++ b/src/ui/ui_syscalls.asm @@ -0,0 +1,98 @@ +code + +equ trap_Error                        -1 +equ trap_Print                        -2 +equ trap_Milliseconds                 -3 +equ trap_Cvar_Set                     -4 +equ trap_Cvar_VariableValue           -5 +equ trap_Cvar_VariableStringBuffer    -6 +equ trap_Cvar_SetValue                -7 +equ trap_Cvar_Reset                   -8 +equ trap_Cvar_Create                  -9 +equ trap_Cvar_InfoStringBuffer        -10 +equ trap_Argc                         -11 +equ trap_Argv                         -12 +equ trap_Cmd_ExecuteText              -13 +equ trap_FS_FOpenFile                 -14 +equ trap_FS_Read                      -15 +equ trap_FS_Write                     -16 +equ trap_FS_FCloseFile                -17 +equ trap_FS_GetFileList               -18 +equ trap_R_RegisterModel              -19 +equ trap_R_RegisterSkin               -20 +equ trap_R_RegisterShaderNoMip        -21 +equ trap_R_ClearScene                 -22 +equ trap_R_AddRefEntityToScene        -23 +equ trap_R_AddPolyToScene             -24 +equ trap_R_AddLightToScene            -25 +equ trap_R_RenderScene                -26 +equ trap_R_SetColor                   -27 +equ trap_R_DrawStretchPic             -28 +equ trap_UpdateScreen                 -29 +equ trap_CM_LerpTag                   -30 +equ trap_CM_LoadModel                 -31 +equ trap_S_RegisterSound              -32 +equ trap_S_StartLocalSound            -33 +equ trap_Key_KeynumToStringBuf        -34 +equ trap_Key_GetBindingBuf            -35 +equ trap_Key_SetBinding               -36 +equ trap_Key_IsDown                   -37 +equ trap_Key_GetOverstrikeMode        -38 +equ trap_Key_SetOverstrikeMode        -39 +equ trap_Key_ClearStates              -40 +equ trap_Key_GetCatcher               -41 +equ trap_Key_SetCatcher               -42         +equ trap_GetClipboardData             -43 +equ trap_GetGlconfig                  -44 +equ trap_GetClientState               -45 +equ trap_GetConfigString              -46 +equ trap_LAN_GetPingQueueCount        -47 +equ trap_LAN_ClearPing                -48 +equ trap_LAN_GetPing                  -49 +equ trap_LAN_GetPingInfo              -50 +equ trap_Cvar_Register                -51 +equ trap_Cvar_Update                  -52 +equ trap_MemoryRemaining              -53 +equ trap_R_RegisterFont               -54 +equ trap_R_ModelBounds                -55 +equ trap_Parse_AddGlobalDefine        -56 +equ trap_Parse_LoadSource             -57 +equ trap_Parse_FreeSource             -58 +equ trap_Parse_ReadToken              -59 +equ trap_Parse_SourceFileAndLine      -60 +equ trap_S_StopBackgroundTrack        -61 +equ trap_S_StartBackgroundTrack       -62 +equ trap_RealTime                     -63 +equ trap_LAN_GetServerCount           -64 +equ trap_LAN_GetServerAddressString   -65 +equ trap_LAN_GetServerInfo            -66 +equ trap_LAN_MarkServerVisible        -67 +equ trap_LAN_UpdateVisiblePings       -68 +equ trap_LAN_ResetPings               -69 +equ trap_LAN_LoadCachedServers        -70 +equ trap_LAN_SaveCachedServers        -71 +equ trap_LAN_AddServer                -72 +equ trap_LAN_RemoveServer             -73 +equ trap_CIN_PlayCinematic            -74 +equ trap_CIN_StopCinematic            -75 +equ trap_CIN_RunCinematic             -76 +equ trap_CIN_DrawCinematic            -77 +equ trap_CIN_SetExtents               -78 +equ trap_R_RemapShader                -79 +equ trap_LAN_ServerStatus             -80 +equ trap_LAN_GetServerPing            -81 +equ trap_LAN_ServerIsVisible          -82 +equ trap_LAN_CompareServers           -83 +equ trap_FS_Seek                      -84 +equ trap_SetPbClStatus                -85 + +equ memset                            -101 +equ memcpy                            -102 +equ strncpy                           -103 +equ sin                               -104 +equ cos                               -105 +equ atan2                             -106 +equ sqrt                              -107 +equ floor                             -108 +equ ceil                              -109 + diff --git a/src/ui/ui_syscalls.c b/src/ui/ui_syscalls.c new file mode 100644 index 0000000..528658e --- /dev/null +++ b/src/ui/ui_syscalls.c @@ -0,0 +1,387 @@ +/* +=========================================================================== +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 "ui_local.h" + +// this file is only included when building a dll +// syscalls.asm is included instead when building a qvm + +static intptr_t (QDECL *syscall)( intptr_t arg, ... ) = (intptr_t (QDECL *)( intptr_t, ...))-1; + +void dllEntry( intptr_t (QDECL *syscallptr)( intptr_t arg,... ) ) { +  syscall = syscallptr; +} + +int PASSFLOAT( float x ) { +  float  floatTemp; +  floatTemp = x; +  return *(int *)&floatTemp; +} + +void trap_Print( const char *string ) { +  syscall( UI_PRINT, string ); +} + +void trap_Error( const char *string ) { +  syscall( UI_ERROR, string ); +} + +int trap_Milliseconds( void ) { +  return syscall( UI_MILLISECONDS ); +} + +void trap_Cvar_Register( vmCvar_t *cvar, const char *var_name, const char *value, int flags ) { +  syscall( UI_CVAR_REGISTER, cvar, var_name, value, flags ); +} + +void trap_Cvar_Update( vmCvar_t *cvar ) { +  syscall( UI_CVAR_UPDATE, cvar ); +} + +void trap_Cvar_Set( const char *var_name, const char *value ) { +  syscall( UI_CVAR_SET, var_name, value ); +} + +float trap_Cvar_VariableValue( const char *var_name ) { +  int temp; +  temp = syscall( UI_CVAR_VARIABLEVALUE, var_name ); +  return (*(float*)&temp); +} + +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) { +  syscall( UI_CVAR_VARIABLESTRINGBUFFER, var_name, buffer, bufsize ); +} + +void trap_Cvar_SetValue( const char *var_name, float value ) { +  syscall( UI_CVAR_SETVALUE, var_name, PASSFLOAT( value ) ); +} + +void trap_Cvar_Reset( const char *name ) { +  syscall( UI_CVAR_RESET, name ); +} + +void trap_Cvar_Create( const char *var_name, const char *var_value, int flags ) { +  syscall( UI_CVAR_CREATE, var_name, var_value, flags ); +} + +void trap_Cvar_InfoStringBuffer( int bit, char *buffer, int bufsize ) { +  syscall( UI_CVAR_INFOSTRINGBUFFER, bit, buffer, bufsize ); +} + +int trap_Argc( void ) { +  return syscall( UI_ARGC ); +} + +void trap_Argv( int n, char *buffer, int bufferLength ) { +  syscall( UI_ARGV, n, buffer, bufferLength ); +} + +void trap_Cmd_ExecuteText( int exec_when, const char *text ) { +  syscall( UI_CMD_EXECUTETEXT, exec_when, text ); +} + +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ) { +  return syscall( UI_FS_FOPENFILE, qpath, f, mode ); +} + +void trap_FS_Read( void *buffer, int len, fileHandle_t f ) { +  syscall( UI_FS_READ, buffer, len, f ); +} + +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ) { +  syscall( UI_FS_WRITE, buffer, len, f ); +} + +void trap_FS_FCloseFile( fileHandle_t f ) { +  syscall( UI_FS_FCLOSEFILE, f ); +} + +int trap_FS_GetFileList(  const char *path, const char *extension, char *listbuf, int bufsize ) { +  return syscall( UI_FS_GETFILELIST, path, extension, listbuf, bufsize ); +} + +int trap_FS_Seek( fileHandle_t f, long offset, int origin ) { +    return syscall( UI_FS_SEEK, f, offset, origin ); +} + +qhandle_t trap_R_RegisterModel( const char *name ) { +  return syscall( UI_R_REGISTERMODEL, name ); +} + +qhandle_t trap_R_RegisterSkin( const char *name ) { +  return syscall( UI_R_REGISTERSKIN, name ); +} + +void trap_R_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font) { +  syscall( UI_R_REGISTERFONT, fontName, pointSize, font ); +} + +qhandle_t trap_R_RegisterShaderNoMip( const char *name ) { +  return syscall( UI_R_REGISTERSHADERNOMIP, name ); +} + +void trap_R_ClearScene( void ) { +  syscall( UI_R_CLEARSCENE ); +} + +void trap_R_AddRefEntityToScene( const refEntity_t *re ) { +  syscall( UI_R_ADDREFENTITYTOSCENE, re ); +} + +void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ) { +  syscall( UI_R_ADDPOLYTOSCENE, hShader, numVerts, verts ); +} + +void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { +  syscall( UI_R_ADDLIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) ); +} + +void trap_R_RenderScene( const refdef_t *fd ) { +  syscall( UI_R_RENDERSCENE, fd ); +} + +void trap_R_SetColor( const float *rgba ) { +  syscall( UI_R_SETCOLOR, rgba ); +} + +void trap_R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ) { +  syscall( UI_R_DRAWSTRETCHPIC, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(w), PASSFLOAT(h), PASSFLOAT(s1), PASSFLOAT(t1), PASSFLOAT(s2), PASSFLOAT(t2), hShader ); +} + +void  trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) { +  syscall( UI_R_MODELBOUNDS, model, mins, maxs ); +} + +void trap_UpdateScreen( void ) { +  syscall( UI_UPDATESCREEN ); +} + +int trap_CM_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, float frac, const char *tagName ) { +  return syscall( UI_CM_LERPTAG, tag, mod, startFrame, endFrame, PASSFLOAT(frac), tagName ); +} + +void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ) { +  syscall( UI_S_STARTLOCALSOUND, sfx, channelNum ); +} + +sfxHandle_t  trap_S_RegisterSound( const char *sample, qboolean compressed ) { +  return syscall( UI_S_REGISTERSOUND, sample, compressed ); +} + +void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen ) { +  syscall( UI_KEY_KEYNUMTOSTRINGBUF, keynum, buf, buflen ); +} + +void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen ) { +  syscall( UI_KEY_GETBINDINGBUF, keynum, buf, buflen ); +} + +void trap_Key_SetBinding( int keynum, const char *binding ) { +  syscall( UI_KEY_SETBINDING, keynum, binding ); +} + +qboolean trap_Key_IsDown( int keynum ) { +  return syscall( UI_KEY_ISDOWN, keynum ); +} + +qboolean trap_Key_GetOverstrikeMode( void ) { +  return syscall( UI_KEY_GETOVERSTRIKEMODE ); +} + +void trap_Key_SetOverstrikeMode( qboolean state ) { +  syscall( UI_KEY_SETOVERSTRIKEMODE, state ); +} + +void trap_Key_ClearStates( void ) { +  syscall( UI_KEY_CLEARSTATES ); +} + +int trap_Key_GetCatcher( void ) { +  return syscall( UI_KEY_GETCATCHER ); +} + +void trap_Key_SetCatcher( int catcher ) { +  syscall( UI_KEY_SETCATCHER, catcher ); +} + +void trap_GetClipboardData( char *buf, int bufsize ) { +  syscall( UI_GETCLIPBOARDDATA, buf, bufsize ); +} + +void trap_GetClientState( uiClientState_t *state ) { +  syscall( UI_GETCLIENTSTATE, state ); +} + +void trap_GetGlconfig( glconfig_t *glconfig ) { +  syscall( UI_GETGLCONFIG, glconfig ); +} + +int trap_GetConfigString( int index, char* buff, int buffsize ) { +  return syscall( UI_GETCONFIGSTRING, index, buff, buffsize ); +} + +int  trap_LAN_GetServerCount( int source ) { +  return syscall( UI_LAN_GETSERVERCOUNT, source ); +} + +void trap_LAN_GetServerAddressString( int source, int n, char *buf, int buflen ) { +  syscall( UI_LAN_GETSERVERADDRESSSTRING, source, n, buf, buflen ); +} + +void trap_LAN_GetServerInfo( int source, int n, char *buf, int buflen ) { +  syscall( UI_LAN_GETSERVERINFO, source, n, buf, buflen ); +} + +int trap_LAN_GetServerPing( int source, int n ) { +  return syscall( UI_LAN_GETSERVERPING, source, n ); +} + +int trap_LAN_GetPingQueueCount( void ) { +  return syscall( UI_LAN_GETPINGQUEUECOUNT ); +} + +int trap_LAN_ServerStatus( const char *serverAddress, char *serverStatus, int maxLen ) { +  return syscall( UI_LAN_SERVERSTATUS, serverAddress, serverStatus, maxLen ); +} + +void trap_LAN_SaveCachedServers( void ) { +  syscall( UI_LAN_SAVECACHEDSERVERS ); +} + +void trap_LAN_LoadCachedServers( void ) { +  syscall( UI_LAN_LOADCACHEDSERVERS ); +} + +void trap_LAN_ResetPings(int n) { +  syscall( UI_LAN_RESETPINGS, n ); +} + +void trap_LAN_ClearPing( int n ) { +  syscall( UI_LAN_CLEARPING, n ); +} + +void trap_LAN_GetPing( int n, char *buf, int buflen, int *pingtime ) { +  syscall( UI_LAN_GETPING, n, buf, buflen, pingtime ); +} + +void trap_LAN_GetPingInfo( int n, char *buf, int buflen ) { +  syscall( UI_LAN_GETPINGINFO, n, buf, buflen ); +} + +void trap_LAN_MarkServerVisible( int source, int n, qboolean visible ) { +  syscall( UI_LAN_MARKSERVERVISIBLE, source, n, visible ); +} + +int trap_LAN_ServerIsVisible( int source, int n) { +  return syscall( UI_LAN_SERVERISVISIBLE, source, n ); +} + +qboolean trap_LAN_UpdateVisiblePings( int source ) { +  return syscall( UI_LAN_UPDATEVISIBLEPINGS, source ); +} + +int trap_LAN_AddServer(int source, const char *name, const char *addr) { +  return syscall( UI_LAN_ADDSERVER, source, name, addr ); +} + +void trap_LAN_RemoveServer(int source, const char *addr) { +  syscall( UI_LAN_REMOVESERVER, source, addr ); +} + +int trap_LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int s2 ) { +  return syscall( UI_LAN_COMPARESERVERS, source, sortKey, sortDir, s1, s2 ); +} + +int trap_MemoryRemaining( void ) { +  return syscall( UI_MEMORY_REMAINING ); +} + +int trap_Parse_AddGlobalDefine( char *define ) { +  return syscall( UI_PARSE_ADD_GLOBAL_DEFINE, define ); +} + +int trap_Parse_LoadSource( const char *filename ) { +  return syscall( UI_PARSE_LOAD_SOURCE, filename ); +} + +int trap_Parse_FreeSource( int handle ) { +  return syscall( UI_PARSE_FREE_SOURCE, handle ); +} + +int trap_Parse_ReadToken( int handle, pc_token_t *pc_token ) { +  return syscall( UI_PARSE_READ_TOKEN, handle, pc_token ); +} + +int trap_Parse_SourceFileAndLine( int handle, char *filename, int *line ) { +  return syscall( UI_PARSE_SOURCE_FILE_AND_LINE, handle, filename, line ); +} + +void trap_S_StopBackgroundTrack( void ) { +  syscall( UI_S_STOPBACKGROUNDTRACK ); +} + +void trap_S_StartBackgroundTrack( const char *intro, const char *loop) { +  syscall( UI_S_STARTBACKGROUNDTRACK, intro, loop ); +} + +int trap_RealTime(qtime_t *qtime) { +  return syscall( UI_REAL_TIME, qtime ); +} + +// this returns a handle.  arg0 is the name in the format "idlogo.roq", set arg1 to NULL, alteredstates to qfalse (do not alter gamestate) +int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits) { +  return syscall(UI_CIN_PLAYCINEMATIC, arg0, xpos, ypos, width, height, bits); +} + +// stops playing the cinematic and ends it.  should always return FMV_EOF +// cinematics must be stopped in reverse order of when they are started +e_status trap_CIN_StopCinematic(int handle) { +  return syscall(UI_CIN_STOPCINEMATIC, handle); +} + + +// will run a frame of the cinematic but will not draw it.  Will return FMV_EOF if the end of the cinematic has been reached. +e_status trap_CIN_RunCinematic (int handle) { +  return syscall(UI_CIN_RUNCINEMATIC, handle); +} + + +// draws the current frame +void trap_CIN_DrawCinematic (int handle) { +  syscall(UI_CIN_DRAWCINEMATIC, handle); +} + + +// allows you to resize the animation dynamically +void trap_CIN_SetExtents (int handle, int x, int y, int w, int h) { +  syscall(UI_CIN_SETEXTENTS, handle, x, y, w, h); +} + + +void  trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ) { +  syscall( UI_R_REMAP_SHADER, oldShader, newShader, timeOffset ); +} + +void trap_SetPbClStatus( int status ) { +    syscall( UI_SET_PBCLSTATUS, status ); +}  | 
